目录

  • 一、正则化之weight_decay(L2正则)
  • 1.1 正则化及相关概念
  • 1.2 正则化策略(L1、L2)
  • (1)L1正则化
  • (2)L2正则化
  • 1.3 L2正则项——weight_decay
  • 二、正则化之Dropout
  • 2.1 Dropout概念
  • 2.2 nn.Dropout
  • 三、归一化之Batch Normalization(BN层)
  • 3.1 Batch Normalization介绍
  • 3.2 Pytorch的Batch Normalization 1d/2d/3d实现
  • (1)nn.BatchNorm1d input = B * 特征数 * 1d特征
  • (2)nn.BatchNorm1d input = B * 特征数 * 2d特征
  • (3)nn.BatchNorm1d input = B * 特征数 * 3d特征
  • 四、归一化之Normalization_layers
  • 4.1Layer Normalization(LN)
  • nn.LayerNorm
  • 4. 2Instance Normalization(IN)
  • nn.InstanceNorm
  • 4.3 Group Normalization(GN)
  • nn.GroupNorm
  • 五、总结
  • 参考博客


一、正则化之weight_decay(L2正则)

1.1 正则化及相关概念

Regularization,可以称为正则化或者是规范化。规则化就是在损失函数加上一些限制。

了解正则化之前,先来了解泛化误差:

泛化误差可分解为偏差、方差与噪声,泛化性能是由学习算法的能力、数据的充分性以及学习任务本身的难度所共同决定的。即泛化误差=偏差+方差+噪声

  • 偏差度量了学习算法的期望预测与真实结果的偏离程度, 即刻画了学习算法本身的拟合能力
  • 方差度量了同样大小的训练集的变动所导致的学习性能的变化,即刻画了数据扰动所造成的影响
  • 噪声则表达了在当前任务上任何学习算法所能达到的期望泛化误差的下界。

那么偏差、方差与数据集有什么关系呢?请看下图:

python 使用gpu 优化正则匹配字符串_python


通常说的过拟合现象,其实就是指高方差,意思是说模型在训练集的拟合效果很好,但是在测试集的效果却非常差, 看看下面这个图像:

python 使用gpu 优化正则匹配字符串_pytorch_02

所以我们最终总结,方差一般指的是数据模型得出来了,能不能对未知数据的扰动预测准确。而偏差说明在训练集当中就已经误差较大了,基本上在测试集中没有好的效果。

实际上,正则化是防止模型过拟合而采取的策略。我们对代价函数增加一个限制条件,限制其较高次的参数大小不能过大。

1.2 正则化策略(L1、L2)

还记我们在损失函数那一部分的内容吗?哈哈可能已经记不清了,下面再回顾一下。

损失函数: 衡量模型输出与真实标签的差异。
而当我们谈及损失函数的时候,往往会有三个概念: 损失函数, 代价函数, 目标函数。 实际上,这三者并不是同一个东西,下面简要了解下:

  • Loss Function:计算一个样本的模型输出与真实标签的差异 python 使用gpu 优化正则匹配字符串_pytorch_03
  • Cost Function:计算整个样本集的模型输出与真实标签的差异,是所有样本Loss function的平均值。即python 使用gpu 优化正则匹配字符串_正则化_04
  • Objective Function:就是Cost Function加上正则项,即Obj=Cost+Regularization

python 使用gpu 优化正则匹配字符串_正则化_05


这里的正则项部分有L1正则项和L2正则项两种。先来看看L1正则化:

(1)L1正则化

L1正则化,又称Lasso Regression,是指权值向量w中各个元素的绝对值之和。比如:向量A的权值为=[1,-1,3], 那么A的L1范数为 |1|+|-1|+|3|。

特点:L1正则化可以将 一些权值缩小到零(稀疏),那么这是如何实现的呢?我们看下图:(以二维情况为例)

python 使用gpu 优化正则匹配字符串_正则_06

图中彩色的一圈一圈的椭圆是Cost Function的等高线,也就是说同一椭圆线上的函数值是相等的。而左下角黑色的矩形线是L正则化的等高线。假设在红色的等高线上有A,B,C三点,显然红色的等高线是图中椭圆形等高线中函数值最小的(从B点往右上方任取一点,其距离原点的距离均大于B),并且B点加上L正则化项后的值也是最小的(这是因为B点的L1正则化项在最里面的矩形线)。此时我们会发现,B点对应的python 使用gpu 优化正则匹配字符串_正则化_07为0, 这个权重在模型中不起作用,可以删除,这便达到了权值稀疏的效果。

(2)L2正则化

L2正则化,指权值向量中各个元素的平方和然后再求平方根,对参数进行二次约束 。

特点:它会使优化求解稳定快速,使权重平滑,但不会形成稀疏解,所以L2适用于特征之间没有关联的情况

下面仍以一个图来说明:

python 使用gpu 优化正则匹配字符串_pytorch_08


彩色的椭圆线还是Cos Function的等高线,下面黑色的圆圈是L2正则等高线(python 使用gpu 优化正则匹配字符串_pytorch_09), 和上面的分析一样,如果我们在A’, B’, C’点确定最优解的话,依然是B’点, 因为它在Cost相等的情况下正则最小。但是我们发现L2正则下并不会出现某个参数为0的情况,而是python 使用gpu 优化正则匹配字符串_正则化_10python 使用gpu 优化正则匹配字符串_python_11都比较小,这是因为L1正则和L2正则的图像性质。此外我们还发现,L2正则化的等高线(二次约束)会更加的平滑,而L1正则化在图像的拐点处是非常尖锐的,所以不可求导在此处。总结一下就是,L2正则项的最优的参数值很小概率出现在坐标轴上,因此每一维的参数都不会是0。当最小化python 使用gpu 优化正则匹配字符串_人工智能_12时,就会使每一项趋近于0

