batch-norm.md 26.4 KB
Newer Older
R
initial  
Rachel Hu 已提交
1
# 批量归一化
A
Aston Zhang 已提交
2 3
:label:`sec_batch_norm`

X
ch7  
xiaotinghe 已提交
4
训练深层神经网络是十分困难的,特别是在较短的时间内使他们收敛更加棘手。
X
xiaotinghe 已提交
5
在本节中,我们将介绍 *批量归一化*(batch normalization) :cite:`Ioffe.Szegedy.2015` ,这是一种流行且有效的技术,可持续加速深层网络的收敛速度。
X
ch6 ch7  
xiaotinghe 已提交
6
再结合在 :numref:`sec_resnet` 中将介绍的残差块,批量归一化使得研究人员能够训练 100 层以上的网络。
A
Aston Zhang 已提交
7

R
half  
Rachel Hu 已提交
8
## 训练深层网络
A
Aston Zhang 已提交
9

R
half  
Rachel Hu 已提交
10
为什么需要批量归一化层呢?让我们来回顾一下训练神经网络时出现的一些实际挑战。
A
Aston Zhang 已提交
11

R
half  
Rachel Hu 已提交
12
首先,数据预处理的方式通常会对最终结果产生巨大影响。
X
xiaotinghe 已提交
13
回想一下我们应用多层感知机来预测房价的例子( :numref:`sec_kaggle_house` )。
R
half  
Rachel Hu 已提交
14
使用真实数据时,我们的第一步是标准化输入特征,使其平均值为0,方差为1。
D
dongh11 已提交
15
直观地说,这种标准化可以很好地与我们的优化器配合使用,因为它可以将参数的量级进行统一。
A
Aston Zhang 已提交
16

X
xiaotinghe 已提交
17 18
第二,对于典型的多层感知机或卷积神经网络。当我们训练时,中间层中的变量(例如,多层感知机中的仿射变换输出)可能具有更广的变化范围:不论是沿着从输入到输出的层,跨同一层中的单元,或是随着时间的推移,模型参数的随着训练更新变幻莫测。
批量归一化的发明者非正式地假设,这些变量分布中的这种偏移可能会阻碍网络的收敛。
X
ch7  
xiaotinghe 已提交
19
直观地说,我们可能会猜想,如果一个层的可变值是另一层的 100 倍,这可能需要对学习率进行补偿调整。
R
half  
Rachel Hu 已提交
20

X
xiaotinghe 已提交
21
第三,更深层的网络很复杂,容易过拟合。
R
half  
Rachel Hu 已提交
22
这意味着正则化变得更加重要。
R
half  
Rachel Hu 已提交
23

X
xiaotinghe 已提交
24
批量归一化应用于单个可选层(也可以应用到所有层),其原理如下:在每次训练迭代中,我们首先归一化输入,即通过减去其均值并除以其标准差,其中两者均基于当前小批量处理。
R
half  
Rachel Hu 已提交
25
接下来,我们应用比例系数和比例偏移。
X
xiaotinghe 已提交
26
正是由于这个基于*批量*统计的*标准化*,才有了*批量归一化*的名称。。
R
half  
Rachel Hu 已提交
27

X
ch7  
xiaotinghe 已提交
28 29 30
请注意,如果我们尝试使用大小为 1 的小批量应用批量归一化,我们将无法学到任何东西。
这是因为在减去均值之后,每个隐藏单元将为 0。
所以,只有使用足够大的小批量,批量归一化这种方法才是有效且稳定的。
X
xiaotinghe 已提交
31
请注意,在应用批量归一化时,批量大小的选择可能比没有批量归一化时更重要。
R
half  
Rachel Hu 已提交
32

X
xiaotinghe 已提交
33
从形式上来说,用 $\mathbf{x} \in \mathcal{B}$ 表示一个来自小批量 $\mathcal{B}$ 的输入,批量归一化$\mathrm{BN}$ 根据以下表达式转换 $\mathbf{x}$:
A
Aston Zhang 已提交
34 35 36 37

