新闻详情

新闻详情

首页 / 资讯中心 / 详情

深度学习训练必懂:epoch、batch、loss与val_loss解析

发布时间:2026/10/2 19:38:15来源:尧图网络
深度学习训练必懂:epoch、batch、loss与val_loss解析
训练日志刷屏的时候epoch、batch、loss、val_loss 这四个词大概是最早出现、也最容易被跳过的。我第一次跑通一个图像分类脚本盯着终端里 loss 从 2.3 掉到 0.4而 val_loss 在 0.6 附近来回横跳第一反应是代码是不是写错了。后来才想明白这不是 bug是模型在明明白白告诉我它开始背训练集了。那一次之后我才真正开始把 loss 和 val_loss 当成两回事来看而不是简单地越小越好。这篇东西想解决的就是这个最基础、但大多数人没被讲透的问题epoch、batch、loss、val_loss 这四个词到底各自指什么它们之间怎么换算训练时该盯哪个数字看到异常曲线又该往哪个方向查。适合刚上手深度学习训练脚本的人也适合已经能跑通模型、但调参基本靠猜的人。我会把概念、公式、代码骨架、曲线诊断和踩过的坑串成一条线尽量让你读完就能回头改自己的训练循环。1. 一次训练循环里epoch、batch、step 各自站在哪个位置1.1 从看完一遍数据说起先给最容易记住的说法epoch 是把训练集完整看一遍这个动作的计数单位。你有 50000 条样本模型从第 1 条看到第 50000 条这就是 1 个 epoch。跑 30 个 epoch意思就是模型把这批数据翻来覆去看了 30 遍。问题在于现实里几乎不会一次把所有样本塞进模型。一是显存装不下二是全量梯度更新一次太慢、且容易卡在鞍点。所以我们会把训练集切成若干小块每块叫一个batch也叫 mini-batch模型吃一个 batch、算一次误差、更新一次参数然后吃下一个。这个吃一个 batch 就更新一次参数的动作叫一个step或者叫 iteration。所以三者的层级关系是epoch 由若干个 step 组成每个 step 处理一个 batch。搞清楚这一层后面所有的换算和调参才有落脚点。我见过不少人在日志里看到step 4000会慌以为训练结束了其实那只是第 4000 次参数更新离 epoch 结束还早——这两个计数器的单位根本不是一回事。1.2 三个换算公式和两个容易踩的边界设训练集样本数为 Nbatch_size 为 B那么每个 epoch 的 step 数 ceil(N / B) # 除不尽时向上取整 一次训练的总 step 数 epochs × ceil(N / B) 等效 batch_size B × 梯度累积步数拿 N 50000、B 128 算一下50000 / 128 390.625也就是一个 epoch 有 391 个 step。最后一个 batch 只有 80 条样本50000 - 390×128 80这是第一个边界。这时候 DataLoader 的drop_last就有用了。drop_lastTrue会丢掉最后那个不满的 batch一个 epoch 变成 390 个 stepdrop_lastFalse保留它但你的最后一个 batch 的统计特性和前面 390 个都不一样。如果网络里有 BatchNorm 层这个 80 条的 batch 会算出一组偏差明显的均值和方差偶尔就能看到 loss 在 epoch 末尾抖一下。规模越小、batch_size 越大这个抖动越明显。第二个边界是梯度累积。显存不够时常见做法是小 batch 前向多次之后再更新一次accum_steps 4 for i, (x, y) in enumerate(loader): loss criterion(model(x), y) / accum_steps # 注意要除 loss.backward() if (i 1) % accum_steps 0: optimizer.step() optimizer.zero_grad(set_to_noneTrue)这样 batch_size32、accum_steps4等效 batch 就是 128。注意 loss 除以 accum_steps 这一步别漏漏了等于把学习率悄悄放大了 4 倍曲线会以一种很难解释的方式发散。另外要清楚梯度累积能把等效 batch 做大但 BatchNorm 看到的仍然只是 32 条样本的统计量它并不能替代真正的大 batch。这是个非常常见的误解。1.3 batch_size 到底在权衡什么batch_size 不是一个随便填个数能跑就行的参数它同时影响三件事而且是互相拉扯的。维度batch 偏小batch 偏大显存占用低高大致与 batch 线性增长单 step 速度慢并行度没吃满快GPU 利用率高梯度噪声大有正则效果但收敛抖小曲线平滑但容易进平坦坏解BatchNorm 统计不稳小样本方差大稳定超参敏感度对学习率较宽容对学习率非常敏感我自己的经验是先用一个能塞进显存、且能被 8 整除的 batch_size 起步32 / 64 / 128跑 3 到 5 个 epoch 看一眼曲线形态再决定要不要动它。不要一上来就为了快点训完把 batch 拉到顶——那样你往往会在后面花三个小时调学习率。还有个小细节batch_size 选 2 的幂很多时候能让 GPU 的矩阵运算更整齐速度略快一点点。这不是玄学是底层 kernel 的向量化对齐问题实测差个百分之几是有可能的。1.4 顺带说一句搜 batch 为什么会搜到一堆不相干的东西如果你去搜索引擎查 batch 相关的资料很容易搜到跟深度学习完全无关的东西——批处理脚本、图形软件里的批量导出功能、某些设备管理里的批量操作设置项甚至一些固件或电源管理里的日志字段。原因是 batch 本身是个通用英文词含义是一批、一炉各行各业都在用。所以查资料时把限定词加上写batch size 梯度、mini-batch 训练、epoch batch step 区别命中率会高得多。同样地loss 也是个撞车严重的词工程、金融、统计里都在用光搜 loss 很可能搜到一堆损失率、损耗相关的页面。这个词在本文里只有一个意思损失函数算出来的那个值。2. loss 和 val_loss 算的是同一个东西吗2.1 先分清 loss 的三种口径同一个屏幕上写着 loss其实可能是三个完全不同的数。第一种是batch loss当前这个 batch 算出来的损失值最原始、噪声最大。它只反映这几十上百条样本的表现上下抖动是正常的。第二种是running loss用一个滑动平均把历史 batch 平滑掉让日志别那么跳。常见写法是running 0.98 * running 0.02 * loss.item()或者累计加权平均。这个数比较好看但注意它会被历史拖尾学习率调整后要过一会儿才能反映出来。第三种是epoch loss整个 epoch 所有样本的加权平均是真正能称得上这个 epoch 训练得怎么样的数。total_loss, total_n 0.0, 0 for x, y in loader: ... bs y.size(0) # 关键用真实样本数加权不是用 batch 数 total_loss loss.item() * bs total_n bs epoch_loss total_loss / total_n这里的坑在于如果你直接对每个 batch 的 loss 取简单平均最后一个不满的 batch 会因为样本少而被高估权重。391 个 batch 里前 390 个是 128 条、最后一个是 80 条简单平均就偏了。样本加权平均才是对的。这个偏差通常不到 1%但当你两个实验的差距本来就只有 0.5% 时它就足以让你得出错误结论。2.2 val_loss 的计算姿势三个开关决定它可不可信val_loss 是模型在没有参与训练的那部分数据上算出来的损失。它的价值在于衡量泛化能力但前提是你得把它算对。有四个地方不能漏第一model.eval()一定要调。它会把 Dropout 关掉、把 BatchNorm 从用当前 batch 统计量切换成用训练期累积的滑动统计量。不调这一句验证时 Dropout 还在随机丢神经元BatchNorm 还在用验证 batch 的统计量算出来的 val_loss 会莫名其妙地偏高且抖得厉害。第二torch.no_grad()一定要包。不是为了数值正确是为了省显存和提速。验证阶段不需要反向传播不关梯度图的话显存会被白白吃掉一大块有时候训练阶段刚好、验证阶段反而 OOM就是这个原因。第三训练和验证必须用同一个损失函数实例。我踩过这个坑训练时用了带类别权重的 CrossEntropyLoss验证时图省事换成了不带权重的版本结果 val_loss 比 loss 低一大截我还高兴了半天以为是泛化好。加权损失的绝对值只有相对意义没法横向比较两个不同定义的 loss 放在一张图上那条曲线一点诊断价值都没有。第四验证集的 DataLoader 不要 shuffle。验证本身不需要打乱但固定顺序能让你的多次评估结果可复现出问题时也能定位到具体是哪些样本算错了。同时drop_last保持默认的 False因为验证不应该丢数据。2.3 真正该看的是差值不是绝对值loss 的绝对值几乎没有参考意义。它取决于你的损失函数定义、有没有加 L2 正则项、数据集难易程度、标签质量。CrossEntropy 下 loss 2.3 大约是随机猜ln 类别数但如果是回归任务MSE 是 0.5 还是 50完全看你的量纲。有意义的量是loss 与 val_loss 的差。我一般这样读两者同步下降差距稳定在某个小区间健康。loss 持续下降val_loss 先降后升过拟合这是最经典的形状。两者都降不下去贴着一条水平线模型没学到东西查学习率、数据归一化、标签是否正确。val_loss 全程剧烈震荡验证集太小或者学习率偏大。顺带提醒一句如果你在 loss 里加了正则项而 val_loss 里没加这两条线天生就不在一个尺度上别拿它们做减法。要么两边都不加正则项只看数据损失要么两边一起加。我个人习惯是正则项照加但日志里额外打一条纯数据损失对比时用这条。3. 把 loss/val_loss 曲线当诊断图来读3.1 四种典型形状对应的四类故障训练曲线不是给你看着好看的它是诊断报告。我整理了一张自己常用的对照表遇到异常时先从这张表里找形状。曲线形状具体表现大概率原因先试什么双降收敛两条线同步下降后趋平间隙小正常不用动看是否还能再降开口背离loss 降、val_loss 触底反弹过拟合加数据增强、加正则、提早停平行高位两条线都贴平台不降学习率过小 / 数据未归一化 / 标签错学习率 ×10 试一次检查输入分布剧烈震荡曲线锯齿明显偶有尖峰学习率过大 / batch 过小学习率减半batch 加倍直接爆掉几步内变 NaN 或 inf学习率过大 / 梯度爆炸 / 除零降 lr、加梯度裁剪、查 loss 里的除法和 log开口背离是最常见的那个。看到 val_loss 开始往上走不必立刻停——先把它当成信号而不是判决。有时候那只是学习率在后期太大导致的震荡把 lr 降一档val_loss 往往能再往下探一点。真正需要动手的是它连续好几个 epoch 单调上行那就是过拟合无疑了。平行高位这个形状最容易被误判成模型容量不够于是有人开始加层、加宽折腾一圈发现没用。其实更常见的原因是输入没做归一化——图像没除以 255、特征量纲差几个数量级。数据没进到一个合理的数值范围里网络前几层的激活值分布会很难看梯度要么太大要么太小。这个坑值得单独花时间查一次。3.2 学习率在曲线上留下的指纹学习率是唯一一个能在曲线上直接看出来的超参。学习率太大时曲线不是缓慢地差而是剧烈地抖。你会看到 loss 一会儿 0.4、一会儿 1.2整体没什么下降趋势甚至偶尔冒出一个尖峰然后回落。再大一点直接 NaN。这时候别怀疑数据先把 lr 砍一半再说。学习率太小时曲线是一条斜率很小的直线很光滑、很漂亮但就是降得太慢。跑完 50 个 epoch 还不如别人 5 个 epoch 的效果。这种情况我一般会把 lr 乘 3 到 10 试一次对比前 3 个 epoch 的下降速度通常能立刻看出差别。现在比较流行的做法是带 warmup 的调度前几百个 step 让 lr 从很小的值线性爬升到设定值之后再按余弦或阶梯衰减。这样曲线上会看到一个先慢后快再平的形态。scheduler torch.optim.lr_scheduler.OneCycleLR( optimizer, max_lr3e-3, total_stepsepochs * len(loader), pct_start0.1 ) # 每个 step 之后调一次不是每个 epoch scheduler.step()注意 OneCycleLR 和 StepLR 的调用频率不一样前者按 step 调后者按 epoch 调。调错频率是很隐蔽的坑因为代码不会报错只是学习率曲线悄悄变成了另一个样子。我建议在日志里把当前 lr 打出来一眼就能确认调度是否按预期走。3.3 早停与最优权重最后一个 epoch 往往不是最好的新手最容易犯的一个错是训练跑完 100 个 epoch直接把最后保存的模型拿去评估。但你的 val_loss 可能在第 62 个 epoch 就到了最低点之后 38 个 epoch 全在做无用功甚至反向优化。正确做法是按验证指标保存最优权重best float(inf) patience, bad_epochs 5, 0 for epoch in range(epochs): train_loss run_epoch(model, train_loader, criterion, optimizer) val_loss run_epoch(model, val_loader, criterion) if val_loss best - 1e-4: # min_delta 防止噪声触发 best, bad_epochs val_loss, 0 torch.save(model.state_dict(), best.pt) else: bad_epochs 1 if bad_epochs patience: break两个参数值得说patience一般取 5 到 10太小会因为正常波动提前停太大就失去了早停的意义min_delta是至少要改善这么多才算改善不设它的话val_loss 从 0.50123 变成 0.50122 也会被当成进步触发不了早停。还有一个进阶提醒val_loss 最优不一定等于业务指标最优。类别极不均衡的分类任务里val_loss 会让模型偏向多数类而你可能真正在意的是少数类的召回率。这种情况下早停和保存权重的判据应该换成业务指标F1、AUC、mAP 等而不是死盯 val_loss。我自己吃过这个亏val_loss 最优的那个 checkpointF1 比第 30 个 epoch 还低两个点。4. 自己写训练循环让这四个数字变得可信4.1 数据加载里的四个开关DataLoader 几个参数看着不起眼直接决定你的 loss 曲线有没有意义。shuffleTrue只给训练集开。训练集不打乱模型会先看到一批同类别样本、再看到另一批梯度方向来回摆曲线会呈现出一种周期性的起伏很难判断收敛情况。验证集不打乱理由前面说过。num_workers在 Linux 下建议设成 4 到 8Windows 下如果用多进程遇到报错设 0 也能跑只是慢。注意 worker 数量太大反而会拖慢因为每个 worker 都要复制一份数据。pin_memoryTrue配合non_blockingTrue能明显加快 GPU 训练的拷贝速度尤其是数据量大的时候。train_loader DataLoader( train_set, batch_size128, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue, ) val_loader DataLoader( val_set, batch_size256, shuffleFalse, num_workers4, pin_memoryTrue, )注意验证集的 batch_size 可以设得比训练集大因为不需要存梯度显存压力小得多。这个细节能让验证速度快一倍以上尤其是每个 epoch 都验证的场景。4.2 训练和验证两段骨架的差异我习惯把训练和验证写进同一个函数用一个train标志位切换这样能保证数据处理和指标聚合逻辑完全一致不会出现两边口径不一样的低级错误。def run_epoch(model, loader, criterion, device, optimizerNone): is_train optimizer is not None model.train(is_train) total_loss, total_n 0.0, 0 for x, y in loader: x x.to(device, non_blockingTrue) y y.to(device, non_blockingTrue) with torch.set_grad_enabled(is_train): logits model(x) loss criterion(logits, y) if is_train: optimizer.zero_grad(set_to_noneTrue) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step() bs y.size(0) total_loss loss.item() * bs total_n bs return total_loss / total_n里面有三个值得注意的点。一是model.train(is_train)一次性把模式设置对比在训练和验证分支里各写一遍不容易漏。二是set_grad_enabled(is_train)比with torch.no_grad()更省事一个函数吃两端。三是clip_grad_norm_我加在了训练分支里阈值 5.0。梯度裁剪对 loss 曲线的直接影响是把那些突然冒出来的尖峰削平。如果你的曲线时不时有一个向上的大尖峰然后恢复加个裁剪通常是有效的手段。它的原理很简单把整个参数组的梯度范数按比例缩放到阈值以内防止某一步更新跨得太大把权重带到坏区域。4.3 日志里该记什么只记 loss 和 val_loss 是不够的排查问题时会发现自己什么线索都没有。我自己固定记录的字段有这么几项。字段作用异常时的含义train_loss / val_loss主指标看背离和平台当前 lr确认调度生效与预期不符说明 step/epoch 调错grad_norm梯度大小突然暴涨是 NaN 前兆epoch 耗时性能基线突然变慢可能存在数据瓶颈业务指标真实效果val_loss 好但指标差要警觉grad_norm这个字段特别值得记很多人的模型在 NaN 之前grad_norm 已经连续几个 step 在异常放大了。把这个数打进日志相当于给训练装了个预警灯。实现上可以在 backward 之后、step 之前读一次总的梯度范数。还有个小技巧把日志同时写进 CSV而不是只让它刷在终端里。终端滚动太快回头想对比两个实验的曲线靠回忆是不靠谱的。CSV 加个画图脚本五分钟能省下你后面几个小时。5. batch_size、epoch 数、loss 函数的三角权衡5.1 改了 batch_size学习率要不要跟着改这是最常被问到的问题之一。经验规律是batch_size 放大 k 倍学习率可以按同样比例放大 k 倍。原因是梯度是 batch 内样本梯度的平均batch 越大平均后的梯度噪声越小方向越可靠因此可以迈更大的步子。比如 batch_size 从 64 提到 2564 倍lr 从 1e-3 提到 4e-3 是个合理的起点。但这个规则有前提只在 SGD 这类优化器上比较成立。用 Adam、AdamW 时因为每个参数有自适应缩放这个关系会弱很多硬套反而容易发散。需要配合 warmup。直接上大 lr前期非常容易炸。小数据集上不适用样本本来就少梯度的统计噪声压不下去。所以我更倾向于这条经验换 batch_size 之后观察前 2 到 3 个 epoch 的下降斜率用实验确定 lr而不是用公式推。公式只能给你一个起点省掉的是盲搜的时间不是替代实验的理由。5.2 epoch 数不该拍脑袋定很多人习惯在脚本里写epochs100然后就不管了。实际上 epoch 数应该由数据规模和曲线形态共同决定。我的做法是分两步先跑 3 到 5 个 epoch 做侦察。看这几个 epoch 里 val_loss 下降了百分之多少、还有没有明显下降趋势、有没有开始背离。如果 5 个 epoch 就把 val_loss 压到了一个平台那 100 个 epoch 就是纯粹的浪费时间。如果 5 个 epoch 时曲线还很陡那就把总 epoch 放大 3 到 5 倍配合早停让它在合适的位置自己停。数据规模也直接影响判断。几千条样本的微调任务3 到 10 个 epoch 常常就够几十万条的从零训练往往要几十上百个 epoch。有一个粗略的直觉单个 epoch 的时间越短你越应该多设几个 epoch 加早停单个 epoch 越慢越要先用小 epoch 侦察。还有个实际因素如果每个 epoch 要跑两小时那你更应该在验证频率上动脑筋比如每两个 epoch 验证一次而不是把 epoch 总数设得很小。5.3 loss 函数选型不均衡场景下的取舍最后说说 loss 函数本身。分类任务默认用交叉熵这一点没什么争议。但当类别极不均衡比如正样本只占 1% 时交叉熵会有一个很典型的现象训练 loss 降得很顺val_loss 也很好看但少数类的指标一塌糊涂。原因很直白——模型只要全猜多数类就能把平均损失压得很低。常见的三种应对各有代价方案做法优点代价类别加权给少数类更大的权重实现简单一行代码loss 绝对值失去可比性重采样过采样少数类 / 欠采样多数类直接改变数据分布过采样容易过拟合焦点损失降低易分样本的权重对极难样本效果好两个超参敏感调参成本高焦点损失focal loss的思路是给每个样本的损失乘一个与预测概率相关的调制因子预测得越准的样本权重越小让模型把注意力集中到难分的样本上。它在目标检测、极端不均衡的分类里确实好用但有两个必须知道的坑。一是超参敏感。里面有个控制聚焦程度的参数和一个平衡因子两个一起调搜索空间不小。我建议固定其中一个只调另一个从比较温和的值开始试。二是它会让 loss 的绝对值彻底失去解释性。加了调制因子之后loss 数值跟模型错了多少之间的关系变得非线性你再拿它去和别的实验比大小是没有意义的。所以用这类损失时早停和模型选择一定要看业务指标别再看 val_loss。如果你只是想先解决问题我建议从类别加权开始因为改动最小、最容易回退。只有当加权之后指标仍然上不去再去考虑焦点损失这类更激进的方案。顺序反了的话你会同时面对数据问题和损失函数调参问题两个变量排查起来非常痛苦。再补一个我在实际项目里养成的习惯每次改 loss 函数都把旧的那个版本的验证结果留着。因为改损失函数本质上是改了整个优化目标的形状曲线没有可比性能比的只有最终的验证指标。留个基线才不至于改到最后发现还不如原来那版然后连原版的结果是多少都记不起来了。还有一件事是我踩过好几次坑才记住的训练日志里的每一个数字都要清楚它是怎么算出来的。是 batch 平均还是样本加权是含正则项还是不含是训练模式还是评估模式。这四个问题搞清楚了loss 和 val_loss 才真正有诊断能力搞不清楚曲线画得再漂亮也只是两张好看的图而已。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

