大纲如下:

  • 正则化之 weight_decay(正则化与偏差和方差, L2 正则化)
  • 正则化之 Dropout(概念以及如何用)
  • 标准化之 Batch Normalization
  • Normalization-layers(Layer Normalization、Instance Normalization、Groupb Normalization)

Ok, let’s go!

2. 正则化 weight_decay(L2 正则)

正则化从字面意思上可能一下子就懵逼,其实这是个纸老虎, 它就是一个减少方差的策略。那么这里就涉及到了一个概念方差, 什么是方差呢?

误差可分解为:偏差,方差与噪声之和。 即误差 = 偏差 + 方差 + 噪声

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

看下面这个图:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图1

我们通常所说的过拟合现象,也就是指的高方差,就是模型在训练集上训练的超级好,几乎全部都能拟合。 但是这种情况如果换一个数据集往往就会非常差, 看看下面这个图像:

系统学习Pytorch笔记九:正则化与标准化大总结 - 图2

我们今天学习的正则化,其实就是再降低方差,那么就有利于解决过拟合的问题, 那么是怎么做到的呢? 我们先来学习 L1 正则和 L2 正则。

image.png

如果加上这种正则项,就是希望我们的代价函数小,同时也希望我们这里的 w i w_i wi小,这样就说明每个样本的权重都很小,这样模型就不会太多的关注某种类型的样本, 模型参数也不会太复杂,有利于缓解过拟合现象。

那么 L1 正则和 L2 正则到底有啥区别呢? 就是正则项的式子不一样吗?
系统学习Pytorch笔记九:正则化与标准化大总结 - 图4

看上面的图,左边这个是涉及 L1 正则化的, 先看看这个, 上面彩色的是 Cost 的等高线图,也就是在同一条线上 Cost 是相等的,比如 A,B,C 三点, 产生的 Cost 是相等的,比如 Cost 等于 0.03. 黑色的矩形表示 L1 正则项的一个等高线,|w1| + |w2| = r, 假设这里的常数 r 为 1。那么矩形框上任意一个点产生的正则项都是 1。 我们现在考虑 A, B,C 三点的目标函数,他们的 Cost 是相等的,那么这三个点哪个点的 Regularization 最小呢? C 点的正则项是 1, 而我们会发现 A 点和 B 点的正则项都比 C 大(其实 A,B,C 这一条 Cost 等高线上就是 C 的正则项最小), 所以 C 点的目标函数最小。所以我们如果在 L1 的情况下找个最优解,既能损失最小,又能权重最小,那么往往这个最优解就发生在坐标轴上,也就是上面的 C 点。 这样一个神奇的现象发生了,w1 竟然等于 0, 就说明参数解有 0 项了, w1 就消失了。所以 L1 正则项一般会产生稀疏的解,也就是有 0 项的解。这是因为加上 L1 之后,我们参数的解往往会发生在坐标轴上导致某些参数的值为 0

我们看右边,这是 L2 正则。彩色的圈还是 Cost 等高线,下面黑色的圆圈是 L2 正则等高线( ∣ w 1 2 ∣ + ∣ w 2 2 ∣ = 1 |w_12|=1 ∣w12∣+∣w22∣=1), 和上面的分析一样,如果我们在 A’, B’, C’点确定最优解的话,依然是 C’点, 因为它在 Cost 相等的情况下正则最小。但是我们发现 L2 正则下不过出现某个参数为 0 的情况,而是 w1 和 w2 都比较小。所以 L2 正则项的最优的参数值很小概率出现在坐标轴上,因此每一维的参数都不会是 0。当最小化 ||w|| 时,就会使每一项趋近于 0

下面借这个机会也总结一下 L1 和 L2 正则化的特点(这个面试经常会问到)

L1 正则化的特点:

  • 不容易计算, 在零点连续但不可导, 需要分段求导
  • L1 模型可以将 一些权值缩小到零(稀疏)
  • 执行隐式变量选择。 这意味着一些变量值对结果的影响降为 0, 就像删除它们一样
  • 其中一些预测因子对应较大的权值, 而其余的(几乎归零)
  • 由于它可以提供稀疏的解决方案, 因此通常是建模特征数量巨大时的首选模型
  • 它任意选择高度相关特征中的任何一个, 并将其余特征对应的系数减少到 0
  • L1 范数对于异常值更具提抗力

L2 正则化的特点:

  • 容易计算, 可导, 适合基于梯度的方法
  • 将一些权值缩小到接近 0
  • 相关的预测特征对应的系数值相似
  • 当特征数量巨大时, 计算量会比较大
  • 对于有相关特征存在的情况, 它会包含所有这些相关的特征, 但是相关特征的权值分布取决于相关性。
  • 对异常值非常敏感
  • 相对于 L1 正则会更加准确

