深度学习代码跑通后如何系统做实验:基线、损失函数与部署精度指南
2026/8/29 8:10:12 网站建设 项目流程

在深度学习项目里,代码能跑通只是第一步。真正拉开差距的,是跑通之后如何系统性地完成模型改进、损失函数调优、实验管理和结果复现。很多团队把大量时间花在改网络结构和调超参数上,却忽略了基线管理、错误分析和评估一致性,最后得到的模型既无法解释,也无法上线。这篇文章要解决的问题就是:代码跑起来之后,下一步应该做什么,以及每一步怎么做才不至于白做。

文章会沿着一条完整的技术主线展开:先讲清楚跑通代码和完成有效训练之间的差距,再建立一个最小可复现的实验基线,然后通过错误分析确定改进方向,接着讨论损失函数和模型结构改进的正确做法,最后把部署阶段的数值精度问题一起讲透。读者可以是刚跑通代码还没理清思路的初学者,也可以是已经在模型改进过程中反复遇到“改了没效果、调了没过拟合、部署后指标掉”的开发人员。学完后,你至少能得到一套可复用的实验记录模板和排查清单,后续所有实验都能往这个框架里填。

1. 先想清楚:代码跑通不等于实验完成

1.1 跑通和有效训练是两种状态

很多人判断一个深度学习项目是否成功,标准是“按下训练按钮之后没有报错,loss一直在下降”。这个标准在演示阶段够用,但在实际项目里远远不够。跑通代码只是说明程序路径是通顺的,而有效训练要求的是结果可信、可复现、可对比

把这两个状态放进同一张表里看会更清楚:

维度代码跑通有效训练
训练过程进程不退出,loss 有变化收敛稳定,指标在合理范围
评估行为能输出验证集结果使用固定评估脚本,指标可复现
模型保存可能只保存了最后一次权重保存最佳权重和完整训练信息
随机性结果每次都不同固定种子后结果基本一致
改进能力改参数后不知道是否真正变好每次实验有对应基线,能判断提升
部署准备未验证数值精度和推理一致性训练和部署精度差异已量化

实际项目里最常见的误区,是看到 loss 下降就以为模型在“学东西”。但如果数据加载顺序错乱、标签映射错误、验证集混入训练集,loss 一样会下降,模型一样会收敛,最后评估出来的指标却没有任何参考价值。所以第一步不是急着换模型,而是先确认现在的实验结果是否可信。

1.2 最小可复现基线是后续所有改进的地基

所谓“最小可复现基线”,是用当前代码得到一组稳定的、固定的指标,并且这组指标可以被任何人重新跑出来。它不需要效果好,甚至不需要超过随机太多,但它必须满足三个条件:数据固定、配置固定、评估方式固定

操作顺序建议如下:

  1. 先用极小规模数据跑 100 个 batch,确认前向、反向、梯度更新不报错。
  2. 用完整训练集跑一个完整 epoch,观察单 epoch 耗时和显存占用。
  3. 在验证集上跑一次评估脚本,记录指标。
  4. 把模型结构、损失函数、优化器、学习率、batch size、数据路径全部写进配置文件。
  5. 保存这次实验的所有日志和最佳权重,作为第 0 号实验。

很多项目没有做第 5 步。等到后来改了损失函数,发现验证指标提升了 2%,却说不清提升是因为损失函数改对了,还是因为这次训练时随机种子碰巧好、数据增强随机方式变了。没有基线,所有改进都是猜测。

1.3 实验记录模板应该包含哪些内容

记录实验不是写日记,而是要让每条信息都能回答“这次实验和上一次到底差在哪里”。推荐用实验编号加配置目录的方式管理,目录结构可以是:

experiments/ exp001_baseline/ config.yaml train.log best_model.pt metrics.json exp002_focal_loss/ config.yaml train.log best_model.pt metrics.json

每个实验目录里至少记录以下信息:

