

介绍了关于infinitensor夏令营课程中训练系统模块部分的训练基础。
神经网络是由大量相互连接的人工神经元构成的计算模型,其结构受到生物神经网络的启发。输入层接收原始特征向量 ,隐藏层中的神经元对输入进行加权求和并经过非线性激活函数:
多层隐藏层会逐步提取和组合特征,输出层再根据最后一层特征生成预测结果 (\hat{y})。训练过程中,神经网络利用样本数据比较预测结果与真实答案之间的误差,并不断调整连接权重和偏置,从而自动学习输入与输出之间的映射关系。简单来说,输入层负责接收数据,隐藏层负责加工和提取特征,输出层负责给出最终预测。
梯度下降法是神经网络训练中最基本的参数优化方法,其目标是不断调整模型参数 ,使损失函数 尽可能小。梯度 表示损失函数在当前位置上升最快的方向,因此沿着梯度的反方向更新参数,就可以让损失逐步下降:
其中, 表示学习率,用于控制每次更新的步长。学习率过小会导致训练速度缓慢,过大则可能越过最低点,使损失震荡甚至无法收敛。整个过程可以理解为在起伏的山坡上不断寻找下坡方向,经过多次迭代逐渐到达较低的位置。需要注意的是,梯度下降通常只能保证找到一个较低的点,不一定能够找到整个损失函数的全局最低点。
损失函数用于衡量模型预测值 与真实标签 之间的差异,是模型训练时需要最小化的目标。损失越小,说明预测结果通常越接近真实值。分类任务常使用交叉熵损失:
它会对模型给正确类别较低概率的情况施加较大的惩罚;回归任务则常使用均方误差:
它通过计算预测值与真实值之间误差的平方来衡量预测偏差。模型训练时,先根据输入 得到预测结果 ,再计算损失 ,通过反向传播求出参数梯度,并使用梯度下降更新参数。这个过程不断重复,使损失逐渐减小,模型也就逐步学会拟合数据中的规律。
前向传播是指输入数据从输入层开始,依次经过各个隐藏层,最终得到模型预测结果 (\hat{y}) 的过程。在这一阶段,网络只使用当前的权重和偏置进行计算,不会更新参数。
对于第 层,通常包含两步计算:
其中, 是上一层的输出, 和 分别是当前层的权重矩阵与偏置向量, 是线性计算结果, 是 ReLU、Sigmoid 等非线性激活函数。输出层采用哪种激活方式取决于具体任务:回归任务可以直接输出连续数值,二分类任务常使用 Sigmoid,多分类任务则通常输出 logits 或经过 Softmax 得到类别概率。训练时,最终预测 会与真实标签 一起用于计算损失:
随后再通过反向传播计算梯度并更新网络参数。简而言之,前向传播负责根据当前参数得到预测结果,反向传播负责根据预测误差调整参数。
神经网络中的每一层通常先进行线性变换,再通过激活函数处理:
如果没有激活函数,多层线性变换仍然可以合并为一次线性变换:
因此,无论网络堆叠多少层,都只能表示简单的线性关系。激活函数通过引入非线性,使不同神经元能够组合出弯曲的决策边界,从而拟合更加复杂的函数。
常见激活函数包括 Sigmoid、Tanh、ReLU 和 Leaky ReLU。
对应的图像如下所示:
反向传播本质上是利用链式法则,在计算图中从输出端向输入端逐步计算梯度。当变量沿同一条链路依次传递时,各节点之间的局部导数相乘。若
则:
当一个变量通过多条路径共同影响最终结果时,需要分别计算每条路径对结果的贡献,再将它们相加。例如:
则:
因此,反向传播可以概括为:同一条路径上的梯度相乘,多条并行路径上的梯度相加。
以函数 为例,变量 通过两条路径影响 :一条直接进入乘法节点,另一条先经过平方和指数运算。根据乘积法则与链式法则:
而:
所以:
这里虽然计算图中多次出现 ,但它们实际上共享同一个输入变量,因此反向传播时必须把所有相关路径产生的梯度贡献相加。神经网络中的反向传播正是按照这一规则,逐层计算损失函数对每个权重和偏置的梯度。
反向传播是神经网络根据损失函数,计算各层参数梯度的过程。
前向传播首先从输入 (x) 出发,逐层计算中间结果:
其中:
再根据预测结果 和真实标签 计算损失:
反向传播从损失函数开始,按照与前向传播相反的方向,利用链式法则逐层计算梯度。
首先计算损失对输出层激活值的梯度:
对于第 层,先经过激活函数反向传播:
其中, 表示对应位置的元素相乘。
再根据线性计算:
计算权重和偏置的梯度:
同时,将梯度继续传递到前一层:
因此,每一层的反向传播都可以概括为三个步骤:
这个过程从输出层一直重复到输入层,最终得到所有参数对损失函数的影响程度。优化器随后使用这些梯度更新参数:
其中, 表示学习率。
反向传播需要使用前向传播过程中计算出的 和 ,因此训练时通常需要缓存各层的中间激活值。这也是训练相比只执行前向传播的推理过程需要更多显存的重要原因。
简单来说,前向传播负责产生预测结果和损失,反向传播负责计算误差应当如何分配到每一层参数上,梯度下降则根据这些梯度更新模型参数。
在机器学习中,完整数据集通常被划分为训练集、验证集和测试集,三者分别承担不同的作用。
训练集用于模型学习和参数优化。训练过程中,模型根据输入数据生成预测结果,计算预测值与真实标签之间的损失,并通过反向传播不断更新权重和偏置。
验证集用于模型开发过程中的性能评估,但不会直接参与参数更新。它通常用于:
如果训练集上的误差持续下降,而验证集上的误差开始上升,通常说明模型正在过度拟合训练数据,此时可以提前停止训练。
测试集用于模型训练完成后的最终性能评估。测试集不应该参与模型训练、超参数调整或模型选择,否则测试结果可能无法真实反映模型对未知数据的泛化能力。
因此,测试集原则上只应在模型和训练方案确定之后使用。
每次执行前向计算,Pytorch 的 AutoGrad 引擎会动态构建一张计算图,用户后续反向传播。初始梯度由用户在调用 .backward() 时显示传入,对标量调用则默认为 1。每个节点只保存必要的中间激活值和局部梯度函数,实现链式法则的逐层传播。
AutoGrad——反向微分系统,在代码执行计算的同时,AutoGrad 会构建一张由 Function 对象组成的计算图,用于反向传播:
.grad_fn 属性,每个非叶子张亮的 .grad_fn 属性指向创建该张量的 Function,是这个图的一个入口点。以平方运算为例:
根据链式法则,若后续损失为 ,则:
反向传播时需要使用 才能计算 ,因此前向传播执行 x.pow(2) 时,Autograd 会保存输入张量 。在调试时,可以通过类似 y.grad_fn._saved_self 的内部属性观察被保存的张量。
不同的运算需要保存的内容并不相同,例如:
其导数为
此时反向传播可以直接利用前向传播的输出 ,不必重新计算指数。因此,指数运算对应的反向节点通常会保存计算结果,可以通过类似 x.grad_fn._saved_result 的属性观察。
需要注意,保存张量不一定表示计算图始终保存原来的 Python 张量对象,例如:
这里的两个张量数值相同,并且可能共享底层存储,但是不一定时同一个 Python 对象。Pytorch 会在部分情况下对张量进行打包和解包,以避免形成如 y → grad_fn → saved tensor → y 的循环引用。
对于反向传播需要的中间张量,PyTorch 可以使用两个钩子控制其保存和恢复方式:
pack_hook:前向传播保存张量时调用;其返回值会替代原始张量存入计算图。unpack_hook:反向传播时需要读取该张量时调用,负责根据返回值恢复出反向传播所需的张量。通过 torch.autograd.graph.saved_tensors_hooks,可以自定义这种保存策略。例如,前向传播时把大型中间张量从 GPU 移到 CPU,甚至保存到磁盘;反向传播时再读取回来。这样可以减少训练过程中的显存占用,但会增加数据传输、磁盘读写和运行时间,因此本质上是在用计算或 I/O 开销换取内存空间。
inf,例如 sqrt(0))。sqrt(-1)),可能为任意情况(抛出错误,NaN 等)。requires_grad=True,且没有使用 torch.no_grad() 包裹,则报错。使用 requires_grad 属性细粒度地控制计算操作是否被纳入 AutoGrad 的计算图中:
requires_grad=True 时,该操作才会被记录进反向计算图中。requires_grad=True 的张量,才会在它们的 .grad 字段中累加反向传过来的梯度值。虽然所有张量都有 requires_grad 属性,但设置它只对叶子张量才有意义。
PyTorch 中还需要区分两组彼此独立的概念:
model.train() 与 model.eval() :控制模型中部分模块采用训练行为还是推理行为。torch.no_grad() 与 torch.inference_mode():控制 Autograd 是否记录运算并构建反向传播计算图。调用 model.eval() 本质上等价于 model.train(false),只是把模型切换为评估模式,并影响那些在训练和推理阶段行为不同的模块,如 Dropout 和 BatchNorm。它不会关闭梯度计算,也不会阻止计算图的建立;在 eval() 模式下,只要输入或参数需要梯度,仍然可以调用 backward()。
torch.no_grad() 会在指定代码区域内关闭反向传播计算图的构建:
在这个代码块中,PyTorch 不会保存反向传播所需的中间结果,因此通常可以减少内存占用和计算开销。退出代码块后,梯度模式会自动恢复。no_grad() 不会改变模型本身的训练或评估状态。
torch.inference_mode() 与 no_grad() 类似,也不会构建反向传播计算图:
但它比 no_grad() 更严格,会进一步跳过部分与 Autograd 有关的额外记录,例如视图跟踪和版本计数,因此在纯推理场景中可能获得更低的额外开销。代价是其中创建的张量受到更多限制,不适合随后重新参与需要梯度的计算。
原地操作是指直接修改原张量中的内容,而不是创建新张量,例如:
相比之下,下面的写法会创建新的张量:
PyTorch 允许部分原地操作,但如果它修改了反向传播所需的数据,就会报错。例如前向传播中计算 ,反向传播需要原来的 来计算:
如果在反向传播前执行 x.add_(1),原来的 被覆盖,PyTorch 就无法确定应使用修改前还是修改后的值计算梯度。
为保证梯度正确,每个 Tensor 都维护一个版本计数器。张量每被原地修改一次,版本号就会增加;前向传播保存中间张量时,也会保存当时的版本号。反向传播读取该张量时,如果发现版本号发生变化,就会抛出错误。
混合精度(Mixed Precision)训练指的是:在训练过程中同时使用 FP16 和 FP32,以提高训练速度、减少显存占用,同时尽可能保持精度不变。
PyTorch 的自动混合精度模块为:torch.amp,其中较老的版本中仅支持 CUDA 后端,位于 torch.cuda.amp,两大核心工具包括 autocast 与 GradScaler。
torch.amp.autocast:自动将某些算子用 FP16 执行,从而提高计算吞吐、降低显存占用。with torch.amp.autocast():上下文管理器的方式调用。matmul/conv/relu/add/mul 等。torch.amp.GradScaler:解决 FP 16 训练时梯度过小导致全为 0 的问题,其工作流程如下:inf/nan):如果梯度健康(没有溢出)则增大 scale;如果出现数值问题则减小 scale,确保稳定训练。神经网络中不同层的计算方式和数值范围不同,对低精度计算的敏感程度也不同。因此,不一定要让整个模型统一使用 FP16 或 FP32,而是可以为不同层选择不同的精度策略。
通常先在小模型上测试各层使用低精度后的误差和训练稳定性,再按照敏感程度进行划分:
这种方法可以在保证训练稳定性的同时,降低显存占用并提高计算速度。
最主要的核心思想也就是:对精度敏感的层保留高精度,对计算量大的稳定层更多使用低精度。
FP16 能表示的数字范围较小,反向传播中的微小梯度可能因为过小而直接变成 0,这称为梯度下溢。损失缩放会先把损失乘以一个较大的系数 :
这样反向传播得到的梯度也会被放大:
在优化器更新参数之前,再将梯度除以 ,恢复原始尺度:
动态损失缩放会自动调节 :
Inf 或 NaN,说明缩放过大,减小缩放系数并跳过本次更新。总体而言,分层混合精度负责决定不同层使用什么精度,动态损失缩放负责保护 FP16 反向传播中的微小梯度。
model.eval()
with torch.no_grad():
prediction = model(x)model.eval()
with torch.inference_mode():
prediction = model(x)x.add_(1)
x += 1
x.relu_()x = x + 1import torch
x = torch.tensor(2.0, requires_grad=True) #requires_grad=True 表示要对该变量求导
y = x ** 2 + 3 * x + 1
y.backward() # 会触发整个传播过程
print(x.grad) # Output:7.0x = torch.randn(5, requires_grad=True)
y = x.exp()
print(y.equal(y.grad_fn._saved_result)) # 数值相同
print(y is y.grad_fn._saved_result) # 可能为 False