The Perceptron Learning Algorithm

A short loop that finds separating weights from examples: add the point when a positive is missed, subtract it when a negative is wrongly accepted.

Deep Learning- Fundamentals to Advanced Concepts

Now the algorithm we have been heading toward. First, what problem does it solve?

The setting

You have mm past examples. Each has nn features and a label of 1 (positive) or 0 (negative). In the film example, each past film is a row of yes/no features, and the label says whether you liked it. Some features could also be real numbers, such as a rating.

Assume the data is linearly separable, so some line, plane or hyperplane puts all the positives on one side and all the negatives on the other. The goal is to find weights ww that do that.

Write PP for the set of positive examples and NN for the set of negative ones. Each example xx includes the constant x0=1x_0 = 1, so the bias is part of ww.

The algorithm

  1. Start with random weights ww.
  2. Repeat until converged:
    1. Pick a random example xx from P∪NP \cup N.
    2. If x∈Px \in P and w⋅x<0w \cdot x < 0, the perceptron missed a positive. Add the example: w←w+xw \leftarrow w + x.
    3. If x∈Nx \in N and w⋅x≥0w \cdot x \ge 0, the perceptron accepted a negative. Subtract the example: w←w−xw \leftarrow w - x.
    4. Otherwise the example is fine. Change nothing.

Convergence means every positive has w⋅x≥0w \cdot x \ge 0 and every negative has w⋅x<0w \cdot x < 0: a full pass over the data needs no correction.

Flowchart of the perceptron learning algorithm
Pick an example, fix the weights only if it is misclassified, and stop when a full pass needs no fix.

At this point "add" and "subtract" look like a magic recipe. The next lesson shows why they move the weights in the right direction.

In code

import numpy as np

class Perceptron:
    def __init__(self, num_features):
        # One extra weight for the bias (w_0)
        self.w = np.random.rand(num_features + 1)

    def predict(self, x):
        # Insert the dummy input x_0 = 1 at the front
        x_with_bias = np.insert(x, 0, 1)
        return 1 if np.dot(self.w, x_with_bias) >= 0 else 0

    def fit(self, X_train, Y_train, max_epochs=1000):
        """Train until a full pass makes no errors, or max_epochs is reached."""
        for epoch in range(max_epochs):
            errors = 0

            for x, y_true in zip(X_train, Y_train):
                y_pred = self.predict(x)
                x_with_bias = np.insert(x, 0, 1)

                if y_true == 1 and y_pred == 0:      # missed a positive
                    self.w = self.w + x_with_bias
                    errors += 1
                elif y_true == 0 and y_pred == 1:    # accepted a negative
                    self.w = self.w - x_with_bias
                    errors += 1

            if errors == 0:
                print(f"Converged in {epoch + 1} epochs.")
                break

Train it on AND:

X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
Y = [0, 0, 0, 1]

p = Perceptron(num_features=2)
p.fit(X, Y)
print([p.predict(x) for x in X])   # [0, 0, 0, 1]

Because the starting weights are random, the number of epochs changes from run to run (a few epochs in our runs), but the final predictions are the same. Try OR. Then try XOR, where it never converges ("Where One Perceptron Fails" explains why).

Try it yourself
Perceptron learning simulator →

Step through the algorithm one example at a time and watch the line move after each correction.

EasyPLA

In the perceptron learning algorithm, what do you do on a positive example with w·x below 0, and on a negative example with w·x at least 0?

EasyPLA

What happens to the algorithm's weights once every example is classified correctly?

MediumPLABias

Why is a bias weight included in w?