文章目录
想象你在训练一个医疗诊断模型。如果数据中健康样本占95%,患病样本占5%,一个简单的模型只要总是预测"健康",就能达到95%的准确度。但这个模型实际上毫无用处,因为它无法识别任何患病案例。
我们需要从多个角度评估模型性能,包括准确度、对数损失、AUC、混淆矩阵、分类报告等。这些指标能够从不同维度反映模型的真实表现。
一、分类准确度(Accuracy)的不足
分类准确度是最直观的评估指标,衡量模型预测正确的样本占总样本的比例。它通过简单的公式
正确预测样本数 / 总样本数
来计算。
# 导入数据
filename = 'pima_data.csv'
names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class']
data = read_csv(filename, names=names)
# 将数据分为输入数据和输出结果
array = data.values
X = array[:, 0:8]
Y = array[:, 8]
num_folds = 10
seed = 7
kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)
model = LogisticRegression()
# 默认值就是准确性
# result = cross_val_score(model, X, Y, cv=kfold, scoring='precision')
result = cross_val_score(model, X, Y, cv=kfold)
print("算法评估结果准确度:%.3f%% (%.3f%%)" % (result.mean(), result.std()))
但是当数据类别不平衡时,比如当数据中某个类别占绝大多数时,准确度会严重误导。模型只要总是预测多数类,就能获得高准确度,但完全无法识别少数类。这意味着模型在真正重要的少数类上表现极差。所以准确度虽然直观,但在类别不平衡时容易严重误导。
因此,我们需要结合精确率、召回率、F1值等其他指标,才能全面评估模型的真实性能。在实际应用中,要根据业务场景选择合适的评估指标。
二、 对数损失函数(Log Loss):概率的准确性
在实际应用中,我们不仅需要知道"是或否",还需要知道"有多确信"。高概率预测比低概率预测更可靠,对数损失能够捕捉这种差异,即:对数损失关注的是 概率的准确性,而非简单的对错判断。想象有两个模型都诊断"患病",但一个90%确信,一个60%确信,对数损失表明对前者更有信心。
1、 原理说明
1.1、数学实现
对数损失函数基于最大似然估计原理,通过计算预测概率与真实标签之间的差异来评估模型:
L o g L o s s = − 1 / N ∗ Σ [ y i ∗ l o g ( p i ) + ( 1 − y i ) ∗ l o g ( 1 − p i ) ] Log Loss = -1/N * Σ[y_i * log(p_i) + (1-y_i) * log(1-p_i)] LogLoss=−1/N∗Σ[yi∗log(pi)+(1−yi)∗log(1−pi)]
其中:
- y i y_i yi:真实标签(0或1)
- p i p_i pi:模型预测的正类概率
N
:样本数量
(LogLoss)损失值代表了模型预测概率与真实标签之间的不匹配程度,损失越小代表预测越准确,损失值量化了模型预测概率的准确性,是评估模型性能的重要指标。
另外,对数损失与交叉熵在二分类问题中本质相同,都是衡量预测概率分布与真实分布之间的差异。因此,在逻辑回归等使用交叉熵作为损失函数的模型中,对数损失自然成为评估指标的首选。
1.2、对数损失是连续、易感知的
准确度是离散的——当概率从0.4提升至0.6时,准确度可能维持不变,导致模型无法感知这一改进。
而对数损失是连续的,任何概率变动都会影响损失值。 在模型训练过程中,对数损失能让模型感知每一次微小改进:从0.4到0.6再到0.8,损失值会持续下降,为模型提供精细的优化反馈。
1.3、假阳性(误判)与假阴性(漏判)
假阳性是模型预测为正类,但实际是负类的错误。 在医疗诊断中,假阳性意味着模型预测患者"患病",但患者实际健康。从数学角度看,当真实标签为0(负类)时,如果模型预测概率大于0.5,就会预测为正类,从而产生假阳性错误。
假阴性是模型预测为负类,但实际是正类的错误。 继续医疗诊断的例子,假阴性意味着模型预测患者"健康",但患者实际患病。数学上,当真实标签为1(正类)时,如果模型预测概率小于0.5,就会预测为负类,从而产生假阴性错误。
假阳性是"误报",假阴性是"漏报",对数损失同时惩罚两者,确保模型在训练过程中学会平衡这两种错误类型,避免偏向某一类。
避免偏向:如果只惩罚假阳性,模型会变得"保守",倾向于预测负类(0);如果只惩罚假阴性,模型会变得"激进",倾向于预测正类(1)。
2、代码实现
from pandas import read_csv
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
filename = 'pima_data.csv' # 糖尿病数据集文件名
names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class']
data = read_csv(filename, names=names)
array = data.values # 将DataFrame转换为numpy数组
X = array[:, 0:8] # 提取前8列作为特征(输入数据)
Y = array[:, 8] # 提取第9列作为目标变量(输出结果)
# 设置交叉验证参数
num_folds = 10 # 10折交叉验证
seed = 7 # 随机种子,确保结果可重现
kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)
model = LogisticRegression() # 创建逻辑回归模型
# 设置评估指标
scoring = 'neg_log_loss' # 使用负对数损失作为评估指标(scikit-learn返回负值)
# 执行交叉验证
result = cross_val_score(model, X, Y, cv=kfold, scoring=scoring) # 进行10折交叉验证
# 输出结果
print('Logloss %.3f (%.3f)' % (result.mean(), result.std())) # 打印平均对数损失和标准差
# Logloss -0.494 (0.061)
损失值范围:对数损失越小越好,理想情况下接近0。
- 优秀:< 0.3
- 良好:0.3 - 0.5
- 一般:0.5 - 0.7
- 较差:> 0.7
三、 AUC与ROC曲线
1、核心概念
真正率(TPR)和 假正率(FPR)
正类:目标类别,通常是少数类或重要事件。 比如:医疗诊断:患病(正类)、垃圾邮件:垃圾邮件(正类)、信用卡欺诈:欺诈交易(正类)。
负类:非目标类别,通常是多数类或正常事件。 医疗诊断:健康(负类); 垃圾邮件:正常邮件(负类); 信用卡欺诈:正常交易(负类)。
真正率
定义:在所有实际正类中,被正确预测为正类的比例。例子:在100个实际患病者中,模型正确识别出80个,真正率就是80%。这意味着模型能够发现80%的病例。
假正率
定义:在所有实际负类中,被错误预测为正类的比例。例子:在100个实际健康人中,模型错误判断10个为患病,假正率就是10%。这意味着模型会误诊10%的健康人。
ROC曲线
ROC的定义与曲线的构建
ROC曲线以真正率(TPR)为纵轴,假正率(FPR)为横轴的曲线。其中,真正率关注**“发现能力”**,假正率关注 “误判风险” 。
ROC曲线通过调整分类阈值来构建。当阈值从0变化到1时,模型会输出不同的真正率和假正率组合,这些点连成曲线就是ROC曲线。
什么是阈值,我们继续使用医生诊断病情来说明:
保守医生(高阈值):只有非常确定才说"有病",漏诊概率相对较高(真正率低),误诊概率低(假正率低),在ROC曲线上的左下角;
激进医生(低阈值):稍有怀疑就说"有病",漏诊概率低(真正率高),但是经常误诊(假正率高) ,在ROC曲线上的右上角;
优秀医生:平衡的敏感度和特异度,在ROC曲线上,靠近左上角。
AUC面积
AUC是ROC曲线下方的面积,取值范围在0.5到1之间。AUC越大,表明模型的分类能力越强。
AUC可以理解为:从正负样本中各随机选择一个样本,模型正确排序这两个样本的概率。
AUC = 0.5:像抛硬币一样随机判断
AUC = 0.7:有一定的区分能力
AUC = 0.9:优秀的分类能力
AUC = 1.0:完美分类,从不犯错
AUC值量化了模型的整体区分能力。AUC=0.5意味着模型没有区分能力(相当于随机猜测),AUC=1意味着完美分类。
AUC不受类别不平衡影响,能够全面评估模型在不同阈值下的表现。这意味着无论正负样本比例如何,AUC都能给出可靠的性能评估。
2、实际应用
医疗诊断:平衡敏感性和特异性
在疾病诊断中,医生需要平衡敏感性和特异性。高敏感性确保不遗漏患者,高特异性避免误诊。ROC曲线帮助选择最优阈值,AUC评估模型的整体诊断能力。
具体例子:
- 癌症筛查:宁可误诊也不漏诊(高TPR优先)
- 常规体检:避免过度诊断(低FPR优先)
金融风控:平衡收益和风险
在信用评估中,银行需要平衡放贷收益和违约风险。ROC曲线帮助确定合适的信用评分阈值,AUC评估模型的整体风险识别能力。
具体例子:
- 信用卡审批:严格控制风险(低FPR优先)
- 小额贷款:扩大客户群体(平衡TPR和FPR)
3、代码实现
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt
# 获取模型预测概率
y_scores = model.predict_proba(X_test)[:, 1]
# 计算ROC曲线
fpr, tpr, _ = roc_curve(y_test, y_scores)
# 计算AUC值
roc_auc = auc(fpr, tpr)
# 绘制ROC曲线
plt.plot(fpr, tpr, label='ROC曲线 (AUC = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], 'k--', label='随机分类器')
plt.xlabel('假正率 (FPR)')
plt.ylabel('真正率 (TPR)')
plt.title('ROC曲线')
plt.legend()
plt.show()
- **优秀**:AUC > 0.9
- **良好**:0.8 < AUC ≤ 0.9
- **一般**:0.7 < AUC ≤ 0.8
- **较差**:AUC ≤ 0.7
四、 混淆矩阵
传统评估方法只看总体准确率,就像只看班级平均分一样,掩盖了每个学生的具体情况。混淆矩阵则像一份详细的成绩单,告诉我们:谁考得好,谁考得差,谁容易混淆,谁需要重点辅导。
核心概念:分类结果的"体检报告"
生活化比喻:混淆矩阵就像医院的体检报告单。报告上不仅告诉你"健康"或"不健康",还会详细列出:哪些人真的健康被正确识别了,哪些人真的生病但被误判为健康了,哪些人其实健康但被误判为生病了,哪些人真的生病被正确识别了。
核心定义:混淆矩阵是一个二维表格,行表示真实情况,列表示预测结果, 每个格子里的数字告诉我们"真实为A类但被预测为B类的样本有多少个"。
重要性说明:它让我们能够精确定位模型的"弱点"——哪些类别容易被误判,哪些错误最频繁,从而有针对性地改进模型。
 ;
工作原理:从"猜拳游戏"理解
工作流程
- 收集预测结果:模型对每个样本做出预测
- 对比真实标签:将预测结果与真实标签一一对应
- 统计分类情况:数出每种"预测-真实"组合的样本数量
- 填入矩阵表格:按行列规则填入对应位置
矩阵说明
行表示真实:每行代表一个真实类别,列表示预测:每列代表一个预测类别。对角线是正确:对角线上的数字表示正确分类的样本数; 非对角线是错误:其他位置的数字表示错误分类的样本数。
与简单准确率相比,混淆矩阵就像从"只看总分"升级到"看各科成绩":简单准确率:只告诉你"对了多少题"; 混淆矩阵:告诉你"哪道题做对了,哪道题做错了"
实际应用:从代码到理解
基础实现
from sklearn.metrics import confusion_matrix
import numpy as np
# 模拟一个简单的二分类结果
y_true = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1] # 真实标签
y_pred = [0, 1, 0, 0, 0, 1, 0, 1, 1, 1] # 预测标签
# 计算混淆矩阵
cm = confusion_matrix(y_true, y_pred)
print("混淆矩阵:")
print(cm)
**结果解释**:
[[4 1] # 真实为0的样本:4个被正确预测为0,1个被错误预测为1
[1 4]] # 真实为1的样本:1个被错误预测为0,4个被正确预测为1
这意味着:
- 5个真实为0的样本中,4个被正确识别,1个被误判为1
- 5个真实为1的样本中,4个被正确识别,1个被误判为0
可视化展示
# ============================================================================
# 导入必要的库和模块
# ============================================================================
# pandas用于数据处理和分析
from pandas import read_csv
import pandas as pd
# sklearn用于机器学习
from sklearn.model_selection import train_test_split # 数据分割工具
from sklearn.linear_model import LogisticRegression # 逻辑回归模型
from sklearn.metrics import confusion_matrix # 混淆矩阵评估
# ============================================================================
# 第一步:数据加载和预处理
# ============================================================================
# 指定数据文件路径(皮马印第安人糖尿病数据集)
filename = 'pima_data.csv'
# 定义数据列名(特征和目标变量)
names = [
'preg', # 怀孕次数
'plas', # 血糖浓度 (mg/dL)
'pres', # 舒张压 (mm Hg)
'skin', # 三头肌皮褶厚度 (mm)
'test', # 2小时血清胰岛素 (mu U/ml)
'mass', # 体重指数 (kg/m²)
'pedi', # 糖尿病家族史函数
'age', # 年龄 (岁)
'class' # 目标变量:是否患糖尿病 (0=否, 1=是)
]
# 读取CSV文件,使用自定义列名
data = read_csv(filename, names=names)
# ============================================================================
# 第二步:数据准备 - 分离特征和目标变量
# ============================================================================
# 将pandas数据框转换为numpy数组,便于数值计算
array = data.values
# 提取特征变量X(前8列):用于预测的输入特征
X = array[:, 0:8] # 选择第0到第7列(索引0-7)
# 提取目标变量Y(第9列):要预测的结果
Y = array[:, 8] # 选择第8列(索引8)
# ============================================================================
# 第三步:数据分割 - 分为训练集和测试集
# ============================================================================
# 设置测试集比例(33%用于测试,67%用于训练)
test_size = 0.33
# 设置随机种子,确保结果可重现
seed = 4
# 使用sklearn的train_test_split函数分割数据
# 注意:这里有个拼写错误,Y_traing应该是Y_train
X_train, X_test, Y_traing, Y_test = train_test_split(
X, Y, # 输入特征和目标变量
test_size=test_size, # 测试集比例
random_state=seed # 随机种子
)
# ============================================================================
# 第四步:模型训练
# ============================================================================
# 创建逻辑回归模型实例
model = LogisticRegression()
# 使用训练数据训练模型
# 模型会学习特征与目标变量之间的关系
model.fit(X_train, Y_traing) # 注意:这里使用的是错误的变量名
# ============================================================================
# 第五步:模型预测
# ============================================================================
# 使用训练好的模型对测试集进行预测
predicted = model.predict(X_test)
# predicted包含每个测试样本的预测结果:
# 0 = 预测为"无糖尿病"
# 1 = 预测为"有糖尿病"
# ============================================================================
# 第六步:模型评估 - 计算混淆矩阵
# ============================================================================
# 计算混淆矩阵,比较预测结果与真实标签
matrix = confusion_matrix(Y_test, predicted)
# 定义类别标签(用于显示)
classes = ['0', '1'] # 0=无糖尿病,1=有糖尿病
# 将混淆矩阵转换为pandas数据框,便于查看
dataframe = pd.DataFrame(
data=matrix, # 混淆矩阵数据
index=classes, # 行标签(真实类别)
columns=classes # 列标签(预测类别)
)
# 打印混淆矩阵结果
print(dataframe)
# ============================================================================
# 混淆矩阵解读说明
# ============================================================================
"""
混淆矩阵格式说明:
预测
真实 0 1
0 TN FP (真负例 假正例)
1 FN TP (假负例 真正例)
其中:
- TN (True Negative): 真负例 - 正确预测为无糖尿病
- FP (False Positive): 假正例 - 错误预测为有糖尿病
- FN (False Negative): 假负例 - 错误预测为无糖尿病
- TP (True Positive): 真正例 - 正确预测为有糖尿病
在医疗场景中:
- FN (假负例) 最危险:有糖尿病但被误判为无糖尿病
- FP (假正例) 次之:无糖尿病但被误判为有糖尿病
"""
0 1
0 150 21
1 28 55
应用场景:从理论到实践
-
在医疗诊断中,漏诊(假阴性) 比 误诊(假阳性) 更危险,因为漏诊可能导致延误治疗时机,威胁患者生命安全。因此,医疗AI系统需要重点关注假阴性率,通过调整模型阈值来平衡敏感性和特异性,确保不遗漏任何潜在患者。
-
垃圾邮件检测中,误判正常邮件(假阳性) 比 漏掉垃圾邮件(假阴性) 更影响用户体验,因为用户可能错过重要的商务邮件或个人消息,造成信息丢失。因此,系统需要优先降低假阳性率,提高精确率,确保正常邮件准确送达。
-
电商推荐系统需要在推荐精确度和用户发现新商品体验之间找到平衡。过度精确可能导致信息茧房,过于宽泛则降低用户信任度。因此,系统需要根据业务目标调整策略,既要保证推荐相关性,又要维持适度多样性,在用户满意度和商业收益间实现最优配置。
五. 分类报告(Classification Report)
分类报告是评估分类模型性能的详细报告,为每个类别提供精确率、召回率、F1分数和支持度等关键指标。
1、生成分类报告
工作流程
- 收集预测结果:模型对所有测试样本进行预测
- 按类别分组:将预测结果按真实类别分组
- 计算各项指标:为每个类别计算精确率、召回率等
- 生成详细报告:汇总所有类别的性能指标
2、关键名词
-
精确率(Precision)
精确率衡量的是所有被模型预测为某类别的样本中,实际真正属于该类别的比例。
例如,在肿瘤检测中,模型预测为“恶性”的病例中,有多少比例是真的恶性。精确率高,说明模型“下结论”时比较谨慎,误报(假阳性)较少。
公式:Precision = TP / (TP + FP) -
召回率(Recall)
召回率衡量的是所有真实属于某类别的样本中,被模型成功识别出来的比例。
例如,在疾病筛查中,所有真正患病的患者中,有多少被模型检测出来。召回率高,说明模型漏报(假阴性)较少,覆盖面广。
公式:Recall = TP / (TP + FN) -
F1分数(F1-score)
F1分数是精确率和召回率的调和平均数,用于综合评价模型的准确性和完整性。
当精确率和召回率存在权衡时,F1分数能反映两者的整体表现。F1分数越高,说明模型在“准确”和“全面”之间取得了较好平衡。
公式:F1 = 2 × (Precision × Recall) / (Precision + Recall) -
支持度(Support)
支持度指的是测试集中每个类别的真实样本数量。
它反映了该类别在评估数据中的出现频次。支持度高的类别,其评估指标更具统计意义;支持度低的类别,指标可能波动较大。
举例:如果测试集中有30个正类样本和70个负类样本,那么正类的支持度是30,负类的支持度是70。
缩写 | 英文全称 | 中文含义 | 具体解释 |
---|---|---|---|
TP | True Positive | 真正例 | 真实为正类,模型也预测为正类。例如:有病的人被正确预测为有病。 |
FP | False Positive | 假正例 | 真实为负类,模型却预测为正类。例如:没病的人被误判为有病。 |
FN | False Negative | 假负例 | 真实为正类,模型却预测为负类。例如:有病的人被误判为没病。 |
TN | True Negative | 真负例 | 真实为负类,模型也预测为负类。例如:没病的人被正确预测为没病。 |
代码实例
```python
# ============================================================================
# 导入必要的库和模块
# ============================================================================
# pandas用于数据处理和分析
from pandas import read_csv
# sklearn用于机器学习
from sklearn.model_selection import train_test_split # 数据分割工具
from sklearn.linear_model import LogisticRegression # 逻辑回归模型
from sklearn.metrics import classification_report # 分类报告评估
# ============================================================================
# 第一步:数据加载和预处理
# ============================================================================
# 指定数据文件路径(皮马印第安人糖尿病数据集)
filename = 'pima_data.csv'
# 定义数据列名(特征和目标变量)
names = [
'preg', # 怀孕次数
'plas', # 血糖浓度 (mg/dL)
'pres', # 舒张压 (mm Hg)
'skin', # 三头肌皮褶厚度 (mm)
'test', # 2小时血清胰岛素 (mu U/ml)
'mass', # 体重指数 (kg/m²)
'pedi', # 糖尿病家族史函数
'age', # 年龄 (岁)
'class' # 目标变量:是否患糖尿病 (0=否, 1=是)
]
# 读取CSV文件,使用自定义列名
data = read_csv(filename, names=names)
# ============================================================================
# 第二步:数据准备 - 分离特征和目标变量
# ============================================================================
# 将pandas数据框转换为numpy数组,便于数值计算
array = data.values
# 提取特征变量X(前8列):用于预测的输入特征
X = array[:, 0:8] # 选择第0到第7列(索引0-7)
# 提取目标变量Y(第9列):要预测的结果
Y = array[:, 8] # 选择第8列(索引8)
# ============================================================================
# 第三步:数据分割 - 分为训练集和测试集
# ============================================================================
# 设置测试集比例(33%用于测试,67%用于训练)
test_size = 0.33
# 设置随机种子,确保结果可重现
seed = 4
# 使用sklearn的train_test_split函数分割数据
# 注意:这里有个拼写错误,Y_traing应该是Y_train
X_train, X_test, Y_traing, Y_test = train_test_split(
X, Y, # 输入特征和目标变量
test_size=test_size, # 测试集比例
random_state=seed # 随机种子
)
# ============================================================================
# 第四步:模型训练
# ============================================================================
# 创建逻辑回归模型实例
model = LogisticRegression()
# 使用训练数据训练模型
# 模型会学习特征与目标变量之间的关系
model.fit(X_train, Y_traing) # 注意:这里使用的是错误的变量名
# ============================================================================
# 第五步:模型预测
# ============================================================================
# 使用训练好的模型对测试集进行预测
predicted = model.predict(X_test)
# predicted包含每个测试样本的预测结果:
# 0 = 预测为"无糖尿病"
# 1 = 预测为"有糖尿病"
# ============================================================================
# 第六步:生成分类报告
# ============================================================================
# 生成详细的分类报告,比较预测结果与真实标签
report = classification_report(Y_test, predicted)
# 打印分类报告结果
print(report)
# ============================================================================
# 分类报告结果详解
# ============================================================================
"""
分类报告输出结果:
precision recall f1-score support
0.0 0.84 0.88 0.86 171
1.0 0.72 0.66 0.69 83
accuracy 0.81 254
macro avg 0.78 0.77 0.78 254
weighted avg 0.80 0.81 0.80 254
结果解读:
1. 类别0(无糖尿病)表现:
- 精确率 = 0.84:预测为"无糖尿病"的样本中,84%是真正无糖尿病的
- 召回率 = 0.88:真实无糖尿病的样本中,88%被正确识别
- F1分数 = 0.86:精确率和召回率的平衡表现
- 支持度 = 171:测试集中有171个无糖尿病的样本
2. 类别1(有糖尿病)表现:
- 精确率 = 0.72:预测为"有糖尿病"的样本中,72%是真正有糖尿病的
- 召回率 = 0.66:真实有糖尿病的样本中,66%被正确识别
- F1分数 = 0.69:精确率和召回率的平衡表现
- 支持度 = 83:测试集中有83个有糖尿病的样本
3. 整体性能:
- 准确率 = 0.81:整体准确率为81%
- 数据不平衡:无糖尿病样本(67%) vs 有糖尿病样本(33%)
4. 关键问题:
- 假阴性率 = 34%:34%的糖尿病患者被漏诊(医疗风险高)
- 假阳性率 = 28%:28%的健康人被误判(心理负担)
- 类别不平衡:少数类表现较差
5. 业务建议:
- 医疗场景:优先提高召回率,减少漏诊风险
- 模型优化:使用类别权重、调整阈值、尝试其他算法
- 风险评估:34%的漏诊率在医疗场景中不可接受
"""
实践建议与总结
- 准确度:直观但需结合业务场景谨慎使用,适合类别平衡的场景
- 对数损失:关注概率输出质量,对模型调参具有重要指导意义
- AUC:提供整体区分能力判断,特别适合类别不平衡场景
- 混淆矩阵:帮助定位模型在哪些类别上表现不足
- 分类报告:提供每个类别的详细性能指标