下面也总结一下L1和L2正则化的特点

L1正则化的特点

  • 不容易计算, 在零点连续但不可导, 需要分段求导
  • L1模型可以将 一些权值缩小到零(稀疏)
  • L1范数对于异常值更具提抗力

    L2正则化的特点
  • 容易计算, 可导, 适合基于梯度的方法
  • 将一些权值缩小到接近0
  • 相关的预测特征对应的系数值相似
  • 对异常值非常敏感

1.3 L2正则项——weight_decay

从直观上讲,python 使用gpu 优化正则匹配字符串_人工智能_13正则化(weight_decay)使得训练的模型在兼顾最小化分类(或其他目标)的Loss的同时,使得权重w尽可能地小,从而将权重约束在一定范围内,减小模型复杂度;同时,如果将w约束在一定范围内,也能够有效防止梯度爆炸。

权重衰减等价于python 使用gpu 优化正则匹配字符串_pytorch_14范数正则化(regularization)。正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小,是应对过拟合的常用手段。我们先描述python 使用gpu 优化正则匹配字符串_pytorch_14范数正则化,再解释它为何又称权重衰减。

python 使用gpu 优化正则匹配字符串_pytorch_14范数正则化在模型原损失函数基础上添加python 使用gpu 优化正则匹配字符串_pytorch_14范数惩罚项,从而得到训练所需要最小化的函数。python 使用gpu 优化正则匹配字符串_pytorch_14范数惩罚项指的是模型权重参数每个元素的平方和与一个正的常数的乘积。以线性回归中的线性回归损失函数python 使用gpu 优化正则匹配字符串_pytorch_19为例,其中python 使用gpu 优化正则匹配字符串_人工智能_20是权重参数,python 使用gpu 优化正则匹配字符串_正则化_21是偏差参数,样本python 使用gpu 优化正则匹配字符串_正则化_22的输入为python 使用gpu 优化正则匹配字符串_python_23,标签为python 使用gpu 优化正则匹配字符串_正则_24,样本数为python 使用gpu 优化正则匹配字符串_python_25。将权重参数用向量python 使用gpu 优化正则匹配字符串_python_26表示,带有python 使用gpu 优化正则匹配字符串_pytorch_14范数惩罚项的新损失函数为python 使用gpu 优化正则匹配字符串_pytorch_28其中超参数python 使用gpu 优化正则匹配字符串_人工智能_29。当权重参数均为0时,惩罚项最小。当python 使用gpu 优化正则匹配字符串_正则_30较大时,惩罚项在损失函数中的比重较大,这通常会使学到的权重参数的元素较接近0。当python 使用gpu 优化正则匹配字符串_正则_30设为0时,惩罚项完全不起作用。上式中python 使用gpu 优化正则匹配字符串_pytorch_14范数平方python 使用gpu 优化正则匹配字符串_pytorch_33展开后得到python 使用gpu 优化正则匹配字符串_python_34。有了python 使用gpu 优化正则匹配字符串_pytorch_14范数惩罚项后,在小批量随机梯度下降中,我们将线性回归中权重python 使用gpu 优化正则匹配字符串_正则化_07python 使用gpu 优化正则匹配字符串_人工智能_37的迭代方式更改为python 使用gpu 优化正则匹配字符串_python_38可见,python 使用gpu 优化正则匹配字符串_pytorch_14范数正则化令权重python 使用gpu 优化正则匹配字符串_正则化_07python 使用gpu 优化正则匹配字符串_人工智能_37先自乘小于1的数,再减去不含惩罚项的梯度。因此,python 使用gpu 优化正则匹配字符串_pytorch_14范数正则化又叫权重衰减。权重衰减通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制,这可能对过拟合有效。实际场景中,我们有时也在惩罚项中添加偏差元素的平方和。

Pytorch中的weight decay是在优化器中实现的,在优化器中加入参数weight_decay即可,参数中的weight_decay等价于正则化系数python 使用gpu 优化正则匹配字符串_正则_43

例如下面的两个随机梯度优化器区别仅仅在于是否设置了参数weight_decay的值:

optim_normal = torch.optim.SGD(net_normal.parameters(), lr=lr_init, momentum=0.9)
optim_wdecay = torch.optim.SGD(net_weight_decay.parameters(), lr=lr_init, momentum=0.9, weight_decay=1e-2)

完整测试代码

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.utils.tensorboard import SummaryWriter
import random
import numpy as np
def set_seed(seed=1):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)

set_seed(1)  # 设置随机种子
n_hidden = 200
max_iter = 2000
disp_interval = 200
lr_init = 0.01


# ============================ step 1/5 数据 ============================
def gen_data(num_data=10, x_range=(-1, 1)):

    w = 1.5
    train_x = torch.linspace(*x_range, num_data).unsqueeze_(1)
    train_y = w*train_x + torch.normal(0, 0.5, size=train_x.size())
    test_x = torch.linspace(*x_range, num_data).unsqueeze_(1)
    test_y = w*test_x + torch.normal(0, 0.3, size=test_x.size())

    return train_x, train_y, test_x, test_y


train_x, train_y, test_x, test_y = gen_data(x_range=(-1, 1))