$$\mathrm{BN}(\mathbf{x}) = \boldsymbol{\gamma} \odot \frac{\mathbf{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}} + \boldsymbol{\beta}.$$
:eqlabel:`eq_batchnorm`

X
xiaotinghe 已提交
38 39 40
在 :eqref:`eq_batchnorm` 中,$\hat{\boldsymbol{\mu}}_\mathcal{B}$ 是样本均值,$\hat{\boldsymbol{\mu}}_\mathcal{B}$ 是小批量 $\mathcal{B}$ 的样本标准差。
应用标准化后,生成的小批量的平均值为 0 和单位方差为 1。
由于单位方差(与其他一些魔法数)是一个任意的选择,因此我们通常包含
R
half  
Rachel Hu 已提交
41 42
*拉伸参数*(scale) $\boldsymbol{\gamma}$ 和 *偏移参数*(shift) $\boldsymbol{\beta}$,它们的形状与 $\mathbf{x}$ 相同。
请注意,$\boldsymbol{\gamma}$ 和 $\boldsymbol{\beta}$ 是需要与其他模型参数一起学习的参数。
A
Aston Zhang 已提交
43

X
xiaotinghe 已提交
44
由于在训练过程中,中间层的变化幅度不能过于剧烈,而批量归一化将每一层主动居中,并将它们重新调整为给定的平均值和大小(通过 $\hat{\boldsymbol{\mu}}_\mathcal{B}$ 和 ${\hat{\boldsymbol{\sigma}}_\mathcal{B}}$)。
A
Aston Zhang 已提交
45

R
half  
Rachel Hu 已提交
46
从形式上来看,我们计算出 :eqref:`eq_batchnorm` 中的 $\hat{\boldsymbol{\mu}}_\mathcal{B}$ 和 ${\hat{\boldsymbol{\sigma}}_\mathcal{B}}$,如下所示:
A
Aston Zhang 已提交
47 48 49 50

$$\begin{aligned} \hat{\boldsymbol{\mu}}_\mathcal{B} &= \frac{1}{|\mathcal{B}|} \sum_{\mathbf{x} \in \mathcal{B}} \mathbf{x},\\
\hat{\boldsymbol{\sigma}}_\mathcal{B}^2 &= \frac{1}{|\mathcal{B}|} \sum_{\mathbf{x} \in \mathcal{B}} (\mathbf{x} - \hat{\boldsymbol{\mu}}_{\mathcal{B}})^2 + \epsilon.\end{aligned}$$

D
dongh11 已提交
51
请注意,我们在方差估计值中添加一个小常量 $\epsilon > 0$,以确保我们永远不会尝试除以零,即使在经验方差估计值可能消失的情况下也是如此。估计值 $\hat{\boldsymbol{\mu}}_\mathcal{B}$ 和 ${\hat{\boldsymbol{\sigma}}_\mathcal{B}}$ 通过使用平均值和方差的噪声(noise)估计来抵消缩放问题。
R
half  
Rachel Hu 已提交
52
你可能会认为这种噪声是一个问题,而事实上它是有益的。
A
Aston Zhang 已提交
53

R
half  
Rachel Hu 已提交
54
事实证明,这是深度学习中一个反复出现的主题。
X
xiaotinghe 已提交
55
由于理论上尚未明确表述的原因,优化中的各种噪声源通常会导致更快的训练和较少的过拟合:这种变化似乎是正则化的一种形式。
X
xiaotinghe 已提交
56
在一些初步研究中, :cite:`Teye.Azizpour.Smith.2018` 和 :cite:`Luo.Wang.Shao.ea.2018` 分别将批量归一化的性质与贝叶斯先验相关联。
R
half  
Rachel Hu 已提交
57
这些理论揭示了为什么批量归一化最适应 $50 \sim 100$ 范围中的中等小批量尺寸的难题。
A
Aston Zhang 已提交
58

R
half  
Rachel Hu 已提交
59 60 61 62 63
另外,批量归一化图层在”训练模式“(通过小批量统计数据归一化)和“预测模式”(通过数据集统计归一化)中的功能不同。
在训练过程中,我们无法得知使用整个数据集来估计平均值和方差,所以只能根据每个小批次的平均值和方差不断训练模型。
而在预测模式下,可以根据整个数据集精确计算批量归一化所需的平均值和方差。