我们下面通过代码来学习 Pytorch 中的 L2 正则项, 在 Pytorch 中, L2 正则项又叫做 weight decay(权值衰减)。那么为啥这个东西叫做权值衰减呢? 怎么衰减了? 我们这样看:首先,我们原来的时候, 参数的更新公式是这样的:

image.png
我们知道λ的取值是 0-1 的,那么就是说每一次迭代之后,这个参数 w i w_i wi本身也会发生一个衰减。也就是说我们加上 L2 正则项与没有加 L2 正则项进行一个对比的话,加入 L2 正则项,这里的 w i w_i wi就会发生数值上的一个衰减。故这就是这个 L2 正则项称为权值衰减的原因。

下面我们就通过代码来学习 L2 正则项:正好再来回顾一下模型训练的五个步骤

  1. def gen_data(num_data=10, x_range=(-1, 1)):
  2. w = 1.5
  3. train_x = troch.linspace(*x_range, num_data).unsqueeze_(1)
  4. train_y = w*train_x + torch.normal(0, 0.5, size=train_x.size())
  5. test_x = torch.linspace(*x_range, num_data).unsqueeze_(1)
  6. test_y = w*test_x + torch.normal(0, 0.3, size=test_x.size())
  7. return train_x, train_y, test_x, test_y
  8. train_x, train_y, test_x, test_y = gen_data(x_range=(-1, 1))
  9. class MLP(nn.Module):
  10. def __init__(self, neural_num):
  11. super(MLP, self).__init__()
  12. self.linears = nn.Sequential(
  13. nn.Linear(1, neural_num),
  14. nn.ReLU(inplace=True),
  15. nn.Linear(neural_num, neural_num),
  16. nn.ReLU(inplace=True),
  17. nn.Linear(neural_num, neural_num),
  18. nn.ReLU(inplace=True),
  19. nn.Linear(neural_num, 1)
  20. )
  21. def forward(self, x):
  22. return self.linears(x)
  23. net_normal = MLP(neural_num=200)
  24. net_weight_decay = MLP(neural_num=200)
  25. optim_normal = torch.optim.SGD(net_normal.parameters(), lr=0.01, momentum=0.9)
  26. optim_wdecay = torch.optim.SGD(net_weight_decay.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-2)
  27. loss_func = torch.nn.MSELoss()
  28. writer = SummaryWriter(comment='_test tensorboard', filename_suffix='12345678)
  29. for epoch in range(2000):
  30. pred_normal, pred_decay = net_normal(train_x), net_weight_decay(train_x)
  31. loss_normal, loss_wdecay = loss_func(pred_normal, train_y), loss_func(pred_wdecay, train_y)
  32. optim_normal.zero_grad()
  33. optim_wdecay.zero_grad()
  34. loss_normal.backward()
  35. loss_wdecay.backward()
  36. optim_normal.step()
  37. optim_wdecay.step()
  38. if (epoch+1) % 200 == 0:
  39. for name, layer in net_normal.named_parameters():
  40. writer.add_histogram(name+'_grad_normal', layer.grad, epoch)
  41. writer.add_histogram(name+'_data_normal', layer, epoch)
  42. for name, layer in net_weight_decay.named_parameters():
  43. writer.add_histogram(name+'_grad_weight_decay', layer.grad, epoch)
  44. writer.add_histogram(name+'_data_weight_decay', layer, epoch)
  45. test_pred_normal, test_pred_wdecay = net_normal(test_x), net_weight_decay(test_x)
  46. plt.scatter(train_x.data.numpy(), train_y.data.numpy(), c='blue', s=50, alpha=0.3, label='train')
  47. plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='red', s=50, alpha=0.3, label='test')
  48. plt.plot(test_x.data.numpy(), test_pred_normal.data.numpy(), 'r-', lw=3, label='no weight decay')
  49. plt.plot(test_x.data.numpy(), test_pred_wdecay.data.numpy(), 'b--', lw=3, label='weight decay')
  50. plt.text(-0.25, -1.5, 'no weight decay loss={:.6f}'.format(loss_normal.item()), fontdict={'size': 15, 'color': 'red'})
  51. plt.text(-0.25, -2, 'weight decay loss={:.6f}'.format(loss_wdecay.item()), fontdict={'size': 15, 'color': 'red'})
  52. plt.ylim((-2.5, 2.5))
  53. plt.legend(loc='upper left')
  54. plt.title("Epoch: {}".format(epoch+1))
  55. plt.show()
  56. plt.close()

