C.W.K.
Stream
Lesson 02 of 08 · published

Linear, Bias, and the MLP

~12 min · linear, mlp, bias

Level 0Tensor Curious
0 XP0/62 lessons0/13 achievements
0/120 XP to next level120 XP to go0% complete

nn.Linear — the most important layer

nn.Linear(in_features, out_features) applies the affine transformation y = x W^T + b. The weight matrix is (out_features, in_features), the bias is (out_features,). This single layer is where 90% of the parameters in most models live (Transformer FFN blocks, classifier heads, embedding projections — all Linear).

Initialization defaults

By default, nn.Linear initializes the weight with Kaiming uniform (good for ReLU-family activations) and the bias with a uniform distribution scaled by the input fan-in. You usually don't override these — but knowing they exist is what makes "my model isn't training" debugging tractable.

The MLP idiom

A multi-layer perceptron is just Linear → activation → Linear → activation → ... → Linear. Modern variants add Dropout for regularization, LayerNorm for stability, and skip connections for very deep networks. The pattern is universal — every Transformer FFN block is a 2-layer MLP with a non-linearity (typically GELU) in the middle.

Code

nn.Linear basics·python
import torch
import torch.nn as nn

# 20 inputs → 10 outputs
linear = nn.Linear(20, 10)
print(linear.weight.shape)   # torch.Size([10, 20])  — out_features × in_features
print(linear.bias.shape)     # torch.Size([10])

x = torch.randn(32, 20)      # batch=32, features=20
y = linear(x)
print(y.shape)               # torch.Size([32, 10])

# Without bias (rare but useful in some Transformer variants)
linear_nb = nn.Linear(20, 10, bias=False)
print(linear_nb.bias)        # None
Build an MLP — the canonical pattern·python
import torch
import torch.nn as nn

class MLP(nn.Module):
    def __init__(self, in_dim, hidden, out_dim, p_drop=0.1):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(in_dim, hidden),
            nn.GELU(),                # transformer-flavor; ReLU works too
            nn.Dropout(p_drop),
            nn.Linear(hidden, hidden),
            nn.GELU(),
            nn.Dropout(p_drop),
            nn.Linear(hidden, out_dim),
        )

    def forward(self, x):
        return self.net(x)

model = MLP(784, 256, 10)
print(f"Params: {sum(p.numel() for p in model.parameters()):,}")
# Params: 269,322
Custom init — when defaults aren't what you want·python
import torch
import torch.nn as nn
import math

class MLP(nn.Module):
    def __init__(self, in_dim, hidden, out_dim):
        super().__init__()
        self.fc1 = nn.Linear(in_dim, hidden)
        self.fc2 = nn.Linear(hidden, out_dim)
        self.act = nn.GELU()
        self._init_weights()

    def _init_weights(self):
        # Custom Xavier init — useful when you want a specific behavior
        for m in self.modules():
            if isinstance(m, nn.Linear):
                nn.init.xavier_normal_(m.weight)
                if m.bias is not None:
                    nn.init.zeros_(m.bias)

    def forward(self, x):
        return self.fc2(self.act(self.fc1(x)))

External links

Exercise

Build an MLP class that's parameterized by a list of hidden dims (so MLP([784, 256, 128, 10]) builds a 3-Linear network). Use nn.ModuleList for the layers. Test on a random batch and confirm output shape.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.