我们知道 BN 通过对输入 mini batch 样本进行 normalization,能够加快网络收敛。但是,BN 不适合用在 RNN 网络。原因是:BN 是对同一个批次所有样本的相同特征进行 normalization 操作,也就是说 Batch 样本的特征数量应该是一样的。
例如:我们在 CNN 网络中输入的 Batch 样本,每个输入图像都有相同数量的 Feature Map,这样 BN 可以对不同的样本的相同 Feature Map 进行 normalization 操作。
例如:我们在 MLP 网络中输入的 Batch 样本,每条输入样本都有相同数量的特征,这样 BN 可以对不同样本的相同特征进行 normalization 操作。
但是,如果是 RNN 网络,输入的 Batch 样本,长度是不一致的。对于一句话,每个字/词可以理解为样本的一个特征,这就导致每个样本的特征数量不同,我们就没办法在特征维度进行 normalization 操作。为了解决该问题,就提出了 Layer Normalization,LN 和 BN 不同之处就在于,对一个样本的不同特征分别进行 normalization 操作,其实也是基于时间步的 normalization 操作。
简单来说,就是对每一个字的向量表示进行标准化计算,计算公式如下:
elementwise_affine = True
时,λ 是一个数据维度的参数。例如:在 Bert 中 hidden size 为 768,所以 λ 就会有 768 维度,对应的 β 也有 768 维度。λ 和 β 相当于对归一化之后做一个线性变换的参数和偏置。
对每一个维度进行标准化之后,得到的每一个维度的数据再乘以 λ 加上 β。
Paper:https://arxiv.org/pdf/1607.06450.pdf
示例代码:
import torch.nn as nn import torch def test(): batch, length, dim = 2, 3, 4 torch.manual_seed(0) batch_input = torch.randint(0, 10, size=[batch, length, dim]).float() print(batch_input) layer_norm = nn.LayerNorm(normalized_shape=dim, eps=1e-5, elementwise_affine=False) output = layer_norm(batch_input) print(output) if __name__ == '__main__': test()
程序执行结果:
tensor([[[4., 9., 3., 0.], [3., 9., 7., 3.], [7., 3., 1., 6.]], [[6., 9., 8., 6.], [6., 8., 4., 3.], [6., 9., 1., 4.]]]) tensor([[[ 0.0000, 1.5430, -0.3086, -1.2344], [-0.9622, 1.3471, 0.5773, -0.9622], [ 1.1531, -0.5241, -1.3628, 0.7338]], [[-0.9622, 1.3471, 0.5773, -0.9622], [ 0.3906, 1.4321, -0.6509, -1.1717], [ 0.3430, 1.3720, -1.3720, -0.3430]]])
- 对 [4., 9., 3., 0.] 进行 normalization,得到[ 0.0000, 1.5430, -0.3086, -1.2344]
- 对 [0., 3., 9., 3.] 进行 normalization,得到 [-0.9622, 1.3471, 0.5773, -0.9622]
- … 以此类推
第一个字 [4, 9, 3, 0] 向量的归一化计算过程如下:
a = torch.tensor([4., 9., 3., 0.]).float() # 先将向量中心化 b = a - torch.mean(a) # 计算方差 # torch.var 计算时会除以 (n-1) # LayNorm 中计算方差时除的是 n var = torch.sum(b ** 2) / 4 # 计算标准差 c = torch.sqrt(var + 1e-5) # 计算归一化后的结果 print(b / c)
总结:
- LayerNorm 使用时,只需要指定 token 的向量维度即可
- LayerNorm 使用时,对每一个字的向量进行归一化,即:将输入的每一个字的向量表示都归一化到均值为 0 标准差为 1 的分布中