现在,我们了解一下批量归一化在实践中是如何工作的。
A
Aston Zhang 已提交
64 65


X
xiaotinghe 已提交
66
## 批量归一化层
A
Aston Zhang 已提交
67

R
half  
Rachel Hu 已提交
68 69 70
回想一下,批量归一化和其他图层之间的一个关键区别是,由于批量归一化在完整的小批次上运行,因此我们不能像以前在引入其他图层时那样忽略批处理的尺寸大小。
我们在下面讨论这两种情况:全连接层和卷积层,他们的批量归一化实现略有不同。

A
Aston Zhang 已提交
71

R
half  
Rachel Hu 已提交
72
### 全连接层
A
Aston Zhang 已提交
73

R
half  
Rachel Hu 已提交
74 75 76
通常,我们将批量归一化层置于全连接层中的仿射变换和激活函数之间。
设全连接层的输入为 u ,权重参数和偏差参数分别为 $\mathbf{W}$ 和 $\mathbf{b}$ ,激活函数为 $\phi$ ,批量归一化的运算符为 $\mathrm{BN}$ 。
那么,使用批量归一化的全连接层的输出的计算详情如下:
A
Aston Zhang 已提交
77 78 79

$$\mathbf{h} = \phi(\mathrm{BN}(\mathbf{W}\mathbf{x} + \mathbf{b}) ).$$

X
xiaotinghe 已提交
80
回想一下,均值和方差是在应用变换的"相同"小批量上计算的。
R
half  
Rachel Hu 已提交
81

A
Aston Zhang 已提交
82 83 84

### 卷积层

R
half  
Rachel Hu 已提交
85 86 87 88 89 90
同样,对于卷积层,我们可以在卷积层之后和非线性激活函数之前应用批量归一化。
当卷积有多个输出通道时,我们需要对这些通道的“每个”输出执行批量归一化,每个通道都有自己的拉伸(scale)和偏移(shift)参数,这两个参数都是标量。
假设我们的微批次包含 $m$ 个示例,并且对于每个通道,卷积的输出具有高度 $p$ 和宽度 $q$。
那么对于卷积层,我们在每个输出通道的 $m \cdot p \cdot q$ 个元素上同时执行每个批量归一化。
因此,在计算平均值和方差时,我们会收集所有空间位置的值,然后在给定通道内应用相同的均值和方差,以便在每个空间位置对值进行归一化。

A
Aston Zhang 已提交
91

R
initial  
Rachel Hu 已提交
92
### 预测过程中的批量归一化
A
Aston Zhang 已提交
93

R
half  
Rachel Hu 已提交
94
正如我们前面提到的,批量归一化在训练模式和预测模式下的行为通常不同。
R
Rachel Hu 已提交
95
首先,将训练好的模型用于预测时,我们不再需要样本均值中的噪声以及在微批次上估计每个小批次产生的样本方差了。
R
half  
Rachel Hu 已提交
96 97
其次,例如,我们可能需要使用我们的模型对逐个样本进行预测。
一种常用的方法是通过移动平均估算整个训练数据集的样本均值和方差,并在预测时使用它们得到确定的输出。
R
Rachel Hu 已提交
98 99
可见,和 dropout 一样,批量归一化层在训练模式和预测模式下的计算结果也是不一样的。

A
Aston Zhang 已提交
100

R
Rachel Hu 已提交
101 102

## 从零实现
A
Aston Zhang 已提交
103 104 105 106 107 108 109 110 111 112

下面,我们从头开始实现一个具有张量的批量归一化层。

