训练神经网络的批量标准化(使用 PyTorch)
构建神经网络是一门艺术,而非一个结果固定的过程。你无法预知最终能否得到有效的模型,而且有很多因素可能导致你的机器学习项目失败。
然而,随着时间的推移,您还将学会一套特定的笔触,这将大大提高您成功的几率。
在现代神经网络理论中,批量标准化可能是您在寻求信息时会遇到的关键概念之一。
这跟基于批量数据进行规范化有点关系……对吧?没错,但这其实就是用不同的词重复了名称。
事实上,批量标准化可以帮助你克服一种叫做内部协变量偏移的现象。这是什么?批量标准化是如何工作的?我们将在这篇博客中解答这些问题。
随后,我们将介绍批量标准化。在这里,我们还将了解它的含义、工作原理、作用以及重要性。这样,您将了解如何使用它来加速训练,甚至避免出现不收敛的情况。
本博客将涵盖:
- 批量标准化在高层次上的作用是什么。
nn.BatchNorm1d
PyTorch 中和之间的区别nn.BatchNorm2d
。- 如何使用 PyTorch 实现批量标准化。
它还包括一次测试运行,以查看与不应用它相比它是否真的能表现得更好。
准备好了吗?出发!
内部协变量偏移:训练缓慢和不收敛的可能解释
假设你有一个像这样的神经网络,它配备了 Dropout 神经元:
你可能还记得高级监督机器学习过程,训练神经网络包括对训练集进行前馈操作。在此操作过程中,数据被输入到神经网络,神经网络会为每个样本生成一个预测,该预测可以与目标数据(即真实值)进行比较。
这会产生由某些损失函数计算出的损失值。
基于损失函数,反向传播会计算所谓的梯度来改善损失,而梯度下降或自适应优化器实际上会改变神经网络神经元的权重。基于这种变化,模型有望在下一次迭代中表现更好,因为下一次迭代会重复这个过程。
改变输入分布
现在,让我们换个视角。你很可能在阅读上一节时,就已经将神经网络视为一个整体了。这完全没问题,这正是我们想要的效果。但现在,让我们将注意力集中到网络本身,把它看作是一堆堆叠在一起但又各自独立的层的集合。
每一层都会接收一些输入,通过与其权重的交互对其进行转换,然后输出结果,供下游的第一层使用。显然,对于输入层(以原始样本作为输入)和输出层(没有后续层)来说,情况并非如此,但你明白我的意思。
现在假设我们将整个训练集输入到神经网络中。第一层会将这些数据转换为其他值。然而,从统计学角度来看,这也是一个样本,因此它具有样本均值和样本标准差。这个过程在每一层都会重复:输入数据可以表示为具有均值 μ 和标准差 σ 的统计样本。
内部协变量偏移
现在请注意两件事:
- 首先,上面的讨论意味着,某些特定层的输入数据分布取决于所有上游层中发生的所有交互。
- 其次,这意味着上游层处理数据的方式的改变将改变该层的输入分布。
训练模型时会发生什么?实际上,你可以通过改变权重来改变层处理数据的方式。
Ioffe & Szegedy (2015) 在他们的论文《批量标准化:通过减少内部协变量偏移加速深度网络训练》中将此过程称为“内部协变量偏移”。他们定义如下:
由于训练期间网络参数的变化导致网络激活分布的变化。
I offe, S. 和 Szegedy, C. (2015)。批量归一化:通过减少内部协变量偏移加速深度网络训练。arXiv 预印本 arXiv:1502.03167。
为什么这很糟糕?
简单来说:它会减慢训练速度。
如果你使用非常严格的方法来定义监督机器学习模型,那么你会说机器学习会产生一个函数,该函数根据某些学习到的映射将某些输入映射到某些输出,这等于由数据中的真实底层映射所做的映射。
对于每一层来说也是如此:每一层本质上都是一个学习将某些输入映射到某些输出的函数,以便整个系统将原始输入映射到所需的输出。
现在想象一下,你从远处观察训练过程。缓慢但稳定地,每一层都在学习表示内部映射,整个系统开始表现出所需的行为。完美,不是吗?
是的,不过你也看到了过程中的一些波动。确实,你可以看到这些层在训练过程中会犯一些微小的错误,因为它们期望输入是某种类型的,但输入却略有不同。它们知道如何处理这种情况,因为变化非常小,但每次遇到这种变化时,它们都必须重新调整。因此,整个过程会花费更长的时间。
实际的机器学习过程也是如此。内部协方差偏移,即每个隐藏层输入数据分布的变化,意味着每一层都需要一些额外的时间来学习权重,从而使整个系统能够最小化整个神经网络的损失值。在极端情况下,虽然这种情况并不常见,但这种偏移甚至可能导致不收敛,或者无法整体学习映射。这种情况尤其发生在未经归一化且因此不适合机器学习的数据集中。
批量标准化简介
说到这种规范化:与其把它留给机器学习工程师,我们难道不能(至少部分地)在神经网络本身中解决这个问题吗?
正是这种思维方式促使 Ioffe & Szegedy (2015) 提出了批量标准化的概念:通过将每一层的输入标准化为可能接近 (μ= 0.0, σ = 1.0) 的学习表征,可以显著降低内部协方差偏移。因此,预计训练过程的速度将显著提升。
但它是如何工作的呢?
让我们来一探究竟。
小批量的每个特征的归一化
关于批量标准化,首先要理解的重要一点是它是基于每个特征进行的。
这意味着,例如,对于特征向量x= [0.23, 1.26, -2.41],并不是对每个维度进行同等的归一化。而是根据样本参数对每个维度进行单独的归一化。方面。
关于批量标准化,第二个需要理解的重要点是它利用小批量(minibatches)来执行标准化过程(Ioffe & Szegedy, 2015)。它避免了使用整个训练集的计算负担,同时假设如果小批量足够大,它们会接近数据集的样本分布。这是一个非常好的想法。
四步流程
现在来看看算法。对于特征向量x _B 中的每个特征 x_B^{(k)}(对于你的隐藏层来说,它不包含你的特征,而是该特定层的输入),批量归一化会在你的小批量 B 上通过四步过程对这些值进行归一化(Ioffe & Szegedy,2015):
- 计算小批量的平均值:
2.计算小批量的方差:
3.标准化值:
4.缩放和平移:
计算平均值和方差
前两个步骤很简单,非常常见,也是标准化步骤中必需的:计算小批量样本 x_B 的第 k 维的平均值 μ 和方差 σ²。
规范化
这些随后用于标准化步骤,其中只要小批量中的样本具有相同的分布并且忽略 ε 的值,预期分布就是 (0, 1) (Ioffe & Szegedy, 2015)。
你可能会问:确实,这个ε,它为什么在那里?
它是为了数值稳定性(Ioffe & Szegedy,2015)。如果方差 σ² 为零,就会出现除以零的误差。这意味着模型会变得数值不稳定。ε 的值可以通过取一个非常小但非零的值来抵消这种影响。
缩放和平移
现在,终于到了第四步:对标准化的输入值进行缩放和平移。我能理解为什么这很奇怪,因为我们在第三步就已经完成了标准化。
请注意,简单地对某一层的每个输入进行归一化可能会改变该层所能表示的内容。例如,对 S 型函数的输入进行归一化会将其限制在非线性函数的线性范围内。为了解决这个问题,我们确保插入到网络中的变换能够表示恒等变换。为此,我们为每个激活函数 x^{(k)} 引入一对参数 γ^{(k)} 和 β^{(k)},它们对归一化值进行缩放和平移:
Ioffe, S. 和 Szegedy, C. (2015)。批量归一化:通过减少内部协变量偏移来加速深度网络训练。arXiv 预印本 arXiv:1502.03167。
非线性的线性区域?代表恒等变换吗?这些是什么?
让我们将相当学术性的英语简化为更简单的版本。
首先,我们来谈谈“非线性的线性机制”。假设我们使用Sigmoid 激活函数,它是一个非线性激活函数(也称“非线性”),在 Ioffe & Szegedy 的论文发表于 2015 年时,这种函数仍然相当常见。
它看起来像这样:
假设我们将其添加到某个任意层。
如果没有批量标准化,该层的输入就不会具有近似(0,1)的分布,因此理论上更有可能取相当大的值(例如 2.5623423...)。
假设我们的层除了传递数据之外什么都不做(为了使我们的情况更简单),这些输入值的激活会产生具有非线性斜率的输出:如您在上图中所看到的,对于域 [2, 4] 中的激活函数的输入,输出会弯曲一点。
然而,对于 ≈ 0 的输入,情况并非如此:在近似 [-0.5, 0.5] 的输入域中,输出没有弯曲,实际上看起来像是一个线性函数。这完全降低了非线性激活的效果,从而降低了模型的性能,这可能不是我们想要的!
等等:我们不是已经标准化为(0, 1)了吗?这意味着我们激活函数的输入在每一层都可能位于 [-1, 1] 的取值范围内吗?哎呀!
这就是为什么作者引入了一个具有一些参数 γ 和 β 的缩放和平移操作,利用这些参数可以在训练期间调整标准化,在极端情况下甚至可以“表示身份变换”(即输入的内容会再次出现 - 完全消除批量标准化步骤)。
这些参数是在训练期间与其他参数一起学习的(Ioffe & Szegedy,2015)。
继续我们的小例子
现在,让我们修改上面的小例子,我们的特征向量x = [0.23, 1.26, -2.41]。
假设我们使用了每批 2 个样本的小批量方法(我知道有点少,但足以解释),集合中还有另一个向量x _a = [0.56, 0.75, 1.00],我们的批量标准化步骤将如下所示(假设 γ = β = 1):
<span style="background-color:#f9f9f9"><span style="color:#242424">| 特征 | 平均值 | 方差 | 输入 | 输出 |
| [0.23,0.56] | 0.395 | 0.054 | 0.23 | -0.710 |
| [1.26,0.75] | 1.005 | 0.130 | 1.26 | 0.707 |
| [-2.41,1.000] | -0.705| 5.81 | -2.41 | -0.707 | </span></span>
我们可以看到,当 γ =β=1 时,我们的值被标准化为大约 (0, 1) 的分布 — 其中带有一些 ε 项。
批量标准化的好处
理论上,在神经网络中使用批量标准化有一些假定的好处(Ioffe & Szegedy,2015):
- 该模型对超参数调整不太敏感。也就是说,以前较大的学习率会导致模型无用,但现在较大的学习率是可以接受的。
- 现在,权重初始化不再那么重要了。
- 可以删除用于添加噪声以利于训练的 Dropout。
推理过程中的批量标准化
虽然小批量方法可以加快训练过程,但它“在推理过程中既不必要也不理想”(Ioffe & Szegedy,2015)。例如,在推断新样本的类别时,你希望基于整个训练集对其进行归一化,因为这样可以产生更好的估计,并且在计算上也是可行的。
因此,在推理过程中,批量标准化步骤如下:
其中 x ∈ X,X 代表完整的训练数据,而不是一些小批量 X_b。
使用 PyTorch 实现批量标准化
在这里,你将继续使用 PyTorch 库实现深度学习的批量标准化。这涉及以下几个步骤:
- 看一下
nn.BatchNorm2d
和之间的区别nn.BatchNorm1d
。 - 编写你的神经网络并构建受批量标准化影响的训练循环。
- 将所有内容整合到完整代码中。
BatchNorm2d 和 BatchNorm1d 之间的区别
首先,PyTorch 中二维和一维 Batch Normalization 的区别。
- 二维批量标准化由 提供
[nn.BatchNorm2d](https://pytorch.org/docs/stable/generated/torch.nn.BatchNorm2d.html)
。 - 对于一维批量标准化,您可以使用
[nn.BatchNorm1d](https://pytorch.org/docs/stable/generated/torch.nn.BatchNorm1d.html)
一维批量标准化在PyTorch网站上的定义如下:
对 2D 或 3D 输入应用批量标准化(具有可选附加通道维度的 1D 输入的小批量)(...)
PyTorch(nd)
…二维批量标准化是这样描述的:
对 4D 输入(具有附加通道维度的 2D 输入的小批量)应用批量标准化 (...)
PyTorch(nd)
让我们总结一下:
- 一维批量标准化(
nn.BatchNorm1d
)对二维或三维输入(一批具有可能通道维度的一维输入)应用批量标准化。 - 二维 BatchNormalization(
nn.BatchNorm2d
)将其应用于 4D 输入(一批具有可能的通道维度的2D输入)。
创建一个文件——例如batchnorm.py
——并在代码编辑器中打开它。同时,请确保您torchvision
的系统上已安装 Python、PyTorch(或在您的 Python 环境中可用)。开始吧!
说明进口
首先,我们要说明我们的进口情况。
- 我们需要
os
基于定义来正确下载数据集。 torch
PyTorch 需要所有基于的导入:torch
它本身、nn
(又名神经网络)模块以及DataLoader
用于加载我们将在今天的神经网络中使用的数据集。- 从中
torchvision
,我们加载CIFAR10
数据集 - 以及transforms
在训练神经网络之前将应用于数据集的一些(主要是图像规范化)。
<span style="background-color:#f9f9f9"><span style="color:#242424"><span style="color:#aa0d91">导入</span>os<span style="color:#aa0d91">从</span>torch
<span style="color:#aa0d91">导入</span>torch<span style="color:#aa0d91">导入</span>nn<span style="color:#aa0d91">从</span>torchvision.datasets<span style="color:#aa0d91">导入</span>CIFAR10<span style="color:#aa0d91">从</span>torch.utils.data<span style="color:#aa0d91">导入</span>DataLoader<span style="color:#aa0d91">从</span>torchvision<span style="color:#aa0d91">导入</span>transforms</span></span>
使用批量标准化定义 nn.Module
接下来是定义nn.Module
。事实上,我们今天不使用Conv
层——这可能会改善你的神经网络。相反,我们会立即将 32x32x3 的输入展平,然后进一步将其处理成 10 类结果(因为 CIFAR10 有 10 个类)。
如你所见,我们之所以BatchNorm1d
在这里应用,是因为我们使用了全连接(又称Linear
)层。请注意,BatchNorm 层的输入数量必须等于该层的输出Linear
数量。
它清楚地展示了如何将批量标准化与 PyTorch 一起应用。
<span style="background-color:#f9f9f9"><span style="color:#242424"><span style="color:#aa0d91">class </span> MLP (nn.Module): <span style="color:#c41a16">'''多层感知器。''' </span><span style="color:#aa0d91">def </span> __init__ ( <span style="color:#5c2699">self</span> ): <span style="color:#5c2699">super</span> ().__init__() self.layers = nn.Sequential( nn.Flatten(), nn.Linear( <span style="color:#1c00cf">32</span> * <span style="color:#1c00cf">32</span> * <span style="color:#1c00cf">3</span> , <span style="color:#1c00cf">64</span> ), nn.BatchNorm1d( <span style="color:#1c00cf">64</span> ), nn.ReLU(), nn.Linear( <span style="color:#1c00cf">64</span> , <span style="color:#1c00cf">32</span> ), nn.BatchNorm1d( <span style="color:#1c00cf">32</span> ), nn.ReLU(), nn.Linear( <span style="color:#1c00cf">32</span> , <span style="color:#1c00cf">10</span> ) ) <span style="color:#aa0d91">def </span> forward ( <span style="color:#5c2699">self, x</span> ): <span style="color:#c41a16">'''前向传递''' </span><span style="color:#aa0d91">return</span> self.layers(x)</span></span>
编写训练循环
接下来是编写训练循环。我们不会在这里详细讨论它,因为我们在关于如何开始使用第一个 PyTorch 模型的专题文章中已经写过了:
不过,为了简单总结一下发生的事情,请看下面:
- 首先,我们将随机数生成器的种子向量设置为一个固定值。这确保了任何差异都是由于数字生成过程的随机性造成的,而不是由于数字生成器本身的伪随机性。
- 然后,我们准备 CIFAR-10 数据集,初始化 MLP 并定义损失函数和优化器。
- 接下来,我们会进行迭代,将当前损失设为 0.0,并开始在数据加载器上进行迭代。我们将梯度设为零,执行前向传播,计算损失,然后执行后向传播,最后进行优化。这实际上就是监督机器学习过程中发生的事情。
- 我们打印通过模型前馈的每个小批次的统计数据。
<span style="background-color:#f9f9f9"><span style="color:#242424"><span style="color:#aa0d91">if</span> __name__ == <span style="color:#c41a16">'__main__'</span> : <span style="color:#007400"># 设置固定随机数种子</span>torch.manual_seed( <span style="color:#1c00cf">42</span> ) <span style="color:#007400"># 准备 CIFAR-10 数据集</span>dataset = CIFAR10(os.getcwd(), download= <span style="color:#aa0d91">True</span> , transform=transforms.ToTensor()) trainloader = torch.utils.data.DataLoader(dataset, batch_size= <span style="color:#1c00cf">10</span> , shuffle= <span style="color:#aa0d91">True</span> , num_workers= <span style="color:#1c00cf">1</span> ) <span style="color:#007400"># 初始化 MLP</span>mlp = MLP() <span style="color:#007400"># 定义损失函数和优化器</span>loss_function = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(mlp.parameters(), lr= <span style="color:#1c00cf">1e-4</span> ) <span style="color:#007400"># 运行训练循环</span><span style="color:#aa0d91">for</span> epoch <span style="color:#aa0d91">in </span> <span style="color:#5c2699">range</span> ( <span style="color:#1c00cf">0</span> , <span style="color:#1c00cf">5</span> ): <span style="color:#007400"># 最多 5 个 epoch </span><span style="color:#007400"># 打印 epoch </span><span style="color:#5c2699">print</span> ( <span style="color:#c41a16">f'Starting epoch <span style="color:#000000">{epoch+ <span style="color:#1c00cf">1</span> }</span> '</span> ) <span style="color:#007400"># 设置当前损失值</span>current_loss = <span style="color:#1c00cf">0.0 </span><span style="color:#007400"># 遍历 DataLoader 获取训练数据</span><span style="color:#aa0d91">for</span> i, data <span style="color:#aa0d91">in </span> <span style="color:#5c2699">enumerate</span> (trainloader, <span style="color:#1c00cf">0</span> ): <span style="color:#007400"># 获取输入</span>inputs, targets = data <span style="color:#007400"># 将梯度清零</span>optimizer.zero_grad() <span style="color:#007400"># 执行前向传递</span>outputs = mlp(inputs) <span style="color:#007400"># 计算损失</span>loss = loss_function(outputs, targets) <span style="color:#007400"># 执行后向传递</span>loss.backward() <span style="color:#007400"># 执行优化</span>optimizer.step() <span style="color:#007400"># 打印统计数据</span>current_loss += loss.item() <span style="color:#aa0d91">if</span> i % <span style="color:#1c00cf">500</span> == <span style="color:#1c00cf">499</span> : <span style="color:#5c2699">print</span> ( <span style="color:#c41a16">'小批量 %5d 后的损失: %.3f'</span> % (i + <span style="color:#1c00cf">1</span> , current_loss / <span style="color:#1c00cf">500</span> )) current_loss = <span style="color:#1c00cf">0.0 </span><span style="color:#007400"># 过程完成。</span><span style="color:#5c2699">print</span> ( <span style="color:#c41a16">'训练过程已完成。'</span> )</span></span>
完整型号代码
也可在后台私我获取到。
<span style="background-color:#f9f9f9"><span style="color:#242424"><span style="color:#aa0d91">导入</span>操作系统<span style="color:#aa0d91">从</span>torch
<span style="color:#aa0d91">导入</span>torch<span style="color:#aa0d91">导入</span>nn<span style="color:#aa0d91">从</span>torchvision.datasets<span style="color:#aa0d91">导入</span>CIFAR10<span style="color:#aa0d91">从</span>torch.utils.data<span style="color:#aa0d91">导入</span>DataLoader<span style="color:#aa0d91">从</span>torchvision<span style="color:#aa0d91">导入</span>transforms<span style="color:#aa0d91">类</span>MLP (nn.Module): <span style="color:#c41a16">'''</span><span style="color:#c41a16"> 多层感知器。</span><span style="color:#c41a16"> ''' </span><span style="color:#aa0d91">def </span>__init__ ( <span style="color:#5c2699">self</span> ): <span style="color:#5c2699">super</span> ().__init__() self.layers = nn.Sequential( nn.Flatten(), nn.Linear( <span style="color:#1c00cf">32</span> * <span style="color:#1c00cf">32</span> * <span style="color:#1c00cf">3</span> , <span style="color:#1c00cf">64</span> ), nn.BatchNorm1d( <span style="color:#1c00cf">64</span> ), nn.ReLU(), nn.Linear( <span style="color:#1c00cf">64</span> , <span style="color:#1c00cf">32</span> ), nn.BatchNorm1d( <span style="color:#1c00cf">32</span> ), nn.ReLU(), nn.Linear( <span style="color:#1c00cf">32</span> , <span style="color:#1c00cf">10</span> ) ) <span style="color:#aa0d91">def </span>forward ( <span style="color:#5c2699">self, x</span> ): <span style="color:#c41a16">'''前向传播''' </span><span style="color:#aa0d91">return</span> self.layers(x) <span style="color:#aa0d91">if</span> __name__ == <span style="color:#c41a16">'__main__'</span> : <span style="color:#007400"># 设置固定随机数种子</span> torch.manual_seed( <span style="color:#1c00cf">42</span> ) <span style="color:#007400"># 准备 CIFAR-10 数据集</span> dataset = CIFAR10(os.getcwd(), download= <span style="color:#aa0d91">True</span> , transform=transforms.ToTensor()) trainloader = torch.utils.data.DataLoader(dataset, batch_size= <span style="color:#1c00cf">10</span> , shuffle= <span style="color:#aa0d91">True</span> , num_workers= <span style="color:#1c00cf">1</span> ) <span style="color:#007400"># 初始化 MLP</span> mlp = MLP() <span style="color:#007400"># 定义损失函数和优化器</span> loss_function = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(mlp.parameters(), lr= <span style="color:#1c00cf">1e-4</span> ) <span style="color:#007400"># 运行训练循环</span><span style="color:#aa0d91">for</span> epoch <span style="color:#aa0d91">in </span><span style="color:#5c2699">range</span> ( <span style="color:#1c00cf">0</span> , <span style="color:#1c00cf">5</span> ): <span style="color:#007400"># 最多 5 个 epoch </span><span style="color:#007400"># 打印 epoch </span><span style="color:#5c2699">print</span> ( <span style="color:#c41a16">f'Starting epoch </span><span style="color:#c41a16"><span style="color:#000000">{epoch+ </span></span><span style="color:#c41a16"><span style="color:#000000"><span style="color:#1c00cf">1</span></span></span><span style="color:#c41a16"><span style="color:#000000"> }</span></span><span style="color:#c41a16"> '</span> ) <span style="color:#007400"># 设置当前损失值</span> current_loss = <span style="color:#1c00cf">0.0 </span><span style="color:#007400"># 遍历 DataLoader 获取训练数据</span><span style="color:#aa0d91">for</span> i, data <span style="color:#aa0d91">in </span><span style="color:#5c2699">enumerate</span> (trainloader, <span style="color:#1c00cf">0</span> ): <span style="color:#007400"># 获取输入</span> input, target = data <span style="color:#007400"># 将梯度归零</span>optimizer.zero_grad() <span style="color:#007400"># 执行前向传递</span>outputs = mlp(inputs) <span style="color:#007400"># 计算损失</span>loss = loss_function(outputs, targets) <span style="color:#007400"># 执行后向传递</span>loss.backward() <span style="color:#007400"># 执行优化</span>optimizer.step() <span style="color:#007400"># 打印统计数据</span>current_loss += loss.item() <span style="color:#aa0d91">if</span> i % <span style="color:#1c00cf">500</span> == <span style="color:#1c00cf">499</span> : <span style="color:#5c2699">print</span> ( <span style="color:#c41a16">'小批量 %5d 后的损失: %.3f'</span> % (i + <span style="color:#1c00cf">1</span> , current_loss / <span style="color:#1c00cf">500</span> )) current_loss = <span style="color:#1c00cf">0.0 </span><span style="color:#007400"># 过程完成。</span><span style="color:#5c2699">print</span> ( <span style="color:#c41a16">'训练过程已完成。'</span> )</span></span>
结果
以下是在 CIFAR-10 数据集上对我们的 MLP 进行 5 个 epoch 训练并使用批量标准化后得到的结果:
<span style="background-color:#f9f9f9"><span style="color:#242424">起始 epoch 5
小批量 500 后的损失:1.573
小批量 1000 后的损失:1.570
小批量 1500 后的损失:1.594
小批量 2000 后的损失:1.568
小批量 2500 后的损失:1.609
小批量 3000 后的损失:1.573
小批量 3500 后的损失:1.570
小批量 4000 后的损失:1.571
小批量 4500 后的损失:1.571
小批量 5000 后的损失:1.584</span></span>
相同,但没有批量标准化:
<span style="background-color:#f9f9f9"><span style="color:#242424">起始 epoch 5
小批量 500 后的损失:1.650
小批量 1000 后的损失:1.656
小批量 1500 后的损失:1.668
小批量 2000 后的损失:1.651
小批量 2500 后的损失:1.664
小批量 3000 后的损失:1.649
小批量 3500 后的损失:1.647
小批量 4000 后的损失:1.648
小批量 4500 后的损失:1.620
小批量 5000 后的损失:1.648</span></span>
显然,但并不令人意外的是,基于批量标准化的模型表现更好。
概括
在这篇博文中,我们探讨了训练过程相对缓慢且不收敛的问题,并指出批量归一化可能有助于减少神经网络的这些问题。批量归一化通过将输入数据的分布缩减至 (0, 1),并逐层执行,理论上有望减少所谓的“内部协方差偏移”,从而加快学习速度。