第 8 章把表格深度学习与无监督异常检测加进了对比表,结论是 MLP 在 42 个财务特征上比 XGBoost 没有优势,Isolation Forest 给出了一个无标签的辅助打分。八章下来,所有方法用的都是同一组财务数字,来自资产负债表与利润表。如果舞弊已经渗入这些数字本身,那剩下的突破口就在数字之外的地方。
10-K 报表里不只有数字。Item 7 那一节叫 Management's Discussion and Analysis,简称 MD&A,是管理层用自然语言对当年经营状况的解释。会计文献从二十一世纪初开始反复发现,舞弊年的 MD&A 在情感倾向、可读性、句长上都和正常年有可识别的差异。本章把这条思路接进 Bao 框架,看看文字里能不能挤出额外的信号。
文本特征的必要性:财务数字与管理层叙事的互补
财务报表数字是受限的。会计准则规定每个科目的口径,外部审计师按相同的准则核对,同一行业里两家公司的总资产、净利润必须可比。一旦管理层下决心做账,他们会先把要操纵的数字调整到看上去合规的位置,再用脚注、附注、MD&A 把审计师可能产生的疑虑提前安抚下去。审计文献里把这种行为称作 narrative obfuscation,直译过来是叙事的混淆。
文字有它自己的指纹。造假的管理层在写 MD&A 时面对的压力与正常经营回顾完全不同。他们要解释为什么指标走势异常,要回应可能的监管关注,要给股东一个可以接受的故事。这些压力在词频层面留下三类特征:负面词比例上升,因为粉饰太平的文本反而更容易出现"风险""不利""不确定"这类风险声明的措辞;不确定词与诉讼词比例上升,反映管理层对自己所述内容的底气不足;文本变冗长、句子变复杂,可读性指标 Fog Index 升高。
Loughran and McDonald 用 1994 至 2008 年的 10-K 全文做实证,发现如果用通用情感词典分析财务文本,三分之四被标为负面的词其实在金融语境下中性,比如 liability 和 tax。他们重新构建了一份专门服务于金融文本的词典,分为 positive、negative、uncertainty、litigious 等几个类别。后续会计 ML 文献几乎都把它作为标准选择。这本书也用同一份词典。
停下来想一想。 如果舞弊管理层有办法把财务数字调整得"看上去合规",他们为什么不也把 MD&A 改写得"看上去正常"?答案在于成本。修改一个数字只需改一个单元格,修改文本要重写一整段叙事,还要保证前后逻辑自洽。改了的痕迹更难抹去,反而留出了识别的余地。
10-K 与 MD&A 的监管要求
10-K 是上市公司每个会计年度向 SEC 提交的年度报告,篇幅从一两百页到五六百页不等。SEC 的 Regulation S-K 第 303 条要求 10-K 必须包含 MD&A 章节,由管理层从自身视角解释经营成果与财务状况。MD&A 在 10-K 里编号为 Item 7,紧跟 Item 6 的财务摘要,结束于 Item 7A 的市场风险定量披露。本章关心的就是 Item 7 这一块文本。
S-K 303 给 MD&A 列了若干必须覆盖的话题:流动性、资本资源、经营成果、关键会计估计、表外安排。它没有规定每一段的措辞和篇幅。管理层在合规框架内有相当大的自由度选择"讲什么"和"怎么讲"。这种自由度意味着同一年两家同行业公司的 MD&A 在长度、口径、语气上可能差好几倍。也正是这种差异给文本特征留出了识别空间。
Loughran-McDonald 财务情感词典
Loughran and McDonald 分析了 1994 至 2008 年的 56,000 余份 10-K,用人工标注配合频率分析筛选出适合金融文本的情感词。他们最常被引用的四个类别是 positive、negative、uncertainty、litigious。positive 类约 354 个词,比如 strong、achieved、benefit,是管理层主动展示经营成绩时倾向使用的词。negative 类约 2,355 个词,是该词典里最大的类别,反映金融文本中"披露风险"的语言天然偏多。uncertainty 类约 297 个词,比如 may、depend、contingent,标记的是管理层对自己所述事项的不确定。litigious 类约 904 个词,比如 lawsuit、regulatory、indictment,标记诉讼与监管相关语言。
设一份 MD&A 文本切分为 个词项,词项集合为 。对类别 ,令 为 LM 词典中属于类别 的词集。该文本的 LM- 分数定义为
即类别 词在该文本中出现的相对频率。
举个具体例子。一份 5,000 词的 MD&A,里面 loss、decline、adverse 这类 negative 词共出现了 70 次,那 LM-neg 分数就是 。Loughran and McDonald 报告 1994–2008 全样本 10-K 的 LM-neg 均值约 1.39%。一家公司当年的 LM-neg 飙到 2.5% 已经显著偏离分布右尾,这种偏离在舞弊样本里出现得更频繁。LM-pos 的均值约 0.75%,LM-unc 约 1.20%,LM-lit 约 0.85%。
四个分数里,文献最一致的发现是负面词比例与盈余意外、未来收益、舞弊概率都呈现统计意义上的关联。positive 类反而在很多研究里显著性不稳定,原因之一是管理层惯用的乐观措辞已经被市场充分预期,边际信息量不大。
回到 Bao 数据。 如果按 Bao 时间切分把 1991–2002 的训练集 57,000 多家公司年度对应的 10-K 全部抓回来,每一份算上面四个分数,理论上可以得到一张和 42 个财务特征并列的文本特征表。第 6 章 XGBoost 的输入从 42 维扩到 46 维,接下来要看 AUC 怎么动。本章卡在了第一步:怎么把 gvkey 映射到 EDGAR 的 CIK 编号,下文有专门一节讲它。
可读性指标:Fog Index 与句长方差
情感词典抓的是用了什么词,可读性指标抓的是怎么把词排列成句子。最早把可读性引入会计研究的是 Li (2008),他用 Fog Index 度量年报披露的易读程度。
设一段文本被切分为 个句子,共 个词,其中复杂词数为 ,复杂词指音节数 的词,不计常见后缀如 -es、-ed、-ing。Fog Index 定义为
举一个数字例子。一段文本共 50 句、1,000 词,其中复杂词 200 个,平均每句 20 词,复杂词比例 20%。Fog Index = 0.4 × (20 + 100 × 0.20) = 0.4 × 40 = 16.0。粗略对应美国教育系统 16 年级的阅读难度,也就是大学毕业水平。Li (2008) 统计 1994–2004 年 10-K,全样本 Fog 均值约 19.8,相当于研究生水平。这个数字本身就提示一件事:年报普遍比一般公众的阅读舒适区难得多。
句长方差是另一个常用指标。同样平均每句 20 词,一种文本可能每句都卡在 18–22 词之间,另一种可能从 5 词到 60 词剧烈起伏。后者通常意味着作者在长复杂句和短补充句之间反复切换。会计文献里的发现是,舞弊年的 MD&A 句长方差往往更大,反映"在叙事中插入大量风险声明和补充注释"的迹象。
实务上,Fog Index 和句长方差的计算依赖于英文音节数判定。Python 端 textstat 包封装了一套规则,准确率在 90% 以上,对会计研究够用。R 端 quanteda.textstats 提供等价实现。本章 Plan B 里把这两个指标的均值与方差按 Li (2008) 报告的全样本分布合成,作为演示。
FinBERT 嵌入:本书第一版的延后选择
LM 词典是基于词频的浅层方法,它把每个词当作独立信号处理,忽略上下文。not strong 这种短语会被切成 not 和 strong 两个词,前者不在词典中,后者落入 positive 类,文本被错判为正面。
近年的方向是预训练语言模型。ProsusAI/finbert 在 Reuters 财经新闻和 10-K 文本上做过领域预训练,能输出每段文本的情感概率分布以及 768 维语义嵌入。后者作为 XGBoost 的额外特征,在小样本舞弊检测里被报告过 0.02–0.04 的 AUC 增量。
把 FinBERT 嵌入加进 Bao 样本需要 GPU。CPU 端跑一份 5,000 词的 MD&A 需要 1–2 分钟,57,000 多份训练集 10-K 估算耗时一周以上。本书第一版受限于公开发行的运行环境,把 FinBERT 章节延后到第二版接入 EDGAR 真实数据时一起处理,避免给读者承诺一个跑不动的方案。
数据获取的工程化
这一节是本章实务价值最高的部分。从 Bao 数据走到一张可用的文本特征表,中间隔了一条工程链路,链路上每一节都有可能拖慢甚至阻塞整个项目。下图画出从 10-K 申报文件到 XGBoost 输入的完整流水线,其中虚线箭头标记的部分是本章因 EDGAR 数据接入受限而走的合成演示路径。
图 9·1 从 10-K 到舞弊概率的文本特征流水线:真实路径先抽取 MD&A,分词后用 Loughran-McDonald 词典匹配 positive/negative/uncertainty/litigious 四类情感词频,并独立计算 Fog Index 可读性,五个文本特征与 Bao 28 维财务特征拼接后送入 XGBoost 输出概率增益。本章因 EDGAR 数据接入受限,以虚线箭头标出的环节用合成特征演示流水线骨架。完整 TikZ 结构图详见 PDF 全文。
链路第一节是 gvkey 到 CIK 的映射。Bao 复制包给的标识列只有 gvkey,这是 Compustat 的内部公司编号。EDGAR 完全不识别 gvkey,它认的是 CIK,即 SEC 给每家注册公司分配的 Central Index Key。两套编号体系的对应关系藏在 Compustat 的 ccmxpf_lnkhist link table 里,需要 WRDS 订阅才能获取。没有订阅的研究者只能走两条退路。一条是用公司名字符串近似匹配 SEC 的 cik-lookup-data.txt,匹配率经验上在 70%–85% 之间,需要人工核对剩下的 15%–30%。另一条是先把 gvkey 映射到 ticker,再用 ticker 查 CIK,但 Bao 复制包同样没给 ticker。本章写作时受限于公开数据,没能完成这一步。
链路第二节是 EDGAR 抓取速率。SEC 公开规定单 IP 不超过 10 请求每秒,请求头需写明研究者邮箱。Python 端用 requests 配合 time.sleep(0.1) 即可遵守。一份训练集对应的 57,000 多份 10-K 全量抓取需要约 96 小时,建议分批存到本地,再做后续解析。EDGAR 偶尔返回 429,碰到这种情况要按指数退避重试。
链路第三节是 MD&A 解析。10-K 的 HTML 没有专属标签标记 Item 分隔,需要用正则扫描类似 Item 7 Management 的开头与 Item 7A 或 Item 8 的结尾。早期文件里 Item 编号有时被写成 ITEM 7 全大写或全角字符,需要做归一化预处理。即便如此,仍约有 5% 的文件无法正确切出 MD&A 段,需要人工兜底或直接丢弃。开源工具 secedgar 与 edgar-crawler 处理了部分情况,但对极早期文件覆盖有限。
链路第四节是 词典与可读性计算。LM 词典从 sraf.nd.edu 下载 CSV 即可。Fog Index 计算需要英文音节判定,前面提过的 textstat 包够用。这一步本身不复杂,但只有前三节都通过之后才轮到这里。
文本特征听起来"再加一组就完事",实际管线远不是那回事。把 EDGAR 抓取、gvkey 链接、MD&A 解析、词典分数全部串通,至少要花两到四周。本书第一版未完成这条管线,章节呈现的合成方案是诚实的折中。任何宣称"周末搞定文本特征"的说法,往往省略了链接表与 5% 解析失败的处理。
在 Bao 数据上的实验:Plan B 合成方案
本书第一版把上面这条管线的真实运行延后到下一版。本章在 Bao 训练 + 验证 + 测试切分上,按 Loughran and McDonald 与 Li (2008) 报告的全样本分布合成六个文本特征,把它拼到第 6 章 XGBoost 的输入上做对比。
合成的设计原则是这样。非舞弊样本的均值参数取自上面两篇文献的全样本分布:LM-pos 0.75%、LM-neg 1.39%、LM-unc 1.20%、LM-lit 0.85%、Fog 19.8、句长方差 8.0。舞弊样本上注入小幅 delta,模拟"管理层口径更负面、更不确定、更涉诉、更冗长"的常见发现。delta 设得相当弱,意在让 AUC 增量落在文献常见的几个百分点,避免合成数据过度好用。
# code/09_text.py 节选(完整脚本见仓库)
import numpy as np, pandas as pd, xgboost as xgb
from sklearn.metrics import roc_auc_score, ndcg_score
rng = np.random.default_rng(2026)
def simulate_text_features(df):
n = len(df); y = df["misstate"].values
pos = rng.normal(0.0075, 0.0020, n).clip(min=0)
neg = rng.normal(0.0139, 0.0030, n).clip(min=0)
unc = rng.normal(0.0120, 0.0025, n).clip(min=0)
lit = rng.normal(0.0085, 0.0022, n).clip(min=0)
fog = rng.normal(19.8, 1.5, n).clip(min=8.0)
sd = rng.normal(8.0, 1.2, n).clip(min=2.0)
m = (y == 1)
neg[m] += rng.normal(0.0008, 0.0010, m.sum()).clip(min=0)
unc[m] += rng.normal(0.0006, 0.0010, m.sum()).clip(min=0)
lit[m] += rng.normal(0.0005, 0.0008, m.sum()).clip(min=0)
fog[m] += rng.normal(0.15, 0.30, m.sum())
sd[m] += rng.normal(0.10, 0.25, m.sum())
return np.column_stack([pos, neg, unc, lit, fog, sd])
训练集 63,930 行含 537 fraud,验证集 30,777 行含 250 fraud,测试集 27,628 行含 107 fraud。合成文本特征在训练集上的舞弊与非舞弊均值差为 LM-neg +0.0008、LM-unc +0.0009、LM-lit +0.0006、Fog +0.139、句长方差 +0.156,均为小幅注入。基线 XGBoost 仅用 42 个财务特征,测试集 AUC 0.6639;增强 XGBoost 拼接 6 个合成文本特征,测试集 AUC 0.6650,增量 +0.0011。AUC 几乎不动,但前 1% 排序提取效率明显改善:NDCG@100 从 0 升到 0.0254,Recall@1% 从 0.93% 升到 4.67%,Precision@1% 从 0.36% 升到 1.81%。这与文献中"文本特征对总体判别帮助有限,对头部排序有改善"的发现方向一致。需要再次提醒:本节所有数字源自合成信号,仅用于演示管线机制,不构成真实文本特征的实证结论。
案例公司方面,Enron 2000 年度的基线打分 0.9492,增强后 0.9008;Tyco 2000 年度基线 0.9121,增强后 0.8578。两家在合成增强下的概率都略有下降,原因是合成的 LM-neg 与 Fog Index 在它们身上的具体抽样未必落在舞弊均值上,模型在拼接特征后做出了边际微调。这种波动在合成实验里属于正常噪声。
时间漂移:文本特征比财务数字老化得更快
第 6 章 XGBoost 的测试集 AUC 比验证集下挫了 0.10,原因之一是训练集与测试集的舞弊密度差异。文本特征会把时间漂移进一步放大。
财务报表数字背后是 GAAP,准则更新虽然有但节奏缓慢。1995 年的总资产口径与 2014 年的总资产口径基本可比。文本则不一样。SEC 在 2003 年发布 Critical Accounting Policies 披露指引,2010 年修订风险因素表述要求,2020 年的 COVID 相关披露要求又增加了一组高频词。监管语言的演变意味着 LM 词典在 1991 年与 2014 年覆盖到的"风险声明"密度本身就不一样,词频分数会跟随监管节奏漂移。
实务上的应对有两条。按时间窗滚动训练,每三到五年用最新窗口的数据重新拟合文本权重;或者把文本特征做相对化处理,按同行业同年度均值标准化,让模型只看相对偏离。两条做法在 Loughran and McDonald 之后的实证文献里都有人尝试,但都没有彻底解决问题。本书第一版受限于合成方案,对时间漂移没有做实证检验,留待真实数据接入后专门处理。
本章累积对比表
表 9·1 第 9 章累积方法对比:文本特征加入合成演示
| 方法 | AUC | NDCG@100 | Recall@1% | Precision@1% | 局限 |
|---|---|---|---|---|---|
| 全部预测为非舞弊 | 0.500 | 0 | 0 | 0 | 无判别力 |
| Beneish M-Score | 0.540 | 0.000 | 0.011 | 0.005 | 规则固定,不学习 |
| Logistic / F-Score | 0.697 | 0.051 | 0.056 | 0.022 | 线性,难捕捉交互 |
| LASSO / Elastic Net | 0.688 | 0.050 | 0.056 | 0.022 | 线性,稀疏可能过强 |
| 单棵决策树 depth=5 | 0.548 | 0.007 | 0.037 | 0.014 | 不稳定,过拟合深叶 |
随机森林 ranger | 0.709 | 0.015 | 0.037 | 0.014 | MDI 偏向连续变量 |
| XGBoost | 0.648 | 0.009 | 0.028 | 0.011 | 时间外推塌陷 |
| RUSBoost (Bao 复刻) | 0.698 | 0.010 | 0.027 | 0.009 | 欠采样丢信息 |
| MLP ReLU | 0.594 | 0.000 | 0.019 | 0.007 | 表格上不及 boosting |
| IsolationForest 无监督 | 0.572 | 0.048 | 0.037 | 0.014 | 异常 舞弊 |
| XGBoost + 文本,合成演示性 | 0.665 | 0.025 | 0.047 | 0.018 | 文本为合成信号,需真实 EDGAR 数据复核 |
最后一行用合成文本特征做的演示,说明文本特征对头部排序的改善幅度比对总体 AUC 的改善更显著。这一观察方向与会计 ML 文献一致,但具体数字仅在本章合成实验里成立。下一章用 SHAP 把表现最佳模型的预测打开,整合前九章的累积对比表给出方法选择决策树,并对全书结论做最后汇总。
本章 R 端没有提供等价脚本。quanteda 在 R 端能做词典匹配,但 EDGAR 抓取与 HTML 解析在 Python 端的工具链更完整,本章只走 Python 路线,完整脚本见 code/09_text.py。
本章知识地图
表 9·2 第 9 章核心概念与常见误解
| 核心概念 | 核心内容 | 常见误解 | 为什么错 |
|---|---|---|---|
| MD&A | 10-K 中 Item 7 的管理层经营讨论与分析章节,由 SEC Regulation S-K 第 303 条要求 | MD&A 是会计师写的,措辞也要按准则来 | MD&A 由管理层以自身视角撰写,自由度大,措辞和篇幅都受管理层选择影响 |
| LM 词典 | Loughran-McDonald 2011 为金融文本构建的情感词典,含 positive、negative、uncertainty、litigious 等类别 | 通用情感词典也能用,反正都是英文 | 通用词典里 liability、tax 等词被标负面,但在金融语境中性,需用 LM 替代 |
| Fog Index | ,对应阅读所需教育年级 | Fog 越低越好 | Fog 衡量易读程度,年报普遍 19 以上对应研究生水平,比较的是相对而不是绝对 |
| gvkey 与 CIK | gvkey 是 Compustat 内部编号,CIK 是 SEC 注册码,两套体系映射靠 link table | 按公司名字符串匹配就能搞定 | 公司名同名、改名、子公司情况复杂,名字匹配率约 70%–85%,剩下需要人工核对 |
| EDGAR 抓取 | SEC 公开 10-K 全文,单 IP 限速 10 请求每秒,请求头需写研究者邮箱 | 抓取就是 requests.get 一行的事 | 限速、429 重试、HTML 编码差异都需要工程化处理;57,000 份 10-K 抓取约 96 小时 |
| 文本特征时间漂移 | 监管语言更新使 LM 词频分数随年份漂移,比财务数字漂移更快 | 词典是固定的,词频分数也稳定 | 词典固定但语言演变,2003 与 2020 的"风险声明"密度不同;建议滚动训练或同行业相对化 |
| Plan A 与 Plan B | Plan A 是 EDGAR 真实抓取走通完整管线,Plan B 是合成文本特征演示管线骨架 | 合成数据的 AUC 增量可以当成真实结果引用 | 合成 delta 是按文献分布人为注入的,AUC 增量只能演示管线,不构成实证结论 |