```{.python .input}
from d2l import mxnet as d2l
from mxnet import autograd, np, npx, init
from mxnet.gluon import nn
npx.set_np()

def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
R
Rachel Hu 已提交
113
    # 通过 `autograd` 来判断当前模式是训练模式还是预测模式
A
Aston Zhang 已提交
114
    if not autograd.is_training():
R
Rachel Hu 已提交
115
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
A
Aston Zhang 已提交
116 117 118 119
        X_hat = (X - moving_mean) / np.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)
        if len(X.shape) == 2:
R
Rachel Hu 已提交
120
            # 使用全连接层的情况,计算特征维上的均值和方差
A
Aston Zhang 已提交
121 122 123
            mean = X.mean(axis=0)
            var = ((X - mean) ** 2).mean(axis=0)
        else:
R
Rachel Hu 已提交
124 125
            # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
            # 这里我们需要保持X的形状以便后面可以做广播运算
A
Aston Zhang 已提交
126 127
            mean = X.mean(axis=(0, 2, 3), keepdims=True)
            var = ((X - mean) ** 2).mean(axis=(0, 2, 3), keepdims=True)
R
Rachel Hu 已提交
128
        # 训练模式下,用当前的均值和方差做标准化
A
Aston Zhang 已提交
129
        X_hat = (X - mean) / np.sqrt(var + eps)
R
Rachel Hu 已提交
130
        # 更新移动平均的均值和方差
A
Aston Zhang 已提交
131 132
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
R
Rachel Hu 已提交
133
    Y = gamma * X_hat + beta  # 缩放和移位
A
Aston Zhang 已提交
134 135 136 137 138 139 140 141 142 143
    return Y, moving_mean, moving_var
```

```{.python .input}
#@tab pytorch
from d2l import torch as d2l
import torch
from torch import nn

def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
R
Rachel Hu 已提交
144
    # 通过 `is_grad_enabled` 来判断当前模式是训练模式还是预测模式
A
Aston Zhang 已提交
145
    if not torch.is_grad_enabled():
R
Rachel Hu 已提交
146
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
A
Aston Zhang 已提交
147 148 149 150
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)
        if len(X.shape) == 2:
R
Rachel Hu 已提交
151
            # 使用全连接层的情况,计算特征维上的均值和方差
A
Aston Zhang 已提交
152 153 154
            mean = X.mean(dim=0)
            var = ((X - mean) ** 2).mean(dim=0)
        else:
R
Rachel Hu 已提交
155 156
            # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
            # 这里我们需要保持X的形状以便后面可以做广播运算
A
Aston Zhang 已提交
157 158
            mean = X.mean(dim=(0, 2, 3), keepdim=True)
            var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
R
Rachel Hu 已提交
159
        # 训练模式下,用当前的均值和方差做标准化
A
Aston Zhang 已提交
160
        X_hat = (X - mean) / torch.sqrt(var + eps)
R
Rachel Hu 已提交
161
        # 更新移动平均的均值和方差
A
Aston Zhang 已提交
162 163
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
R
Rachel Hu 已提交
164
    Y = gamma * X_hat + beta  # 缩放和移位
A
Aston Zhang 已提交
165 166 167 168 169 170 171 172 173
    return Y, moving_mean.data, moving_var.data
```

```{.python .input}
#@tab tensorflow
from d2l import tensorflow as d2l
import tensorflow as tf

def batch_norm(X, gamma, beta, moving_mean, moving_var, eps):
R
Rachel Hu 已提交
174
    # 计算移动方差元平方根的倒数
A
Aston Zhang 已提交
175
    inv = tf.cast(tf.math.rsqrt(moving_var + eps), X.dtype)
R
Rachel Hu 已提交
176
    # 缩放和移位
A
Aston Zhang 已提交
177 178 179 180 181
    inv *= gamma
    Y = X * inv + (beta - moving_mean * inv)
    return Y
```

R
Rachel Hu 已提交
182 183 184
我们现在可以创建一个正确的 `BatchNorm` 图层。
这个层将保持适当的参数:拉伸 `gamma` 和偏移 `beta`, 这两个参数将在训练过程中更新。
此外,我们的图层将保存均值和方差的移动平均值,以便在模型预测期间随后使用。
A
Aston Zhang 已提交
185

