01
引言
本文是手撕Transformer系列的第五篇。它从头开始介绍层归一化(Layer Normalization),并用代码对其进行了实现。
02
背景介绍
一般来说,归一化是将特征转换到特定尺度下的过程。对特征进行归一化处理的方法有很多,最著名的是基于最小-最大特征缩放和基于Standard Score的缩放。
Min-Max Feature Scaling
最小-最大特征缩放将数值转换为 [0,1] 范围。其计算公式如下:
等式的右侧将每个值减去 X_min;当 X = X_min 时,分子变为 0。除以分母后,输出为 0。同样,当X = X_max 时,会出现新的最大值。当这个值除以 X_max - X_min 时,就变成了 1。这就是范围如何变为 0 和 1 之间的过程。
下面的代码示例演示了该过程:
import torch
X = torch.Tensor([22, 5, 6, 8, 10, 19,2])
X_max = X.max() # 22
X_min = X.min() # 2
# [22-2, 5-2, 6-2, 8-2, 10-2, 19-2, 2-2] =
numerator = X-X_min # [20, 3, 4, 6, 8, 17, 0]
denominator = X_max-X_min # 22 - 2 = 20
# [20/20, 3/20, 4/20, 6/20, 8/20, 17/20, 0/20]
X_new = numerator/denominator
输出如下:
tensor([1.00, 0.15, 0.20, 0.30, 0.40, 0.85, 0.00])
Standard Score
import torch
X = torch.Tensor([22, 5, 6, 8, 10, 19,2])
n = len(X)
mean = X.sum()/n # X.mean()
std = (((X-mean)**2).sum()/n).sqrt() # X.std(unbiased=False)
z_scores = (X - mean)/std
print(mean, std, z_scores, sep="\n")
输出如下:
tensor(10.2857)
tensor(6.9016)
tensor([ 1.6973, -0.7659, -0.6210, -0.3312, -0.0414, 1.2626, -1.2005])
03
为什么需要Normalization?
还有学者认为缺乏归一化会导致梯度过大,最终爆炸,使模型训练变得不稳定。因此,在许多情况下,数据应在进入模型前应该进行规范化处理。
04
Layer Normalization
一般来说,层归一化可确保 "特定层中的所有神经元在给定输入的所有特征上具有有效的相同分布"。
对于每个输入(记为 x),可使用修改后的 z-分数方程计算层归一化:
μ 代表最后 D 个维度的平均值
σ² 代表最后 D 个维度的方差 ε 是一个极小的值,当 σ² 较小时,它能起到帮助作用 γ 和 β 是可学习的参数
05
举个例子
为了演示层归一化的计算方法,我们将形状为 (4,5,3) 的张量在其矩阵中进行归一化,而矩阵的维度大小为 (5,3)。这意味着 D 为 2。
# Input Tensor: 4 matrices of 5 rows and 3 columns
X = torch.randint(0, 100, (4, 5, 3)).float()
# Shape to be Normalized: 5 rows, 3 columns
normalized_shape = (5, 3)
# Number of Dimensions in the Shape to be Normalized
D = len(normalized_shape)
# Set the Default Values for Epsilon, Gamma, and Beta
eps = 1e-5
gamma = torch.ones(normalized_shape)
beta = torch.zeros(normalized_shape)
print(X)
tensor([[[76., 2., 43.],
[79., 50., 29.],
[59., 78., 73.],
[95., 94., 76.],
[ 9., 74., 64.]],
[[76., 87., 50.],
[ 2., 65., 44.],
[74., 9., 82.],
[83., 54., 82.],
[ 6., 97., 52.]],
[[88., 19., 95.],
[14., 96., 96.],
[93., 58., 0.],
[19., 37., 6.],
[28., 23., 7.]],
[[ 7., 54., 59.],
[57., 30., 18.],
[88., 89., 63.],
[56., 75., 56.],
[63., 23., 73.]]])
# Normalize
for i in range(0,4): # loop through each matrix
mean = X[i].mean() # mean
var = X[i].var(unbiased=False) # variance
layer_norm = (X[i]-mean)/(torch.sqrt(var+eps))*gamma + beta
print(f"μ = {mean:.4f}")
print(f"σ^{2} = {var:.4f}")
print(layer_norm)
print("="*50)
layer_normalization = nn.LayerNorm(normalized_shape) # nn.LayerNorm((5,3))
layer_normalization(X)
06
NLP中的应用
在自然语言处理中,层归一化发生在每个标记Token的嵌入维度上。对于有 2 个序列、3 个标记和 5 个元素嵌入的批次,其维度形状为(2, 3, 5)。由于最后一个维度将被规范化,因此 D 将为 1。嵌入的形状将是(5, )。必须将其初始化为一个元组,以确保其值能被 LayerNorm 模块提取出来。或者也可以使用 X.shape[-1]。
# Input Tensor: 2 sequences of 3 tokens with 5 dimensional embeddings
X = torch.randint(2, 3, 5)
# Shape to be Normalized: 5 dimensional embedding
normalized_shape = (5,)
# Number of Dimensions in the Shape to be Normalized
D = len(normalized_shape) # 1
# Create the LayerNorm
layer_normalization = nn.LayerNorm(normalized_shape)
# view the beta and gamma and beta
print(layer_normalization.state_dict())
OrderedDict([('weight', tensor([1., 1., 1., 1., 1.])),
('bias', tensor([0., 0., 0., 0., 0.]))])
print(X)
tensor([[[49., 90., 29., 76., 33.],
[86., 42., 20., 56., 79.],
[40., 49., 72., 16., 85.]],
[[44., 62., 14., 46., 5.],
[22., 45., 8., 47., 78.],
[96., 17., 7., 56., 60.]]])
print(X.mean(2, keepdims=True) # maintains the dimensions of X)
tensor([[[55.4000],
[56.6000],
[52.4000]],
[[34.2000],
[40.0000],
[47.2000]]])
print(layer_normalization(X))
07
Transformer中的实现
尽管 PyTorch 已内置了 LayerNorm 模块,但为了更好地理解其在Transformer模型中的使用,我们可以重新创建该模块。实现过程相对简单,并尽量在注释中进行代码解释。
class LayerNorm(nn.Module):
def __init__(self, features, eps=1e-5):
super().__init__()
# initialize gamma to be all ones
self.gamma = nn.Parameter(torch.ones(features))
# initialize beta to be all zeros
self.beta = nn.Parameter(torch.zeros(features))
# initialize epsilon
self.eps = eps
def forward(self, src):
# mean of the token embeddings
mean = src.mean(-1, keepdim=True)
# variance of the token embeddings
var = src.var(-1, keepdim=True,unbiased=False)
# return the normalized value
return self.gamma * (src - mean) / torch.sqrt(var + self.eps) + self.beta
点击上方小卡片关注我
添加个人微信,进专属粉丝群!