在“勇敢学习机器学习”系列的最后一部分,学习者和导师专注于学习 DNN 训练的两个基本理论:梯度下降和反向传播。
他们的旅程从查看梯度下降如何是使损失函数最小化的关键开始。对于在多层隐藏网络中计算梯度的复杂性感到好奇,学习者随后转向反向传播。通过将反向传播分解为 3 个部分,学习者了解了反向传播及其使用链式法则高效计算这些层梯度的方法。在这次问答环节中,学习者质疑在 PyTorch 和 Tensorflow 等自动化高级深度学习框架的时代,理解这些复杂过程的重要性。
这是我们的深度学习探索的第一篇帖子,由学习者和导师之间的互动引导。为了使内容易于消化,我决定将我的 DNN 系列分解成更易管理的部分。这样,我可以彻底探索每个概念,而不会让你感到不知所措。
今天的讨论将通过关注不稳定梯度的挑战来回答这个问题,不稳定梯度是使深度神经网络(DNN)训练困难的主要因素。我们将探讨各种解决这个问题的策略,使用一个名为 DNN(代表美味营养的小吃)的微型冰淇淋工厂的类比来展示有效的解决方案。在随后的文章中,导师将详细讨论每个解决方案,展示这些解决方案如何在 PyTorch 框架中实现。
深入 DNN 的世界,我们将使用一个我非常喜欢的不寻常的类比——将 DNN 想象成一个冰淇淋工厂。有趣的是,我曾经问过 ChatGPT 在冰淇淋领域“DNN”可能代表什么,经过 5 分钟的思考,它建议“美味营养的小吃”。我喜欢这个建议!因此,我决定采用这个有趣的类比,用一点甜蜜和乐趣来帮助揭示那些令人畏惧的 DNN 概念。随着我们深入到深度学习的深处,想象我们是在管理一个名为 DNN 的微型冰淇淋工厂。谁知道呢,也许有一天,DNN 冰淇淋将成为现实。这对机器学习/深度学习爱好者来说将是一种真正的享受!
让我们从学习使用 PyTorch 训练神经网络的基礎结构开始这段旅程。始终,让我们将其重新表述为一个基本问题——
你能展示在 PyTorch 中如何实现反向传播和梯度下降吗?
使用 PyTorch 训练神经网络的代码示例有助于理解梯度下降和反向传播的关系和作用。
import torch
# Define the model
model = CustomizedModel()
# Define the loss function
loss_fn = torch.nn.L1Loss()
# Define optimizer
optimizer = torch.optim.SGD(params = model.parameters(),
lr = 0.01, momentum = 0.9)
epoches = 10
for epoch in range(epoches):
# Step 1: Setting the Model to Training Mode:
model.train()
# Step 2: Forward Pass - Making Predictions
y_pred = model(X_train)
# Step 3: Calculating the Loss
loss = loss_fn(y_pred, y_train)
# Step 4: Backpropagation - Calculating Gradients
optimizer.zero_grad() # clears old gradients
loss.backward() # performs backpropagation to compute the gradients of the loss w.r.t model parameters
# Step 5: Gradient Descent - Updating Parameters
optimizer.step()
在这个代码片段中,使用了loss.backward()来执行反向传播。这个过程是从损失开始的,而不是从优化器开始的,因为反向传播的目的是计算损失相对于每个参数的梯度。一旦确定了这些梯度,优化器就会使用这些梯度通过梯度下降步骤来更新每个参数。在我看来,optimizer.step()方法的名字“step”恰当地表明了对所有模型参数的单次更新。这可以被视为在优化过程中沿着损失表面迈出一步。
为了更好地理解“沿着损失表面的一步”是什么意思,我强烈建议阅读我的关于梯度下降的文章。在文章中,我使用视频游戏类比来生动地展示我们如何导航损失表面。此外,你也会喜欢 ChatGPT 绘制的标题图片。
你能解释神经网络中梯度消失和梯度爆炸的概念吗?这些问题的主要成因是什么?
在深度神经网络(DNN)中计算梯度是复杂的,主要是因为大量的参数分布在许多隐藏层中。反向传播通过计算损失相对于每个参数的梯度,应用链式法则简化了这一计算。由于链式法则,这个过程涉及到通过连续乘法计算出的导数,其中值可能会发生剧烈变化。这导致梯度可能随着我们移动到较低的层(接近输入层的层)而变得极其小或大。这类似于通过一系列复杂的水过滤器追踪回溯以增强水质,一层层地。早期层的影响很难评估,因为它会被后续层所改变。这种不稳定的梯度在训练深度神经网络时引入了重大挑战,导致了被称为“消失”(梯度极其小)和“爆炸”(梯度过大)的梯度问题。这种不稳定性使得 DNN 训练变得困难,并限制了架构的层数。换句话说,如果不解决这一问题,DNN 无法达到所需的深度。
如果你想知道为什么反向传播涉及连续的乘法,我建议查看我之前关于反向传播的文章。我的早期文章关于反向传播。在文章中,我通过将其分解为 3 个部分来解释计算,并使用代码示例使其更容易理解。我还探讨了为什么研究人员通常更喜欢更深更窄的深度神经网络,而不是更宽更浅的神经网络。
我明白梯度消失可能是一个问题,因为底层参数几乎不更新,使得使用如此小的梯度进行学习变得困难。但为什么梯度爆炸也成问题呢?它们不是为后续层提供大量更新吗?
你正确地指出,大的梯度可能导致参数的显著更新。然而,这并不总是情况,大规模更新并不总是有益的。让我解释一下为什么梯度爆炸会阻碍深度神经网络的有效训练:
-
梯度爆炸并不总是意味着大规模更新,它可能导致数值不稳定和溢出。梯度爆炸并不总是导致大的、有意义的更新。在用计算机进行训练时,过大的梯度可能导致数值溢出。例如,考虑一个网络,在某一层的梯度是 10⁷,这是一个很大但可行的数字。在反向传播过程中,由于链式法则,这个值乘以其他大值,可能会超过标准浮点表示的限制,导致溢出,梯度变为
NaN(表示不是一个数字)。当使用parameter = parameter - learning_rate * gradient更新参数时,如果梯度是NaN,参数也会变成NaN。这会破坏网络的正向传播,使其无法生成有用的预测。 -
大规模更新并不总是有益的。大的梯度值可能导致参数发生剧烈变化,引起参数更新的振荡和不稳定。这可能导致训练时间更长,收敛困难,以及可能超过损失函数的全局最小值。与相对较大的学习率相结合,这些振荡可能会显著减慢训练过程。
-
大的梯度并不一定具有信息量。 认为更大的梯度总是更有信息量并且导致有意义的更新是一种误解。事实上,如果梯度变得过大,它们可能会掩盖较小但更有意义的梯度的贡献。想象一下在损失景观中导航,大型梯度,通常受异常值或噪声的影响,可能会误导我们选择下一步。此外,大型梯度可能对某些层有益,而对其他层则不然,导致学习不平衡。例如,上层的大梯度可能会导致使用 sigmoid 激活函数的层的极端小导数。这可能导致网络内部学习过程的不平衡。
为什么由爆炸性梯度引起的权重更新中的振荡最终会变得有害?我明白小批量大小的随机梯度下降(SGD)也会导致振荡,但为什么那不会引起类似的问题?
你指出,小批量大小的随机梯度下降(SGD)确实会在权重更新中引入一些不稳定性。然而,这种类型的振荡是可控的且相对较小的,主要是因为它源自数据本身。利用训练数据的小子集通常不会偏离完整数据集的行为太远,这意味着噪声是可管理的。此外,这种可管理的噪声水平可以通过提高对数据微小变化的敏感性来提高模型的可泛化性。本质上,在导航损失表面时,SGD 可能会做出偏离最优化路径到全局最小值的决策。然而,这些次优步骤并不远偏离理想步骤。 这种方法降低了陷入平台和鞍点的可能性,使我们能够远离这些区域,向潜在的更好局部最小值甚至全局最小值移动,在 DNN 的复杂景观中。
另一方面,由爆炸性梯度引起的振荡具有不同的行为。由于爆炸性梯度,我们可能会对参数进行大幅更新,导致损失表面的显著移动。这些更新可能非常大,以至于将模型推到初始位置相当遥远的位置,甚至比全局最小值还要远。与 SGD 不同,SGD 的步长很小,使我们保持接近原始位置,爆炸性梯度可能会抵消我们之前的进展,迫使我们重新做所有艰苦的工作以找到全局最小值。
为了可视化这个过程,可以将其想象成玩一个角色扮演游戏(RPG),在这个游戏中,我们使用魔法(类似于优化器)来引导我们的移动,朝着地图最低点处的宝藏前进。有了 SGD 的魔法,我们可能会偏离最佳路线,但通常我们会朝着宝藏前进。因此,如果我们移动得足够快,我们很可能会到达并得到宝藏。但是,如果梯度爆炸,那就好像被随机扔到地图上的一个新、不熟悉的地方,需要我们重新开始探索。在最坏的情况下,我们可能会到达离宝藏最远的地方,这使得在有限的时间和计算资源下几乎不可能达到我们的目标。
那么,我们如何知道梯度是否太大或太小?我们如何检测这些问题?
检测不稳定的梯度对于确保不同层以一致的速度有效学习至关重要。如果你怀疑你的模型正在遭受梯度爆炸或消失,考虑以下方法来有效地识别这些问题:
-
跟踪训练损失。观察每个 epoch 的训练损失是一个简单而有价值的方法。损失的突然上升或其变为 NaN 可能表明某些权重被过于激进地更新,这可能是由于大的梯度导致数值溢出。这种情况通常指向梯度爆炸。相反,损失图中的平台期或几个 epoch 中的微小下降可能是梯度消失的迹象,表明权重几乎未更新,因为梯度非常小。然而,解读这些迹象并不总是直接的,需要持续的评估来确定训练过程是否在进展。
-
直接监控梯度。一种更直接的方法是关注梯度本身。然而,在大型、深层网络中,逐个检查每个梯度可能是不切实际的,计算梯度范数提供了一种简化但有效的替代方案。范数可以针对所有层共同计算或单独计算,它关注梯度的幅度,提供了一个随时间比较的单个指标。这种方法特别适用于识别梯度爆炸,因为过大的值总会突出显示。对于梯度消失,小的范数可能暗示存在问题,但它们不太确定。通过直方图可视化梯度分布也可以突出显示异常值和极端值,并且可以轻松捕捉。
-
监控权重更新和激活输出。由于不稳定的梯度会直接影响权重更新,跟踪权重的变化是一个合理的步骤。权重中突然出现显著的大变化或变为 NaN,表明发生了梯度爆炸。如果权重在一段时间内保持静态,那么梯度消失可能是原因。同样,分析每一层的激活输出可以提供关于权重和梯度行为的额外见解。
对于实际应用,TensorBoard 作为一种流行的可视化训练进度的工具脱颖而出。它支持 TensorFlow 和 PyTorch,并允许详细跟踪损失、梯度等。它可以是一个很好的工具来识别与梯度相关的问题。
你是在暗示,在 DNN 中,我们旨在让所有层以相同的速度学习吗?
我们并不期望 DNN 中的每一层都以相同的速率学习。我们的目标是让整个网络中的权重更新保持一致和平衡。例如,梯度消失会引发问题,因为它们导致靠近输出的上层(梯度较大的层)学习得更快,并较早收敛,而靠近输入的下层(梯度较小的层)由于梯度小而几乎以随机的方式调整权重,导致落后。这可能导致网络收敛到一个次优的局部最小值,只有上层得到适当的训练,而下层几乎未变。一般来说,我们想要确保每一层的更新速率与其对最终模型预测的影响相匹配。梯度消失和爆炸的问题在于它们打破了这种平衡,不稳定的梯度会导致层以不成比例的方式更新。 这就像只转动身体而不移动脚来尝试向左走,你不会走得很远。
因此,目标是在整个网络中实现稳定且均匀的权重更新。这正是自适应学习率优化器发挥作用的地方,它们提供了显著的好处。它们根据历史梯度动态调整学习率,有助于更有效地减少损失。
实际上,像 PyTorch 和 TensorFlow 这样的框架允许为每个层指定不同的学习率。这种能力在微调预训练模型或迁移学习期间特别有益。它允许根据特定要求对每个层进行定制的学习率调整。以下是一些有用的讨论,可以在 Stack Overflow 和 PyTorch 论坛上找到。
我们通常如何处理训练 DNN 时梯度不稳定的问题?
想象我们的深度神经网络(DNN)模型就像一个迷你冰淇淋工厂,原料通过各个部门流动,最终在生产线末端由顾客对冰淇淋进行评分。这个迷你工厂中的每个部门代表 DNN 中的一个层,而顾客对冰淇淋口味的反馈则代表用于通过反向传播改进食谱的损失梯度。
然而,我们的工厂面临一个挑战:客户的反馈没有通过部门有效地传达回来。一些部门对反馈过度反应,做出剧烈的改变(类似于爆炸的梯度),而其他部门则忽视反馈,几乎不做任何调整(类似于消失的梯度)。
作为工厂经理,我们的目标是确保在每个阶段适当地处理反馈,以持续生产出令客户满意的冰淇淋。以下是我们可以采取的策略:
-
正确设置工厂(权重初始化)。 第一步是确保工厂平稳运行并生产出高质量的冰淇淋。正确设置工厂非常重要。我们需要确保我们生产线(类似于深度神经网络中的权重)的初始设置恰到好处,既不过强也不过弱,以生产出符合一般客户偏好的基础口味。这就像在深度神经网络中选择适当的权重初始化,以避免梯度过小或过大,确保基于反馈的调整稳定流动。
-
生产前及生产过程中的质量控制(批量归一化)。 随着原料混合并在生产线(深度神经网络中的层)中前进,我们会引入质量检查点以在各个阶段标准化混合物。这确保了每一批半成品保持一致,防止任何一层生产出过于多样化的冰淇淋,这可能会使我们的客户反馈变得误导性,并使我们的调整效果降低。这类似于深度神经网络中的批量归一化,其中层的输出被归一化以保持激活的稳定分布,有助于更平滑的梯度流动。
-
调整反馈系统以避免过度反应(梯度裁剪)。 当客户反馈到来时,至关重要的是没有任何部门过度反应并根据一批反馈做出剧烈改变,这可能会打乱整个生产线。通过实施一个系统,其中极端反馈(无论是过于积极还是过于消极)被调节或裁剪,你确保变化是渐进和可控的,类似于深度神经网络中的梯度裁剪,这可以防止梯度爆炸。类似的想法是使用一些特殊的网络架构,如带有跳跃连接的 ResNet,也可以减轻梯度消失的问题。
-
优化工作流程和反馈路径(激活函数)。 在我们的深度神经网络冰淇淋工厂中,一些部门充当信使,将半成品产品向前传递,并将客户反馈向后传递。他们是确保最终产品正确无误的关键。如果他们不能准确传递中间产品,最终结果可能是一批不符合标准的产品。同样,他们处理客户反馈并在部门间传递的方式也很重要。如果反馈被忽视,可能会错过重要信息,而过度的放大反馈可能导致过度反应和工厂中的剧烈变化。因此,选择合适的沟通者(激活函数)确保产品和反馈的顺利传递,并保持生产线高效和响应。就像在我们的工厂中一样,选择合适的激活函数在深度神经网络中确保我们得到准确的预测,并保持稳定的梯度,避免在反向传播过程中的消失或爆炸,并确保不同参数的有效更新。
-
通过部门调整反馈响应强度(自适应学习率)。 最后,并非所有部门都应该以相同的强度对反馈做出反应。例如,口味部门可能需要比包装部门更敏感地对待口味反馈。通过调整每个部门从反馈中学习多少(类似于在深度神经网络的不同层中设置自适应学习率),您可以微调工厂对客户偏好的响应,确保更精准和高效的改进。
下次,我将详细介绍激活函数和权重初始化方法。我将不仅仅简单地跑过公式和列出它们的优缺点,而是计划分享塑造我理解的关键问题——这些问题经常被忽视,但对于理解为什么每种方法和函数以这种方式形成至关重要。这些讨论甚至可能让我们有能力创新我们自己的函数和权重初始化方法。
对于那些愿意与我一起踏上这个系列之旅的人,请随时跟随。您的参与——通过点赞、评论和关注——为这项事业提供动力。这不仅仅是鼓励;这是这个教育系列的心跳。**随着我继续深化对这些主题的理解,我经常回顾并更新我过去的帖子,用新的见解丰富它们。**所以,请继续关注更多内容!
(除非另有说明,所有图片均为作者提供)
本系列的其他帖子:
如果你喜欢这篇文章,你可以在LinkedIn上找到我。
516

被折叠的 条评论
为什么被折叠?