R
Rachel Hu 已提交
186 187 188
撇开算法细节,注意我们实现图层的基础设计模式。
通常情况下,我们用一个单独的函数定义其数学原理,比如说 `batch_norm`
然后,我们将此功能集成到一个自定义层中,其代码主要处理簿记问题,例如将数据移动到训练设备(如 GPU)、分配和初始化任何必需的变量、跟踪移动平均线(此处为均值和方差)等。
X
ch6 ch7  
xiaotinghe 已提交
189
为了方便起见,我们并不担心在这里自动推断输入形状,因此我们需要指定整个特征的数量。
R
Rachel Hu 已提交
190
不用担心,深度学习框架中的批量归一化 API 将为我们解决上述问题,我们稍后将展示这一点。
A
Aston Zhang 已提交
191 192 193

```{.python .input}
class BatchNorm(nn.Block):
R
Rachel Hu 已提交
194 195
    # `num_features`:完全连接层的输出数量或卷积层的输出通道数。
    # `num_dims`:2表示完全连接层,4表示卷积层
A
Aston Zhang 已提交
196 197 198 199 200 201
    def __init__(self, num_features, num_dims, **kwargs):
        super().__init__(**kwargs)
        if num_dims == 2:
            shape = (1, num_features)
        else:
            shape = (1, num_features, 1, 1)
R
Rachel Hu 已提交
202
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
A
Aston Zhang 已提交
203 204
        self.gamma = self.params.get('gamma', shape=shape, init=init.One())
        self.beta = self.params.get('beta', shape=shape, init=init.Zero())
R
Rachel Hu 已提交
205
        # 不参与求梯度和迭代的变量,全在内存上初始化成0
A
Aston Zhang 已提交
206 207 208 209
        self.moving_mean = np.zeros(shape)
        self.moving_var = np.zeros(shape)

    def forward(self, X):
R
Rachel Hu 已提交
210 211
        # 如果 `X` 不在内存上,将 `moving_mean` 和 `moving_var` 
        # 复制到 `X` 所在显存上
A
Aston Zhang 已提交
212 213 214
        if self.moving_mean.ctx != X.ctx:
            self.moving_mean = self.moving_mean.copyto(X.ctx)
            self.moving_var = self.moving_var.copyto(X.ctx)
R
Rachel Hu 已提交
215
        # 保存更新过的 `moving_mean` 和 `moving_var`
A
Aston Zhang 已提交
216 217 218 219 220 221 222 223 224
        Y, self.moving_mean, self.moving_var = batch_norm(
            X, self.gamma.data(), self.beta.data(), self.moving_mean,
            self.moving_var, eps=1e-12, momentum=0.9)
        return Y
```

```{.python .input}
#@tab pytorch
class BatchNorm(nn.Module):
R
Rachel Hu 已提交
225 226
    # `num_features`:完全连接层的输出数量或卷积层的输出通道数。
    # `num_dims`:2表示完全连接层,4表示卷积层
A
Aston Zhang 已提交
227 228 229 230 231 232
    def __init__(self, num_features, num_dims):
        super().__init__()
        if num_dims == 2:
            shape = (1, num_features)
        else:
            shape = (1, num_features, 1, 1)
R
Rachel Hu 已提交
233
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
A
Aston Zhang 已提交
234 235
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
R
Rachel Hu 已提交
236
        # 不参与求梯度和迭代的变量,全在内存上初始化成0
A
Aston Zhang 已提交
237 238 239 240
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.zeros(shape)

    def forward(self, X):
R
Rachel Hu 已提交
241 242
        # 如果 `X` 不在内存上,将 `moving_mean` 和 `moving_var` 
        # 复制到 `X` 所在显存上
A
Aston Zhang 已提交
243 244 245
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
R
Rachel Hu 已提交
246
        # 保存更新过的 `moving_mean` 和 `moving_var`
A
Aston Zhang 已提交
247 248 249 250 251 252 253 254 255 256 257 258 259 260
        Y, self.moving_mean, self.moving_var = batch_norm(
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y
```

