1. 项目概述

在目标检测领域,YOLO系列模型因其出色的实时性和准确性而广受欢迎。作为最新迭代版本,YOLO26在模型架构和训练策略上都有显著改进。但在实际研究过程中,如何系统评估模型性能、分析训练过程的有效性,往往成为困扰研究者的难题。

本文将详细介绍如何使用Python绘制YOLO26训练过程中的关键指标曲线对比图,包括mAP@0.5、mAP@0.5:0.95等精度指标,以及各类损失函数的变化趋势。这些可视化工具不仅能直观展示模型性能,更能为后续的调参和改进提供数据支撑。

提示:本文提供的代码和方法同样适用于YOLOv5、YOLOv8等其他版本,只需稍作调整即可迁移使用。

2. 核心指标解析

2.1 精度指标详解

mAP(mean Average Precision)是评估目标检测模型性能的核心指标。在YOLO26的训练日志中,我们主要关注两个变体:

  1. mAP@0.5 :当IoU阈值为0.5时的平均精度

    • 计算方式:对每个类别计算PR曲线下面积,然后对所有类别取平均
    • 特点:相对宽松的评价标准,适合初步评估模型性能
  2. mAP@0.5:0.95 :IoU阈值从0.5到0.95(步长0.05)的平均mAP

    • 计算方式:在不同IoU阈值下计算mAP,然后取平均值
    • 特点:更严格的评价标准,能全面反映模型定位精度
# mAP计算核心逻辑示例
def calculate_map(predictions, ground_truths, iou_threshold=0.5):
    """
    计算指定IoU阈值下的mAP
    :param predictions: 模型预测结果列表
    :param ground_truths: 真实标注列表 
    :param iou_threshold: IoU阈值
    :return: mAP值
    """
    aps = []
    for class_id in all_classes:
        # 计算每个类别的AP
        ap = compute_ap_for_class(predictions, ground_truths, class_id, iou_threshold)
        aps.append(ap)
    return np.mean(aps)

2.2 损失函数解析

YOLO26的损失函数通常包含三个主要部分:

  1. 边界框回归损失(box_loss)

    • 使用CIoU Loss,考虑重叠区域、中心点距离和长宽比
    • 公式:$L_{CIoU} = 1 - IoU + \frac{\rho^2(b,b^{gt})}{c^2} + \alpha v$
  2. 分类损失(cls_loss)

    • 通常使用二元交叉熵(BCE)损失
    • 公式:$L_{cls} = -\frac{1}{N}\sum_i[y_i\log(p_i)+(1-y_i)\log(1-p_i)]$
  3. 目标性损失(obj_loss)

    • 评估网格单元是否包含物体
    • 同样使用BCE损失

3. 数据准备与处理

3.1 训练日志解析

YOLO26的训练过程会生成包含以下关键信息的日志文件(通常为.txt或.csv格式):

epoch      gpu_mem       box       obj       cls    labels  img_size
0/299       3.77G    0.0921    0.0245    0.00955       32       640
1/299       3.77G    0.0903    0.0238    0.00912       32       640
...

我们需要从这些日志中提取以下数据用于可视化:

  • 训练轮次(epoch)
  • 各类损失值(box_loss, obj_loss, cls_loss)
  • 验证集mAP值(mAP@0.5, mAP@0.5:0.95)
import pandas as pd

def parse_log_file(log_path):
    """
    解析YOLO训练日志文件
    :param log_path: 日志文件路径
    :return: 包含解析数据的DataFrame
    """
    data = []
    with open(log_path, 'r') as f:
        for line in f:
            if line.startswith('epoch'):
                continue  # 跳过标题行
            parts = line.strip().split()
            if len(parts) >= 6:  # 确保是有效数据行
                epoch_info = parts[0].split('/')
                current_epoch = int(epoch_info[0])
                total_epochs = int(epoch_info[1])
                
                record = {
                    'epoch': current_epoch,
                    'box_loss': float(parts[2]),
                    'obj_loss': float(parts[3]),
                    'cls_loss': float(parts[4]),
                    # 可根据实际日志格式添加更多字段
                }
                data.append(record)
    
    return pd.DataFrame(data)

