基于相关性与标准差的多模型评价指标可视化比较 —— 泰勒图应用解析

文摘   2024-09-30 21:11   安徽  

背景

随着机器学习技术的广泛应用,如何对多个模型的性能进行科学合理的可视化评价也是一个有趣的问题,除了常规的评价指标可视化外,泰勒图可作为一种融合相关性与标准差的可视化工具,能够为我们提供直观的模型比较方式

在此背景下,D. Lai等人在期刊《Engineering Applications of Artificial Intelligence》(2024年)中发表的文章,展示了通过 泰勒图 评估多种机器学习模型(如XGBoost、ANN、GPR和NGBoost)在回归任务中的表现,他们通过观察模型预测值与真实数据的标准差和相关性来衡量模型性能,受此启发,本文将基于相似的原理,结合 XGBoost、随机森林(Random Forest) 和 CatBoost 等几种主流回归模型,利用泰勒图进行性能对比分析,带领大家深入了解多模型的可视化评估方法

代码实现

数据读取并分割

import pandas as pdimport numpy as npimport matplotlib.pyplot as pltfrom sklearn.model_selection import train_test_splitplt.rcParams['font.family'] = 'Times New Roman'plt.rcParams['axes.unicode_minus'] = False
df = pd.read_excel('数据.xlsx')
# 划分特征和目标变量X = df.drop(['price'], axis=1)y = df['price']
# 划分训练集和测试集X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

读取 Excel 文件中的数据,并将特征和目标变量进行划分,然后使用 train_test_split 将数据集分为训练集和测试集

XGBoost模型训练并保存评价指标

import xgboost as xgbfrom sklearn.model_selection import GridSearchCV
# XGBoost模型参数params_xgb = { 'learning_rate': 0.02, # 学习率,控制每一步的步长,用于防止过拟合。典型值范围:0.01 - 0.1 'booster': 'gbtree', # 提升方法,这里使用梯度提升树(Gradient Boosting Tree) 'objective': 'reg:squarederror', # 损失函数,这里使用平方误差 'max_leaves': 127, # 每棵树的叶子节点数量,控制模型复杂度。较大值可以提高模型复杂度但可能导致过拟合 'verbosity': 1, # 控制 XGBoost 输出信息的详细程度,0表示无输出,1表示输出进度信息 'seed': 42, # 随机种子,用于重现模型的结果 'nthread': -1, # 并行运算的线程数量,-1表示使用所有可用的CPU核心 'colsample_bytree': 0.6, # 每棵树随机选择的特征比例,用于增加模型的泛化能力 'subsample': 0.7 # 每次迭代时随机选择的样本比例,用于增加模型的泛化能力}
model_xgb = xgb.XGBRegressor(**params_xgb)
# 定义参数网格,用于网格搜索param_grid = { 'n_estimators': [100, 200], # 树的数量,控制模型的复杂度 'max_depth': [3, 4], # 树的最大深度,控制模型的复杂度,防止过拟合 'min_child_weight': [1, 2], # 节点最小权重,值越大,算法越保守,用于控制过拟合}
grid_search = GridSearchCV( estimator=model_xgb, param_grid=param_grid, scoring='neg_root_mean_squared_error', cv=5, n_jobs=-1, verbose=1)

# 训练模型grid_search.fit(X_train, y_train)xgboost = grid_search.best_estimator_
from sklearn.metrics import mean_squared_errorfrom sklearn.metrics import r2_score
# 预测y_pred = xgboost.predict(X_test)
# 计算标准差std_dev_pred = np.std(y_pred)std_dev_obs = np.std(y_test)
# 计算相关系数correlation = np.corrcoef(y_test, y_pred)[0, 1]
# 计算均方根误差 (RMSE)rmse = np.sqrt(mean_squared_error(y_test, y_pred))
# 计算 R2r2 = r2_score(y_test, y_pred)
# 保存为DataFramemetrics_df = pd.DataFrame({ 'Model': ['XGBoost'], 'Standard Deviation (Pred)': [std_dev_pred], 'Standard Deviation (Observed)': [std_dev_obs], 'Correlation': [correlation], 'RMSE': [rmse], 'R2 Score': [r2]})
metrics_df

通过网格搜索对 XGBoost 模型进行超参数优化,训练得到最佳模型后,对测试数据进行预测,并计算和保存各种回归评估指标评估指标详细解释如下

对测试集 X_test 进行预测,并计算以下评价指标:
  • 标准差:通过 np.std 计算模型预测值和真实观测值的标准差,分别用 std_dev_pred 和 std_dev_obs 表示

  • 相关系数:通过 np.corrcoef 计算预测值与真实值之间的相关性

  • 均方根误差:通过 mean_squared_error 计算预测值和真实值之间的误差,衡量模型的精度

  • R²值:通过 r2_score 计算模型的拟合优度,R² 值越接近 1 说明模型的拟合效果越好
这里演示只针对测试集做可视化,文献中针对训练集、验证集、测试集均有做
RF模型训练并保存评价指标
from sklearn.ensemble import RandomForestRegressor# 创建随机森林回归器实例rf_regressor = RandomForestRegressor(    random_state=42,    min_samples_split=2,    min_samples_leaf=1,    criterion='squared_error'  # 对回归来说,默认为'squared_error',即均方误差)
# 定义参数网格,用于网格搜索param_grid = { 'n_estimators': [100, 200], # 森林中树的数量 'max_depth': [None, 10], # 每棵树的最大深度}
# 使用GridSearchCV进行网格搜索和k折交叉验证grid_search_rf = GridSearchCV( estimator=rf_regressor, param_grid=param_grid, scoring='neg_mean_squared_error', # 回归任务中常用的评价指标 cv=5, # 5折交叉验证 n_jobs=-1, # 并行计算 verbose=1 # 输出详细进度信息)
# 训练模型grid_search_rf.fit(X_train, y_train)
# 输出最优参数print("Best parameters found: ", grid_search_rf.best_params_)print("Best negative mean squared error score: ", grid_search_rf.best_score_)
# 使用最优参数训练的模型RF = grid_search_rf.best_estimator_
# 预测y_pred_RF = RF.predict(X_test)
# 计算 RF 模型的评价指标std_dev_pred_RF = np.std(y_pred_RF)correlation_RF = np.corrcoef(y_test, y_pred_RF)[0, 1]rmse_RF = np.sqrt(mean_squared_error(y_test, y_pred_RF))r2_RF = r2_score(y_test, y_pred_RF)
# 创建一个包含 RF 模型评价指标的新 DataFramenew_row = pd.DataFrame({ 'Model': ['RF'], 'Standard Deviation (Pred)': [std_dev_pred_RF], 'Standard Deviation (Observed)': [std_dev_obs], # 这个是一样的 'Correlation': [correlation_RF], 'RMSE': [rmse_RF], 'R2 Score': [r2_RF]})
# 使用 pd.concat 将新行添加到 metrics_df 中metrics_df = pd.concat([metrics_df, new_row], ignore_index=True)metrics_df

这里所有评价指标保存到一个dataframe下方便接下来的可视化
catboost模型训练并保存评价指标
# 导入所需的库from catboost import CatBoostRegressor
# CatBoost模型参数params_catboost = { 'learning_rate': 0.02, # 学习率,控制每一步的步长 'depth': 6, # 树的深度,控制模型复杂度 'loss_function': 'RMSE', # 损失函数,回归任务常用均方根误差 'verbose': 100, # 控制 CatBoost 输出信息的详细程度 'random_seed': 42, # 随机种子,用于重现模型的结果 'thread_count': -1, # 并行运算的线程数量 'subsample': 0.7, # 每次迭代时随机选择的样本比例,用于增加模型的泛化能力 'l2_leaf_reg': 3.0 # L2正则化项的系数,用于防止过拟合}
# 初始化CatBoost回归模型model_catboost = CatBoostRegressor(**params_catboost)
# 定义参数网格,用于网格搜索param_grid_catboost = { 'iterations': [100, 200], # 迭代次数 'depth': [3, 4], # 树的深度 'learning_rate': [0.01, 0.02], # 学习率}
# 使用GridSearchCV进行网格搜索和k折交叉验证grid_search_catboost = GridSearchCV( estimator=model_catboost, param_grid=param_grid_catboost, scoring='neg_mean_squared_error', # 评价指标为负均方误差 cv=5, # 5折交叉验证 n_jobs=-1, # 并行计算 verbose=1 # 输出详细进度信息)
# 训练模型grid_search_catboost.fit(X_train, y_train)
# 输出最优参数print("Best parameters found: ", grid_search_catboost.best_params_)print("Best RMSE score: ", (-grid_search_catboost.best_score_)**0.5)
# 使用最优参数训练模型catboost = grid_search_catboost.best_estimator_
# 使用最优的CatBoost模型对测试集进行预测y_pred_catboost = catboost.predict(X_test)
# 计算CatBoost模型的评价指标std_dev_pred_catboost = np.std(y_pred_catboost)correlation_catboost = np.corrcoef(y_test, y_pred_catboost)[0, 1]rmse_catboost = np.sqrt(mean_squared_error(y_test, y_pred_catboost))r2_catboost = r2_score(y_test, y_pred_catboost)
# 创建一个包含CatBoost模型评价指标的新DataFramenew_row_catboost = pd.DataFrame({ 'Model': ['CatBoost'], 'Standard Deviation (Pred)': [std_dev_pred_catboost], 'Standard Deviation (Observed)': [std_dev_obs], # 这个是一样的 'Correlation': [correlation_catboost], 'RMSE': [rmse_catboost], 'R2 Score': [r2_catboost]})
# 使用 pd.concat 将新行添加到 metrics_df 中metrics_df = pd.concat([metrics_df, new_row_catboost], ignore_index=True)metrics_df

从上述表格中的结果可以看出,在三个模型中,随机森林(RF) 的表现最好,相关系数最高(0.899142),均方根误差(RMSE)最低(0.503940),并且 R² 值(0.806201)也最高。虽然从这个表格中可以判断出哪个模型最优,但随着模型数量的增加,仅通过表格进行对比会变得不够直观。因此,接下来我们将通过可视化手段来更加清晰地展示各个模型的性能差异,使得评估过程更加直观明了

在可视化之前,先重点说明一下相关系数和标准差两个重要指标:
  • 相关系数:它衡量的是模型预测值和真实值之间的线性相关性,取值范围在 -1 到 1 之间。相关系数越接近 1,表示模型预测值与真实值的拟合效果越好,预测越准确,理想的模型应该有接近 1 的相关系数

  • 标准差:标准差衡量的是数据的离散程度,模型的预测标准差应该尽可能接近真实观测值的标准差,如果模型预测的标准差和观测值的标准差越接近,说明模型能够较好地捕捉数据的变化范围
因此,最优的模型 应该是具有较高的相关系数(接近 1)和预测标准差接近观测标准差的模型,在接下来的可视化中,我们将利用这些指标,结合泰勒图等工具,来直观地展示各个模型的表现

初始可视化

def taylor_diagram_with_obs_line(stddev, corrcoef, obs_stddev, models, colors, markers):    """    stddev: 模型预测的标准差列表    corrcoef: 模型的相关系数列表    obs_stddev: 观测值的标准差    models: 模型名称列表    colors: 颜色列表    markers: 标记样式列表    """    # 创建极坐标轴,设置为1/4圆    fig = plt.figure(figsize=(8, 8))    ax = fig.add_subplot(111, polar=True)
# 将相关系数转换为弧度:相关性从0到1 theta = np.arccos(corrcoef)
# 绘制径向网格线:标准差 stddev_ticks = np.arange(0, 1.75, 0.25) ax.set_ylim(0, 1.75) ax.set_yticks(stddev_ticks) ax.set_yticklabels([f'{t:.2f}' for t in stddev_ticks])
# 设置径向网格线标注为标准差 ax.set_rlabel_position(0)
# 绘制角度网格线:相关系数,增加0.95和0.99两个刻度 correlation_ticks = np.concatenate((np.arange(0, 0.91, 0.1), [0.95, 0.99])) ax.set_thetagrids(np.degrees(np.arccos(correlation_ticks)), labels=[f'{c:.2f}' for c in correlation_ticks])
# 绘制观测点 ax.plot(0, obs_stddev, 'ko', label='Observed', markersize=10)
# 绘制每个模型的点 for i, model in enumerate(models): ax.plot(theta[i], stddev[i], color=colors[i], marker=markers[i], label=model, markersize=10)
# 绘制一条从观测值到Y轴的红色虚线半圆 theta_obs = np.linspace(0, np.pi/2, 100) r_obs = np.full_like(theta_obs, obs_stddev) ax.plot(theta_obs, r_obs, 'r--', label=f'Observed = {obs_stddev:.2f}')
# 设置角度范围为1/4圆 (从 0 到 π/2 弧度,即从 1 到 0 的相关性) ax.set_thetalim(0, np.pi/2)
# 添加标题 ax.set_xlabel('Standard Deviation', fontsize=14, labelpad=20) ax.text(np.pi/4, 1.9, 'Correlation', fontsize=14, ha='center')
# 添加图例 plt.legend(loc='upper right') plt.savefig('1.pdf', format='pdf', bbox_inches='tight', dpi=1200) # 显示图形 plt.show()stddev = metrics_df['Standard Deviation (Pred)']corrcoef = metrics_df['Correlation']obs_stddev = std_dev_obsmodels = metrics_df['Model']colors = ['b', 'g', 'r']markers = ['o', 's', 'D']

# 调用函数绘制带obs_stddev虚线半圆的1/4圆Taylor图taylor_diagram_with_obs_line(stddev, corrcoef, obs_stddev, models, colors, markers)

泰勒图的作用是同时展示模型预测与真实值之间的标准差和相关性,具体解释如下:
  • 径向距离(从图中心到点的位置):表示预测值的标准差,越接近红色虚线(观测值的标准差),说明模型的标准差与真实观测值越接近,模型捕捉到数据波动的能力越好

  • 角度:表示相关性,越接近右侧(对应 1 的位置),说明模型预测值与真实值之间的相关性越强,拟合效果越好

  • 黑点:代表真实的观测值位置,标准差为1.144(图中虚线弧对应的红线)

  • 蓝点(XGBoost)、绿点(随机森林,RF)、红点(CatBoost):代表不同模型的预测结果,蓝色、绿色和红色点代表各个模型的标准差和相关性

  • RF(绿色方块):表现最好,它的标准差较为接近真实观测值(1.007050),而且相关性也最高(0.899142),说明模型预测的结果与实际值之间的匹配程度最好

泰勒图是一种有效的工具,可以直观地展示多个模型在预测任务中的表现,通过该图,可以同时比较模型的标准差和相关性,结合图中的结果,可以看到 随机森林(RF) 模型在标准差和相关性上都表现最佳
优化可视化
def taylor_diagram_with_obs_line_and_distances(stddev, corrcoef, obs_stddev, models, colors, markers):    """    stddev: 模型预测的标准差列表    corrcoef: 模型的相关系数列表    obs_stddev: 观测值的标准差    models: 模型名称列表    colors: 颜色列表    markers: 标记样式列表    """    fig = plt.figure(figsize=(8, 8))    ax = fig.add_subplot(111, polar=True)    theta = np.arccos(corrcoef)    stddev_ticks = np.arange(0, 1.75, 0.25)    ax.set_ylim(0, 1.75)    ax.set_yticks(stddev_ticks)    ax.set_yticklabels([f'{t:.2f}' for t in stddev_ticks])    ax.set_rlabel_position(0)    correlation_ticks = np.concatenate((np.arange(0, 0.91, 0.1), [0.95, 0.99]))    ax.set_thetagrids(np.degrees(np.arccos(correlation_ticks)), labels=[f'{c:.2f}' for c in correlation_ticks])    ax.plot(0, obs_stddev, 'ko', label='Observed', markersize=10)
# 绘制每个模型的点并计算距离 for i, model in enumerate(models): ax.plot(theta[i], stddev[i], color=colors[i], marker=markers[i], label=model, markersize=10) # 计算模型点到观测点的距离 distance = np.sqrt((stddev[i] - obs_stddev)**2 + (obs_stddev * np.sin(theta[i]))**2) # 在模型点和观测点之间绘制一条虚线表示距离 ax.plot([0, theta[i]], [obs_stddev, stddev[i]], 'k--', alpha=0.5) # 标注距离值        ax.text((theta[i] + 0) / 2, (stddev[i] + obs_stddev) / 2f'{distance:.2f}', color=colors[i], fontsize=10) theta_obs = np.linspace(0, np.pi/2, 100) r_obs = np.full_like(theta_obs, obs_stddev)    ax.plot(theta_obs, r_obs, 'r--', label=f'Observed = {obs_stddev:.2f}')    ax.set_thetalim(0, np.pi/2) ax.set_xlabel('Standard Deviation', fontsize=14, labelpad=20)    ax.text(np.pi/41.9'Correlation', fontsize=14, ha='center') plt.legend(loc='upper right') plt.savefig('2.pdf', format='pdf', bbox_inches='tight', dpi=1200) plt.show()taylor_diagram_with_obs_line_and_distances(stddev, corrcoef, obs_stddev, models, colors, markers)

这段代码是对之前的泰勒图绘制代码进行优化,加入了 模型预测值与真实观测值之间的距离 计算与可视化,通过在泰勒图中绘制距离虚线,并在图上标注距离值,能够更加直观地对各个模型的预测性能进行量化评估,结合了标准差和相关性的误差,距离越小,表明模型预测值与真实观测值之间的拟合效果越好,模型排名与原始评价指标(如拟合优度)一致,进一步确认了RF表现最佳,其次是XGBoost,最后是CatBoost,但是通过这种可视形式更直观
总结
在本次复现的泰勒图中,虽然通过距离计算和标注直观展示了各模型与观测值的差异,但与文献中的图仍存在一定区别。文献中的泰勒图以 观测值的标准差(obs_stddev) 为圆心,绘制了多个不同半径的圆,直观展示模型标准差与观测值的差异,更加突出模型点与观测标准差之间的相对误差。然而,由于本图采用极坐标系,这种圆形可视化方式无法完美实现,因此引入了欧氏距离来结合标准差和相关性,量化并展示模型与观测值的差异。另外,文献中的观测值标准差统一为 1,表明可能对各数据集进行了标准化处理导致标准差为1,并在接下来的实现中没有进行数据还原。作者认为可视化前应根据具体情况选择是否进行数据标准化或还原,或者直接使用原始数据进行处理,以确保结果的真实性和一致性

往期推荐

SCI图表复现:整合数据分布与相关系数的高级可视化策略
复现顶刊Streamlit部署预测模型APP
树模型系列:如何通过XGBoost提取特征贡献度
SHAP进阶解析:机器学习、深度学习模型解释保姆级教程
特征选择:Lasso和Boruta算法的结合应用
从基础到进阶:优化SHAP力图,让样本解读更直观
SCI图表复现:优化SHAP特征贡献图展示更多模型细节
多模型中的特征贡献度比较与可视化图解

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

微信号|deep_ML

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

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

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

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

欢迎关注、点赞、转发~

个人观点,仅供参考

GISer last
GISer last 公众号 主要以分享互联网数据资源为主。也分享过GIS、FME等技术教程方法。我个人对于大数据资源、可视化制作、地图制图等方面有很大兴趣,也会分享个人的一些应用和教程。
 最新文章