财务舞弊检测实践 · 第 8

表格深度学习与无监督异常检测

第 5 章用随机森林把测试集 AUC 从单棵树的 0.548 推到 0.709,第 6 章 XGBoost 在测试集上落到 0.648,第 7 章用 RUSBoost 复刻 Bao 论文的旗舰结果,测试 AUC 0.6982。本章夹在中间,处理两个会被频繁问到的问题。第一个问题:既然深度学习在图像与文本上一统江湖,为什么会计 ML 圈还在反复用 XGBoost 和随机森林?第二个问题:如果手上的 AAER 标签本来就"阳性可信、阴性不可信",能不能干脆抛掉标签,让模型自己识别"长得不像普通公司"的样本?前一个问题的答案在表格深度学习里,后一个问题的答案在无监督异常检测里。本章把两条路径放在同一份 Bao 数据上跑一遍,看它们与监督树类方法差在哪里。

图 8·1 把两种方法的结构并排画出,左侧 MLP 用三层全连接把 42 维输入压成单一概率,右侧 Isolation Forest 用随机切分树把异常样本几刀就孤立开。两幅示意图共同展示本章核心:一种方法把所有特征同时喂进密集权重矩阵,另一种方法每次只挑一个变量切一刀。

图 8·1 表格深度学习与异常检测两种方法的结构对照:左侧 MLP 全连接前馈结构,输入层 42 维经两层隐层 426432142 \to 64 \to 32 \to 1 输出 sigmoid 概率 p^\hat p;右侧 Isolation Forest 异常样本路径短、普通样本路径长,路径长度作为异常分数依据。完整 TikZ 结构图详见 PDF 全文。

表格深度学习的位置

深度学习在图像和文本上的胜利来自一件事:原始像素或 token 序列里没有现成的结构化特征,神经网络靠多层非线性变换把"低层模式"逐步组装成"高层概念"。表格数据的处境刚好相反。Bao 的 42 个变量,每一列都已经是会计学家精心定义过的语义单元,比如总资产、销售收入、软性资产占比。这些特征本身就处在"高层概念"的层级上,再叠几层非线性变换并不能从中挖出新的结构。Grinsztajn 等人在 NeurIPS 2022 的基准研究里系统比较了 45 个公开表格数据集,结论是中等规模、列含义混合的表格上,XGBoost、随机森林这类基于树的模型在大多数任务里仍然胜过 MLP、ResNet、FT-Transformer。本章不挑战这条经验规律,而是在 Bao 数据上把它具体化:MLP 的测试 AUC 0.5944,明显低于 LASSO 的 0.6876、XGBoost 的 0.6480 与随机森林的 0.7087。

MLP 的会计学解读

多层感知机即 MLP,是最朴素的前馈神经网络。它把输入向量 xx 经过若干层"线性变换 + 非线性激活",最后输出一个实数得分,再用 sigmoid 把得分压到 (0,1)(0, 1) 区间作为舞弊概率。在会计语境里,MLP 的每一层隐藏神经元都可以看作一组"自动学出来的衍生比率",它们是原始 42 个变量的非线性组合。某个隐藏神经元可能学到"应付账款 ap 与销售收入 sale 的比值若超过某个阈值就给舞弊得分加 0.3",这种规则在线性逻辑回归里写不出来,因为它涉及变量之间的乘性交互。

具体看数字。本章网络两层隐藏神经元数分别是 64 和 32。第一隐藏层把 42 维输入压成 64 维中间表示,第二隐藏层再压成 32 维。举一个隐藏神经元,假设它学到的权重把 soft_assetsissue 的系数都设为 +1+1、其余全为零、偏置 0.8-0.8。某家公司的 soft_assets 是 0.6、issue 是 1,则该神经元输出 max(0,0.6+10.8)=0.8\max(0, 0.6 + 1 - 0.8) = 0.8;另一家公司这两个变量都接近零,则输出 max(0,0+00.8)=0\max(0, 0 + 0 - 0.8) = 0。当线性组合超过偏置阈值时,神经元才"激活"。

