深度模型的优化(1):批标准化(Batch Normalization,BN)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度模型的优化(1):批标准化(Batch Normalization,BN)相关的知识,希望对你有一定的参考价值。

参考技术A 1 导入
1.1 独立同分布
  统计机器学习的经典假设:source domain和target domain的数据分布是一致的,也就是说,训练数据和测试数据满足独立同分布。这是通过训练的模型能在测试集上获得好的效果的前提。

1.2 Internal Covariate Shift
  Covariate Shift:是机器学习的一个问题,同时迁移学习也会涉及到这个概念。假设x是属于特征空间的某一样本点,y是标签。covariate这个词,其实就是指这里的x,那么Covariate Shift可以直接根据字面意思去理解:样本点x的变化。
  对于迁移学习的Covariate Shift的规范化描述:设源域(source domain)和目标域(target domain)的输入空间均为X, 输出空间均为Y. 源域的边际分布

  Internal Covariate Shift(ICS)描述:在BN的论文中,Covariate Shift指的是神经网络的输入X的分布老是变化,不符合独立同分布假设。而对于深度学习这种包含很多隐层的网络结构,每层的输出都是下一层的输入,在训练中,当梯度更新的时候,前一层的参数发生变化,使得前一层的输出发生变化,使得下一层的输入发生变化,这就意味着下一层的输入发生了Covariate Shift,这就是所谓的“Internal Covariate Shift”,Internal指的是深层网络的隐层,是发生在网络内部的事情,而不是Covariate Shift问题只发生在输入层。

  Internal Covariate Shift带来的问题:(1)上层网络需要不停调整来适应输入数据分布的变化,导致网络学习速度的降低:梯度下降的过程会让每一层的参数发生变化,进而使得每一层的线性与非线性计算结果分布产生变化。后层网络就要不停地去适应这种分布变化,这个时候就会使得整个网络的学习速率过慢。(2)网络的训练过程容易陷入梯度饱和区,减缓网络收敛速度(我的理解:就是指梯度爆炸。同时我认为使用Sigmoid或者tanh这样的激活函数容易梯度爆炸的原因:该激活函数的非饱和区较小,且单调递增,且梯度高,因此迭代相乘几次后非常容易越过非线性区进入饱和区)。

  Covariate Shift VS Internal Covariate Shift:关于Covariate Shift, 知乎 已经给出了不错的解释。但是针对Internal Covariate Shift,我们又被作者误导了。Covariate Shift ≠ Internal Covariate Shift,前者是迁移学习问题,后者是一个训练优化问题。正如 知乎 的层主所说的那样,各层添加零均值、单位方差的共轭分布,只针对数值,而不针对表征。实际上,如果把表征也”共荣化“,那就反而糟糕了。多层神经网络可以看作是一个迁移学习问题,层与层之间的抽象等级不同,比如学习一只猫,经过多层神经网络抽象后,就可以迁移分裂成多个机器学习问题:学习猫脸、学习猫腿、学习猫身、学习猫爪、学习猫尾。如果normalize之后,这五个部分的表征分布都变一样了,那么Deep Learning不是可以废掉了?所以说,normalize仅仅是数值层面的均衡化,以及表征层面的轻度破坏化。Internal Covariate Shift只针对数值偏移,而Covariate Shift才针对表征偏移。

3 算法
3.1 思路
  为了达到简化计算的目的,单独对每个特征进行标准化就可以了,让每个特征都有均值为0,方差为1的分布就OK。为了尽可能保留数据的原始表达能力,加个线性变换操作。BN是基于Mini-Batch的基础上计算的。

3.2 具体

  这三步就是我们在刚刚一直说的标准化工序, 但是公式的后面还有一个反向操作, 将 normalize 后的数据再扩展和平移。原来这是为了让神经网络自己去学着使用和修改这个扩展参数 γ和平移参数 β, 这样神经网络就能自己慢慢琢磨出前面的标准化操作到底有没有起到优化的作用, 如果没有起到作用, 我就使用 γ和β来抵消一些 normalization 的操作,当γ² = σ²和β = μ时,可以实现等价变换(Identity Transform)并且保留了原始输入特征的分布信息。

注: 在进行normalization的过程中,由于我们的规范化操作会对减去均值,因此,偏置项b可以被忽略掉或可以被置为0,即:BN(Wμ+b) = BN(Wμ)

3.3 梯度下降公式

  

