The problem
LayerNorm (layer normalization) keeps the numbers inside a network at a steady scale. It takes each token's vector of numbers on its own, shifts it to mean 0, rescales it to variance 1, then applies a learned gain and bias (both vectors of numbers). The theory page explains why this helps.
You will write the forward pass and the backward pass.
Forward
First the mean and the variance of the vector:
Then normalize, scale and shift:
where:
- is one token's vector and its entry ;
- (mu) is its mean and (sigma squared) its variance, the average squared distance from the mean;
- ("x hat") is the normalized vector, with mean 0 and variance 1;
- (epsilon) is
eps, which avoids dividing by zero; - is elementwise multiplication.
Example. , , .
- . Distances from the mean , squared , so .
- .
- .
Backward
The backward pass receives grad_y, the gradient of the loss with respect to , and returns three gradients.
For and : is added to entry of every token's output, and multiplies of every token. So:
- the gradient of is
grad_ysummed over all tokens; - the gradient of is
grad_y * x_hatsummed over all tokens.
Example. Two tokens with
grad_y = [[1, 0, 0, 0], [1, 0, 0, 0]]: the gradient of is[2, 0, 0, 0]. If both tokens had the above, the gradient of would be in its first entry, and 0 elsewhere.
For : every entry of depends on every entry of , through and . The theory page derives the result. With grad_y and :
where:
- a bar means the mean over the entries of the token;
- removes the part of the gradient that would only shift all entries together (normalization undoes any shift);
- removes the part that would only stretch the vector (normalization undoes any stretch).
What to implement
layernorm_forward(x, gamma, beta, eps=1e-5):xhas shape . Return(y, cache), wherecacheis anything you need for the backward pass (for example , and ).layernorm_backward(grad_y, cache): return(dx, dgamma, dbeta), with the shapes ofx,gammaandbeta.
Requirements
- Match PyTorch's
LayerNorm: the variance divides by (np.vardoes this by default), andepsis added inside the square root. xcan have any number of leading axes: , , ... Normalize over the last axis only.dgammaanddbetasum over all the other axes.- No Python loops.
Hints
- Use
keepdims=Truefor the means and the variance over the last axis, so they line up withx. - To sum over every axis except the last, pass a tuple:
axis=tuple(range(x.ndim - 1)).