L2 正则使用也比较简单,就是在优化器里面指定weight_decay这个参数即可。我们可以看一下正则化模型和不带正则化模型的效果:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图6

可以清楚的发现, 不带正则化的红色线发生了过拟合现象。 下面通过 Tensorboard 观察一下梯度参数的一个分布情况,这里面可以明显的看出衰减表示的意思:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图7

左边的是不带正则化的模型参数的分布情况,我们可以看到从迭代开始到结束整个权值的分布都没有什么变化,右边是加入了 weight decay 的分布, 可以看到这个衰减的趋势, 这说明 L2 正则起作用了,使得在迭代过程中权值在不断的缩减,以至于模型不会过于复杂产生过拟合。

那么这个 L2 正则是怎么实现的呢? 我们再通过调试的方式看看背后的运行机制, 在optim_wdecay.step()这一句代码前打上断点,然后 debug,步入,就进入了 sgd 的 step 方法:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图8

好了,L2 正则化的使用和内部实现机制就到这里吧,要知道 L2 正则化干啥用,怎么用差不多就行了。 一般是在模型过拟合的时候用到这个方式, 当然除了 L2 正则化,在模型发生过拟合的时候还有其他的方式,比如 Dropout,也是常用的一种方式,我们可以看看这是个什么东西?

3. 正则化之 Dropout

Dropout 技术也是解决过拟合问题的一个非常重要的一个手段, 那么什么叫做 Dropout 呢? Dropout 又应该怎么用呢? 下面就分为两个方面来分别叙述:

3.1 Dropout 概念

Dropout 叫做随机失活。就是我们给出一个概率 (随机),让某个神经元的权重为 0(失活)。 下面给出个图直观感受一下:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图9

就是每一层,让某些神经元不起作用,这样就就相当于把网络进行简化了 (左边和右边可以对比),我们有时候之所以会出现过拟合现象,就是因为我们的网络太复杂了,参数太多了,并且我们后面层的网络也可能太过于依赖前层的某个神经元,加入 Dropout 之后, 首先网络会变得简单,减少一些参数,并且由于不知道浅层的哪些神经元会失活,导致后面的网络不敢放太多的权重在前层的某个神经元,这样就减轻了一个过渡依赖的现象, 对特征少了依赖, 从而有利于缓解过拟合。 这个类似于我们期末考试的时候有没有,老师总是会给我们画出一个重点,但是由于我们不知道这些重点哪些会真的出现在试卷上,所以就得把精力分的均匀一些,都得看看, 这样保险一些,也能泛化一点,至少只要是这些类型的题都会做。 而如果我们不把精力分的均匀一些,只关注某种题型, 那么准糊一波。

好了,上面就是我们 Dropout 的一个原理了, 也不是太难理解,尤其是有了这个期末考试的例子, 那么关于 Dropout 还有一个注意的问题,就是数据的尺度变化。 这个是什么意思呢? 我们用 Dropout 的时候是这样用的: 只在训练的时候开启 Dropout,而测试的时候是不用 Dropout 的,也就是说模型训练的时候会随机失活一部分神经元, 而测试的时候我们用所有的神经元,那么这时候就会出现这个数据尺度的问题, 所以测试的时候,所有权重都乘以 1-drop_prob, 以保证训练和测试时尺度变化一致, drop_prob 是我们的随机失活概率。 这个应该怎么理解呢? 我们依然拿上面那个图来说:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图10

看上面这个图,假设我们的输入是 100 个特征, 那么第一层的第一个神经元的表达式应该是这样, 这里先假设不失活:

image.png
这样采用 Dropout 的训练集和不采用 Dropout 的测试集的尺度就变成一致了, 写代码的时候要注意这个地方。

那么,我们如何实现 Dropout 呢?

3.2 nn.Dropout

Pytorch 中给我们提供了 Dropout 层, nn.Dropout
系统学习Pytorch笔记九:正则化与标准化大总结 - 图12

这里的 p 就是被舍弃概率,也就是失活概率。

