在Keras中,我应该在哪里调用BatchNormalization函数?

211

如果我想在Keras中使用BatchNormalization函数,那么我只需要在开始时调用它一次吗?

我阅读了这个文档:http://keras.io/layers/normalization/

我不知道应该在哪里调用它。以下是我的代码尝试使用它:

model = Sequential()
keras.layers.normalization.BatchNormalization(epsilon=1e-06, mode=0, momentum=0.9, weights=None)
model.add(Dense(64, input_dim=14, init='uniform'))
model.add(Activation('tanh'))
model.add(Dropout(0.5))
model.add(Dense(64, init='uniform'))
model.add(Activation('tanh'))
model.add(Dropout(0.5))
model.add(Dense(2, init='uniform'))
model.add(Activation('softmax'))

sgd = SGD(lr=0.1, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='binary_crossentropy', optimizer=sgd)
model.fit(X_train, y_train, nb_epoch=20, batch_size=16, show_accuracy=True, validation_split=0.2, verbose = 2)

我之所以问这个问题,是因为如果我在第二行代码包含批归一化并且我在没有第二行的情况下运行代码,我得到了类似的输出。那么要么我没有在正确的位置调用该函数,要么我猜它并没有产生太大的区别。

8个回答

269

为了更详细地回答这个问题,正如Pavel所说,批量归一化只是另一层,因此您可以将其用作创建所需网络架构的层。

通常的用法是在网络的线性和非线性层之间使用BN,因为它可以对激活函数的输入进行归一化,以使您处于激活函数(例如Sigmoid)的线性部分的中心位置。这里有一个小讨论:这里

以您上面的情况为例,这可能看起来像:


# import BatchNormalization
from keras.layers.normalization import BatchNormalization

# instantiate model
model = Sequential()

# we can think of this chunk as the input layer
model.add(Dense(64, input_dim=14, init='uniform'))
model.add(BatchNormalization())
model.add(Activation('tanh'))
model.add(Dropout(0.5))

# we can think of this chunk as the hidden layer    
model.add(Dense(64, init='uniform'))
model.add(BatchNormalization())
model.add(Activation('tanh'))
model.add(Dropout(0.5))

# we can think of this chunk as the output layer
model.add(Dense(2, init='uniform'))
model.add(BatchNormalization())
model.add(Activation('softmax'))

# setting up the optimization of our weights 
sgd = SGD(lr=0.1, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='binary_crossentropy', optimizer=sgd)

# running the fitting
model.fit(X_train, y_train, nb_epoch=20, batch_size=16, show_accuracy=True, validation_split=0.2, verbose = 2)
希望这能更加澄清事情。