记录项示例值作用
数据集版本data_v2_20240401数据是否变化
模型结构resnet50 + se_block结构是否变化
损失函数cross_entropy_weighted损失是否变化
优化器sgd_momentum_0.9优化策略是否变化
初始学习率0.01超参数是否变化
batch size32训练吞吐是否变化
训练轮数50训练量是否变化
最佳 epoch37最佳模型出现位置
验证指标acc=0.862最终结果
备注只改了 Focal Loss gamma=2.0人类可读的变更说明

如果你想在团队里使用,建议把这份记录做成 Markdown 模板或 YAML 文件,放在每个实验目录里。没有记录的训练,等于没有跑过。

2. 从第一次有效训练开始,把“能训练”升级成“可评估”

2.1 固定随机种子和确定性设置

深度学习训练过程中存在多个随机来源:数据加载顺序、模型权重初始化、Dropout 随机失活、GPU 并行计算顺序。如果不固定随机种子,哪怕训练代码完全一致,两次训练出来的模型指标也会有波动。波动幅度可能不大,但足够掩盖真实改进效果。

PyTorch 里一个常用的固定种子函数如下:

import random import numpy as np import torch def set_seed(seed: int): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False

代码里有几个关键点需要理解。torch.manual_seed负责 CPU 上的随机数生成,torch.cuda.manual_seed_all负责所有 GPU 上的随机数生成,这两个通常要同时设置。cudnn.deterministic = True让 cuDNN 选择确定性算法,保证卷积计算顺序稳定。cudnn.benchmark = False关闭自动寻找最快卷积算法的机制,因为 benchmark 模式会引入微小波动。

但要注意,固定 seed 不能解决所有可复现问题。DataLoader 开启num_workers > 0之后,数据加载子进程的随机行为仍然可能造成波动。如果对复现性要求很高,可以给 DataLoader 也设置generator=torch.Generator().manual_seed(seed),并且在测试阶段关闭 Dropout、BatchNorm 切换为 eval 模式。

注意:确定性模式会让训练变慢一些。它在“复现实验结果”和“追求最快训练速度”之间是一种取舍。日常消融实验建议开启确定性,纯生产训练如果对速度敏感,可以关闭确定性但保留日志记录。

2.2 记录训练指标和评估指标

训练指标是给训练过程看的,评估指标是给模型效果看的。训练时最常看的指标是 loss,它能反映收敛趋势,但 loss 下降不代表评估指标会同步上升,尤其在模型过拟合之后。所以每次实验至少要同时记录两类指标:

  • 训练过程指标:loss、梯度范数、当前学习率、每秒处理样本数。
  • 验证评估指标:准确率、精确率、召回率、F1、mAP、IoU 等,具体取决于任务类型。

记录指标最省事的方式是使用 TensorBoard 或 WandB。如果不想引入额外平台,也可以自己写一个轻量保存逻辑:

best_score = 0.0 for epoch in range(epochs): train_loss = train_one_epoch(model, train_loader, optimizer, criterion, device) val_score = validate(model, val_loader, device) print(f"epoch={epoch}, train_loss={train_loss:.4f}, val_score={val_score:.4f}") if val_score > best_score: best_score = val_score torch.save( { "epoch": epoch, "model_state_dict": model.state_dict(), "optimizer_state_dict": optimizer.state_dict(), "best_score": best_score, "config": config, }, "best_model.pt", )

这段代码里有两个容易被新手忽视的地方。一个是保存 optimizer_state_dict,断点续训时需要恢复优化器的动量等信息,只保存模型权重会导致续训后训练行为发生变化。另一个是保存 config,checkpoint 文件自带配置后,后续排查权重来自哪次实验时就不用再去翻目录。

这里的验证指标可以是准确率,也可以是 mAP、IoU 等。关键是判断“哪个值越大模型越好”时要保持一致,后续所有实验都用同一个评估函数计算,否则会前功尽弃。

2.3 验证集必须与训练集严格隔离

有一种“假改进”现象是:训练时发现 loss 不断下降,验证集指标也一路暴涨,最后部署到真实场景却表现很差。最常见的原因是验证集没有和训练集严格隔离,或者验证流程里混入了不合理的预处理。