下面就用上面正则化 L2 的代码实例看看不用 L2,而是加上 Dropout 的效果:

  1. def gen_data(num_data=10, x_range=(-1, 1)):
  2. w = 1.5
  3. train_x = torch.linspace(*x_range, num_data).unsqueeze_(1)
  4. train_y = w*train_x + torch.normal(0, 0.5, size=train_x.size())
  5. test_x = torch.linspace(*x_range, num_data).unsqueeze_(1)
  6. test_y = w*test_x + torch.normal(0, 0.3, size=test_x.size())
  7. return train_x, train_y, test_x, test_y
  8. train_x, train_y, test_x, test_y = gen_data(x_range=(-1, 1))
  9. class MLP(nn.Module):
  10. def __init__(self, neural_num, d_prob=0.5):
  11. super(MLP, self).__init__()
  12. self.linears = nn.Sequential(
  13. nn.Linear(1, neural_num),
  14. nn.ReLU(inplace=True),
  15. nn.Dropout(d_prob),
  16. nn.Linear(neural_num, neural_num),
  17. nn.ReLU(inplace=True),
  18. nn.Dropout(d_prob),
  19. nn.Linear(neural_num, neural_num),
  20. nn.ReLU(inplace=True),
  21. nn.Dropout(d_prob),
  22. nn.Linear(neural_num, 1),
  23. )
  24. def forward(self, x):
  25. return self.linears(x)
  26. net_prob_0 = MLP(neural_num=n_hidden, d_prob=0.)
  27. net_prob_05 = MLP(neural_num=n_hidden, d_prob=0.5)
  28. optim_normal = torch.optim.SGD(net_prob_0.parameters(), lr=lr_init, momentum=0.9)
  29. optim_reglar = torch.optim.SGD(net_prob_05.parameters(), lr=lr_init, momentum=0.9)
  30. loss_func = torch.nn.MSELoss()
  31. writer = SummaryWriter(comment='_test_tensorboard', filename_suffix="12345678")
  32. for epoch in range(max_iter):
  33. pred_normal, pred_wdecay = net_prob_0(train_x), net_prob_05(train_x)
  34. loss_normal, loss_wdecay = loss_func(pred_normal, train_y), loss_func(pred_wdecay, train_y)
  35. optim_normal.zero_grad()
  36. optim_reglar.zero_grad()
  37. loss_normal.backward()
  38. loss_wdecay.backward()
  39. optim_normal.step()
  40. optim_reglar.step()
  41. if (epoch+1) % disp_interval == 0:
  42. net_prob_0.eval()
  43. net_prob_05.eval()
  44. for name, layer in net_prob_0.named_parameters():
  45. writer.add_histogram(name + '_grad_normal', layer.grad, epoch)
  46. writer.add_histogram(name + '_data_normal', layer, epoch)
  47. for name, layer in net_prob_05.named_parameters():
  48. writer.add_histogram(name + '_grad_regularization', layer.grad, epoch)
  49. writer.add_histogram(name + '_data_regularization', layer, epoch)
  50. test_pred_prob_0, test_pred_prob_05 = net_prob_0(test_x), net_prob_05(test_x)
  51. plt.scatter(train_x.data.numpy(), train_y.data.numpy(), c='blue', s=50, alpha=0.3, label='train')
  52. plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='red', s=50, alpha=0.3, label='test')
  53. plt.plot(test_x.data.numpy(), test_pred_prob_0.data.numpy(), 'r-', lw=3, label='d_prob_0')
  54. plt.plot(test_x.data.numpy(), test_pred_prob_05.data.numpy(), 'b--', lw=3, label='d_prob_05')
  55. plt.text(-0.25, -1.5, 'd_prob_0 loss={:.8f}'.format(loss_normal.item()), fontdict={'size': 15, 'color': 'red'})
  56. plt.text(-0.25, -2, 'd_prob_05 loss={:.6f}'.format(loss_wdecay.item()), fontdict={'size': 15, 'color': 'red'})
  57. plt.ylim((-2.5, 2.5))
  58. plt.legend(loc='upper left')
  59. plt.title("Epoch: {}".format(epoch+1))
  60. plt.show()
  61. plt.close()
  62. net_prob_0.train()
  63. net_prob_05.train()

所以上面的代码里面要注意两点, 第一点就是 Dropout 加的时候注意放置的位置,第二点就是由于 Dropout 操作,模型训练和测试是不一样的,上面我们说了,训练的时候采用 Dropout 而测试的时候不用 Dropout, 那么我们在迭代的时候,就得告诉网络目前是什么状态,如果要测试,就得先用.eval()函数告诉网络一下子,训练的时候就用.train()函数告诉网络一下子。 我们下面看一下 Dropout 正则化后的效果:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图13

可以清楚的发现, 不带正则化的红色线发生了过拟合现象, 并且 Dropout 的效果和 L2 正则差不多,下面通过 Tensorboard 观察一下梯度参数的一个分布情况,看看是不是也和 L2 正则一样:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图14

