Nonlinear features and multilayer networks

Introduction

Add nonlinear hidden features when one linear decision boundary cannot represent the task.

Learning goal

Prove that a small ReLU MLP can represent XOR and track every tensor shape.

Before you start

Linear layers, ReLU, logits, and basic matrix multiplication.

Lesson plan

  1. Expose the XOR limit
  2. Construct hidden ReLU features
  3. Separate representability from training

The problem

One linear classifier draws one hyperplane. It cannot solve XOR, where (0,0) and (1,1) share a class while (0,1) and (1,0) share the other.

Stacking linear layers without an activation does not help. Their matrix products collapse into one linear transformation. A nonlinear activation prevents that collapse.

Work through a small example

Define two hidden features: h1=ReLU(x1-x2) and h2=ReLU(x2-x1). Their sum is zero when inputs match and one when they differ. A final threshold separates the XOR classes.

A two-input, two-hidden-unit, one-output networkTwo scalar input circles connect to two ReLU hidden circles. Their outputs connect to one scalar output circle. The hidden units detect opposite input differences. x1x2 ReLUx1−x2ReLUx2−x1 different?h1+h2

The general rule

An MLP layer computes H=activation(XW+b). For X shaped (B,D) and hidden width H, first-layer weights conceptually map D to H; PyTorch stores them as (H,D). The output layer maps hidden features to class logits.

ReLU computes max(0,x) elementwise. Piecewise-linear hidden units divide input space into regions; later layers combine them into nonlinear boundaries.

Implement and inspect

This code hand-sets weights to prove that a small MLP can represent XOR. It does not train those weights. nn.Sequential chains the listed functions in order, sending each output into the next layer. torch.no_grad() lets us assign parameter values without adding those assignments to an automatic-gradient graph.

import torch
from torch import nn

x = torch.tensor([[0., 0.], [0., 1.], [1., 0.], [1., 1.]])
model = nn.Sequential(nn.Linear(2, 2), nn.ReLU(), nn.Linear(2, 1))
with torch.no_grad():
    model[0].weight.copy_(torch.tensor([[1., -1.], [-1., 1.]]))
    model[0].bias.zero_()
    model[2].weight.copy_(torch.tensor([[1., 1.]]))
    model[2].bias.fill_(-0.5)

logits = model(x).squeeze(1)
print(logits.tolist())
print((logits > 0).long().tolist())

Expected logits are [-0.5,0.5,0.5,-0.5] and classes [0,1,1,0]. Input shape is (4,2), hidden shape (4,2), and output shape (4,1).

The parent integration lesson trains an MLP with cross-entropy and gradient-based updates. That step replaces hand-chosen parameters with weights learned from examples.

Engineering checks

Go deeper

A sufficiently wide MLP can approximate continuous functions on compact regions, but existence does not guarantee efficient learning, good generalization, or sensible behavior outside training support.

Width, depth, and activation affect expressivity and optimization. Deep compositions can reuse features efficiently, while poor scaling can shrink or explode signals. Symmetries mean many parameter settings represent the same function, making the loss geometry non-convex.

Practice and recap

Question: Why do two linear layers with no ReLU still make a linear classifier?

Worked answer

(XW1+b1)W2+b2 = X(W1W2) + (b1W2+b2). The products can be renamed as one weight and one bias. ReLU makes the transformation input-dependent, so this algebraic collapse no longer works.

An MLP learns nonlinear intermediate features. Use it when held-out evidence shows that one linear boundary is an important limitation.