Code Architecture — Layer Normalization Notebook
Overview
The notebook implements Layer Normalization from scratch in NumPy and PyTorch, plugs it into an RNN/Transformer block, and compares training stability against BatchNorm on sequence data. All computation is CPU-friendly (no GPU required).
Section-by-Section Breakdown
1. Imports & Setup
torch,torch.nn,numpy,matplotlibfor visualization- Random seed fixing for reproducibility
2. LayerNorm from Scratch in NumPy
- Function
layer_norm_numpy(x, gain, bias, eps=1e-5)- Input:
xis a 1D array of H elements (the summed inputs to a layer for one sample) - Compute mean μ = mean(x) across all H elements
- Compute variance σ² = var(x) across all H elements
- Normalize: x_norm = (x - μ) / sqrt(σ² + eps)
- Scale and shift: out = gain * x_norm + bias
- Returns normalized output
- Input:
- Gradient verification: numerical gradient check against analytic gradients
- Verify dL/dx, dL/dgain, dL/dbias via finite differences
- Assert relative error < 1e-5
3. LayerNorm as a PyTorch Module
- Class
LayerNorm(nn.Module)__init__(features, eps=1e-5): initializesself.gamma = nn.Parameter(torch.ones(features))andself.beta = nn.Parameter(torch.zeros(features))forward(x): x has shape (batch, seq_len, features) or (batch, features)- Compute mean and variance over the last dimension (features)
- Normalize and apply gain/bias
- Key shapes:
- Input: (B, H) → mean/var over dim=-1, keepdim=True
- Input: (B, T, H) → mean/var over dim=-1, keepdim=True
- Output: same shape as input
4. BatchNorm for Comparison
- Class
BatchNorm1d(nn.Module)— simplified from-scratch version- Normalizes over the batch dimension (dim=0)
- Maintains running mean/var for inference
forward(x): during training, use batch statistics and update running stats; during eval, use running stats
5. Toy Sequence Classification Task
- Generate a synthetic sequence dataset:
- Each sample is a sequence of T=20 timesteps with H=64 features
- Two classes: “increasing trend + noise” vs “decreasing trend + noise”
- 1000 train samples, 200 test samples
- Data shapes: X_train (1000, 20, 64), y_train (1000,)
6. Simple RNN with Normalization
- Class
RNNCellWithNorm(nn.Module)- Computes: a_t = W_hh @ h_{t-1} + W_xh @ x_t
- Applies normalization (LayerNorm or BatchNorm or None) to a_t
- Applies non-linearity: h_t = tanh(a_t_normalized)
- Parameters: W_hh (H, H), W_xh (H, H), plus norm module
- Class
SimpleRNN(nn.Module)- Wraps the RNN cell, unrolls over T timesteps
- Final hidden state → linear classifier → class logits
- Forward: iterates over timesteps, applies cell, returns logits from final h
7. Training Loop
- Three models: NoNorm, BatchNorm, LayerNorm — identical architecture except normalization
- Optimizer: Adam, lr=0.001
- Loss: CrossEntropyLoss
- Epochs: 50
- Record training loss, test accuracy per epoch
- Key data flow:
X (B,T,H) → RNN unroll → h_T (B,H) → Linear(H, 2) → logits (B,2) → CrossEntropy
8. Transformer Block with LayerNorm
- Class
TransformerBlock(nn.Module)- Self-attention (simplified, single-head) + LayerNorm + feedforward + LayerNorm
- Pre-norm style: LayerNorm before attention and before FFN
- Demonstrates where LayerNorm sits in a transformer block
- Apply to the same sequence classification task
9. Visualization & Comparison
- Plot 1: Training loss curves (NoNorm vs BatchNorm vs LayerNorm)
- Plot 2: Test accuracy curves over epochs
- Plot 3: Hidden state magnitude over timesteps (showing LayerNorm stabilizes dynamics)
- Plot 4: Gradient flow comparison (gradient norm per layer over training)
10. Analysis
- Print final accuracies for all three normalization schemes
- Discuss: LayerNorm converges faster, stabilizes hidden state, doesn’t depend on batch size
- Show that BatchNorm with batch_size=1 degrades (online learning scenario) while LayerNorm doesn’t
Key Functions/Classes
| Component | Purpose | |—|—| | layer_norm_numpy() | Pure NumPy implementation for gradient verification | | LayerNorm(nn.Module) | Reusable PyTorch LayerNorm module | | BatchNorm1d(nn.Module) | From-scratch BatchNorm for comparison | | RNNCellWithNorm | RNN cell with pluggable normalization | | SimpleRNN | Full RNN model for sequence classification | | TransformerBlock | Simplified transformer block with LayerNorm |
Data Flow & Shapes
Input: (B=64, T=20, H=64)
↓ RNN unroll (t=1..20)
h_t: (64, 64) at each step
↓ final h_T
h_T: (64, 64)
↓ Linear(64, 2)
logits: (64, 2)
↓ CrossEntropy
loss: scalar
Deliberate Simplifications vs Full Paper
- Toy dataset — synthetic trend classification instead of real NLP tasks (MNIST, COCO, CNN corpus)
- Small model — 64 hidden units, 20 timesteps (paper used 2400-dim encoders)
- No convolutional experiments — paper has a ConvNet section; we focus on RNN/Transformer which is where LayerNorm shines
- Single-head attention — simplified transformer block instead of multi-head
- No skip-thought / order-embedding experiments — these require large datasets and multi-day training
- Gradient analysis is empirical — paper’s Riemannian geometry analysis is theoretical; we show practical gradient flow instead
- Batch size sensitivity — demonstrated empirically rather than through the paper’s invariance proofs