需要检查的点包括:

  1. 数据划分时是否按文件列表随机切分,同一个样本是否可能同时出现在训练集和验证集。
  2. 评估时是否使用了训练阶段的数据增强,比如随机裁剪、随机翻转。验证集应该只做确定性预处理。
  3. 训练时的归一化参数,是否也用在验证和推理阶段。均值、方差必须和训练阶段一致。
  4. 验证脚本和数据加载代码是否和训练脚本完全分离,避免开发时不小心修改了评估函数。

如果发现验证集指标比训练集还好一大截,不要高兴,先怀疑数据泄漏。正确做法是先画出训练集和验证集的标签分布,确认分布接近,再检查文件路径是否有重叠。

3. 用错误分析决定改进方向,而不是凭感觉调参

3.1 对验证集做逐样本错误分析

loss 是一个聚合指标,它告诉你模型整体表现不好,但不会告诉你具体错在哪里。模型改进最忌讳的做法是:看到指标不行,就盲目改学习率、加网络层数、换损失函数。正确做法是先做错误分析,把模型在验证集上的错误样本翻出来,看它们是哪一种错误。

简单实现如下:

model.eval() wrong_samples = [] with torch.no_grad(): for batch_idx, (x, y_true) in enumerate(val_loader): logits = model(x) y_pred = logits.argmax(dim=1) probs = torch.softmax(logits, dim=1) mask = y_pred != y_true for i in torch.nonzero(mask).flatten().tolist(): wrong_samples.append( { "batch": batch_idx, "index_in_batch": i, "true": y_true[i].item(), "pred": y_pred[i].item(), "confidence": probs[i, y_pred[i]].item(), } ) print(f"total wrong samples: {len(wrong_samples)}")

拿到错误样本之后,先不要急着看 loss。按置信度排序,观察两个极端:

  • 高置信度但预测错误:模型很自信地错了。这类样本往往意味着训练数据里有标签噪声,或者模型在某些特征上过拟合。
  • 低置信度错误:模型本身就犹豫。这类样本说明模型学到该类别特征的能力不足,通常需要增加数据量、增加特征表达能力,或者调整损失函数。

3.2 使用混淆矩阵定位类别问题

对分类任务来说,混淆矩阵是最直接的定位工具。使用 sklearn 可以快速计算:

from sklearn.metrics import confusion_matrix, classification_report cm = confusion_matrix(y_true_list, y_pred_list) print(cm) print(classification_report(y_true_list, y_pred_list))

读取混淆矩阵时要关注几个位置:

  • 对角线数值是否明显高于其它位置。
  • 哪些类别之间互相混淆最严重。
  • 某类的样本量是否很少,导致 recall 很低。

如果模型总是把类别 A 预测成类别 B,说明这两个类别在特征空间里过于接近,优先考虑数据增强、类别加权或者难例挖掘,而不是盲目加深网络。如果某个类别样本极少,换更复杂的模型大概率没有用,重点应该放在数据采集、数据增强或者使用类别平衡的损失函数。

3.3 可视化 bad case,发现标注和预处理问题

在图像任务中,把预测错误的输入图片连同标签、预测结果、置信度一起保存下来,是找数据问题的最快方式。很多模型“改进”到最后发现,真正的问题是标注边界不统一,或者预处理时做了错误归一化。

可视化 bad case 时建议按类别分组保存,比如:

bad_cases/ class_0/ sample_with_true_0_pred_1.jpg ... class_1/ ...

一个很小的细节:保存图片时要同时保存原始输入和模型实际看到的输入。模型实际看到的输入是经过归一化、resize 之后的版本。如果你只保存原始图片,可能会漏掉预处理环节导致的问题。

注意:可视化 bad case 的目标不是找到几个错误样本就结束,而是统计错误模式。等你看到第三十个错误样本时,基本能判断是数据问题、模型容量问题还是类别不平衡问题。

4. 损失函数改进:先理解当前损失函数的局限,再动手修改