3.2 多实验数据整合

当进行多组对比实验时(如不同超参数、不同模型结构),我们需要将多组实验数据整合在一起进行对比分析:

def combine_experiments(experiments):
    """
    合并多个实验的数据用于对比
    :param experiments: 实验数据字典,格式为{'exp1': df1, 'exp2': df2}
    :return: 合并后的DataFrame
    """
    combined = pd.DataFrame()
    for exp_name, df in experiments.items():
        df['experiment'] = exp_name
        combined = pd.concat([combined, df], ignore_index=True)
    return combined

4. 可视化实现

4.1 精度曲线绘制

mAP曲线的绘制可以帮助我们直观比较不同实验配置下模型的检测精度变化:

import matplotlib.pyplot as plt
import seaborn as sns

def plot_map_curves(data, map_type='mAP_0.5', save_path=None):
    """
    绘制mAP变化曲线
    :param data: 包含实验数据的DataFrame
    :param map_type: 要绘制的mAP类型('mAP_0.5'或'mAP_0.5_0.95')
    :param save_path: 图片保存路径(可选)
    """
    plt.figure(figsize=(12, 6))
    sns.set_style("whitegrid")
    
    # 使用seaborn绘制曲线
    ax = sns.lineplot(data=data, x='epoch', y=map_type, 
                     hue='experiment', style='experiment',
                     markers=True, dashes=False, linewidth=2.5)
    
    # 设置图表标题和标签
    map_name = 'mAP@0.5' if map_type == 'mAP_0.5' else 'mAP@0.5:0.95'
    plt.title(f'{map_name} Curve Comparison', fontsize=14, pad=20)
    plt.xlabel('Epoch', fontsize=12)
    plt.ylabel(map_name, fontsize=12)
    
    # 调整图例位置和样式
    plt.legend(title='Experiment', bbox_to_anchor=(1.05, 1), 
               loc='upper left', borderaxespad=0)
    
    # 保存或显示图表
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=300)
    plt.show()

4.2 损失曲线绘制

损失函数曲线可以帮助我们监控训练过程的收敛情况:

def plot_loss_curves(data, loss_types=['box_loss', 'obj_loss', 'cls_loss'], save_path=None):
    """
    绘制损失函数变化曲线
    :param data: 包含实验数据的DataFrame
    :param loss_types: 要绘制的损失类型列表
    :param save_path: 图片保存路径(可选)
    """
    plt.figure(figsize=(14, 8))
    sns.set_style("whitegrid")
    
    # 创建子图
    fig, axes = plt.subplots(len(loss_types), 1, figsize=(14, 6*len(loss_types)))
    
    if len(loss_types) == 1:
        axes = [axes]  # 确保axes始终是列表
    
    for i, loss_type in enumerate(loss_types):
        # 绘制单个损失曲线
        sns.lineplot(data=data, x='epoch', y=loss_type, 
                    hue='experiment', style='experiment',
                    markers=True, dashes=False, linewidth=2.5,
                    ax=axes[i])
        
        # 设置子图标题和标签
        axes[i].set_title(f'{loss_type} Curve', fontsize=12, pad=10)
        axes[i].set_xlabel('Epoch', fontsize=10)
        axes[i].set_ylabel(loss_type, fontsize=10)
        axes[i].legend(title='Experiment', bbox_to_anchor=(1.05, 1), 
                      loc='upper left', borderaxespad=0)
    
    plt.tight_layout()
    
    # 保存或显示图表
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=300)
    plt.show()

4.3 多实验对比可视化

当需要同时比较多个实验的mAP和损失曲线时,可以使用子图组合:

def plot_comprehensive_comparison(data, save_path=None):
    """
    绘制综合对比图,包含mAP和各类损失曲线
    :param data: 包含实验数据的DataFrame
    :param save_path: 图片保存路径(可选)
    """
    plt.figure(figsize=(16, 12))
    sns.set_style("whitegrid")
    
    # 创建2x2的子图布局
    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    
    # 绘制mAP@0.5曲线
    sns.lineplot(data=data, x='epoch', y='mAP_0.5', 
                hue='experiment', style='experiment',
                markers=True, dashes=False, linewidth=2.5,
                ax=axes[0, 0])
    axes[0, 0].set_title('mAP@0.5 Comparison', fontsize=12)
    
    # 绘制mAP@0.5:0.95曲线
    sns.lineplot(data=data, x='epoch', y='mAP_0.5_0.95', 
                hue='experiment', style='experiment',
                markers=True, dashes=False, linewidth=2.5,
                ax=axes[0, 1])
    axes[0, 1].set_title('mAP@0.5:0.95 Comparison', fontsize=12)
    
    # 绘制box_loss曲线
    sns.lineplot(data=data, x='epoch', y='box_loss', 
                hue='experiment', style='experiment',
                markers=True, dashes=False, linewidth=2.5,
                ax=axes[1, 0])
    axes[1, 0].set_title('Box Loss Comparison', fontsize=12)
    
    # 绘制cls_loss曲线
    sns.lineplot(data=data, x='epoch', y='cls_loss', 
                hue='experiment', style='experiment',
                markers=True, dashes=False, linewidth=2.5,
                ax=axes[1, 1])
    axes[1, 1].set_title('Classification Loss Comparison', fontsize=12)
    
    # 调整图例
    for ax in axes.flat:
        ax.legend(title='Experiment', bbox_to_anchor=(1.05, 1), 
                 loc='upper left', borderaxespad=0)
    
    plt.tight_layout()
    
    # 保存或显示图表
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=300)
    plt.show()

5. 高级可视化技巧

5.1 平滑处理技术

训练过程中的指标往往存在波动,适当的平滑处理可以使趋势更加清晰:

def apply_smoothing(data, column, window_size=5):
    """
    对指定列应用移动平均平滑
    :param data: 包含数据的DataFrame
    :param column: 要平滑的列名
    :param window_size: 滑动窗口大小
    :return: 平滑后的Series
    """
    return data[column].rolling(window=window_size, min_periods=1).mean()

def plot_with_smoothing(data, column, window_size=5, save_path=None):
    """
    绘制原始数据和平滑后的对比曲线
    :param data: 包含数据的DataFrame
    :param column: 要绘制的列名
    :param window_size: 平滑窗口大小
    :param save_path: 图片保存路径(可选)
    """
    plt.figure(figsize=(12, 6))
    sns.set_style("whitegrid")
    
    # 计算平滑数据
    data['smoothed'] = data.groupby('experiment')[column].transform(
        lambda x: x.rolling(window=window_size, min_periods=1).mean()
    )
    
    # 绘制原始数据(半透明)
    sns.lineplot(data=data, x='epoch', y=column,
                hue='experiment', style='experiment',
                alpha=0.3, linewidth=1.5, legend=False)
    
    # 绘制平滑数据
    sns.lineplot(data=data, x='epoch', y='smoothed',
                hue='experiment', style='experiment',
                linewidth=2.5)
    
    # 设置图表标题和标签
    plt.title(f'{column} with Smoothing (window={window_size})', fontsize=14, pad=20)
    plt.xlabel('Epoch', fontsize=12)
    plt.ylabel(column, fontsize=12)
    
    # 调整图例
    plt.legend(title='Experiment', bbox_to_anchor=(1.05, 1), 
              loc='upper left', borderaxespad=0)
    
    # 保存或显示图表
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=300)
    plt.show()

5.2 置信区间可视化

对于多次重复实验,可以展示指标的均值和置信区间:

def plot_with_confidence(data, column, n_boot=1000, save_path=None):
    """
    绘制带置信区间的曲线图
    :param data: 包含数据的DataFrame
    :param column: 要绘制的列名
    :param n_boot: 自助采样次数
    :param save_path: 图片保存路径(可选)
    """
    plt.figure(figsize=(12, 6))
    sns.set_style("whitegrid")
    
    # 使用seaborn绘制带置信区间的曲线
    ax = sns.lineplot(data=data, x='epoch', y=column,
                     hue='experiment', style='experiment',
                     ci=95, n_boot=n_boot,
                     linewidth=2.5)
    
    # 设置图表标题和标签
    plt.title(f'{column} with 95% Confidence Interval', fontsize=14, pad=20)
    plt.xlabel('Epoch', fontsize=12)
    plt.ylabel(column, fontsize=12)
    
    # 调整图例
    plt.legend(title='Experiment', bbox_to_anchor=(1.05, 1), 
              loc='upper left', borderaxespad=0)
    
    # 保存或显示图表
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=300)
    plt.show()

