Non-linearity is what makes the network powerful
Without a non-linear activation between layers, stacking linears just composes into one bigger linear — you'd have one trainable matrix, no matter how many nn.Linear calls. Activations are what let networks learn arbitrary functions.
The shortlist you'll actually use
- ReLU —
max(0, x). The default for hidden layers in CNNs and pre-Transformer MLPs. Cheap, effective, but suffers from "dying ReLU" when too many neurons hit zero permanently. - GELU — Gaussian Error Linear Unit. The default in modern Transformers (BERT, GPT, ViT). Smooth, slightly slower than ReLU, empirically better for attention-based models.
- SiLU (a.k.a. Swish) —
x * sigmoid(x). Standard in modern ConvNets (EfficientNet) and many recent vision Transformers. Also what gates the FFN in LLaMA-family models (SwiGLU). - Sigmoid — squash to (0, 1). Use for binary classification outputs and gating mechanisms (LSTM gates, gated MLPs). Don't use in deep hidden layers — the gradient saturates badly.
- Tanh — squash to (-1, 1). Mostly historical; LSTMs still use it. Same saturation problem as sigmoid.
- Softmax — turn logits into a probability distribution. Almost always on the LAST dim. Used as the final activation in classifiers and as the score normalizer in attention.
Module form vs functional form
Most activations come in two flavors: nn.ReLU() (a Module) and F.relu(x) (a function). For activations, both are stateless, so they're functionally equivalent. Use the Module form when you want it to show up in print(model) or model.modules(); use the functional form for one-off uses inside forward().