准确率计算:ML-From-Scratch模型评估指标实现
你是否在实现机器学习模型时,经常困惑于如何正确评估模型性能?当训练完一个分类器后,仅仅看"准确率(Accuracy)"真的足够吗?本文将带你深入理解ML-From-Scratch项目中评估指标的实现原理,掌握从混淆矩阵到F1分数的完整计算逻辑,让你的模型评估更加专业可靠。
读完本文你将获得:
- 混淆矩阵(Confusion Matrix)的底层实现逻辑
- 精确率(Precision)与召回率(Recall)的数学原理与NumPy实现
- F1分数(F1-Score)的调和平均计算方法
- 多类别分类场景下的指标计算技巧
- 完整的模型评估流水线代码示例
1. 评估指标的重要性:为什么准确率不等于一切
在机器学习任务中,模型评估是验证算法有效性的关键环节。以二分类问题为例,我们通常将样本分为"正类"(Positive)和"负类"(Negative),模型预测结果与实际标签的组合形成四种情况,这就是混淆矩阵(Confusion Matrix) 的基础。
1.1 单指标陷阱与多指标体系
准确率(Accuracy)定义为所有正确预测样本占总样本的比例:
accuracy = (TP + TN) / (TP + TN + FP + FN)
但在实际应用中,单一准确率指标存在显著局限性:
| 场景 | 准确率问题 | 解决方案 |
|---|---|---|
| 不平衡数据 | 99%负样本时,无脑预测负例也能得99%准确率 | 使用精确率/召回率 |
| 风险敏感任务 | 疾病检测中漏诊(假负例)代价远高于误诊 | 优先关注召回率 |
| 多类别分类 | 无法反映各类别表现差异 | 宏平均/微平均指标 |
ML-From-Scratch项目采用模块化设计,将各类评估指标封装在data_operation.py中,形成完整的指标计算体系。
2. 混淆矩阵:评估指标的基础构建块
混淆矩阵是所有分类评估指标的基础。在ML-From-Scratch项目中,混淆矩阵的实现位于mlfromscratch/utils/data_operation.py文件,核心代码如下:
def confusion_matrix(y_true, y_pred, normalize=False):
"""
计算混淆矩阵
Parameters:
-----------
y_true : array-like, shape = [n_samples]
真实标签
y_pred : array-like, shape = [n_samples]
预测标签
normalize : bool, optional (default=False)
是否归一化混淆矩阵
Returns:
--------
array, shape = [n_classes, n_classes]
混淆矩阵
"""
# 获取所有唯一类别
classes = np.unique(np.concatenate((y_true, y_pred)))
n_classes = len(classes)
# 初始化混淆矩阵
cm = np.zeros((n_classes, n_classes), dtype=np.int64)
# 计算混淆矩阵
for i in range(len(y_true)):
true_class = np.where(classes == y_true[i])[0][0]
pred_class = np.where(classes == y_pred[i])[0][0]
cm[true_class, pred_class] += 1
# 归一化处理
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
return cm
这段代码实现了混淆矩阵的核心功能:
- 自动识别所有类别标签
- 初始化n×n的混淆矩阵(n为类别数)
- 通过循环统计每个类别的预测情况
- 支持归一化选项,方便比较不同样本量的模型
2.1 二分类混淆矩阵可视化
对于二分类问题,混淆矩阵呈现为2×2矩阵:
使用上述函数计算该示例的混淆矩阵:
y_true = np.array([1, 0, 1, 1, 0, 1, 0, 0, 1, 0] * 10) # 100个样本
y_pred = np.array([1, 0, 1, 1, 1, 0, 0, 0, 1, 0] * 10) # 模拟预测结果
cm = confusion_matrix(y_true, y_pred)
print(cm)
# 输出:
# [[40 5]
# [10 45]]
3. 核心评估指标的数学原理与实现
3.1 准确率(Accuracy)
准确率是最直观的评估指标,表示所有预测正确的样本占总样本的比例:
def accuracy_score(y_true, y_pred):
"""
计算准确率
Parameters:
-----------
y_true : array-like, shape = [n_samples]
真实标签
y_pred : array-like, shape = [n_samples]
预测标签
Returns:
--------
float
准确率
"""
return np.mean(y_true == y_pred)
在上述二分类示例中:
accuracy = accuracy_score(y_true, y_pred)
print(f"准确率: {accuracy:.2f}") # 输出: 准确率: 0.85
但准确率在不平衡数据集上会产生误导。例如,当99%的样本为负例时,即使模型全部预测为负例,准确率也能达到99%,但这样的模型毫无实用价值。
3.2 精确率(Precision)与召回率(Recall)
精确率(查准率)衡量的是预测为正例的样本中,真正为正例的比例;召回率(查全率)衡量的是所有真实正例中,被成功预测的比例:
def precision_score(y_true, y_pred, average='binary'):
"""
计算精确率
Parameters:
-----------
y_true : array-like, shape = [n_samples]
真实标签
y_pred : array-like, shape = [n_samples]
预测标签
average : str, optional (default='binary')
多类别问题的平均方式:
- 'binary': 二分类问题
- 'micro': 计算全局精确率
- 'macro': 计算各类别精确率的算术平均
- 'weighted': 计算加权平均
Returns:
--------
float
精确率
"""
cm = confusion_matrix(y_true, y_pred)
precision = []
for i in range(cm.shape[0]):
tp = cm[i, i]
fp = np.sum(cm[:, i]) - tp
if tp + fp == 0:
precision.append(0.0)
else:
precision.append(tp / (tp + fp))
if average == 'binary':
return precision[1] # 二分类问题返回正例的精确率
elif average == 'micro':
return np.sum(np.diag(cm)) / np.sum(cm)
elif average == 'macro':
return np.mean(precision)
elif average == 'weighted':
support = np.sum(cm, axis=1)
return np.sum(np.array(precision) * support) / np.sum(support)
else:
raise ValueError(f"Invalid average: {average}")
def recall_score(y_true, y_pred, average='binary'):
"""
计算召回率
Parameters:
-----------
y_true : array-like, shape = [n_samples]
真实标签
y_pred : array-like, shape = [n_samples]
预测标签
average : str, optional (default='binary')
多类别问题的平均方式
Returns:
--------
float
召回率
"""
cm = confusion_matrix(y_true, y_pred)
recall = []
for i in range(cm.shape[0]):
tp = cm[i, i]
fn = np.sum(cm[i, :]) - tp
if tp + fn == 0:
recall.append(0.0)
else:
recall.append(tp / (tp + fn))
if average == 'binary':
return recall[1] # 二分类问题返回正例的召回率
elif average == 'micro':
return np.sum(np.diag(cm)) / np.sum(cm)
elif average == 'macro':
return np.mean(recall)
elif average == 'weighted':
support = np.sum(cm, axis=1)
return np.sum(np.array(recall) * support) / np.sum(support)
else:
raise ValueError(f"Invalid average: {average}")
在上述二分类示例中:
precision = precision_score(y_true, y_pred) # TP/(TP+FP) = 45/(45+5) = 0.90
recall = recall_score(y_true, y_pred) # TP/(TP+FN) = 45/(45+10) = 0.82
print(f"精确率: {precision:.2f}, 召回率: {recall:.2f}")
3.3 F1分数:精确率与召回率的调和平均
F1分数是精确率和召回率的调和平均数,用于综合评价模型性能:
def f1_score(y_true, y_pred, average='binary'):
"""
计算F1分数
Parameters:
-----------
y_true : array-like, shape = [n_samples]
真实标签
y_pred : array-like, shape = [n_samples]
预测标签
average : str, optional (default='binary')
多类别问题的平均方式
Returns:
--------
float
F1分数
"""
precision = precision_score(y_true, y_pred, average=average)
recall = recall_score(y_true, y_pred, average=average)
if precision + recall == 0:
return 0.0
return 2 * (precision * recall) / (precision + recall)
在上述二分类示例中:
f1 = f1_score(y_true, y_pred) # 2*(0.90*0.82)/(0.90+0.82) ≈ 0.86
print(f"F1分数: {f1:.2f}")
3.4 多类别分类的评估策略
对于多类别分类问题,ML-From-Scratch提供了三种平均策略:
-
宏平均(Macro-average): 计算每个类别的指标,然后取算术平均
precision_macro = precision_score(y_true, y_pred, average='macro') -
微平均(Micro-average): 将所有类别合并计算总体指标
precision_micro = precision_score(y_true, y_pred, average='micro') -
加权平均(Weighted-average): 根据每个类别的样本数量加权平均
precision_weighted = precision_score(y_true, y_pred, average='weighted')
三种策略的适用场景对比:
| 平均策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 宏平均 | 平等对待所有类别 | 受小类别影响大 | 类别分布均匀 |
| 微平均 | 反映总体分类效果 | 忽视类别不平衡 | 类别重要性相同 |
| 加权平均 | 考虑类别样本量 | 大类别主导结果 | 类别分布不均 |
4. 模型评估完整流水线
ML-From-Scratch项目中,通常将评估指标集成到模型训练流程中:
from mlfromscratch.supervised_learning import LogisticRegression
from mlfromscratch.utils import train_test_split, accuracy_score, f1_score
# 1. 准备数据
X, y = load_dataset() # 加载数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# 2. 训练模型
model = LogisticRegression()
model.fit(X_train, y_train)
# 3. 模型预测
y_pred = model.predict(X_test)
# 4. 多指标评估
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
# 5. 输出评估报告
print(f"模型评估报告:")
print(f"准确率: {accuracy:.4f}")
print(f"精确率: {precision:.4f}")
print(f"召回率: {recall:.4f}")
print(f"F1分数: {f1:.4f}")
4.1 评估指标在分类模型中的应用
以逻辑回归模型为例,完整的训练与评估流程:
5. 常见问题与解决方案
5.1 类别不平衡问题
当数据集中类别分布不均时,准确率会产生误导。解决方案:
- 使用精确率、召回率和F1分数作为主要指标
- 对少数类进行过采样或多数类进行欠采样
- 使用AUC-ROC曲线评估整体排序能力
5.2 多标签分类评估
ML-From-Scratch的评估指标支持多标签分类,只需将average参数设为None:
precision_per_class = precision_score(y_true, y_pred, average=None)
for i, score in enumerate(precision_per_class):
print(f"类别 {i} 精确率: {score:.4f}")
5.3 评估指标的性能优化
对于大规模数据集,可使用向量化操作替代循环,提升计算效率:
# 向量化实现混淆矩阵计算(适用于二分类)
def fast_confusion_matrix(y_true, y_pred):
"""快速计算二分类混淆矩阵"""
TP = np.sum((y_true == 1) & (y_pred == 1))
TN = np.sum((y_true == 0) & (y_pred == 0))
FP = np.sum((y_true == 0) & (y_pred == 1))
FN = np.sum((y_true == 1) & (y_pred == 0))
return np.array([[TN, FP], [FN, TP]])
6. 总结与展望
本文详细介绍了ML-From-Scratch项目中模型评估指标的实现原理,从混淆矩阵到F1分数,完整覆盖了分类任务的核心评估方法。通过掌握这些指标的底层实现,你不仅能更好地理解模型性能,还能根据具体任务需求选择合适的评估策略。
回顾本文核心要点:
- 混淆矩阵是所有评估指标的基础,反映了各类别预测的详细情况
- 准确率适合平衡数据集,精确率和召回率适合不平衡数据集
- F1分数是综合评价模型性能的理想指标
- 多类别分类需根据实际需求选择合适的平均策略
未来,ML-From-Scratch项目可能会增加更多高级评估指标,如ROC曲线、PR曲线下面积等,进一步完善模型评估体系。
希望本文能帮助你构建更加科学的模型评估流程,让你的机器学习项目更加专业可靠!如果你觉得本文有帮助,请点赞收藏,并关注项目后续更新。下一篇我们将探讨回归模型的评估指标实现,敬请期待!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