3.4 测试时
  在测试时,可能需要测试的样本只有1个或者少数几个,此时用μ和σ可能是有偏估计。因此采用一个方法:u和σ被替换为训练阶段收集的运行均值这使得模型可以对单一样本评估,无须使用定义于整个小批量的u和σ。

4 总结
  (1)能够减少Interal Covariate Shift的问题,从而减少train的时间,使得对于deep网络的训练更加可行。(BN后的模型每一轮训练收敛快,但每一轮的计算量大,有文章称使用Batch Normalization会带来30%额外的计算开销。)

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

即使对于某组parameter同时乘以k倍后,最终的结果还是会keep不变的。

  (4)能够减少overfitting问题的发生:
  在Batch Normalization中,由于我们使用mini-batch的均值与方差作为对整体训练样本均值与方差的估计,尽管每一个batch中的数据都是从总体样本中抽样得到,但不同mini-batch的均值与方差会有所不同,这就为网络的学习过程中增加了随机噪音,与Dropout通过关闭神经元给网络训练带来噪音类似,在一定程度上对模型起到了正则化的效果。
  另外,原作者通过也证明了网络加入BN后,可以丢弃Dropout,模型也同样具有很好的泛化效果。

5 试验
  一个详细的试验在 Batch Normalization原理与实战 这篇博客里能看到。

6 参考:
Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift:
【机器学习】covariate shift现象的解释 - CSDN博客: https://blog.csdn.net/mao_xiao_feng/article/details/54317852
从Bayesian角度浅析Batch Normalization - 博客园: https://www.cnblogs.com/neopenx/p/5211969.html
Batch Normalization导读: https://zhuanlan.zhihu.com/p/38176412

PyTorch学习(十四)Batch_Normalization(批标准化)

神经网络太深的话,传到后面,受到激励函数饱和区间、失效期间的影响,最后导致神经网络学不到了。

批标准化:将分散数据统一的一种方法,优化神经网络。处理方式大概为下图:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码如下:


import torch
from torch import nn
from torch.nn import init
import torch.utils.data as Data
import matplotlib.pyplot as plt
import numpy as np

# torch.manual_seed(1)    # reproducible
# np.random.seed(1)

# 
N_SAMPLES = 2000
BATCH_SIZE = 64
EPOCH = 12
LR = 0.03
N_HIDDEN = 8 #8层
ACTIVATION = torch.tanh#采用的激活函数
B_INIT = -0.2   #

# 训练数据
x = np.linspace(-7, 10, N_SAMPLES)[:, np.newaxis]
noise = np.random.normal(0, 2, x.shape)
y = np.square(x) - 5 + noise

# 测试数据
test_x = np.linspace(-7, 10, 200)[:, np.newaxis]
noise = np.random.normal(0, 2, test_x.shape)
test_y = np.square(test_x) - 5 + noise

train_x, train_y = torch.from_numpy(x).float(), torch.from_numpy(y).float()
test_x = torch.from_numpy(test_x).float()
test_y = torch.from_numpy(test_y).float()

train_dataset = Data.TensorDataset(train_x, train_y)
train_loader = Data.DataLoader(dataset=train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2,)

# 看一下数据
plt.scatter(train_x.numpy(), train_y.numpy(), c='#FF9359', s=50, alpha=0.2, label='train')
plt.legend(loc='upper left')

#搭建网络
class Net(nn.Module):
    def __init__(self, batch_normalization=False):
        super(Net, self).__init__()
        self.do_bn = batch_normalization   #批处理
        self.fcs = []#定义两个list
        self.bns = []
        self.bn_input = nn.BatchNorm1d(1, momentum=0.5)   #  

        #下面定义hiddenlayer
        for i in range(N_HIDDEN):               # 
            input_size = 1 if i == 0 else 10
            fc = nn.Linear(input_size, 10)
            setattr(self, 'fc%i' % i, fc)       # 
            self._set_init(fc)                  # 
            self.fcs.append(fc)
            if self.do_bn:
                bn = nn.BatchNorm1d(10, momentum=0.5)
                setattr(self, 'bn%i' % i, bn)   # 
                self.bns.append(bn)

        self.predict = nn.Linear(10, 1)         # 
        self._set_init(self.predict)            #  

    def _set_init(self, layer):
        init.normal_(layer.weight, mean=0., std=.1)
        init.constant_(layer.bias, B_INIT)

    def forward(self, x):
        pre_activation = [x]
        if self.do_bn: x = self.bn_input(x)     # input batch normalization
        layer_input = [x]
        for i in range(N_HIDDEN):
            x = self.fcs[i](x)
            pre_activation.append(x)
            if self.do_bn: x = self.bns[i](x)   # batch normalization
            x = ACTIVATION(x)
            layer_input.append(x)
        out = self.predict(x)
        return out, layer_input, pre_activation