从上面这两个图,也可以看出 Dropout 有利于收缩权重的分布。 类似于 L2 的一个功能,但是这里可看不出衰减, 不信? 我们对比一下 Dropout 和 L2:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图15

image.png

上面训练集和测试集的尺度依然是相等,并且还和原来数据的相等了。

好了,Dropout 的内容就是这些了,下面我们还要学习一个在网络中很实用的一个技术叫做 BatchNormalization。

4. 标准化之 BatchNormalization

4.1 BatchNormalization 是什么以及为啥用?

BatchNormalization 就是批标准化, 批指的是 mini-batch, 标准化也就是 0 均值 1 方差,看似这个东西比较简单,但是威力却是很强, 有下面几个优点(来自 2015 年原文《BatchNormalization:Accelerating Deep Network Train by Reducing Internal Covariate Shift》, 这篇论文堪称这一年深度学习界最重要的一篇论文):

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

好了,既然优点辣么多,肯定是要好好学习这个方法, 那么下面就看看到底 BatchNormlization 是怎么做到这么强大的, 计算方式到底是啥?下面就是 BatchNormlization 的算法流程:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图17

最后这句称为 affine transform, 可以增强模型的容纳能力,使得模型自己更加灵活,让模型自己去判断是否需要去改变数据的一个分布(这里的 γ \gamma γ和 β \beta β是可学习参数, 类似于神经网络的权值 w w w 和 b b b), 如果模型发现改变分布不是太好,那么让 γ = σ B 2 + ϵ \gamma = \sqrt{\sigma{\mathcal{B}}^{2}+\epsilon} γ=σB2+ϵ , β = μ B \beta = \mu{\mathcal{B}} β=μB, 这样我们的 y i y_i yi依然是等于 x i x_i xi,没有改变分布。这就是 affine transform 的功能,提供了一个可逆的操作, 到底需不需要改变数据分布,把这个权利交给模型自己学习。

BatchNormlization(BN)既然这么简单,为啥能起那么大的作用呢? 其实这里 BN 有点无心插柳柳成荫的感觉,可以看一下上面 BN 论文的标题,会发现这个算法提出本来是想解决 Internal Covariate Shift 这个问题的,这个问题在权重初始化那里介绍过,就是网络输出层的数值尺度的变化导致网络无法训练:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图18

还记得这几个公式吗:
image.png
我们发现每一层的方差竟然是前面所有层方差的连乘,那么假设有一层尺度上有点不正常,那么随着网络的加深,很容易引起梯度消失或者爆炸。所以权值初始化那里就考虑采用一种初始化的方式控制网络输出层的一个尺度。 所以 BN 的提出, 也是为了解决这个问题的,只不过解决了这个问题之后,竟然发现带来了一系列的优点,上面提到的那些。

下面就通过代码看一下,加了 BN 之后,为啥不用精心的设置权值初始化了, 依然是权值初始化那里的那份代码:

  1. class MLP(nn.Module):
  2. def __init__(self, neural_num, layers=100):
  3. super(MLP, self).__init__()
  4. self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
  5. self.bns = nn.ModuleList([nn.BatchNorm1d(neural_num) for i in range(layers)])
  6. self.neural_num = neural_num
  7. def forward(self, x):
  8. for (i, linear), bn in zip(enumerate(self.linears), self.bns):
  9. x = linear(x)
  10. x = torch.relu(x)
  11. if torch.isnan(x.std()):
  12. print("output is nan in {} layers".format(i))
  13. break
  14. print("layers:{}, std:{}".format(i, x.std().item()))
  15. return x
  16. def initialize(self):
  17. for m in self.modules():
  18. if isinstance(m, nn.Linear):
  19. nn.init.kaiming_normal_(m.weight.data)
  20. neural_nums = 256
  21. layer_nums = 100
  22. batch_size = 16
  23. net = MLP(neural_nums, layer_nums)
  24. inputs = torch.randn((batch_size, neural_nums))
  25. output = net(inputs)
  26. print(output)

首先我们先不用权值初始化,不用 BN, 看看网络出现的问题:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图20

那么我们加上权值初始化,由于网络里面用到了 relu, 所以这里使用 Kaiming 初始化方法, 看下效果:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图21

这里是我们精心设计了权值初始化的一个方法,考虑到 relu, 我们得用 Kaiming 初始化,考虑到 tanh,我们还得用 Xavier, 还是挺复杂的, 那么我们假设不用权值初始化,而是在网络层的激活函数前加上 BN 呢?
系统学习Pytorch笔记九:正则化与标准化大总结 - 图22