6. 论文级图表优化

6.1 学术图表规范

为满足论文发表要求,图表需要遵循以下规范:

  1. 字体和字号

    • 标题:14pt加粗
    • 坐标轴标签:12pt
    • 刻度标签:10pt
    • 图例:10pt
  2. 线条和标记

    • 主要曲线:2-2.5pt线宽
    • 对比曲线:使用不同线型(实线、虚线、点线等)
    • 数据点:适当使用标记(圆形、方形、三角形等)
  3. 颜色方案

    • 使用高对比度、易于区分的颜色
    • 避免使用红色/绿色组合(考虑色盲读者)
    • 推荐使用ColorBrewer的定性配色方案
def create_publication_ready_plot(data, column, save_path=None):
    """
    创建符合论文发表要求的高质量图表
    :param data: 包含数据的DataFrame
    :param column: 要绘制的列名
    :param save_path: 图片保存路径(可选)
    """
    plt.figure(figsize=(8, 5))
    sns.set_style("white")
    
    # 设置论文专用样式
    plt.rcParams['font.family'] = 'Times New Roman'
    plt.rcParams['font.size'] = 12
    plt.rcParams['axes.labelsize'] = 12
    plt.rcParams['axes.titlesize'] = 14
    plt.rcParams['xtick.labelsize'] = 10
    plt.rcParams['ytick.labelsize'] = 10
    plt.rcParams['legend.fontsize'] = 10
    
    # 使用ColorBrewer的定性配色方案
    palette = sns.color_palette("Set1", n_colors=len(data['experiment'].unique()))
    
    # 绘制图表
    ax = sns.lineplot(data=data, x='epoch', y=column,
                     hue='experiment', style='experiment',
                     palette=palette,
                     linewidth=2, markersize=8,
                     markers=True, dashes=False)
    
    # 设置标题和标签
    ax.set_title(column.replace('_', ' ').title(), pad=15)
    ax.set_xlabel('Epoch', labelpad=10)
    ax.set_ylabel(column.replace('_', ' ').title(), labelpad=10)
    
    # 调整图例
    ax.legend(title='Experiment', bbox_to_anchor=(1.05, 1), 
             loc='upper left', borderaxespad=0, frameon=False)
    
    # 设置网格线
    ax.grid(True, linestyle='--', alpha=0.6)
    
    # 保存图表(推荐PDF或EPS格式以保持矢量质量)
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=600, 
                   format='pdf' if save_path.endswith('.pdf') else 'png')
    plt.show()

6.2 多子图组合展示

在论文中,通常需要将多个相关图表组合在一起展示:

def create_multi_panel_figure(data, save_path=None):
    """
    创建包含多个子图的组合图表
    :param data: 包含数据的DataFrame
    :param save_path: 图片保存路径(可选)
    """
    # 设置整体样式
    sns.set_style("white")
    plt.rcParams['font.family'] = 'Times New Roman'
    plt.rcParams['font.size'] = 10
    
    # 创建图形对象
    fig = plt.figure(figsize=(12, 10))
    gs = fig.add_gridspec(3, 2)
    
    # 子图1:mAP@0.5
    ax1 = fig.add_subplot(gs[0, 0])
    sns.lineplot(data=data, x='epoch', y='mAP_0.5', 
                hue='experiment', ax=ax1,
                linewidth=1.5)
    ax1.set_title('(a) mAP@0.5', pad=10)
    ax1.set_xlabel('')
    ax1.set_ylabel('mAP@0.5')
    
    # 子图2:mAP@0.5:0.95
    ax2 = fig.add_subplot(gs[0, 1])
    sns.lineplot(data=data, x='epoch', y='mAP_0.5_0.95', 
                hue='experiment', ax=ax2,
                linewidth=1.5)
    ax2.set_title('(b) mAP@0.5:0.95', pad=10)
    ax2.set_xlabel('')
    ax2.set_ylabel('mAP@0.5:0.95')
    ax2.legend_.remove()
    
    # 子图3:Box Loss
    ax3 = fig.add_subplot(gs[1, 0])
    sns.lineplot(data=data, x='epoch', y='box_loss', 
                hue='experiment', ax=ax3,
                linewidth=1.5)
    ax3.set_title('(c) Box Loss', pad=10)
    ax3.set_xlabel('')
    ax3.set_ylabel('Box Loss')
    ax3.legend_.remove()
    
    # 子图4:Objectness Loss
    ax4 = fig.add_subplot(gs[1, 1])
    sns.lineplot(data=data, x='epoch', y='obj_loss', 
                hue='experiment', ax=ax4,
                linewidth=1.5)
    ax4.set_title('(d) Objectness Loss', pad=10)
    ax4.set_xlabel('')
    ax4.set_ylabel('Objectness Loss')
    ax4.legend_.remove()
    
    # 子图5:Classification Loss
    ax5 = fig.add_subplot(gs[2, :])
    sns.lineplot(data=data, x='epoch', y='cls_loss', 
                hue='experiment', ax=ax5,
                linewidth=1.5)
    ax5.set_title('(e) Classification Loss', pad=10)
    ax5.set_xlabel('Epoch')
    ax5.set_ylabel('Classification Loss')
    ax5.legend_.remove()
    
    # 调整布局
    plt.tight_layout()
    
    # 添加共享图例
    handles, labels = ax1.get_legend_handles_labels()
    fig.legend(handles, labels, loc='lower center', 
              ncol=len(data['experiment'].unique()),
              bbox_to_anchor=(0.5, 0.01), frameon=False)
    
    # 保存图表
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=600, 
                   format='pdf' if save_path.endswith('.pdf') else 'png')
    plt.show()

7. 实际应用案例

7.1 案例一:学习率策略对比

假设我们比较了三种不同的学习率调度策略对YOLO26训练的影响:

  1. 恒定学习率(lr=0.01)
  2. 余弦退火调度
  3. 多步衰减调度
# 加载三个实验的数据
exp1 = parse_log_file('logs/yolo26_constant_lr.log')
exp1['experiment'] = 'Constant LR'
exp2 = parse_log_file('logs/yolo26_cosine_lr.log')
exp2['experiment'] = 'Cosine Annealing'
exp3 = parse_log_file('logs/yolo26_multistep_lr.log')
exp3['experiment'] = 'MultiStep Decay'

# 合并数据
combined_data = pd.concat([exp1, exp2, exp3])

# 绘制mAP对比图
plot_map_curves(combined_data, map_type='mAP_0.5_0.95',
               save_path='figures/lr_strategy_map_comparison.png')

# 绘制损失对比图
plot_loss_curves(combined_data, loss_types=['box_loss', 'cls_loss'],
                save_path='figures/lr_strategy_loss_comparison.png')

7.2 案例二:数据增强策略分析

比较不同数据增强策略对模型性能的影响:

  1. 基础增强(翻转、缩放)
  2. 中等增强(添加Mosaic、MixUp)
  3. 强增强(添加CutMix、随机擦除)
# 加载数据
exp1 = parse_log_file('logs/yolo26_basic_aug.log')
exp1['experiment'] = 'Basic Augmentation'
exp2 = parse_log_file('logs/yolo26_medium_aug.log')
exp2['experiment'] = 'Medium Augmentation'
exp3 = parse_log_file('logs/yolo26_strong_aug.log')
exp3['experiment'] = 'Strong Augmentation'

# 合并数据
combined_data = pd.concat([exp1, exp2, exp3])

# 创建综合对比图
plot_comprehensive_comparison(combined_data,
                             save_path='figures/augmentation_comparison.png')

8. 常见问题与解决方案

8.1 数据解析问题

问题1:日志格式不一致

  • 现象:不同版本的YOLO可能产生略有不同的日志格式
  • 解决方案:编写灵活的解析函数,使用正则表达式匹配关键数据
import re

