1. 项目概述为什么损失函数是深度学习的“导航仪”搞深度学习无论是做图像分类、目标检测还是自然语言处理你绕不开的一个核心概念就是损失函数。很多人把它当成一个数学公式调包时填进去就完事了但真正理解它才是你从“调参侠”迈向“炼丹师”的关键一步。简单来说损失函数就是模型在训练过程中的“导航仪”和“成绩单”。它量化了模型预测结果与真实答案之间的差距这个差距值我们称之为“损失”Loss。训练的本质就是通过反向传播和优化算法不断地调整模型内部的参数让这个损失值尽可能变小。所以损失函数选得对不对、理解得透不透直接决定了你的模型能不能学好、学得快不快、最终效果稳不稳定。从这些热搜词就能看出大家的关注点有人在纠结PyTorch环境怎么配cuda12.1 pytorch版本,anaconda配置pytorch环境有人在死磕YOLO的改进yolov8 改进损失函数还有大量新手在寻找入门路径《零基础入门深度学习》,理解深度学习。这反映了一个普遍现象很多朋友急于上手项目却忽略了像损失函数这样的基石理论。结果就是当模型效果不佳时只会盲目调整学习率、换网络结构却没想到问题可能出在最根本的“评判标准”上。本文将彻底拆解损失函数不仅讲清原理更会用PyTorch手把手带你实战让你真正掌握这个核心工具知其然更知其所以然。2. 损失函数的核心原理与设计思想2.1 损失函数的本质量化“错误”损失函数的核心任务就一个用一个可微分的标量值来衡量模型预测值与真实值之间的差异。这个“可微分”的要求至关重要因为现代深度学习依赖梯度下降法进行优化只有损失函数对模型参数可导我们才能计算梯度从而知道该往哪个方向调整参数能让损失降低。举个例子就像教一个孩子认苹果。你每次指着一个苹果说“这是苹果”模型孩子会输出一个预测比如“这是苹果的概率是80%”。损失函数的作用就是评判这个80%的预测和100%的真实标签之间的差距。如果孩子说“这是橘子的概率是90%”那损失值就会很大提醒我们“教错了”需要纠正。在设计上损失函数需要满足几个基本性质非负性损失值通常大于等于零完美预测时损失为零。可微性便于利用梯度进行优化。针对性不同任务需要不同的损失函数。比如回归任务关心数值差距常用均方误差分类任务关心概率分布差异常用交叉熵。2.2 常见损失函数家族全解析根据任务类型损失函数主要分为几大家族2.2.1 回归任务损失函数回归任务预测连续值如房价、温度。均方误差MSE, Mean Squared Error: 最常用的回归损失。计算预测值与真实值之差的平方的平均值。公式为 $L \frac{1}{n}\sum_{i1}^{n}(y_i - \hat{y}_i)^2$。它对大的误差惩罚更重因为平方因此对异常值比较敏感。PyTorch实现:torch.nn.MSELoss()适用场景 当数据中的噪声服从高斯分布且你不希望出现大的预测偏差时。平均绝对误差MAE, Mean Absolute Error: 计算预测值与真实值之差的绝对值的平均值。公式为 $L \frac{1}{n}\sum_{i1}^{n}|y_i - \hat{y}_i|$。相比MSE它对异常值的鲁棒性更强因为误差是线性增长的。PyTorch实现:torch.nn.L1Loss()(注意L1 Loss就是MAE)适用场景 数据中存在显著异常值时MAE通常比MSE更稳定。Huber Loss: 可以看作是MSE和MAE的结合体。它设定一个阈值δ。当误差绝对值小于δ时采用类似MSE的二次项保证在零点附近可导且平滑当误差大于δ时采用类似MAE的一次项降低对异常值的敏感度。这使其兼具MSE的优化友好性和MAE的鲁棒性。PyTorch实现:torch.nn.HuberLoss(delta1.0)(delta即阈值δ)适用场景 当你对数据中的异常值有一定顾虑但又不想完全放弃MSE的优化效率时。2.2.2 分类任务损失函数分类任务预测离散类别如图片中是猫还是狗。交叉熵损失Cross-Entropy Loss: 这是分类任务的绝对主力尤其适用于多分类。它衡量的是模型预测的概率分布与真实标签的“独热编码”one-hot分布之间的差异。公式为 $L -\sum_{i1}^{C} y_i \log(\hat{y}_i)$其中C是类别数$y_i$是真实标签0或1$\hat{y}_i$是预测为第i类的概率。直观理解就是模型对正确类别的预测概率越高损失就越低。PyTorch实现:torch.nn.CrossEntropyLoss()。这里有个关键细节PyTorch的CrossEntropyLoss实际上已经内置了Softmax操作。这意味着你的模型最后一层不需要再手动加Softmax直接输出原始的“分数”logits即可。nn.CrossEntropyLoss会先做Softmax再计算交叉熵。这既保证了数值稳定性也简化了代码。二分类交叉熵BCE Loss: 交叉熵在二分类任务下的特例。PyTorch中对应torch.nn.BCELoss()。注意使用BCELoss时模型的输出需要先通过Sigmoid函数压缩到[0,1]区间代表正类的概率。带Logits的BCE Loss:torch.nn.BCEWithLogitsLoss()。这个版本将Sigmoid和BCELoss合并同样是为了数值稳定性。这是二分类任务的首选能避免在Sigmoid层可能出现的数值溢出问题。负对数似然损失NLL Loss: 公式为 $L -\log(\hat{y}_{class})$即直接取模型对真实类别预测概率的负对数。它通常与LogSoftmax层结合使用torch.nn.LogSoftmaxtorch.nn.NLLLoss其效果等价于CrossEntropyLoss。在一些需要更灵活处理输出层的情况下会用到。2.2.3 其他高级损失函数合页损失Hinge Loss: 主要用于支持向量机SVM但在某些深度学习分类场景中也有应用特别是要求“间隔”margin最大化的场景。对比损失Contrastive Loss、三元组损失Triplet Loss: 常用于度量学习、人脸识别、图像检索等任务。它们的目标不是直接分类而是学习一个特征嵌入空间使得相似样本的特征距离近不相似样本的特征距离远。Focal Loss: 出自何恺明大神的论文主要用于解决目标检测中前景-背景类别极端不平衡的问题。它在标准交叉熵损失的基础上增加了一个调制因子让模型更关注难分类的样本从而提升对少量目标类别的检测精度。这也是yolov8 改进损失函数中常被讨论和尝试的一个方向。注意损失函数的选择没有银弹。MSE对异常值敏感MAE的梯度在零点不平滑可能导致收敛慢。交叉熵是分类任务的事实标准但样本极度不平衡时可能需要Focal Loss等变体。理解其特性和适用场景比记住公式更重要。3. PyTorch中的损失函数实战与内部机制3.1 环境搭建与基础使用模式在深入代码之前确保你的PyTorch环境是正确的。从热搜词cuda12.1 pytorch版本、anaconda配置pytorch环境可以看出环境是很多人的第一道坎。这里给出一个清晰的指引创建并激活Conda环境(推荐)conda create -n pytorch_env python3.9 conda activate pytorch_env安装PyTorch 务必去 PyTorch官网 使用官方安装命令生成器。根据你的CUDA版本通过nvidia-smi查看选择对应命令。例如对于CUDA 12.1# 这是一个示例请以官网生成器为准 pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121对于电脑中显卡是intel(r) arc(tm) 130t gpu的用户目前PyTorch对Intel Arc显卡的官方原生支持仍在完善中你可能需要关注Intel Extension for PyTorch (IPEX) 来获得最佳性能。验证安装import torch print(torch.__version__) # 查看版本 print(torch.cuda.is_available()) # 查看CUDA是否可用安装好后在PyTorch中使用损失函数的标准流程如下import torch import torch.nn as nn # 1. 定义损失函数 criterion nn.CrossEntropyLoss() # 以交叉熵为例 # 2. 模拟前向传播得到预测值和真实标签 # batch_size4, num_classes10 logits torch.randn(4, 10) # 模型输出的原始分数未经过Softmax labels torch.tensor([1, 3, 9, 5]) # 真实类别标签 # 3. 计算损失 loss criterion(logits, labels) print(fLoss: {loss.item()})这个流程是固定的定义、前向传播得到预测、计算损失、反向传播loss.backward()。3.2 关键参数详解与自定义修改PyTorch的损失函数类通常提供一些参数来调整其行为理解这些参数能让你更精细地控制训练。reduction(减少方式): 这是最重要的参数之一决定了如何对批次batch中每个样本的损失进行聚合。有三个选项mean(默认): 计算批次内所有样本损失的平均值。这是最常用的设置。sum: 计算批次内所有样本损失的总和。当你想自己控制批次损失的权重时有用。none: 不对损失进行聚合返回一个与批次中每个样本一一对应的损失张量。这在需要为每个样本计算不同权重如难易样本挖掘的高级技巧中会用到。criterion_none nn.MSELoss(reductionnone) loss_tensor criterion_none(pred, target) # loss_tensor形状为 [batch_size]weight(类别权重): 在CrossEntropyLoss和BCELoss中可以传入一个一维张量为每个类别指定一个权重。这在处理类别不平衡的数据集时非常有用。例如如果某个类别样本很少可以给它设置一个较大的权重让模型更关注它。# 假设有3个类第0类样本极少我们给它10倍权重 class_weights torch.tensor([10.0, 1.0, 1.0]) criterion nn.CrossEntropyLoss(weightclass_weights)pos_weight(正样本权重): 这是BCEWithLogitsLoss特有的参数用于解决二分类中正负样本不平衡的问题。例如正负样本比例为1:9则可以设置pos_weight9相当于在计算损失时对正样本的损失项乘以9以平衡其影响。实操心得 当你的模型在训练集上表现不错但在某个特定类别上召回率极低时第一个应该检查的就是数据类别平衡性并尝试使用weight或pos_weight参数。这往往比盲目调整网络结构或超参数更有效。3.3 手撕代码实现一个自定义的Focal Loss理解原理最好的方式就是实现它。Focal Loss的公式为 $FL(p_t) -\alpha_t (1 - p_t)^\gamma \log(p_t)$ 其中$p_t$是模型对真实类别的预测概率$\alpha_t$是类别权重$\gamma$是调制因子focusing parameter用于降低易分类样本的权重。我们来用PyTorch实现一个支持多分类的Focal Lossclass FocalLoss(nn.Module): def __init__(self, alphaNone, gamma2.0, reductionmean): alpha: 类别权重列表长度等于类别数。例如 [0.25, 0.75] gamma: 聚焦参数gamma越大对易分类样本的抑制越强。 reduction: none, mean, sum super(FocalLoss, self).__init__() self.alpha alpha self.gamma gamma self.reduction reduction # 内置的交叉熵损失我们将在其基础上改造 self.ce_loss nn.CrossEntropyLoss(reductionnone, weightalpha) def forward(self, inputs, targets): # 计算标准的交叉熵损失 (未经reduction) ce_loss self.ce_loss(inputs, targets) # 获取每个样本对真实类别的预测概率 pt torch.exp(-ce_loss) # 因为 CE -log(p_t)所以 p_t exp(-CE) # 计算Focal Loss的调制因子 focal_weight (1 - pt) ** self.gamma # 应用调制因子 focal_loss focal_weight * ce_loss # 根据reduction参数聚合损失 if self.reduction mean: return focal_loss.mean() elif self.reduction sum: return focal_loss.sum() else: # none return focal_loss # 使用示例 focal_criterion FocalLoss(alpha[0.5, 0.5], gamma2.0) logits torch.randn(8, 2) # 二分类 labels torch.randint(0, 2, (8,)) loss focal_criterion(logits, labels) print(fFocal Loss: {loss.item()})通过这个实现你可以清晰地看到Focal Loss如何通过(1 - p_t)^\gamma来降低那些预测概率高易分类的样本的损失贡献从而让模型更专注于难分类的样本。你可以尝试将其应用到你的目标检测或分类任务中特别是在正负样本极不平衡的场景下。4. 损失函数在训练中的动态行为与调优策略4.1 损失曲线解读不只是看它下降训练模型时我们一定会看损失曲线。但损失曲线能告诉我们的远不止“损失在下降”这么简单。结合热搜词yolov8画损失函数曲线图我们来深入解读理想情况 训练损失和验证损失都平稳下降并最终趋于一个较低的稳定值且两者之间差距不大。这说明模型学习良好没有过拟合也没有欠拟合。训练损失下降验证损失上升过拟合 这是最经典的过拟合信号。模型在训练集上“学得太好”记住了噪声和细节导致在未见过的验证集上表现变差。此时应观察损失曲线一旦验证损失连续多个epoch不降反升就可以考虑提前停止训练Early Stopping。训练损失和验证损失都很高且下降缓慢欠拟合 模型能力不足无法捕捉数据中的基本模式。可能的原因包括模型结构太简单、特征工程不足、学习率设置过低等。损失曲线剧烈震荡 通常意味着学习率设置得太高。优化过程在损失函数的“山谷”里跳跃无法稳定下降到最低点。应适当降低学习率。损失曲线早早就进入平台期 损失下降到一定程度后几乎不再变化。这可能是因为学习率太低优化动力不足也可能是模型架构存在瓶颈或者损失函数本身对于当前任务已接近最优解。实操心得 画损失曲线时一定要把训练损失和验证损失画在同一张图上进行对比。单独看训练损失下降是没意义的。使用TensorBoard、Weights Biases或简单的Matplotlib都可以。对于YOLOv8其训练日志通常会自动生成损失曲线图务必仔细分析。4.2 损失函数与优化器的协同损失函数定义了目标而优化器如SGD, Adam决定了如何朝着目标前进。它们需要协同工作。损失函数的尺度影响学习率 不同的损失函数计算出的损失值尺度可能不同。例如MSE的损失值通常比MAE大因为平方。这意味着当你更换损失函数时最优学习率很可能需要重新调整。一个经验法则是如果新损失函数计算出的初始损失值比之前大一个数量级那么学习率可能需要相应调小。梯度消失/爆炸与损失函数 某些损失函数或网络层如Sigmoid, Tanh可能导致梯度变得极小消失或极大爆炸。虽然现代激活函数ReLU及其变体和权重初始化方法缓解了这一问题但在设计非常深的网络或使用特定损失函数时仍需警惕。选择像MSE这类具有二次形式的损失函数时在饱和区附近梯度可能会很小影响训练。自适应优化器如Adam的鲁棒性 Adam等优化器内置了学习率自适应机制对不同尺度的梯度有更好的适应性。因此在更换损失函数后使用Adam通常比SGD更不容易因学习率设置不当而失败。这也是Adam成为默认选择的原因之一。4.3 多任务学习与自定义复合损失在实际复杂项目中我们经常需要让模型同时优化多个目标这就是多任务学习。例如一个自动驾驶感知模型可能需要同时预测车辆边界框回归、车辆类别分类和车道线分割。这就需要设计一个复合损失函数。复合损失的本质是多个基础损失的加权和总损失 w1 * Loss1 w2 * Loss2 ...关键挑战在于权重的设置手动调参 根据任务的重要性凭经验设置。例如认为分类比定位更重要就给分类损失更高的权重。这种方法简单但费时费力。不确定性加权 一种更优雅的方法是让模型自己学习每个任务的权重。核心思想是将每个任务的损失视为一个高斯分布将分布的方差不确定性作为可学习的参数并让总损失包含这个不确定性。不确定性高的任务其损失权重会自动降低。PyTorch实现起来需要一些技巧但已有相关研究论文和开源代码。GradNorm 另一种动态调整权重的方法其目标是让不同任务的梯度具有相近的量级从而平衡它们的学习速度。这里给出一个简单的多任务损失示例目标检测中的分类回归class MultiTaskLoss(nn.Module): def __init__(self, cls_weight1.0, reg_weight1.0): super().__init__() self.cls_weight cls_weight self.reg_weight reg_weight self.cls_loss nn.CrossEntropyLoss() # 分类损失 self.reg_loss nn.SmoothL1Loss() # 回归损失比L2对异常值更鲁棒 def forward(self, cls_pred, reg_pred, cls_target, reg_target): loss_cls self.cls_loss(cls_pred, cls_target) loss_reg self.reg_loss(reg_pred, reg_target) total_loss self.cls_weight * loss_cls self.reg_weight * loss_reg return total_loss, {cls_loss: loss_cls.item(), reg_loss: loss_reg.item()}在训练循环中你不仅可以得到总损失还能记录下各个子损失的独立值方便分析和调试权重设置是否合理。5. 高级话题损失函数背后的数学与前沿思考5.1 从信息论视角看交叉熵交叉熵损失并非凭空而来它有深刻的信息论背景。在信息论中熵Entropy衡量的是一个概率分布的不确定性。交叉熵 $H(p, q)$ 衡量的是当我们用估计的概率分布 $q$ 去编码真实分布 $p$ 时所需的平均编码长度比特数。在分类任务中真实分布 $p$ 是独热编码确定性的熵为0估计分布 $q$ 是模型的预测概率。我们的目标是最小化交叉熵即让模型预测的分布 $q$ 尽可能接近真实分布 $p$。当 $q p$ 时交叉熵等于真实分布的熵此时为0达到最小值。这就是为什么交叉熵能作为分类任务完美的损失函数——它直接最小化了模型预测分布与真实分布之间的“信息差异”。KL散度Kullback-Leibler Divergence是另一个密切相关的概念它衡量两个分布之间的差异$D_{KL}(p || q) H(p, q) - H(p)$。由于在分类任务中 $H(p)0$所以最小化交叉熵 $H(p, q)$ 等价于最小化KL散度 $D_{KL}(p || q)$。这个视角让我们理解交叉熵损失是在迫使模型的预测概率分布向真实的“独热”分布对齐。5.2 损失函数的地形与优化困境我们可以把损失函数想象成一片复杂的高维地形因为模型参数很多我们的目标是找到这片地形中的最低点全局最小值。但现实是这片地形布满了局部最小值Local Minima 优化器可能陷入某个小坑里出不来虽然这里比周围都低但并不是整片区域最低的点。鞍点Saddle Points 在某些方向上是高点在另一些方向上是低点。在高维空间中鞍点比局部最小值更常见。梯度在鞍点处为零优化会停滞。平坦区域Plateaus 梯度非常小的广阔区域优化速度极慢。优化算法如带动量的SGD、Adam就是我们的“登山装备”帮助我们在复杂地形中更有效地寻找最低点。损失函数本身的地形特性直接决定了优化的难度。例如使用MSE损失配合线性回归其损失地形是一个漂亮的“碗状”凸函数只有一个全局最小值优化很简单。但深度神经网络的损失地形极其非凸充满了各种陷阱。一个重要的实践启示是损失函数的平滑性很重要。这就是为什么在分类任务中我们使用交叉熵配合Softmax而不是直接优化分类准确率。准确率是一个离散的、不可导的指标其地形是阶梯状的梯度几乎处处为零无法指导优化。而交叉熵提供了平滑、可导的替代目标引导模型参数朝着提高准确率的方向移动。5.3 对抗性训练与鲁棒性损失随着深度学习在安全关键领域如自动驾驶、金融的应用模型的鲁棒性受到越来越多的关注。对抗性样本Adversarial Examples——通过对输入添加人眼难以察觉的微小扰动就能使模型产生严重错误——暴露了传统损失函数训练的模型的脆弱性。对抗性训练Adversarial Training是一种提升模型鲁棒性的重要技术。其核心思想是在训练过程中不仅使用原始样本 $(x, y)$还动态地生成针对当前模型的对抗性样本 $x_{adv}$并将这些“坏样本”也纳入训练。此时的损失函数变为原始损失和对抗性损失的综合$L_{total} L(f(x), y) \lambda \cdot L(f(x_{adv}), y)$其中$x_{adv} x \epsilon \cdot sign(\nabla_x L(f(x), y))$这就是著名的FGSMFast Gradient Sign Method攻击。$\lambda$ 是一个权衡系数。这种训练方式相当于让损失函数“看到”更多样的、具有挑战性的样本迫使模型学习到更本质、更鲁棒的特征而不是依赖于数据中脆弱的统计相关性。虽然这会增加训练的计算成本并可能轻微降低在干净数据上的标准准确率但能显著提升模型面对恶意干扰时的稳定性。对于追求高可靠性的应用将对抗性损失纳入考量是损失函数设计的前沿方向之一。6. 实战从零构建一个图像分类项目的完整损失函数应用流程让我们用一个完整的、可运行的CIFAR-10图像分类项目串联起损失函数的所有知识点。你会看到从数据加载、模型定义、损失函数和优化器选择、训练循环到结果分析的完整过程。6.1 项目初始化与数据准备import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms import matplotlib.pyplot as plt # 1. 定义数据预处理和增强 transform_train transforms.Compose([ transforms.RandomCrop(32, padding4), # 随机裁剪数据增强 transforms.RandomHorizontalFlip(), # 随机水平翻转数据增强 transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), # CIFAR-10的均值和标准差 ]) transform_test transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) # 2. 加载数据集 trainset torchvision.datasets.CIFAR10(root./data, trainTrue, downloadTrue, transformtransform_train) trainloader torch.utils.data.DataLoader(trainset, batch_size128, shuffleTrue, num_workers2) testset torchvision.datasets.CIFAR10(root./data, trainFalse, downloadTrue, transformtransform_test) testloader torch.utils.data.DataLoader(testset, batch_size100, shuffleFalse, num_workers2) classes (plane, car, bird, cat, deer, dog, frog, horse, ship, truck)6.2 定义一个简单的卷积神经网络模型class SimpleCNN(nn.Module): def __init__(self, num_classes10): super(SimpleCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), ) self.classifier nn.Sequential( nn.Dropout(p0.5), # Dropout防止过拟合 nn.Linear(64 * 8 * 8, 512), nn.ReLU(inplaceTrue), nn.Dropout(p0.5), nn.Linear(512, num_classes) # 输出10个类别的logits ) def forward(self, x): x self.features(x) x torch.flatten(x, 1) # 展平特征图 x self.classifier(x) return x device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleCNN().to(device)6.3 配置损失函数、优化器与学习率调度器这里我们展示不同损失函数的选择和对比。在实际项目中你可以通过注释/取消注释来切换体验。# 选择1标准交叉熵损失最常用 criterion nn.CrossEntropyLoss() # 选择2带类别权重的交叉熵损失假设我们人为认为某些类更重要 # 计算或定义类别权重例如从数据集中计算 # class_counts [train_data中每个类的样本数] # class_weights 1.0 / torch.tensor(class_counts, dtypetorch.float) # class_weights class_weights / class_weights.sum() # 归一化可选 # criterion nn.CrossEntropyLoss(weightclass_weights.to(device)) # 选择3我们之前自定义的Focal Loss (用于处理难易样本不平衡) # criterion FocalLoss(gamma2.0, reductionmean).to(device) # 优化器Adam是默认的稳健选择 optimizer optim.Adam(model.parameters(), lr0.001, weight_decay1e-4) # weight_decay是L2正则化 # 学习率调度器在训练过程中动态降低学习率有助于后期精细调参 scheduler optim.lr_scheduler.StepLR(optimizer, step_size30, gamma0.1) # 每30个epoch学习率乘以0.16.4 训练循环与损失监控这是核心部分我们将详细记录并可视化损失。def train_model(model, trainloader, testloader, criterion, optimizer, scheduler, num_epochs50): train_loss_history [] test_loss_history [] test_acc_history [] for epoch in range(num_epochs): model.train() # 设置为训练模式 running_loss 0.0 for i, data in enumerate(trainloader, 0): inputs, labels data inputs, labels inputs.to(device), labels.to(device) # 清零梯度 optimizer.zero_grad() # 前向传播 outputs model(inputs) loss criterion(outputs, labels) # 反向传播与优化 loss.backward() optimizer.step() # 统计损失 running_loss loss.item() # 每100个batch打印一次 if i % 100 99: print(f[Epoch {epoch1}, Batch {i1}] loss: {running_loss / 100:.3f}) running_loss 0.0 # 一个epoch结束后在测试集上评估 model.eval() # 设置为评估模式 test_loss 0.0 correct 0 total 0 with torch.no_grad(): # 不计算梯度节省内存和计算 for data in testloader: images, labels data images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) test_loss loss.item() _, predicted torch.max(outputs.data, 1) total labels.size(0) correct (predicted labels).sum().item() avg_train_loss ... # 需要从trainloader中计算或记录每个epoch的平均训练损失 avg_test_loss test_loss / len(testloader) test_accuracy 100 * correct / total train_loss_history.append(avg_train_loss) test_loss_history.append(avg_test_loss) test_acc_history.append(test_accuracy) print(fEpoch {epoch1} finished. Test Loss: {avg_test_loss:.3f}, Test Acc: {test_accuracy:.2f}%) # 更新学习率 scheduler.step() print(Training Finished.) return train_loss_history, test_loss_history, test_acc_history # 开始训练 train_loss_hist, test_loss_hist, test_acc_hist train_model(model, trainloader, testloader, criterion, optimizer, scheduler, num_epochs50)6.5 结果分析与问题排查训练完成后绘制损失和准确率曲线是必须的。# 绘制损失曲线 plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(range(1, len(train_loss_hist)1), train_loss_hist, labelTrain Loss) plt.plot(range(1, len(test_loss_hist)1), test_loss_hist, labelTest Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(Training and Test Loss) plt.legend() plt.grid(True) # 绘制准确率曲线 plt.subplot(1, 2, 2) plt.plot(range(1, len(test_acc_hist)1), test_acc_hist, labelTest Accuracy, colorgreen) plt.xlabel(Epoch) plt.ylabel(Accuracy (%)) plt.title(Test Accuracy) plt.legend() plt.grid(True) plt.tight_layout() plt.show()如何根据曲线排查问题如果测试损失远高于训练损失典型的过拟合。可以尝试增加数据增强如RandomRotation,ColorJitter。增强正则化增大Dropout比率、增大weight_decayL2正则化强度。使用更简单的模型。如果训练损失下降很慢可能是学习率太低、模型容量不足或优化器问题。可以尝试适当提高学习率或使用学习率预热Warmup。使用更复杂的模型架构。检查梯度是否消失可以通过打印模型中间层的梯度范数来诊断。如果损失曲线出现NaN非数字这是一个严重错误。常见原因学习率过高导致梯度爆炸。立即降低学习率。损失函数或模型中有数学上不稳定的操作例如对数为零或负数。确保模型输出经过适当的激活函数如Softmax处理并且输入数据经过归一化。数据中包含无效值如NaN或Inf。在数据加载后添加检查。一个关键的调试技巧在训练开始的前几个batch手动计算并打印一次损失值并检查模型输出和标签的尺度、范围是否合理。这能提前发现很多数据预处理或模型定义的问题。