可以发现,强大的 BN 依然可以保证数据的尺度,并且好处就是我们不用再考虑用什么样的方式进行权值的初始化。下面再从人民币二分类的实验中就加权值初始化,加 BN 和啥都不加三者产生的一个效果:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图23

这里也可以看出 BN 层的作用,可以约束我们特征输入层的一个尺度范围,让它保持一个良好的分布,让模型训练起来更加简单。

4.2 Pytorch 中的 BatchNormalization

Pytorch 中提供了三种 BatchNorm 方法:

  • nn.BatchNorm1d
  • nn.BatchNorm2d
  • nn.BatchNorm3d

上面三个 BatchNorm 方法都继承__BatchNorm这个基类,初始化参数如下:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图24

这里的num_features表示一个样本的特征数量,这是最重要的一个参数。eps表示分母修正项, momentum表示指数加权平均估计当前 mean/var。 affine表示是否需要 affine transform, track_running_stats表示是训练状态还是测试状态,这个也是非常关键的,因为我们发现 momentum 那里有个均值和方差,如果是训练状态,那么就需要重新估计 mean 和方差,而如果是测试状态,就用训练时候统计的均值和方差。

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

  • running_mean: 均值
  • running_var: 方差
  • weight: affine transform 中的 γ
  • bias: affine transforom 中的 β

这四个属性分别对应我们公式中用到的四个属性:
image.png
这里的均值和方差是采用指数加权平均进行计算的, 不仅要考虑当前 mini-batch 的均值和方差,还考虑上一个 mini-batch 的均值和方差(当然是在训练的时候,测试的时候是用当前的统计值。)

runningmean = (1-momentum) prerunning_mean + momentum_mean_t
running_var = (1-momentum)
prerunning_var + momentum var_t

了解了三个方法的基本属性,下面得看看这三个方法到底用的时候有啥区别?

  • nn.BatchNorm1d -> input = B 特征数 1d 特征
    系统学习Pytorch笔记九:正则化与标准化大总结 - 图26
    下面直接动过代码看看上面这个一维的这个 BN 方法要怎么用:```python batch_size = 3
    num_features = 5
    momentum = 0.3

features_shape = (1)

feature_map = torch.ones(features_shape)
feature_maps = torch.stack([feature_map*(i+1) for i in range(num_features)], dim=0)
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)], dim=0)
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)

  1. print("\niteration:{}, running mean: {} ".format(i, bn.running_mean))
  2. print("iteration:{}, running var:{} ".format(i, bn.running_var))
  3. mean_t, var_t = 2, 0
  4. running_mean = (1 - momentum) * running_mean + momentum * mean_t
  5. running_var = (1 - momentum) * running_var + momentum * var_t
  6. print("iteration:{}, 第二个特征的running mean: {} ".format(i, running_mean))
  7. print("iteration:{}, 第二个特征的running var:{}".format(i, running_var))
  1. <br />下面看一下结果:<br />![](https://img-blog.csdnimg.cn/20200423205649732.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d1emhvbmdxaWFuZw==,size_1,color_FFFFFF,t_70#pic_center#align=left&display=inline&height=315&margin=%5Bobject%20Object%5D&originHeight=315&originWidth=706&status=done&style=none&width=706)<br />所以我们得知道,**当前 mini-bath 所得到的用于对数据进行 Normalize 的这个均值,不是当前 mini-batch 得到的均值,而是会考虑前面 mini-batch 的数据信息, 加权平均的一个均值和方差**。下面通过调试看看 BN 里面的四个非常重要的参数:均值,方差, gamma 和 beta 它们的 shape:<br />![](https://img-blog.csdnimg.cn/20200423210424103.png#pic_center#align=left&display=inline&height=190&margin=%5Bobject%20Object%5D&originHeight=190&originWidth=500&status=done&style=none&width=500)
  2. - nn.BatchNorm2d -> input = B _ 特征数 _ 2d 特征<br />![](https://img-blog.csdnimg.cn/2020042321123919.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d1emhvbmdxaWFuZw==,size_1,color_FFFFFF,t_70#pic_center#align=left&display=inline&height=301&margin=%5Bobject%20Object%5D&originHeight=301&originWidth=417&status=done&style=none&width=417)<br />卷积图像中经常是这种 2d 的。
  3. - nn.BatchNorm3d -> input = B _ 特征数 _ 3d 特征<br />![](https://img-blog.csdnimg.cn/20200423211844938.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d1emhvbmdxaWFuZw==,size_1,color_FFFFFF,t_70#pic_center#align=left&display=inline&height=338&margin=%5Bobject%20Object%5D&originHeight=338&originWidth=425&status=done&style=none&width=425)<br />这个在时空序列中会用到。
  4. 这就是 BatchNormalization 的内容了,下面再学习一些其他的 Normalization 的一些知识。
  5. <a name="f33dd38d"></a>
  6. ### 4.3 其他的 Normalization 方法
  7. 我们常见的 Normalization 方法其实有四种,分别是 Batch Normalization(BN)、Layer Normalization(LN)、Instance Normalization(IN)、Group Normalization(GN)。这四种方式既然都是 Normalization,那么有什么相同点和异同点呢?
  8. 相同点就是公式上:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/750176/1600941950851-ec36417e-ebc8-4222-8b71-2a4010a92161.png#align=left&display=inline&height=95&margin=%5Bobject%20Object%5D&name=image.png&originHeight=190&originWidth=530&size=15340&status=done&style=none&width=265)<br />而不同点,就是求取 μ B \mu_B μB和 σ B \sigma_B σB的方式不同。BatchNormalization 我们上面已经学习了,这个是在一个 batch 上去计算均值和方差,Layer Normalization 是以层为单位去计算均值和方差, Instance Normalization 主要在图像生成方法中使用的一个方法,Group Normalization 是按组为单位计算均值和方差。 下面我们就一一来看后面的是三个 Normalization 方法:
  9. 1. Layer Normalization<br />起因: BN 不适用于变长的网络,如 RNN, 所以提出了逐层计算均值和方差的思路。<br />![](https://img-blog.csdnimg.cn/20200423221015194.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d1emhvbmdxaWFuZw==,size_1,color_FFFFFF,t_70#pic_center#align=left&display=inline&height=298&margin=%5Bobject%20Object%5D&originHeight=298&originWidth=314&status=done&style=none&width=314)<br />BN 与 LN 的区别:
  10. - LN 中同层神经元输入拥有相同的均值和方差,不同的输入样本有不同的均值和方差;
  11. - BN 中则针对不同神经元输入计算均值和方差,同一个 batch 中的输入拥有相同的均值和方差。
  12. 还要注意, 在 LN 中不再有 running_mean 和 running_var, 并且 gamma 和 beta 为逐元素的。 下面我们看看 Pytorch 中的 LN:
  13. ```python
  14. nn.LayerNorm(normalized_shape, eps=1e-05, elementwise_affine=True)

