深度学习面试高频之Batch Normalization

在我找这份工作的时候,面试了将近有十家公司左右。总体分两个方面一个是偏项目,另外一个是偏深度学习的常见应用层。比如为什么使用batch norm可以加速收敛,卷积的实现原理和池化的作用等等。因为我是本科应届生,可能问的问题不会特别的深入。但是个人觉得如果掌握好这些基础知识,无论是在应聘岗位还是在实际工作中。都会起着决定性作用(最近在用C++实现前馈网络和网络结构的剪枝)。如果coding能力跟上,对数据熟悉。调优一个模型是没有太大问题的。
本文主要讲解batch norm的基础实现过程,真正在训练过程中会有momentum(均值,方差平滑)等相关参数

momentum 参数讲解

from torch import nn
import numpy as np
import torch
torch.manual_seed(18)
tensor = torch.randint(1,9,(1,3,3,3)).float() # 创建 [batch,channels,height,width] 
batch_norm = nn.BatchNorm2d(3) # channels = 3
# batch_norm.training =False
if __name__ == "__main__":
    print(tensor)
    for _ in range(10):
        batch_norm(tensor)
        print(batch_norm.running_mean)

在这里插入图片描述
我们发现这里的均值是有三个,这里每一个均值对应的一个channel中的feature map。我们来手动计算一下第一个特征图的均值(3+4+1+6+7+2+7+3+3)/9=4.0,我们发现running_mean不仅不等于均值4.0,反而还是在不断变化的。这是我在实现bn过程中遇到的坑,还记得我们前面说过的momentum参数吗?更新公式为 r u n n i n g _ m e a n = ( 1 − m o m e n t u m ) ∗ o l d _ m e a n + m o m e n t u m ∗ c u r r e n t _ m e a n running\_mean = (1-momentum)*old\_mean+momentum*current\_mean running_mean=(1momentum)old_mean+momentumcurrent_mean,这里是指均值,方差平滑的含义。个人感觉是减轻数据中噪音点的影响。这里再解释就很轻松啦,初始化的 o l d _ m e a n = 0 old\_mean=0 old_mean=0。bn默认的momentum=0.1,故第一次计算:0.4=0.14+0.90 ,第二次计算:0.76=0.14+0.90.4…,如果循环100次,均值就非常接近4了。
这种平滑的思想运用的很多,包括梯度平滑,权重平滑等等。都能够有效的增强模型训练过程中对异常点的鲁棒性。

实现原理

# -*- coding: utf-8 -*-
# @Time    : 2019/8/8 13:35
# @Author  : ljf
import torch
from torch import nn
from torch import optim
import numpy as np

# TODO
# 一 数据
train_x = torch.rand(size=[10,3,8,8])
train_y = torch.rand(size=[10,3,8,8])
# np.random.seed(18)
temp_x = [[1,2,3,4,5,6,7,8],
          [-1,-2,-3,-4,-5,-6,-7,-8],
          [1,2,3,4,5,6,7,8],
          [-1,-2,-3,-4,-5,-6,-7,-8],
          [1, 2, 3, 4, 5, 6, 7, 8],
          [-1, -2, -3, -4, -5, -6, -7, -8],
          [1, 2, 3, 4, 5, 6, 7, 8],
          [-1, -2, -3, -4, -5, -6, -7, -8]]
temp_y = np.array([[temp_x,temp_x,temp_x]])
test_x = torch.Tensor(temp_y)
print(test_x.size())
# print(test_x)
class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.bn = nn.BatchNorm2d(num_features=3)
        # for m in self.modules():
        #     if isinstance(m,nn.Conv2d):
        #         nn.init.constant_(m.weight,1.0)
        #         nn.init.constant_(m.bias,1.0)
    def forward(self, x):
        out = self.bn(x)
        return out
# 三 优化器,损失函数
is_evaluate = True
model = Net()
if is_evaluate:
    model.load_state_dict(torch.load("./pth/batchnorm2d.pth"))
    mean = test_x.mean(dim=[2, 3], keepdim=True)
    var = test_x.var(dim=[2, 3], keepdim=True)
    # print(model.bn.running_mean)
    _out = (test_x - model.bn.running_mean.view(1, 3, 1, 1)) / torch.sqrt(
        model.bn.running_var.view(1, 3, 1, 1) + model.bn.eps)
    _output = model.bn.weight.view(mean.size()) * _out + model.bn.bias.view(mean.size())
    print(_output)
    model.eval()
    # print(model.bn.eps)
    pred_y = model(test_x)
    print(pred_y)
