写在前面
到此为止,我们几乎已经大致了解了所有常用的模型。但是有一个拖欠了很久的问题,对于这些模型,我们到底怎么评估他的好坏?这篇博文就将对分类、回归模型的评价指标作介绍。
- 1.python基础;
- 2.ai模型概念+基础;
- 3.数据预处理;
- 4.机器学习模型--1.编码(嵌入);2.聚类;3.降维;4.回归(预测);5.分类;
- 5.正则化技术;
- 6.神经网络模型--1.概念+基础;2.几种常见的神经网络模型;
- 7.对回归、分类模型的评价方式;
- 8.简单强化学习概念;
- 9.几种常见的启发式算法及应用场景;
- 10.机器学习延申应用-数据分析相关内容--1.A/B Test;2.辛普森悖论;3.蒙特卡洛模拟;
- 11.数据挖掘--关联规则挖掘
- 12.数学建模--决策分析方法,评价模型
- 13.主动学习(半监督学习)
- 以及其他的与人工智能相关的学习经历,如数据挖掘、计算机视觉-OCR光学字符识别、大模型等。
目录
评估方法
现实任务中往往会对学习器的泛化性能、时间开销、存储开销、可解释性等方面的因素进行评估并做出选择。
我们假设:测试集是从样本真实分布中独立采样获得,将测试集上的“测试误差”作为泛化误差的近似,
- 所以测试集要和训练集中的样本尽量互斥。
通常将包含m个样本的数据集D拆分成训练集S和测试集T,常见方法:
- 留出法:直接将数据集划分为两个互斥集合,在S上训练,训练/测试集划分要尽可能保持数据分布的一致性,一般若干次随机划分、重复实验取平均值。
from sklearn.model_selection import train_test_split X = ... # 特征数据 y = ... # 标签数据 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
- 交叉验证法:将数据集分层采样划分为k个大小相似的互斥子集,每次用k-1个子集的并集作为训练集,余下的子集作为测试集,最终返回k个测试结果的均值,k最常用的取值是10。
from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier X = ... # 特征数据 y = ... # 标签数据 model = RandomForestClassifier() scores = cross_val_score(model, X, y, cv=5) # 5折交叉验证 print(scores)
- 留一法:与留出法类似,将数据集D划分为k个子集同样存在多种划分方式,为了减小因样本划分不同而引入的差别,k折交叉验证通常随机使用不同的划分重复p次,最终的评估结果是这p次k折交叉验证结果的均值,例如常见的“10次10折交叉验证”。
from sklearn.model_selection import LeaveOneOut from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier X = ... # 特征数据 y = ... # 标签数据 model = RandomForestClassifier() loo = LeaveOneOut() scores = cross_val_score(model, X, y, cv=loo) print(scores)
但是:
- 留出法和交叉验证法: 由于保留了一部分样本用于测试,因此实际评估的模型所使用的训练级比D小,这必然会引入一些因训练样本规模不同而导致的估计偏差。
- 留一法:受训练样本规模变化的影响较小,但计算复杂度太高了。
- 以自助采样法为基础,对数据集D有放回采样m次得到训练集D',D/D'用做测试集,生成多个训练集。
from sklearn.utils import resample X = ... # 特征数据 y = ... # 标签数据 # 从原始数据集抽取一个Bootstrap样本 X_resampled, y_resampled = resample(X, y, replace=True, random_state=42)
当然,在实际应用中最常用的还是留出法train_test_split()函数。
分类模型的性能度量
错误率
m个样本中有a个样本分类错误,E=a/m。
误差:样本真实输出与预测输出之间的差异。
- 泛化误差:除训练集外所有样本, 新样本上的误差。
- 泛化性能是由学习算法的能力、数据的充分性以及学习任务本身的难度所共同决定的。给定学习任务为了取得好的泛化性能,需要使偏差小(充分拟合数据)而且方差较小(减少数据扰动产生的影响)。
- 测试性能并不等于泛化性能
- 测试性能随着测试集的变化而变化
- 很多机器学习算法本身有一定的随机性
我们希望泛化误差小,但由于事先并不知道新样本的特征,我们只能努力使经验误差最小化;
- 经验误差就有过拟合和欠拟合的问题(具体可见本专栏《4.4分类》)
运用到概率论与数理统计中二项检验、T检验、MCNEMAR检验、FRIEDMAN检验、NEMENYI后续检验、偏差、方差、噪声等概念来进行比较检验。
混淆矩阵
混淆矩阵用于分类任务,展示模型预测的四种可能结果:
- TP (True Positive): 预测为正,实际为正。
- TN (True Negative): 预测为负,实际为负。
- FP (False Positive): 预测为正,实际为负(误报)。
- FN (False Negative): 预测为负,实际为正(漏报)。
通过混淆矩阵可以直观地分析模型的误差类型和分类性能。
很多时候我们有多个二分类的混淆矩阵,例如:
- 进行多次训练或测试,每次得到一个混淆矩阵;
- 或者是在多个数据集上进行测试或训练,希望估计算法的全局性能;
- 或者是执行多分类任务,每两两类别的组合都对应一个混淆矩阵…
总之我们希望在n个二分类混淆矩阵上综合考察查准率和查全率
代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
# 生成一个简单的分类数据集
X, y = make_classification(n_samples=100, n_features=4, n_classes=2, random_state=42)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 创建并训练一个简单的逻辑回归模型
model = LogisticRegression()
model.fit(X_train, y_train)
# 预测测试集
y_pred = model.predict(X_test)
# 计算混淆矩阵
cm = confusion_matrix(y_test, y_pred)
# 可视化混淆矩阵
disp = ConfusionMatrixDisplay(confusion_matrix=cm)
disp.plot()
# 显示图像
plt.show()
输出:
指标
1. 准确率(Accuracy)
正确预测的样本占总样本的比例。它反映了模型的整体预测性能,但在类别不平衡时可能不太可靠。
2. 精确率(Precision)查准率
正确预测的正类样本占所有预测为正类样本的比例。高精确率意味着模型在预测正类时有很少的误报。
3. 召回率(Recall)查全率
正确预测的正类样本占所有真实正类样本的比例。高召回率表示模型能够识别大部分正类样本。
查准率和查全率一般是一对矛盾的度量。
根据学习器的预测结果, 按正例可能性大小对样例进行排序,并逐个把样本作为正例进行预测,则可以得到查准率-查全率曲线,简称“P-R曲线”。
- 若一个学习器的P-R曲线被另外一个的P-R曲线完全包住,则可断言后者的性能优于前者,如图, 学习器A的性能优于学习器C;
- 如果两个学习器的P-R曲线发生了交叉,如图A和B,则难以一般性的断言两者孰优孰劣,只能在具体的查准率和查全率条件下进行比较。
- 然而在很多情形下,人们往往仍希望把A与B比出个高低,这时一个比较合理的依据就是:比较P-R曲线下面积的大小,他在一定程度上表征了学习器在查准率和查全率上取得相对双高的比例,但这个值不太容易评估算,因此人们设计的一套综合查考虑查准率,查询率的性能度量:平衡点。
按正例可能性大小对样例进行排序:排在 最前面 是学习器认为“最可能”是正例的样本,排在 最后的 则是学习器认为“最不可能”是正例的样本。并逐个把样本作为正例进行预测。
- 若更重视“查准率”,则可选择排序中靠前的位置进行截断;
- 若更重视“查全率”,则可选择靠后的位置来进行截断;
4. F1 Score
精确率和召回率的调和平均,用于综合考虑这两个指标。适用于类别不平衡的数据。
宏/微查准率、查全率、F1
多个混淆矩阵时:
5. AUC(Area Under Curve)
ROC曲线下的面积,衡量分类器的整体性能。值越接近1表示模型性能越好。
ROC曲线
受试者工作特征曲线,描绘真阳性率(召回率)与假阳性率(1 - 特异性)之间的关系,展示模型在不同阈值下的表现。
- 纵轴是:“真正例率”(True Positive Rate,简称TPR),TP/(TP+FN),即R,查全率。
- 横轴是:“假正例率”(False Positive Rate,简称FPR),TP/(TN+FP),即P,查准率。
在非均等代价下,ROC曲线不能直接反映出学习器的期望总体代价,而“代价曲线”可以。
代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, auc
# 生成一个简单的分类数据集
X, y = make_classification(n_samples=1000, n_features=10, n_classes=2, random_state=42)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 创建并训练一个逻辑回归模型
model = LogisticRegression()
model.fit(X_train, y_train)
# 获取模型预测的概率值
y_scores = model.predict_proba(X_test)[:, 1]
# 计算ROC曲线
fpr, tpr, thresholds = roc_curve(y_test, y_scores)
# 计算AUC值
roc_auc = auc(fpr, tpr)
# 绘制ROC曲线
plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc='lower right')
plt.show()
输出:
理想模型:
回归模型的性能度量
1. 均方误差(MSE)
预测值与真实值差的平方的平均数。
2. 均方根误差(RMSE)
MSE的平方根,表示模型误差的标准差。更直观地反映误差的大小。
3. 平均绝对误差(MAE)
预测值与真实值差的绝对值的平均数。
4. 决定系数(R²)
衡量模型解释方差的能力,范围在0到1之间,越接近1表示模型越好。
敏感性分析
在大多数情况下,“敏感性分析”和“灵敏度分析”是指同一种分析方法,其英文都是 sensitivity analysis。这两种说法在中文文献和应用中通常可以互换使用,其目的是评估模型或系统中各个输入参数的不确定性如何影响输出结果,从而判断哪些参数对结果最敏感。
常见于经济模型、工程设计、环境模型、风险评估等多个领域。可以通过局部敏感性分析(例如,计算偏导数或局部扰动)或全局敏感性分析(例如,采用方差分解方法或蒙特卡洛模拟)来进行。
全局敏感性分析-PAWN方法
PAWN 方法是一种全局敏感性分析方法,用于评估模型输出对各个输入参数变化的敏感性。其基本思想是比较当某个输入变量取固定值时模型输出的条件分布与不固定该输入变量时模型输出的无条件分布之间的差异。较大的差异说明该输入参数对模型输出具有较大影响。
无条件与条件分布
-
无条件分布
:指模型输出 Y 在所有输入变量取值下的累积分布函数(CDF)。
-
条件分布
:指在固定某个输入变量
时,模型输出 Y 的累积分布函数。
差异度量
PAWN 方法通过计算条件分布和无条件分布之间的差异来反映敏感性。常用的度量指标是Kolmogorov-Smirnov (KS) 距离(具体可见本专栏《4.1聚类》)其定义为:
其中, 表示对所有 y 值取最大差异。对于每个输入参数
和其取值 x,计算出的
越大,说明在该点上输入参数对输出分布的影响越显著。
全局敏感性指标
为了全面评估某个输入变量的敏感性,PAWN 方法通常会:
- 在输入变量的取值范围内选取多个代表性值(例如通过等距分布采样)。
- 对于每个固定值 x,计算 KS 距离
。
- 最终可能综合这些距离(如取最大值、均值或者其他统计量)来量化该输入变量的敏感性。
例如,一个全局敏感性指标可以定义为:
或
其中, 为 Xi 的一组选定值。
以下是一段使用GBR(梯度提升回归)模型对特征进行PAWN敏感性分析的代码:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_split
# 数据路径
file_path = r'.\data.csv'
# 读取数据
data = pd.read_csv(file_path)
features = ['Event_Num', 'Athlete_Score', 'Historical_Score', 'Is_Home']
target = 'Total'
# 分离数据集
# X_train, X_test, y_train, y_test = train_test_split(data[features], data[target], test_size=0.3, random_state=42)
train_data = data[(data['Year'] >= 1948) & (data['Year'] <= 2004)]
test_data = data[(data['Year'] >= 2008) & (data['Year'] <= 2024)]
X_train = train_data[features]
y_train = train_data[target]
X_test = test_data[features]
y_test = test_data[target]
# 模型训练
model = GradientBoostingRegressor(n_estimators=100, max_depth=3)
model.fit(X_train, y_train)
# 定义用于PAWN敏感性分析的函数
def pawn_sensitivity_analysis(data, feature, model, num_samples=10):
feature_values = np.linspace(data[feature].min(), data[feature].max(), num_samples)
cdfs = []
for value in feature_values:
# 创建修改后的特征集
temp_data = data.copy()
temp_data[feature] = value
# 预测
predictions = model.predict(temp_data[features])
sorted_predictions = np.sort(predictions)
cdf = np.arange(len(sorted_predictions)) / float(len(sorted_predictions))
cdfs.append((sorted_predictions, cdf))
# 绘制CDF图
plt.figure(figsize=(10, 6))
for i, (sorted_predictions, cdf) in enumerate(cdfs):
plt.plot(sorted_predictions, cdf, label=f'{feature}={feature_values[i]:.2f}')
plt.title(f'Cumulative Distribution Functions (CDFs) for different values of {feature}')
plt.xlabel('Predicted Total')
plt.ylabel('CDF')
plt.legend()
plt.grid(True)
plt.show()
# 对每个数值特征执行PAWN分析
for feature in ['Event_Num', 'Athlete_Score', 'Historical_Score']:
pawn_sensitivity_analysis(data, feature, model)
-
采样输入值:
np.linspace
用于在输入特征的最小值与最大值之间均匀生成num_samples
个代表性值,这相当于在该变量的取值范围内进行离散采样。 -
初始化 CDF 集合:
cdfs
用于保存每个固定值下预测输出的累积分布函数。 -
固定输入变量:对于循环中的每个特定的
value
,复制原始数据data
,并将所分析的feature
的所有取值替换为当前的value
。这相当于构造条件分布:在该特征固定为某个值时,观察其它特征不变的情况下模型预测的变化。 -
模型预测与排序:利用修改后的数据进行预测,并将预测结果排序。排序后的预测值用来构造累积分布函数(CDF)。
-
计算 CDF:通过
np.arange(len(sorted_predictions)) / float(len(sorted_predictions))
构造出经验 CDF。这里的做法是:对于排序后的第 iii 个预测值,其 CDF 值为 i/ni/ni/n,其中 nnn 是样本总数。 -
保存结果:每个特定值对应的 (sorted_predictions,cdf)(sorted\_predictions, cdf)(sorted_predictions,cdf) 被保存到
cdfs
列表中。
结果就是会生成对于每个特征的如下图:
每条线代表仅变化该特征,其他特征不变的回归分析预计结果。
局部敏感性分析
局部敏感性分析(Local Sensitivity Analysis)主要关注在模型输入参数的某个固定点附近,模型输出对输入变化的响应程度。这种方法通常用于探究在某一参考点(例如,最佳拟合点、当前操作点或基准状态)附近,小幅度的输入扰动如何影响模型输出。
与全局敏感性分析关注整个参数空间的影响不同,局部敏感性分析只在某个局部区域内进行分析,通常假设模型在该区域内是可微的。常见的应用场景包括:
- 对模型输出在某一固定操作点的鲁棒性分析;
- 识别哪些输入参数在当前状态下对输出影响最大,从而指导参数校准或优化。
一阶导数法(梯度敏感性分析)
在可微模型中,可以通过计算输出 y 关于输入参数 xi 的偏导数来评估敏感性。设模型为
局部敏感性指标为:
其中 x0 为参考点。偏导数的绝对值越大,表示在该参考点上,xi 的微小扰动对输出y的影响越大。
假设我们的模型为一个简单的二次函数:
我们选择参考点 (x1,x2)=(1.0,1.0),对每个参数施加小扰动,然后观察输出变化。下面给出 Python 代码示例,并利用 Matplotlib 进行可视化。
import numpy as np
import matplotlib.pyplot as plt
# 定义模型函数
def model(x1, x2):
return x1**2 + 2*x2
# 参考点(基准状态)
x1_0, x2_0 = 1.0, 1.0
y0 = model(x1_0, x2_0)
# 定义扰动幅度(可以调整)
delta = 0.1
# 分别对x1和x2进行局部敏感性分析
# 对 x1 进行扰动:固定 x2 = 1.0
x1_values = np.linspace(x1_0 - 3*delta, x1_0 + 3*delta, 100)
y_x1 = model(x1_values, x2_0)
# 对 x2 进行扰动:固定 x1 = 1.0
x2_values = np.linspace(x2_0 - 3*delta, x2_0 + 3*delta, 100)
y_x2 = model(x1_0, x2_values)
# 绘制 x1 扰动下的敏感性曲线
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(x1_values, y_x1, label='Model output vs. $x_1$')
plt.axvline(x=x1_0, color='r', linestyle='--', label='Reference $x_1$')
plt.axhline(y=y0, color='gray', linestyle='--', label='Baseline output')
plt.xlabel('$x_1$')
plt.ylabel('Output $y$')
plt.title('Local Sensitivity Analysis:fixed $x_2=1.0$')
plt.legend()
plt.grid(True)
# 绘制 x2 扰动下的敏感性曲线
plt.subplot(1, 2, 2)
plt.plot(x2_values, y_x2, label='Model output vs. $x_2$', color='orange')
plt.axvline(x=x2_0, color='r', linestyle='--', label='Reference $x_2$')
plt.axhline(y=y0, color='gray', linestyle='--', label='Baseline output')
plt.xlabel('$x_2$')
plt.ylabel('Output $y$')
plt.title('Local Sensitivity Analysis:fixed $x_1=1.0$')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# 计算局部敏感性指标(数值近似导数)
sensitivity_x1 = (model(x1_0 + delta, x2_0) - model(x1_0, x2_0)) / delta
sensitivity_x2 = (model(x1_0, x2_0 + delta) - model(x1_0, x2_0)) / delta
print(f"Local Sensitivity Analysis Indicator:∂y/∂x1 ≈ {sensitivity_x1:.3f}")
print(f"Local Sensitivity Analysis Indicator:∂y/∂x2 ≈ {sensitivity_x2:.3f}")
-
模型定义:
我们定义了一个简单的二次模型: -
参考点:
选择 x1=1.0 和 x2=1.0 作为基准状态,对应的输出。
-
扰动与数据生成:
- 对 x1 在参考点附近([1−0.3,1+0.3])生成100个值,保持 x2 固定,从而得到一组输出 y。
- 对 x2 同样操作,固定 x1=1.0。
-
绘图:
分别绘制 x1 和 x2 扰动下模型输出随输入变化的曲线。参考值通过虚线标记。 -
局部敏感性指标计算:
使用一阶差分近似导数计算局部敏感性:输出的数值指标说明在基准点上,哪个输入变量的微小变化会引起更大的输出变化。
Local Sensitivity Analysis Indicator:∂y/∂x1 ≈ 2.100
Local Sensitivity Analysis Indicator:∂y/∂x2 ≈ 2.000
单变量扰动(One-At-A-Time, OAT)法
这种方法逐一对每个输入参数施加小幅度的扰动,同时保持其他参数固定不变,观察模型输出的变化。这种方法直观且易于实现,尤其适用于黑盒模型或数值模型。
总结
本文详细介绍了常用的模型评估方法,包括测试集的选择、交叉验证方法、分类模型性能度量以及回归模型性能度量。评估方法帮助我们衡量模型的泛化能力、计算复杂度和存储开销等。主要的评估方法有留出法、交叉验证法、留一法和自助法等,每种方法都有其优缺点。对于分类模型的评估,文中讲解了多种性能指标,如准确率、精确率、召回率、F1分数、AUC值等,并通过混淆矩阵来直观展示模型的预测结果。对于回归模型,均方误差(MSE)、均方根误差(RMSE)、平均绝对误差(MAE)和决定系数(R²)被用来衡量模型的表现。并介绍了敏感性分析。总之,评估方法根据不同的任务需求可以有不同的侧重点,选择合适的评估指标对于比较模型的效果至关重要。