# ============================ step 2/5 模型 ============================
class MLP(nn.Module):
    def __init__(self, neural_num):
        super(MLP, self).__init__()
        self.linears = nn.Sequential(
            nn.Linear(1, neural_num),
            nn.ReLU(inplace=True),
            nn.Linear(neural_num, neural_num),
            nn.ReLU(inplace=True),
            nn.Linear(neural_num, neural_num),
            nn.ReLU(inplace=True),
            nn.Linear(neural_num, 1),
        )

    def forward(self, x):
        return self.linears(x)


net_normal = MLP(neural_num=n_hidden)
net_weight_decay = MLP(neural_num=n_hidden)

# ============================ step 3/5 优化器 ============================
optim_normal = torch.optim.SGD(net_normal.parameters(), lr=lr_init, momentum=0.9)
optim_wdecay = torch.optim.SGD(net_weight_decay.parameters(), lr=lr_init, momentum=0.9, weight_decay=1e-2)

# ============================ step 4/5 损失函数 ============================
loss_func = torch.nn.MSELoss()

# ============================ step 5/5 迭代训练 ============================

writer = SummaryWriter(comment='_test_tensorboard', filename_suffix="12345678")
for epoch in range(max_iter):

    # forward
    pred_normal, pred_wdecay = net_normal(train_x), net_weight_decay(train_x)
    loss_normal, loss_wdecay = loss_func(pred_normal, train_y), loss_func(pred_wdecay, train_y)

    optim_normal.zero_grad()
    optim_wdecay.zero_grad()

    loss_normal.backward()
    loss_wdecay.backward()

    optim_normal.step()
    optim_wdecay.step()

    if (epoch+1) % disp_interval == 0:

        # 可视化
        for name, layer in net_normal.named_parameters():
            writer.add_histogram(name + '_grad_normal', layer.grad, epoch)
            writer.add_histogram(name + '_data_normal', layer, epoch)

        for name, layer in net_weight_decay.named_parameters():
            writer.add_histogram(name + '_grad_weight_decay', layer.grad, epoch)
            writer.add_histogram(name + '_data_weight_decay', layer, epoch)

        test_pred_normal, test_pred_wdecay = net_normal(test_x), net_weight_decay(test_x)

        # 绘图
        plt.scatter(train_x.data.numpy(), train_y.data.numpy(), c='blue', s=50, alpha=0.3, label='train')
        plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='red', s=50, alpha=0.3, label='test')
        plt.plot(test_x.data.numpy(), test_pred_normal.data.numpy(), 'r-', lw=3, label='no weight decay')
        plt.plot(test_x.data.numpy(), test_pred_wdecay.data.numpy(), 'b--', lw=3, label='weight decay')
        plt.text(-0.25, -1.5, 'no weight decay loss={:.6f}'.format(loss_normal.item()), fontdict={'size': 15, 'color': 'red'})
        plt.text(-0.25, -2, 'weight decay loss={:.6f}'.format(loss_wdecay.item()), fontdict={'size': 15, 'color': 'red'})

        plt.ylim((-2.5, 2.5))
        plt.legend(loc='upper left')
        plt.title("Epoch: {}".format(epoch+1))
        plt.show()
        plt.close()

运行结果:

python 使用gpu 优化正则匹配字符串_正则化_44


由上图我们可以发现, 不带正则化的红色线发生了过拟合现象。 下面我们再通过调试的方式正则化是怎么实现的, 在optim_wdecay.step()前打上断点,然后debug,反复执行step into,到下图位置:

python 使用gpu 优化正则匹配字符串_python_45


再次反复执行step into,便进入了sgd的_singer_tensor_sgd()方法:

python 使用gpu 优化正则匹配字符串_正则化_46


上面便是一个简要的了解,下面看看Dropout吧。

二、正则化之Dropout

当面对更多的特征而样本不足时,线性模型往往会过拟合。
相反,当给出更多样本而不是特征,通常线性模型不会过拟合。
不幸的是,线性模型泛化的可靠性是有代价的。
简单地说,线性模型没有考虑到特征之间的交互作用。
对于每个特征,线性模型必须指定正的或负的权重,而忽略其他特征。

泛化性和灵活性之间的这种基本权衡被描述为偏差-方差权衡(bias-variance tradeoff)。
线性模型有很高的偏差:它们只能表示一小类函数。
然而,这些模型的方差很低:它们在不同的随机数据样本上可以得出相似的结果。

深度神经网络位于偏差-方差的另一端。
与线性模型不同,神经网络并不局限于单独查看每个特征,而是学习特征之间的交互。即使我们有比特征多得多的样本,深度神经网络也有可能过拟合。假设标签是随机均匀分配的,并且有10个类别,那么分类器在测试数据上很难取得高于10%的精度,那么这里的泛化差距就高达90%,如此严重的过拟合。
深度网络的泛化性质令人费解,而这种泛化性质的数学基础仍然是悬而未决的研究问题。本节,我们将谈谈Dropout改进深层网络的泛化性。

2.1 Dropout概念

Dropout也称为暂退法。回想一下:带有1个隐藏层和5个隐藏单元的多层感知机。
当我们将暂退法应用到隐藏层,以python 使用gpu 优化正则匹配字符串_正则_47的概率将隐藏单元置为零时,结果可以看作一个只包含原始神经元子集的网络。比如在下图中中,删除了python 使用gpu 优化正则匹配字符串_pytorch_48python 使用gpu 优化正则匹配字符串_pytorch_49,因此输出的计算不再依赖于python 使用gpu 优化正则匹配字符串_pytorch_48python 使用gpu 优化正则匹配字符串_pytorch_49,并且它们各自的梯度在执行反向传播时也会消失。这样,输出层的计算不能过度依赖于python 使用gpu 优化正则匹配字符串_正则化_52的任何一个元素。