else:
    optimizer = optim.SGD(model.parameters(), lr=0.001)
    criterion = nn.MSELoss()
    # 四 迭代数据
    for i in range(20):
   
        output = model(train_x)
        if i ==0:
            print(output.size())
        loss = criterion(output, train_y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    # 五 模型保存

    torch.save(model.state_dict(),"./pth/batchnorm2d.pth")

常见作用

防止梯度消失

针对sigmoid等激活函数,通过bn能够将数据放缩到线性区。sigmoid在区间[-4,4]之外它的导数基本接近0.

数据分布一致

机器学习的本质是学习分布,NN深了之后会产生偏移,BN使每一层的数据分布缩放到一致。

数据增强

一定程度的数据扩充,加了数据抖动的操作(加了偏移量?)

对参数w适应性更强,训练更稳定

  • 测试阶段使用 Batch Normalization?
    训练阶段每一个mini-batch的均值 μ b a t c h \mu_{batch} μbatch σ b a t c h 2 \sigma^2_{batch} σbatch2的均值作为测试使用,或者使用吴恩达课程中提出的指数加权平均

  • 范化和线性变换使得每一层网络的输入数据的均值和方差都在一定范围内,使得后一层网络不必不断去适应底层网络中输入的变化,从而实现每一层网络中独立学习,有利于提高整个神经网络的学习速度。

  • 学习率设置太高时,会使得参数更新步伐过大,容易出现震荡和不收敛。但是使用BN的网络将不会受到参数数值大小的影响。例如,我们对参数 W W W进行缩放得到 a W a W aW。对于缩放前的值 W x Wx Wx,我们设其均值为 μ 1 \mu_1 μ1,方差为 σ 1 2 \sigma_1^2 σ12;对于缩放值(也就是上一层的输出) α W x \alpha W x αWx,设其均值为 μ 2 \mu_2 μ2,方差为 σ 2 2 \sigma_2^2 σ22,于是我们有
    μ 2 = a μ 1 , σ 2 2 = a 2 σ 1 2 \mu_{2}=a \mu_{1}, \quad \sigma_{2}^{2}=a^{2} \sigma_{1}^{2} μ2=aμ1,σ22=a2σ12,如果忽略 ϵ \epsilon ϵ,则有:
    B N ( a W u ) = γ ⋅ a W x − μ 2 σ 2 2 + β = γ ⋅ a W x − a μ 1 a 2 σ 1 2 + β = γ ⋅ W u − μ 1 σ 1 2 + β = B N ( W x ) B N(a W u)=\gamma \cdot \frac{a W x-\mu_{2}}{\sqrt{\sigma_{2}^{2}}}+\beta=\gamma \cdot \frac{a W x-a \mu_{1}}{\sqrt{a^{2} \sigma_{1}^{2}}}+\beta=\gamma \cdot \frac{W u-\mu_{1}}{\sqrt{\sigma_{1}^{2}}}+\beta=B N(W x) BN(aWu)=γσ22 aWxμ2+β=γa2σ12 aWxaμ1+β=γσ12 Wuμ1+β=BN(Wx)

    ∂ B N ( ( a W ) u ) ∂ x = γ ⋅ a W σ 2 2 = γ ⋅ a W a 2 σ 1 2 = ∂ B N ( W x ) ∂ x \frac{\partial B N((a W) u)}{\partial x}=\gamma \cdot \frac{a W}{\sqrt{\sigma_{2}^{2}}}=\gamma \cdot \frac{a W}{\sqrt{a^{2} \sigma_{1}^{2}}}=\frac{\partial B N(W x)}{\partial x} xBN((aW)u)=γσ22 aW=γa2σ12 aW=xBN(Wx) 对输入 x x x求导

    ∂ B N ( ( a W ) x ) ∂ ( a W ) = γ ⋅ x σ 2 2 = γ ⋅ x a σ 1 2 = 1 a ⋅ ∂ B N ( W x ) ∂ W \frac{\partial B N((a W) x)}{\partial(a W)}=\gamma \cdot \frac{x}{\sqrt{\sigma_{2}^{2}}}=\gamma \cdot \frac{x}{a \sqrt{\sigma_{1}^{2}}}=\frac{1}{a} \cdot \frac{\partial B N(W x)}{\partial W} (aW)BN((aW)x)=γσ22 x=γaσ12 x=a1WBN(Wx)这里的 a w a w aw是放大后的权重值

我们可以看到,经过BN操作以后,权重的缩放值会被“抹去”,因此保证了输入数据分布稳定在一定范围内。另外,权重的缩放并不会影响到对 x x x 的梯度计算;并且当权重越大时,即 a a a 越大, 1 a \frac{1}{a} a1 越小,意味着权重 W W W 的梯度反而越小,这样BN就保证了梯度不会依赖于参数的scale,使得参数的更新处在更加稳定的状态。

因此,在使用Batch Normalization之后,抑制了参数微小变化随着网络层数加深被放大的问题,使得网络对参数大小的适应能力更强,此时我们可以设置较大的学习率而不用过于担心模型divergence的风险。

猜你喜欢

转载自blog.csdn.net/weixin_42662358/article/details/100089058