YOLO训练曲线绘制指南:从results.csv到论文级图表 论文写到一半最烦的事情往往不是方法讲不清楚而是那张训练过程的曲线图怎么画都不满意。对准YOLO26这种较新的检测模型来说精准率、召回率、mAP_0.5、mAP_0.5:0.95再加三四个损失项要在一张图里既体现训练动态又满足论文图表的基本规范确实不是直接截图能糊弄过去的。这篇内容就是围绕这个需求来的从训练日志文件的字段结构入手把每个指标对应到坐标轴然后给出可以直接跑的绘图代码最后聊聊论文提交前那些容易被忽略的排版细节和实际踩坑经验。无论你是第一次接触目标检测训练的新手还是已经跑过不少实验、想提升图表质量的进阶玩家这篇都应该能省下你一下午的调图时间。1. 训练过程中产生的数据到底存在哪里1.1 默认训练目录里都有什么用YOLO系列模型做目标检测训练通常不会自己手动记录每轮loss和精度。官方训练脚本在每次训练启动后会自动创建一个以训练时间命名的输出目录里面会包含多个文件和子目录。很多人只关心最终的best.pt权重文件在哪里对旁边那个文本格式的结果文件毫不在意实际上那才是画曲线最重要的原料。训练结束后打开这个输出目录一般能看到下面这些东西训练过程中的权重文件会按最后的epoch保存best和last会单独标出来每个epoch的预测结果图片会按类别放在子目录里另外还有一个自动生成的results.png汇总图以及一个记录全部训练数据的results.csv文件。真正画曲线的时候results.csv才是重点。它每一行对应一个epoch每一列对应一个具体的数值指标从损失到mAP全部都有可以说是训练过程的电子账本。1.2 为什么不能直接截results.png交差官方脚本每次训练都会生成一张汇总图很多人就直接把这张图截进论文里了。如果你只是自己看训练情况完全够用但要放进论文问题就很明显。第一官方默认图的配色和字体不一定是目标期刊、学校模板的要求第二它默认把损失和指标全部放在同一张图里排成两排虽然信息全但视觉上不够清爽第三图片分辨率和尺寸是固定的放大之后文字会模糊审稿人那边容易被挑刺。更重要的是论文图表讲究“每个坐标轴都能讲清楚一个故事”。训练集的box_loss一条线验证集的mAP_0.5一条线它们要表达的结论不同混在一张官方底色图里反而削弱了可读性。我的习惯是自己重新读csv重新排版把损失曲线单独画一张把精准率、召回率、mAP系列放在另一张这样才能做到每个子图都有明确的功能性。1.3 什么时候需要“论文级”的重新绘制这里要说清楚不是所有实验都需要精心重绘。如果是内部调试、快速看收敛情况官方生成results.png完全够用别浪费时间。但凡是准备投稿、进毕业论文、做组会正式汇报或者要把训练曲线放进对比实验章节那就必须重新绘制。另外还有个典型场景做消融实验时需要把不同配置的曲线放在同一张图里对比比如是否使用注意力模块、是否换了数据增强策略。这种多条曲线叠加的需求官方图完全满足不了只能自己写脚本。2. results.csv字段逐个拆解2.1 损失项字段csv文件第一列一般是epoch从0或者1开始递增后面跟着好几列以train/和val/开头的字段代表训练集和验证集上的各类损失。不同的模型版本命名可能有微小差异但核心的几个损失项是很有规律的。box_loss对应边界框回归损失衡量预测框和真实框的位置偏差cls_loss对应分类损失衡量预测类别是否正确dfl_loss是分布焦点损失这个其实是在优化边界框各个边的分布用于提升定位精度。这几个损失在训练早期数值都偏高随着epoch推进会逐步下降。画曲线时训练集和验证集的同类型损失建议画在同一子图里这样收敛趋势和过拟合信号看得最直接。如果验证损失曲线先降后升就说明模型开始出现过拟合苗头这在论文讨论部分是可以专门拿出来分析的现象。2.2 评估指标字段指标部分的字段通常以metrics/开头常见的是precision、recall、mAP50、mAP50-95。precision对应精准率计算的是所有预测框里有百分之多少预测对了recall对应召回率计算的是所有真实框里有多少被模型找出来了mAP50是IoU阈值为0.5时的平均精度均值mAP50-95则是在0.5到0.95区间按0.05步长取多个IoU阈值然后求平均所以它更严格也更接近论文里常说的综合性能指标。这几个指标按照官方说明都是在验证集上计算的所以曲线呈现的规律通常没有损失那么平滑尤其是数据集比较小的时候每条曲线都可能带不少锯齿。画图的时候可以把它们放在同一个坐标轴里也可以拆成三个子图。我个人建议前3000字内把三者合并到一张图因为它们量纲一致相互之间有参照价值一眼就能看出模型有没有陷入“精准率很高、召回率偏低”这种不平衡状态。2.3 辅助字段与时间字段除了上面两类csv里还有每次epoch的耗时time字段以及学习率lr相关字段。学习率曲线在写论文的时候不一定会单独列出来但在判断训练策略合理性时很有参考价值。比如采用余弦退火调度时学习率会先稳定再下降如果训练中断或者参数配置错了学习率曲线会直接暴露问题。另外time字段可以用来估算整个训练周期这部分如果在论文实验环境里提到了GPU型号可以作为佐证材料但一般不会当作正式图表输出。为了让你对字段结构有个整体印象下面这张表列的是典型训练日志中的关键列名字段写法常见含义描述论文中使用建议train/box_loss训练集边界框回归损失画入损失子图train/cls_loss训练集分类损失画入损失子图train/dfl_loss训练集分布焦点损失画入损失子图val/box_loss验证集边界框回归损失与训练集同图画对比val/cls_loss验证集分类损失与训练集同图画对比val/dfl_loss验证集分布焦点损失与训练集同图画对比metrics/precision验证集精准率画入指标子图metrics/recall验证集召回率画入指标子图metrics/mAP50mAP0.5画入指标子图metrics/mAP50-95mAP0.5:0.95画入指标子图time每轮训练耗时一般不画可作参考lr/pg0等各参数组学习率判断调度策略时参考3. 用pandas加matplotlib把曲线画出来3.1 读取并整理数据绘图之前先把环境准备好依赖其实就两个pandas负责读csvmatplotlib负责画图。如果你还要做平滑处理numpy也会用上。安装命令不展开写了直接看下面的读取代码。import pandas as pd import matplotlib.pyplot as plt import numpy as np from pathlib import Path # 根据自己训练的目录修改 csv_path Path(runs/detect/train/results.csv) df pd.read_csv(csv_path) # 去掉列名里的空格csv里有些列名可能带缩进 df.columns df.columns.str.strip() print(df.columns.tolist())这段代码执行完之后你应该能看到一个完整的列名列表。如果发现某些字段不存在比如没有dfl_loss说明你的模型版本可能没启用这个损失或者列名有变化先打印列名再做映射比硬编码写死更稳妥。把列名打印出来检查这一步非常重要因为不同模型版本的字段命名确实有过调整我就碰到过明明训练正常、最后画图却因为列名对不上而报KeyError的情况。3.2 关于曲线平滑的取舍训练曲线的真实数据往往抖动明显尤其是小数据集上的mAP曲线锯齿非常密集。直接画原曲线图会显得很凌乱画平滑后的曲线又担心被质疑修改了数据。在这个问题上论文图的通行做法是原始曲线用半透明的浅色画一层平滑后的曲线用实色叠加在上面这样既保留了真实数据的形态又能让趋势一目了然。一个简单的指数移动平均实现如下def smooth_curve(values, weight0.8): smoothed [] last values[0] for v in values: last last * weight v * (1 - weight) smoothed.append(last) return np.array(smoothed)需要注意的是平滑系数不要取得太大0.7到0.85之间比较合适太大的话曲线会被拉得过分迟钝反而看不出loss在某个epoch附近的突变。另外图例里要明确标注“smoothed”或者在图注里说明使用了平滑处理这是个学术诚信层面的细节。3.3 损失曲线的绘制画损失曲线时我习惯把训练集和验证集的同一类损失放在同一个子图里这样可以直接对比两者gap的大小。下面代码画了三个子图分别对应box_loss、cls_loss、dfl_loss每个子图里有训练和验证两条曲线。fig, axes plt.subplots(1, 3, figsize(12, 4)) loss_pairs [ (train/box_loss, val/box_loss, Box Loss), (train/cls_loss, val/cls_loss, Cls Loss), (train/dfl_loss, val/dfl_loss, DFL Loss), ] epochs df[epoch].values for ax, (train_col, val_col, title) in zip(axes, loss_pairs): train_vals df[train_col].values val_vals df[val_col].values ax.plot(epochs, train_vals, alpha0.2, colortab:blue, labeltrain raw) ax.plot(epochs, smooth_curve(train_vals), colortab:blue, labeltrain smooth) ax.plot(epochs, val_vals, alpha0.2, colortab:orange, labelval raw) ax.plot(epochs, smooth_curve(val_vals), colortab:orange, labelval smooth) ax.set_title(title) ax.set_xlabel(Epoch) ax.set_ylabel(Loss) ax.legend(fontsize8) plt.tight_layout() plt.savefig(loss_curves.png, dpi300) plt.show()这里有个小技巧先画透明的原始曲线再画平滑曲线顺序不能反。如果反过来透明曲线会盖住平滑曲线视觉效果会脏很多。train和val用同一色系的深色和浅色区分比一个红一个蓝的配色在印刷时更友好。3.4 评估指标曲线的绘制评估指标曲线我通常单独一张图把precision、recall、mAP50、mAP50-95四条曲线画在同一个坐标轴里。因为它们的数值范围都在0到1之间放在同一张图里对比非常直观。如果你更喜欢“一个指标一个子图”也可以参考损失图的布局方式但那样会占版面论文里不一定放得下。以同一张图为标准的话代码是下面这样fig, ax plt.subplots(figsize(8, 5)) metric_cols [ (metrics/precision(B), Precision), (metrics/recall(B), Recall), (metrics/mAP50(B), mAP0.5), (metrics/mAP50-95(B), mAP0.5:0.95), ] colors [tab:red, tab:green, tab:blue, tab:purple] for (col, label), color in zip(metric_cols, colors): raw df[col].values ax.plot(epochs, raw, alpha0.15, colorcolor, linewidth0.8) ax.plot(epochs, smooth_curve(raw), colorcolor, linewidth1.8, labellabel) ax.set_xlabel(Epoch) ax.set_ylabel(Metric Value) ax.set_ylim(0, 1.05) ax.legend(fontsize10) ax.grid(True, linestyle--, alpha0.3) plt.tight_layout() plt.savefig(metric_curves.png, dpi300) plt.show()这里默认了字段名里带(B)后缀如果你打印列名后发现没这个后缀去掉就行。同一份代码跑1个epoch就出图效率非常高。我建议先分别保存单张图最后再按论文排版需要横向拼接或者竖向拼接不要在第一步就把所有图压在一个fig里那样版面不好控制。3.5 两种布局方案的取舍在最终成图阶段你只会面临两种选择一种是把所有曲线全部放在一个大图中分两行三列展示另一种是按功能拆成两张图损失归损失指标归指标。从论文排版效率来说我倾向于后者。损失图适合放在训练细节部分配合你描述的优化器、学习率策略指标图适合放在结果对比部分配合你和其他模型的对比表格。审稿人看图表时最关心的信息路径越短越好一张图只承担一个任务比一张图又是损失又是mAP的效果好得多。4. 论文图的细节门道4.1 字体与样式设置先统一不同期刊、不同学校的学位论文字体规范差异很大。有的要求图内文字全部用Times New Roman有的要求中文宋体。但YOLO模型画图默认用的字体非常简陋横纵坐标轴文字偏小中文还会出现方框。所以每次画图前第一件事就是统一字体配置plt.rcParams[font.family] Times New Roman plt.rcParams[font.size] 10 plt.rcParams[axes.labelsize] 11 plt.rcParams[axes.titlesize] 12 plt.rcParams[legend.fontsize] 9 plt.rcParams[xtick.labelsize] 9 plt.rcParams[ytick.labelsize] 9如果你的论文需要中文标签比如横轴叫“训练轮数”那还需要多设置中文字体。比较省事的方式是在上面基础上再指定一个中文字体回退避免出现方框乱码。常用的中文字体如SimHei、Microsoft YaHei都可以用但记得设置完之后先简单跑一个测试图确认显示正常再继续画正式图。4.2 图片尺寸、分辨率和格式论文插图的分辨率要求通常在300dpi以上这一点在保存时可以明确写出。尺寸方面单栏图片建议宽度在8到9厘米双栏图片建议在14到17厘米之间具体要看你投的期刊模板。Matplotlib在保存时可以直接指定所以不需要先在屏幕上凑合看最后统一设置即可。关于保存格式我在实际投稿中比较过PNG、SVG和PDF三种。PNG最通用但放大到很大倍率时边缘会有锯齿SVG和PDF是矢量格式文字线条都会清晰很多。很多期刊在终稿上传时是接受矢量图的所以我的习惯是日常查看用PNG投稿前重新保存一份PDF或SVG按模板尺寸调整好。部分在线投稿系统对文件大小有要求矢量图太大的话再转成高分辨率PNG也不迟。4.3 坐标轴范围设置有讲究Y轴范围如果从0开始会暴露训练早期loss很高的那段曲线导致中后段的loss变化看起来像一条水平线视觉上会显得模型好像没有收敛。针对loss图我通常把Y轴范围设为从最小值往下留10%余量到最大值往上留一点余量针对mAP图因为指标范围天然在0到1之间就把范围固定在0.5到1.0或0到1.05。这里的关键定力是不能为了让曲线好看就过度裁剪坐标轴否则审稿人或者答辩老师一眼就能看出来反而产生适得其反的效果。Y轴裁剪得合理与否判断标准很简单训练初期那段剧烈下降要能看到轮廓中期平稳段要有足够的纵向分辨力。5. 画图过程中容易踩的坑与处理习惯5.1 训练提前中断导致曲线不完整用过早停止机制或者手动中断训练的情况非常常见。中断之后results.csv里只有中断前那些epoch的数据直接画图的话曲线会戛然而止看起来很不协调。如果你的论文实验里确实接受了这个提前停止的模型那曲线就应该如实反映训练轮数不需要人为补点。但如果你只是本地调试时中断了正式实验还会完整重跑那当前这版图就是临时的别浪费时间调样式等正式数据出来再调。还有种情况是训练过程中电脑重启保存的csv被覆盖了一部分。碰到这种情况不用慌检查一下csv的行数和epoch最大值是否对应。如果发现最后几个epoch缺失而你的实验设置里又正好有学习率回调、验证频率这类功能可以先确认下是不是回调本身就跳过了某些epoch而不是文件损坏。5.2 best.pt与last.pt对应曲线的确认画mAP曲线时有个不容易注意到的细节论文里报的最终指标一般对应的是训练过程中验证集表现最好的那个权重文件也就是best.pt而不是训练到最后一步的last.pt。如果你只画出曲线不标出best.pt对应的epoch审稿人就会去核对表格里你的数值和曲线最高点能不能对上。这里的最稳做法是在绘图脚本里读取权重文件名中带有的epoch号或者在训练日志里找到best模型保存时的epoch然后在曲线上用竖线或者圆点把这个位置标出来。best_epoch 120 # 替换成你训练日志里实际记录的best epoch ax.axvline(xbest_epoch, colorgray, linestyle:, linewidth1) ax.annotate(best, xy(best_epoch, 0.5), xytext(best_epoch 2, 0.55), fontsize9, colorgray)这个标注看似简单但在论文写作中作用不小能够让表格里的数字和曲线直接互相印证。5.3 多条曲线对比时对齐训练轮数做消融实验时不同实验可能训练同样的epoch数但其中某个实验因为数据集不同、batch size调整等原因提前触发了早停。这种情况下直接把多个csv画在同一张图里会发现短的那条曲线提前结束后面的对比区域等于没有数据。规范做法是要么把所有实验统一设置成固定epoch数并关闭早停要么只对比前若干个epoch。画到同一张图之前先用epoch列做对齐确保横坐标语义一致。还有个更隐蔽的坑如果不同实验的验证频率不一样比如一个每2个epoch验证一次一个每个epoch都验证那么同样的横坐标上点的密度不同画出来的平滑曲线也会有很大差异这时候必须统一验证频率再比较。5.4 字段命名在不同版本间的变化YOLO系列模型更新换代很快字段命名的兼容性问题比想象中常见。同一套代码从这个版本换到那个版本列名可能会从metrics/mAP50(B)变成metrics/mAP50或者少了括号后缀。最简单的规避方法就是每次新建绘图脚本时先跑第一节里的列名打印代码把实际字段确认一遍再画。为了省事你还可以在读取后用代码做一份字段映射字典把不同写法的名字统一成你自己约定好的内部名字这样脚本的可复用性会大大提升。5.5 怎么从曲线判断训练是否健康这项内容不完全算绘图技巧但每条曲线画出来之后你得能读懂它。正常情况下box_loss和cls_loss在中前期应该快速下降后期趋于平缓验证集loss和训练集loss保持同步下降时说明模型状态健康。如果验证loss在某个epoch触底反弹而训练loss还在继续下降这就是过拟合的高频信号。对应地mAP50-95曲线应该逐步爬升到后期出现平台期震荡幅度如果一直很大且均值没有上升趋势说明学习率设置可能偏大或者数据集的难度分布不均匀。养成每次训练完都看一遍曲线形态的习惯比只盯着最终指标更能帮助判断实验是否真的做对了。回到画图这件事本身我的体会是do not过度美化把时间花在理解数据上更重要但必要的美化规范确实要做因为在论文里一张没有坐标说明、没有图注、字体混乱的曲线图会直接影响读者对实验结果的信任度。上面这套流程试过几次之后你完全可以把绘图脚本固化成模板每次新实验出来改一改路径就出图总共不超过十分钟。真到了交稿前两天才想起来要改图的时候你就会感谢当初在这套脚本上花过功夫。