背景
随着机器学习技术的广泛应用,如何对多个模型的性能进行科学合理的可视化评价也是一个有趣的问题,除了常规的评价指标可视化外,泰勒图可作为一种融合相关性与标准差的可视化工具,能够为我们提供直观的模型比较方式
在此背景下,D. Lai等人在期刊《Engineering Applications of Artificial Intelligence》(2024年)中发表的文章,展示了通过 泰勒图 评估多种机器学习模型(如XGBoost、ANN、GPR和NGBoost)在回归任务中的表现,他们通过观察模型预测值与真实数据的标准差和相关性来衡量模型性能,受此启发,本文将基于相似的原理,结合 XGBoost、随机森林(Random Forest) 和 CatBoost 等几种主流回归模型,利用泰勒图进行性能对比分析,带领大家深入了解多模型的可视化评估方法
代码实现
数据读取并分割
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('数据.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 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, # 每棵树随机选择的特征比例,用于增加模型的泛化能力
'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]
# 计算均方根误差 (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
通过网格搜索对 XGBoost 模型进行超参数优化,训练得到最佳模型后,对测试数据进行预测,并计算和保存各种回归评估指标,评估指标详细解释如下
标准差:通过 np.std 计算模型预测值和真实观测值的标准差,分别用 std_dev_pred 和 std_dev_obs 表示
相关系数:通过 np.corrcoef 计算预测值与真实值之间的相关性
均方根误差:通过 mean_squared_error 计算预测值和真实值之间的误差,衡量模型的精度
R²值:通过 r2_score 计算模型的拟合优度,R² 值越接近 1 说明模型的拟合效果越好
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 模型评价指标的新 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
# 导入所需的库
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 将新行添加到 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 的相关系数
标准差:标准差衡量的是数据的离散程度,模型的预测标准差应该尽可能接近真实观测值的标准差,如果模型预测的标准差和观测值的标准差越接近,说明模型能够较好地捕捉数据的变化范围
初始可视化
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_obs
models = 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),说明模型预测的结果与实际值之间的匹配程度最好
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) / 2, f'{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/4, 1.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)
从零开始:手把手教你部署顶刊机器学习在线预测APP并解读模型结果
微信号|deep_ML
欢迎添加作者微信进入Python、ChatGPT群
进群请备注Python或AI进入相关群
无需科学上网、同步官网所有功能、使用无限制
如果你对类似于这样的文章感兴趣。
欢迎关注、点赞、转发~