# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3.6
#     language: python
#     name: python36
# ---

# !pip3 install torch

# # Model Creation

# +
import torch
import numpy as np
import matplotlib.pyplot as plt
import torch.nn as nn

from sklearn import datasets

# +
# Get linearly separable dataset, clustered around two points

n_pts = 100
centers = [[-0.5, 0.5],[0.5, -0.5]]
X, y = datasets.make_blobs(n_samples=n_pts, random_state=123,centers=centers, cluster_std=0.4)

x_data = torch.Tensor(X)
y_data = torch.Tensor(y.reshape(100,1))

print(X)
print(y)


# -

def scatter_plot():
    '''
    Print a scatterplot
    '''
    plt.scatter(X[y==0,0], X[y==0, 1]) # Where labels are 0
    plt.scatter(X[y==1,0], X[y==1, 1]) # Where labels are 1
# X and y are numpy arrays, they must be in the form of tensors.


# Plot Raw Data
scatter_plot()


class Model(nn.Module):
    '''
    Class of a perceptron for linear classifier (logistic regression)
    '''
    def __init__(self,input_size, output_size):
        super().__init__() # inherit from parent class
        self.linear = nn.Linear(input_size, output_size)
        
    def forward(self, x):
        '''
        pass forward through linear model and apply sigmoid (to convert output to probability)
        '''
        pred = torch.sigmoid(self.linear(x))
        return pred
    def predict(self, x):
        pred = self.forward(x)
        if pred >= 0.5:
            return 1
        else:
            return 0
    


# +
# Set seed, initialize model and print model parameters (random)

torch.manual_seed(2)
model = Model(2,1)
print(list(model.parameters()))

# +
[w,b] = model.parameters()
w1, w2 = w.view(2) # unpack Weights

def get_params():
    return(w1.item(), w2.item(), b[0].item())


# -

def plot_fit(title):
    plt.title = title
    # 0 = w1x1 + w2x2 + b # Equation Line
    w1, w2, b1 = get_params()
    x1 = np.array([-2.0,2.0]) # numbers here is range of data in the plot above
    x2 = (w1*x1 + b1)/-w2
    plt.plot(x1,x2, 'r')
    scatter_plot()
    


plot_fit('Initial Model')

# # Model Training

# 1. Compute error (loss) of the model based on cross entropy criterian.
#
# 2. Take the gradient of the loss function (derivative of the error).  Subtract this from the weights and it will update them in the direction of the least error.
#
# 3. Train with n number of epochs

# +
# Step 1 - Cross Entropy
criterion = nn.BCELoss()

# Step 2 - Stochastic Gradient Descent
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01)

# +
# Step 3 - Train
epochs = 2000
losses = []

for i in range(epochs):
    y_pred = model.forward(x_data)
    loss = criterion(y_pred, y_data)
    print("epoch:",i,"loss",loss.item())
    
    losses.append(loss.item())
    optimizer.zero_grad() # Set gradient to zero because they accumulate between ruins
    loss.backward() # Compute the gradient
    optimizer.step() # Update the parameters
    
# -

# plot the loss
plt.plot(range(epochs), losses)
plt.ylabel('Loss')
plt.xlabel('epoch')

# Plot trained model on data
plot_fit('Trained Model')

# # Testing

# +
# Define some testing points
point1 = torch.Tensor([1.0, -1.0])
point2 = torch.tensor([-1.0, 1.0])

# Plot the test points
plt.plot(point1.numpy()[0],point1.numpy()[1],'ro')
plt.plot(point2.numpy()[0],point2.numpy()[1],'ko')

print('Red point positive probability = {}'.format(model.forward(point1).item()))
print('Black point positive probability = {}'.format(model.forward(point2).item()))


plot_fit('Trained Model')

StarcoderdataJupyterScriptsDedupFiltered