AI一周事件(2025年11月19日-11月25日):TaoToken 统一 Key 接入周报 2026/10/2 20:37:09

AI一周事件(2025年11月19日-11月25日):TaoToken 统一 Key 接入周报

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
工程项目配套供应企业生产厂家选购参考汇总 2026/10/2 20:37:08

工程项目配套供应企业生产厂家选购参考汇总

选购工程项目配套供应厂家,核心看什么?答案在于专业能力与交付保障的双重硬核。酒店、会所、样板间、民宿类项目,家具采购金额大、节点要求严、批次交付多,一旦厂家供货延期或品质失稳,直接影响整体工程进度。因此,工…

阅读更多 →
基于LoRa的多节点追踪系统:RSSI测距与航向解算实战 2026/10/2 20:37:08

基于LoRa的多节点追踪系统:RSSI测距与航向解算实战

如果你最近搜“LoRa”,大概率会看到一长串“LoRa微调”“LoRa训练”的教程——那些说的是大模型领域里的 Low-Rank Adaptation,跟咱们这篇要聊的通信 LoRa 完全是两个世界。我这篇要说的 LoRa,全称 Long Range,是做远距离低功耗无…

阅读更多 →
深圳潮十创意:AI搜索获客+智能优化,解决企业获客难问题 2026/10/2 20:37:08