def flexible_parse_log(log_path):
    """
    灵活解析不同格式的YOLO训练日志
    :param log_path: 日志文件路径
    :return: 解析后的DataFrame
    """
    data = []
    epoch_pattern = re.compile(r'(\d+)/(\d+)')
    metric_pattern = re.compile(r'\d+\.\d+')
    
    with open(log_path, 'r') as f:
        for line in f:
            if not line.strip() or line.startswith('epoch'):
                continue
            
            # 匹配epoch信息
            epoch_match = epoch_pattern.search(line)
            if not epoch_match:
                continue
                
            current_epoch = int(epoch_match.group(1))
            total_epochs = int(epoch_match.group(2))
            
            # 匹配指标数值
            metrics = metric_pattern.findall(line)
            if len(metrics) < 3:  # 至少需要box, obj, cls三个损失
                continue
                
            record = {
                'epoch': current_epoch,
                'box_loss': float(metrics[0]),
                'obj_loss': float(metrics[1]),
                'cls_loss': float(metrics[2]),
            }
            
            # 尝试匹配mAP值(如果有)
            if len(metrics) >= 5:
                record['mAP_0.5'] = float(metrics[-2])
                record['mAP_0.5_0.95'] = float(metrics[-1])
            
            data.append(record)
    
    return pd.DataFrame(data)

8.2 可视化问题

问题2:曲线过于波动

  • 现象:训练曲线波动剧烈,难以观察整体趋势
  • 解决方案:
    1. 应用移动平均平滑
    2. 适当增大批量大小(batch size)
    3. 检查学习率是否设置过高

问题3:多实验曲线重叠

  • 现象:多条曲线颜色相近难以区分
  • 解决方案:
    1. 使用高对比度配色方案
    2. 结合不同线型和标记
    3. 将部分曲线绘制在次坐标轴上
def plot_with_secondary_axis(data, primary_col, secondary_col, save_path=None):
    """
    使用双坐标轴绘制两组不同量纲的曲线
    :param data: 包含数据的DataFrame
    :param primary_col: 主坐标轴列名
    :param secondary_col: 次坐标轴列名
    :param save_path: 图片保存路径(可选)
    """
    fig, ax1 = plt.subplots(figsize=(12, 6))
    
    # 绘制主坐标轴曲线
    color = 'tab:blue'
    ax1.set_xlabel('Epoch', fontsize=12)
    ax1.set_ylabel(primary_col, color=color, fontsize=12)
    line1 = ax1.plot(data['epoch'], data[primary_col], 
                    color=color, linewidth=2, label=primary_col)
    ax1.tick_params(axis='y', labelcolor=color)
    
    # 创建次坐标轴
    ax2 = ax1.twinx()
    color = 'tab:red'
    ax2.set_ylabel(secondary_col, color=color, fontsize=12)
    line2 = ax2.plot(data['epoch'], data[secondary_col],
                    color=color, linestyle='--', 
                    linewidth=2, label=secondary_col)
    ax2.tick_params(axis='y', labelcolor=color)
    
    # 合并图例
    lines = line1 + line2
    labels = [l.get_label() for l in lines]
    ax1.legend(lines, labels, loc='upper left')
    
    plt.title(f'{primary_col} vs {secondary_col}', fontsize=14, pad=20)
    
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=300)
    plt.show()

8.3 性能优化问题

问题4:大规模数据可视化卡顿

  • 现象:当实验次数多或epoch数量大时,绘图速度变慢
  • 解决方案:
    1. 对数据进行下采样(每N个epoch取一个点)
    2. 使用更高效的可视化库(如Plotly的WebGL渲染)
    3. 预先计算并存储聚合数据
def downsample_data(data, stride=5):
    """
    对数据进行下采样以减少绘图负担
    :param data: 原始DataFrame
    :param stride: 采样步长
    :return: 下采样后的DataFrame
    """
    return data.groupby('experiment').apply(
        lambda x: x.iloc[::stride] if len(x) > 100 else x
    ).reset_index(drop=True)

