【竞赛系列】机器学习实操项目06——客户信用评估模型进阶流程(特征重要性分析与稳定性监控)
上一章:【竞赛系列】机器学习实操项目05——客户信用评估模型进阶流程(含XGBoost、LightGBM、CatBoost 高级模型对比与参数优化)
下一章:
机器学习核心知识点目录:机器学习核心知识点目录
机器学习实战项目目录:【从 0 到 1 落地】机器学习实操项目目录:覆盖入门到进阶,大学生就业 / 竞赛必备
文章目录
- 任务总览
- 1. 导入核心工具库
- 2. 数据加载与初步预处理
- 3. 划分训练集与验证集
- 4. 特征预处理(标准化+独热编码)
- 5. 检查特征矩阵形状
- 6. 提取特征名称(用于后续分析)
- 7. 特征重要性分析(基于随机森林)
- 8. 特征筛选策略1:使用所有特征(基准模型)
- 9. 特征筛选策略2:基于重要性阈值筛选
- 10. 特征筛选策略3:选择固定数量的重要特征
- 11. 特征稳定性监控:PSI(群体稳定性指数)定义
- 12. 准备特征稳定性分析的数据
- 13. 计算各特征的PSI并评估稳定性
- 14. 整理并展示特征稳定性结果
- 15. 生成特征稳定性报告
- 16. 特征分析总结与应用建议
任务总览
本项目旨在开发一个客户信用评估模型,通过分析客户的基本信息、账户特征等数据,预测客户是否为"好客户"(Target=1)或"坏客户"(Target=0)。这一模型可帮助金融机构在信贷审批过程中做出更合理的决策,平衡风险控制与业务发展。
本流程围绕客户信用风险分类任务(预测客户是否为“好客户”),进行特征重要性分析与稳定性监控。
通过网盘分享的文件:用户信用评分数据集
链接: https://pan.baidu.com/s/1F2R0rOLuw4_ntaKMPL8otg?pwd=1x27 提取码: 1x27
1. 导入核心工具库
该步骤加载数据处理、机器学习建模、特征选择和模型评估所需的基础库,为后续特征分析提供工具支持。
# 导入必要的数据处理和机器学习库
import pandas as pd # 用于数据处理和分析(如DataFrame操作、特征名称管理)
import numpy as np # 用于数值计算(如数组操作、分箱计算)
import matplotlib.pyplot as plt # 用于数据可视化(如特征重要性条形图)
from sklearn.model_selection import train_test_split # 用于划分训练集和验证集
from sklearn.preprocessing import StandardScaler, OneHotEncoder # 用于特征预处理
from sklearn.metrics import roc_auc_score, confusion_matrix, classification_report # 用于模型评估(核心指标AUC)
2. 数据加载与初步预处理
该步骤读取训练集和测试集数据,处理分类特征的缺失值,并分离特征与目标变量,为后续特征分析准备基础数据。
# 2.1 数据加载与预处理
# 读取训练数据和测试数据(包含客户特征与信用标签)
train = pd.read_csv('train.csv') # 加载训练数据集(含目标变量Target)
test = pd.read_csv('test.csv') # 加载测试数据集(用于最终预测和特征稳定性分析)# 处理缺失值 - 针对分类特征Credit_Product(客户是否有信贷产品)的缺失值
# 填充为'Unknown'(保留缺失信息,避免因简单删除导致的样本损失)
train['Credit_Product'] = train['Credit_Product'].fillna('Unknown')
test['Credit_Product'] = test['Credit_Product'].fillna('Unknown')# 分离特征和目标变量(Target为1表示“好客户”,0表示“坏客户”)
# 训练数据中删除ID列(无预测意义)和Target列(目标变量),得到特征矩阵X
X = train.drop(['ID', 'Target'], axis=1)
# 提取目标变量(客户信用标签),得到标签向量y
y = train['Target']
3. 划分训练集与验证集
该步骤将训练数据拆分为“训练集”(用于模型训练和特征重要性分析)和“验证集”(用于评估特征筛选后的模型性能),采用分层抽样确保数据分布一致性。
# 3.1 划分训练集和验证集
# 将数据分为训练集(80%)和验证集(20%),用于后续特征筛选的效果验证
# 分层抽样(stratify=y)确保训练集和验证集的目标变量分布一致,避免评估偏差
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, # 验证集比例20%stratify=y, # 按目标变量分层抽样random_state=42 # 随机种子,确保结果可复现
)
4. 特征预处理(标准化+独热编码)
该步骤对数值特征进行标准化(消除量纲影响),对分类特征进行独热编码(转换为模型可识别的格式),并合并处理后的特征,形成最终输入矩阵。
# 4.1 特征预处理
# 定义分类特征和数值特征(根据数据类型和业务逻辑划分)
# 分类特征:取值为离散类别的特征(如性别、职业、地区编码)
cat_cols = ['Gender', 'Region_Code', 'Occupation', 'Channel_Code', 'Credit_Product', 'Is_Active']
# 数值特征:取值为连续数值的特征(如年龄、账户余额、客户在网时长)
num_cols = ['Age', 'Vintage', 'Avg_Account_Balance']# 对分类变量进行独热编码(One-Hot Encoding)
# 创建独热编码器,handle_unknown='ignore'表示遇到未知类别时编码为全0
encoder = OneHotEncoder(handle_unknown='ignore')
# 在训练集上拟合并转换分类特征(拟合训练集分布,避免数据泄露)
X_train_encoded = encoder.fit_transform(X_train[cat_cols])
# 在验证集上转换分类特征(使用训练集编码器,保持一致性)
X_val_encoded = encoder.transform(X_val[cat_cols])
# 在测试集上转换分类特征(用于后续特征稳定性分析)
X_test_encoded = encoder.transform(test[cat_cols])# 数值特征标准化(Z-score标准化:均值为0,标准差为1)
scaler = StandardScaler()
# 在训练集上拟合并转换数值特征
X_train_num = scaler.fit_transform(X_train[num_cols])
# 在验证集上转换数值特征
X_val_num = scaler.transform(X_val[num_cols])
# 在测试集上转换数值特征
X_test_num = scaler.transform(test[num_cols])# 合并处理后的特征(水平堆叠数值特征和编码后的分类特征)
X_train_final = np.hstack([X_train_num, X_train_encoded.toarray()]) # 训练集最终特征矩阵
X_val_final = np.hstack([X_val_num, X_val_encoded.toarray()]) # 验证集最终特征矩阵
X_test_final = np.hstack([X_test_num, X_test_encoded.toarray()]) # 测试集最终特征矩阵
5. 检查特征矩阵形状
该步骤验证预处理后的数据维度是否符合预期,确保训练集、验证集、测试集的特征数量一致,为后续特征分析奠定基础。
# 5.1 打印形状检查(验证特征矩阵维度是否正确)
print(f"训练集形状: {X_train_final.shape}") # 格式:(样本数, 特征数),如(156580, 53)
print(f"验证集形状: {X_val_final.shape}") # 验证集样本数应为训练集的20%左右,特征数需一致
print(f"测试集形状: {X_test_final.shape}") # 测试集特征数需与训练集一致
输出结果:
训练集形状: (156580, 53)
验证集形状: (39145, 53)
测试集形状: (50000, 53)
6. 提取特征名称(用于后续分析)
该步骤整合数值特征和编码后的分类特征名称,为特征重要性分析和稳定性监控提供可识别的特征标签。
# 6.1 获取特征名称(将数值特征和编码后的分类特征名称合并,便于后续分析)
num_feature_names = num_cols # 数值特征名称(直接使用原始列名)
# 分类特征编码后的名称(如Gender_Female、Gender_Male,由OneHotEncoder生成)
cat_feature_names = encoder.get_feature_names_out(cat_cols)
# 合并所有特征名称(数值特征在前,分类特征在后)
all_feature_names = np.concatenate([num_feature_names, cat_feature_names])print(f"原始特征数量: {len(all_feature_names)}") # 输出总特征数,验证是否与特征矩阵列数一致
输出结果:
原始特征数量: 53
7. 特征重要性分析(基于随机森林)
该步骤训练随机森林模型,通过模型自带的特征重要性指标,量化每个特征对预测结果的贡献度,并可视化Top20重要特征,识别关键预测因子。
# 7.1 特征重要性分析(使用随机森林模型评估特征对预测的贡献度)
# 训练随机森林模型(树的数量100,确保结果稳定;n_jobs=-1使用所有CPU加速)
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
rf.fit(X_train_final, y_train) # 用训练集特征和标签训练模型# 提取特征重要性并排序(按重要性降序排列,便于筛选关键特征)
feature_importances = pd.DataFrame({'Feature': all_feature_names, # 特征名称'Importance': rf.feature_importances_ # 特征重要性得分(范围0-1,总和为1)
}).sort_values('Importance', ascending=False) # 按重要性降序排序# 可视化Top20特征重要性(水平条形图,重要性最高的特征在顶部)
plt.figure(figsize=(12, 8)) # 图表大小:宽12,高8
# 绘制水平条形图(取前20个最重要的特征)
plt.barh(feature_importances['Feature'][:20], feature_importances['Importance'][:20])
plt.xlabel('特征重要性') # x轴标签
plt.title('Top 20特征重要性') # 图表标题
plt.gca().invert_yaxis() # 翻转y轴,使重要性最高的特征在顶部
plt.tight_layout() # 自动调整布局,避免标签重叠
plt.show() # 显示图表
输出结果:
8. 特征筛选策略1:使用所有特征(基准模型)
该步骤以“使用所有特征”作为基准策略,训练随机森林模型并计算验证集AUC,为后续特征筛选策略提供对比基准。
# 8.1 不同特征筛选策略对比(初始化结果列表,存储各策略的性能)
results = []# 策略1: 使用所有特征(作为基准,对比其他筛选策略的效果)
print("\n策略1: 使用所有特征")
# 训练随机森林模型(与特征重要性分析的模型参数一致,确保公平对比)
model = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
model.fit(X_train_final, y_train) # 用全部特征训练模型
# 预测验证集的“好客户”概率(取第二列[:,1])
val_pred = model.predict_proba(X_val_final)[:, 1]
# 计算AUC(评估模型分类性能,越高越好)
auc = roc_auc_score(y_val, val_pred)
# 记录结果(策略名称、AUC、特征数量)
results.append({'Strategy': '所有特征','AUC': auc,'Num_Features': X_train_final.shape[1]
})
# 打印该策略的性能
print(f"特征数量: {X_train_final.shape[1]}, AUC: {auc:.4f}")
输出结果:
策略1: 使用所有特征
特征数量: 53, AUC: 0.8477
9. 特征筛选策略2:基于重要性阈值筛选
该步骤通过设置不同的重要性阈值(0.001、0.005、0.01),筛选出重要性高于阈值的特征,训练模型并评估性能,分析特征数量减少对模型效果的影响。
# 9.1 策略2: 使用重要性阈值筛选特征(保留重要性高于阈值的特征)
thresholds = [0.001, 0.005, 0.01] # 不同的重要性阈值(从小到大)# 从sklearn导入特征选择工具(基于模型的特征选择)
from sklearn.feature_selection import SelectFromModel
for threshold in thresholds:print(f"\n策略2: 重要性 > {threshold}")# 基于随机森林的特征重要性选择特征(保留重要性>threshold的特征)selector = SelectFromModel(rf, threshold=threshold, prefit=True) # prefit=True表示使用已训练的rf模型X_train_selected = selector.transform(X_train_final) # 筛选训练集特征X_val_selected = selector.transform(X_val_final) # 筛选验证集特征(使用相同的选择规则)# 获取选择的特征名称(用于分析和报告)selected_features = all_feature_names[selector.get_support()] # get_support()返回布尔数组,标记选中的特征# 用筛选后的特征训练新模型model = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)model.fit(X_train_selected, y_train)# 预测验证集并计算AUCval_pred = model.predict_proba(X_val_selected)[:, 1]auc = roc_auc_score(y_val, val_pred)# 记录结果(包含前3个重要特征,便于分析)results.append({'Strategy': f'重要性 > {threshold}','AUC': auc,'Num_Features': X_train_selected.shape[1],'Top_Features': ", ".join(selected_features[:3]) # 只显示前3个重要特征})# 打印该策略的性能和关键特征print(f"特征数量: {X_train_selected.shape[1]}, AUC: {auc:.4f}")print(f"重要特征: {', '.join(selected_features[:5])}...") # 显示前5个重要特征
输出结果:
策略2: 重要性 > 0.001
特征数量: 52, AUC: 0.8468
重要特征: Age, Vintage, Avg_Account_Balance, Gender_Female, Gender_Male...策略2: 重要性 > 0.005
特征数量: 20, AUC: 0.8436
重要特征: Age, Vintage, Avg_Account_Balance, Gender_Female, Gender_Male...策略2: 重要性 > 0.01
特征数量: 9, AUC: 0.8338
重要特征: Age, Vintage, Avg_Account_Balance, Occupation_Salaried, Channel_Code_X1...
10. 特征筛选策略3:选择固定数量的重要特征
该步骤通过选择Top N个重要特征(N=5、10、15、20),训练模型并评估性能,分析“保留最关键特征”对模型效果的影响,寻找特征数量与性能的平衡点。
# 10.1 策略3: 选择固定数量的重要特征(保留重要性排名前N的特征)
feature_counts = [5, 10, 15, 20] # 不同的特征数量(从小到大)for count in feature_counts:print(f"\n策略3: 前{count}个重要特征")# 选择最重要的count个特征(从之前排序的feature_importances中取前count个)top_k_features = feature_importances.head(count)['Feature'].values# 获取这些特征在原始特征矩阵中的索引(用于筛选)indices = [np.where(all_feature_names == feature)[0][0] for feature in top_k_features]# 筛选训练集和验证集的特征X_train_selected = X_train_final[:, indices]X_val_selected = X_val_final[:, indices]# 用筛选后的特征训练新模型model = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)model.fit(X_train_selected, y_train)# 预测验证集并计算AUCval_pred = model.predict_proba(X_val_selected)[:, 1]auc = roc_auc_score(y_val, val_pred)# 记录结果results.append({'Strategy': f'前{count}个特征','AUC': auc,'Num_Features': count,'Top_Features': ", ".join(top_k_features[:3]) # 显示前3个特征})# 打印该策略的性能和关键特征print(f"特征数量: {count}, AUC: {auc:.4f}")print(f"重要特征: {', '.join(top_k_features[:5])}...") # 显示前5个特征
输出结果:
策略3: 前5个重要特征
特征数量: 5, AUC: 0.8177
重要特征: Avg_Account_Balance, Vintage, Credit_Product_Unknown, Age, Credit_Product_No...策略3: 前10个重要特征
特征数量: 10, AUC: 0.8331
重要特征: Avg_Account_Balance, Vintage, Credit_Product_Unknown, Age, Credit_Product_No...策略3: 前15个重要特征
特征数量: 15, AUC: 0.8412
重要特征: Avg_Account_Balance, Vintage, Credit_Product_Unknown, Age, Credit_Product_No...策略3: 前20个重要特征
特征数量: 20, AUC: 0.8435
重要特征: Avg_Account_Balance, Vintage, Credit_Product_Unknown, Age, Credit_Product_No...
11. 特征稳定性监控:PSI(群体稳定性指数)定义
该步骤定义PSI(Population Stability Index)计算函数,用于量化训练集(基准数据)与测试集(新数据)的特征分布差异,评估特征稳定性(PSI越小,分布越稳定)。
# 11.1 特征稳定性监控(PSI指标)
# PSI (Population Stability Index) 用于衡量特征在不同数据集(如训练集vs测试集)中的分布差异
# - PSI < 0.1: 特征分布稳定(分布差异小)
# - 0.1 ≤ PSI < 0.2: 特征有轻微变化(需关注)
# - PSI ≥ 0.2: 特征显著漂移(可能影响模型泛化能力,需处理)# 定义计算PSI的函数
def calculate_psi(expected, actual, bins=10):"""计算特征PSI(群体稳定性指数)参数:expected: 基准数据(通常是训练集)actual: 实际数据(通常是新数据集,如测试集)bins: 分箱数量,默认为10(将特征值分为10个区间计算分布)返回:psi: 计算得到的PSI值(越大表示分布差异越大)"""# 创建分箱点:使用基准数据的分位数作为分箱边界(确保分箱方式一致)breakpoints = np.linspace(0, 1, bins+1)[1:-1] # 生成0.1, 0.2, ..., 0.9分位数点# 计算基准数据在每个分箱中的百分比expected_percents = np.histogram(expected, bins=np.quantile(expected, breakpoints))[0] / len(expected)# 计算新数据在相同分箱中的百分比(使用基准数据的分箱边界)actual_percents = np.histogram(actual, bins=np.quantile(expected, breakpoints))[0] / len(actual)# 避免零除问题:将极小的百分比(<0.001)替换为0.001expected_percents = np.clip(expected_percents, 0.001, None)actual_percents = np.clip(actual_percents, 0.001, None)# 计算PSI值:PSI = Σ[(实际比例 - 基准比例) * ln(实际比例/基准比例)]psi = np.sum((actual_percents - expected_percents) * np.log(actual_percents / expected_percents))return psi
12. 准备特征稳定性分析的数据
该步骤将训练集和测试集的特征矩阵转换为DataFrame,并添加特征名称,便于后续遍历每个特征计算PSI。
# 12.1 初始化结果列表,用于存储每个特征的PSI计算结果
psi_results = []# 将训练集和测试集的特征矩阵转换为DataFrame,并添加特征名称
base_data = pd.DataFrame(X_train_final) # 基准数据(训练集)
base_data.columns = all_feature_names # 添加特征名称new_data = pd.DataFrame(X_test_final) # 新数据(测试集)
new_data.columns = all_feature_names # 添加特征名称
13. 计算各特征的PSI并评估稳定性
该步骤遍历所有特征,计算训练集与测试集的PSI值,根据PSI大小评估特征稳定性(稳定/轻微变化/显著漂移),并记录特征均值差异,辅助分析分布变化原因。
# 13.1 遍历基准数据中的每个特征,计算PSI并评估稳定性
for feature in all_feature_names:# 跳过分类特征(PSI主要适用于连续特征,分类特征分布差异可通过其他方式评估)# 判断标准:如果特征唯一值少于10个,视为分类特征if base_data[feature].nunique() < 10:continue # 跳过当前分类特征,继续下一个# 计算当前特征的PSI值(对比训练集和测试集分布)psi = calculate_psi(base_data[feature], new_data[feature])# 根据PSI值评估特征稳定性if psi < 0.1:stability = "稳定" # PSI < 0.1 表示特征分布稳定elif psi < 0.2:stability = "轻微变化" # 0.1 ≤ PSI < 0.2 表示有轻微变化else:stability = "显著漂移" # PSI ≥ 0.2 表示特征分布发生显著变化# 收集特征的相关统计信息(用于后续分析)psi_results.append({"Feature": feature, # 特征名称"PSI": psi, # 计算得到的PSI值"Stability": stability, # 稳定性评估结果"Base_Mean": base_data[feature].mean(), # 基准数据(训练集)的特征均值"New_Mean": new_data[feature].mean(), # 新数据(测试集)的特征均值"Diff": abs(base_data[feature].mean() - new_data[feature].mean()) # 均值差异绝对值})
14. 整理并展示特征稳定性结果
该步骤将PSI计算结果转换为DataFrame,按PSI值降序排序,直观展示各特征的稳定性情况,为特征选择和模型维护提供依据。
# 14.1 将PSI结果转换为DataFrame,并按PSI值降序排序(便于查看最不稳定的特征)
psi_df = pd.DataFrame(psi_results).sort_values('PSI', ascending=False)
psi_df # 显示PSI结果表格
输出结果:
Feature | PSI | Stability | Base_Mean | New_Mean | Diff | |
---|---|---|---|---|---|---|
1 | Vintage | 0.000285 | 稳定 | -7.578275e-18 | 0.008963 | 0.008963 |
0 | Age | 0.000270 | 稳定 | 2.595673e-17 | 0.003749 | 0.003749 |
2 | Avg_Account_Balance | 0.000078 | 稳定 | 4.301919e-17 | 0.002310 | 0.002310 |
15. 生成特征稳定性报告
该步骤汇总特征稳定性分析结果,统计不同稳定性等级(稳定/轻微变化/显著漂移)的特征数量,若存在显著漂移特征则重点标注,为模型监控和更新提供决策支持。
# 15.1 输出特征稳定性报告(汇总分析结果,便于快速掌握特征分布变化情况)
print("\n特征稳定性报告:")
# 打印监测的总特征数(仅包含连续特征)
print(f"监测特征总数: {len(psi_df)}")
# 打印稳定特征数量(PSI < 0.1)
print(f"稳定特征(PSI<0.1): {len(psi_df[psi_df['PSI']<0.1])}")
# 打印轻微变化特征数量(0.1 ≤ PSI < 0.2)
print(f"轻微变化特征(0.1<=PSI<0.2): {len(psi_df[(psi_df['PSI']>=0.1) & (psi_df['PSI']<0.2)])}")
# 打印显著漂移特征数量(PSI ≥ 0.2)
print(f"显著漂移特征(PSI>=0.2): {len(psi_df[psi_df['PSI']>=0.2])}")# 如果有显著漂移特征,打印Top5(便于重点关注和处理)
if not psi_df[psi_df['PSI'] >= 0.2].empty:print("\n显著漂移特征Top5:")# 选择关键列(特征名称、PSI值、均值差异)并打印前5个print(psi_df[psi_df['PSI'] >= 0.2].head(5)[['Feature', 'PSI', 'Base_Mean', 'New_Mean', 'Diff']])
输出结果:
特征稳定性报告:
监测特征总数: 3
稳定特征(PSI<0.1): 3
轻微变化特征(0.1<=PSI<0.2): 0
显著漂移特征(PSI>=0.2): 0
16. 特征分析总结与应用建议
该步骤综合特征重要性和稳定性分析结果,总结关键发现并提出特征选择建议,为模型优化提供方向(如保留高重要性且稳定的特征,剔除低重要性或漂移特征)。
特征分析总结
- 特征重要性:Avg_Account_Balance(账户平均余额)、Vintage(客户在网时长)、Age(年龄)是预测客户信用的核心特征。
- 特征筛选效果:
- 使用所有特征时AUC为0.8477(基准);
- 保留前20个重要特征时AUC为0.8435(仅下降0.0042,但特征数量减少62%);
- 特征重要性阈值0.005时,20个特征的AUC为0.8436(与前20特征效果接近)。
- 特征稳定性:所有连续特征PSI均<0.1,分布稳定(训练集与测试集分布一致,模型泛化风险低)。
应用建议
- 特征选择:推荐保留前20个重要特征(或重要性>0.005的特征),在几乎不损失性能的前提下减少计算成本。
- 模型监控:定期(如每月)计算生产环境特征与训练集的PSI,若出现显著漂移(PSI≥0.2)需及时更新模型。
- 特征工程:基于核心特征(如账户余额、在网时长)构建交互特征(如余额×在网时长),可能进一步提升模型性能。
上一章:【竞赛系列】机器学习实操项目05——客户信用评估模型进阶流程(含XGBoost、LightGBM、CatBoost 高级模型对比与参数优化)
下一章:
机器学习核心知识点目录:机器学习核心知识点目录
机器学习实战项目目录:【从 0 到 1 落地】机器学习实操项目目录:覆盖入门到进阶,大学生就业 / 竞赛必备