1、简介

BatchNormalization原文: BN原论文)

Batch Normalization是google团队在2015年提出的, 该方法能够加速网络的收敛并提高准确率。

image

本文分为以下几个部分:

  1. BN的原理
  2. 使用pytorch验证本文观点
  3. BN使用注意事项

2、Batch Normalization原理

在图像预处理中通常会对图像进行标准化处理, 这样能够加速网络的收敛, 对于Conv1来说, 输入就是满足某一分布的特征矩阵, 但是对Conv2而言的feature map就不一定满足某一分布规律了(注意这里所说的满足某一分布规律并不是指某一个feature map的数据要满足分布规律, 理论上指整个训练样本集所对应的feature map的数据要满足分布规律)。而我们的Batch Normalization的目的就是使我们的feature map满足均值为0,方差为1的分布规律。

image

下面是从原论文中截取的原话

image

对于一个拥有d维的输入x, 我们将对它的每一个维度进行标准化处理, 假设我们输入的x是RGB三通道的彩色图像,这里的d就是输入图像的channels即d=3, , 其中代表的就是R通道对应的特征矩阵, 以此类推。标准化处理也是分别对我们的R通道, G通道, B通道进行处理。

原文中提供了更加相似的计算公式。

image

上面提到让feature map满足某一分布规律, 理论上指整个训练样本集所对应的feature map的数据要满足分布规律,也就是说要算出整个训练集的feature map然后再进行标准化处理。对于一个大型的数据集明显是不可能的,所以论文中说的是batch normalization, 也就是我们计算一个batch数据的feature map然后再进行标准化(batch越大越接近整个数据集的分布, 效果越好) .

根据上面的公式可以知道代表计算的feature map每个维度(channel)的均值,注意是一个向量,而不是一个值向量的每个元素代表一个维度(channel)的均值。代表计算的feature map每个维度(channels)的方差, 是一个向量, 而不是一个值向量的每一个元素代表一个维度(channel)的方差, 然后根据计算标准化处理得到的值。下图给出了一个计算的示例。

image

所以可以得出

上面的示例展示了一个batch为2(两张图片)的Batch Normalization的计算过程, 假设feature1, feature2分别是由image1、image2经过一系列卷积池化后的得到的特征矩阵, feature的channel为2, 那么代表该batch的所有feature的channel1的数据, 同理代表该batch的所有feature的channel2的数据。然后分别计算的均值和方差, 得到两个向量。

然后再根据标准差计算公式分别计算每个channel的值(公式中的是一个很小的常量, 防止分母为零的情况)。

batch normalization之后,每个元素的计算公式为:

网络训练过程中, 通过一个batch一个batch的数据进行训练, 但是在预测过程中通常是输入一张图片进行预测,因此预测是batch size=1, 如果再通过上述方法计算均值和方差就没有意义了。

所以在训练过程中要去不断地计算每个batch的均值和方差, 并使用移动平均(moving average)的方法记录统计的均值和方差。在训练完成后我们可以近似认为所统计的均值和方法就等于整个训练集的均值和方差。然后在验证以及预测过程中, 就使用统计得到的均值和方差进行标准化处理。

其实还可以发现论文中还有两个参数, 用来调整数值分布的方差大小, 用来调整数据均值的位置。这两个参数是在反向传播过程中学习得到的, 默认为1, 默认为0。

2、使用pytorch进行试验

上面提到,在训练过程中, 均值和方差是通过计算当前批次数据得到的,而在验证和预测过程中使用的均值和方差是一个统计量,记为

\mu_{statistic + 1} = (1 - momentum) \mu_{statistic} + momentum \mu_{now}

\sigma^2_{statistic + 1} = (1 - momentum) \sigma^2_{statistic} + momentum\sigma^2_{statistic}

\sigma^2_{now} = \frac{1}{m}\sum_{i=1}^{m}(x_i - \mu_{now})^2

\sigma^2_{now} = \frac{1}{m-1}\sum_{i=1}^{m}(x_i - \mu_{now})^2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# !/usr/bin/env python
# -*-coding:utf-8 -*-
"""
# @File : pytorch_batch_normalization.py
# @Time :2023/11/3 11:13
# @Author :0399
# @version :python 3.9
# @Software : PyCharm
# @Description:
"""
# ================【功能:】====================
import numpy as np
import torch.nn as nn
import torch

def bn_process(feature, mean, var):
feature_shape = feature.shape
for i in range(feature_shape[1]):
# [batch, channel, height, width]
feature_t = feature[:, i, :, :]
mean_t = feature_t.mean()
# 总体标准差
std_t1 = feature_t.std()
# 样本标准差 当ddof=1时,计算的是样本的标准差
std_t2 = feature_t.std(ddof=1)

# bn process
# 这里记得加上eps和pytorch保持一致
feature[:, i, :, :] = (feature[:, i, :, :] - mean_t) / np.sqrt(std_t1 ** 2 + 1e-5)
# update calculating mean and var
mean[i] = mean[i] * 0.9 + mean_t * 0.1
var[i] = var[i] * 0.9 + (std_t2 ** 2) * 0.1
print(feature)

# 随机生成一个batch为2, channel为2, height, width均为2的特征向量
feature1 = torch.randn(2, 2, 2, 2)
# 初始化均值和方差
calculate_mean = [0.0, 0.0]
calculate_var = [1.0, 1.0]

# 注意使用copy()深拷贝
bn_process(feature1.numpy().copy(), calculate_mean, calculate_var)

bn = nn.BatchNorm2d(2, eps=1e-5)
output = bn(feature1)
print(output)
"""
# feature
[[[[ 0.648684 -0.85507095]
[ 0.58417344 1.8898206 ]]

[[ 0.97886676 1.3034139 ]
[-1.1044223 -0.48206437]]]


[[[-0.20273271 0.27278942]
[-1.3778524 -0.95981157]]

[[ 1.0157013 -1.6016374 ]
[-0.43428668 0.324429 ]]]]
# output
tensor([[[[ 0.6487, -0.8551],
[ 0.5842, 1.8898]],

[[ 0.9789, 1.3034],
[-1.1044, -0.4821]]],


[[[-0.2027, 0.2728],
[-1.3779, -0.9598]],

[[ 1.0157, -1.6016],
[-0.4343, 0.3244]]]], grad_fn=<NativeBatchNormBackward0>)

"""

结果明显一样,只是精度不同。

4、使用BN时需要注意的问题

训练时training参数设置为true, 验证时设置为false。在pytorch中可通过创建模型的model.train()和model.eval()方法控制。

batch size尽可能设置大一点, 设置很小后表现很糟糕, 设置越大,求解出来的均值和方差越接近整个训练集的均值和方差。

建议将bn层凡在conv和激活层之间,且卷积层不要使用偏置bias, 因为设置了偏置,最后的结果也一样。