python 使用gpu 优化正则匹配字符串_人工智能_53

通常,我们在测试时不用暂退法。给定一个训练好的模型和一个新的样本,我们不会丢弃任何节点,因此不需要标准化。也就是说只在训练的时候才会Dropout,而测试的时候是不用Dropout的,那么这时候就会出现这个数据尺度的问题,所以在测试的所有权重都乘以1-python 使用gpu 优化正则匹配字符串_正则_47(这里指的是Dropout的概率), 以保证训练和测试时尺度变化一致。

可能听起来很抽象,举个栗子:假设我们的输入是100个特征, 那么第一层的第一个神经元的表达式应该是这样, 这里先假设不失活:那么有python 使用gpu 优化正则匹配字符串_pytorch_55且假设python 使用gpu 优化正则匹配字符串_人工智能_56, 那么第一层第1个神经元的输出就为python 使用gpu 优化正则匹配字符串_正则_57, 注意这是不Dropout的情况。 假设Dropout的概率drop_prob=0.3,也就是在整体上差不多可以理解成30个不起作用,那么第一层的输出将变为70, 比起不Dropout而言30, 这就是一个数据尺度的变化。而测试的时候我们用的是全部的神经元,尺度会变成100,这就导致了模型在数值上有了一个差异。因此,我们在测试的时候,需要所有的权重乘以1-drop_prob这一项,这样采用Dropout的训练集和不采用Dropout的测试集的尺度就变成一致了。

不过好在torch.nn中有实现Dropout的模块,下面就来看看。

2.2 nn.Dropout

功能:Dropout层

参数:python 使用gpu 优化正则匹配字符串_正则_47表示被舍弃概率

测试代码

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.utils.tensorboard import SummaryWriter
import numpy as np
import random
def set_seed(seed=1):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)

set_seed(1)  # 设置随机种子
n_hidden = 200
max_iter = 2000
disp_interval = 400
lr_init = 0.01


# ============================ step 1/5 数据 ============================
def gen_data(num_data=10, x_range=(-1, 1)):

    w = 1.5
    train_x = torch.linspace(*x_range, num_data).unsqueeze_(1)
    train_y = w*train_x + torch.normal(0, 0.5, size=train_x.size())
    test_x = torch.linspace(*x_range, num_data).unsqueeze_(1)
    test_y = w*test_x + torch.normal(0, 0.3, size=test_x.size())

    return train_x, train_y, test_x, test_y


train_x, train_y, test_x, test_y = gen_data(x_range=(-1, 1))


# ============================ step 2/5 模型 ============================
class MLP(nn.Module):
    def __init__(self, neural_num, d_prob=0.5):
        super(MLP, self).__init__()
        self.linears = nn.Sequential(

            nn.Linear(1, neural_num),
            nn.ReLU(inplace=True),

            nn.Dropout(d_prob),
            nn.Linear(neural_num, neural_num),
            nn.ReLU(inplace=True),

            nn.Dropout(d_prob),
            nn.Linear(neural_num, neural_num),
            nn.ReLU(inplace=True),

            nn.Dropout(d_prob),
            nn.Linear(neural_num, 1),
        )

    def forward(self, x):
        return self.linears(x)


net_prob_0 = MLP(neural_num=n_hidden, d_prob=0.)
net_prob_05 = MLP(neural_num=n_hidden, d_prob=0.5)

# ============================ step 3/5 优化器 ============================
optim_normal = torch.optim.SGD(net_prob_0.parameters(), lr=lr_init, momentum=0.9)
optim_reglar = torch.optim.SGD(net_prob_05.parameters(), lr=lr_init, momentum=0.9)

# ============================ step 4/5 损失函数 ============================
loss_func = torch.nn.MSELoss()

# ============================ step 5/5 迭代训练 ============================

writer = SummaryWriter(comment='_test_tensorboard', filename_suffix="12345678")
for epoch in range(max_iter):

    pred_normal, pred_wdecay = net_prob_0(train_x), net_prob_05(train_x)
    loss_normal, loss_wdecay = loss_func(pred_normal, train_y), loss_func(pred_wdecay, train_y)

    optim_normal.zero_grad()
    optim_reglar.zero_grad()

    loss_normal.backward()
    loss_wdecay.backward()

    optim_normal.step()
    optim_reglar.step()

    if (epoch+1) % disp_interval == 0:

        net_prob_0.eval()
        net_prob_05.eval()

        # 可视化
        for name, layer in net_prob_0.named_parameters():
            writer.add_histogram(name + '_grad_normal', layer.grad, epoch)
            writer.add_histogram(name + '_data_normal', layer, epoch)

        for name, layer in net_prob_05.named_parameters():
            writer.add_histogram(name + '_grad_regularization', layer.grad, epoch)
            writer.add_histogram(name + '_data_regularization', layer, epoch)

        test_pred_prob_0, test_pred_prob_05 = net_prob_0(test_x), net_prob_05(test_x)

        # 绘图
        plt.scatter(train_x.data.numpy(), train_y.data.numpy(), c='blue', s=50, alpha=0.3, label='train')
        plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='red', s=50, alpha=0.3, label='test')
        plt.plot(test_x.data.numpy(), test_pred_prob_0.data.numpy(), 'r-', lw=3, label='d_prob_0')
        plt.plot(test_x.data.numpy(), test_pred_prob_05.data.numpy(), 'b--', lw=3, label='d_prob_05')
        plt.text(-0.25, -1.5, 'd_prob_0 loss={:.8f}'.format(loss_normal.item()), fontdict={'size': 15, 'color': 'red'})
        plt.text(-0.25, -2, 'd_prob_05 loss={:.6f}'.format(loss_wdecay.item()), fontdict={'size': 15, 'color': 'red'})

        plt.ylim((-2.5, 2.5))
        plt.legend(loc='upper left')
        plt.title("Epoch: {}".format(epoch+1))
        plt.show()
        plt.close()

        net_prob_0.train()
        net_prob_05.train()