定义ReLU 激活函数

zRz \in \mathbb{R},整流线性单元简称 ReLU 定义为

ReLU(z)=max(0,z).\mathrm{ReLU}(z) = \max(0, z).

在反向传播里,当 z>0z > 0 时导数为 1,当 z<0z < 0 时导数为 0。这种分段线性的形式让深层网络的梯度更容易传播,也让单个神经元只对它"关心"的输入区域响应。

把多层这样的非线性单元串起来,整个 MLP 的预测函数可以写成

p^(x)=σ(W3ReLU(W2ReLU(W1x+b1)+b2)+b3),\hat{p}(x) = \sigma\bigl(W_3^\top \mathrm{ReLU}(W_2^\top \mathrm{ReLU}(W_1^\top x + b_1) + b_2) + b_3\bigr),

其中 σ(z)=1/(1+ez)\sigma(z) = 1/(1+e^{-z}) 是 sigmoid,W1R42×64W_1 \in \mathbb{R}^{42 \times 64}W2R64×32W_2 \in \mathbb{R}^{64 \times 32}W3R32×1W_3 \in \mathbb{R}^{32 \times 1} 是三层权重矩阵,b1,b2,b3b_1, b_2, b_3 是偏置。训练目标是最小化二元交叉熵,优化器用 Adam。从参数量看,逻辑回归只有 43 个参数,本章 MLP 的参数量是 42×64+64+64×32+32+32+1=4,86542 \times 64 + 64 + 64 \times 32 + 32 + 32 + 1 = 4{,}865。对训练样本仅 537 个舞弊正例的不平衡问题来说,4,865 个参数已经远超"信号 / 参数比"的安全区。

在 Bao 数据上的 MLP 实验

沿用 chap04 与 chap05 的数据流水线:剔除 42 特征任一为 NA 的行,z-score 标准化器仅在训练集上拟合,再 transform 测试集;MLP 用 sklearn.neural_network.MLPClassifier,设 hidden_layer_sizes=(64, 32)batch_size=256max_iter=200early_stopping=Truevalidation_fraction=0.15random_state=2026。早停从训练集里再切 15% 当内部验证集,每轮 epoch 后看验证准确率,连续若干轮不再提升就停下。

from sklearn.preprocessing import StandardScaler
from sklearn.neural_network import MLPClassifier
import numpy as np, random
random.seed(2026); np.random.seed(2026)

scaler = StandardScaler()
X_train = scaler.fit_transform(train[features].values)
X_test  = scaler.transform(test[features].values)

mlp = MLPClassifier(
    hidden_layer_sizes=(64, 32),
    activation="relu", solver="adam",
    batch_size=256, max_iter=200,
    early_stopping=True, validation_fraction=0.15,
    random_state=2026,
).fit(X_train, y_train)
结果解读MLP 训练

训练样本 63,930 行 / 537 阳性,测试样本 27,628 行 / 107 阳性。MLP 在第 12 个 epoch 触发早停,全部训练耗时 1.06 秒。内部验证集上的最佳准确率是 0.9914,看起来非常漂亮。但这个数字与"全部预测为非舞弊"的零模型准确率 0.9921 几乎贴住,提示模型其实在内部验证集上也基本是全预测多数类。第 1 章的雷区在这里再次浮现:极端不平衡下,准确率对零模型友好,对有判别力的模型不友好。

表 8·1 MLP 在测试集 2009–2014 上的性能,n=27,628,阳性 107

模型AUCNDCG@100Recall@1%Precision@1%
MLP [64,32][64, 32] ReLU0.59440.00000.01870.0072

