Multi-Layer Perceptron
- Universal approximation theorem (Cybenko 1989, Hornik 1991): a single hidden layer MLP can approximate any continuous function
- MLPs generalized the perceptron by adding hidden layers and smooth non-linear activations (sigmoid, tanh, ReLU)
- The core architecture behind feedforward neural networks in all modern deep learning
A multi-layer perceptron stacks layers of perceptron-like units with non-linear activations between them. Each layer learns a progressively higher-level feature representation. Training uses backpropagation to compute gradients layer by layer.
- Every deep learning architecture (CNN, RNN, Transformer) contains MLP sub-networks (FFN layers)
- The universal approximation theorem explains why depth + nonlinearity beats shallow linear models
- Connects to activation function design (ReLU, GELU, SwiGLU) in modern transformers
- State the multi-layer perceptron model: a stack of affine transforms interleaved with element-wise non-linear activations.
- State the universal approximation theorem and explain why width alone is insufficient in practice (Telgarsky 2016).
- Compare sigmoid, tanh, ReLU, Leaky ReLU, GELU, and Swish on range, gradient at the origin, and vulnerability to vanishing or dying activations.
- Derive Glorot/Xavier and He weight initialization from a variance-preservation argument and pick the right one for a given activation.
- Implement an MLP from scratch in NumPy with manual backpropagation and reproduce it in PyTorch and TensorFlow Keras.
TL;DR
A multi-layer perceptron (MLP) is a feedforward neural network: a stack of affine maps interleaved with element-wise non-linearities . Cybenko 1989 [1] and Hornik 1991 [2] proved that a single hidden layer of finite width with a sigmoidal (or any non-polynomial squashing) activation can uniformly approximate any continuous function on a compact set. Telgarsky 2016 [3] showed why depth still matters in practice: certain functions need exponentially more parameters when forced into a shallow architecture. Modern MLPs use ReLU [6][7] or its smooth cousins (GELU [8], Swish [9]) and a variance-preserving initialization (Xavier [4] for tanh, He [5] for ReLU). They are trained by backpropagation [10].
Intuition
A single perceptron draws one hyperplane and outputs which side of it you sit on. That is enough for AND or OR but not XOR. Stack two perceptrons: the first carves the input space into half-planes, the second combines those half-planes into a more interesting region. Stack a few more, and you can carve out any continuous region.
The MLP is exactly that picture written in matrices. Every hidden layer takes the previous activations, projects them through a learned weight matrix, adds a bias, then squashes the result through a non-linear activation. Without the non-linearity, the whole stack collapses into a single affine map (composition of linear maps is linear), and you are back to the perceptron. The non-linearity is what makes depth useful.
Three equivalent ways to read the same equation:
- Function composition. where each .
- Geometric warping. Each layer warps the input space; the final layer just runs a linear classifier on a now-easy representation.
- Feature learning. Hidden layers learn features; the final layer is the model on those features. The deep-learning bet is that the features you would have hand-engineered fall out of gradient descent.
Math
Definition of the MLP and its forward pass
The model
For an input , an -layer MLP is
with weights , biases , and element-wise activation . The output is interpreted as a regression target, a classification logit, or (after softmax) a probability vector.
Why a non-linearity is mandatory
If every is the identity, then
a single affine map. Depth without non-linearity buys nothing. Even one layer of element-wise non-linearity unlocks universal approximation.
Backprop in one slide
For a scalar loss , define where is the pre-activation. Then
This is reverse-mode automatic differentiation specialized to a feed-forward DAG. Cost: one backward pass per loss, with the same asymptotic complexity as one forward pass [10].
Universal approximation: Cybenko 1989 and Hornik 1991
Cybenko’s theorem (sigmoidal version)
A function is sigmoidal if as and as . Cybenko proved [1]:
Let be any continuous sigmoidal function and let be compact. For any continuous and any , there exist a finite , weights , biases , and coefficients such that
In words: a single hidden layer of finite width with a sigmoidal activation can uniformly approximate any continuous function on a compact set.
The proof is a Hahn-Banach plus Riesz representation argument: assume the closure of finite sums is not dense in , derive a non-zero signed measure that annihilates every , then show that any sigmoidal is discriminatory, contradicting the construction. The constructive content is hidden inside the contradiction; the bound is not given.
Hornik’s extension
Hornik 1991 [2] generalized the theorem in two directions:
- The activation can be any non-constant, bounded, continuous function (not specifically sigmoidal).
- The result extends from to for any finite measure , with convergence in norm.
A polynomial activation cannot do this (the span of polynomials of bounded degree is finite-dimensional), which is why ReLU is fine (non-polynomial, piecewise-linear) but is not.
What the theorem does not say
Universal approximation is an existence statement. It is silent on:
- How wide must be. The required width can grow exponentially in the input dimension (curse of dimensionality).
- Whether SGD finds the approximating weights. Optimization is a separate problem.
- Generalization. Approximation is on the training data; test error needs additional assumptions (smoothness, margin, low intrinsic dimension).
This is the gap depth fills.
Why depth helps: Telgarsky 2016
Telgarsky [3] constructed a family of functions on that a deep ReLU network with layers and constant width can represent exactly, but any shallow network with two layers needs at least
to approximate within constant error. The idea is that ReLU networks with depth can implement repeated sawtooth folds: each layer doubles the number of linear pieces, so layers give pieces with parameters, while a shallow net needs parameters to write the same number of pieces directly.
Concretely, the function is a triangle wave folded times. A 3-layer ReLU net of width 2 represents one fold; composition of such blocks is a -layer net with parameters. Reproducing with a 2-layer net requires one hidden unit per linear piece, so units.
The takeaway: width and depth are not parameter-equivalent. Some functions are exponentially cheaper to express with depth, even if width alone is universal.
This complements universal approximation: depth is not strictly necessary in theory, but it is exponentially more efficient on the structured functions we actually want to learn (compositional, hierarchical, with shared sub-features) [11][12].
Activation function zoo
The activation is what makes depth useful, and different choices have very different optimization properties. The shapes you should know cold:
| Activation | Formula | Range | Gradient at 0 | Vanishing gradient? | Where used today |
|---|---|---|---|---|---|
| Sigmoid | sigma(z) = 1 / (1 + e^-z) | (0, 1) | 0.25 | yes (saturates both sides) | Output of binary classifiers and gates in LSTM/GRU; not used in hidden layers anymore. |
| Tanh | tanh(z) = (e^z - e^-z) / (e^z + e^-z) | (-1, 1) | 1.0 | yes (saturates) | Hidden layers in classic RNNs; mostly displaced by ReLU. |
| ReLU | max(0, z) | [0, +inf) | 1 (right) / 0 (left) | no (right side has gradient 1) | Default for CNNs and most MLPs since 2012. |
| Leaky ReLU | max(alpha z, z) with alpha approx 0.01 | (-inf, +inf) | 1 | no | Mitigates the dying-ReLU problem; common in GANs. |
| GELU | z * Phi(z) | (-inf, +inf) | 0.5 | no | Default in transformers (BERT, GPT-2 onwards). |
| Swish / SiLU | z * sigma(z) | (-inf, +inf) | 0.5 | no | EfficientNet, Stable Diffusion U-Nets, many recent CV models. |
A few facts worth memorizing:
- Sigmoid saturates on both sides. Its derivative is at most , so a deep sigmoid stack multiplies derivatives in and the gradient vanishes geometrically with depth.
- ReLU has gradient 1 on the active side, which is why it trains so much faster than sigmoid [6][7]. The price is the dying-ReLU failure mode: a unit with all-negative pre-activations gets zero gradient and never recovers. Leaky ReLU keeps a small slope on the left to avoid this.
- GELU and Swish are smooth approximations of ReLU that behave better in transformers because the smooth gradient interacts better with normalization layers and very deep stacks. GELU is essentially times the Gaussian CDF; Swish is times the sigmoid [8][9].
The shapes are easier to internalize visually than from formulas. The figure below overlays sigmoid, tanh, ReLU, and GELU on a common range:
Figure 1. Sigmoid saturates on both sides; tanh saturates symmetrically around 0. ReLU and GELU stay linear on the right (good gradient) and only differ on the left, where GELU’s smooth tail avoids the dying-ReLU problem. ReLU and GELU are scaled by 1/2 vertically to share the figure.
Weight initialization
Even with the right activation, a deep stack will not train if the initial weights are wrong. The standard derivations come from a single principle: keep the variance of activations and gradients constant across layers.
Glorot/Xavier initialization (tanh) and He initialization (ReLU)
Setup
At initialization, treat the input to a layer as a random vector with zero mean and unit variance per coordinate, and treat the weights as iid with mean zero and variance , independent of . Look at one pre-activation:
so and
For the pre-activation variance to equal the input variance we need
Glorot/Xavier (tanh)
For tanh, the activation acts approximately linearly near zero, so the same condition applies on the backward pass with in place of . Glorot and Bengio [4] compromised between the two by averaging:
In practice this is sampled either as or uniformly on (same variance).
He (ReLU)
ReLU kills half of the pre-activations: when is symmetric around zero. To compensate, He et al. [5] doubled the variance:
Why it matters
Without variance preservation:
- Too small a variance: pre-activations shrink toward zero, gradients shrink with them, and the network is stuck.
- Too large: pre-activations explode, ReLU gradients become huge for the active half, training diverges.
He init is the default for ReLU/Leaky-ReLU/GELU networks today (torch.nn.init.kaiming_normal_); Xavier remains correct for tanh/sigmoid layers and is still used in attention’s softmax temperature.
Implementations
Three idiomatic implementations of the same network: a from-scratch NumPy MLP with manual backpropagation, a PyTorch nn.Sequential, and a TensorFlow Keras Sequential. The NumPy version is intentionally explicit; the framework versions show the production form.
import numpy as np
def init(d_in, d_hid, d_out, seed=0):
rng = np.random.default_rng(seed)
# He init for ReLU hidden, Xavier for the linear/sigmoid output.
W1 = rng.normal(0, np.sqrt(2 / d_in), size=(d_hid, d_in))
b1 = np.zeros(d_hid)
W2 = rng.normal(0, np.sqrt(1 / d_hid), size=(d_out, d_hid))
b2 = np.zeros(d_out)
return W1, b1, W2, b2
def relu(z): return np.maximum(0.0, z)
def relu_grad(z): return (z > 0).astype(z.dtype)
def sigmoid(z): return 1.0 / (1.0 + np.exp(-z))
def forward(X, params):
W1, b1, W2, b2 = params
Z1 = X @ W1.T + b1 # (n, d_hid)
H1 = relu(Z1)
Z2 = H1 @ W2.T + b2 # (n, d_out)
Yhat = sigmoid(Z2)
cache = (X, Z1, H1, Z2, Yhat)
return Yhat, cache
def bce_loss(y, yhat, eps=1e-9):
return -np.mean(y * np.log(yhat + eps) + (1 - y) * np.log(1 - yhat + eps))
def backward(y, params, cache):
W1, b1, W2, b2 = params
X, Z1, H1, Z2, Yhat = cache
n = X.shape[0]
dZ2 = (Yhat - y) / n # sigmoid + BCE simplification
dW2 = dZ2.T @ H1
db2 = dZ2.sum(0)
dH1 = dZ2 @ W2
dZ1 = dH1 * relu_grad(Z1)
dW1 = dZ1.T @ X
db1 = dZ1.sum(0)
return dW1, db1, dW2, db2
def step(params, grads, lr):
W1, b1, W2, b2 = params
dW1, db1, dW2, db2 = grads
return (W1 - lr*dW1, b1 - lr*db1, W2 - lr*dW2, b2 - lr*db2)
# XOR
X = np.array([[0,0],[0,1],[1,0],[1,1]], dtype=float)
y = np.array([[0],[1],[1],[0]], dtype=float)
params = init(2, 8, 1)
for it in range(2000):
yhat, cache = forward(X, params)
grads = backward(y, params, cache)
params = step(params, grads, lr=0.5)
yhat, _ = forward(X, params)
print("XOR predictions:", yhat.round(3).ravel())Run the from-scratch NumPy MLP live in the browser. It learns XOR within a few hundred steps:
(no output yet)
Real-world uses
The plain MLP is rarely the headline architecture today, but it is everywhere:
| Domain | Use | Why an MLP works |
|---|---|---|
| Tabular learning | Click-through rate, churn, credit risk | Tree ensembles still win on small/medium tabular, but DNNs catch up at scale and via embeddings (DLRM at Meta). |
| Transformer FFN block | Two-layer MLP between attention layers (with GELU) | About two-thirds of transformer parameters live in these MLPs; they do per-token feature transformation. |
| CNN classifier head | Final dense layer(s) after global pooling | Maps spatial features to class logits. |
| Recommender embeddings | MLP on concatenated user / item / context embeddings | The "tower" architecture in two-tower retrieval and the deep arm of Wide & Deep. |
| Reinforcement learning | Policy and value networks | Standard for low-dimensional state spaces; CNN/Transformer encoders feed into the same MLP heads. |
Pitfalls
Stacking linear layers with no activation is mathematically a single linear layer (composition of affine maps is affine). Always check that every hidden layer has an activation; this is a surprisingly common bug when refactoring.
A ReLU unit whose pre-activation is negative on every minibatch has gradient zero and never recovers. Symptoms: sudden plateaus, fraction of dead units climbing during training. Fixes: He init at the right scale, lower learning rate, or switch to Leaky ReLU / GELU.
He init for ReLU and Xavier for tanh are not interchangeable. Using Xavier in a deep ReLU stack halves the variance per layer and the network silently fails to train; using He on a tanh stack doubles it and saturates the activations. Match the init to the activation.
For tabular data: 2-3 hidden layers, 64-512 units each, ReLU or GELU, He init, AdamW with weight decay 1e-4, batch norm or layer norm between layers, dropout 0.1 on the larger layers, cosine LR schedule with warmup. This is a strong baseline before reaching for fancier architectures.
Pop quiz
- 1. What does Cybenko 1989 (and Hornik 1991) actually prove?
- 2. Why does Telgarsky 2016 say depth helps when width alone is universal?
- 3. Which initialization should you use for a deep ReLU MLP?
- 4. What is the dying ReLU problem?
- 5. Why does removing all activations collapse an MLP to a linear model?
Interview Q&A
State and sketch a proof of the universal approximation theorem (Cybenko 1989).research
Statement. Let be any continuous sigmoidal function. Let be compact. For any continuous and any , there exist a finite , weights , biases , and coefficients such that
Proof sketch. Let be the linear span of all functions of the form , restricted to . We want to show the closure in equals .
Suppose for contradiction . By Hahn-Banach there is a non-zero continuous linear functional on that vanishes on . By Riesz representation this functional is integration against a non-zero signed Borel measure on , so
Cybenko’s key step is to show that any sigmoidal is discriminatory: the only signed measure satisfying the above is , contradicting the Hahn-Banach measure being non-zero. The discriminatory property follows by sliding to extreme magnitudes so approaches the indicator of a half-space, then varying and to scan all half-spaces and using uniqueness of measures determined by their values on half-spaces. Therefore , completing the proof. [1]
Caveats. The proof is non-constructive (no bound on ), and the universal-approximation property is silent about optimization (whether SGD finds these weights) and generalization (whether the approximation works off the training set). Hornik 1991 generalizes to any non-constant bounded continuous activation and to norms [2].
Derive the backprop equations for a 2-layer MLP with ReLU and softmax cross-entropy.onsite
Forward pass on a single example:
with target one-hot and loss .
Output layer. A standard simplification: softmax + cross-entropy makes
So and .
Hidden layer. Apply chain rule:
Then and .
Vectorized over a minibatch. Replace with and divide gradients by . The whole pass is time and the same memory as the forward pass. [10]
Why does ReLU avoid the vanishing gradient problem that plagues sigmoid?phone
The sigmoid derivative is , which is at most at and decays exponentially in the tails. Backprop multiplies these per-layer derivatives, so a -layer sigmoid stack contracts the gradient by at most in expectation: 10 layers give a 10 shrinkage. The gradient effectively vanishes before it reaches the early layers and they stop learning.
ReLU has derivative exactly 1 on the active side and exactly 0 on the dead side. There is no contraction on the active path, so gradients flow freely through the (typically half-active) layers. The cost is the dying ReLU mode (gradient is hard zero, not just small) which is mitigated by Leaky ReLU, ELU, GELU, or Swish. This shift in 2010-2012 was as important to deep learning’s success as backprop itself [6][7].
Derive the variance-preservation argument behind He init for ReLU.onsite
Treat the input as iid with and , and weights as iid with and , independent of . The pre-activation has
Now apply ReLU: . If is symmetric around zero, with probability and with probability , so
ignoring the small mean-shift (rigorous derivation in [5]). To keep , we need
That is the He-normal init. For Xavier (tanh, which acts approximately linearly near zero) the factor of is absent, giving ; the symmetric average between forward and backward passes then yields the standard form [4].
If width alone is universal, why do we use depth at all?research
Three reasons, in increasing depth:
- Representation efficiency. Telgarsky 2016 [3] exhibits functions on that a -deep ReLU net of constant width represents exactly with parameters, but any 2-layer ReLU net needs units. Depth gives compositional reuse: each layer doubles the number of linear pieces with extra parameters.
- Inductive bias. The functions we want to learn (vision pipelines, language hierarchies) are themselves compositional. A deep architecture aligns the model’s structure with the target’s structure, which improves sample complexity even when the function is realizable shallowly.
- Optimization landscape. Counterintuitively, deep nets are often easier to optimize than wide shallow ones with the same capacity. Empirically loss landscapes of overparameterized deep nets exhibit fewer bad local minima (Choromanska et al. 2015 and follow-ups), and the implicit bias of SGD on overparameterized models steers toward solutions that generalize. Wide shallow models do not enjoy the same implicit-bias story [11].
The high-level summary: universal approximation says depth is not strictly necessary; everything else says depth is exponentially more efficient on the structured targets we care about.
A short project that takes about an evening:
- Implement the from-scratch NumPy MLP above for digit classification on MNIST. Two hidden layers of 256 units, ReLU, He init, plain SGD. Target above 96% on the test set.
- Swap ReLU for tanh and confirm Xavier init beats He init in that setting; measure the gap.
- Replace SGD with Adam (the next page) and watch the convergence speed jump.
- Stretch: implement a CNN front end and feed its features into the same MLP head. Compare to the pure-MLP baseline; the gap on MNIST is small but on CIFAR-10 it is enormous.
References
- Cybenko, G. (1989). Approximation by superpositions of a sigmoidal function. Mathematics of Control, Signals, and Systems 2(4), 303-314. doi:10.1007/BF02551274
- Hornik, K. (1991). Approximation capabilities of multilayer feedforward networks. Neural Networks 4(2), 251-257. doi:10.1016/0893-6080(91)90009-T
- Telgarsky, M. (2016). Benefits of depth in neural networks. Conference on Learning Theory (COLT) 2016. arXiv:1602.04485
- Glorot, X. and Bengio, Y. (2010). Understanding the difficulty of training deep feedforward neural networks. AISTATS 2010, JMLR W&CP 9, 249-256. https://proceedings.mlr.press/v9/glorot10a.html
- He, K., Zhang, X., Ren, S., and Sun, J. (2015). Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. IEEE International Conference on Computer Vision (ICCV) 2015. arXiv:1502.01852
- Nair, V. and Hinton, G. E. (2010). Rectified linear units improve restricted Boltzmann machines. Proc. 27th International Conference on Machine Learning (ICML), 807-814. https://icml.cc/Conferences/2010/papers/432.pdf
- Glorot, X., Bordes, A., and Bengio, Y. (2011). Deep sparse rectifier neural networks. AISTATS 2011, JMLR W&CP 15, 315-323. https://proceedings.mlr.press/v15/glorot11a.html
- Hendrycks, D. and Gimpel, K. (2016). Gaussian Error Linear Units (GELUs). arXiv preprint. arXiv:1606.08415
- Ramachandran, P., Zoph, B., and Le, Q. V. (2017). Searching for Activation Functions. arXiv preprint. arXiv:1710.05941
- Rumelhart, D. E., Hinton, G. E., and Williams, R. J. (1986). Learning representations by back-propagating errors. Nature 323, 533-536. doi:10.1038/323533a0
- Goodfellow, I., Bengio, Y., and Courville, A. (2016). Deep Learning. MIT Press. https://www.deeplearningbook.org
- LeCun, Y., Bengio, Y., and Hinton, G. (2015). Deep learning. Nature 521, 436-444. doi:10.1038/nature14539