By the end of this lesson, you should be able to:
\[ z = w_1x_1 + w_2x_2 + b \qquad\qquad \hat y = \text{step}(z) = \begin{cases} 1, & z \ge 0\\ 0, & z < 0 \end{cases} \]
The perceptron predicts 1 on one side of the line
\[ w_1x_1 + w_2x_2 + b = 0 \]
and 0 on the other side.
Tip
A perceptron draws one straight line. That is both its strength and its limit.
| Gate | \(w_1\) | \(w_2\) | \(b\) | Rule |
|---|---|---|---|---|
| AND | 1 | 1 | −1.5 | fires only if \(x_1+x_2 \ge 1.5\) |
| OR | 1 | 1 | −0.5 | fires if \(x_1+x_2 \ge 0.5\) |
Check OR on \((0,1)\): \(\;z = 1(0) + 1(1) - 0.5 = 0.5 \ge 0 \;\Rightarrow\; \hat y = 1\) ✓
Check AND on \((0,1)\): \(\;z = 1(0) + 1(1) - 1.5 = -0.5 < 0 \;\Rightarrow\; \hat y = 0\) ✓
XOR (“exclusive or”) is 1 when exactly one input is 1.
| \(x_1\) | \(x_2\) | XOR |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Your turn (1 minute): find \(w_1, w_2, b\) so that a single perceptron computes XOR.
Spoiler: you can’t. Let’s see why.
The two blue points sit on opposite corners. Any straight line that separates them from one orange X puts the other orange X on the wrong side.
Suppose some \(w_1, w_2, b\) worked. Then all four rows must hold:
| Input | Need | Condition |
|---|---|---|
| \((0,0)\) | 0 | \(b < 0\) |
| \((0,1)\) | 1 | \(w_2 + b \ge 0\) |
| \((1,0)\) | 1 | \(w_1 + b \ge 0\) |
| \((1,1)\) | 0 | \(w_1 + w_2 + b < 0\) |
Add rows 2 and 3: \(\;w_1 + w_2 + 2b \ge 0 \;\Rightarrow\; w_1 + w_2 + b \ge -b\)
Row 1 says \(b<0\), so \(-b > 0\), so \(\;w_1 + w_2 + b > 0\). That contradicts row 4. No solution exists. ∎
History: Minsky & Papert (1969) highlighted limits like this one. The fix is more than one layer.
XOR means “at least one is on, but not both”:
\[ \text{XOR}(x_1,x_2) = \text{OR}(x_1,x_2)\ \text{ AND NOT }\ \text{AND}(x_1,x_2) \]
| \(x_1\) | \(x_2\) | OR | AND | OR and not AND | XOR |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 | 1 | 1 |
| 1 | 0 | 1 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 | 0 |
OR and AND are each one perceptron. Can the last step (“\(h_1\) and not \(h_2\)”) be a perceptron too?
Use \(\;\hat y = \text{step}(h_1 - h_2 - 0.5)\)
| \(h_1\) | \(h_2\) | \(z = h_1 - h_2 - 0.5\) | \(\hat y\) |
|---|---|---|---|
| 0 | 0 | −0.5 | 0 |
| 1 | 0 | 0.5 | 1 |
| 1 | 1 | −0.5 | 0 |
The pair \((h_1,h_2) = (0,1)\) never happens: AND can’t be on while OR is off.
So: three perceptrons, wired together, compute XOR.
\[ h_1 = \text{step}(x_1 + x_2 - 0.5) \qquad h_2 = \text{step}(x_1 + x_2 - 1.5) \qquad \hat y = \text{step}(h_1 - h_2 - 0.5) \]
Hidden unit 1 (OR):
\[ z_1 = (1)(0) + (1)(1) - 0.5 = 0.5 \quad\Rightarrow\quad h_1 = \text{step}(0.5) = 1 \]
Hidden unit 2 (AND):
\[ z_2 = (1)(0) + (1)(1) - 1.5 = -0.5 \quad\Rightarrow\quad h_2 = \text{step}(-0.5) = 0 \]
Output unit: its inputs are now \(h_1\) and \(h_2\), not \(x_1\) and \(x_2\).
\[ z_{\text{out}} = (1)(1) + (-1)(0) - 0.5 = 0.5 \quad\Rightarrow\quad \hat y = 1 \]
Target XOR\((0,1) = 1\). Correct ✓
This is the case a single perceptron could never get right.
\[ z_1 = (1)(1) + (1)(1) - 0.5 = 1.5 \quad\Rightarrow\quad h_1 = 1 \]
\[ z_2 = (1)(1) + (1)(1) - 1.5 = 0.5 \quad\Rightarrow\quad h_2 = 1 \]
\[ z_{\text{out}} = (1)(1) + (-1)(1) - 0.5 = -0.5 \quad\Rightarrow\quad \hat y = 0 \]
Target XOR\((1,1) = 0\). Correct ✓. The AND unit “vetoed” the OR unit.
| \(x_1\) | \(x_2\) | \(z_1\) | \(h_1\) | \(z_2\) | \(h_2\) | \(z_{\text{out}}\) | \(\hat y\) | XOR |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | −0.5 | 0 | −1.5 | 0 | −0.5 | 0 | 0 ✓ |
| 0 | 1 | 0.5 | 1 | −0.5 | 0 | 0.5 | 1 | 1 ✓ |
| 1 | 0 | 0.5 | 1 | −0.5 | 0 | 0.5 | 1 | 1 ✓ |
| 1 | 1 | 1.5 | 1 | 0.5 | 1 | −0.5 | 0 | 0 ✓ |
Tip
Forward pass recipe: compute every node in a layer, then use those outputs as the inputs to the next layer. Repeat until you reach the output.
The hidden layer moves the points into a new space \((h_1, h_2)\). There, both XOR = 1 inputs land on the same spot, and one line separates the classes.
More hidden units → more lines → more complicated regions.
Big idea: hidden layers let the network build non-linear decision boundaries out of simple linear pieces.
Drag the sliders to solve XOR, then switch the target to XNOR, NAND, …
Stack the hidden weights in a matrix, one row per hidden unit:
\[ W = \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \end{bmatrix} = \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix}, \quad \mathbf b = \begin{bmatrix} -0.5 \\ -1.5 \end{bmatrix}, \quad \mathbf v = \begin{bmatrix} 1 \\ -1 \end{bmatrix}, \quad c = -0.5 \]
\[ \mathbf h = f(W\mathbf x + \mathbf b) \qquad\qquad \hat y = f(\mathbf v^\top \mathbf h + c) \]
Example \(\mathbf x = (1,0)\): \(\;W\mathbf x + \mathbf b = \begin{bmatrix} 1 \\ 1 \end{bmatrix} + \begin{bmatrix} -0.5 \\ -1.5 \end{bmatrix} = \begin{bmatrix} 0.5 \\ -0.5 \end{bmatrix} \Rightarrow \mathbf h = \begin{bmatrix} 1 \\ 0 \end{bmatrix}\)
This is exactly how libraries compute it: one matrix multiply per layer.
Each layer has (inputs × outputs) weights + (outputs) biases.
Our XOR network (2 → 2 → 1):
\[ \underbrace{2\times 2 + 2}_{\text{hidden}} \;+\; \underbrace{2 \times 1 + 1}_{\text{output}} \;=\; 6 + 3 = 9 \text{ parameters} \]
A digit classifier (784 → 128 → 10):
\[ (784 \times 128 + 128) + (128 \times 10 + 10) = 100{,}480 + 1{,}290 = 101{,}770 \]
We chose the 9 XOR weights by hand. Nobody can hand-pick 101,770, so the network has to learn them.
The perceptron rule uses the error at that neuron:
\[ w_i \leftarrow w_i + \eta\,(y - \hat y)\,x_i \]
We need a way to share the blame for an output error among the hidden units. This is the credit assignment problem.
Nudging a weight a little almost never changes a step output, so the slope is 0 and calculus gives no signal. MLPs use smooth activations instead:
\[ \sigma(z) = \frac{1}{1 + e^{-z}} \qquad\qquad \sigma'(z) = \sigma(z)\,\bigl(1 - \sigma(z)\bigr) \]
Without an activation, each layer is linear:
\[ \mathbf h = W_1 \mathbf x + \mathbf b_1, \qquad \hat y = W_2 \mathbf h + \mathbf b_2 \]
Substitute:
\[ \hat y = W_2(W_1 \mathbf x + \mathbf b_1) + \mathbf b_2 = \underbrace{(W_2 W_1)}_{W'}\mathbf x + \underbrace{(W_2 \mathbf b_1 + \mathbf b_2)}_{\mathbf b'} \]
That is still one straight line, no matter how many layers we stack!
Important
The non-linear activation between layers is what gives an MLP its power.
Input \(\mathbf x = (1, 0)\), target \(y = 1\), loss \(L = \tfrac12 (y - \hat y)^2\), learning rate \(\eta = 1\).
\[ z_1 = (1)(1) + (1)(0) - 0.5 = 0.5 \qquad z_2 = (1)(1) + (1)(0) - 1.5 = -0.5 \]
\[ h_1 = \sigma(0.5) = \frac{1}{1 + e^{-0.5}} = 0.6225 \qquad h_2 = \sigma(-0.5) = 0.3775 \]
Note
Compared with the step version (\(h_1 = 1,\ h_2 = 0\)), the sigmoid gives soft answers between 0 and 1.
\[ z_{\text{out}} = (1)(0.6225) + (-1)(0.3775) - 0.5 = -0.255 \]
\[ \hat y = \sigma(-0.255) = 0.437 \]
\[ L = \tfrac12 (1 - 0.437)^2 = 0.159 \]
The target is 1, but the network only says 0.437. With sigmoids, our hand-picked weights are too weak:
| \(\mathbf x\) | (0,0) | (0,1) | (1,0) | (1,1) |
|---|---|---|---|---|
| \(\hat y\) | 0.424 | 0.437 | 0.437 | 0.424 |
Every output is near 0.43. So we train the network.
Gradient descent: move every parameter downhill, \(\;\theta \leftarrow \theta - \eta \dfrac{\partial L}{\partial \theta}\).
Error signal at the output (chain rule: loss → sigmoid → \(z_{\text{out}}\)):
\[ \delta_{\text{out}} = (\hat y - y)\,\hat y\,(1 - \hat y) = (0.437 - 1)(0.437)(0.563) = -0.139 \]
Each output-layer gradient is \(\delta_{\text{out}}\) × (the input on that wire):
| Parameter | Gradient | New value \((\eta = 1)\) |
|---|---|---|
| \(v_1\) | \(\delta_{\text{out}} \cdot h_1 = -0.139 \times 0.6225 = -0.086\) | \(1 - (-0.086) = 1.086\) |
| \(v_2\) | \(\delta_{\text{out}} \cdot h_2 = -0.139 \times 0.3775 = -0.052\) | \(-1 - (-0.052) = -0.948\) |
| \(c\) | \(\delta_{\text{out}} \cdot 1 = -0.139\) | \(-0.5 - (-0.139) = -0.361\) |
The error flows backwards through the weight \(v_j\) and through hidden unit \(j\)’s sigmoid:
\[ \delta_j = \delta_{\text{out}} \cdot v_j \cdot h_j(1 - h_j) \qquad h_1(1-h_1) = h_2(1-h_2) = 0.235 \]
\[ \delta_1 = (-0.139)(1)(0.235) = -0.033 \qquad \delta_2 = (-0.139)(-1)(0.235) = +0.033 \]
Gradient = \(\delta_j\) × input. Since \(x_2 = 0\), the \(w_{12}\) and \(w_{22}\) gradients are 0 and those weights don’t change.
| Parameter | Gradient | New value |
|---|---|---|
| \(w_{11}\) | \(\delta_1 \cdot x_1 = -0.033\) | \(1.033\) |
| \(b_1\) | \(\delta_1 = -0.033\) | \(-0.467\) |
| \(w_{21}\) | \(\delta_2 \cdot x_1 = +0.033\) | \(0.967\) |
| \(b_2\) | \(\delta_2 = +0.033\) | \(-1.533\) |
Run the forward pass again on \(\mathbf x = (1,0)\) with the new parameters:
| Before | After 1 update | |
|---|---|---|
| \(\hat y\) | 0.437 | 0.497 |
| Loss \(L\) | 0.159 | 0.127 |
| Perceptron | MLP | |
|---|---|---|
| Activation | step | sigmoid / ReLU (smooth) |
| Forward pass | one weighted sum | one weighted sum per layer |
| Error signal | \(e = y - \hat y\) | loss \(L\), e.g. \(\tfrac12(y - \hat y)^2\) |
| Update | \(w_i \leftarrow w_i + \eta\, e\, x_i\) | \(w \leftarrow w - \eta\, \partial L/\partial w\) (via backprop) |
| Can learn XOR? | ✗ | ✓ |
\[ \boxed{\text{Forward pass} \rightarrow \text{Loss} \rightarrow \text{Backward pass} \rightarrow \text{Update} \rightarrow \text{Repeat}} \]
import numpy as np
from sklearn.linear_model import Perceptron
from sklearn.neural_network import MLPClassifier
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0]) # XOR
p = Perceptron(random_state=0).fit(X, y)
print("Perceptron:", p.predict(X), "accuracy", p.score(X, y))
mlp = MLPClassifier(hidden_layer_sizes=(4,), activation="tanh",
solver="lbfgs", max_iter=1000, random_state=0).fit(X, y)
print("MLP: ", mlp.predict(X), "accuracy", mlp.score(X, y))Perceptron: [0 0 0 0] accuracy 0.5
MLP: [0 1 1 0] accuracy 1.0
hidden_layer_sizes=(4,) means one hidden layer with 4 units. (8, 8) would mean two hidden layers.
\[ h_1 = \text{step}(2x_1 - x_2 - 1.5) \qquad h_2 = \text{step}(-x_1 + 2x_2 - 1.5) \qquad \hat y = \text{step}(h_1 + h_2 - 0.5) \]
Fill in the table. Which logic function is this?
| \(x_1\) | \(x_2\) | \(h_1\) | \(h_2\) | \(\hat y\) |
|---|---|---|---|---|
| 0 | 0 | ? | ? | ? |
| 0 | 1 | ? | ? | ? |
| 1 | 0 | ? | ? | ? |
| 1 | 1 | ? | ? | ? |
| \(x_1\) | \(x_2\) | \(z_1\) | \(h_1\) | \(z_2\) | \(h_2\) | \(\hat y\) |
|---|---|---|---|---|---|---|
| 0 | 0 | −1.5 | 0 | −1.5 | 0 | 0 |
| 0 | 1 | −2.5 | 0 | 0.5 | 1 | 1 |
| 1 | 0 | 0.5 | 1 | −2.5 | 0 | 1 |
| 1 | 1 | −0.5 | 0 | −0.5 | 0 | 0 |
It is XOR again, built a different way: \(h_1\) = “\(x_1\) and not \(x_2\)”, \(h_2\) = “\(x_2\) and not \(x_1\)”, and the output is OR\((h_1, h_2)\). The same function can have many different weight settings.
Use the network from Practice 1.
XNOR is the opposite of XOR: 1 when the inputs are equal.
Keep the lecture’s hidden layer (\(h_1\) = OR, \(h_2\) = AND). Choose new output weights \(v_1, v_2, c\).
Flip the output unit: \(\;\hat y = \text{step}(-h_1 + h_2 + 0.5)\)
| \((h_1,h_2)\) | \(z\) | \(\hat y\) |
|---|---|---|
| (0,0) from (0,0) | 0.5 | 1 |
| (1,0) from (0,1) and (1,0) | −0.5 | 0 |
| (1,1) from (1,1) | 0.5 | 1 |
Negating every output weight and the bias flips every decision (no input lands exactly on \(z = 0\)).
How many weights and biases does each fully connected network have?
\((4 \cdot 5 + 5) + (5 \cdot 3 + 3) = 25 + 18 = \mathbf{43}\)
\((784 \cdot 128 + 128) + (128 \cdot 10 + 10) = \mathbf{101{,}770}\)
\((10 \cdot 20 + 20) + (20 \cdot 20 + 20) + (20 \cdot 1 + 1) = 220 + 420 + 21 = \mathbf{661}\)
A “network” has no activation functions:
\[ h = 2x + 1, \qquad \hat y = 3h - 4 \]
Write \(\hat y\) directly in terms of \(x\). What does this tell you?
\(\hat y = 3(2x + 1) - 4 = 6x - 1\).
Two linear layers collapse into one linear function. Without a non-linear activation, extra layers add nothing, so the model still can’t learn XOR.
Use the sigmoid network from worked example 2 (lecture weights, \(c = -0.5\)) with input \(\mathbf x = (0, 0)\), target \(y = 0\).
Find \(h_1\), \(h_2\), \(\hat y\) and \(L = \frac12 (y - \hat y)^2\). Use \(\sigma(-0.5) = 0.378\), \(\sigma(-1.5) = 0.182\), \(\sigma(-0.304) = 0.424\).
\(z_1 = -0.5 \Rightarrow h_1 = 0.378\); \(\quad z_2 = -1.5 \Rightarrow h_2 = 0.182\)
\(z_{\text{out}} = 0.378 - 0.182 - 0.5 = -0.304 \Rightarrow \hat y = 0.424\)
\(L = \frac12 (0 - 0.424)^2 = 0.090\)
Continue Practice 6 (\(\hat y = 0.424\), \(y = 0\), \(\eta = 1\)).
Design an MLP with three inputs \(x_1, x_2, x_3 \in \{0,1\}\) that outputs 1 when exactly one input is 1.
Hint: reuse the “OR and not AND” idea with \(s = x_1 + x_2 + x_3\).
\(h_1 = \text{step}(x_1 + x_2 + x_3 - 0.5)\): “at least one is on”
\(h_2 = \text{step}(x_1 + x_2 + x_3 - 1.5)\): “at least two are on”
\(\hat y = \text{step}(h_1 - h_2 - 0.5)\): “at least one, but not two or more”
Same shape as our XOR net, with one more input!
\[ \boxed{\text{Forward pass} \rightarrow \text{Loss} \rightarrow \text{Backward pass} \rightarrow \text{Update} \rightarrow \text{Repeat}} \]