AUC 0.5944 比第 4 章 LASSO 的 0.6876 低 0.09,比第 5 章随机森林的 0.7087 低 0.11,比第 6 章 XGBoost 的 0.6480 也低 0.05。NDCG@100 直接归零,意味着把 MLP 得分排前 100 名里没有一个真舞弊。Recall@1% 等于 0.0187,前 277 名只命中 2 个真舞弊,比同期 LASSO 的 6 个、RF 的 4 个都少。一个用了几千个参数学了 12 轮的神经网络,在 Bao 数据上明显跑不过两百多个参数的逻辑回归。

表格深度学习在小样本下的劣势

这种"MLP 跑不过树"的现象在表格学习里有普遍性,并不止于 Bao 数据。Grinsztajn 等人给出三个机制层面的解释。第一,树类模型在每个分裂点只看一个变量的阈值,对单特征的尺度变换、异常点、单调变换都天然不敏感;MLP 的全连接层把所有 42 个特征同时线性混合,一两个量级异常的特征就能扭曲整层权重的分布。第二,树是片段常数函数,能直接逼近"软性资产 >0.6> 0.6 时风险升一档"这种阶跃式决策;MLP 用平滑的 ReLU 复合需要更多神经元才能拟合阶跃形状。第三,树类模型对"无关变量"具有天然鲁棒性,分裂阈值搜索阶段会自动忽略噪声特征;而 MLP 的所有 42 维输入都被第一隐藏层 64 个神经元同时处理,噪声在前向传播里持续累积。

Bao 数据具备所有这三种"对 MLP 不利"的特性。28 个原始 Compustat 变量量级跨度极大,从 prcc_f 的两位数股价到 at 上百亿美元的总资产;14 个比率类变量大多以离散点为主、长尾分布。会计研究里反复出现的核心信号"软性资产占比突破阈值即可疑",本质上就是阶跃式决策。再加上训练集只有 537 个舞弊样本,MLP 的 4,865 个参数在如此少的正例上很难找到稳定的非线性结构,第 12 个 epoch 即触发早停,更像是"模型来不及学习"而非"模型已学好"。

雷区深度学习胜过树的经验规律有前提

机器学习文献里"深度学习胜过树"的经验大多在 n105n \geq 10^5 量级、变量在百到千、且标签较干净的数据上得到。Bao 训练集 n=63,930n = 63{,}930 看似不小,但正例只有 537、特征只有 42 列,且 AAER 标签存在阴性不可信的 noisy negative 问题。在这种"中等规模 + 极端不平衡 + 含噪标签"的表格上,MLP 落后树类方法不止一两个百分点,差距是整整一档。把 Grinsztajn 等人的结论挪用到会计 ML 时,必须先确认数据规模与标签质量是否符合那条经验的前提。

无监督异常检测:标签不可信场景下的备选方案

第 1 章讲过 AAER 标签的方向性问题:阳性是 SEC 经过调查后的认定,可信;阴性是"没被 SEC 找上",但其中藏着多少未被发现的舞弊不得而知。监督学习的所有方法都把阴性当成真阴性来训练,等于告诉模型"这些公司一定干净"。如果阴性里其实有相当数量的潜在舞弊,监督模型学到的决策面就被扭曲了。

无监督异常检测换了一个思路。它根本不看标签,只让算法回答一个问题:这家公司在 42 维特征空间里看起来"像不像普通公司"? 如果某家公司的特征向量在密集人群里很难找到邻居、或者只要切几刀就能把它从主体中孤立出来,就给它一个高异常分数。SEC 是否查到它、是否记入 AAER,都不影响打分。无监督方法的好处是绕开 noisy negative 问题;代价是它打出的"异常"不等同于"舞弊"。一家发生重大并购、业务换轨道的公司也可能在特征空间里显得突兀,但并不舞弊。

Isolation Forest 的直觉与算法

