外部数据验证与测试集评估:模型泛化能力的关键一环

大模型机器学习关系型数据库

picture.image

背景

外部验证是机器学习中的一种方法,用于评估模型在真实世界中的表现,它是指使用模型在未参与过模型训练或调参过程的数据集上进行评估,以确保模型的泛化能力和稳健性,这个过程帮助我们了解模型是否能对新数据做出准确的预测,避免过拟合问题

外部验证的思想

外部验证的核心思想是通过引入新数据,即模型之前没有见过的数据,来评估模型的性能。这种方式模仿了模型部署后的实际使用场景,确保模型不仅仅在训练数据上表现好,而是能够在完全未知的数据上也有良好的表现

外部验证的流程通常如下:

  • 训练模型:在训练数据上建立模型
  • 外部验证:在完全独立的外部数据集(不包含在训练数据或验证数据中的数据)上测试模型的性能,评估其泛化能力

这种验证方式特别适合于需要高准确性和强泛化能力的场景,如医学诊断模型、金融预测模型等

测试集和外部验证的区别

测试集是机器学习过程中用于评估模型性能的数据集,但它与外部验证的数据集有所不同,主要区别在于以下几点:

测试集:

  • 通常是在数据划分过程中,从原始数据中分割出来的一部分数据(如原始数据的20%)用于最终的模型性能评估
  • 它虽然在训练时没有被使用,但它与训练集同属于相同的数据来源,这可能导致它与训练数据存在一定的相似性
  • 测试集仍然是一种内部评估的手段,因为它来源于同一分布的原始数据集

外部验证:

  • 外部验证的数据集来自完全不同的数据来源,与训练数据集可能有不同的分布和特征。比如在不同的时间段、不同的地理位置或其他不同的条件下收集的数据
  • 外部验证比测试集更能反映模型在真实环境中的表现,因为它能衡量模型是否能很好地泛化到新的数据
  • 它通常在测试集之后进行,以确保在不同场景下模型的适应能力

案例

  • 测试集:你用一个数据集训练了一个房价预测模型,把数据集的20%留作测试集,这个数据来自同一个城市、同一时间段,模型在测试集上的表现良好
  • 外部验证:你用来自另一个城市的数据进行外部验证,看看模型在这个不同城市的房价数据上表现如何,这更接近于模型在实际使用中的场景,因为真实世界中的数据往往是变化的

总结来说,测试集是模型内部评估的重要部分,而外部验证则更注重评估模型在不同数据分布下的表现,是更广泛意义上的模型性能测试方法

代码实现

数据读取并分割


          
import pandas as pd
          
import numpy as np
          
import matplotlib.pyplot as plt
          
from sklearn.model_selection import train_test_split
          
plt.rcParams['font.family'] = 'Times New Roman'
          
plt.rcParams['axes.unicode_minus'] = False
          
df = pd.read_csv('模型数据.csv')
          
# 划分特征和目标变量
          
X = df.drop(['P class'], axis=1)
          
y = df['P class']
          
# 划分训练集和测试集
          
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, 
          
                                                    random_state=42, stratify=df['P class'])
          
df
      

picture.image

读取的CSV数据集中分离出特征变量和目标变量,然后将数据集按照80%的训练集和20%的测试集进行划分,并确保P class列的分类比例在划分中保持一致

模型构建


          
from sklearn.ensemble import RandomForestClassifier
          
# 创建随机森林分类器实例,并设置参数
          
rf_classifier = RandomForestClassifier(
          
    n_estimators=100,          # 树的数量,默认为100。
          
    criterion='gini',          # 'criterion'参数指定用于拆分的质量指标。可以选择'gini'(基尼系数)或'entropy'(信息增益)。
          
    max_depth=7,               # 'max_depth'限制每棵树的最大深度。None表示不限制深度。
          
    min_samples_split=2,       # 节点分裂所需的最小样本数,默认为2。
          
    min_samples_leaf=1,        # 叶子节点所需的最小样本数,默认为1。
          
    min_weight_fraction_leaf=0.0,  # 与'min_samples_leaf'类似,但基于总样本权重,默认为0.0。
          
    random_state=42,           # 控制随机数生成,以便结果可复现。
          
    max_leaf_nodes=None,       # 限制每棵树的最大叶子节点数。None表示不限制。
          
    min_impurity_decrease=0.0  # 分裂节点时要求的最小不纯度减少量。
          
)
          
# 训练模型
          
rf_classifier.fit(X_train, y_train)
      

picture.image

创建并配置一个随机森林分类器,然后使用训练数据X_train和y_train对模型进行训练

测试集上的模型评价

分类模型评估报告生成


          
from sklearn.metrics import classification_report
          
# 预测测试集
          
y_pred = rf_classifier.predict(X_test)
          
# 输出模型报告, 查看评价指标
          
print(classification_report(y_test, y_pred))
      

picture.image

使用训练好的随机森林模型对测试集X_test进行预测,生成预测结果y_pred,然后通过classification_report函数对比真实标签y_test和预测标签y_pred,输出模型的评价指标,如精确率、召回率、F1值等,用于衡量模型的分类性能

混淆矩阵热力图


          
from sklearn.metrics import confusion_matrix
          
import seaborn as sns
          
# 输出混淆矩阵
          
conf_matrix = confusion_matrix(y_test, y_pred)
          
# 绘制热力图
          
plt.figure(figsize=(10, 7), dpi=1200)
          
sns.heatmap(conf_matrix, annot=True, annot_kws={'size':15}, 
          
            fmt='d', cmap='YlGnBu', cbar_kws={'shrink': 0.75})
          
plt.xlabel('Predicted Label', fontsize=12)
          
plt.ylabel('True Label', fontsize=12)
          
plt.title('Confusion matrix heat map', fontsize=15)
          
plt.savefig('Confusion matrix heat map.pdf', format='pdf', bbox_inches='tight')
          
plt.show()
      

picture.image

使用confusion_matrix函数根据真实标签y_test和预测标签y_pred计算混淆矩阵, 使用seaborn库中的heatmap函数将混淆矩阵绘制成热力图,显示每个类别的真实值和预测值之间的对应关系,图中标注了每个单元格的数值

多分类ROC绘制


          
from sklearn import metrics
          
from sklearn.preprocessing import label_binarize
          
# 预测并计算概率
          
ytest_proba_rf = rf_classifier.predict_proba(X_test)
          
# 将y标签转换成one-hot形式
          
ytest_one_rf = label_binarize(y_test, classes=[0, 1, 2])
          
# 宏平均法计算AUC
          
rf_AUC = {}
          
rf_FPR = {}
          
rf_TPR = {}
          
for i in range(ytest_one_rf.shape[1]):
          
    rf_FPR[i], rf_TPR[i], thresholds = metrics.roc_curve(ytest_one_rf[:, i], ytest_proba_rf[:, i])
          
    rf_AUC[i] = metrics.auc(rf_FPR[i], rf_TPR[i])
          
print(rf_AUC)
          
# 合并所有的FPR并排序去重
          
rf_FPR_final = np.unique(np.concatenate([rf_FPR[i] for i in range(ytest_one_rf.shape[1])]))
          
# 计算宏平均TPR
          
rf_TPR_all = np.zeros_like(rf_FPR_final)
          
for i in range(ytest_one_rf.shape[1]):
          
    rf_TPR_all += np.interp(rf_FPR_final, rf_FPR[i], rf_TPR[i])
          
rf_TPR_final = rf_TPR_all / ytest_one_rf.shape[1]
          
# 计算最终的宏平均AUC
          
rf_AUC_final = metrics.auc(rf_FPR_final, rf_TPR_final)
          
AUC_final_rf = rf_AUC_final  # 最终AUC
          
print(f"Macro Average AUC with Random Forest: {AUC_final_rf}")
      

picture.image

类别 0 的 AUC 值为 0.8478,表示模型在类别 0 上的性能, 类别 1 的 AUC 值为 0.7821,表示模型在类别 1 上的性能,类别 2 的 AUC 值为 0.9127,表示模型在类别 2 上的性能,宏平均 AUC:Macro Average AUC with Random Forest: 0.8519585664677686,表示对所有类别的AUC取平均值,衡量模型在所有类别上的整体表现


          
plt.figure(figsize=(10, 10), dpi=1200)
          
# 使用不同的颜色和线型
          
plt.plot(rf_FPR[0], rf_TPR[0], color='#1f77b4', linestyle='-', label='Class 0 ROC  AUC={:.4f}'.format(rf_AUC[0]), lw=2)
          
plt.plot(rf_FPR[1], rf_TPR[1], color='#ff7f0e', linestyle='-', label='Class 1 ROC  AUC={:.4f}'.format(rf_AUC[1]), lw=2)
          
plt.plot(rf_FPR[2], rf_TPR[2], color='#2ca02c', linestyle='-', label='Class 2 ROC  AUC={:.4f}'.format(rf_AUC[2]), lw=2)
          
# 宏平均ROC曲线
          
plt.plot(rf_FPR_final, rf_TPR_final, color='#000000', linestyle='-', label='Macro Average ROC  AUC={:.4f}'.format(rf_AUC_final), lw=3)
          
# 45度参考线
          
plt.plot([0, 1], [0, 1], color='gray', linestyle='--', lw=2, label='45 Degree Reference Line')
          
plt.xlabel('False Positive Rate (FPR)', fontsize=15)
          
plt.ylabel('True Positive Rate (TPR)', fontsize=15)
          
plt.title('Random Forest Classification ROC Curves and AUC', fontsize=18)
          
plt.grid(linestyle='--', alpha=0.7)
          
plt.legend(loc='lower right', framealpha=0.9, fontsize=12)
          
plt.savefig('RF_optimized.pdf', format='pdf', bbox_inches='tight')
          
plt.show()
      

picture.image

使用随机森林模型进行分类后的ROC曲线绘制及AUC(曲线下面积)的计算,最终输出每个类别和宏平均(macro-average)的AUC值,并将ROC曲线图保存为PDF文件,这里具体的多分类ROC曲线绘制参考往期文章——多分类如何绘制ROC曲线--宏平均ROC曲线

外部验证——外部数据上的模型评价


          
data = pd.read_csv('外部数据.csv')
          
data
      

picture.image

代码的目的是将外部数据集加载进来,为了对之前训练好的模型进行外部验证,评估模型在新数据上的表现


          
predictions = rf_classifier.predict(data.drop(['P class'], axis=1)) # 对外部数据进行预测
          
# 输出模型报告, 查看评价指标
          
print(classification_report(data['P class'], predictions))
      

picture.image

首先使用训练好的随机森林模型rf_classifier对外部数据集(去除了目标变量P class后)的特征进行预测,并生成预测结果predictions,然后通过classification_report函数将模型的预测结果与外部数据的真实标签P class进行对比,输出一份详细的分类报告,包括精确率、召回率、F1值等指标,用于评估模型在外部数据上的性能表现


          
conf_matrix = confusion_matrix(data['P class'], predictions)
          
# 绘制热力图
          
plt.figure(figsize=(10, 7), dpi=1200)
          
sns.heatmap(conf_matrix, annot=True, annot_kws={'size':15}, 
          
            fmt='d', cmap='YlGnBu', cbar_kws={'shrink': 0.75})
          
plt.xlabel('Predicted Label', fontsize=12)
          
plt.ylabel('True Label', fontsize=12)
          
plt.title('Confusion Matrix Heat Map (External Data)', fontsize=15)
          
plt.savefig('Confusion Matrix Heat Map (External Data).pdf', format='pdf', bbox_inches='tight')
          
plt.show()
      

picture.image

首先计算外部数据集的真实标签P class与模型预测结果predictions之间的混淆矩阵conf_matrix,接着使用seaborn库的heatmap函数绘制该混淆矩阵的热力图,并设置图形尺寸、颜色映射、标注字体大小和坐标轴标签,最终保存生成的混淆矩阵热力图为名为“Confusion Matrix Heat Map (External Data).pdf”的PDF文件,并将图形显示出来,用以直观展示模型在外部数据集上的分类表现


          
# 预测并计算概率
          
ytest_proba_rf = rf_classifier.predict_proba(data.drop(['P class'], axis=1))
          
# 将y标签转换成one-hot形式
          
ytest_one_rf = label_binarize(data['P class'], classes=[0, 1, 2])
          
# 宏平均法计算AUC
          
rf_AUC = {}
          
rf_FPR = {}
          
rf_TPR = {}
          
for i in range(ytest_one_rf.shape[1]):
          
    rf_FPR[i], rf_TPR[i], thresholds = metrics.roc_curve(ytest_one_rf[:, i], ytest_proba_rf[:, i])
          
    rf_AUC[i] = metrics.auc(rf_FPR[i], rf_TPR[i])
          
print(rf_AUC)
          
# 合并所有的FPR并排序去重
          
rf_FPR_final = np.unique(np.concatenate([rf_FPR[i] for i in range(ytest_one_rf.shape[1])]))
          
# 计算宏平均TPR
          
rf_TPR_all = np.zeros_like(rf_FPR_final)
          
for i in range(ytest_one_rf.shape[1]):
          
    rf_TPR_all += np.interp(rf_FPR_final, rf_FPR[i], rf_TPR[i])
          
rf_TPR_final = rf_TPR_all / ytest_one_rf.shape[1]
          
# 计算最终的宏平均AUC
          
rf_AUC_final = metrics.auc(rf_FPR_final, rf_TPR_final)
          
AUC_final_rf = rf_AUC_final  # 最终AUC
          
plt.figure(figsize=(10, 10), dpi=1200)
          
# 使用不同的颜色和线型
          
plt.plot(rf_FPR[0], rf_TPR[0], color='#1f77b4', linestyle='-', label='Class 0 ROC  AUC={:.4f}'.format(rf_AUC[0]), lw=2)
          
plt.plot(rf_FPR[1], rf_TPR[1], color='#ff7f0e', linestyle='-', label='Class 1 ROC  AUC={:.4f}'.format(rf_AUC[1]), lw=2)
          
plt.plot(rf_FPR[2], rf_TPR[2], color='#2ca02c', linestyle='-', label='Class 2 ROC  AUC={:.4f}'.format(rf_AUC[2]), lw=2)
          
# 宏平均ROC曲线
          
plt.plot(rf_FPR_final, rf_TPR_final, color='#000000', linestyle='-', label='Macro Average ROC  AUC={:.4f}'.format(rf_AUC_final), lw=3)
          
# 45度参考线
          
plt.plot([0, 1], [0, 1], color='gray', linestyle='--', lw=2, label='45 Degree Reference Line')
          
plt.xlabel('False Positive Rate (FPR)', fontsize=15)
          
plt.ylabel('True Positive Rate (TPR)', fontsize=15)
          
plt.title('Random Forest Classification ROC Curves and AUC (External Data)', fontsize=18)
          
plt.grid(linestyle='--', alpha=0.7)
          
plt.legend(loc='lower right', framealpha=0.9, fontsize=12)
          
plt.savefig('RF_optimized_(External Data).pdf', format='pdf', bbox_inches='tight')
          
plt.show()
      

picture.image

同样使用训练好的随机森林模型对外部数据进行预测,生成预测概率,然后将真实标签转换为one-hot形式,并通过循环计算每个类别的ROC曲线(FPR和TPR)及对应的AUC值,接着合并所有类别的FPR并计算宏平均TPR,最终得到宏平均AUC;接下来,代码绘制各类别的ROC曲线以及宏平均ROC曲线,添加图例、标签和45度参考线,最后将该图保存为PDF文件并显示出来,目的是评估模型在外部数据集上的整体分类性能

总结

实际上代码的原理与在测试集上的模型评估非常类似,主要区别在于评估对象是外部数据,而非之前的数据划分,通过外部数据进行验证,进一步检查模型的泛化能力和在新数据上的表现。关键步骤依然包括生成预测概率、计算ROC曲线及AUC、绘制图表,唯一的变化是所使用的验证数据源不同,即外部验证数据替代了测试集,尽管本项目的示例是基于多分类任务,但其核心原理可以轻松扩展到二分类任务和回归任务。这些任务的评估方式在概念上是相同的,区别主要在于具体的评价指标和方法存在区别,最后确保外部数据和训练数据在特征、分布、预处理等方面保持一致性是外部验证有效性的基础。如果外部数据的特征或分布与训练数据不一致,模型可能无法在外部数据上表现出与训练时相同的效果,导致不准确的评估结果,因此,模型在验证时应该特别注意这些一致性问题,以确保其能够在新数据上有效泛化

往期推荐

SCI图表复现:整合数据分布与相关系数的高级可视化策略

复现顶刊Streamlit部署预测模型APP

树模型系列:如何通过XGBoost提取特征贡献度

不止 SHAP 力图:LIME 实现任意黑盒模型的单样本解释

特征选择:Lasso和Boruta算法的结合应用

从基础到进阶:优化SHAP力图,让样本解读更直观

SCI图表复现:优化SHAP特征贡献图展示更多模型细节

多模型中的特征贡献度比较与可视化图解

从零开始:手把手教你部署顶刊机器学习在线预测APP并解读模型结果

picture.image

picture.image

picture.image

微信号|deep_ML

欢迎添加作者微信进入Python、ChatGPT群

进群请备注Python或AI进入相关群

无需科学上网、同步官网所有功能、使用无限制

如果你对类似于这样的文章感兴趣。

欢迎关注、点赞、转发~

个人观点,仅供参考

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论