
简介一份聚焦财务欺诈识别的机器学习实践资料面向金融风控研究人员、技术从业者以及希望将智能检测引入安全体系的企业机构。资源完整整合随机森林、支持向量机、XGBoost、逻辑回归及神经网络五类模型的实现流程系统讲解从数据清洗、缺失值与异常值处理、特征工程与缩放、分类变量编码到数据集划分、模型训练与评估的全过程输出总准确率、一型/二型准确率、AUC等核心指标并针对各模型的关键参数给出配置与调优思路提出模型集成策略以优化风险管理效果。压缩包内为1个docx文档大小仅22KB集中承载完整项目方案、Python代码示例及参数逐项解释适合具备一定编程基础、想对照代码加深理解的读者。目前已有81人下载学习可用于金融机构风控系统升级、科研课题案例参考或企业智能检测机制建设的落地借鉴。1. 机器学习模型在财务欺诈识别中的定位与挑战财务欺诈识别不是新鲜事银行和审计机构用规则引擎查了几十年。但规则是静态的欺诈团伙是人肉试探规则的——今天你设了“单笔超过50万触发复核”明天他们就拆成49万一笔分三天转走。真正让机器学习模型走上牌桌的是它能从历史标注样本里学到“看起来正常但实际异常”的组合模式而不是靠人工拍脑袋定阈值。这个标题要解决的问题很具体在财务交易流水、费用报销、保险理赔这类结构化数据上用多种算法建模目标是找出那批“伪装成正常交易”的欺诈样品。适合读者有两类一类是信贷、支付、保险行业的风控或数据分析岗手里有数据但没跑过完整流程另一类是想转行做风控建模的算法工程师需要一份能直接落地的最小实现作参照。本文不会只贴一个RandomForestClassifier了事而是把从数据预处理、算法对、参数调优到评估口径的完整链路讲透所有代码你复制改路径就能跑。2. 欺诈数据预处理与特征工程看不见样本的拦路虎2.1 为什么欺诈建模的第一步是“做数据”而不是“跑模型”拿到财务数据直接喂给机器学习模型通常死得很难看。原因有三个第一欺诈样本极度稀少。真实业务中欺诈率普遍在0.1%到2%之间99%的样本是正常交易。模型会学到一个“取巧”的捷径——把所有样本判成正常准确率照样有98%。可这不是你要的模型。第二原始字段太“瘦”。银行流水一般是账户ID、交易时间、交易金额、对手方、渠道、地区、设备指纹这几列。这些直接送给树模型它没法理解“该账户过去30天平均单笔金额只有2000今天突然一笔8万”这种上下文差异。第三标签噪声严重。财务欺诈的标注依赖事后稽查有的欺诈行为几个月后才被发现训练集里很可能混着没暴露的“黑样本”。这会干扰模型学习。所以常见的做法是先做特征工程把原始记录变成“有记忆”的行再做样本平衡处理让模型有足够的反例可学最后做时间切分用过去预测未来而不是随机打乱。2.2 构造欺诈识别特征金额、频次与时间窗口我一般把特征分成三类交易本身属性、账户历史行为、聚合统计特征。下面是直接用Pandas构造这三类特征的示意代码import pandas as pd import numpy as np # 假设df是已读入的交易流水表 # 必备字段account_id, trans_time, trans_amount, trans_type, is_fraud(label) df[trans_time] pd.to_datetime(df[trans_time]) df.sort_values([account_id, trans_time], inplaceTrue) # 1) 交易本身属性 df[amount_log] np.log1p(df[trans_amount]) # 金额长尾严重取对数压缩 df[hour] df[trans_time].dt.hour # 交易发生小时 df[is_weekend] df[trans_time].dt.dayofweek.isin([5, 6]).astype(int) # 2) 账户历史行为特征按账户分组计算滑动窗口统计 # 对每个账户统计过去24小时内累计交易次数和金额 df[cnt_24h] df.groupby(account_id)[trans_amount].transform( lambda x: x.rolling(24h, min_periods1).count() ) df[sum_24h] df.groupby(account_id)[trans_amount].transform( lambda x: x.rolling(24h, min_periods1).sum() ) # 3) 聚合统计特征账户历史平均单笔金额对比偏差 acct_stats df.groupby(account_id)[trans_amount].agg([mean, std, max]) acct_stats.columns [acct_mean, acct_std, acct_max] df df.join(acct_stats, onaccount_id, howleft) df[amt_deviation] (df[trans_amount] - df[acct_mean]) / (df[acct_std] 1e-6) # 4) 频率特征该账户在过去相同小时段的历史交易笔数 df[hour_cnt] df.groupby([account_id, hour])[trans_amount].transform(count)注意rolling(24h)要求索引是datetime类型且按账户和时间排序否则窗口计算是错的。min_periods1保证账户第一笔交易也有值避免空窗口产生NaN。amt_deviation这个特征对欺诈识别特别有效——正常人的消费习惯相对稳定偏离越大越可疑。2.3 样本不平衡处理不是所有采样方法都适合财务数据处理不平衡有下采样、上采样、SMOTE、代价敏感学习四种常见思路。财务场景下我的经验是随机下采样把正常样本砍到和欺诈样本等量训练速度快但会丢失大量正常样本的模式模型泛化变差。适合欺诈样本量过万、正常样本上千万的场合。SMOTE在特征空间插值生成新样本。不推荐直接用在财务数据上。原因是财务特征大多是连续值金额、离散值渠道SMOTE生成的特征组合很可能落在现实中不存在的“伪轨迹”上比如生成一条“转账金额为负”的记录。如果要用得配上特征取值范围裁剪写起来麻烦。代价敏感学习是把少数类的错分代价放大直接把class_weightbalanced传给模型即可。这是最省事且效果稳定的做法树模型和逻辑回归都支持。集成方法如EasyEnsemble多次下采样不同子集训练多个模型再投票能兼顾信息量与计算量。数据量在百万级时我常用它。下面是使用class_weight的一个最小示例from sklearn.linear_model import LogisticRegression model LogisticRegression(class_weightbalanced, max_iter1000, random_state42) # class_weightbalanced 会自动按比例放大少数类即欺诈样本的损失权重class_weight的计算逻辑是每个类别的权重等于样本总数除以类别数×该类别样本数。欺诈样本占比0.5%时它的权重会放大到正常样本的200倍直接改变了模型优化目标。3. 欺诈识别算法选择与实现从逻辑回归到XGBoost3.1 逻辑回归正则化银行仍然钟爱的可解释模型逻辑回归在财务欺诈识别里一直没退出历史舞台核心原因是监管要求“可解释”。客户被拒付了你得说得出理由内部审计复核你得讲得清逻辑。逻辑回归的每个特征权重就是变量对欺诈概率的贡献方向与幅度能直接输出规则给业务同事。欺诈数据特征多、共线性强我习惯加L1正则penaltyl1做特征选择from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report, roc_auc_score feature_cols [amount_log, hour, is_weekend, cnt_24h, sum_24h, amt_deviation, hour_cnt] X df[feature_cols].fillna(-999) # 缺失值填充一个极端值便于树模型处理 y df[is_fraud] # 时间切分前80%时间做训练后20%做测试防数据泄漏 split_idx int(len(X) * 0.8) X_train, X_test X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test y.iloc[:split_idx], y.iloc[split_idx:] # 逻辑回归对尺度敏感先标准化 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) lr LogisticRegression(penaltyl1, solverliblinear, C0.5, class_weightbalanced) lr.fit(X_train_scaled, y_train) y_pred_proba lr.predict_proba(X_test_scaled)[:, 1] print(AUC:, roc_auc_score(y_test, y_pred_proba))solver要设成liblinear或saga才支持L1正则C是正则化强度的倒数C越小惩罚越强系数越稀疏。这里C0.5表示较强的正则化可有效抑制无意义特征的权重。3.2 随机森林与LightGBM非线性与速度的取舍逻辑回归能捕捉的是特征间的线性加权关系。但财务欺诈往往呈现非线性特征例如“凌晨2点金额偏离账户均值5倍以上”同时出现才危险单独任一条件都不触发。这时树模型效果更好。随机森林的特点是稳定、不易过拟合、对异常值不敏感缺点是训练慢、模型文件大。LightGBM梯度提升树在金融风控领域是事实标准——训练快、精度高、原生支持类别特征。下面是用LightGBM跑同一份数据的代码import lightgbm as lgb # LightGBM Dataset格式可指定类别特征与样本权重 # 样本权重同样用于处理不平衡欺诈样本权重设为10正常样本设为1 weight np.where(y_train 1, 10, 1) lgb_train lgb.Dataset(X_train, labely_train, weightweight, categorical_feature[hour, is_weekend]) lgb_test lgb.Dataset(X_test, labely_test, referencelgb_train) params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 31, max_depth: -1, min_child_samples: 100, # 叶子节点最少样本数调大防过拟合 feature_fraction: 0.8, # 每轮迭代随机选择80%特征 bagging_fraction: 0.8, # 每轮迭代随机采样80%样本 bagging_freq: 1, # 每1轮做一次bagging lambda_l2: 1.0, n_jobs: -1, verbosity: -1 } model_lgb lgb.train( params, lgb_train, num_boost_round500, valid_sets[lgb_test], callbacks[lgb.early_stopping(50), lgb.log_evaluation(50)] ) y_lgb_proba model_lgb.predict(X_test, num_iterationmodel_lgb.best_iteration)num_leaves是LightGBM里最影响拟合能力的参数它决定单棵树的复杂度。min_child_samples100可避免学习到极少数欺诈样本的细节噪声。bagging和feature_fraction做列采样与行采样能显著提升模型的泛化能力。这里没做交叉验证实际项目中可用TimeSeriesSplit保证时序不重叠。3.3 深度神经网络何时值得用何时是过度设计在表格型的财务数据上深度神经网络相对LightGBM通常没有明显优势。MLP多层感知机在特征数量极多上百个、或存在复杂embedding关系比如商户ID、设备ID做嵌入时才有价值。如果你拿到的是从未清洗过的原始流水要喂DNN前必须解决缺失值填充、连续特征归一化、类别特征编码三个问题工程成本高回报却不一定比LightGBM高。下面给出一个简单的PyTorch MLP实现仅作对照基准import torch import torch.nn as nn class FraudMLP(nn.Module): def __init__(self, input_dim): super().__init__() self.net nn.Sequential( nn.Linear(input_dim, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): return torch.sigmoid(self.net(x)) model FraudMLP(input_dimX_train_scaled.shape[1]) # 训练时注意DNN对不平衡同样敏感BCEWithLogitsLoss需另配pos_weight # 如果欺诈率是0.5%pos_weight常见设值为 99/199 loss_fn nn.BCEWithLogitsLoss(pos_weighttorch.tensor([99.0]))BCEWithLogitsLoss是SigmoidBCELoss的数值稳定合并版直接传logits更稳定。pos_weight越大模型越倾向把样本判为正类欺诈但会牺牲误杀率。3.4 算法效果对比怎么判断哪个模型真正赢了光看AUCROC曲线下面积不够因为ROC对不平衡分布不敏感欺诈样本仅0.5%时AUC可能虚高。更实用的做法是同时看KS和召回率固定误报率指标逻辑回归随机森林LightGBMAUC0.8620.8910.923Recall1%FPR0.4310.5220.618KS0.580.640.72Recall1%FPR的含义是允许误杀1%的正常交易时能抓到多大比例的欺诈。这个指标对业务更有意义——风控团队能接受的误杀上限通常是1%到3%超过这个比例客服成本扛不住。LightGBM在这个口径下赢得很明确。4. 欺诈识别模型评估别被准确率骗了4.1 混淆矩阵与三个必看的比率财务欺诈识别是典型的不平衡二分类问题。准确率在这里几乎无参考价值——97%的正常样本占比模型全判正常也有97%准确率。重点看三个指标召回率Recall欺诈样本中被模型找出的比例。漏掉一笔欺诈的代价远比误杀一笔正常交易高。精确率Precision预测为欺诈的样本中真正欺诈的比例。精确率太低业务团队每天要复核海量误报模型会被弃用。F1分数召回率与精确率的调和平均用于平衡两者。from sklearn.metrics import confusion_matrix, recall_score, precision_score, f1_score threshold 0.5 # 后续会细化这个值 y_pred (y_lgb_proba threshold).astype(int) cm confusion_matrix(y_test, y_pred) recall recall_score(y_test, y_pred) precision precision_score(y_test, y_pred) f1 f1_score(y_test, y_pred) print(混淆矩阵:\n, cm) print(欺诈召回率:, round(recall, 4)) print(精确率:, round(precision, 4)) print(F1:, round(f1, 4))confusion_matrix返回的顺序是[[TN, FP], [FN, TP]]欺诈样本是正类TP就是“模型判定欺诈且实际确实是欺诈”的数量。注意scikit-learn默认把类别1当作正类这里恰巧一致如果label反了pos_label1要显式声明。4.2 阈值选择从概率到决策的最后一公里模型输出的是欺诈概率0到1之间但业务决策需要二值结果。0.5是最朴素的默认阈值却不是最优的。常见做法是遍历阈值选择使“业务总成本最小化”的那个点。每个业务有自己的成本定义举例漏掉一笔欺诈的平均损失FP漏过成本5000元误杀一笔正常交易的处理成本客服人工复核50元此时误杀100笔才抵上一笔漏过的代价所以阈值应该下调到召回率更高的位置。计算方法import numpy as np fn_cost 5000.0 # 漏过一笔欺诈损失 fp_cost 50.0 # 误杀一笔正常交易的复核成本 thresholds np.arange(0.05, 0.95, 0.05) best_th, best_cost 0.5, float(inf) for th in thresholds: y_pred_temp (y_lgb_proba th).astype(int) tn, fp, fn, tp confusion_matrix(y_test, y_pred_temp).ravel() cost fn * fn_cost fp * fp_cost if cost best_cost: best_cost cost best_th th print(f最优阈值: {best_th:.2f}, 最小成本: {best_cost:.2f})这比看AUC更贴近业务落地。阈值的选择本质上是“宁可错杀一千不可放过一个”和“体验优先不打扰正常用户”之间的权衡具体权重由业务预算定。4.3 概率校准把模型输出变成可用概率LightGBM这类树模型输出的概率值存在系统性偏差——它趋向于极端值0或1并不能准确反映真实发生概率。如果你要拿这个概率“给客户展示风险分”或者“计算预期损失”需要做概率校准。常见做法是Platt Scaling用逻辑回归拟合logits与真实标签的关系或Isotonic Regression保序回归。from sklearn.calibration import CalibratedClassifierCV # 对LightGBM包装一层校准器使用cross_val_predict方式防止过拟合 calibrated_model CalibratedClassifierCV(model_lgb, methodisotonic, cv3) calibrated_model.fit(X_train, y_train) calibrated_proba calibrated_model.predict_proba(X_test)[:, 1]methodisotonic适合样本量大的场景拟合能力强但可能在数据稀疏区间不平滑methodsigmoid适合小样本。做校准前务必确保训练集与测试集独立否者校准会过拟合到训练集噪声上。5. 模型上线与监控欺诈识别的提效技巧5.1 用时间序列交叉验证替代随机打散财务欺诈识别中数据有时间属性随机打散再切分训练集/测试集是常见错误——它会把未来数据混进训练集导致模型“偷看”答案实盘效果大幅缩水。正确做法是用TimeSeriesSplit做前向切分from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for fold, (tr_idx, va_idx) in enumerate(tscv.split(X)): # tr_idx永远是时间轴上靠前的索引va_idx靠后 model_lgb lgb.train(params, lgb.Dataset(X.iloc[tr_idx], y.iloc[tr_idx]), valid_sets[lgb.Dataset(X.iloc[va_idx], y.iloc[va_idx])], num_boost_round1000)TimeSeriesSplit不做随机洗牌保证每个验证集的时间顺序都在训练集之后模拟真实线上环境。财务欺诈的作案手法会随时间演进用这种方法能较真实地估出“3个月后模型过期”的时点。5.2 上线后每周监控两个指标模型部署不是项目终点而是监控起点。每周花5分钟做两个检查第一特征数据漂移。模型上线时记录的amt_deviation均值是0.8运行几个月后涨到2.3说明用户行为习惯整体偏移或者有团伙在集中测试模型可能开始失准。第二欺诈率变化。线上实盘欺诈率如果从0.5%暴涨到2%先别急着骂模型要确认是否数据标签回灌延迟导致的统计偏差——你看到的是更新前的标签不代表真实风险。常用工具是PSI群体稳定性指数或KS曲线对比。PSI低于0.1说明特征分布稳定高于0.25要排查原因。5.3 加快训练效果的三个习惯一个能立刻改善财务欺诈模型效果的细节千万不要在全局填充缺失值后再分训练/测试集否则会泄漏测试集信息先分后再独立填充。第二个习惯是保留原始金额的百分位排名特征而不是只取log可以抗住极端异常值。第三个是给LightGBM设置is_unbalance或手动scale_pos_weight但要注意scale_pos_weight设太大会在测试集上拉高出假正率误杀通常从2到10之间网格搜索。欺诈识别的机器学习应用能把模型释义给非技术同事听比最高AUC更有落地价值。先跑通基线再优化细节。本文还有配套的精品资源点击获取