数据敏感场景下的CART决策树优化实践:从特征选择到模型鲁棒性提升

1次阅读
没有评论

共计 2851 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

问题背景

在金融风控、医疗诊断等数据敏感领域,传统 CART 决策树经常会遇到这样的问题:特征值的微小变化导致分裂点剧烈波动。比如在信用卡反欺诈场景中,用户行为特征的分布可能因为营销活动或季节性因素发生变化,这时标准的决策树模型容易产生不稳定的预测结果。

数据敏感场景下的 CART 决策树优化实践:从特征选择到模型鲁棒性提升

具体表现有:

  • 同一批数据在不同时间训练会得到完全不同的树结构
  • 测试集上加入少量噪声后模型准确率大幅下降
  • 特征重要性排名随数据采样剧烈变化

技术方案

改进的 Gini 系数计算

传统 Gini 系数公式为:

Gini = 1 - Σ(p_i)^2

我们引入特征稳定性权重 w 后的改进版本:

Gini_weighted = w * (1 - Σ(p_i)^2)

其中 w 通过 PSI(Population Stability Index)计算:

w = 1 / (1 + PSI)
PSI = Σ[(实际占比 - 预期占比) * ln(实际占比 / 预期占比)]

动态剪枝算法

剪枝阈值不再固定,而是根据特征稳定性动态调整:

α = α_base * (1 + PSI)

其中 α_base 是基础剪枝参数,PSI>0.25 时会自动增强剪枝力度。

代码实现

带稳定性约束的节点分裂

import numpy as np
from sklearn.tree import DecisionTreeClassifier

class StableDecisionTree(DecisionTreeClassifier):
    def __init__(self, max_depth=None, min_samples_split=2, psi_threshold=0.1):
        super().__init__(max_depth=max_depth, min_samples_split=min_samples_split)
        self.psi_threshold = psi_threshold

    def _compute_psi(self, X_train, X_test, feature_idx):
        # 计算训练集和测试集的分布差异
        train_counts = np.histogram(X_train[:, feature_idx], bins=10)[0]
        test_counts = np.histogram(X_test[:, feature_idx], bins=10)[0]

        train_pct = train_counts / len(X_train)
        test_pct = test_counts / len(X_test)

        psi = np.sum((test_pct - train_pct) * np.log(test_pct / train_pct))
        return psi

    def _compute_stable_gini(self, X, y, feature_idx, threshold):
        # 标准 Gini 计算
        left_mask = X[:, feature_idx] <= threshold
        n_left, n_right = np.sum(left_mask), len(X) - np.sum(left_mask)

        if n_left == 0 or n_right == 0:
            return float('inf')

        p_left = [np.sum(y[left_mask]==c)/n_left for c in np.unique(y)]
        p_right = [np.sum(y[~left_mask]==c)/n_right for c in np.unique(y)]

        gini_left = 1 - np.sum(np.square(p_left))
        gini_right = 1 - np.sum(np.square(p_right))

        gini = (n_left * gini_left + n_right * gini_right) / len(X)

        # 添加稳定性惩罚项
        if hasattr(self, 'reference_distribution_'):
            psi = self._compute_psi(self.reference_distribution_, X, feature_idx)
            if psi > self.psi_threshold:
                gini *= (1 + psi)

        return gini

特征重要性可视化

import matplotlib.pyplot as plt
import seaborn as sns

def plot_feature_stability(clf, X_train, X_test, feature_names):
    psi_scores = [clf._compute_psi(X_train, X_test, i) 
                 for i in range(X_train.shape[1])]

    importances = clf.feature_importances_

    plt.figure(figsize=(10, 6))
    sns.scatterplot(x=importances, y=psi_scores, hue=feature_names)
    plt.axhline(y=clf.psi_threshold, color='r', linestyle='--')
    plt.xlabel('Feature Importance')
    plt.ylabel('PSI Score')
    plt.title('Feature Importance vs Stability')
    plt.legend(bbox_to_anchor=(1.05, 1))

生产建议

  1. 监控机制 :
  2. 每周计算各特征的 PSI 值
  3. 当 PSI>0.25 时触发告警

  4. 重训练策略 :

  5. 累计 3 次告警后自动启动模型重训练
  6. 保留 10% 的历史数据作为稳定性验证集

  7. 特征使用规范 :

  8. 对 PSI 高的特征限制最大分裂深度
  9. 禁止单一特征重要性超过 40%

验证方法

对抗测试设计

  1. 向测试数据添加高斯噪声(σ=0.1* 标准差)
  2. 随机交换 10% 样本的特征值
  3. 模拟特征漂移(平移分布均值)

评估指标

from sklearn.metrics import roc_auc_score

def evaluate_robustness(model, X_original, X_perturbed, y_true):
    auc_original = roc_auc_score(y_true, model.predict_proba(X_original)[:,1])
    auc_perturbed = roc_auc_score(y_true, model.predict_proba(X_perturbed)[:,1])

    robustness = 1 - abs(auc_original - auc_perturbed) / auc_original
    return robustness

总结

通过将特征稳定性纳入决策树的分裂标准,我们有效降低了模型对数据波动的敏感性。在实际金融风控项目中,这种改进使模型的周环比预测一致性从 72% 提升到了 89%,同时保持了原有的准确率水平。建议在以下场景优先考虑此方案:

  • 特征工程阶段发现高方差特征
  • 线上数据分布随时间明显变化
  • 模型可解释性要求较高的场景

未来可以探索的方向包括:将稳定性约束扩展到其他树模型(如 XGBoost),以及开发端到端的自动稳定性优化框架。

正文完
 0
评论(没有评论)