Batch Normalization详解以及pytorch实验

Batch Normalization是google团队在2015年论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》提出的。通过该方法能够加速网络的收敛并提升准确率。在网上虽然已经有很多相关文章,但基本都是摆上论文中的公式泛泛而谈,bn真正是如何运作的很少有提及。本文主要分为以下几个部分:

(1)BN的原理

(2)使用pytorch验证本文的观点

(3)使用BN需要注意的地方(BN没用好就是个坑)

1.Batch Normalization原理

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

看到这里应该还是蒙的,不要慌,喝口水,慢慢来。下面是从原论文中截取的原话,注意标黄的部分:

“对于一个拥有d维的输入x,我们将对它的每一个维度进行标准化处理。”  假设我们输入的x是RGB三通道的彩色图像,那么这里的d就是输入图像的channels即d=3,x=(x^{(1)}, x^{(2)}, x^{(3)}),其中x^{(1)}就代表我们的R通道所对应的特征矩阵,依此类推。标准化处理也就是分别对我们的R通道,G通道,B通道进行处理。上面的公式不用看,原文提供了更加详细的计算公式:

我们刚刚有说让feature map满足某一分布规律,理论上是指整个训练样本集所对应feature map的数据要满足分布规律,也就是说要计算出整个训练集的feature map然后在进行标准化处理,对于一个大型的数据集明显是不可能的,所以论文中说的是Batch Normalization,也就是我们计算一个Batch数据的feature map然后在进行标准化(batch越大越接近整个数据集的分布,效果越好)。我们根据上图的公式可以知道\mu _{\ss }代表着我们计算的feature map每个维度(channel)的均值,注意\mu _{\ss }是一个向量不是一个值,\mu _{\ss }向量的每一个元素代表着一个维度(channel)的均值。\sigma_{\ss }^{2}代表着我们计算的feature map每个维度(channel)的方差,注意\sigma_{\ss }^{2}是一个向量不是一个值,\sigma_{\ss }^{2}向量的每一个元素代表着一个维度(channel)的方差,然后根据\mu _{\ss }和\sigma_{\ss }^{2}计算标准化处理后得到的值。下图给出了一个计算均值\mu _{\ss }和方差\sigma_{\ss }^{2}的示例:

上图展示了一个batch size为2(两张图片)的Batch Normalization的计算过程,假设feature1、feature2分别是由image1、image2经过一系列卷积池化后得到的特征矩阵,feature的channel为2,那么x^{(1)}代表该batch的所有feature的channel1的数据,同理x^{^{(2)}}代表该batch的所有feature的channel2的数据。然后分别计算x^{(1)}和x^{^{(2)}}的均值与方差,得到我们的\mu _{\ss }和\sigma_{\ss }^{2}两个向量。然后在根据标准差计算公式分别计算每个channel的值(公式中的\epsilon是一个很小的常量,防止分母为零的情况)。在我们训练网络的过程中,我们是通过一个batch一个batch的数据进行训练的,但是我们在预测过程中通常都是输入一张图片进行预测,此时batch size为1,如果在通过上述方法计算均值和方差就没有意义了。所以我们在训练过程中要去不断的计算每个batch的均值和方差,并使用移动平均(moving average)的方法记录统计的均值和方差,在训练完后我们可以近似认为所统计的均值和方差就等于整个训练集的均值和方差。然后在我们验证以及预测过程中,就使用统计得到的均值和方差进行标准化处理。

细心的同学会发现,在原论文公式中不是还有\gamma,\beta两个参数吗?是的,\gamma是用来调整数值分布的方差大小,\beta是用来调节数值均值的位置。这两个参数是在反向传播过程中学习得到的,\gamma的默认值是1,\beta的默认值是0。

2.使用pytorch进行试验

下面是我使用pytorch做的测试,代码如下:

(1)bn_process函数是自定义的bn处理方法验证是否和使用官方bn处理方法结果一致。在bn_process中计算输入batch数据的每个维度(这里的维度是channel维度)的均值和标准差(标准差等于方差开平方),然后通过计算得到的均值和总体标准差对feature每个维度进行标准化,然后使用均值和样本标准差更新统计均值和标准差。

(2)初始化统计均值是一个元素为0的向量,元素个数等于channel深度;初始化统计方差是一个元素为1的向量,元素个数等于channel深度,初始化\gamma=1,\beta=0。

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()
        # 样本标准差
        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的特征向量
# [batch, channel, height, width]
feature1 = torch.randn(2, 2, 2, 2)
# 初始化统计均值和方差
calculate_mean = [0.0, 0.0]
calculate_var = [1.0, 1.0]
# print(feature1.numpy())
 
# 注意要使用copy()深拷贝
bn_process(feature1.numpy().copy(), calculate_mean, calculate_var)
 
bn = nn.BatchNorm2d(2, eps=1e-5)
output = bn(feature1)
print(output)

  

首先我在最后设置了一个断点进行调试,查看下官方bn对feature处理后得到的统计均值和方差。我们可以发现官方提供的bn的running_mean和running_var和我们自己计算的calculate_mean和calculate_var是一模一样的(只是精度不同)。