```{.python .input}
#@tab tensorflow
class BatchNorm(tf.keras.layers.Layer):
    def __init__(self, **kwargs):
        super(BatchNorm, self).__init__(**kwargs)

    def build(self, input_shape):
        weight_shape = [input_shape[-1], ]
R
Rachel Hu 已提交
261
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
A
Aston Zhang 已提交
262 263 264 265
        self.gamma = self.add_weight(name='gamma', shape=weight_shape,
            initializer=tf.initializers.ones, trainable=True)
        self.beta = self.add_weight(name='beta', shape=weight_shape,
            initializer=tf.initializers.zeros, trainable=True)
R
Rachel Hu 已提交
266
        # 不参与求梯度和迭代的变量,全在内存上初始化成0
A
Aston Zhang 已提交
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
        self.moving_mean = self.add_weight(name='moving_mean',
            shape=weight_shape, initializer=tf.initializers.zeros,
            trainable=False)
        self.moving_variance = self.add_weight(name='moving_variance',
            shape=weight_shape, initializer=tf.initializers.zeros,
            trainable=False)
        super(BatchNorm, self).build(input_shape)

    def assign_moving_average(self, variable, value):
        momentum = 0.9
        delta = variable * momentum + value * (1 - momentum)
        return variable.assign(delta)

    @tf.function
    def call(self, inputs, training):
        if training:
            axes = list(range(len(inputs.shape) - 1))
            batch_mean = tf.reduce_mean(inputs, axes, keepdims=True)
            batch_variance = tf.reduce_mean(tf.math.squared_difference(
                inputs, tf.stop_gradient(batch_mean)), axes, keepdims=True)
            batch_mean = tf.squeeze(batch_mean, axes)
            batch_variance = tf.squeeze(batch_variance, axes)
            mean_update = self.assign_moving_average(
                self.moving_mean, batch_mean)
            variance_update = self.assign_moving_average(
                self.moving_variance, batch_variance)
            self.add_update(mean_update)
            self.add_update(variance_update)
            mean, variance = batch_mean, batch_variance
        else:
            mean, variance = self.moving_mean, self.moving_variance
        output = batch_norm(inputs, moving_mean=mean, moving_var=variance,
            beta=self.beta, gamma=self.gamma, eps=1e-5)
        return output
```

R
Rachel Hu 已提交
303
##  使用批量归一化层的 LeNet
A
Aston Zhang 已提交
304

X
xiaotinghe 已提交
305
为了更好理解如何应用 `BatchNorm`,下面我们将其应用于 LeNet 模型( :numref:`sec_lenet` )。
R
Rachel Hu 已提交
306
回想一下,批量归一化是在卷积层或全连接层之后、相应的激活函数之前应用的。
A
Aston Zhang 已提交
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340

```{.python .input}
net = nn.Sequential()
net.add(nn.Conv2D(6, kernel_size=5),
        BatchNorm(6, num_dims=4),
        nn.Activation('sigmoid'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Conv2D(16, kernel_size=5),
        BatchNorm(16, num_dims=4),
        nn.Activation('sigmoid'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Dense(120),
        BatchNorm(120, num_dims=2),
        nn.Activation('sigmoid'),
        nn.Dense(84),
        BatchNorm(84, num_dims=2),
        nn.Activation('sigmoid'),
        nn.Dense(10))
```

```{.python .input}
#@tab pytorch
net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
    nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
    nn.MaxPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
    nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
    nn.Linear(84, 10))
```

```{.python .input}
#@tab tensorflow
R
Rachel Hu 已提交
341
# 回想一下,这个函数必须传递给 `d2l.train_ch6`。
D
dongh11 已提交
342
# 或者说为了利用我们现有的CPU/GPU设备,需要在`strategy.scope()`建立模型
A
Aston Zhang 已提交
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
def net():
    return tf.keras.models.Sequential([
        tf.keras.layers.Conv2D(filters=6, kernel_size=5,
                               input_shape=(28, 28, 1)),
        BatchNorm(),
        tf.keras.layers.Activation('sigmoid'),
        tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
        tf.keras.layers.Conv2D(filters=16, kernel_size=5),
        BatchNorm(),
        tf.keras.layers.Activation('sigmoid'),
        tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(120),
        BatchNorm(),
        tf.keras.layers.Activation('sigmoid'),
        tf.keras.layers.Dense(84),
        BatchNorm(),
        tf.keras.layers.Activation('sigmoid'),
        tf.keras.layers.Dense(10)]
    )
```