上面的代码里面要注意两点, 第一点就是Dropout加的时候注意放置的位置,需要放在第二个全连接层的前面;第二点就是由于Dropout操作,模型训练和测试是不一样的。那么我们在迭代的时候,就得先用.eval()函数,训练的时候就用.train()函数。 我们下面看一下Dropout正则化后的效果:

python 使用gpu 优化正则匹配字符串_正则化_59

三、归一化之Batch Normalization(BN层)

3.1 Batch Normalization介绍

Batch Normalization,简称BN,是google团队在2015年论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》提出的。批指的是mini-batch, 标准化也就是0均值1方差,这是一个看起来很简单的神经网络训练的技巧,但是它不仅可以加快了模型的收敛速度,而且更重要的是在一定程度缓解了深层网络中“梯度弥散(特征分布较散)”的问题,从而使得训练深层网络模型更加容易和稳定。所以目前BN已经成为几乎所有卷积神经网络的标配技巧了。

Batch Normalization的优点如下:

  • 可以用更大学习率,加速模型收敛
  • 可以不用精心设计权值初始化
  • 可以不用Dropout或者较小的Dropout
  • 可以不用L2或者较小的weight decay
  • 可以不用局部响应标准化(AlexNet中用到过)

下面是BatchNormlization的算法流程:

python 使用gpu 优化正则匹配字符串_人工智能_60

算法中最后这句python 使用gpu 优化正则匹配字符串_正则_61被称为affine transform, 作用是增强模型的容纳能力,模型自己去判断是否需要去改变数据的一个分布(这里的python 使用gpu 优化正则匹配字符串_人工智能_62python 使用gpu 优化正则匹配字符串_正则化_63是可学习参数, 类似于神经网络的权值python 使用gpu 优化正则匹配字符串_正则_64python 使用gpu 优化正则匹配字符串_正则化_21),如果模型发现改变分布不是太好,则令python 使用gpu 优化正则匹配字符串_python_66再利用最后这句话,会发现python 使用gpu 优化正则匹配字符串_正则_67仍然等于python 使用gpu 优化正则匹配字符串_人工智能_68,相当于没有改变分布。

那么BN和权值初始化相比,是怎么实现这么好的效果的呢?下面先回顾一下上节权值初始化的内容:

python 使用gpu 优化正则匹配字符串_正则_69


第一个隐层的方差如下计算:

python 使用gpu 优化正则匹配字符串_人工智能_70由上面的公式我们发现每一层的方差是前面所有层方差的连乘。假设有一层尺度上不正常,那么随着网络的加深,很容易引起梯度消失或者爆炸。所以权值初始化那里就考虑采用一种初始化的方式控制网络输出层的一个尺度。 而BN的提出就是为了解决这个问题的,只不过解决了这个问题之后,刚好顺带了上面提到的那些一系列的优点。

测试代码如下:

import torch
import numpy as np
import torch.nn as nn
import random

def set_seed(seed=1):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)

set_seed(1)  # 设置随机种子


class MLP(nn.Module):
    def __init__(self, neural_num, layers=100):
        super(MLP, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
        self.bns = nn.ModuleList([nn.BatchNorm1d(neural_num) for i in range(layers)])
        self.neural_num = neural_num

    def forward(self, x):

        for (i, linear), bn in zip(enumerate(self.linears), self.bns):
            x = linear(x)
            # x = bn(x)
            x = torch.relu(x)

            if torch.isnan(x.std()):
                print("output is nan in {} layers".format(i))
                break

            print("layers:{}, std:{}".format(i, x.std().item()))

        return x

    def initialize(self):
        for m in self.modules():
            if isinstance(m, nn.Linear):

                # method 1
                # nn.init.normal_(m.weight.data, std=1)    # normal: mean=0, std=1

                # method 2 kaiming
                nn.init.kaiming_normal_(m.weight.data)


neural_nums = 256
layer_nums = 100
batch_size = 16

net = MLP(neural_nums, layer_nums)
# net.initialize()

inputs = torch.randn((batch_size, neural_nums))  # normal: mean=0, std=1

output = net(inputs)
print(output)

首先不用权值初始化,也不使用BN, 看看网络的输出结果:

python 使用gpu 优化正则匹配字符串_python_71


下面我们修改部分,权值初始化,由于用了非饱和函数relu, 所以使用Kaiming初始化方法。梯度在0.6左右,结果如下所示:

python 使用gpu 优化正则匹配字符串_python_72


同样地,修改部分代码,在激活函数层前加上BN,也能使梯度控制在一个合理的范围,结果如图:

python 使用gpu 优化正则匹配字符串_pytorch_73

通过上面的对比我们可以发现,BN和权值初始化都可以保证数据的尺度,而BN好处就是不用再考虑用什么样的方式进行权值的初始化。

3.2 Pytorch的Batch Normalization 1d/2d/3d实现

Pytorch中提供了三种BatchNorm方法:

  • nn.BatchNorm1d
  • nn.BatchNorm2d
  • nn.BatchNorm3d
    上面三个BatchNorm方法都继承__BatchNorm这个基类,初始化参数如下:

python 使用gpu 优化正则匹配字符串_pytorch_74

参数

作用

num_features

表示样本的特征数量(通道数)

eps

表示分母修正项,为数值稳定性而加到分母上的值,一般设置比较小的数:1e的-5次方,防止除以0导致错误

momentum

表示指数加权平均估计当前mean/var(通常设置为0.1)

affine

布尔变量,控制是否需要进行Affine,默认为打开

track_running_stats

是训练状态,还是测试状态。(如果在训练状态,均值、方差需要重新估计;如果在测试状态,会采用当前的统计信息,均值、方差固定的,但训练时这两个数据是会根据batch发生改变。)

需要说明的是这里其他几个参数都不重要,只需注意num_features就可以了。num_features就是需要归一化的那一维的维度。

而BatchNorm的三个方法也是有属性的:

属性

意义

running_mean

均值

running_var

方差

weight

affine transform中的python 使用gpu 优化正则匹配字符串_正则_75

bias

affine transforom中的python 使用gpu 优化正则匹配字符串_人工智能_76

这四个属性分别对应公式中用到的四个属性:

python 使用gpu 优化正则匹配字符串_正则_77

要说明的是,这里的均值和方差是采用指数加权平均(EMA,我们在上一节介绍过)进行计算的, 即:

running_mean = (1-momentum) * pre_running_mean + momentum*mean_t
running_var = (1-momentum) * pre_running_var + momentum * var_t

不仅要考虑当前mini-batch的均值和方差,还考虑上一个mini-batch的均值和方差(当然是在训练的时候,测试的时候是用当前的统计值。)

(1)nn.BatchNorm1d input = B * 特征数 * 1d特征

nn.BatchNorm1d本身不是给定输入矩阵,输出归一化结果的函数,而是定义了一个方法,再用这个方法去做归一化。

python 使用gpu 优化正则匹配字符串_人工智能_78

测试代码

import torch
import numpy as np
import torch.nn as nn
import random

def set_seed(seed=1):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)