Liu 等人提出的 Isolation Forest 把异常检测重新定义为"切几刀能把它隔离"。设想 42 维特征空间里有 60,000 个公司点。如果随机挑一个特征、随机选一个切点,把空间切成两半,再对包含该样本的那一半接着递归切下去,每个样本最终都会被切到一个只有它自己的小立方体里。一个"普通"样本要被孤立,平均需要切很多刀,因为它周围有密集的邻居;一个"异常"样本周围邻居稀疏,只要切几刀就能与其它样本完全分开。被孤立所需要的"路径长度",就是 Isolation Forest 给每个样本的异常打分依据。

具体看数字。某棵随机切分树切到 64 行样本组成的子集时,假设这 64 行里某家公司在 prcc_f 上的取值离群,被一刀切到只有 1 行的子节点,该公司从根到叶的路径长度只有 4 步;而该子集里最普通的那家公司一路被切到深度 12 才孤立。前者得到的异常分数远高于后者。Isolation Forest 不止训练一棵树,它训练 200 棵互相独立的随机切分树,对每个样本的路径长度取平均,再做归一化。

定义Isolation Forest 路径长度

设某样本 xx 在第 bb 棵随机切分树里从根节点到孤立它的叶节点经过 hb(x)h_b(x) 次切分。BB 棵树上的平均路径长度为 hˉ(x)=1Bb=1Bhb(x)\bar{h}(x) = \frac{1}{B} \sum_{b=1}^{B} h_b(x)。对应的异常分数定义为

s(x,n)=2hˉ(x)/c(n),s(x, n) = 2^{-\bar{h}(x) / c(n)},

其中 c(n)c(n) 是 BST 里 nn 个样本的预期未成功搜索路径长度,作为对路径长度的归一化常数。s(x,n)s(x, n) 取值范围 [0,1][0, 1],越接近 1 越异常。

这个算法有两个工程优势。第一,它不依赖样本对样本的距离矩阵,每棵树训练是 O(nlogn)O(n \log n) 的,整个 forest 在 60,000 行 42 列数据上几秒就能跑完。第二,它对维数不敏感。基于距离的异常检测算法在 42 维空间里会被维度灾难拖累,因为高维下"两点距离"几乎处处相等;Isolation Forest 靠"切一刀"代替"算距离",不计算距离矩阵,不受维度灾难影响。

在 Bao 数据上的 Isolation Forest 实验

把训练集喂给 IsolationForest,关键参数是 n_estimators=200contamination=0.0066random_state=2026contamination 等于 0.0066 是按全样本舞弊率 0.66% 设定的,相当于告诉算法"我估计大约 0.66% 的样本是异常",它会用这个比例去标定输出阈值;如果只关心排序,把它设成多少都不影响相对排名。decision_function 返回的值越大代表越普通、越小代表越异常,本章为对齐"高分 = 可疑"的统一约定,对返回值取负号作为最终异常分数。

from sklearn.ensemble import IsolationForest

iforest = IsolationForest(
    n_estimators=200, contamination=0.0066,
    random_state=2026, n_jobs=4,
).fit(X_train)             # fit 只喂 X,不喂 y

iforest_score_test = -1.0 * iforest.decision_function(X_test)
结果解读IsolationForest 训练与评估

整个 forest 在 63,930 行训练集上训练耗时 0.52 秒。注意 fit 调用里没有 y_train 参数,标签从未进入训练流程。把测试集的 42 维特征向量送进去,得到每行的异常分数。测试集 AUC 0.5715、NDCG@100 0.0481、Recall@1% 0.0374、Precision@1% 0.0144。

表 8·2 Isolation Forest 测试集性能,n=27,628,阳性 107

模型AUCNDCG@100Recall@1%Precision@1%
IsolationForest 200 trees0.57150.04810.03740.0144