然后我们打印出通过自定义bn_process函数得到的输出以及使用官方bn处理得到输出,明显结果是一样的(只是精度不同):

3.使用BN时需要注意的问题

(1)训练时要将traning参数设置为True,在验证时将trainning参数设置为False。在pytorch中可通过创建模型的model.train()和model.eval()方法控制。

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

(3)建议将bn层放在卷积层(Conv)和激活层(例如Relu)之间,且卷积层不要使用偏置bias,因为没有用,参考下图推理,即使使用了偏置bias求出的结果也是一样的\bg_white \large y_{i}^{b}=y_{i}

 最后给出李宏毅老师关于batch normalization的视频讲解:

https://www.bilibili.com/video/av9770302?p=10

bn:定义:就是对每一批的数据的通道数据进行处理。

        目的:使我们的feature map满足均值为0,方差为1的分布规律

         优点:压缩数据,提高运算能力

        多一个小数据:然后在根据标准差计算公式分别计算每个channel的值(公式中的\epsilon是一个很小的常量,防止分母为零的情

        缺点:计算出整个训练集的feature map然后在进行标准化处理,对于一个大型的数据集明显是不可能的。损失梯度也不是全局最优解,而是bitch量最优解。如果是把整个数据作为bitch,那就是一条直线。否则,反之。

                因此为了解决这个问题,出现了优化器。

                优化器:

                           SDG.:随机梯度下降法。正对权重学习率下降。wt+1 =wt-ln*g(wt)

                                     缺点:1.容易受样本的噪声的影响。(样本的标注错误,可能下降的方向相反)

                                                2.可能陷入局部最优解。

                                                3,速度慢

                                      优点:方向是对的

                           SDG+MOENTION:每次更新,源权值减去move的速度(由动力因素控制0.9)

                                       优点:就是把弯曲的线(Wt--->Wt+1)变成直线.更快,更准确。

                                                  容易受样本的噪声的影响减少

                                       适用:研究人,论文都选择。慢,方向对。

                              Adagrad: 自适应学习率。也就说让学习率自己变化。方向对,速度快

                              RmSprop:更改控制学习率。方向对,速度快

                              Adam:综合以上的几种方式。

                              

                                        

                                     

        pytorch使用:使用的是Xn与Xn+1的关系进行更新,动力为0.1

        特别:在预测过程中通常都是输入一张图片进行预测,此时batch size为1,如果在通过上述方法计算均值和方差就没有意义了

        代码上的使用:

                        (1)训练时要将traning参数设置为True,在验证时将trainning参数设置为False。在pytorch中可通过创建模型的model.train()和model.eval()方法控制。

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

                        (3)建议将bn层放在卷积层(Conv)和激活层(例如Relu)之间,且卷积层不要使用偏置bias,因为没有用,参考下图推理,即使使用了偏置bias求出的结果也是一样的
 

              

————————————————
版权声明:本文为CSDN博主「太阳花的小绿豆」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37541097/article/details/104434557

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PyTorch 中,可以使用 `nn.BatchNorm` 模块来实现 Batch Normalization。该模块需要指定输入数据的维度,即 `num_features` 参数。 以下是一个示例代码: ```python import torch.nn as nn # 定义一个包含 BatchNorm 的神经网络 class Net(nn.Module): def __init__(self, input_size, num_classes): super(Net, self).__init__() self.fc1 = nn.Linear(input_size, 256) self.bn1 = nn.BatchNorm1d(256) # BatchNorm 模块 self.relu = nn.ReLU() self.fc2 = nn.Linear(256, 128) self.bn2 = nn.BatchNorm1d(128) # BatchNorm 模块 self.fc3 = nn.Linear(128, num_classes) def forward(self, x): out = self.fc1(x) out = self.bn1(out) out = self.relu(out) out = self.fc2(out) out = self.bn2(out) out = self.relu(out) out = self.fc3(out) return out ``` 在上述代码中,我们定义了一个包含两个 BatchNorm 模块的神经网络,分别在全连接层和激活函数之间使用。在前向传递过程中,我们首先将输入 `x` 传递给第一个全连接层 `fc1`,然后将输出传递给 BatchNorm 模块 `bn1`,再传递给激活函数 `relu`,以此类推。最后,我们将输出传递给最后一个全连接层 `fc3`,并返回其结果。 要使用该神经网络进行训练,我们可以使用以下代码: ```python import torch.optim as optim import torch.nn.functional as F # 初始化神经网络和优化器 net = Net(input_size, num_classes) optimizer = optim.Adam(net.parameters(), lr=0.001) # 训练神经网络 for epoch in range(num_epochs): for batch_idx, (data, targets) in enumerate(train_loader): # 将数据传递给神经网络进行前向传递 outputs = net(data) # 计算损失函数 loss = F.cross_entropy(outputs, targets) # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step() # 打印训练的进度 if (batch_idx+1) % 100 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Step [{batch_idx+1}/{n_total_steps}], Loss: {loss.item():.4f}') ``` 在训练过程中,我们首先将数据传递给神经网络进行前向传递,并计算损失函数。然后,我们使用反向传播和优化器来更新神经网络的参数。最后,我们打印训练的进度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值