刚接触MatConvNet的人大概率都会对着训练日志发呆。屏幕上一行行滚过去objective: 0.5324、top1err: 0.2311、top5err: 0.0712看着眼熟但真要问一句“这几个数到底谁影响谁”一时半会儿还真说不利索。我当时也一样跑了一个星期的分类模型每天盯着损失曲线下降就以为万事大吉结果去验证集上一测准确率惨得没法看。后来才把这几个指标彻底捋顺objective、top1error、top5error其实分别回答了三件完全不同的事——模型在优化什么、最严苛的标准下答对多少、在允许一定模糊的情况下又答对多少。这篇文章就把它们掰开揉碎讲清楚顺便把MatConvNet里的使用经验也一并整理出来。适合正在用或准备用MatConvNet做图像分类、模型调参和训练结果分析的人。1. objective训练时唯一被优化的数字1.1 objective到底在说什么objective翻译过来就是“目标函数”也常被称为损失函数loss function。在分类任务里它衡量的是模型当前预测结果和真实标签之间的差异。差异越大这个数字就越高模型学得越好这个数字就越低。训练过程本质上就是反复调整网络里的权重参数让这个数字一点点降下去。MatConvNet里做图像分类最后一层通常是一个softmaxloss层。它做的事情分两步第一步对网络输出的得分向量做softmax得到每个类别的预测概率第二步把预测概率和真实标签代入交叉熵公式算出当前这个样本的损失。交叉熵公式看起来是这样的L -log(p_c)其中p_c代表模型分配给“真实类别c”的概率。这里有个很直观的理解方式如果模型对正确类别给出的概率是0.9那这个样本的损失就是-log(0.9) ≈ 0.105很小如果模型很糊涂给正确类别的概率只有0.1那损失就是-log(0.1) ≈ 2.302明显变大。一个batch比如32张图的objective就是这些单样本损失的平均值。注意MatConvNet在训练时输出的objective通常还包含权重衰减weight decay带来的正则项。也就是说屏幕上那个数字不纯是数据损失而是数据损失 正则损失的组合。正则项的作用是约束网络权重不要过大防止过拟合。提示如果你想看纯数据损失需要自己在训练代码里把正则项剔除或者查看MatConvNet中关于weightDecay的拆分日志。绝大多数情况下我们直接看输出的总objective就够了但心里要清楚它的组成。1.2 为什么objective一直在变训练日志里的objective不是单调下降的。很多新手会以为loss应该一路走低看到某次迭代的objective反而涨了就慌。实际训练过程中objective呈现出的是“整体下降、局部震荡”的形态。震荡的来源有两个一是batch sampling的随机性每个batch的样本难度不一样平均损失自然有波动二是学习率设置学习率偏大时参数更新步长也大损失波动会更明显。判断是否正常更有效的做法是看整体趋势而不是纠结某一次迭代的值。通常训练刚启动时objective下降非常快因为模型从随机初始化状态快速学到粗粒度特征随着训练推进下降速度放缓后期变成缓慢爬坡式下降甚至在高位平台附近抖动。我在实际训练里习惯把objective曲线导出成csv然后用画图工具拉平滑曲线看。别直接在训练终端里肉眼看滚动数字容易被局部波动干扰判断。平滑后的损失曲线如果能在每个学习率阶段都稳定下降说明训练状态基本健康。1.3 objective不是用来横向比较模型的这一点极其容易被忽略。每个模型的objective绝对值大小受损失函数形式、正则项权重、batch大小、类别数等多种因素影响。你训练一个10类分类模型的objective为0.6别人训练一个1000类分类模型的objective为1.8这两个数字没有可比性。即使在同一个任务里不同网络的objective也不能直接拿来判断谁更强。A网络的objective是0.35B网络是0.40不一定代表A就一定比B好。原因在于objective的计算包含了正则项而两个网络设置的weightDecay参数可能不同权重衰减带来的损失大小也不同。所以objective的绝对数值只能作为参考它真正的作用是让你观察“自己的模型在训练过程中有没有持续变好”。跨模型比较、判断最终效果好坏的依据还得看后面要说的top1error和top5error。2. top1error和top5error模型“答对”的不同标准2.1 top1error最常用也最严格top1error指的是在模型对一张图片输出的所有类别概率中概率最大的那个类别不是真实标签的比例。换句话说如果我对一批图片做预测每次只取模型最有信心的那个答案答错的样本占比就是top1error。举个例子一个训练好的模型对一张猫的图片输出各类别概率其中“猫”是0.89其他类别都远低于这个值。那这张图就算“答对”了一次。反之只要模型最自信的类别不是真实类别哪怕真实类别排第二、概率高达0.45这张图对top1而言依然算是错误。所以top1error和top1准确率的关系是top1准确率 1 - top1error。有些地方直接报accuracy有些地方报error说的其实是同一件事。在MatConvNet里训练日志显示的top1err就是这个错误率。它衡量的是模型最核心的判别能力——在严格的单次猜测中能答对多少。对大部分分类任务来说top1error是最终发布模型时的首要参考指标。2.2 top5error给模型五次机会top5error的判定标准宽松许多。它把模型对一张图片输出的各类别概率从高到低排序取前五个类别。如果真实标签出现在这五个类别里这张图就算“答对”否则才算“错误”。所以top5error必定小于等于top1error而且类别数越多两者差距往往越大。这个指标在类别数量很大、类别之间存在相似语义时特别有用。以大规模图像分类数据集为例1000个类别中包含了大量语义相近的类别比如不同品种的狗、不同型号的汽车、不同种类的蛇。有些类别人眼都很难分辨强行要求模型把概率最高的答案精确命中并不公平。top5error给了模型“犯错”的余地——只要第一选择错了但前五个候选里有正确的就说明模型其实已经学到了相关特征只是对细粒度差异不够敏感。我一般会把top5error当作“模型学到东西了没有”的粗信号把top1error当作“模型学得够不够精细”的细信号。2.3 top1error和top5error如何计算出来在MatConvNet的softmaxloss层内部除了计算损失值还会同时计算这两个错误率。计算步骤如下对于每一个样本从网络最后一层拿到各类别得分logits做softmax变成概率。对概率从高到低排序记录概率最大的类别下标与真实标签比较如果不相等top1错误计数加1。看真实标签是否排在前五名之内如果不在top5错误计数加1。一个batch统计完毕后把错误计数除以batch内样本总数得到top1error和top5error。拿一个batch举例样本A真实标签是猫模型前五名里没有猫 → top1错、top5错 样本B真实标签是狗模型top1是狗 → top1对、top5对 样本C真实标签是汽车模型top3才有汽车 → top1错、top5对 样本D真实标签是鸟模型top1是鸟 → top1对、top5对那这4个样本的top1error 2/4 0.5top5error 1/4 0.25。在训练代码里看日志时遇到top1err: 0.2311, top5err: 0.0712这样的输出就可以翻译成这批样本中23.11%的图片模型第一选择就错了但只有7.12%的图片模型连前五个候选都没猜对。注意top5error虽然是分类指标里的“宽松版”但在类别数少比如10类、20类的任务中它的参考价值会降低。类别就那么几个前五名几乎覆盖了一半以上这时候top5error很容易降得很低反而不如top1error敏感。3. 在MatConvNet里这些指标是怎么被记录和输出的3.1 网络结构与loss层的配置MatConvNet的卷积网络通常用一个结构体数组net.layers来定义。一个典型的分类网络最后一层往往是net.layers{end1} struct(type, softmaxloss) ;这一层会接收前面卷积层输出的得分再结合imdb里的真实标签计算objective、top1error和top5error。训练时MatConvNet的cnn_train函数会自动把这三个量记录到info结构体里每隔若干次迭代打印一次。如果你用的是DAG格式的cnn_train_dag则需要在网络构建时显式定义一个名为objective的loss节点。DAG网络里节点输出的top1error和top5error同样会被训练函数自动跟踪并汇总到输出日志里。区别只是定义方式更灵活但最终你在终端看到的字段是类似的。3.2 训练日志的读取方法MatConvNet训练过程中命令行会周期性输出当前epoch和迭代次数后面的信息大致长这样epoch 1 : 1/1250 (lr 0.0100) objective 2.1123 top1err 0.6821 top5err 0.4321 epoch 1 : 2/1250 (lr 0.0100) objective 1.8762 top1err 0.6543 top5err 0.4011注意这里的top1err和top5err是训练集当前batch的错误率不是验证集的。训练函数还会在一个epoch结束后对验证集做完整评测输出类似epoch 1 : valid objective 1.7234, top1err 0.5234, top5err 0.2987我强烈建议在训练结束后把info.train.top1err、info.val.top1err等字段导出保存用画图工具把train和val曲线画到同一张图里。看两条曲线的相对位置比看任何单个数字都有用。info结构体里具体有哪些字段可以在训练结束后用disp(info)查看常见的有info.train.objective、info.train.top1err、info.train.top5err、info.val.objective、info.val.top1err、info.val.top5err以及对应的epoch记录。3.3 用测试阶段网络获取top5预测结果训练好的模型如果你要实际部署或做可视化分析不能用包含softmaxloss层的网络直接跑因为这一层前向传播输出的不是概率而是一个标量损失。正确做法是把最后一层的type改为softmax或者干脆把倒数第二个输出层的结果取出来。我通常是这样处理的% 假设net是训练好的网络最后一层是softmaxloss net.layers{end}.type softmax ; res vl_simplenn(net, im_uint8) ; probs squeeze(res(end).x) ; [top5_scores, top5_idx] sort(probs, descend) ; top5_idx top5_idx(1:5) ;这里probs是每个类别的预测概率排序后取前五名就能直观看到模型对一张图片的五个“候选答案”。我会在人工评估模型时随机抽一批错题把top5列出来看模型把真实类别排在第几位。如果大量错题的真实类别都排在第二第三位说明模型只是细粒度不够如果真实类别完全不在前五那多半是训练数据本身覆盖不足或特征学习有问题。3.4 MatConvNet的error计算细节MatConvNet在计算top5error时有个实现细节它默认对每个样本按类别得分从高到低排序取前五个索引然后判断真实标签的索引是否在其中。这个逻辑在GPU版本和CPU版本里是一致的。唯一需要注意的地方是vl_nnsoftmaxloss接收的labels通常是1到C的整数序号而不是one-hot编码。如果你的数据标签是从0开始编号的传给loss层之前要记得全部加1。我在某次迁移学习时就栽过这个跟头数据集标签从0开始忘了加1结果训练过程中top1error始终在99%附近徘徊objective也不下降。排查半天才意识到是把“0”当成了“类别0”传给MatConvNet而它期望的是“类别1”。提示MatConvNet的label约定是从1开始的。如果标签范围是0~C-1必须在构造imdb时统一做labels labels 1处理。4. 用objective和error的组合判断模型状态4.1 训练集和验证集的表现要分开看只观察训练集上的objective和error没有任何意义因为模型理论上可以把训练集死记硬背下来train top1error完全可以做到0但一到验证集就崩。要判断模型是否健康必须同时盯住训练集和验证集两条曲线。这里我整理了四种典型组合基本覆盖大多数情况训练集表现验证集表现可能原因建议objective持续下降error同步下降error同步下降模型正常学习保持现有策略继续训练objective下降error勉强不动error也不动或微降模型容量不足或数据本身特征不明显考虑换更大模型、增加特征复杂度objective下降error降到很低error降到某个平台后开始反弹过拟合增加正则、数据增强或提前停止objective降不下去error居高不下error同样居高欠拟合或训练配置错误检查学习率、数据预处理、网络结构很多人在第一个epoch结束后看到train top1error快速下降就觉得模型学得不错实际验证集top1error可能还高得离谱。这时候不用着急初始阶段训练集和验证集差距大是正常的。关注的是整个训练过程中验证集error的下降趋势。4.2 借助top5诊断模型“卡在哪里”top5error和top1error之间的差距是一个很细腻的诊断信号。如果两者差距很小说明模型犯的错大多是“严重错误”——连前五个候选中都找不到正确答案特征学习存在系统性缺陷。如果两者差距很大说明模型的“轻度混淆”很多——真实类别通常能进前五只是最自信的类别选错了。后面这种情况最常见的成因是相似类别。比如一个识别动物品种的模型把“哈士奇”认成“阿拉斯加”这属于top1层面的错误但“哈士奇”通常还在top5里。遇到这种状况可以考虑两类手段一是增加细粒度数据特别是相似类别之间的差异样本二是在损失函数层面做调整比如加大难样本权重、引入度量学习损失。我自己遇到最典型的一次问题是一个中等规模的分类任务训练到最后train top5error已经降到0.03但top1error还在0.19。抽了一批照片查看发现全部是“相似物种”“不同角度同物”这类易混淆样本。后来在数据增强里加入随机裁剪、颜色抖动以及增加难样本的采样权重top1error才从0.19降到0.12。这个过程中top5error几乎没有变化说明它早就饱和了真正的瓶颈在细粒度判别上。4.3 fine-tuning时要盯住哪些数字做迁移学习、用预训练模型微调时objective和error的含义会发生一些变化。预训练模型通常已经具备了很强的通用特征提取能力微调的初始objective不会像从零训练那么高top1error通常起步就有不错的水平。微调阶段要特别注意验证集top1error的波动幅度。因为微调通常学习率较低训练集loss可能在缓慢变化但验证集top1error可能出现明显震荡。这时候不要盲目降低学习率或加大正则先看看震荡是否周期性出现、是否与学习率衰减节点吻合。我见过很多人在微调阶段发现val top1err反弹就急着early stopping其实只是某个batch的数据恰好偏难。微调小数据集时top5error比top1error更稳定因为它对细粒度差异没那么敏感。如果你想判断“模型整体上有没有学到新任务的特征”看top5error更可靠如果你想判断“模型最终能不能上线”还是得看top1error。注意micro-average和macro-average在类别不平衡时会给出差异很大的error结果。MatConvNet自带的error计算默认是micro方式也就是把总体样本错误数除以总体样本数。如果你的数据集类别分布非常不均匀建议单独写一个按类别平均的评估函数防止少数类被大量多数类掩盖。5. 常见问题与避坑经验5.1 objective很低但top1error始终下不去这个问题我踩过不止一次。现象是训练日志里objective降得挺好看验证集objective也在降但val top1error卡在某个高位。原因通常有三种。第一种模型过拟合了训练集的“易学表面特征”。比如数据集中背景信息过于一致模型学到了背景和类别的强相关性一旦验证集背景不同就抓瞎。解决办法是加强数据增强尤其是随机裁剪、颜色扰动、水平翻转。第二种类别不均衡训练时大多数样本来自占比高的类模型对少数类几乎不学。这时候可以用类别加权损失或者在采样时控制每个batch的类别比例。第三种学习率过低模型在某个局部最优附近反复横跳无法跳到更优区域。可以尝试阶段性调高学习率或使用余弦退火策略。判断具体是哪种原因最直接的办法是看验证集每个类别的单独错误率。把per-class error打印出来一眼就能看出瓶颈在哪几个类。5.2 为什么top5error和top1error差距特别大如果top5error已经很低而top1error依然偏高说明模型“有知识但不够精准”。这种情况不一定要通过加数据解决有时是模型输出的概率分布过于平滑最大概率和次大概率差距很小。一个实用的处理方式是对softmax引入温度系数。推理时把logits除以一个大于1的温度系数让概率分布变得更尖锐往往能抬高top1准确率。MatConvNet里实现起来也很简单测试阶段在最后一层softmax前对得分做一下缩放temperature 2.0 ; % 假设res是softmax之前也就是最后一个卷积或全连接层的输出x scores_after_temperature res(end-1).x / temperature ; probs softmax(scores_after_temperature) ;这个技巧在知识蒸馏里很常用但在常规模型评估时同样能带来一个点左右的top1提升。注意温度系数需要根据验证集来调不是越大越好太大会让概率分布过于尖锐反而把错误的类别也“顶”上来了。5.3 objective和error并不是一回事这是最核心的概念objective是训练过程中用来做梯度下降的优化目标它必须是连续、可微的这样梯度才能回传。而error是评估指标它衡量的是最终任务效果不参与梯度计算。误差函数自身带有不可导的排序操作比如top1error里比较“概率最大的类别”和“真实标签”是否相等这个比较过程的梯度是没法定义的所以error只能作为监控指标。理解这一点就能解释很多现象objective的变化规律和error不一定同步训练后期objective还在缓慢下降但error可能已经进入平台期反过来某些操作能明显降低error但objective数值却出现反弹。比如调整温度系数改的是推理阶段不影响训练目标。5.4 关于这些指标我给新手的实操建议如果你是第一次跑MatConvNet训练这里有几条我在实践中总结下来的建议能帮你少走弯路每次训练开始前先把验证集的top1error和top5error打出来确认数据预处理、标签映射都没问题。我见过太多人训练一整天后才发现验证集标签根本对不上。固定随机种子保证训练可复现。MatConvNet里可以在训练前设置rng(0)否则你很难判断某次结果差异是模型问题还是随机性带来的。保存历史记录。每训练一个epoch就把info.train和info.val的objective、top1err、top5err存成mat文件后续画趋势图、写复现文档都需要。不要把val集的任何信息带进训练调参过程比如用val top1err去反复选学习率选到最后val效果好、真正上线就露馅。对于分类任务评价模型最终要同时给出top1error和top5error。只报一个数字容易误导——top5很好不一定代表模型可用top1很好也不代表模型在所有类别上都均衡。我个人在实际操作中最大的体会是objective决定模型能不能学到东西top1error决定模型学到的东西够不够精准top5error则帮你判断模型是不是“差得离谱”。长期跑实验以来我已经习惯在每次迭代时同时扫一眼三个数而不是只盯loss。特别是当objective还在降、top1error却纹丝不动的时候一定要停下来查一查原因很可能训练已经进入无效优化阶段。最后再分享一个小技巧每隔几个epoch随机抽20张验证集图片用3.3里的方式输出每张图的top5预测结果人工读一遍。这个习惯看上去很原始但比任何统计指标都更能发现问题。比如你可能会发现模型把所有深色背景的图片都倾向预测成某几个类这种系统性偏见在数值指标里很难被注意到但一眼看预测列表就能发现。MatConvNet的这三个指标本身并不复杂但它们之间的关系、它们各自能反映什么对训练结果的判断影响非常大。下一次跑训练的时候别再只盯着那个不断下降的objective了把top1error和top5error一起看你会少走不少弯路。