深圳潮十创意:AI搜索获客+智能优化,解决企业获客难问题

当流量红利见顶,AI搜索获客正在改写中小企业生意逻辑 过去十年,中小企业做线上获客,绕不开两条路:要么花钱投广告,要么做搜索引擎优化。这两条路都曾创造过无数增长神话,但如今,它们的边际效应正…

阅读更多 →
Superpowers实战:为Codex CLI构建规划记忆与审查的AI协作层 2026/10/2 20:37:07

Superpowers实战:为Codex CLI构建规划记忆与审查的AI协作层

你用过Codex CLI吗?如果你和我一样,花了几周时间让它处理真实项目,大概率会碰到同一个尴尬:小任务很惊艳,一旦涉及多文件修改、跨模块重构、需要遵守项目里既有约定时,它就变成一个“健忘的天才”——上下文…

阅读更多 →
Git 实战应用常见技巧:用 TaoToken 统一管理多 AI 工具的提交配置 2026/10/2 20:37:01

Git 实战应用常见技巧:用 TaoToken 统一管理多 AI 工具的提交配置

1. 多 AI 工具协作下 Git 提交配置为什么会乱 本地同时跑 Cline、CC Switch、Claude Code、Codex 这几类 AI 编码工具的人,大概率都遇到过同一个场景:早上打开项目,Cline 里改完代码准备提交,结果 git commit 卡在认证上&#xff…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