set_seed(1)  # 设置随机种子

# ======================================== nn.BatchNorm1d
batch_size = 3# 3个样本
num_features = 5    # 5个特征
momentum = 0.3     # 计算后面均值,方差的时候用到

features_shape = (1) # 特征维度

feature_map = torch.ones(features_shape)                                                    # 1D
feature_maps = torch.stack([feature_map*(i+1) for i in range(num_features)], dim=0)         # 2D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)], dim=0)             # 3D
print("input data:\n{} shape is {}".format(feature_maps_bs, feature_maps_bs.shape))

bn = nn.BatchNorm1d(num_features=num_features, momentum=momentum)

running_mean, running_var = 0, 1 # 第一次迭代时需要初始化

for i in range(2):
    outputs = bn(feature_maps_bs)

    print("\niteration:{}, running mean: {} ".format(i, bn.running_mean))
    print("iteration:{}, running var:{} ".format(i, bn.running_var))

    mean_t, var_t = 2, 0

    running_mean = (1 - momentum) * running_mean + momentum * mean_t
    running_var = (1 - momentum) * running_var + momentum * var_t

    print("iteration:{}, 第二个特征的running mean: {} ".format(i, running_mean))
    print("iteration:{}, 第二个特征的running var:{}".format(i, running_var))

运行结果:

python 使用gpu 优化正则匹配字符串_人工智能_79

由此可以知道当前mini-bath所得到的用于对数据进行Normalize的这个均值,不是当前mini-batch得到的均值,而会考虑前面mini-batch的数据, 加权平均的一个均值和方差。接下来看看BN里面的四个非常重要的参数:均值,方差, gamma和beta:

python 使用gpu 优化正则匹配字符串_人工智能_80

(2)nn.BatchNorm1d input = B * 特征数 * 2d特征

python 使用gpu 优化正则匹配字符串_python_81

测试代码:(导入包的部分与nn.BatchNorm1d一致)

# ======================================== nn.BatchNorm2d
batch_size = 3
num_features = 6
momentum = 0.3

features_shape = (2, 2)

feature_map = torch.ones(features_shape)                                                    # 2D
feature_maps = torch.stack([feature_map*(i+1) for i in range(num_features)], dim=0)         # 3D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)], dim=0)             # 4D

print("input data:\n{} shape is {}".format(feature_maps_bs, feature_maps_bs.shape))

bn = nn.BatchNorm2d(num_features=num_features, momentum=momentum)

running_mean, running_var = 0, 1

for i in range(2):
    outputs = bn(feature_maps_bs)

    print("\niter:{}, running_mean.shape: {}".format(i, bn.running_mean.shape))
    print("iter:{}, running_var.shape: {}".format(i, bn.running_var.shape))

    print("iter:{}, weight.shape: {}".format(i, bn.weight.shape))
    print("iter:{}, bias.shape: {}".format(i, bn.bias.shape))

(3)nn.BatchNorm1d input = B * 特征数 * 3d特征

这个在时空序列中会用到。

python 使用gpu 优化正则匹配字符串_正则_82

测试代码:(导入包的部分与nn.BatchNorm1d一致)

# ======================================== nn.BatchNorm3d
batch_size = 3
num_features = 4
momentum = 0.3

features_shape = (2, 2, 3)

feature = torch.ones(features_shape)                                                # 3D
feature_map = torch.stack([feature * (i + 1) for i in range(num_features)], dim=0)  # 4D
feature_maps = torch.stack([feature_map for i in range(batch_size)], dim=0)         # 5D

print("input data:\n{} shape is {}".format(feature_maps, feature_maps.shape))

bn = nn.BatchNorm3d(num_features=num_features, momentum=momentum)

running_mean, running_var = 0, 1