IsolationForest 的 AUC 0.5715,与 MLP 的 0.5944 在同一档。两个数字都明显高于零模型 0.500,说明无监督路径并非"完全无效",但都远低于 LASSO 的 0.6876 与随机森林的 0.7087。一个有趣的对照是 NDCG@100:IsolationForest 0.0481 远高于 MLP 的 0.0000,意味着 IF 把极少数真舞弊推到了排序前列附近,而 MLP 在前 100 名里完全空仓。Recall@1% 上 IF 命中 4 个真舞弊,MLP 命中 2 个;监督学习反而在"前 1% 命中率"上输给了无监督方法,这是个值得思考的反例。

停下来想一想。 IsolationForest 没有看过任何 AAER 标签,凭什么能拿到 0.57 的 AUC?答案是它捕捉到的是"统计异常",而真舞弊公司在 42 维财务向量里相对其它公司就是异常的,比如 Enron 2000 年总资产 655 亿、销售 1,008 亿,远超训练集 1991–2002 年的中位数公司。统计异常与会计舞弊之间存在天然的相关性,但两者并非同一件事。

雷区伪装型舞弊让 Isolation Forest 失效

Isolation Forest 的"易孤立 = 异常"前提依赖一个潜在假设:异常样本在特征空间中确实稀疏。如果舞弊公司的会计造假手段恰好就是把财务比率做得"看起来非常正常",这些样本反而埋进了密集人群里,路径长度与普通公司无异,被算法判为"非异常"。Bao 数据里的舞弊公司分两类:一类是 Enron 这种特征极端的"暴露型",IF 容易抓;一类是把利润率、营业现金流刻意压在行业中位数附近的"伪装型",IF 几乎抓不到。无监督方法的上限因此比监督方法低一截,本章测试集 AUC 0.5715 已经接近这个上限。

半监督混合的可能性

监督学习被 noisy negative 拖累、无监督学习被 camouflage 拖累,两条路径各有缺陷。一个可行的折中是半监督混合:先用 IsolationForest 给所有训练样本算异常分数,再把这个分数作为额外特征加入 XGBoost 与 RF 的特征集;或者反过来,先用监督模型给训练集上的"高置信度阴性"打分,把分数最低的一部分作为"可信阴性"喂给后续模型,剩余阴性当作 unlabeled 处理。这条混合路径在医学影像异常检测里已被反复验证有效。本书第 10 章会回到这个问题,把无监督打分作为 SHAP 解释的辅助通道之一。

案例公司打分对比

把 Enron 与 Tyco 在 fyear=2000 的两条记录同时喂给 MLP 与 IsolationForest,得到下表的打分。两家公司的真实标签都是 1。

表 8·3 案例公司在 MLP 与 IsolationForest 下的打分,fyear=2000

公司gvkeyfyear真实 misstateMLP ppIF score
Enron6127200010.00090.0321
Tyco International10787200010.00680.0363

MLP 把两家公司都打成"几乎不可能舞弊"。Enron 的 MLP 概率 0.0009,比测试集舞弊率 0.339% 还低三倍;Tyco 的 0.0068 与基线持平。对照第 4 章 LASSO 给 Enron 的 0.7292、第 5 章随机森林的 0.6813、第 6 章 XGBoost 的 0.9839,这三条监督学习曲线给出的"高度可疑"信号在 MLP 这里完全消失。这是 MLP 在不平衡数据上的典型失败模式:早停被验证集准确率主导,模型在 12 个 epoch 里学到的几乎是"全预测多数类",外推时 sigmoid 输出向 0 塌缩。

IsolationForest 的得分位于 0.03 附近,说明两家公司被算法判为"略偏离正常",但远没有进入测试集前 1%。Enron 在 IF 排序里位于前 5%,Tyco 位于前 4%,与"路径长度短一些"的直觉一致;都没有冲到前 1%。回到 AAER 数据。 两家公司的财务规模在测试集 2009–2014 里相对而言已经不再罕见,行业整体扩张让"百亿级总资产"成为常态,IsolationForest 看到的密度估计因此被稀释了。

Python 实现细节