def plot_large_dataset(data, column, save_path=None):
    """
    优化大规模数据集的可视化
    :param data: 包含数据的DataFrame
    :param column: 要绘制的列名
    :param save_path: 图片保存路径(可选)
    """
    # 下采样数据
    if len(data) > 1000:
        data = downsample_data(data)
    
    # 使用更高效的可视化方法
    plt.figure(figsize=(12, 6))
    for exp in data['experiment'].unique():
        exp_data = data[data['experiment'] == exp]
        plt.plot(exp_data['epoch'], exp_data[column], 
                linewidth=1.5, label=exp)
    
    plt.xlabel('Epoch', fontsize=12)
    plt.ylabel(column, fontsize=12)
    plt.title(column.replace('_', ' ').title(), fontsize=14, pad=20)
    plt.legend()
    plt.grid(True)
    
    if save_path:
        plt.savefig(save_path, bbox_inches='tight', dpi=300)
    plt.show()

9. 扩展应用与进阶技巧

9.1 自定义指标监控

除了默认的mAP和损失指标,我们还可以监控自定义指标:

def monitor_custom_metric(log_dir, metric_name, metric_func):
    """
    监控自定义指标
    :param log_dir: 日志目录路径
    :param metric_name: 指标名称
    :param metric_func: 计算指标的函数
    """
    all_data = []
    
    # 遍历所有实验日志
    for log_file in os.listdir(log_dir):
        if log_file.endswith('.log'):
            exp_name = log_file.replace('.log', '')
            log_path = os.path.join(log_dir, log_file)
            
            # 解析日志并计算自定义指标
            data = parse_log_file(log_path)
            data[metric_name] = data.apply(metric_func, axis=1)
            data['experiment'] = exp_name
            all_data.append(data)
    
    # 合并数据并绘制曲线
    combined = pd.concat(all_data)
    plot_map_curves(combined, map_type=metric_name,
                   save_path=f'figures/{metric_name}_comparison.png')

9.2 实时训练监控

使用TensorBoard或自定义Web界面实现实时监控:

from flask import Flask, render_template
import threading
import time

app = Flask(__name__)

def background_monitor(log_dir, refresh_interval=60):
    """
    后台监控日志文件变化
    :param log_dir: 日志目录路径
    :param refresh_interval: 刷新间隔(秒)
    """
    while True:
        # 检查新日志并更新数据
        update_visualizations(log_dir)
        time.sleep(refresh_interval)

@app.route('/')
def dashboard():
    """
    显示训练监控仪表盘
    """
    return render_template('dashboard.html')

def run_monitoring_server(log_dir, port=5000):
    """
    启动监控服务器
    :param log_dir: 日志目录路径
    :param port: 服务器端口
    """
    # 启动后台监控线程
    monitor_thread = threading.Thread(
        target=background_monitor,
        args=(log_dir,),
        daemon=True
    )
    monitor_thread.start()
    
    # 启动Flask服务器
    app.run(port=port)

9.3 自动化报告生成

将可视化结果整合为PDF报告:

from fpdf import FPDF
import glob

def generate_report(image_dir, output_path='report.pdf'):
    """
    生成PDF格式的实验报告
    :param image_dir: 包含可视化图片的目录
    :param output_path: 输出PDF路径
    """
    pdf = FPDF()
    pdf.set_auto_page_break(auto=True, margin=15)
    
    # 添加封面
    pdf.add_page()
    pdf.set_font('Arial', 'B', 16)
    pdf.cell(0, 10, 'YOLO26 Training Experiment Report', 0, 1, 'C')
    pdf.ln(20)
    
    # 添加目录
    pdf.set_font('Arial', 'I', 12)
    pdf.cell(0, 10, 'Table of Contents', 0, 1)
    pdf.ln(10)
    
    # 添加内容页
    image_files = sorted(glob.glob(os.path.join(image_dir, '*.png')))
    for img_path in image_files:
        pdf.add_page()
        img_name = os.path.basename(img_path).replace('.png', '').replace('_', ' ')
        pdf.set_font('Arial', 'B', 12)
        pdf.cell(0, 10, img_name, 0, 1)
        pdf.ln(5)
        pdf.image(img_path, x=10, w=190)
    
    # 保存PDF
    pdf.output(output_path)

更多推荐