for i in range(2):
    outputs = bn(feature_maps)

    print("\niter:{}, running_mean.shape: {}".format(i, bn.running_mean.shape))
    print("iter:{}, running_var.shape: {}".format(i, bn.running_var.shape))

    print("iter:{}, weight.shape: {}".format(i, bn.weight.shape))
    print("iter:{}, bias.shape: {}".format(i, bn.bias.shape))

四、归一化之Normalization_layers

常见的Normalization方法其实有四种,分别是Batch Normalization(BN)、Layer Normalization(LN)、Instance Normalization(IN)、Group Normalization(GN)。首先,来介绍一下深度网络中的数据维度。深度网络中的数据维度一般是**( N , C , H , W ) 或者( N , H , W , C ) **格式,N 是batch size,H / W是feature的高/宽,C是feature的channel(特征数或者通道数)。

  • Batch Normalization(BN):取不同样本的同一个通道的特征做归一化,逐特征维度归一化。这个就是对batch维度进行计算。所以假设5个100通道的特征图的话,就会计算出100个均值方差。5个batch中每一个通道就会计算出来一个均值方差。
  • Layer Normalization(LN):取的是同一个样本的不同通道做归一化,逐个样本归一化。5个10通道的特征图,LN会给出5个均值方差。
  • Instance Normalization(IN):仅仅对每一个图片的每一个通道做归一化,逐个通道归一化。也就是说,对【H,W】维度做归一化。假设一个特征图有10个通道,那么就会得到10个均值和10个方差;要是一个batch有5个样本,每个样本有10个通道,那么IN总共会计算出50个均值方差。
  • Group Normalization(GN):这个是介于LN和IN之间的一种方法。假设Group分成2个,那么10个通道就会被分成5和5两组。然后5个10通道特征图会计算出10个均值方差。

下图是一个可视化:

用表格进行一下对比:

名称

操作维度

优点

缺点

适用情况

BN

(N,H,W)

减轻对初始值的依赖,提高网络的训练效率。可以使用更高色学习速率

依赖Batch的大小,当Batch太小计算的均值和方差不稳定

深度固定的CNN/DNN等神经网络

LN

(C,H,W)

抛弃对Batch大小的依赖

由于其根据不同的通道进行了归一化,因此对相似度较大的特征LN会降低模型的表达能力

mini_Batch较小的场景、动态网络结构、RNN

IN

(H,W)

对于样式转换任务,IN在丢弃图像对比度信息方面优于BN

IN还可以看作是将LN单独应用于每个通道,就像通道的数量为1的LN一样,其大部分实际用效果较差

风格化类的图像应用

GN

LN与IN的中间体

由于GN可利用跨通道的依赖关系因此其优于IN;因为它允许对每一组通道学习不同的分布所以它也比LN好;当批大小较小时,GN始终优于BN

当批处理大小非常大时,GN的伸缩性不如BN,可能无法匹配BN的性能

Batch较小


4.1Layer Normalization(LN)

起因:BN不适用于变长的网络,如RNN(网络神经元长度不一样)

思路:逐层计算均值和方差(在相同样本中计算均值和方差)

python 使用gpu 优化正则匹配字符串_pytorch_83

LN和BN的区别

  • 不再有running_mean和running_var
  • gamma和beta为逐元素;BN中的gamma和beta的维度为特征个数

对于该变长网络,如果进行BN,只能计算前三个均值和方差,再多计算就会因为神经元得缺失而出现偏差。

nn.LayerNorm

python 使用gpu 优化正则匹配字符串_pytorch_84

参数

含义

normalized_shape

该层特征形状

eps

分母修正项,为数值稳定性而加到分母上的值,一般设置比较小的数:1e的-5次方,防止除以0导致错误

elementwise affine

是否需要affine transform(布尔变量,控制是否需要进行Affine,默认为打开)

注意:当输入是卷积的特征图,则求平均数的数据为channelHW;

测试代码

import torch
import numpy as np
import torch.nn as nn
import random

def set_seed(seed=1):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    
set_seed(1)  # 设置随机种子

# ======================================== nn.layer norm

'''
基本设置,8 * 6 * (3, 4)的输入,后三个维度是一层数据,第一个维度是batch_size
'''
batch_size = 8
num_features = 6

features_shape = (3, 4)

# 根据基本设置产生输入
feature_map = torch.ones(features_shape)  # 2D
feature_maps = torch.stack([feature_map * (i + 1) for i in range(num_features)], dim=0)  # 3D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)], dim=0)  # 4D


# LN的基本使用,LayerNorm(LN)的第一个参数在这里是[6, 3, 4],也就是这一层的特征形状

# 实际上下面几种写法都行,但是要注意一定得从后往前写
# feature_maps_bs shape is [8, 6, 3, 4],  B * C * H * W
# ln = nn.LayerNorm(feature_maps_bs.size()[1:], elementwise_affine=True)
# ln = nn.LayerNorm(feature_maps_bs.size()[1:], elementwise_affine=False)
# ln = nn.LayerNorm([6, 3, 4])
ln = nn.LayerNorm([6, 3, 4])

output = ln(feature_maps_bs)

'''
打印输出结果,weight.shape就是gamma的形状,这里是逐元素计算的。feature_maps_bs[0,...]是输入的第一层的数据,而output[0, ...]是输出的第一层的数据。
'''
print("Layer Normalization")
print(ln.weight.shape)
print(feature_maps_bs[0, ...])
print(output[0, ...])

运行结果:

python 使用gpu 优化正则匹配字符串_python_85

于是从运行结果中也能基本上看出LN和BN的区别: LN它是以层为单位的, 而BN是以batch为单位的。

