背景
随着机器学习技术的广泛应用,如何对多个模型的性能进行科学合理的可视化评价也是一个有趣的问题,除了常规的评价指标可视化外,泰勒图可作为一种融合相关性与标准差的可视化工具,能够为我们提供直观的模型比较方式
在此背景下,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
通过网格搜索对 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
这里所有评价指标保存到一个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
在可视化之前,先重点说明一下相关系数和标准差两个重要指标:
- 相关系数:它衡量的是模型预测值和真实值之间的线性相关性,取值范围在 -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()
优化可视化
在原有的泰勒图上,加入了一条标准差为1.0的红色虚线,这条线是一个参考线,用于快速判断模型预测的标准差是否接近观测值的标准差,与文献保持一致
代码获取:如果您希望获取本文展示的改进版代码(包含文献可视化中的Observed标注),请添加作者微信联系获取
往期推荐
从零开始:手把手教你部署顶刊机器学习在线预测APP并解读模型结果
微信号|deep_ML
欢迎添加作者微信进入Python、ChatGPT群
进群请备注Python或AI进入相关群
无需科学上网、同步官网所有功能、使用无限制
如果你对类似于这样的文章感兴趣。
欢迎关注、点赞、转发~
个人观点,仅供参考