本章 R 端不再实现,原因有二。第一,torch 与 R 的 nnet 在 MLP 上接口差异很大,多写一份对全书统一性帮助不大。第二,Isolation Forest 的 R 端实现 isotree 与 sklearn 在树构造细节上存在不可调和的差异,两边数字会差到 0.02 以上,没法照 chap04 / chap05 那种"小数点后两位一致"标准来对照。完整 Python 脚本在 code/08_dl_iforest.py,依赖 scikit-learn 1.3+。随机种子 np.random.seed(2026) + random.seed(2026)MLPClassifierIsolationForest 都显式传 random_state=2026

本章累积对比表

表 8·4 方法对比表,截至第 8 章

方法AUCNDCG@100Recall@1%Precision@1%核心局限
全部预测为非舞弊0.500000无判别力
Beneish M-Score0.53990.00000.01100.0049八变量规则,无学习
逻辑回归 42 特征0.69660.05100.05610.0217高维下系数不稳
LASSO 最优0.68760.04950.05610.0217不平衡下 λ\lambda 易过大
随机森林 ranger0.70870.01500.03740.0144MDI 偏向连续变量
XGBoost0.64800.00860.02800.0108训练→测试塌陷 0.10
RUSBoost (Bao 复刻)0.69820.00950.02680.0091欠采样丢信息,需多轮恢复
MLP [64,32][64, 32] ReLU0.59440.00000.01870.0072中等规模表格上跑不过树
IsolationForest 200 棵0.57150.04810.03740.0144无监督,camouflage 抓不到

把"打不过"的方法也写进对比表,是本书的一项纪律。如果只汇报跑赢的方法,读者会得到一张被幸存者偏差染色的对比图,以为换换工具就能稳赚。真相是 MLP 与 IsolationForest 在 Bao 数据上都没有跑过 LASSO 与随机森林,把这点诚实写下来,下一章引入 RUSBoost 复刻 Bao 旗舰结果时,读者才能客观判断"为什么是树类方法主导了会计 ML"。第 9 章将走另一条扩展路径:把建模数据从财务报表扩展到 MD&A 文本与 Loughran-McDonald 词典,看文本特征能不能给前几章的最佳模型带来增量。

本章知识地图

表 8·5 第 8 章核心概念与常见误解

核心概念核心内容常见误解为什么错
MLP 表格学习多层 ReLU 前馈网络对 42 个会计变量的非线性组合深度学习一定胜过传统方法中等规模、含噪标签的表格上,树类方法长期占优;本章 MLP AUC 0.59 落后 RF 0.71
ReLU 激活max(0,z)\max(0, z),分段线性,正区间梯度恒为 1ReLU 越多越好深度过大时 dead ReLU 让神经元永久失活;本章 12 epoch 早停里这个问题尚未触发
早停 early_stopping从训练集再切 15% 当验证集,连续若干轮不提升即停早停 = 不会过拟合内部验证集若被多数类主导,准确率提早收敛会让模型停在欠拟合阶段
Isolation Forest随机切分树,路径长度短的样本视为异常,无标签IF 找出来的异常 = 舞弊IF 找的是统计异常,与会计舞弊只有相关性;伪装型舞弊会被漏掉
路径长度归一化s(x,n)=2hˉ(x)/c(n)s(x, n) = 2^{-\bar{h}(x)/c(n)},输出 [0,1][0, 1]路径长度本身就是分数样本量不同的子节点对应的路径长度不可直接比较,必须除以 BST 期望长度
noisy negative阴性样本里藏着未被 SEC 查到的潜在舞弊没标 AAER 就是干净SEC 执法资源有限,阴性不可信;监督学习把这部分当真阴性会扭曲决策面
半监督混合IF 异常分作为额外特征喂给 XGBoost / RF无监督打分能直接替代监督模型IF 上限受 camouflage 限制,单独用不够;与监督模型混用才是常见路径