import numpy as np
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from IPython.display import clear_output
import sklearn.datasets as datasets
fig, ax = plt.subplots()
# This is the simplest way to create a plot with Pyplot. The 'fig' object represents the entire figure you will
# create, and the 'ax' object represents the axes you will plot on. By passing a value or tuple to subplots(),
# you can create multiple axes or a grid of axes that all belong to the same figure. Here, we just create
# one set of axes by leaving the argument blank.
#Let's generate some data to plot:
x = np.linspace(0,10,100)
y = (x-3)**3
z = np.log(x)
#The plot function plots the data on the axes as a graph
ax.plot(x,y)
ax.plot(x,z)
#We also ought to adorn our axes with the appropriate titles and legends:
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_title('A Simple Function')
ax.legend(['The Simple Function', 'Dmitri\'s favorite function']) # Legends take a list of labels, in case you've plotted more than one curve on the axes
#Though it's not necessary here, we can also adjust the axis limits:
#ax.set_xlim(-10,10)
#ax.set_ylim(-0,100)
#When the plot is ready, use fig.show()
fig.show()
#If necessary, you can easily save a figure you've generated with plt.savefig()
# You can also plot on 3D axes with Pyplot
fig = plt.figure() # We can't pass the projection argument directly to subplot(), so we generate the axes in two steps
ax = fig.add_subplot(projection = '3d')
# To plot a surface, create a 'meshgrid' of points in the x,y plane:
x = np.linspace(0,10,100)
y = np.linspace(0,10,100)
X,Y = np.meshgrid(x,y)
# You can then evaluate a 2D function on the meshgrid coordinates, then plot the surface
Z = np.sin(X)+np.cos(Y)
ax.plot_surface(X,Y,Z, cmap = 'coolwarm') # Trying out different colormaps is fun!
# We can also add axis labels and such:
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('My Lumpy Function')
# For surfaces, it isn't trivial to set up a legend, but you can do so easily for scatter plots and such
ax.set_zlim(-1.1,1.1)
ax.view_init(20,30) # This sets the perspective angle from which we view the graph'''
#Defining a PyTorch Tensor
x = torch.tensor([1.0, 2.0, 3.0])
print(x)
print(x.device)
print(x.type())
#If we had a CUDA capable GPU, we could define our tensors on a GPU
# x = torcbh.tensor([1.0, 2.0, 3.0], device="cuda:0")
#PyTorch recycles a lot of the syntax from NumPy so we can carry out our familiar NumPy operations on PyTorch tensors
x = torch.tensor([1.0, 2.0, 3.0])
x += 5
print(x)
print(x.size())
x = torch.tensor(([1, 2], [3, 4]))
print(x.T)
y = torch.tensor(([1, 3], [5, 6]))
print(x @ y) #What does this do?
print(x * y) #How is this different from above?
x = torch.ones((6))
x.unsqueeze(0)
print(x)
x = x.unsqueeze(0)
print(x)
print(x.shape)
# Let's create some x values to input into a function:
inputs = torch.tensor([1.,2.,3.,4.]) # Decimals to make this a float tensor (can't calculate gradients for ints)
print("Our input tensor is:")
print(inputs)
# Currently, this tensor behaves just like a Numpy array. The magic occurs when we ask PyTorch to start
# tracking this tensor's computational relationship to other tensors we might create:
inputs.requires_grad = True
# We can now look at the tensor's grad attribute, which should currently be None. This is because we have not
# specified some scalar tensor for which we can calculate a gradient
print("The gradient attribute of our input tensor is:")
print(inputs.grad)
# Now, let's use our input tensor to construct a scalar output:
output = torch.sum(torch.log(inputs)) # torch.log and np.log both refer to the natural logarithm
print("Our scalar output is:")
print(output)
# Next, we can ask PyTorch to populate the grad attribute of our input tensor by calling backward() on
# the output scalar. This will automatically calculate the gradient of the output scalar with respect to
# the input tensor and populate input.grad with those values
# PyTorch knows how to do this because it has kept track of a computational graph relating inputs to output
# under the hood.
print("Backpropagating the gradient...")
output.backward()
print("The gradient of output with respect to inputs is:")
print(inputs.grad)
# Verify with your basic calculus skills that this is the appropriate gradient!
# Pretty cool how we don't have to manage any of the details of the calculation manually; PyTorch creates a
# computational graph once we set requires_grad to True and keep track of all of the computations under the hood,
# backpropagating through those computations once we call backward().
# Create function for f(x) = x**2
def f(x):
return x**2
# Create 100 points from -1 to 1
x = torch.linspace(-1, 1, 100, requires_grad=True)
# Y = sigma f(x) for all x
y = torch.sum(f(x))
# Tell torch to take the gradients through backward propagation
y.backward(torch.ones_like(y))
# Create plot
fig, ax = plt.subplots()
X, Y = [i.detach().numpy() for i in [x, y]]
ax.plot(X, f(X))
ax.plot(X, x.grad.detach().numpy())
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.legend(['x**2', 'd/dx x**2'])
ax.set_title('Gradient')
fig.show()
# Generate some linearly separable data
np.random.seed(3) # Set a fixed RNG seed so everyone's data looks the same
n_data = 100 # How many data points we want in each cluster
a_data = np.random.multivariate_normal([1.0,1.0], 0.2*np.eye(2), [n_data]) # Generate the data
b_data = np.random.multivariate_normal([-1.0,-1.0], 0.2*np.eye(2), [n_data])
# Plot the data (using Pyplot's scatter function)
fig, ax = plt.subplots()
ax.scatter(a_data[:,0], a_data[:,1], color = 'blue')
ax.scatter(b_data[:,0], b_data[:,1], color = 'red')
ax.set_title("Linearly Separable Data")
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.show()
# First, let's concatenate our data into one big matrix:
data = np.concatenate((a_data, b_data), axis = 0)
print(np.shape(data))
# We've become familiar with the normal equations to solve the problem Xw = y, where X is our data matrix,
# w is a vector containing our parameters A, B, C, and y is a vector of target values.
# Firstly, what should our target values be? A simple choice is to set the target value of class A to 0, and class B to 1.
# In practice, we could set these targets to any two distinct values and choose the classifying isosurface as a
# value between the two.
targets = np.concatenate((np.zeros(n_data), np.ones(n_data)), axis = 0)
# Secondly, how do we capture the behavior of our scalar parameter C? Our data X is of size (200,2), but we need to
# have 3 parameters w0, w1, w2 = A, B, C. A simple way to do this is to add a column of ones to our data matrix X;
# then, the matrix multiplication Xw will create a vector y populated with values y = w0*x0 + w1*x1 + w2, which is
# exactly what we want!
augmented_data = np.concatenate((data, np.ones([2*n_data,1])), axis = 1)
print(augmented_data)
print(np.shape(augmented_data))
# inv(x^T x) x^T t
parameters = np.linalg.inv(augmented_data.transpose() @ augmented_data) @ augmented_data.transpose() @ targets
# Create separating line
# Classification graph goes from ~-2.5 to ~2.5, so make ~100 points from those bounds
x = np.linspace(-2.5, 2.5, 100)
y = np.linspace(-2.5, 2.5, 100)
X, Y = np.meshgrid(x, y) # Cartesion product of x and Y
# Get Z component for 3D plot later
Z = parameters @ np.array([X, Y, 1], dtype=object) # Create [wX + wY + w] (100x100) !Warning says must include dtype=object
# Create 2D plot
fig, ax = plt.subplots()
ax.scatter(a_data[:,0], a_data[:,1], color='blue') # (col 1 (xs), col 2 (ys), color)
ax.scatter(b_data[:,0], b_data[:,1], color='red')
# Since X, Y, and Z data, need to project 3D surface on 2D plane using contour
ax.contour(X, Y, Z, levels=[0.5]) # Played with levels, why 0.5?
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_title('Linearly Separated Data')
ax.legend(['A', 'B'])
fig.show()
# Make 3D plot
fig, ax = plt.subplots()
ax = fig.add_subplot(projection ='3d')
ax.scatter(a_data[:,0], a_data[:,1], 0, color='blue') # Have to add z component to get alignment in plane
ax.scatter(b_data[:,0], b_data[:,1], 1, color='red')
ax.plot_surface(X, Y, Z, cmap='coolwarm')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Linearly Separated Data, 3D')
ax.legend(['A', 'B'])
ax.view_init(25, 140)
fig.show()
# Find the accuracy
# Class 0 where f > 0.5, Class 1 where f <= 0.5
augmentedAData = augmented_data[:100]
aAboveHalf = np.where((augmentedAData @ parameters) <= 0.5)[0]
# Last line returns tuple of (array, nothing). Used [0] to get array
# Use .shape to get the number of occurences for probability
num_aAboveHalf = aAboveHalf.shape[0]
# Do the same with b_data
augmentedBData = augmented_data[100:]
bAboveHalf = np.where((augmentedBData @ parameters) > 0.5)[0]
num_bAboveHalf = bAboveHalf.shape[0]
# Sum and divide by number of points to give accuracy
accuracy = (num_aAboveHalf + num_bAboveHalf) / (2*n_data)
print(f"Final parameters: {parameters}")
print(f"Accuracy: {accuracy * 100}%")
# Let's see an example of how this might work
torch.manual_seed(3)
# Let's create a bunch of data - in this case, noisy linear data
x = torch.rand(100)*10
y = 2*x + 3 + torch.randn(100)*1 # Noisy data
# We want to find the line of best fit; i.e. parameters (theta) = [m, b] that minimizes the MSE loss between
# y and mx + b. Intuitively, our parameters should be close to m = 2 and b = 3.
# Let's start with the initial guess (theta) = [0,0]
theta = torch.tensor([0., 0.], requires_grad = True) # Initial parameter guess
# Let's apply our gradient descent algorithm:
alpha = 1e-2 # Learning Rate
losses = [] # Keep track of losses
for step in range(1000):
predictions = theta[0]*x + theta[1] # Make a prediction
loss = torch.mean(torch.square(predictions - y)) # Compute Loss
losses.append(loss.detach().numpy())
loss.backward() # Backpropagate to calculate gradients
theta.data = theta.data - alpha*theta.grad.data # Update parameters (have to use .data here for complex reasons...)
theta.grad.data.zero_() # Zero out the gradient for the next step
# Now just graph the data and report
fig, ax = plt.subplots()
x = x.detach().numpy()
y = y.detach().numpy()
theta = theta.detach().numpy()
x_grid = np.linspace(0, 10, 100)
print(f"Our final parameters are: {theta}")
ax.scatter(x,y)
ax.plot(x_grid, theta[0]*x_grid + theta[1], color = 'red')
ax.legend(['Data', 'Line of Best Fit'])
ax.set_title('Gradient Descent')
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.show()
# Plot the decline of the loss function
fig, ax = plt.subplots()
ax.plot(losses, color = 'red')
ax.set_title('Model Loss')
ax.set_xlabel('Training Epoch')
ax.set_ylabel('Loss')
fig.show()
# Note that our answer is identical!
# The answer seems reasonable! Without any analytical solution, we were able to leverage gradients to find a minimum
# in the loss function
torch.manual_seed(3)
#Create Data
x = torch.rand(100)*10
y = 2*x + 3 + torch.randn(100)*1 # Noisy data
# Now, let's create a model that subclasses torch.nn.Module:
class Best_Fit(torch.nn.Module):
def __init__(self): # Always need an init function
super().__init__() # Initializes the superclass
self.theta = torch.nn.Parameter(torch.zeros(2)) # defines our model parameters at an initial guess of [0,0]
def forward(self, x): # Defines how to generate a prediction given data input
return self.theta[0]*x + self.theta[1]
# Now create a model object and optimizer
model = Best_Fit()
optimizer = torch.optim.SGD(model.parameters(), lr = 1e-2) # We pass our model parameters and define a learning rate
losses = [] # Keep track of losses
# Now the loop looks much cleaner
for step in range(1000):
predictions = model(x) # We can just call model(x) to pass our data through the model
loss = torch.mean(torch.square(predictions - y)) # Compute Loss
losses.append(loss.detach().numpy()) # Store the loss
loss.backward() # Backpropagate to calculate gradients
optimizer.step() # Takes a step of whatever optimization scheme we've defined
optimizer.zero_grad() # Zeros the gradients without any weird syntax
# Now just graph the data and report
fig, ax = plt.subplots()
x = x.detach().numpy()
y = y.detach().numpy()
theta = model.theta.detach().numpy()
x_grid = np.linspace(0, 10, 100)
print(f"Our final parameters are: {theta}")
ax.scatter(x,y)
ax.plot(x_grid, theta[0]*x_grid + theta[1], color = 'red')
ax.legend(['Data', 'Line of Best Fit'])
ax.set_title('Gradient Descent')
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.show()
fig, ax = plt.subplots()
ax.plot(losses, color = 'red')
ax.set_title('Model Loss')
ax.set_xlabel('Training Epoch')
ax.set_ylabel('Loss')
fig.show()
# Note that our answer is identical!
# Referencing https://pytorch.org/tutorials/beginner/introyt/autogradyt_tutorial.html
# Create regression model
class Regression(torch.nn.Module):
def __init__(self):
super().__init__()
self.weights = torch.nn.Parameter(torch.ones(3)) # Can't call 'parameters,' Module has method of same name
def forward(self, x):
predicted = torch.cat((x, torch.ones(len(x), 1)), axis=1) @ self.weights # Multiply augmented data and weights for prediction
return predicted
model = Regression()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001) # Need weights to be of type torn.nn.Parameter(tensor)
# Fix data to be in torch setting using data and targets from last exercise
data = torch.Tensor(data)
targets = torch.Tensor(targets)
# Begin looping for optimization
losses = list() # Keep track of losses for a plot
for i in range(100):
prediction = model.forward(data)
loss = ((prediction - targets)**2).sum() # Sum of (predicted - true)**2
losses.append(loss.detach().numpy())
loss.backward()
optimizer.step()
optimizer.zero_grad()
# Retrieve optimized parameters
optParams = model.weights.detach().numpy()
# Create separating line
x = np.linspace(-2.5, 2.5, 100)
y = np.linspace(-2.5, 2.5, 100)
X, Y = np.meshgrid(x, y)
Z = optParams @ np.array([X, Y, 1], dtype=object)
# Create loss figure
fig, ax = plt.subplots()
ax.plot(losses)
ax.set_xlabel('Trial')
ax.set_ylabel('Sum of Squares Loss')
ax.set_title('Sum of Squares Loss While Training')
fig.show()
# Create 2D plot
fig, ax = plt.subplots()
ax.scatter(a_data[:,0], a_data[:,1], color='blue')
ax.scatter(b_data[:,0], b_data[:,1], color='red')
ax.contour(X, Y, Z, levels=[0.5])
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_title('Linearly Separated Data Using PyTorch')
ax.legend(['A', 'B'])
fig.show()
# Make 3D plot
fig, ax = plt.subplots()
ax = fig.add_subplot(projection ='3d')
ax.scatter(a_data[:,0], a_data[:,1], 0, color='blue')
ax.scatter(b_data[:,0], b_data[:,1], 1, color='red')
ax.plot_surface(X, Y, Z, cmap='coolwarm')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Linearly Separated Data Using PyTorch, 3D')
ax.legend(['A', 'B'])
ax.view_init(25, 140)
fig.show()
# Find the accuracy using model
predictions = model.forward(data).detach().numpy()
predictedA = predictions[:100]
aAboveHalf = np.where(predictedA <= 0.5)[0]
num_aAboveHalf = aAboveHalf.shape[0]
predictedB = predictions[100:]
bAboveHalf = np.where(predictedB > 0.5)[0]
num_bAboveHalf = bAboveHalf.shape[0]
accuracy = (num_aAboveHalf + num_bAboveHalf) / (2*n_data)
print(f"Final parameters: {optParams}")
print(f"Accuracy: {accuracy * 100}%")
The two results look very similar, with identical classification accuracies of 100% and parameters that are quite close (but not the same).
# Generate Two-Moons data
np.random.seed(4) # Set a fixed RNG seed so everyone's data looks the same
n_data = 100 # How many data points we want in each cluster
data, targets = datasets.make_moons((n_data,n_data), shuffle = False, noise = 0.05) # Create the data
b_data = data[:n_data] # Label the data
a_data = data[n_data:]
fig, ax = plt.subplots() # Plot the data
ax.scatter(a_data[:,0], a_data[:,1], color = 'blue')
ax.scatter(b_data[:,0], b_data[:,1], color = 'red')
ax.set_title("Data that is NOT Linearly Separable")
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.show()
data = np.concatenate((a_data, b_data), axis=0)
augmented = np.concatenate((data, np.ones([2*n_data, 1])), axis=1)
targets = np.concatenate((np.zeros(n_data), np.ones(n_data)), axis=0)
parameters = np.linalg.inv(augmented.transpose() @ augmented) @ augmented.transpose() @ targets
# Create separating line
x = np.linspace(-1.25, 2.1, 100)
y = np.linspace(-0.6, 1.2, 100)
X, Y = np.meshgrid(x, y)
Z = parameters @ np.array([X, Y, 1], dtype=object)
# Create 2D plot
fig, ax = plt.subplots()
ax.scatter(a_data[:,0], a_data[:,1], color='blue')
ax.scatter(b_data[:,0], b_data[:,1], color='red')
ax.contour(X, Y, Z, levels=[0.5])
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_title('Nonlinearly Separable Data with Linear Regression')
ax.legend(['A', 'B'])
fig.show()
# Make 3D plot
fig, ax = plt.subplots()
ax = fig.add_subplot(projection ='3d')
ax.scatter(a_data[:,0], a_data[:,1], 0, color='blue')
ax.scatter(b_data[:,0], b_data[:,1], 1, color='red')
ax.plot_surface(X, Y, Z, cmap='coolwarm')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Nonlinearly Separable Data with Linear Regression, 3D')
ax.legend(['A', 'B'])
ax.view_init(15, 10)
fig.show()
# Find the accuracy
augmentedAData = augmented[:100]
aAboveHalf = np.where((augmented[:100] @ parameters) <= 0.5)[0]
num_aAboveHalf = aAboveHalf.shape[0]
augmentedBData = augmented[100:]
bAboveHalf = np.where((augmentedBData @ parameters) > 0.5)[0]
num_bAboveHalf = bAboveHalf.shape[0]
# Sum and divide by number of points to give accuracy???
accuracy = (num_aAboveHalf + num_bAboveHalf) / (2*n_data)
print(f"Final parameters: {parameters}")
print(f"Accuracy: {accuracy * 100}%")
model = Regression()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001) # Need weights to be of type torn.nn.Parameter(tensor)
# Fix data to be in torch setting using data and targets from last exercise
data = torch.Tensor(data)
targets = torch.Tensor(targets)
# Begin looping for optimization
losses = list() # Keep track of losses for a plot
for i in range(100):
prediction = model.forward(data)
loss = ((prediction - targets)**2).sum() # Sum of (predicted - true)**2
losses.append(loss.detach().numpy())
loss.backward()
optimizer.step()
optimizer.zero_grad()
# Retrieve optimized parameters
optParams = model.weights.detach().numpy()
# Create separating line
x = np.linspace(-1.25, 2.1, 100)
y = np.linspace(-0.6, 1.2, 100)
X, Y = np.meshgrid(x, y)
Z = optParams @ np.array([X, Y, 1], dtype=object)
# Create loss figure
fig, ax = plt.subplots()
ax.plot(losses)
ax.set_xlabel('Trial')
ax.set_ylabel('Sum of Squares Loss')
ax.set_title('Sum of Squares Loss While Training')
fig.show()
# Create 2D plot
fig, ax = plt.subplots()
ax.scatter(a_data[:,0], a_data[:,1], color='blue')
ax.scatter(b_data[:,0], b_data[:,1], color='red')
ax.contour(X, Y, Z, levels=[0.5])
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_title('Nonlinearly Separated Data with Linear Regression Using PyTorch')
ax.legend(['A', 'B'])
fig.show()
# Make 3D plot
fig, ax = plt.subplots()
ax = fig.add_subplot(projection ='3d')
ax.scatter(a_data[:,0], a_data[:,1], 0, color='blue')
ax.scatter(b_data[:,0], b_data[:,1], 1, color='red')
ax.plot_surface(X, Y, Z, cmap='coolwarm')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Nonlinearly Separated Data with Linear Regression Using PyTorch, 3D')
ax.legend(['A', 'B'])
ax.view_init(15, 10)
fig.show()
# Find the accuracy using model
predictions = model.forward(data).detach().numpy()
predictedA = predictions[:100]
aAboveHalf = np.where(predictedA <= 0.5)[0]
num_aAboveHalf = aAboveHalf.shape[0]
predictedB = predictions[100:]
bAboveHalf = np.where(predictedB > 0.5)[0]
num_bAboveHalf = bAboveHalf.shape[0]
accuracy = (num_aAboveHalf + num_bAboveHalf) / (2*n_data)
print(f"Final parameters: {optParams}")
print(f"Accuracy: {accuracy * 100}%")
The classification accuracy is only 86%, which isn't great. You can see from the plots that many points are being misclassified because the two sets of data points are not linearly separable. Regardless, the classification accuracy and parameters of both the manual and machine learning approaches are roughly the same.
class Sigmoid(torch.nn.Module):
def __init__(self):
super().__init__()
self.weights = torch.nn.Parameter(torch.ones(2, 6)) # Of shape 2 (bc x, y) by order + 1 (1, x, x**2, ...)
def forward(self, x):
# Polynomial for nonlinearity???
# Each row some phi_k, s.t. phi_k(x) = x^k
def phi(x):
someTensor = torch.ones(len(x))
numTimes = 5
def expand(x):
retTensor = torch.clone(someTensor)
for k in range(numTimes):
retTensor = torch.column_stack((retTensor, x**(k+1)))
return retTensor
xs = x[:,0]
ys = x[:,1]
xRet = expand(xs)
yRet = expand(ys)
"""
print(f"xRet: {xRet.shape}")
print(f"yRet: {yRet.shape}")
print(f"Weights: {self.weights[0].shape}")
"""
return (xRet, yRet)
xs, ys = phi(x)
predicted = xs @ self.weights[0] + ys @ self.weights[1]
return torch.sigmoid(predicted)
model = Sigmoid()
optimizer = torch.optim.SGD(model.parameters(), lr=0.05)
# Fix data to be in torch setting using data and targets from last exercise
data = torch.Tensor(data)
targets = torch.Tensor(targets)
# Begin looping for optimization
losses = list() # Keep track of losses for a plot
for i in range(500):
prediction = model.forward(data)
loss = torch.nn.functional.binary_cross_entropy(prediction, targets, reduction='sum') # Doesn't work well unless reduction is sum. Why?
losses.append(loss.detach().numpy())
loss.backward()
optimizer.step()
optimizer.zero_grad()
# Create separating line
def nonlinearRegression(x, y, weights):
# Can't get to work???
"""
someArray = np.array([1])
numTimes = 3
def expand(x):
retArray = np.copy(someArray)
for k in range(numTimes):
#retArray = np.column_stack((retArray, x**(k+1)))
retArray = np.append(retArray, x**(k+1))
return retArray
xRet = expand(x)
yRet = expand(y)
"""
# Hard coded -- can't find a good way to generalize
xRet = np.array([1, x, x**2, x**3, x**4, x**5], dtype=object)
yRet = np.array([1, y, y**2, y**3, y**4, y**5], dtype=object)
"""
print(xRet)
someArray = np.array([1])
for i in range(1):
someArray = np.append(someArray, np.array(x**(i+1)))
print(someArray)
"""
"""
print(f"xRet: {xRet.shape}")
print(f"yRet: {yRet.shape}")
print(f"Weights: {weights[0].shape}")
"""
preRet = xRet @ weights[0] + yRet @ weights[1]
return (1 / (1 + np.exp((-1) * preRet)))
x = np.linspace(-1.25, 2.1, 100)
y = np.linspace(-0.6, 1.2, 100)
X, Y = np.meshgrid(x, y)
#print(X.shape)
# Retrieve optimized parameters
optParams = model.weights.detach().numpy()
Z = nonlinearRegression(X, Y, optParams)
# Create loss figure
fig, ax = plt.subplots()
ax.plot(losses)
ax.set_xlabel('Trial')
ax.set_ylabel('BCE Loss')
ax.set_title('BCE Loss While Training')
fig.show()
# Create 2D plot
fig, ax = plt.subplots()
ax.scatter(a_data[:,0], a_data[:,1], color='blue')
ax.scatter(b_data[:,0], b_data[:,1], color='red')
ax.contour(X, Y, Z, levels=[0.5])
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_title('Logistically Fitted Data Using PyTorch')
ax.legend(['A', 'B'])
fig.show()
# Make 3D plot
fig, ax = plt.subplots()
ax = fig.add_subplot(projection ='3d')
ax.scatter(a_data[:,0], a_data[:,1], 0, color='blue')
ax.scatter(b_data[:,0], b_data[:,1], 1, color='red')
ax.plot_surface(X, Y, Z, cmap='coolwarm')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Logistically Fitted Data Using PyTorch, 3D')
ax.legend(['A', 'B'])
ax.view_init(40, -30)
fig.show()
# Find the accuracy using model
predictions = model.forward(data).detach().numpy()
predictedA = predictions[:100]
aAboveHalf = np.where(predictedA <= 0.5)[0]
num_aAboveHalf = aAboveHalf.shape[0]
predictedB = predictions[100:]
bAboveHalf = np.where(predictedB > 0.5)[0]
num_bAboveHalf = bAboveHalf.shape[0]
accuracy = (num_aAboveHalf + num_bAboveHalf) / (2*n_data)
print(f"Final parameters: {optParams}")
print(f"Accuracy: {accuracy * 100}%")