29
了解,据说实践中批量归一化在激活函数之后的效果更好。 - Claudiu
16
嗨,@Claudiu,你能否对此进行详细说明?它似乎直接与上面的答案相矛盾。 - Ben Ogorek
8
@benogorek:没问题,基本上我完全基于这里的结果(https://github.com/ducha-aiki/caffenet-benchmark/blob/master/batchnorm.md),在relu之后放置批量归一化效果更好。顺便说一下,我在尝试的一个网络上无论如何都没有成功应用它。 - Claudiu
35
有趣。继续阅读该摘要,如果你看到后面,它说他们最好的模型[GoogLeNet128_BN_lim0606]实际上在ReLU之前有BN层。因此,虽然在某些情况下激活函数后加BN可能会提高准确性,但在整个模型构建之前,放置在ReLU之前效果最佳。可能在激活函数后添加BN可以提高准确性,但很可能取决于具体问题。 - Lucas Ramadan
9
大致是这样的。例如,可以看看ReginaldIII在reddit上的评论(https://redd.it/67gonq)。他们说:“BN正在规范卷积输出的特征分布,其中一些特征可能是负值,并被类似于ReLU的非线性函数截断。如果在激活之前进行规范化,则会将这些负值立即包括在规范化中,然后从特征空间中剔除它们。激活后的BN将归一化正特征,而不会通过那些无法传递到下一个卷积层的特征对其进行统计偏差。” - mab
显示剩余8条评论

76

此帖子有误导性。我试图评论Lucas Ramadan的答案,但我没有正确的权限,所以我在这里放置它。

批归一化最好在激活函数之后使用,这里这里是原因:它被开发用于防止内部协变量漂移。当一个层的激活分布在训练过程中显着变化时,就会出现内部协变量漂移。批归一化用于使特定层的输入的分布(这些输入实际上是激活函数的结果)由于每个批次的参数更新而不随时间改变(或者至少允许以优势的方式改变)。它使用批次统计数据进行归一化,然后使用批次归一化参数(原始论文中的gamma和beta)"确保插入网络的转换可以表示恒等变换"(引用原始论文)。但问题在于我们试图归一化一个层的输入,所以它应该立即放在网络中下一层之前。无论是否在激活函数之后取决于具体架构。


35
在deeplearning.ai课程中,我刚刚看到Andrew Ng表示在深度学习社区内存在对此问题的辩论。他倾向于在非线性函数之前应用批量归一化。 - shahensha
3
@kRazzyR 我的意思是安德鲁·吴教授在他的深度学习课程中谈到了这个话题,网址是https://www.deeplearning.ai/。他说社区对于正确的做法存在分歧,而他更喜欢在应用非线性之前先应用批量归一化。 - shahensha
4
@jmancuso,BN在激活函数之前应用。从论文中可以看到,方程为g(BN(Wx + b)),其中g是激活函数。 - o12d10
测试前和测试后哪个更适合,事先没有人知道哪个更好。但从理论上讲,非线性处理在测试前更有意义。 - Sergey Bushmanov

53

这篇帖子讨论了BN应该在当前层的非线性之前还是在前一层的激活函数上应用。

虽然没有正确答案,Batch Normalization的作者表示应该在当前层的非线性之前立即应用它。原因(引自原始论文)-

“我们将BN变换立即添加到非线性之前,通过对x = Wu+b进行标准化。我们也可以对层输入u进行标准化,但由于u很可能是另一个非线性的输出,所以其分布形状很可能会在训练过程中发生变化,并且约束它的一阶和二阶矩不会消除协变量漂移。相比之下,Wu + b 更有可能具有对称、非稀疏分布,即“更高斯”(Hyv¨arinen & Oja,2000);对其进行标准化很可能会产生具有稳定分布的激活。”


4
在我的个人经验中,这并没有太大的区别,但其他条件相同的情况下,我总是看到当批归一化应用在非线性之前(激活函数之前)时,BN表现略微更好。 - Brad Hesse

35

Keras现在支持use_bias=False选项,因此我们可以通过以下方式节省一些计算:

model.add(Dense(64, use_bias=False))
model.add(BatchNormalization(axis=bn_axis))
model.add(Activation('tanh'))
或者
model.add(Convolution2D(64, 3, 3, use_bias=False))
model.add(BatchNormalization(axis=bn_axis))
model.add(Activation('relu'))

model.add(BatchNormalization())model.add(BatchNormalization(axis=bn_axis)) 有什么不同? - kRazzy R
@kRazzR,无论您使用tensorflow作为后端,都不会有区别。这是因为他从keras.applications模块中复制了此内容,其中需要指定bn_axis以支持channels_firstchannels_last格式。 - ldavid
11
请问有人能详细解释一下这与OP的问题有什么关系吗?(我是个比较新手的神经网络,可能会忽略掉一些东西。) - Pepacz
这个回答与OP的问题无关。 - Scott

33

现在几乎成为一种趋势,先使用一个Conv2D 层,然后是一个ReLu层,最后是一个BatchNormalization层。因此,我编写了一个小函数,可以一次性调用它们所有的功能。这样可以使模型定义看起来更加简洁易读。

def Conv2DReluBatchNorm(n_filter, w_filter, h_filter, inputs):
    return BatchNormalization()(Activation(activation='relu')(Convolution2D(n_filter, w_filter, h_filter, border_mode='same')(inputs)))

7
可以考虑将这个转移到Keras吗? - sachinruk

9

批量归一化用于通过调整激活函数的均值和缩放来归一化输入层和隐藏层。由于这种附加层的规范化效应,深度神经网络可以使用更高的学习率而不会出现梯度消失或梯度爆炸的情况。此外,批量归一化使网络正则化,使其更容易泛化,因此不需要使用丢弃(dropout)来减少过拟合。

在使用Keras中的Dense()或Conv2D()计算线性函数之后,我们立即使用BatchNormalization()来计算层中的线性函数,然后使用Activation()向该层添加非线性转换。

from keras.layers.normalization import BatchNormalization
model = Sequential()
model.add(Dense(64, input_dim=14, init='uniform'))
model.add(BatchNormalization(epsilon=1e-06, mode=0, momentum=0.9, weights=None))
model.add(Activation('tanh'))
model.add(Dropout(0.5))
model.add(Dense(64, init='uniform'))
model.add(BatchNormalization(epsilon=1e-06, mode=0, momentum=0.9, weights=None))
model.add(Activation('tanh'))
model.add(Dropout(0.5))
model.add(Dense(2, init='uniform'))
model.add(BatchNormalization(epsilon=1e-06, mode=0, momentum=0.9, weights=None))
model.add(Activation('softmax'))

sgd = SGD(lr=0.1, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='binary_crossentropy', optimizer=sgd)
model.fit(X_train, y_train, nb_epoch=20, batch_size=16, show_accuracy=True, 
validation_split=0.2, verbose = 2)

批量归一化是如何应用的?

假设我们将 a[l-1] 作为输入传递到第 l 层。同时,我们有第 l 层的权重 W[l] 和偏置单元 b[l]。令 a[l] 是计算得到的激活向量(即添加非线性后)第 l 层,z[l] 是在添加非线性之前的向量。

  1. 使用 a[l-1] 和 W[l] 可以计算出第 l 层的 z[l]
  2. 通常,在前馈传播中,我们会在这个阶段将偏置单元添加到 z[l] 中,像这样 z[l]+b[l],但在批量归一化中,不需要进行 b[l] 的添加步骤,也不使用 b[l] 参数。
  3. 计算 z[l] 的均值并从每个元素中减去
  4. 使用标准差除以 (z[l] - mean)。将其称为 Z_temp[l]
  5. 现在定义新的参数 γ 和 β 将隐藏层的比例更改如下:

    z_norm[l] = γ.Z_temp[l] + β

在这段代码中,Dense() 采用 a[l-1]、使用 W[l] 并计算 z[l]。然后,立即的 BatchNormalization() 将执行上述步骤以给出 z_norm[l]。然后,立即的 Activation() 将计算 tanh(z_norm[l]) 以给出 a[l] 即。

a[l] = tanh(z_norm[l])

7

1
在我添加BatchNormalization之后,val_acc停止了每个epoch的增长。在我添加BatchNormalization之后,val_acc在每个epoch之后保持不变。我以为Batch Normalization应该会增加val_acc。我如何知道它是否正常工作?你知道可能是什么原因导致这种情况吗? - pr338
不幸的是,链接已失效 :( - user2324712
在Keras的分支中有该示例的副本(例如https://github.com/WenchenLi/kaggle/blob/master/otto/keras/kaggle_otto_nn.py),但我不知道为什么它被从原始的Keras存储库中删除,以及代码是否与最新的Keras版本兼容。 - Pavel Surmenok

1

关于批量归一化应该在非线性激活之前还是之后进行的辩论,又有了新的观点:

除了原始论文使用在激活之前进行批量归一化之外,Bengio的书《深度学习》第8.7.1节也提供了一些理由,说明为什么在激活之后(或直接在下一层的输入之前)应用批量归一化可能会导致一些问题:

自然而然地会想到我们应该将批量归一化应用于输入X还是变换值XW+b上。Ioffe和Szegedy(2015)推荐后者。更具体地说,XW+b应该被XW的归一化版本所取代。偏置项应该被省略,因为它与批量归一化参数β的应用是重复的。一层的输入通常是前一层中的非线性激活函数(例如修正线性函数)的输出。因此,输入的统计数据更加非高斯,并且不易通过线性操作进行标准化。

换句话说,如果我们使用relu激活函数,所有负值都被映射为零。这可能会导致平均值已经非常接近于零,但其余数据的分布将严重向右倾斜。尝试将该数据规范化为漂亮的钟形曲线可能不会得到最佳结果。对于relu家族之外的激活函数,这可能不是一个很大的问题。
请记住,有报告称,在激活函数后使用批量归一化可以获得更好的结果,而其他人则在批量归一化放置在激活函数之前时获得最佳结果。最好测试您的模型使用两种配置,并且如果批量归一化后的激活可以显着降低验证损失,请改用该配置。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接