R
Rachel Hu 已提交
365
和以前一样,我们将在 Fashion-MNIST 数据集训练我们的网络。
X
xiaotinghe 已提交
366
这个代码与我们第一次训练 LeNet( :numref:`sec_lenet` )时几乎完全相同,主要区别在于学习率大得多。
A
Aston Zhang 已提交
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381

```{.python .input}
#@tab mxnet, pytorch
lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr)
```

```{.python .input}
#@tab tensorflow
lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
net = d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr)
```

R
Rachel Hu 已提交
382
让我们来看看从第一个批量归一化层中学到的拉伸参数 `gamma` 和偏移参数 `beta`
A
Aston Zhang 已提交
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397

```{.python .input}
net[1].gamma.data().reshape(-1,), net[1].beta.data().reshape(-1,)
```

```{.python .input}
#@tab pytorch
net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))
```

```{.python .input}
#@tab tensorflow
tf.reshape(net.layers[1].gamma, (-1,)), tf.reshape(net.layers[1].beta, (-1,))
```

D
dongh11 已提交
398
## 简明实现
A
Aston Zhang 已提交
399

R
Rachel Hu 已提交
400 401
除了使用我们刚刚定义的 `BatchNorm` ,我们也可以直接使用深度学习框架中定义的 `BatchNorm`
该代码看起来几乎与我们上面的代码相同。
A
Aston Zhang 已提交
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457

```{.python .input}
net = nn.Sequential()
net.add(nn.Conv2D(6, kernel_size=5),
        nn.BatchNorm(),
        nn.Activation('sigmoid'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Conv2D(16, kernel_size=5),
        nn.BatchNorm(),
        nn.Activation('sigmoid'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Dense(120),
        nn.BatchNorm(),
        nn.Activation('sigmoid'),
        nn.Dense(84),
        nn.BatchNorm(),
        nn.Activation('sigmoid'),
        nn.Dense(10))
```

```{.python .input}
#@tab pytorch
net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
    nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
    nn.MaxPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
    nn.Linear(84, 10))
```

```{.python .input}
#@tab tensorflow
def net():
    return tf.keras.models.Sequential([
        tf.keras.layers.Conv2D(filters=6, kernel_size=5,
                               input_shape=(28, 28, 1)),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Activation('sigmoid'),
        tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
        tf.keras.layers.Conv2D(filters=16, kernel_size=5),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Activation('sigmoid'),
        tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(120),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Activation('sigmoid'),
        tf.keras.layers.Dense(84),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Activation('sigmoid'),
        tf.keras.layers.Dense(10),
    ])
```

R
Rachel Hu 已提交
458 459
下面,我们使用相同的超参数来训练我们的模型。
请注意,通常高级 API 变体运行速度快得多,因为它的代码已编译为 C++ 或 CUDA,而我们的自定义代码由 Python 实现。
A
Aston Zhang 已提交
460 461 462 463 464 465 466 467

```{.python .input}
#@tab all
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr)
```

## 争议

R
Rachel Hu 已提交
468 469
直观地说,批量归一化被认为可以使优化更加平滑。
然而,我们必须小心区分投机直觉和对我们观察到的现象的真实解释。
X
xiaotinghe 已提交
470
回想一下,我们甚至不知道为什么简单的神经网络(多层感知机和传统的卷积神经网络)为什么如此有效。
R
Rachel Hu 已提交
471 472 473 474 475 476 477 478 479 480
即使在 dropout 和权重衰减的情况下,它们仍然非常灵活,因此无法通过传统的学习理论泛化保证来解释它们是否能够概括到看不见的数据。

