SCI图表:基于相关性和标准差的多模型评价——泰勒图解析

机器学习NoSQL数据库数据安全

picture.image

背景

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

picture.image

picture.image

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

作用

模型对比

通过泰勒图 (Taylor Diagram) 来直观比较模型的性能,泰勒图通过标准差和相关系数来展示模型表现,并包含RMSE的等高线

  • 标准差比率:图中每个点到原点的径向距离表示预测值与观测值的标准差之比
  • 相关系数角度:从x轴(0弧度)到点的角度表示观测值和预测值之间的相关性
  • 红色虚线:指示标准差为1的参考线,用于快速判断预测精度
  • RMSE等高线表示预测值与观测值之间的均方根误差,通常用虚线绘制,距离原点较近且角度较小的点通常表示更优的模型

最终的泰勒图对比了各模型相对于观测数据的表现,为模型的统计特性提供了直观的展示

代码实现

数据读取并分割


          
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_excel('2024-10-27-公众号Python机器学习AI.xlsx')
          

          
# 划分特征和目标变量
          
X = df.drop(['待预测变量Y'], axis=1)
          
y = df['待预测变量Y']
          

          
# 划分训练集和测试集
          
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 xgb
          
from 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,          # 每棵树随机2024-10-27-公众号Python机器学习AI选择的特征比例,用于增加模型的泛化能力
          
    '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_error
          
from 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]
          

          
# 计算均方根2024-10-27-公众号Python机器学习AI误差 (RMSE)
          
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
          

          
# 计算 R2
          
r2 = r2_score(y_test, y_pred)
          

          
# 保存为DataFrame
          
metrics_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
      

picture.image

通过网格搜索对 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 模型2024-10-27-公众号Python机器学习AI评价指标的新 DataFrame
          
new_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 
      

picture.image

这里所有评价指标保存到一个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模型评价指标的新DataFrame
          
new_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 将2024-10-27-公众号Python机器学习AI新行添加到 metrics_df 中
          
metrics_df = pd.concat([metrics_df, new_row_catboost], ignore_index=True)
          
metrics_df
      

picture.image

在可视化之前,先重点说明一下相关系数和标准差两个重要指标:

  • 相关系数:它衡量的是模型预测值和真实值之间的线性相关性,取值范围在 -1 到 1 之间。相关系数越接近 1,表示模型预测值与真实值的拟合效果越好,预测越准确,理想的模型应该有接近 1 的相关系数
  • 标准差:标准差衡量的是数据的离散程度,模型的预测标准差应该尽可能接近真实观测值的标准差,如果模型预测的标准差和观测值的标准差越接近,说明模型能够较好地捕捉数据的变化范围

因此,最优的模型 应该是具有较高的相关系数(接近 1)和预测标准差接近观测标准差的模型,在接下来的可视化中,我们将利用这些指标,结合泰勒图等工具,来直观地展示各个模型的表现

初始可视化


          
from mpl_toolkits.axisartist import floating_axes
          
from mpl_toolkits.axisartist.grid_finder import FixedLocator, DictFormatter
          
from matplotlib.projections import PolarAxes
          

          
def set_tayloraxes(fig, location):
          
    trans = PolarAxes.PolarTransform(apply_theta_transforms=False)
          
    r1_locs = np.hstack((np.arange(1,10)/10.0, [0.95, 0.99]))
          
    t1_locs = np.arccos(r1_locs)
          
    gl1 = FixedLocator(t1_locs)
          
    tf1 = DictFormatter(dict(zip(t1_locs, map(str, r1_locs))))
          
    
          
    r2_locs = np.arange(0, 2, 0.25)
          
    r2_labels = ['0', '0.25', '0.50', '0.75', '1.00', '1.25', '1.50', '1.75']
          
    gl2 = FixedLocator(r2_locs)
          
    tf2 = DictFormatter(dict(zip(r2_locs, r2_labels)))
          
    
          
    ghelper = floating_axes.GridHelperCurveLinear(trans, extremes=(0, np.pi/2, 0, 1.75),
          
                                                  grid_locator1=gl1, tick_formatter1=tf1,
          
                                                  grid_locator2=gl2, tick_formatter2=tf2)
          
    ax = floating_axes.FloatingSubplot(fig, location, grid_helper=ghelper)
          
    fig.add_subplot(ax)
          

          
    ax.axis["top"].set_axis_direction("bottom")
          
    ax.axis["top"].toggle(ticklabels=True, label=True)
          
    ax.axis["top"].major_ticklabels.set_axis_direction("top")
          
    ax.axis["top"].label.set_axis_direction("top")
          
    ax.axis["top"].label.set_text("Correlation")
          
    ax.axis["top"].label.set_fontsize(14)
          

          
    ax.axis["left"].set_axis_direction("bottom")
          
    ax.axis["left"].label.set_text("Standard deviation")
          
    ax.axis["left"].label.set_fontsize(14)
          

          
    ax.axis["right"].set_axis_direction("top")
          
    ax.axis["right"].toggle(ticklabels=True)
          
    ax.axis["right"].major_ticklabels.set_axis_direction("left")
          
    ax.axis["bottom"].set_visible(False)
          

          
    ax.grid(True)
          
    polar_ax = ax.get_aux_axes(trans)
          

          
    rs, ts = np.meshgrid(np.linspace(0, 1.75, 100), np.linspace(0, np.pi/2, 100))
          
    rms = np.sqrt(1 + rs**2 - 2 * rs * np.cos(ts))
          
    CS = polar_ax.contour(ts, rs, rms, colors='gray', linestyles='--')
          
    plt.clabel(CS, inline=1, fontsize=10)
          
    
          
    return polar_ax
          

          
def plot_taylor(ax, std_obs, std_pred, correlation, **kwargs):
          

          
    theta = np.arccos(correlation)
          
    radius = std_pred / std_obs
          
    ax.plot(theta, radius, **kwargs)
          

          
# 去掉 'label' 的位置参2024-10-27-公众号Python机器学习AI数传递,改为仅在关键字参数中传递
          
fig = plt.figure(figsize=(8, 8), dpi=1200)
          
ax = set_tayloraxes(fig, 111)
          

          
# 在泰勒图上绘制每个模型的数据点
          
for i, row in metrics_df.iterrows():
          
    plot_taylor(ax, row['Standard Deviation (Observed)'], row['Standard Deviation (Pred)'], 
          
                row['Correlation'], marker='o', markersize=8, label=row['Model'])
          

          
# 添加图例
          
ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.1))
          
plt.savefig("2024-10-27-公众号Python机器学习AI——1.pdf", format='pdf',bbox_inches='tight')
          
plt.show()
      

picture.image

优化可视化

在原有的泰勒图上,加入了一条标准差为1.0的红色虚线,这条线是一个参考线,用于快速判断模型预测的标准差是否接近观测值的标准差,与文献保持一致

picture.image

代码获取:如果您希望获取本文展示的改进版代码(包含文献可视化中的Observed标注),请添加作者微信联系获取

往期推荐

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

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

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

SHAP进阶解析:机器学习、深度学习模型解释保姆级教程

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

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

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

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

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

picture.image

picture.image

picture.image

微信号|deep_ML

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

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

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

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

欢迎关注、点赞、转发~

个人观点,仅供参考

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