Skip to content

Latest commit

 

History

History
92 lines (45 loc) · 4.14 KB

File metadata and controls

92 lines (45 loc) · 4.14 KB

Layer Normalization

对于batch normalization实际上有两种说法,一种是说BN能够解决“Internal Covariate Shift”这种问题。简单理解就是随着层数的增加,中间层的输出会发生“漂移”。另外一种说法是:BN能够解决梯度弥散。通过将输出进行适当的缩放,可以缓解梯度消失的状况。

那么NLP领域中,我们很少遇到BN,而出现了很多的LN,例如bert等模型都使用layer normalization。这是为什么呢?

BN与LN主要区别

主要区别在于normalization的方向不同!

Batch Norm顾名思义是对一个batch进行操作。假设我们有10行3列 的数据,即我们的batchsize = 10,每一行数据有三个特征,假设这三个特征是【身高、体重、年龄】。那么BN是针对每一列(特征)进行缩放,例如算出【身高】的均值与方差,再对身高这一列的10个数据进行缩放。体重和年龄同理。这是一种“列缩放”。

而Layer Norm方向相反,它针对的是每一行进行缩放。即只看一笔数据,算出这笔所有特征的均值与方差再缩放。这是一种“行缩放”。

细心的你已经看出来,Layer Normalization对所有的特征进行缩放,这显得很没道理。我们算出一行这【身高、体重、年龄】三个特征的均值方差并对其进行缩放,事实上会因为特征的量纲不同而产生很大的影响。但是BN则没有这个影响,因为BN是对一列进行缩放,一列的量纲单位都是相同的。

那么我们为什么还要使用LN呢?因为NLP领域中,LN更为合适。

如果我们将一批文本组成一个batch,那么BN的操作方向是,对每句话的第一个词进行操作。但语言文本的复杂性是很高的,任何一个词都有可能放在初始位置,且词序可能并不影响我们对句子的理解。而BN是针对每个位置进行缩放,这不符合NLP的规律

而LN则是针对一句话进行缩放的,且LN一般用在第三维度,如[batchsize, seq_len, dims]中的dims,一般为词向量的维度,或者是RNN的输出维度等等,这一维度各个特征的量纲应该相同。因此也不会遇到上面因为特征的量纲不同而导致的缩放问题。

如下图所示:

bn_vs_ln

假如我们的词向量是100维(如图是立方体的高),batchsize是64(立方体中的N)。

BN:固定每句话的第一个位置,则这个切片是 (64, 100)维的矩阵。

LN:固定一句话,则切片是(seq_len, 100)维。

但是,BN取出一条 **(1,64)的向量(绿色剪头方向)并进行缩放,LN则是取出一条(1, 100)**维(红色箭头)进行缩放。

Layer-normalization代码

Layer normalization代码实现实例如下:

class LayerNormalization(tf.keras.layers.Layer):
    def __init__(self, epsilon=1e-8, **kwargs):
        super(LayerNormalization, self).__init__(**kwargs)
        self.epsilon = epsilon
    def build(self, input_shape):
        self.gamma = self.add_weight(name='gamma',
                                     shape=input_shape[-1:],
                                     initializer=tf.ones_initializer(),
                                     trainable=True)
        self.beta = self.add_weight(name='beta',
                                    shape=input_shape[-1:],
                                    initializer=tf.zeros_initializer(),
                                    trainable=True)
        super(LayerNormalization, self).build(input_shape)
    def call(self, x): # x shape=[batch_size, seq_len, d_model]
        mean = tf.keras.backend.mean(x, axis=-1, keepdims=True)
        std = tf.keras.backend.std(x, axis=-1, keepdims=True)
        return self.gamma * (x - mean) / (std + self.epsilon) + self.beta

参考资料

本文参考了该知乎博客。

本文中的代码参考此博客。