XGBoost实战:5种处理信用卡欺诈检测中样本不平衡的调参技巧
金融风控领域的从业者都知道,信用卡欺诈检测是个典型的"大海捞针"问题——正常交易占99%以上,而欺诈交易可能不到1%。这种极端不平衡的数据分布,让很多机器学习模型的表现大打折扣。上周我刚帮一家银行优化他们的欺诈检测系统,发现原始模型的召回率只有30%,这意味着70%的欺诈交易都没被抓住。经过一轮XGBoost参数调优后,我们将召回率提升到了85%,而误报率仅增加了2个百分点。
1. 权重调整:让模型看见那些被忽略的欺诈交易
在信用卡数据中,正常交易和欺诈交易的比例往往达到1000:1甚至更高。这种情况下,XGBoost会倾向于把所有样本都预测为正常交易,因为这样就能获得99.9%的"准确率"——这显然是个陷阱。
scale_pos_weight参数是XGBoost为解决这类问题提供的"急救包"。它的原理很简单:给少数类(欺诈交易)更高的权重,让模型在训练时更关注这些稀有样本。计算这个参数的最佳实践是:
# 计算正负样本比例
neg_pos_ratio = len(y_train[y_train==0]) / len(y_train[y_train==1])
xgb_params = {
'scale_pos_weight': neg_pos_ratio,
'objective': 'binary:logistic',
'eval_metric': 'aucpr' # 对于不平衡数据,PR曲线比ROC更敏感
}
但要注意,盲目放大这个权重会导致模型过于敏感。我的经验是:
- 初始值设为负正样本比
- 在0.5倍到2倍这个区间进行网格搜索
- 配合early_stopping防止过拟合
提示:在金融场景中,误报成本也很高。过高的scale_pos_weight会导致大量正常交易被拦截,影响用户体验。
2. 损失函数改造:教会模型理解业务代价
标准交叉熵损失函数假设误分类的代价是对称的,但在欺诈检测中,漏掉一个欺诈交易(False Negative)的代价可能是误拦截正常交易(False Positive)的100倍。
XGBoost允许我们自定义损失函数。下面是一个加权对数损失实现,给FN设置了10倍于FP的惩罚:
def weighted_logloss(preds, dtrain):
labels = dtrain.get_label()
preds = 1.0 / (1.0 + np.exp(-preds)) # 转换成概率
# 设置权重矩阵
weights = np.where(labels==1, 10, 1) # FN代价是FP的10倍
grad = preds - labels
hess = preds * (1.0 - preds)
return grad * weights, hess * weights # 加权后的梯度和二阶导
model = xgb.train(
params={'eval_metric': 'aucpr'},
dtrain=train_matrix,
num_boost_round=1000,
obj=weighted_logloss,
early_stopping_rounds=50
)
实际应用中,损失函数的权重应该与业务代价相匹配。建议:
- 与风控团队确认FN和FP的实际成本比
- 从小权重开始测试,逐步增加
- 监控验证集的召回率和精确率变化
3. 采样策略:在数据层面重塑平衡
当样本不平衡极其严重时(如1:10000),仅靠调整参数可能不够。这时可以考虑采样策略:
3.1 SMOTE过采样实战
SMOTE通过插值生成合成样本,比简单的复制更有效。但要注意:
- 只对训练集过采样,测试集保持原始分布
- 配合交叉验证避免数据泄露
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import make_pipeline
# 创建包含SMOTE的pipeline
smote_pipeline = make_pipeline(
SMOTE(sampling_strategy=0.1, random_state=42), # 将少数类提升到10%
xgb.XGBClassifier(**xgb_params)
)
# 使用TimeSeriesSplit避免打乱时间顺序
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, val_idx in tscv.split(X):
X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
smote_pipeline.fit(X_train, y_train)
preds = smote_pipeline.predict_proba(X_val)[:,1]
3.2 欠采样技巧
对于海量数据,可以随机欠采样多数类。进阶做法是:
- Tomek Links:移除边界附近的多数类样本
- Cluster Centroids:对多数类聚类后保留中心点
from imblearn.under_sampling import ClusterCentroids
cc = ClusterCentroids(
sampling_strategy={0: 100000}, # 保留10万多数类样本
random_state=42,
n_jobs=-1
)
X_res, y_res = cc.fit_resample(X, y)
4. 阈值优化:寻找业务最佳平衡点
XGBoost默认使用0.5作为分类阈值,但在欺诈检测中,这个值通常太高。我们可以:
- 在验证集上生成预测概率
- 遍历不同阈值,计算业务指标
- 选择最优阈值
from sklearn.metrics import precision_recall_curve
y_proba = model.predict_proba(X_val)[:,1]
precision, recall, thresholds = precision_recall_curve(y_val, y_proba)
# 寻找使2*recall + precision最大的阈值
f_score = (2 * recall + precision)
opt_idx = np.argmax(f_score)
optimal_threshold = thresholds[opt_idx]
# 应用最优阈值
y_pred = (y_proba >= optimal_threshold).astype(int)
对于信用卡欺诈,通常关注:
- 召回率:尽可能抓住更多欺诈
- 精确率:控制人工审核量
- 误报率:不影响正常用户
5. 评估指标选择:超越准确率的评判标准
在不平衡数据上,准确率是毫无意义的指标。我们应该关注:
| 指标 | 公式 | 适用场景 |
|---|---|---|
| AUC-PR | 精确率-召回率曲线下面积 | 极度不平衡时比AUC-ROC更敏感 |
| F1-Score | 2*(精确率*召回率)/(精确率+召回率) | 平衡精确率和召回率 |
| MCC | (TPTN - FPFN)/sqrt((TP+FP)(TP+FN)(TN+FP)(TN+FN)) | 考虑所有混淆矩阵元素 |
在XGBoost中设置多指标监控:
eval_metrics = {
'eval_metric': ['aucpr', 'error', 'logloss'],
'early_stopping_rounds': 50
}
model = xgb.train(
params=eval_metrics,
dtrain=train_matrix,
num_boost_round=1000,
evals=[(train_matrix, 'train'), (valid_matrix, 'valid')],
verbose_eval=50
)
实际项目中,我会先与业务方确认:
- 最不能接受的错误类型(通常是漏检欺诈)
- 可承受的最大误报率
- 人工审核团队的处理能力
然后根据这些约束来选择合适的评估指标。
150



被折叠的 条评论
为什么被折叠?