#没有和有批处理的神经网络
nets = [Net(batch_normalization=False), Net(batch_normalization=True)]


#创建两个优化函数
opts = [torch.optim.Adam(net.parameters(), lr=LR) for net in nets]

loss_func = torch.nn.MSELoss()


def plot_histogram(l_in, l_in_bn, pre_ac, pre_ac_bn):
    for i, (ax_pa, ax_pa_bn, ax, ax_bn) in enumerate(zip(axs[0, :], axs[1, :], axs[2, :], axs[3, :])):
        [a.clear() for a in [ax_pa, ax_pa_bn, ax, ax_bn]]
        if i == 0:
            p_range = (-7, 10);the_range = (-7, 10)
        else:
            p_range = (-4, 4);the_range = (-1, 1)
        ax_pa.set_title('L' + str(i))
        ax_pa.hist(pre_ac[i].data.numpy().ravel(), bins=10, range=p_range, color='#FF9359', alpha=0.5);ax_pa_bn.hist(pre_ac_bn[i].data.numpy().ravel(), bins=10, range=p_range, color='#74BCFF', alpha=0.5)
        ax.hist(l_in[i].data.numpy().ravel(), bins=10, range=the_range, color='#FF9359');ax_bn.hist(l_in_bn[i].data.numpy().ravel(), bins=10, range=the_range, color='#74BCFF')
        for a in [ax_pa, ax, ax_pa_bn, ax_bn]: a.set_yticks(());a.set_xticks(())
        ax_pa_bn.set_xticks(p_range);ax_bn.set_xticks(the_range)
        axs[0, 0].set_ylabel('PreAct');axs[1, 0].set_ylabel('BN PreAct');axs[2, 0].set_ylabel('Act');axs[3, 0].set_ylabel('BN Act')
    plt.pause(0.01)


if __name__ == "__main__":
    f, axs = plt.subplots(4, N_HIDDEN + 1, figsize=(10, 5))
    plt.ion()  
    plt.show()

    # 训练
    losses = [[], []]  # 

    for epoch in range(EPOCH):
        print('Epoch: ', epoch)
        layer_inputs, pre_acts = [], []
        for net, l in zip(nets, losses):
            net.eval()              #  
            pred, layer_input, pre_act = net(test_x)
            l.append(loss_func(pred, test_y).data.item())
            layer_inputs.append(layer_input)
            pre_acts.append(pre_act)
            net.train()             #  
        plot_histogram(*layer_inputs, *pre_acts)     #    

        for step, (b_x, b_y) in enumerate(train_loader):
            for net, opt in zip(nets, opts):     #  
                pred, _, _ = net(b_x)
                loss = loss_func(pred, b_y)
                opt.zero_grad()
                loss.backward()
                opt.step()    #  

    plt.ioff()

    plt.figure(2)
    plt.plot(losses[0], c='#FF9359', lw=3, label='Original')
    plt.plot(losses[1], c='#74BCFF', lw=3, label='Batch Normalization')
    plt.xlabel('step');plt.ylabel('test loss');plt.ylim((0, 2000));plt.legend(loc='best')

    #  
    #  
    [net.eval() for net in nets]    #  
    preds = [net(test_x)[0] for net in nets]
    plt.figure(3)
    plt.plot(test_x.data.numpy(), preds[0].data.numpy(), c='#FF9359', lw=4, label='Original')
    plt.plot(test_x.data.numpy(), preds[1].data.numpy(), c='#74BCFF', lw=4, label='Batch Normalization')
    plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='r', s=50, alpha=0.2, label='train')
    plt.legend(loc='best')
    plt.show()

结果图:
学习效果:原来的神经网络死掉了,而批处理的还可以进行学习。
在这里插入图片描述
误差变换曲线:原来的误差基本不变了,批处理的还在进行学习,减小误差。
在这里插入图片描述

以上是关于深度模型的优化(1):批标准化(Batch Normalization,BN)的主要内容,如果未能解决你的问题,请参考以下文章

12. 批标准化(Batch Normalization )

深度学习批归一化(Batch Normalization)

[转] 深入理解Batch Normalization批标准化

PyTorch学习(十四)Batch_Normalization(批标准化)

深度学习中epoch,batch的概念--笔记

深度学习中epoch,batch的概念--笔记