此外如果我们把参数elementwise_affine设置为False, 会报AttributeError: 'NoneType' object has no attribute 'shape', 另外还得说一下normalized_shape,可以自己指定, 但得注意一定要从最后维度开始。

4. 2Instance Normalization(IN)

起因:BN在图像生成(Image Generation)中不适用

思路:逐Instance(channel)计算均值和方差

图像生成中,一个batch不同图像有不同的迁移风格,所以不能对图片的batch进行BN,所以提出了逐通道的IN。

下面通过一个示意图分析IN的具体作用,如果现在有三个样本,每个样本有三个特征图,每个特征图的大小为2*2,因为每个样本代表的风格是不同的,不能将batch_size大小的图片进行BN。

python 使用gpu 优化正则匹配字符串_pytorch_86

IN通过逐通道(每个特征图)计算均值和方差,如上图中,第一个样本有三个特征图,在每一个特征图中计算均值和方差。

nn.InstanceNorm

python 使用gpu 优化正则匹配字符串_pytorch_87

参数

含义

num_features

一个样本特征维度(通道数)

eps

分母修正项,为数值稳定性而加到分母上的值,一般设置比较小的数:1e的-5次方,防止除以0导致错误

momentum

移动平均的动量值(通常设置为0.1)

affine

是否需要affine transform(布尔变量,控制是否需要进行Affine,默认为打开)

track_running_stats

是训练状态,还是测试状态。(如果在训练状态,均值、方差需要重新估计;如果在测试状态,会采用当前的统计信息,均值、方差固定的,但训练时这两个数据是会根据batch发生改变。)

测试代码:(nn.InstanceNorm的使用和BN差不多,同样有nn.InstanceNorm1d,nn.InstanceNorm2d,nn.InstanceNorm3d,其使用方法和BN使用方法差不多)(导入包部分与LN相同)

batch_size = 3
num_features = 3
momentum = 0.3

features_shape = (2, 2)

feature_map = torch.ones(features_shape)    # 2D
feature_maps = torch.stack([feature_map * (i + 1) for i in range(num_features)], dim=0)  # 3D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)], dim=0)  # 4D

print("Instance Normalization")
print("input data:\n{} shape is {}".format(feature_maps_bs, feature_maps_bs.shape))

instance_n = nn.InstanceNorm2d(num_features=num_features, momentum=momentum)

for i in range(1):
    outputs = instance_n(feature_maps_bs)

    print(outputs)
    # print("\niter:{}, running_mean.shape: {}".format(i, bn.running_mean.shape))
    # print("iter:{}, running_var.shape: {}".format(i, bn.running_var.shape))
    # print("iter:{}, weight.shape: {}".format(i, bn.weight.shape))
    # print("iter:{}, bias.shape: {}".format(i, bn.bias.shape))

运行结果:因为IN是基于通道计算均值和方差,因此得到的output的均值和方差为零。

python 使用gpu 优化正则匹配字符串_pytorch_88

4.3 Group Normalization(GN)

起因:小batch样本中,BN估计的均值和方差不准确;
思路:数据不够,通道来凑;

和GN对比的区别

  • 不再有running_mean和running_var;
  • gamma和beta为逐通道(channel);

应用场景:大模型(小batch size)任务;大模型任务占据很大的内存,batch_size只能很小。

python 使用gpu 优化正则匹配字符串_正则化_89

如图,batch_size非常少,如果用BN,因为batch_size非常小导致估计的均值和方差不准确,会导致BN失效。GN会在通道上进行分组,再基于分组后得到的数据计算均值和方差。

nn.GroupNorm

python 使用gpu 优化正则匹配字符串_正则_90

参数

含义

num_groups

分组数,通常为2的n次方;

num_channels

通道数(特征数);

eps

分母修正项,为数值稳定性而加到分母上的值,一般设置比较小的数:1e的-5次方,防止除以0导致错误

affine

是否需要affine transform(布尔变量,控制是否需要进行Affine,默认为打开)

测试代码

batch_size = 2
num_features = 4

# 分组需要被整除,写成3会报错
num_groups = 2   # 3 Expected number of channels in input to be divisible by num_groups

features_shape = (2, 2)

feature_map = torch.ones(features_shape)    # 2D
feature_maps = torch.stack([feature_map * (i + 1) for i in range(num_features)], dim=0)  # 3D
feature_maps_bs = torch.stack([feature_maps * (i + 1) for i in range(batch_size)], dim=0)  # 4D


gn = nn.GroupNorm(num_groups, num_features) # 传进多少特征图并且要分成多少组
outputs = gn(feature_maps_bs)

print("Group Normalization")
print(gn.weight.shape)
print(outputs[0])

运行结果:

python 使用gpu 优化正则匹配字符串_人工智能_91

下面做一个总结,利用参考博客作者的美图~

python 使用gpu 优化正则匹配字符串_正则化_92

五、总结

这次内容首先是正则化,正则化是一种缓解模型的过拟合问题的策略,我们学习了L2正则的原理,L1正则和L2正则的区别,然后学习了L2正则等价于权重衰减。 然后又学习了Dropout正则化,并且对比了一下L2正则和Dropout正则的效果。

第二部分是标准化,这主要是解决网络层输出的数据尺度变化不一致的问题, 首先学习了Batch Normalization,学习了它的原理和具体的使用方法,然后又介绍了其他三种标准化方法, LayerNorm Normalization、Instance Normalization和Group Normalization, 最后对比这四种方法。

下面依然是一张思维导图把知识拎起来:

python 使用gpu 优化正则匹配字符串_正则化_93

参考博客

动手学深度学习

Pytorch教程