009-YOLO11重新绘制results曲线-精度召回率和损失分析

📅 2026/7/9 3:56:00
009-YOLO11重新绘制results曲线-精度召回率和损失分析
009-不只看 results.png重新绘制 YOLO11 精度、召回率和损失曲线本文基于 Ultralytics8.3.253的训练结果文件整理。训练完成后不要只看默认的results.png更建议重新读取results.csv把 Precision、Recall、mAP50、mAP50-95 和各类 loss 单独画出来方便后续做 YOLO11 改进实验对比。摘要YOLO11 训练完成后Ultralytics 会自动生成results.png。这张图很方便但在做改进实验时还不够细不同实验曲线挤在一起、单个指标看不清、最佳 epoch 不容易定位、mAP50 和 mAP50-95 容易被混着看。本文给出一份可以直接运行的曲线重绘脚本用来读取一个或多个results.csv分别绘制检测指标曲线、训练与验证损失曲线、学习率曲线并导出最佳 epoch 汇总表。文章不填写虚构训练结果读者只需要把脚本中的路径换成自己的训练目录即可。关键词009、YOLO11、Ultralytics 8.3.253、results.csv、results.png、mAP50、mAP50-95、训练曲线一、为什么不要只看默认 results.pngUltralytics 默认会根据results.csv绘制results.png。这个功能很实用但如果后续要做模型改进单靠默认图会遇到几个问题问题影响曲线图尺寸固定指标多时阅读不够清楚多实验对比不方便baseline 和改进模型不容易叠在一起最佳 epoch 不直观需要回到 CSV 才能找到mAP50 与 mAP50-95 容易混看结论可能写得不严谨loss 与指标关系不明显不利于判断过拟合或欠拟合所以建议把results.csv当成主要数据源默认results.png只作为快速预览。二、YOLO11 训练结果目录以前面建立的 baseline 为例训练结果目录通常类似runs/ yolo11_baseline/ yolo11n_s0_e100_i640/ args.yaml results.csv results.png confusion_matrix.png weights/ best.pt last.pt其中文件作用args.yaml保存本次训练参数results.csv保存每个 epoch 的 loss、指标和学习率results.png官方默认训练曲线图best.pt验证指标最优权重last.pt最后一轮权重本文脚本主要读取results.csv。三、results.csv 中常见列名不同任务的列名可能略有差别。普通检测任务中常见列名包括列名含义epoch当前训练轮数train/box_loss训练集框回归损失train/cls_loss训练集分类损失train/dfl_loss训练集 DFL 损失metrics/precision(B)检测框 Precisionmetrics/recall(B)检测框 Recallmetrics/mAP50(B)IoU0.5 下的 mAPmetrics/mAP50-95(B)IoU 0.5 到 0.95 的平均 mAPval/box_loss验证集框回归损失val/cls_loss验证集分类损失val/dfl_loss验证集 DFL 损失注意mAP50 和 mAP50-95 必须分开看。mAP50 更宽松mAP50-95 对定位质量要求更高。四、安装绘图依赖脚本需要pandas和matplotlibpipinstallpandas matplotlib如果要平滑曲线脚本内部使用pandas的滚动平均不额外依赖 scipy。五、完整曲线重绘脚本新建文件plot_yolo11_results.pyimportargparsefrompathlibimportPathimportmatplotlib.pyplotaspltimportpandasaspd METRIC_COLUMNS[metrics/precision(B),metrics/recall(B),metrics/mAP50(B),metrics/mAP50-95(B),]LOSS_COLUMNS[train/box_loss,train/cls_loss,train/dfl_loss,val/box_loss,val/cls_loss,val/dfl_loss,]defread_result_csv(path):dfpd.read_csv(path)df.columns[c.strip()forcindf.columns]ifepochnotindf.columns:df.insert(0,epoch,range(len(df)))returndfdefsmooth(series,window):ifwindow1:returnseriesreturnseries.rolling(windowwindow,min_periods1).mean()deflabel_from_path(path):pathPath(path)ifpath.nameresults.csv:returnpath.parent.namereturnpath.stemdefplot_group(datasets,columns,out_path,title,smooth_window):existing_columns[colforcolincolumnsifany(colindf.columnsfor_,dfindatasets)]ifnotexisting_columns:print(fskip{title}, no matched columns)returnfig,axesplt.subplots(len(existing_columns),1,figsize(10,3.2*len(existing_columns)),sharexTrue)iflen(existing_columns)1:axes[axes]forax,colinzip(axes,existing_columns):forlabel,dfindatasets:ifcolnotindf.columns:continuexdf[epoch]ydf[col].astype(float)ax.plot(x,y,linewidth1.4,alpha0.45,labelf{label}raw)ax.plot(x,smooth(y,smooth_window),linewidth2.0,labelf{label}smooth)ax.set_title(col)ax.grid(True,linestyle--,alpha0.3)ax.legend()axes[-1].set_xlabel(epoch)fig.suptitle(title)fig.tight_layout()fig.savefig(out_path,dpi220)plt.close(fig)print(fsaved:{out_path})defexport_best_summary(datasets,out_path):rows[]forlabel,dfindatasets:ifmetrics/mAP50-95(B)notindf.columns:continuebest_indexdf[metrics/mAP50-95(B)].astype(float).idxmax()rowdf.loc[best_index]rows.append({experiment:label,best_epoch:int(row[epoch]),precision:row.get(metrics/precision(B),),recall:row.get(metrics/recall(B),),mAP50:row.get(metrics/mAP50(B),),mAP50-95:row.get(metrics/mAP50-95(B),),})ifrows:pd.DataFrame(rows).to_csv(out_path,indexFalse,encodingutf-8-sig)print(fsaved:{out_path})defmain():parserargparse.ArgumentParser()parser.add_argument(--csv,nargs,requiredTrue,helpone or more results.csv paths)parser.add_argument(--out,defaultcurve_outputs,helpoutput directory)parser.add_argument(--smooth,typeint,default5,helprolling average window)argsparser.parse_args()out_dirPath(args.out)out_dir.mkdir(parentsTrue,exist_okTrue)datasets[]forcsv_pathinargs.csv:pathPath(csv_path)datasets.append((label_from_path(path),read_result_csv(path)))plot_group(datasets,METRIC_COLUMNS,out_dir/metrics_curves.png,YOLO11 metrics curves,args.smooth)plot_group(datasets,LOSS_COLUMNS,out_dir/loss_curves.png,YOLO11 loss curves,args.smooth)lr_columns[colforcolindatasets[0][1].columnsifcol.startswith(lr/)]plot_group(datasets,lr_columns,out_dir/lr_curves.png,YOLO11 learning rate curves,args.smooth)export_best_summary(datasets,out_dir/best_epoch_summary.csv)if__name____main__:main()这段脚本做了四件事读取一个或多个results.csv自动识别指标列、loss 列和学习率列输出单独的曲线图按 mAP50-95 找出每个实验的最佳 epoch。六、绘制单个实验曲线如果只想分析 baseline可以运行python plot_yolo11_results.py--csvruns/yolo11_baseline/yolo11n_s0_e100_i640/results.csv--outruns/curve_analysis/baseline运行后会生成runs/ curve_analysis/ baseline/ metrics_curves.png loss_curves.png lr_curves.png best_epoch_summary.csv其中metrics_curves.png重点看 Precision、Recall、mAP50、mAP50-95loss_curves.png重点看训练和验证 loss 的变化。七、对比 baseline 和改进模型后续写注意力机制或 Neck 改进文章时可以把两个实验的results.csv放在一起画python plot_yolo11_results.py--csvruns/yolo11_baseline/yolo11n_s0_e100_i640/results.csv runs/yolo11_exp/yolo11n_newmodule_s0_e100_i640/results.csv--outruns/curve_analysis/baseline_vs_newmodule这样可以得到同一张指标曲线对比图。比单独放两张results.png更容易看出哪个模型收敛更快哪个模型后期更稳定mAP50 和 mAP50-95 是否同时改善Precision 提高时 Recall 有没有下降验证 loss 是否出现反弹。如果只是最后一个数略高但整条曲线波动很大就不要急着写“明显提升”。改进实验更看重稳定趋势。八、best_epoch_summary.csv 怎么用脚本会输出一个汇总文件best_epoch_summary.csv表格字段如下字段说明experiment实验名称默认取结果目录名best_epochmAP50-95 最高的 epochprecision最佳 epoch 的 Precisionrecall最佳 epoch 的 RecallmAP50最佳 epoch 的 mAP50mAP50-95最佳 epoch 的 mAP50-95后续写文章时可以把这张表整理成实验对比表。注意不要把last.pt的最后一轮指标和best.pt对应的最佳指标混着写。九、如何通过曲线判断问题下面是几种常见曲线现象。曲线现象可能原因train loss 降val loss 升可能过拟合mAP50 升mAP50-95 不升大致能检出但定位质量未改善Precision 升Recall 降模型更保守漏检可能增加Recall 升Precision 降检得更多但误检可能增加指标前期涨后期掉学习率、增强关闭或过拟合需要排查曲线剧烈波动数据量小、batch 小、标注噪声或训练不稳定这些只是排查方向不是绝对结论。真正写改进实验时还需要结合验证集预测图、混淆矩阵和数据集统计一起判断。十、重新绘图时容易踩的坑1. CSV 列名前面有空格有些版本写出的results.csv列名前可能带空格。脚本中已经使用df.columns[c.strip()forcindf.columns]这一步可以避免列名匹配失败。2. 不同任务列名不一样检测、分割、姿态任务的列名不同。本文脚本主要面向普通检测任务。如果是分割任务需要额外加入 mask 相关列名。3. 平滑窗口不能太大--smooth只是为了看趋势。窗口过大会掩盖真实波动。一般可以从5开始训练轮数很少时可以设成1。4. 不要只看最后一个 epoch最后一轮不一定是最佳模型。正式对比建议看 mAP50-95 最优的 epoch并确认对应权重是否为best.pt。5. 不要只看 mAP50mAP50 更宽松mAP50-95 更严格。后续写 YOLO11 改进文章时两个指标都要写。十一、总结本文围绕 YOLO11 训练结果分析给出了一份重新绘制results.csv曲线的脚本。相比只看默认results.png重新绘图可以更清楚地观察 Precision、Recall、mAP50、mAP50-95、训练 loss、验证 loss 和学习率变化。这一步对后续改进实验非常重要。一个模块是否有效不能只看单个最终数值而要看完整训练曲线是否稳定、mAP50 和 mAP50-95 是否同时改善、Precision 和 Recall 是否出现明显取舍。后面继续做注意力、Neck、Backbone 或 Loss 改进时建议每个实验都保留results.csv并用同一套脚本重新绘制曲线。这样文章里的结论会更清楚也更容易让读者复现。