4.1 交叉熵损失函数为什么是默认选择

交叉熵损失函数是分类任务最常用的损失函数,PyTorch 中可以直接使用:

import torch.nn as nn criterion = nn.CrossEntropyLoss(weight=None)

它的含义是:对于正确类别,希望模型输出的概率尽量接近 1。从公式上看:

L = -sum(y * log(p))

其中 y 是 one-hot 标签,p 是模型预测的概率分布。这个公式简单、可导、梯度稳定,在类别均衡的多分类任务里已经是很好的选择。

但交叉熵有几个明显局限:

  1. 对类别不平衡不敏感。如果 99% 的样本是背景类,1% 是目标类,交叉熵会让模型学习到一个“把所有人预测成背景”的局部最优,loss 依然很低。
  2. 对难易样本一视同仁。大量已经分类正确的样本仍然贡献梯度,可能淹没少量困难样本的梯度。
  3. 对像素级分割任务,它只考虑逐像素误差,不考虑区域整体结构。

因此在做损失函数改进之前,先要问自己:当前任务的问题到底是不平衡、难样本、还是区域连续性?不要因为“别人用了 Focal Loss 效果不错”就直接照搬。

4.2 常见损失函数改进与实现示例

损失函数核心思想适合任务注意事项
加权交叉熵按类别频率给 loss 加权类别不平衡分类权重设计会影响收敛,过大会导致震荡
Focal Loss降低易分样本权重,聚焦难样本稠密目标检测、极端不平衡gamma 和 alpha 需要调参
Dice Loss优化区域重叠度医学图像分割小目标区域训练不稳定
CE + Dice结合逐像素和区域损失分割任务两个损失需要控制量级

加权交叉熵只需要改nn.CrossEntropyLoss(weight=class_weight),权重为每个类别的样本占比倒数或平方根倒数。

Focal Loss 是另一个常用改进,核心实现如下:

import torch import torch.nn as nn import torch.nn.functional as F class FocalLoss(nn.Module): def __init__(self, alpha=None, gamma=2.0, reduction="mean"): super().__init__() self.alpha = alpha self.gamma = gamma self.reduction = reduction def forward(self, logits, targets): ce = F.cross_entropy(logits, targets, reduction="none") pt = torch.exp(-ce) focal_loss = (1 - pt) ** self.gamma * ce if self.alpha is not None: if isinstance(self.alpha, (float, int)): alpha_t = self.alpha else: alpha_t = self.alpha[targets] focal_loss = alpha_t * focal_loss if self.reduction == "mean": return focal_loss.mean() if self.reduction == "sum": return focal_loss.sum() return focal_loss

这段代码的关键是pt。它表示模型对真实类别的预测概率,数值越接近 1,说明样本越容易分类。(1 - pt) ** gamma会给容易分类的样本一个很小的系数,给难分类的样本保留较大权重。gamma 通常取 2,越大越关注难样本,但过大容易让训练不稳定。

如果是二分类任务或者类别极不平衡的目标检测,还有一个思路是使用基于归一化高斯距离的 NWD-Loss 处理小目标,它把目标框建模成高斯分布,缓解小目标对 IoU 类损失过于敏感的问题。这类损失在特定任务中很有效,但落地前要确认实现和任务场景是否匹配。

做损失函数改进时最容易踩三个坑:

  • 只在训练集上对比 loss 是否下降,却不看验证集评估指标。损失函数的改进一定要落到评估指标上。
  • 直接组合多个损失后数值量级差异巨大。比如 CE 是 1.0,Dice 是 0.1,组合后 Dice 贡献被忽略,实验结果等于没改。
  • alpha 或 class weight 没有放到正确的设备上。代码里用了self.alpha[targets]时,alpha 必须和 targets 在同一个 device,否则会报设备不一致错误。

4.3 自定义组合损失时如何控制量级

组合多个损失时,先分别计算每个损失在第一批数据上的数值范围,再设置权重。下面是一个 CE + Dice 的组合:

class CombinedLoss(nn.Module): def __init__(self, ce_weight=1.0, dice_weight=1.0): super().__init__() self.ce = nn.CrossEntropyLoss() self.ce_weight = ce_weight self.dice_weight = dice_weight def forward(self, logits, targets): ce_loss = self.ce(logits, targets) dice_loss = dice_loss_fn(logits, targets) return self.ce_weight * ce_loss + self.dice_weight * dice_loss

对应的 Dice Loss 简版实现:

def dice_loss_fn(logits, targets, eps=1e-6): probs = torch.softmax(logits, dim=1) num_classes = probs.shape[1] one_hot = F.one_hot(targets, num_classes=num_classes).permute(0, 3, 1, 2).float() intersection = (probs * one_hot).sum(dim=(2, 3)) union = probs.sum(dim=(2, 3)) + one_hot.sum(dim=(2, 3)) dice = (2.0 * intersection + eps) / (union + eps) return 1.0 - dice.mean()

logits 的形状是(B, C, H, W),targets 的形状是(B, H, W),两个损失相加前最好都打印出来看一下数值量级。推荐先让每个损失单独在 100 个 batch 上输出平均值,再决定权重。不要凭感觉写ce_weight=1.0, dice_weight=10.0,可能一上来就把梯度方向带偏。

5. 模型结构改进:在核心模块上做小而稳的改动

5.1 先确定改进位置:主干、颈部还是头部

模型结构改进不能没有方向。以常见检测和分割网络为例,可以把改动位置分成三段:

改进位置常见手段影响范围风险
主干网络换 ResNet 系列、增加注意力模块特征提取能力计算量增加,收敛变慢
颈部网络FPN 多尺度融合、PANet多尺度特征融合结构复杂度提升
头部网络检测头、分割头、分类头任务预测能力容易过拟合

先跑完基线实验,再结合第 3 章的错误分析结果判断瓶颈在哪。如果小目标检测效果差,问题可能出在颈部特征融合不够,而不是简单加深主干;如果所有类别的边缘都比较粗糙,问题可能出在分割头或损失函数;如果训练集上效果很好但验证集效果差,结构已经够强,应该先处理过拟合。

以 UNet 这类分割网络为例,如果觉得特征通道的利用效率不高,可以在编码器每个 block 的卷积之后插入 SE Block。这类改动比直接更换整个主干更容易控制变量。

5.2 给模型加入注意力模块的示例

SE Block 是一种经典的通道注意力模块,它通过全局池化计算每个通道的重要性,然后对特征图做通道加权。实现如下:

import torch from torch import nn class SEBlock(nn.Module): def __init__(self, channels, reduction=16): super().__init__() self.squeeze = nn.AdaptiveAvgPool2d(1) self.excitation = nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplace=True), nn.Linear(channels // reduction, channels), nn.Sigmoid(), ) def forward(self, x): b, c, _, _ = x.shape y = self.squeeze(x).view(b, c) y = self.excitation(y).view(b, c, 1, 1) return x * y

SE Block 的 forward 过程分为两步:先用全局平均池化把一个通道压缩成一个数值,得到全局描述;再通过两层全连接和 Sigmoid 得到 0 到 1 之间的权重,把权重乘回原始特征图。reduction控制中间层的压缩比例,常见取 16。

插入已有网络时,要保证位置一致。通常放在残差块之后、激活函数之前。以 UNet 为例,可以在每个编码器 block 的卷积输出后面加一个SEBlock(channels)。如果插入位置不一致,对比实验就无法解释是注意力模块起作用,还是只是网络加深了。

注意:不要试图在同一个实验里同时换主干、加注意力、改损失函数。一次只改一个变量,否则改进的来源无法判断。

5.3 改进后的对比实验必须遵守一个变量原则

设计对比实验时,用表格记录会比记忆更可靠:

实验编号网络结构损失函数其他训练配置验证指标结论
exp001baselineCElr=0.01, bs=320.862基线
exp002baselineFocal Loss同上0.871损失改进有效
exp003baseline + SECE同上0.868结构改进有效
exp004baseline + SEFocal Loss同上0.875两者叠加有效

这里面最容易出错的地方是“其他训练配置”并没有真的保持一致。比如改了损失函数之后,某个全局temperature参数没有复用;或者在加注意力模块时,没有保持初始化的方式一致。建议把训练代码抽成同一个入口,让 loss 和 model 通过配置文件传入,而不是复制多个脚本。

6. 模型部署前要搞清楚数值精度:FP32、FP16、BF16、TF32

6.1 四种浮点格式的核心差异

模型改进完、评估也满意之后,进入部署阶段,很多问题会集中体现在数值精度上。训练时默认使用 FP32,但部署推理时为了提速和节省显存,通常会把模型转换成 FP16、BF16,或者利用 Tensor Core 的 TF32 计算路径。理解这些格式的差异,既是部署基础,也是在模型改进过程中避免“训练好、部署差”的关键。

格式总位数指数位尾数位常见用途主要注意点
FP3232 位8 位23 位训练默认、CPU/GPU 通用精度高但显存占用大
FP1616 位5 位10 位混合精度训练、GPU 推理加速范围小,容易溢出,需要 loss scaling
BF1616 位8 位7 位混合精度训练、大模型训练范围接近 FP32,但精度相对较低
TF3232 位输入19 位内部精度内部Tensor Core 加速路径Ampere 及更新架构常见,精度介于 FP32 和 FP16 之间

FP16 的问题是数值范围太小,指数位只有 5 位,能表示的最大值约 65504。训练过程中激活值一旦超过这个范围,就会出现 inf 或 NaN。所以 PyTorch 做混合精度训练时,需要搭配GradScaler,它反向传播前对梯度做缩放,更新参数前再缩小回来。

BF16 用 16 位换来了和 FP32 一样的指数范围,尾数位却少了,所以它的相对精度比 FP16 低,但由于动态范围大,大模型训练更稳定。TF32 不是一个新的存储格式,而是在 Tensor Core 计算中使用的一种截断精度模式,读取 FP32 数据后用 19 位内部精度计算。

6.2 用 PyTorch AMP 做混合精度训练时的正确姿势

PyTorch 官方推荐使用torch.cuda.amp做自动混合精度训练,核心代码是:

scaler = torch.cuda.amp.GradScaler() for batch in train_loader: x, y = batch optimizer.zero_grad() with torch.cuda.amp.autocast(): logits = model(x) loss = criterion(logits, y) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()

autocast负责在 forward 过程中自动选择哪些算子用 FP16、哪些继续用 FP32。GradScaler负责把 loss 放大一个系数,完成反向传播后再把梯度缩小回来,避免小梯度在 FP16 下变成 0。

这段代码常见的问题是:

  • 忘了加GradScaler,导致 loss 正常但梯度已经溢出。
  • autocast外部手动把模型输出转成 FP16,反而破坏了自动精度选择。
  • 验证和部署阶段不小心启用了autocast,导致输出精度低于预期。

6.3 如何量化验证精度修改是否破坏模型

部署前如果用 FP16 或 TF32 跑推理,必须做一次精度对比。方法很简单:用同一批测试数据分别跑 FP32 和 FP16,计算预测输出的最大绝对误差,以及评估指标差异。

model_fp32.eval() model_fp16.eval() max_diff = 0.0 with torch.no_grad(): for x in test_loader: y1 = model_fp32(x) y2 = model_fp16(x).float() diff = (y1 - y2).abs().max().item() max_diff = max(max_diff, diff) print(f"max diff: {max_diff:.6f}")

如果最大误差很小,再分别计算 FP32 和 FP16 的评估指标。评估指标通常允许零点几百分比的波动。如果 mAP 掉得比较多,优先检查:

  1. 归一化参数是否一致。
  2. 模型是否包含对数值范围敏感的算子,比如 Softmax 和 LayerNorm。
  3. 是否有溢出或下溢,中间激活值是否出现 inf。

生产环境里不要只看“模型能推理”就认为部署成功,要同时记录部署精度和参考精度,两者差异必须在可接受范围内。

7. 常见问题排查与完整实验管理清单

7.1 从 loss 现象倒推问题来源

模型改进过程中遇到的情况通常有规律。下面是一张可以直接对照排查的表:

问题现象可能原因检查方式处理建议
loss 完全不变学习率过小、梯度为 0、数据和标签不对应、模型输出被错误截断打印 loss 和梯度范数;检查输入输出形状用极小数据过拟合一个 batch,再用标准学习率训练
loss 下降很快但验证指标不涨数据泄漏、过拟合、验证代码不一致检查数据划分;对比训练和验证预处理修复数据划分,统一评估脚本
loss 出现 NaN学习率过大、FP16 溢出、log(0)、梯度爆炸打印梯度范数;开启 AMP 的 GradScaler降低学习率,检查损失函数内部有无除零风险
训练集指标好但验证集差过拟合观察训练和验证曲线是否持续拉大增加数据增强、降低模型容量、加入正则化
训练不稳定震荡batch size 过小、损失函数数值量级失衡画出每个 batch 的 loss增加 batch size,调整损失权重
换了损失函数后指标反而下降没有对齐数值量级、参数不合适分别打印各分量 loss单独验证各损失,再组合

排查时建议按顺序来:先检查输入数据的形状和标签分布,再检查模型 forward 和 loss 是否对齐,接着看梯度范数,最后才调学习率。直接改学习率很容易掩盖真实问题,尤其是标签错位这类基础问题。

7.2 每次实验发布前都要确认的清单

整理一份可复用的实验管理清单,可以贴在项目 README 里,每次实验开始和结束各检查一遍:

  • [ ] 代码是否提交到 Git,commit 号是否记录。
  • [ ] 数据集版本、划分方式是否固定。
  • [ ] 是否设置随机种子。
  • [ ] 模型结构、损失函数、优化器、学习率、batch size 是否写入配置。
  • [ ] 验证集和训练集是否严格分离。
  • [ ] 评估脚本是否与训练脚本独立并保持固定。
  • [ ] 是否保存了 best checkpoint 和 last checkpoint。
  • [ ] checkpoint 是否包含 optimizer 状态和 config。
  • [ ] 是否记录了训练日志和评估指标。
  • [ ] 部署阶段是否用同一套预处理。
  • [ ] 若切换 FP16、BF16 或 TF32,是否做了精度对比。

这套清单的核心价值不是走形式,而是保证任何一次“改进”都能追溯到明确的实验变化。没有这套流程,模型改进基本靠运气。

7.3 下一步可以扩展的方向

如果基线已经稳定,错误分析也做完了,损失函数和模型结构都有一轮有效改进,可以继续向三个方向扩展:

  1. 超参数搜索。使用网格搜索、随机搜索或贝叶斯优化,但每次搜索都要走同一个实验记录流程,否则结果无法归因。
  2. 数据增强策略。从简单翻转、裁剪,到更强的 MixUp、CutMix 等,重点是观察验证集是否同步提升。
  3. 模型压缩。训练完成后做剪枝、量化和知识蒸馏,目标是在保住评估指标的前提下减小模型体积和推理耗时。

这三个方向都不是独立的,最终都要回到同一个问题:评估指标是否提升、是否可解释、是否可复现。没有评估闭环的扩展,只是在制造更多的实验噪声。

回到开头那个问题:深度学习代码跑通后要做什么?核心答案不是马上换大模型,也不是盲目调学习率,而是先把基线训练、评估、记录这套闭环建立起来,然后在损失函数和模型结构上做受控改进,最后在部署精度层面验证结果是否可接受。对于刚入门的读者,建议从今天开始给每个实验建一个目录,把配置、日志、权重、指标全部放进去,坚持几个项目之后,你会明显感觉到模型的每次改进都有据可查,不会再出现“改了不知道有没有用”的处境。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询