Day 5: Perceptrons in Python from Scratch. (Code)

Perceptron implementation in Python from scratch using PyTorch and SkLearn.

By Nandeshwar

Jun 05, 2021

Day 5.png


Perceptrons are building blocks of Neural Networks. In this blog post, we will do an implementation of classic Rosenblatt Perceptron from scratch in PyTorch. If you need a refresher then go through this article to get an idea about the maths behind this.

I chose to use PyTorch instead of NumPy because it is widely used in the deep learning community. All the blog posts in the 100 Days of Deep Learning project will be mostly covered using PyTorch only.

Wanna jump right to code, check out complete code on Github.

Perceptron code implementation in Python using PyTorch.

The very first thing we need to create a Perceptron implementation is a dataset. We use the amazing Scikit Learn library to create a custom dataset.

Do bear in mind that Perceptron can only do binary classification that is why create a dataset for classification.

We create a dataset of 100 features of two different types (100 - 100 each). We split the dataset into a 70/30 ratio for training and testing.

X, y = datasets.make_blobs(n_samples=100, n_features=2, centers=2, cluster_std=1.05, random_state=6)
X = torch.from_numpy(X)
y = torch.from_numpy(y)

X_train, X_test = X[:70], X[70:]
y_train, y_test = y[:70], y[70:]

# Normalize (mean zero, unit variance)
mu, sigma = X_train.mean(axis=0), X_train.std(axis=0)
X_train = (X_train - mu) / sigma
X_test = (X_test - mu) / sigma

fig = plt.figure(figsize=(10, 8))

plt.plot(X_train[:, 0][y_train == 0], X_train[:, 1][y_train == 0], 'r^')
plt.plot(X_train[:, 0][y_train == 1], X_train[:, 1][y_train == 1], 'bs')
plt.xlabel("feature_1")
plt.ylabel("feature_2")
plt.title('Binary data')


day5-fig1.png

You can see from the above visualization of training data, we have two different classes.

print('Feature count', torch.bincount(y))
print('X shape:', X.shape)
print('y shape:', y.shape)

#Output 
Feature count tensor([50, 50])
X shape: torch.Size([100, 2])
y shape: torch.Size([100])

Perceptron Model class

Next, we define a perceptron model. We define 4 main methods in this class namely

  • forward
  • backward
  • train
  • evaluate
def custom_where(cond, x_1, x_2):
    return (cond * x_1) + (~(cond) * x_2)

class Perceptron():
    def __init__(self, num_features: int):
        self.num_features = 2
        self.weights = torch.zeros(num_features, 1, dtype=torch.float32)
        self.bias = torch.zeros(1, dtype=torch.float32)
    
    def forward(self, x):
        linear = torch.add(torch.mm(x, self.weights), self.bias)
        predictions = custom_where(linear > 0., 1, 0).float()
        return predictions
        
    def backward(self, x, y):
        predictions = self.forward(x)
        errors = y - predictions
        return errors
    
    def train(self, x, y, epochs):
        for e in range(epochs):
            for i in range(y.size()[0]):
                errors = self.backward(x[i].view(1, self.num_features), y[i]).view(-1)
                self.weights += (errors * x[i]).view(self.num_features, 1)
                self.bias = errors
                    
    def evaluate(self, x, y):
        predictions = self.forward(x).view(-1)
        accuracy = torch.sum(predictions == y).float() / y.size()[0]
        return accuracy

Once we have declared out model we run the code by creating a object of Perceptron class. Let's see what it gives out in terms of Weights and Bias.

ppn = Perceptron(num_features=2)

X_train_tensor = X_train.clone().detach().type(torch.FloatTensor).to('cpu')
y_train_tensor = y_train.clone().detach().type(torch.FloatTensor).to('cpu')

ppn.train(X_train_tensor, y_train_tensor, epochs=5)

print('Model parameters:')
print('  Weights: %s' % ppn.weights)
print('  Bias: %s' % ppn.bias)

# Output
Model parameters:
  Weights: tensor([[-0.2096],
        [-2.2456]])
  Bias: tensor([0.])

Next we check the accuracy of the model on test dataset.

X_test_tensor = X_test.clone().detach().type(torch.FloatTensor).to('cpu')
y_test_tensor = y_test.clone().detach().type(torch.FloatTensor).to('cpu')

test_acc = ppn.evaluate(X_test_tensor, y_test_tensor)
print('Test set accuracy: %.2f%%' % (test_acc*100))

# Output
Test set accuracy: 100.00%

Great job!

Our model has achieved 100% accuracy on our dataset. This means from the complete dataset even on the test_dataset which we have not used to train the Perceptron model has achieved 100% accuracy.

Visualization

Let us visualize this to view how our model is classifying the input.

##########################
### 2D Decision Boundary
##########################

w, b = ppn.weights, ppn.bias

x_min = -2
y_min = ( (-(w[0] * x_min) - b[0]) 
          / w[1] )

x_max = 2
y_max = ( (-(w[0] * x_max) - b[0]) 
          / w[1] )


fig, ax = plt.subplots(1, 2, sharex=True, figsize=(9, 4))

ax[0].plot([x_min, x_max], [y_min, y_max])
ax[0].title.set_text('Training dataset [70%]')
ax[1].plot([x_min, x_max], [y_min, y_max])
ax[1].title.set_text('Test dataset [30%]')

ax[0].scatter(X_train[y_train==0, 0], X_train[y_train==0, 1], label='class 0', marker='o')
ax[0].scatter(X_train[y_train==1, 0], X_train[y_train==1, 1], label='class 1', marker='s')

ax[1].scatter(X_test[y_test==0, 0], X_test[y_test==0, 1], label='class 0', marker='o')
ax[1].scatter(X_test[y_test==1, 0], X_test[y_test==1, 1], label='class 1', marker='s')

ax[1].legend(loc='upper left')
plt.show()

day5-fig2.png

The visualizations are for training data and testing data respectively. You can view from the visualization above that model is making a clear distinction between both the classes. This shows that 100% accuracy achieved by the model is indeed right.

Conclusion

We created a very basic Perceptron model from scratch and achieved 100% accuracy on our dataset. I suggest running the code on your machine by yourself and tinker with the dataset and model to see how it performs in different cases.

Get the complete code on GitHub.

References


Tags

Code
Deep Learning
Beginner