在深度学习项目中,评价指标不仅是衡量模型性能的标尺,更直接影响模型选择与调优方向。本文将从一个可扩展的 OOP 设计出发,构建覆盖分类、分割、回归三大任务的评价指标体系。

基类设计

首先定义一个抽象基类,统一接口,方便后续扩展:

from abc import ABC, abstractmethod
import numpy as np

class BaseMetric(ABC):
    @abstractmethod
    def update(self, y_pred, y_true):
        """累积一批预测结果"""
        pass

    @abstractmethod
    def compute(self):
        """计算并返回当前指标值"""
        pass

    @abstractmethod
    def reset(self):
        """重置累积状态"""
        pass

分类指标

对于二分类 / 多分类任务,核心指标均来自混淆矩阵:

Accuracy=TP+TNTP+TN+FP+FN,Precision=TPTP+FPAccuracy = \frac{TP + TN}{TP + TN + FP + FN},\quad Precision = \frac{TP}{TP + FP}

Recall=TPTP+FN,F1=2PrecisionRecallPrecision+RecallRecall = \frac{TP}{TP + FN},\quad F_1 = \frac{2 \cdot Precision \cdot Recall}{Precision + Recall}

class ClassificationMetrics(BaseMetric):
    def __init__(self, num_classes):
        self.num_classes = num_classes
        self.reset()

    def update(self, y_pred, y_true):
        for p, t in zip(y_pred, y_true):
            self.conf_mat[t][p] += 1

    def compute(self):
        tp = np.diag(self.conf_mat)
        fp = self.conf_mat.sum(axis=0) - tp
        fn = self.conf_mat.sum(axis=1) - tp
        precision = tp / (tp + fp + 1e-8)
        recall = tp / (tp + fn + 1e-8)
        return {
            'accuracy': tp.sum() / self.conf_mat.sum(),
            'precision': precision.mean(),
            'recall': recall.mean(),
            'f1': (2 * precision * recall / (precision + recall + 1e-8)).mean()
        }

    def reset(self):
        self.conf_mat = np.zeros((self.num_classes, self.num_classes))

AUC-ROC 可通过 sklearn.metrics.roc_auc_score 计算,适合评估模型在不同阈值下的排序能力。

分割指标

分割任务关注像素级预测与真实标注的重叠程度:

IoU=ABAB,Dice=2ABA+BIoU = \frac{|A \cap B|}{|A \cup B|},\quad Dice = \frac{2|A \cap B|}{|A| + |B|}

class SegmentationMetrics(BaseMetric):
    def __init__(self, threshold=0.5):
        self.threshold = threshold
        self.reset()

    def update(self, y_pred, y_true):
        pred_bin = (y_pred > self.threshold).astype(int)
        self.intersection += (pred_bin * y_true).sum()
        self.union += ((pred_bin + y_true) > 0).sum()
        self.total_pred += pred_bin.sum()
        self.total_true += y_true.sum()
        self.pixel_correct += (pred_bin == y_true).sum()
        self.pixel_total += y_true.size

    def compute(self):
        iou = self.intersection / (self.union + 1e-8)
        dice = 2 * self.intersection / (self.total_pred + self.total_true + 1e-8)
        return {
            'iou': iou,
            'dice': dice,
            'pixel_accuracy': self.pixel_correct / self.pixel_total
        }

    def reset(self):
        self.intersection = self.union = 0
        self.total_pred = self.total_true = 0
        self.pixel_correct = self.pixel_total = 0

回归指标

回归任务常用指标:

MSE=1ni=1n(yiy^i)2,MAE=1ni=1nyiy^iMSE = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2,\quad MAE = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i|

R2=1(yiy^i)2(yiyˉ)2R^2 = 1 - \frac{\sum(y_i - \hat{y}_i)^2}{\sum(y_i - \bar{y})^2}

class RegressionMetrics(BaseMetric):
    def __init__(self):
        self.reset()

    def update(self, y_pred, y_true):
        self.se += ((y_pred - y_true) ** 2).sum()
        self.ae += np.abs(y_pred - y_true).sum()
        self.n += y_true.size
        self.y_true_all = np.append(self.y_true_all, y_true.flatten())

    def compute(self):
        mse = self.se / self.n
        mae = self.ae / self.n
        ss_res = self.se
        ss_tot = ((self.y_true_all - self.y_true_all.mean()) ** 2).sum()
        r2 = 1 - ss_res / (ss_tot + 1e-8)
        return {'mse': mse, 'mae': mae, 'r2': r2}

    def reset(self):
        self.se = self.ae = self.n = 0
        self.y_true_all = np.array([])

设计要点总结

  1. 统一接口update / compute / reset 三元组适配训练循环,支持逐 batch 累积后统一计算;
  2. 数值稳定:分母加 1e-8 避免除零,尤其在类别不平衡时至关重要;
  3. 可扩展性:新增指标只需继承基类并实现三个方法,不影响现有代码;
  4. 与 PyTorch 兼容:实际使用时可将 numpy 替换为 torch 张量操作,保持梯度流。

一个设计良好的指标系统能让实验迭代更高效,也便于团队协作中统一评估标准。