Build and train a 2 → 8 → 1 network, then inspect all four of its binary predictions.
Step 1
The XOR puzzle
XOR means exclusive or: output 1 when two bits differ, otherwise 0. Its four examples are (0,0) → 0, (0,1) → 1, (1,0) → 1, and (1,1) → 0.
Imagine those inputs as the corners of a square. The positive examples sit on opposite corners. No single straight line separates them from the other two corners. A linear model with one output and a threshold cannot solve this pattern.
This is a tiny learning exercise, not a generalization benchmark: we train on all four possible binary inputs. Lesson 6 will measure predictions on genuinely held-out examples.
Step 2
Two layers and a bend in the middle
nn.Sequential passes values through its layers in order. nn.Linear(2, 8) makes eight weighted combinations of two inputs, each with its own bias. nn.Tanh() bends those values into the range -1 to 1. nn.Linear(8, 1) combines the hidden values into one final score.
Without a nonlinear activation between them, stacking these linear layers would still be one linear calculation. The nonlinear hidden layer is what allows a more flexible boundary.
The output is a logit, an unrestricted score—not a probability yet. BCEWithLogitsLoss combines a sigmoid transformation with binary cross-entropy in a numerically stable calculation. Do not add a sigmoid before this loss. For this loss, the targets are floating-point 0s and 1s with the same shape as the logits.
An optimizer updates the parameters. Here we use Adam with a learning rate of 0.03. It adjusts update sizes using gradient history; it still depends on gradients from backward().
Step 3
Train, then inspect each prediction
The loop should now look familiar. optimizer.zero_grad() replaces our manual gradient clearing; optimizer.step() replaces the manual subtraction. The model starts in training mode, then switches to evaluation mode for inference.
model.eval() changes the behavior of certain layers, such as dropout; it does not turn gradient tracking off. That is why we also use torch.no_grad(). Our simple Linear/Tanh network has no dropout, but this is a useful habit.
At inference time, sigmoid turns a logit into a value between 0 and 1. We interpret this as the model's estimated probability of class 1 and use a 0.5 threshold. A confident score is not a guarantee that a model is correct.
Your turn
Practice & explain
Use the practice section at the end of this lesson's Colab notebook, or work in your local PyTorch environment. Keep a copy of your changes.
5.1 · Remove the activation
Create a fresh model with just Linear(2, 8) and Linear(8, 1), recreate the optimizer, and train again. Compare the predictions and loss with the original network. Explain why extra linear layers alone cannot solve XOR.
Need a hint?
When you replace a model, the old optimizer still refers to the old parameters. Create a new optimizer for the new model.
5.2 · Try a smaller hidden layer
Compare 2, 4, and 8 hidden neurons. Reset the seed, model, and optimizer for each experiment. Record parameter count, training loss, and all four predictions; do not claim that every initialization will converge.
Need a hint?
Change both Linear(2, hidden_size) and Linear(hidden_size, 1). With h hidden neurons, there are 4*h + 1 parameters.