ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Bagging与DCRN的财务造假预测源码实战:从特征工程到集成学习

基于Bagging与DCRN的财务造假预测源码实战:从特征工程到集成学习 简介这份资源面向金融风控、数据挖掘方向的学习者与竞赛选手提供一套完整的上市公司财务数据造假预测方案涵盖从特征工程到深度学习建模的全流程。压缩包共33个文件约35.46MB包含15个csv数据集、9个py源码脚本、3张png结果图以及xlsx、pdf、md等说明文档覆盖数据预处理、模型训练与评估各环节。方案先用SMOTE过采样结合决策树、随机森林、ExtraTree、XGBoost四种树模型计算特征重要性均值筛选制造业与其他行业前30个关键特征再以多层感知机、残差网络和Cross网络构建DCRN模型通过短路连接与类外积运算增强特征交互并引入Batch Normalize加速收敛、缓解过拟合最后叠加Bagging集成降低方差。目前已有191人学习读者可获得可复现的源码、数据集、训练模型与运行说明以及ROC曲线和特征重要性等分析结果适合作为赛题复现与深度学习二分类实战的参考。1. 财务造假预测这套源码为什么值得你花一个下午跑通上市公司财务造假预测听起来像是一个离业务很远的研究课题但真正做过风控建模的人都知道它本质上是一个典型的「高维表格数据 极度不平衡二分类」问题。这类问题在信贷违约、保险欺诈、设备故障预警里反复出现换的只是字段名。这套基于 Bagging 和深度学习的上市公司财务数据造假预测源码把一整套从特征工程到集成学习的流程打包好了包含 DCRN 网络模型、Bagging 集成脚本、K 折交叉验证、ROC 曲线绘制和特征重要性分析数据侧给了附件 CSV 和预处理后的 preprocessed_data.csv。适合谁适合已经会跑 Python、想找一个结构完整、能改能调的二分类项目练手的人也适合做风控方向、想看看深度学习在表格数据上怎么和树模型打配合的从业者。它不解决你的业务问题但它给你一个能跑通、能拆开看、能替换数据的骨架。2. 数据管道与特征筛选从原始附件到 preprocessed_data.csv2.1 先搞清楚数据长什么样拿到压缩包第一件事不是急着跑 main.py而是把 data 目录下的文件过一遍。附件1.csv、附件2.csv、附件3.xlsx 是原始数据preprocessed_data.csv 是已经处理过的版本。我一般会先做三件事看行数列数、看标签分布、看缺失值比例。财务造假预测的标签通常是 0/11 代表造假这类数据里 1 的占比往往很低可能不到 5%。如果你直接拿原始数据训练模型会倾向于全预测 0准确率看着很高但召回率惨不忍睹。import pandas as pd df pd.read_csv(data/preprocessed_data.csv) print(df.shape) print(df[label].value_counts(normalizeTrue)) print(df.isnull().sum().sort_values(ascendingFalse).head(10))这段代码做三件事打印数据维度、看标签比例、看缺失值最多的前 10 个字段。参数上没什么好调的但你要注意 label 这个列名不同数据集可能叫 target、flag、is_fraud跑之前先确认。如果标签比例低于 1:20后面训练时必须处理不平衡否则模型学不到正类。2.2 特征工程feature_eng.py 里做了什么feature_eng.py 是特征工程的主脚本config.py 里放了路径和超参数。这套源码的特征筛选思路是先用 SMOTE 过采样把正负样本拉平然后跑 DecisionTree、RandomForest、ExtraTree、XGBoost 四种树模型各自算特征重要性再取均值最后选出制造业和其他行业各前 30 个特征。这个做法在表格数据里很常见因为单棵树的特征重要性有随机性多模型平均能稳一些。from sklearn.ensemble import RandomForestClassifier from sklearn.tree import DecisionTreeClassifier from xgboost import XGBClassifier from sklearn.ensemble import ExtraTreesClassifier import numpy as np models { dt: DecisionTreeClassifier(random_state42), rf: RandomForestClassifier(n_estimators200, random_state42), et: ExtraTreesClassifier(n_estimators200, random_state42), xgb: XGBClassifier(n_estimators200, random_state42, use_label_encoderFalse) } importance_dict {} for name, model in models.items(): model.fit(X_train, y_train) importance_dict[name] model.feature_importances_ avg_importance np.mean(list(importance_dict.values()), axis0) top30_idx np.argsort(avg_importance)[-30:]逻辑说明四个模型分别拟合训练集拿到各自的 feature_importances_然后按列取平均。n_estimators 设 200 是常见起点树太少重要性波动大树太多跑得慢。random_state 固定住是为了结果可复现不然每次跑出来的 top30 特征可能不一样后面模型效果对不上。注意 XGBoost 的 use_label_encoder 参数在新版本里已经废弃如果报错就删掉。选前 30 个特征是人为设定的阈值你可以改成 20 或 50看验证集效果调。2.3 预处理后的数据怎么接进训练流程preprocessed_data.csv 应该是已经做过缺失值填充和标准化后的版本。如果你要换自己的数据需要保证几点标签列名和 config.py 里一致、数值列没有无穷值、类别列已经做过编码。常见做法是把所有特征转成 float标签转成 int。如果数据里有日期列要么去掉要么拆成年月日三个数值特征不要直接丢进去。X df.drop(columns[label]).astype(float) y df[label].astype(int) from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )stratifyy 是关键参数保证训练集和测试集里正负样本比例一致。如果不加这个测试集里可能一个正样本都没有评估指标就没意义了。test_size 设 0.2 是常规做法数据量小的时候可以调到 0.3留更多测试样本。3. DCRN 网络拆解多层残差、Cross 网络和 BatchNorm 怎么搭3.1 为什么不用纯树模型而选深度学习树模型在表格数据上一直很强XGBoost、LightGBM 在很多比赛里碾压深度学习。但这套源码选 DCRN 是有理由的树模型对特征交叉的捕捉是隐式的靠分裂节点逼近而 Cross 网络可以显式地做特征外积把两个特征的交互直接算出来。另外树模型对少量样本的泛化能力有限深度学习靠参数共享和 BatchNorm 可以在小样本上也能学到一些模式。当然这不是说深度学习一定更好而是给你多一个选择。实际跑下来DCRN 加 Bagging 的 AUC 可能和调好的 XGBoost 差不多但它的结构更灵活你可以改子网络、加层、换激活函数。3.2 DCRN.py 里的三个子网络DCRN 的全称是 Deep-CrossResidual-NetWork由三部分组成多层感知机MLP、多层残差网络、Cross 网络。MLP 就是全连接层堆叠负责基础特征变换。残差网络通过短路连接把输入直接加到输出上解决深层网络的梯度消失问题。Cross 网络做的是特征交叉公式类似 x1 * x2 的外积操作但加了权重矩阵让网络自己学交叉的强度。import torch import torch.nn as nn class ResidualBlock(nn.Module): def __init__(self, dim): super().__init__() self.fc nn.Linear(dim, dim) self.bn nn.BatchNorm1d(dim) self.relu nn.ReLU() def forward(self, x): out self.fc(x) out self.bn(out) out self.relu(out) return x out # 短路连接逻辑说明ResidualBlock 里先做线性变换再过 BatchNorm 和 ReLU最后把输入 x 加回来。dim 是特征维度必须保持一致不然加法对不上。BatchNorm1d 在表格数据上很关键它把每层输出拉回均值 0 方差 1加速收敛也有正则化效果。注意训练和推理时 BatchNorm 行为不同推理时要调 model.eval()不然结果会飘。3.3 Cross 网络的实现细节Cross 网络的核心是交叉层每一层做一次特征外积。假设输入是 x0第 l 层的输出是 x_l交叉操作可以写成 x_{l1} x0 * (W * x_l b) x_l。这个设计让网络能显式地建模二阶甚至高阶特征交互。class CrossLayer(nn.Module): def __init__(self, dim): super().__init__() self.weight nn.Parameter(torch.randn(dim)) self.bias nn.Parameter(torch.zeros(dim)) def forward(self, x0, xl): # x0: 初始输入, xl: 当前层输入 cross x0 * (self.weight * xl self.bias) return cross xlweight 和 bias 是可学习参数维度等于特征数。x0 是初始输入在整个 Cross 网络里保持不变这样每一层都能和原始特征做交叉。常见做法是堆 2 到 3 层 Cross 层太多会过拟合。如果数据特征维度很高比如超过 100Cross 层的参数量会很大这时候可以先降维再进 Cross 网络。3.4 BatchNorm 和丢弃过采样的取舍摘要里提到这套模型舍弃了 SMOTE 过采样依靠神经网络自身的学习能力捕捉少量样本。这个选择有争议但可以理解过采样会引入重复样本让模型记住正类的特定模式泛化到新数据时可能翻车。BatchNorm 在这里起到稳定训练的作用它让每层的输入分布保持稳定梯度不会爆炸或消失。如果你发现模型在正类上召回率很低可以试试加回 SMOTE或者用 Focal Loss 替代交叉熵让模型更关注难分类的样本。4. Bagging 集成与 K 折交叉验证把方差压下来4.1 bagging.py 做了什么Bagging 的核心思想是从训练集里有放回地抽多个子集每个子集训练一个模型最后投票或平均。这套源码在 DCRN 基础上做 Bagging目的是降低方差。深度学习模型本身方差就高不同初始化、不同数据顺序都会导致结果波动Bagging 能把这种波动平均掉。from sklearn.utils import resample import numpy as np n_estimators 10 models [] for i in range(n_estimators): X_boot, y_boot resample(X_train, y_train, replaceTrue, random_statei) model DCRN(input_dimX_train.shape[1]) model.fit(X_boot, y_boot) models.append(model) def predict_ensemble(models, X): preds np.mean([m.predict_proba(X) for m in models], axis0) return predsn_estimators 设 10 是起点越多越稳但越慢。resample 的 replaceTrue 表示有放回抽样random_state 每个模型不同保证子集有差异。预测时取概率平均不是投票因为二分类的概率平均比硬投票更平滑。如果跑得动可以加到 20 或 30 个基模型但收益递减。4.2 K 折交叉验证怎么用k_fold_cross_valid.py 是验证脚本用来评估模型稳定性。K 折的做法是把数据分成 K 份每次拿 K-1 份训练1 份验证循环 K 次最后看平均指标。这套源码里 K 通常设 5 或 10。from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) auc_scores [] for train_idx, val_idx in skf.split(X, y): X_tr, X_val X.iloc[train_idx], X.iloc[val_idx] y_tr, y_val y.iloc[train_idx], y.iloc[val_idx] model DCRN(input_dimX.shape[1]) model.fit(X_tr, y_tr) preds model.predict_proba(X_val)[:, 1] auc_scores.append(roc_auc_score(y_val, preds)) print(fMean AUC: {np.mean(auc_scores):.4f} ± {np.std(auc_scores):.4f})StratifiedKFold 保证每折里正负样本比例一致shuffleTrue 打乱顺序。AUC 是这类不平衡数据的首选指标比准确率靠谱。看结果时不要只看均值标准差也很重要如果标准差超过 0.05说明模型不稳定可能要加数据或调参。4.3 ROC 曲线和特征重要性输出roc_curve 目录下应该有画 ROC 曲线的脚本features_imp 目录下是特征重要性图。ROC 曲线横轴是假正率纵轴是真正率曲线下面积就是 AUC。画图时记得把多个模型的曲线画在一起对比比如 DCRN、Bagging DCRN、XGBoost这样能直观看出差距。import matplotlib.pyplot as plt from sklearn.metrics import roc_curve, auc fpr, tpr, _ roc_curve(y_test, preds) roc_auc auc(fpr, tpr) plt.figure() plt.plot(fpr, tpr, labelfDCRN (AUC {roc_auc:.3f})) plt.plot([0, 1], [0, 1], k--) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.legend() plt.savefig(roc_curve/dcrn_roc.png)这段代码画单条 ROC 曲线k-- 是对角线代表随机猜测。保存图片时注意路径roc_curve 目录要提前建好不然会报错。特征重要性图可以用 barh 横向柱状图把 top30 特征的名字和权重画出来方便业务方看哪些指标最关键。5. 避坑与排查跑这套源码时最容易翻车的五个地方5.1 标签列名对不上KeyError 直接报错现象跑 main.py 时报 KeyError: label或者提示找不到目标列。原因不同数据集的标签列名不一样config.py 里写的是 label但你的数据可能叫 target、flag、is_fraud。解决打开 config.py找到 TARGET_COL 这个变量改成你数据里实际的列名。如果列名有空格或特殊字符先用 df.columns 打印出来确认。5.2 正负样本极度不平衡模型全预测 0现象训练完准确率 95% 以上但召回率为 0AUC 接近 0.5。原因正类样本太少模型学到「全预测负类」就能拿高准确率。解决先看标签比例如果正类低于 5%要么用 SMOTE 过采样要么在损失函数里加类别权重。PyTorch 里可以用 pos_weight 参数给正类加权sklearn 里用 class_weightbalanced。5.3 BatchNorm 在推理时没切 eval 模式结果每次不一样现象同一个测试样本跑两次预测结果不同。原因模型还在训练模式BatchNorm 用当前 batch 的均值和方差而不是全局统计量。解决预测前调 model.eval()并且用 torch.no_grad() 包住推理过程。如果用了 Dropout也要切 eval 模式不然会随机丢神经元。5.4 特征重要性每次跑出来不一样top30 特征对不上现象两次运行 feature_eng.py选出的 top30 特征有差异。原因树模型的随机性random_state 没固定或者 n_estimators 太小。解决所有模型都设 random_state42n_estimators 至少 200。如果还波动把四种模型的权重平均改成取交集或者用 SelectFromModel 做稳定性选择。5.5 内存不够Bagging 跑一半崩了现象跑 bagging.py 时内存溢出程序被 kill。原因每个基模型都复制一份数据10 个模型就是 10 倍内存。解决减少 n_estimators或者用增量学习的方式每次只加载一个子集。如果数据本身很大先把 float64 转成 float32能省一半内存。另外PyTorch 的 DataLoader 设 num_workers0 可以减少内存开销。6. 换自己的数据怎么改从字段映射到模型微调6.1 字段映射和配置文件修改这套源码的 config.py 是入口所有路径和超参数都在里面。换数据时先改 DATA_PATH 指向你的 CSV再改 TARGET_COL 和 FEATURE_COLS。如果特征列很多不用手动列用 drop 排除标签列和 ID 列就行。常见做法是保留一个 id 列用于追溯但训练时要去掉不然模型会学到 ID 和标签的虚假关联。# config.py 示例 DATA_PATH data/your_data.csv TARGET_COL is_fraud DROP_COLS [id, company_name, report_date] TEST_SIZE 0.2 RANDOM_STATE 42DROP_COLS 里放的是不能进模型的列比如公司名称、报告日期、ID。这些列要么是字符串要么和标签有泄漏关系。report_date 可以拆成年份和季度两个数值特征但不要直接丢原始日期。6.2 模型微调的几个关键参数DCRN 的可调参数集中在 DCRN.py 和 config.py 里。隐藏层维度、Cross 层数、学习率、batch size 是最影响效果的四个。隐藏层维度一般设 64 或 128太小欠拟合太大过拟合。Cross 层数 2 到 3 层够了再多参数量爆炸。学习率从 1e-3 开始用 Adam 优化器如果 loss 震荡就降到 1e-4。batch size 设 64 或 128太小训练慢太大泛化差。# 训练循环里的关键参数 optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) criterion nn.BCEWithLogitsLoss(pos_weighttorch.tensor([5.0])) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience5)weight_decay 是 L2 正则防止过拟合。pos_weight 给正类加权5.0 表示正类损失放大 5 倍具体值看正类比例一般是负类数除以正类数。scheduler 在验证 loss 不降时自动降学习率patience5 表示等 5 个 epoch。6.3 验证模型是否真的学到了东西跑完训练不要只看 loss 曲线。我一般会做三件事看混淆矩阵、看不同阈值下的召回率和精确率、看特征重要性是否符合业务直觉。如果模型把「审计意见」这种强特征排到 50 名开外大概率有问题。另外用 SHAP 值解释单个预测看看哪些特征把样本推向了正类。from sklearn.metrics import confusion_matrix, classification_report preds_binary (preds 0.5).astype(int) print(confusion_matrix(y_test, preds_binary)) print(classification_report(y_test, preds_binary))阈值 0.5 是默认的但实际业务里可以调。如果更看重召回率把阈值降到 0.3宁可错杀不可放过。classification_report 会输出精确率、召回率、F1看哪个指标对你的场景更重要。6.4 一个我踩过的坑数据泄漏有一次我跑类似项目AUC 高到 0.99高兴了半天后来发现预处理时把测试集的数据也算进了标准化参数里。正确做法是标准化、缺失值填充这些操作只能在训练集上 fit然后 transform 测试集。这套源码的 preprocessed_data.csv 如果是全局处理的换数据时要注意重新做一遍。从那以后我每次跑新数据都强制先切分训练测试集再做任何预处理这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进