在提出批量归一化的论文中,作者除了介绍了其应用,还解释了其原理:通过减少 *内部协变量偏移*(internal covariate shift)。
据推测,作者所说的“内部协变量转移”类似于上述的投机直觉,即变量值的分布在训练过程中会发生变化。
然而,这种解释有两个问题:
i)这种偏移与严格定义的 *协变量偏移*(covariate shift)非常不同,所以这个名字用词不当。
ii)这种解释只提供了一种不明确的直觉,但留下了一个有待后续挖掘的问题:为什么这项技术如此有效?。
本书旨在传达实践者用来发展深层神经网络的直觉。
然而,重要的是将这些指导性直觉与既定的科学事实区分开来。
最终,当你掌握了这些方法,并开始撰写自己的研究论文时,你会希望清楚地区分技术和直觉。
A
Aston Zhang 已提交
481

R
Rachel Hu 已提交
482 483
随着批量归一化的普及,“内部协变量偏移”的解释反复出现在技术文献的辩论,特别是关于“如何展示机器学习研究”的更广泛的讨论中。
Ali Rahimi 在接受 2017 年 NeurIPS 大会的“接受时间考验奖”(Test of Time Award)时发表了一篇令人难忘的演讲。他将“内部协变量转移”作为焦点,将现代深度学习的实践比作炼金术。
X
xiaotinghe 已提交
484
他对该示例进行了详细回顾 :cite:`Lipton.Steinhardt.2018` ,概述了机器学习中令人不安的趋势。
R
Rachel Hu 已提交
485 486 487 488 489
此外,一些作者对批量归一化的成功提出了另一种解释:在某些方面,批量归一化的表现出与原始论文 :cite:`Santurkar.Tsipras.Ilyas.ea.2018` 中声称的行为是相反的。

然而,与技术机器学习文献中成千上万类似模糊的声明相比,内部协变量偏移没有什么更值得批评。
很可能,它作为这些辩论的焦点而产生共鸣,要归功于它对目标受众的广泛认可。
批量归一化已经证明是一种不可或缺的方法,适用于几乎所有图像分类器,在学术界获得了数万引用。
A
Aston Zhang 已提交
490 491 492



R
initial  
Rachel Hu 已提交
493
## 小结
A
Aston Zhang 已提交
494

R
Rachel Hu 已提交
495 496 497 498 499
* 在模型训练过程中,批量归一化利用小批量的均值和标准差,不断调整神经网络的中间输出,使整个神经网络各层的中间输出值更加稳定。
* 批量归一化在全连接层和卷积层的使用略有不同。
* 批量归一化层和 dropout 层一样,在训练模式和预测模式下计算不同。
* 批量归一化有许多有益的副作用,主要是正则化。另一方面,”减少内部协变量偏移“的原始动机似乎不是一个有效的解释。

A
Aston Zhang 已提交
500 501 502

## 练习

R
Rachel Hu 已提交
503 504
1. 在使用批量归一化之前,我们是否可以从全连接层或卷积层中删除偏差参数?为什么?
1. 比较LeNet在使用和不使用批量归一化情况下的学习率。
A
Aston Zhang 已提交
505
    1. 绘制训练和测试准确度的提高。
R
Rachel Hu 已提交
506 507 508
    1. 你的学习率有多高?
1. 我们是否需要在每个层中进行批量归一化?尝试一下?
1. 你可以通过批量归一化来替换 dropout 吗?行为如何改变?
X
ch6 ch7  
xiaotinghe 已提交
509
1. 确定参数 `beta``gamma`,并观察和分析结果。
R
Rachel Hu 已提交
510 511
1. 查看高级 API 中有关 `BatchNorm` 的在线文档,以查看其他批量归一化的应用。
1. 研究思路:想想你可以应用的其他“归一化”转换?你可以应用概率积分变换吗?全秩协方差估计如何?
A
Aston Zhang 已提交
512 513 514 515 516 517 518 519 520 521 522 523

:begin_tab:`mxnet`
[Discussions](https://discuss.d2l.ai/t/83)
:end_tab:

:begin_tab:`pytorch`
[Discussions](https://discuss.d2l.ai/t/84)
:end_tab:

:begin_tab:`tensorflow`
[Discussions](https://discuss.d2l.ai/t/330)
:end_tab: