从2D到3D:部分依赖图(PDP)如何揭示特征组合对模型预测的综合影响
背景
部分依赖图(PDP)是解释机器学习模型的一种工具,用来展示模型的预测结果如何随着一个或多个特征值的变化而变化,对于单个特征,PDP通过保持其他特征不变,分析该特征取值的不同对模型输出的影响,帮助理解模型如何利用该特征进行决策,sklearn.inspection库目前只支持单特征的PDP和两个特征的2D PDP可视化输出,但可以基于PDP的原理自行实现3D PDP的可视化,基础PDP参考往期文章——PDP(部分依赖图)、ICE(个体条件期望)解释机器学习模型保姆级教程
代码实现
数据加载与分割
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Times New Roman'
plt.rcParams['axes.unicode_minus'] = False
df = pd.read_excel('california.xlsx')
from sklearn.model_selection import train_test_split, KFold
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)
df.head()
从Excel文件中读取数据,将特征变量与目标变量(房价 price)分离,并划分为训练集和测试集,用于机器学习模型的训练和评估。数据集中包括了房龄(HouseAge)、中位收入(MedInc)、平均房间数(AveRooms)等特征,目标变量为房价(price)
模型构建
from sklearn.metrics import root_mean_squared_error
from catboost import CatBoostRegressor
# CatBoost模型参数
params_cat = {
'learning_rate': 0.02, # 学习率,控制每一步的步长,用于防止过拟合。典型值范围:0.01 - 0.1
'iterations': 1000, # 弱学习器(决策树)的数量
'depth': 6, # 决策树的深度,控制模型复杂度
'eval_metric': 'RMSE', # 评估指标,这里使用均方根误差(Root Mean Squared Error,简称RMSE)
'random_seed': 42, # 随机种子,用于重现模型的结果
'verbose': 500 # 控制CatBoost输出信息的详细程度,每100次迭代输出一次
}
# 准备k折交叉验证
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = []
best_score = np.inf
best_model = None
# 交叉验证
for fold, (train_index, val_index) in enumerate(kf.split(X_train, y_train)):
X_train_fold, X_val_fold = X_train.iloc[train_index], X_train.iloc[val_index]
y_train_fold, y_val_fold = y_train.iloc[train_index], y_train.iloc[val_index]
model = CatBoostRegressor(**params_cat)
model.fit(X_train_fold, y_train_fold, eval_set=(X_val_fold, y_val_fold), early_stopping_rounds=100)
# 预测并计算得分
y_val_pred = model.predict(X_val_fold)
score = root_mean_squared_error(y_val_fold, y_val_pred) # RMSE
scores.append(score)
print(f'第 {fold + 1} 折 RMSE: {score}')
# 保存得分最好的模型
if score < best_score:
best_score = score
best_model = model
print(f'最佳 RMSE: {best_score}')
通过使用5折交叉验证来训练CatBoost回归模型,每一折模型都会基于训练集进行训练,并在验证集上进行评估,计算出对应的RMSE(均方根误差),在每一折训练中,模型会自动调整参数以避免过拟合,使用早停策略来选择最佳的迭代次数,最终,程序会输出每一折的RMSE得分,并保存得分最好的模型,最后输出整个交叉验证中最优模型的RMSE,表示模型在验证集上取得的最低预测误差,这一过程有助于评估模型的稳健性并防止过拟合,为PDP解释做准备
2D PDP解释
from sklearn.inspection import PartialDependenceDisplay
# 选择两个特征绘制2D PDP
features = ['MedInc', 'AveOccup']
# 使用 contour_kw 参数绘制2D PDP
fig, ax = plt.subplots(figsize=(10, 6), dpi=1200)
PartialDependenceDisplay.from_estimator(
best_model,
X_test,
features=[features],
kind='average',
grid_resolution=50,
contour_kw={'cmap': 'viridis', 'alpha': 0.8},
ax=ax
)
plt.suptitle('2D Partial Dependence Plot for MedInc and AveOccup')
plt.savefig("2D Partial Dependence Plot for MedInc and AveOccup.pdf", format='pdf',bbox_inches='tight')
plt.show()
使用PartialDependenceDisplay生成一个2D部分依赖图(PDP),用于展示两个特征(MedInc和AveOccup)对目标变量(房价)的影响,代码中的best_model是之前训练好的CatBoost模型,X_test是测试集中的特征数据,features指定了感兴趣的两个特征,即中位收入(MedInc)和平均家庭规模(AveOccup)
2D PDP图通过将MedInc(中位收入)和AveOccup(平均家庭规模)组合的不同值映射到房价预测结果的等高线图上,展示了模型对这两个特征共同作用的反应,图中的颜色表示房价的预测值,颜色从左到右逐渐变化,反映出随着中位收入增加,房价预测值也逐步增加,同时,等高线代表了不同的房价预测范围,等高线的数值越大,表示在这些区域中房价预测越高,通过观察可以发现,MedInc的增大对房价的影响更加显著,而AveOccup的影响相对较小,但它们的共同作用仍然对房价预测产生综合影响,这一可视化有助于直观地理解两个特征如何相互配合影响目标变量的趋势
3D PDP解释
from mpl_toolkits.mplot3d import Axes3D
def plot_3d_pdp_scatter(model, X, features, grid_resolution=20):
"""
绘制三个特征的3D PDP散点图,不固定任何特征值。
参数:
- model: 训练好的机器学习模型(例如:随机森林,XGBoost等)。
- X: 数据集(特征矩阵)。
- features: 需要分析的三个特征名列表,格式为 ['feature1', 'feature2', 'feature3']。
- grid_resolution: 网格分辨率,决定网格点的密度
"""
# 获取三个特征的数据
feature_1, feature_2, feature_3 = features
# 构建网格
feature_1_vals = np.linspace(X[feature_1].min(), X[feature_1].max(), grid_resolution)
feature_2_vals = np.linspace(X[feature_2].min(), X[feature_2].max(), grid_resolution)
feature_3_vals = np.linspace(X[feature_3].min(), X[feature_3].max(), grid_resolution)
# 生成三维网格
grid_1, grid_2, grid_3 = np.meshgrid(feature_1_vals, feature_2_vals, feature_3_vals)
# 将网格数据转换为数据框的格式,传递给模型进行预测
grid_points = np.c_[grid_1.ravel(), grid_2.ravel(), grid_3.ravel()]
X_grid = np.zeros((grid_points.shape[0], X.shape[1])) # 创建空白矩阵,维度与X相同
X_grid[:, X.columns.get_loc(feature_1)] = grid_points[:, 0]
X_grid[:, X.columns.get_loc(feature_2)] = grid_points[:, 1]
X_grid[:, X.columns.get_loc(feature_3)] = grid_points[:, 2]
# 对网格数据进行预测
preds = model.predict(X_grid)
# 绘制3D散点图
fig = plt.figure(figsize=(10, 8),dpi=1200)
ax = fig.add_subplot(111, projection='3d')
# 使用散点图绘制三维特征,并使用预测值作为颜色
sc = ax.scatter(grid_1.ravel(), grid_2.ravel(), grid_3.ravel(), c=preds, cmap='viridis', alpha=0.8)
# 添加颜色条
plt.colorbar(sc, ax=ax, label='Prediction')
ax.set_xlabel(feature_1)
ax.set_ylabel(feature_2)
ax.set_zlabel(feature_3)
plt.title(f'3D Partial Dependence Scatter Plot for {feature_1}, {feature_2}, and {feature_3}')
plt.savefig("3D.pdf", format='pdf', bbox_inches='tight')
plt.show()
features = ['MedInc', 'AveOccup', 'HouseAge']
plot_3d_pdp_scatter(best_model, X_test, features)
通过构建三维网格实现3D PDP(部分依赖图)的可视化,展示了三个特征(MedInc,AveOccup,HouseAge)对目标变量(房价)预测的影响,具体做法是,首先为每个特征构建取值范围并生成三维网格,然后将这些网格点组合成一组数据点,传递给训练好的模型进行预测。接着,代码使用三维散点图显示这些特征组合下的预测值,其中颜色代表房价预测值,颜色从深到浅依次表示房价从低到高,通过这种3D可视化,可以清晰地看到这三个特征对房价预测的共同作用和交互影响,例如,中位收入较高和房龄较新的区域往往房价预测较高,而家庭规模的影响较小但仍有一定作用,这种可视化方法帮助深入理解模型对多个特征组合的反应
固定特征的3D部分依赖图
def plot_3d_pdp(model, X, features, fixed_feature=None, fixed_value=None, grid_resolution=50):
"""
绘制三个特征的3D PDP图,其中一个特征固定值,并加上热度条。
参数:
- model: 训练好的机器学习模型(例如:随机森林,XGBoost等)。
- X: 数据集(特征矩阵)。
- features: 需要分析的三个特征名列表,格式为 ['feature1', 'feature2', 'feature3']。
- fixed_feature: 需要固定的特征名(默认为features列表中的第三个特征)。
- fixed_value: 第三个特征的固定值(可以是其均值、中位数或任意值,默认为其均值)。
- grid_resolution: 网格分辨率,决定网格点的密度。
"""
feature_1, feature_2, feature_3 = features
# 如果没有指定固定特征,默认将第三个特征固定
if fixed_feature is None:
fixed_feature = feature_3
# 如果没有指定固定值,则使用该特征的均值
if fixed_value is None:
fixed_value = X[fixed_feature].mean()
# 构建网格,针对两个未固定的特征
if fixed_feature == feature_1:
varying_features = [feature_2, feature_3]
elif fixed_feature == feature_2:
varying_features = [feature_1, feature_3]
else:
varying_features = [feature_1, feature_2]
feature_1_vals = np.linspace(X[varying_features[0]].min(), X[varying_features[0]].max(), grid_resolution)
feature_2_vals = np.linspace(X[varying_features[1]].min(), X[varying_features[1]].max(), grid_resolution)
grid_1, grid_2 = np.meshgrid(feature_1_vals, feature_2_vals)
# 构建网格数据,传递给模型进行预测
grid_points = np.c_[grid_1.ravel(), grid_2.ravel()]
X_grid = np.zeros((grid_points.shape[0], X.shape[1])) # 创建空白矩阵,维度与X相同
X_grid[:, X.columns.get_loc(varying_features[0])] = grid_points[:, 0]
X_grid[:, X.columns.get_loc(varying_features[1])] = grid_points[:, 1]
X_grid[:, X.columns.get_loc(fixed_feature)] = fixed_value # 固定特征值
# 对网格数据进行预测
preds = model.predict(X_grid).reshape(grid_1.shape)
# 绘制3D图像
fig = plt.figure(figsize=(10, 8), dpi=1200)
ax = fig.add_subplot(111, projection='3d')
# 绘制等高线图
surface = ax.plot_surface(grid_1, grid_2, preds, cmap='viridis', alpha=0.8)
# 添加颜色热度条
fig.colorbar(surface, ax=ax, shrink=0.5, aspect=10)
ax.set_xlabel(varying_features[0])
ax.set_ylabel(varying_features[1])
ax.set_zlabel('Prediction')
plt.title(f'3D Partial Dependence Plot for {varying_features[0]}, {varying_features[1]} with {fixed_feature}={fixed_value}')
plt.savefig("3D one.pdf", format='pdf', bbox_inches='tight')
plt.show()
features = ['MedInc', 'AveOccup', 'HouseAge']
plot_3d_pdp(best_model, X_test, features, fixed_feature='HouseAge')
在进行模型解释时,如果对所有特征进行可视化展示(如3个特征),会形成一个三维正方体图像,代表所有特征组合下的模型预测值,虽然这种全方位的可视化非常全面,但对于实际理解模型各特征如何结合影响预测来说,复杂度较高,因此,固定一个特征值可以简化分析,并展示另外两个特征的组合如何影响预测结果,通过固定某个特征,能更容易观察这两个特征对目标变量的影响,同时保持模型预测的复杂性
代码通过生成一个3D部分依赖图,固定HouseAge的特定值(默认是其均值或用户指定值),然后分析MedInc(中位收入)和AveOccup(平均家庭规模)对房价预测的影响。代码首先创建了一个包含MedInc和AveOccup的网格,将这两个特征在不同取值下组合,然后传递给模型进行预测,最后,通过3D等高线图将预测结果可视化,其中颜色表示房价的预测值
在图中,HouseAge被固定为31.14岁,表示房龄固定情况下中位收入(MedInc)和平均家庭规模(AveOccup)如何共同作用影响房价预测。颜色从深紫色到亮黄色逐渐变化,表明预测值从低到高,可以观察到,中位收入对房价预测的影响相对明显,随着MedInc的增大,房价预测值显著增加;而AveOccup的影响较为平缓,整体变化不大,通过这张图,能更直观地看到这两个特征的组合如何在特定房龄下影响房价预测
本文章转载微信公众号@Python机器学习AI