这里的normalized_shape表示该层特征形状,这个依然是最重要的。 eps 表示分母修正项, elementwise_affine 表示是否需要 affine transform。
好了, 下面又得看看怎么用了,它是怎么进行计算的, 这个才是关键, LayerNorm 常用在 RNN 当中,在 CNN 和 FCN 中不如 BN 的效果好。
系统学习Pytorch笔记九:正则化与标准化大总结 - 图27
上面是代码部分, 下面我们看看结果:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图28
所以从这里我们也能基本上看出 LN 和 BN 的一个区别了, LN 它是以层为单位的, 而 BN 是以 batch 为单位的,后面还有个神图, 一看就差不多知道这几种标准化方法到底是怎么算的了。
如果我们把参数elementwise_affine设置为 False, 会报AttributeError: 'NoneType' object has no attribute 'shape', 另外还得说一下normalized_shape, 这个我们也可以自己指定, 但得注意一定要从最后维度开始, 这是啥意思?
系统学习Pytorch笔记九:正则化与标准化大总结 - 图29
最后这种情况的报错:

  1. RuntimeError: Given normalized_shape=[6, 3], expected input with shape [*, 6, 3], but got input of size[8, 6, 3, 4]
  1. Instance Normalization
    起因: BN 在图像生成中不适用, 思路就是逐个 Instance(channel) 计算均值和方差。比如在图像风格迁移中,每一个样本的风格是不一样的,所以我们不能像 BN 那样从多个样本里面去计算平均值和方差了。那么应该怎么算呢? 还是以上面的一个图为例:
    系统学习Pytorch笔记九:正则化与标准化大总结 - 图30
    下面看看 Pytorch 提供的 InstanceNorm 这个网络层怎么使用:python nn.InstanceNorm2d(num_features, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
    这里的num_features表示一个样本的特征数量 (最重要), eps表示分母修正项, momentum指数加权平均估计当前的 mean/var, affine是否需要 affine transform, track_running_stats表示训练状态还是测试状态。这个和 BN 的参数是一样的,并且也有 1d/2d/3d, 当然区分方法和 BN 那边一样。 下面依然是代码中看一下 Instance Normalization 是如何计算的:
    系统学习Pytorch笔记九:正则化与标准化大总结 - 图31
    下面看看结果:
    系统学习Pytorch笔记九:正则化与标准化大总结 - 图32
  2. Group Normalization
    起因: 小 batch 样本中, BN 估计的值不准, 这种 Normalization 的思路就是数据不够, 通道来凑。 一样用于大模型(小 batch size)的任务。
    系统学习Pytorch笔记九:正则化与标准化大总结 - 图33
    这个有点 LayerNorm 的感觉,只不过相当于 LayerNorm 进行了分组, 这个和 LN 一样,不再有 running_mean 和 running_var, gamma 和 beta 为逐个通道的。看看 Pytorch 提供的 GroupNorm:python nn.GroupNorm(num_groups, num_channels, eps=1e-05, affine=True)
    num_groups表示分组数, num_channels表示通道数(特征数), eps表示分母修正项, affine是否需要 affine transform。 这里前两个参数是必须的,要不然机器也不知道你要咋分组,每组多少个等。 提供了组数和通道数,两者的商就代表一组是多少个特征图, 这样从一组中求平均和方差。还要注意分组的时候要能整除。下面也是从代码中看看怎么用:
    系统学习Pytorch笔记九:正则化与标准化大总结 - 图34

好了,上面就是四种标准化方法了, BN, LN, IN 和 GN 都是为了克服Internal Covariate Shift(ICS)提出来的,它们的计算公式差不多,只不过计算均值和方差的时候采用的方式不同, 下面来一个神图:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图35

好吧,这个图把四维的数据画成了维的,如果不太好理解的话, 那么就采用上面的几个图理解一下, 把上面四种方式的计算合起来:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图36

关于这四种方法的详细介绍,可以参考这篇文章https://blog.csdn.net/liuxiao214/article/details/81037416

5. 总结

这次的学习内容就到这里了,这次的整理依然挺多, 主要是总结了一下 Pytorch 中常用的正则化和标准化的一些方法,依然是快速梳理一遍。

首先是正则化那块,正则化主要是缓解模型的过拟合问题,我们从 L2 正则化开始,L2 正则化也叫权重衰减,我们学习了 L2 正则的原理,L1 正则和 L2 正则的区别,然后学习了 L2 正则在 Pytorch 中的使用。 然后又学习了 Dropout 正则化,依然是原理和使用,并且对比了一下 L2 正则和 Dropout 正则的效果。

标准化主要是解决网络层输出的数据尺度变化不一致的问题, 首先学习了 Batch Normalization,这个非常重要,有很多的优点, 学习了它的原理和具体的使用方法,然后又介绍了其他三种标准化方法, LayerNorm Normalization、Instance Normalization 和 Group Normalization, 分别看了一下是如何计算的并且在 Pytorch 中如何使用的。 最后对比了一下这四种方法。

下面依然是一张思维导图把知识拎起来:
系统学习Pytorch笔记九:正则化与标准化大总结 - 图37

好了, 到这里为止,关于机器学习模型训练的五个步骤的细节部分就结束了, 我们从数据模块捋一捋, 数据模块,主要学习了 DataLoader 和 Dataset 的工作原理,构建数据生成器, 还顺带着学习了 transforms 处理图像。模型模块,学习了 Module, 模型容器, 还有各种卷积,池化,激活函数等层, 还学习了数据初始化的一些方法。 损失函数模块,介绍了各种损失函数的使用。 优化器模块,介绍了优化器和学习率调整策略。 迭代训练模块学习了 Tensorboard 可视化方法,学习了正则化和标准化。 通过上面的学习,基本上可以把机器学习模型训练的五个步骤的各个细节搭建一个框架出来了。

最后打算再安排一篇类似后记的东西,通过上面五个步骤,我们就可以通过 Pytorch 搭建一个模型并且进行有效的训练,而模型搭建完了之后我们要保存下来,以备后面的使用,并且在大型任务中我们不可能从头自己搭建模型,往往需要模型的迁移, 为了提高训练效率,我们往往需要使用 GPU, 最后再整理一些 Pytorch 中常见的报错作为结束。 所以下一篇的内容,我们从模型的保存与加载, 模型的微调技术, GPU 使用和 Pytorch 常见报错四方面来整理。 最后一篇了,继续 Rush 吧 😉

PS: 本次学习视频来自 B 站https://www.bilibili.com/video/BV1EE41177ot?from=search&seid=13894259699897815176, 时间长了有可能被和谐了。 所有代码链接:

链接:https://pan.baidu.com/s/1c5EYdd0w8j6w3g54KTxJJA
提取码:k7rh
https://blog.csdn.net/wuzhongqiang/article/details/105612578