十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

企业财务异常检测模型落地全复盘:从规则引擎到混合架构

企业财务异常检测模型落地全复盘:从规则引擎到混合架构 财务异常检测这件事我接触得越久越发现它不是单纯的规则问题。早年做财务数字化的时候大多数企业靠的是财务复核人员对着Excel和ERP硬查查不查得出全靠经验、运气和当天的精力状态。后来上了规则引擎写了几百条SQL和阈值逻辑确实能捞出一部分明显异常但真正的问题在于规则是你告诉系统什么是异常而异常往往是看起来正常但放在上下文里不对劲的东西。这个不对劲靠人是写不出来的。所以当我开始设计这套智能化的企业财务异常检测模型时我给自己定的目标很直接把财务复核从抽样检查变成全量筛查用异常检测模型把可疑单据从几万条里挑出来再交给人工复核。这篇文章是我从业务拆解、数据准备、算法选型、模型评估到线上部署的全过程复盘适合正在做财务数字化、智能风控、异常检测项目的同行参考也适合刚入手异常检测但还没想清楚业务怎么落地的算法工程师。1. 项目整体思路与业务问题拆解1.1 先用业务语言定义财务异常做模型之前最忌讳的就是直接拿数据开跑。财务异常不是一个标准化的概念不同企业、不同行业、不同管理粒度下异常的定义完全不一样。我在项目启动会上跟财务总监聊了两个小时最后把企业财务异常分成了四类第一类是费用报销类异常比如差旅费发票连号、单笔金额卡在审批限额边缘、同一人员短期内频繁提交大额报销、报销摘要与发票内容不一致等。第二类是采购付款类异常比如供应商集中度过高、采购单价明显高于历史均价、同一供应商短期内突然提价、对公付款与合同金额不匹配等。第三类是关联交易类异常比如与关联方之间的资金往来频率异常、交易价格偏离市场公允值、期末突击确认收入等。第四类是账务处理类异常比如会计分录中借贷不平衡、频繁冲销重分类、季度末和年末调节利润的痕迹、折旧摊销突然跳变等。这四类业务的共同特点是单条数据看都说得通但放进时间序列和群体上下文里就露馅了。所以整个模型的定位非常清晰它不是替代财务复核而是做一轮全量的风险排序把得分最高的单据优先推给人工。这个定位决定了我后续所有的技术选择不需要追求100%的准确率但必须保证高召回、低漏报、可解释、能追责。1.2 为什么最终选择规则兜底模型排序的混合架构老实说我也见过一些团队上来就要用深度学习端到端替代规则引擎最后基本都死在业务部门不认账这个环节。财务场景跟工业视觉不同工业异常检测算法可以接受一个黑盒模型告诉你这个零件有缺陷因为缺陷的物理特征相对稳定但财务场景里每一笔被模型标注为异常的单据都需要财务人员去核实、去追责、去写说明如果模型不能解释为什么它觉得这笔有问题财务负责人不会签字。所以我最终采用的是规则兜底模型排序的混合架构。规则引擎负责处理明确的、已知的异常模式属于保底机制覆盖大概三成的异常检出模型层负责在规则之外捕捉那些说不出但感觉不对的复杂模式输出一个0到1的异常分数最后做决策融合规则命中直接进入高风险队列模型分数超过阈值也进高风险队列两者都命中的优先级最高。这套架构的好处有三个。第一是上线压力小规则引擎可以先跑起来模型作为增量能力逐步接入。第二是财务团队有掌控感他们能随时查看规则命中记录模型只负责提高效率和覆盖范围。第三是模型迭代时的容错率高就算模型某个月表现不佳规则引擎依然能兜住大部分基本风险。1.3 团队配置与项目节奏这是一场跨部门协作战这类项目能不能落地说实话七成取决于业务方跟技术方有没有坐在同一张桌子上。我当时的团队配置是财务共享中心出三位骨干做业务专家负责定义异常场景、提供历史判例、对模型结果做人工复核数据团队出两位数据工程师负责取数、清洗、搭建特征宽表算法侧是我和一位同事负责模型设计、训练、评估和部署。每周固定两次对齐会财务专家带着真实单据来算法同事带着模型输出的Top案例去两边对着看。项目节奏上我拆成了四个阶段第一个月做业务梳理和标签积累第二个月做特征工程和基线模型第三个月做模型调优和线下评估第四个月试运行并迭代。如果你们公司财务数据质量差前两个阶段的时间至少还要翻倍别急着上模型账都对不平的时候谈异常检测没有意义。2. 数据准备与特征工程算法不神奇特征才是大头2.1 财务数据的特点决定了特征工程的方法论做过财务数据的人都知道这玩意儿跟电商点击流、工业传感器数据完全不是一个物种。它的特点非常鲜明第一是金额分布极度偏态有的单据金额几十块钱有的几百上千万直接喂给模型模型基本会被大额样本带着走。第二是类别特征特别多科目编码、供应商、部门、人员、费用类型、合同编号这些高基数的类别特征处理起来很麻烦。第三是强周期性财务数据天然带月结、季报、年末冲账的节律异常检测如果不考虑这个周期性所有的期末突增都会被误报成异常。所以做财务异常检测的特征工程核心思路就八个字分而治之、上下文化。分而治之是把数据按照业务域拆开报销单据做一套特征付款单做一套特征总账分录做一套特征各建各的模型而不是把所有数据搅在一起训练一个大杂烩模型。上下文化是每个特征都要放在参照系里看金额本身没有意义但同一人员过去三个月平均报销金额的5倍就有意义了。2.2 特征抽取的具体做法与参数选择我以费用报销场景为例拆解一下实际操作。对每一张报销单我会从四个维度去构建特征。单据维度是最基础的包括报销金额、费用类型、发票张数、单张发票平均金额、摘要文本长度、审批链路中经过的节点数。这些特征可以直接从ERP和报销系统里拉到。人员维度就重要了包括该员工过去30天、90天、180天的累计报销金额、报销频次、最大单笔金额、金额标准差、环比变化率。这里有个小技巧环比变化率用中位数而不是均值因为财务数据里偶发大额报销会把均值拉偏中位数更稳健。部门维度要计算部门平均报销水平、员工报销金额占部门总报销的比例、部门费用在月度序列上的突变程度。供应商维度针对采购付款场景做包括与该供应商的历史交易次数、历史均单价、最新一笔单价相对历史均价的偏离倍数、价格波动系数。时间序列维度是我下功夫最多的地方我借鉴了工业异常检测里滑动窗口滤波模型的思路对每个员工或者每个供应商的近12个月金额序列用滑动窗口切出多个子序列计算窗口内的均值、方差、最大值、偏度和峰度再去看最新窗口相对历史窗口的偏移程度。滑动窗口的窗口大小我一般设置为3个月和6个月两档步长1个月。为什么是这个参数因为财务违规行为通常有季节性掩盖的需求比如一个员工连续三个月每月报销8000元第四个月突然报了一笔5万3个月窗口正好能捕捉到这种相对平滑背景下的突变而6个月窗口能捕捉更长期的缓慢抬升趋势比如供应商价格温水煮青蛙式地上涨。窗口太短容易把正常波动当异常窗口太长又会把真实异常平滑掉3加6的组合在实测里效果比较稳。2.3 标签怎么来的没有准确标签怎么做模型这是财务异常检测项目里最绕不开的一个坎。绝大多数企业根本没有标注好的异常单据数据集你总不能指着财务负责人的鼻子说来帮我标十万条数据。我的做法是先无监督后弱监督分三步走。第一步请财务专家从历史单据里挑出他们认为确定异常的一批样本大概几百条就够了作为种子样本。第二步用无监督模型比如孤立森林和自编码器在全量数据上跑一遍把得分Top500的单据交给财务专家复核他们确认异常的部分合并进种子样本。第三步把这几百条正样本当成弱监督信号训练一个有监督的排序模型用模型的全量预测结果再筛一轮人工复核后反馈进训练集。循环两三轮之后标签池会越来越大模型也会越来越准。这其实就是无监督异常检测模型评价方式的变体应用没有标准标签时用专家反馈构造近似标签再用precision at N这类指标来评价模型输出质量。财务领域的专家反馈非常值钱每次让专家看50条模型输出比什么离线指标都管用。3. 算法选型与模型融合从孤立森林到梯度提升树3.1 无监督模型孤立森林和自编码器的落地对比我第一版先上了孤立森林原因很简单快、稳、解释成本低。孤立森林的核心思想跟其他距离类算法完全不一样它不做密度估计而是用随机切割的方式把样本切成孤岛。异常点因为特征值组合独特容易被更少的切割次数孤立出来。实际操作中我会把树的数量设在500到1000之间采样大小设在256这个参数在多数财务数据集上都有不错的稳定性。孤立森林适合做第一道粗筛但它的缺点也很明显它对局部异常敏感对全局异常不敏感而且它给出的分数没有概率含义很难跟其他模型分数做融合。所以我同时上了自编码器。自编码器的思路是把特征压缩到低维再还原如果一条样本的重构误差特别大说明它不符合数据里的主流模式。我在实际项目中把自编码器设计成三层结构输入层维度跟特征数一致隐层维度压到输入层的一半再压到四分之一然后对称升维。训练时用Adam优化器学习率设0.001batch size设128早停轮数设10。实测下来自编码器对组合型异常的捕捉能力比孤立森林强比如一个员工既提高了报销频次又增加了单笔金额这种组合偏移在孤立森林里可能被拆散成两个维度的轻微异常但在自编码器的重构误差里会被放大。不过自编码器在训练数据被污染的情况下容易把异常样本学进去所以我会先用孤立森林粗筛一遍剔除明显的异常点再拿干净数据去训练自编码器。3.2 有监督模型为什么最终主力是LightGBM当标签池积累到一定规模后我开始上监督模型。选型时我对比了XGBoost、LightGBM和CatBoost。最终主力是LightGBM理由有三训练速度快调参成本低对类别特征有原生支持。财务特征里高基数的类别特征非常多比如供应商ID可能有几万个取值LightGBM用直方图算法切分时天然比XGBoost的预排序算法省内存。特征层面我把原始特征、聚合特征、时间序列特征全部灌进去大概两百多维。LightGBM的超参数我最终的配置是learning_rate 0.05num_leaves 63max_depth 7min_data_in_leaf 50feature_fraction 0.8bagging_fraction 0.8bagging_freq 1lambda_l2 1.0。这套参数不是一次性调出来的前前后后跑了差不多三轮网格搜索。我的经验是先用粗网格找学习率和num_leaves的大致范围再微调正则系数最后用早停确定迭代轮数一般500到2000轮之间就收敛了。不过有监督模型的训练样本里面有个结构性问题正样本量太少。财务异常在真实业务里的占比大概就是千分之一到千分之五的水平直接用原始分布训练模型会倾向于把所有样本都预测成正常。我处理这个问题的方式是负样本做欠采样正样本做SMOTE过采样把训练集的正负比控制在1比10到1比20之间。要说明的是测试集保持真实分布不能做任何采样否则评估指标会虚高。3.3 为什么不直接上深度学习序列模型在什么场景才值得用我知道很多人看到智能化这三个字就想着直接上手Transformer或者LSTM这里我泼个冷水。财务异常检测的数据形态跟NLP或CV完全不一样单条单据的原始结构化特征只有几十维数据量也就几十万条级别这种规模下树模型往往比深度模型效果更好训练和部署成本还低得多。深度学习不是不能用而是要分场景。在我的项目里深度学习只在一个地方体现了价值就是处理报销摘要文本和备注信息。这些文本里有大量线索比如摘要里写着客户招待费但发票明细是日用品这种语义矛盾传统关键字匹配很难覆盖所有写法。我尝试过用预训练的中文BERT做文本编码把摘要文本embedding成向量再拼接到结构化特征里。但说实话全量跑BERT embedding的成本和维护负担都不小最终我采用的是轻量方案先用规则从文本里抽关键词和金额实体再考虑有限的文本编码特征。如果你们公司的数据量能到百万级以上且文本信息在异常判别中占比很高那再考虑上序列模型也不迟。3.4 模型融合不同视角的投票比单一模型更稳模型融合这个环节直接决定了线上效果的上限。我最终采用了孤立森林自编码器LightGBM三模型融合的架构每个模型看到的数据视角不同融合后能互相补位。孤立森林看的是特征空间的孤立难度自编码器看的是重构误差LightGBM看的是在标签样本上学到的判别模式。融合时要解决分数不可比的问题。三个模型输出的分数范围不一样孤立森林的分数在0到1之间但偏向0.5附近自编码器的重构误差数值范围不固定LightGBM输出的是概率。我做了一个简单的两阶段融合第一阶段把每个模型的分数各自做分位数归一化把原始分数映射到它在历史分布中的百分位第二阶段用加权平均LightGBM权重0.5自编码器权重0.3孤立森林权重0.2。权重取值不是拍脑袋定的我用验证集做了小范围搜索比较了多组权重组合下的P100指标最后选了这组。另外还做了一个决策规则当任一模型给出的归一化分数超过0.95时直接判为高风险当三个模型的分数都超过0.8时也判为高风险。这种强信号单点触发弱信号共振触发的设计比单纯看加权平均分要灵活得多。4. 模型评估与效果验证没有标签怎么证明模型有用4.1 无监督异常检测模型评价方式的适配改造无监督异常检测模型的评价一直是个老大难问题没有标准标签你不能拿准确率说事。我在这个项目里用的是业务导向的评估方案核心指标是模型输出TopN样本中财务专家确认异常的比例也就是PrecisionN。我会让财务专家每次看100条模型输出按确定异常、疑似异常、正常三档打标然后计算P100。这个指标的优点是非常直观业务方能直接感知模型的价值缺点是标注成本高不能频繁做。为了弥补无标签评估的偏差我还引入了一个替代指标叫风险覆盖率定义是模型判定为高风险的样本集合中规则引擎和人工抽查命中异常的比例。这个指标虽然不完美但它能把模型的效果跟业务原有的风控基线做对比论证模型规则比纯规则多了哪些增量价值。如果你的项目想推给管理层看直接用真实业务数字说话远比晒AUC有说服力。4.2 离线评估中的时间穿越陷阱与正确切分方式评估财务异常检测模型有一个特别容易踩的坑时间穿越。财务数据有强时间结构如果你直接把过去三年的数据随机切分成训练集和测试集模型在训练时已经看到了数据的整体分布测试结果会虚高。正确做法是按时间顺序切分比如用前两年的数据训练用最近一年的数据测试。这一点听起来简单但实际操作中很多人因为省事就随机切分了最后上线效果跟离线指标差了十万八千里。时间切分还有一个更细的层次就是特征计算时不能使用未来信息。比如我在计算员工过去90天报销金额这个特征时如果数据表和特征表没有做严格的时间对齐很容易把员工未来发生的报销也算进历史窗口里这属于最严重的泄漏形式。我的做法是所有聚合特征都必须在单据发生日期之前的一个固定窗口内计算且窗口截止时间严格小于预测时间点。代码层面统一封装成一个函数防止不同同事写特征时各自为政。4.3 效果评估的结果和调优过程复盘经过三轮迭代后的实际效果是这样规则引擎单独运行时的风险检出率大概是基线水平的2.3倍加上模型融合后提升到3.8倍。这里说的风险检出率是财务专家在两个周期内确认异常的单据数量比较不是模型自己算出来的指标。换句话说混合架构帮财务团队多发现了一倍多的可疑单据而且误报率控制在可接受范围内。调优过程中我印象最深的一个案例是关于部门费用异常。第一次迭代时模型老是报出一些正常部门的高额度报销把财务团队搞得很烦。后来查特征重要度发现模型很依赖报销金额这个原始特征大额报销单在特征空间里天然容易被孤立森林标记为离群点但大额报销本身不能说明它异常。我调整了特征构造逻辑把金额从绝对值改为相对该员工历史水平的分位数以及相对部门均值的倍数同时把单笔金额超过5万元的单据单独建了一条通道走人工复核不参与模型排序。调整之后误报率明显下降模型才真正被业务方接受。5. 模型部署与线上运行从离线实验到生产系统的最后一公里5.1 推理链路设计与实时评分架构做财务异常检测的模型部署跟互联网推荐系统的部署节奏完全不同不需要毫秒级响应但必须保证稳定、可追踪、可回溯。我的设计是这样的报销系统里新单据提交后触发一个异步消息财务数据中心的任务调度器拉取单据数据经过特征计算模块生成特征向量再调用三个模型分别打分融合后输出风险分和风险等级写回风控系统的结果表。财务复核人员在审计工作台里按风险分降序查看单据。整个链路里最需要用心的是特征计算模块。因为线上推一条单据的特征时必须算出截至当前时刻该员工的历史聚合特征这要求特征计算能实时拉取历史数据并做聚合。我一开始用Python直接查数据库性能完全跟不上后来改成了预聚合方案每天凌晨把历史窗口的聚合特征全量算好存入特征表线上推理时只做一次查表和增量拼接耗时从秒级降到了几十毫秒。5.2 部署环境与模型量化选型FP16、BF16还是INT8讲到部署很多做算法的人第一反应是上GPU。我的建议是先想清楚你的推理量级。财务异常检测一天可能就几万条推理请求CPU完全扛得住用GPU纯属浪费。我当时在昇腾910B-A2服务器上尝试过模型推理也测过用vLLM跑开源模型做文本向量的场景但最终发现在当前的业务量级下根本没到需要高性能推理引擎的地步一个多进程的Flask服务加上缓存就稳稳能跑。如果你们确实要上GPU或者有低延迟需求量化是绕不开的话题。FP32对硬件资源太奢侈FP16和BF16在多数场景下都是很好的选择它们俩的差异主要在数值表示范围FP16的指数位只有5位大数容易溢出BF16的指数位跟FP32一致但尾数位少精度会低一些。财务评分这种任务模型输出的分数本身带有很大的不确定性用BF16比FP16更稳因为它不容易在中间计算时产生溢出。INT8则更激进需要做校准稍有不慎精度损失就很大我建议在财务场景里慎用除非你对推理延迟真的非常敏感。5.3 线上监控、月度重训与效果回归机制上线不是终点是另一轮迭代的起点。我的监控体系分为三层第一层是模型输入特征监控检查特征分布有没有发生剧烈偏移比如突然来了大量新供应商类别、报销频次整体抬升这种变化往往会影响模型稳定性第二层是模型分数监控看每日高分段样本占比是否有突变如果异常率突然翻倍大概率是数据口径变了而不是业务真的变差第三层是业务结果监控定期跟财务团队对齐确认的异常单据统计模型的真实检出量和漏报量。重训频率我设为每个月一次因为财务数据有明显的月末、季末、年末节律模型需要不断适应新的业务模式。每个月的训练流程是这样的用过去12个月的数据作为训练集月初跑一次全量训练用上个月的数据做测试集对比新模型和线上模型的P100表现如果提升超过一个百分点就切换上线否则继续沿用旧模型。这种稳健的迭代节奏虽然看着慢但避免了模型来回切换导致的结果不可比。6. 常见问题与排查技巧实录6.1 数据泄漏你以为的优秀效果可能全是幻觉这是我跟团队反复强调的一个问题也是所有数据挖掘项目里最常见的翻车点。财务异常检测做特征工程时很容易在不知不觉中引入未来信息。我之前有个同事做特征时直接对全量数据做了标准化StandardScaler的均值和方差是从全数据集算出来的包含未来数据这在严格意义上就是泄漏。因为当你想预测某一天的单据时你不应该知道未来几天全量数据的分布。排查数据泄漏我常用的一个技巧是用模型跑一条历史单据手动检查它的特征计算过程。比如选一笔2024年3月15日的报销单手工计算该员工过去90天报销总额再对比特征表里存的值如果对不上就说明特征管道有时间错位。这种检查虽然费时间但每次模型效果异常好的时候都应该做一遍。6.2 概念漂移新政策、新科目、新系统的连锁反应财务是高度受政策影响的领域会计准则一调整会计科目就得变以前模型学到的模式立马就过时了。我遇到过最典型的一次概念漂移是公司全面切换报销系统老系统的部门编码跟新系统完全对不上导致部门维度的特征全部失效。那段时间模型误报率陡增因为很多老部门的报销在新系统里成了无归属类别特征空间里变成了天然离群点。针对这类问题我的对策是两件事。第一上线前给关键特征做版本标记系统切换时能快速定位到受影响的特征集合第二建立特征重要度监控每周输出Top20重要特征的分布偏移情况一旦发现某个高重要度特征分布剧烈变化就立即检查业务口径是否有变动。这套监控机制挽救了不止一次模型失效的情况。6.3 可解释性模型不解释财务不签字财务场景对可解释性的要求比大多数业务场景都要苛刻。财务复核人员必须要知道为什么这笔单据被标记为异常否则他没法写审计说明。我最终给每个异常单据生成一份风险解释报告内容包括模型给出的风险分、命中的规则、贡献最大的前五个特征以及它们的具体取值和参照值。实现上我用的是SHAP值LightGBM模型可以直接调用tree explainer快速算特征贡献。自编码器和孤立森林这种无监督模型不太好直接套SHAP我的做法是把它们的评分结果单独展示比如该单据在自编码器中的重构误差为0.85超过历史95%的样本配合分数的百分位说明财务人员就能理解模型的判断依据。报告模板的细节一定要跟财务团队反复打磨他们看得顺眼的报告模型才活得下去。6.4 类别不平衡与阈值设定不要迷信0.5这个默认值财务数据里异常样本占比极低直接用默认阈值0.5模型会漏掉大量真异常。我处理阈值的方式是画PR曲线在验证集上观察不同阈值下的精确率和召回率然后结合财务团队能承受的人工复核量来定阈值。实测下来阈值设在0.3到0.4之间时能在不压垮复核团队的前提下覆盖更多异常。阈值还可以按业务场景分桶设置。比如金额大于10万元的大额单据阈值设低一点宁可多复核也不能漏小额单据阈值设高一些减少误报干扰。这种分桶策略听上去简单但带来的业务满意度提升非常明显财务团队不再觉得模型在添乱。6.5 模型与规则冲突时以谁为准最后说一个容易被忽视的坑规则引擎和模型之间会有冲突。有时候规则引擎判某笔单据正常但模型给它打了很高的异常分财务团队来质询时你需要一个明确的分歧处理策略。我的方案是如果规则引擎明确命中高危规则直接走高风险流程不等模型结果如果模型打分高但规则全部未命中标记为模型建议复核交由人工参考处理。这样做的好处是规则始终作为兜底模型作为增量建议两者不会互相扯皮。这个项目做下来我最大的体会是财务异常检测模型的难点从来不在算法本身而在于你愿不愿意花时间扎进业务里把那些说不清道不明的不对劲翻译成数学模型能理解的语言。如果你正准备做类似的项目我给三条建议第一前期多花时间跟财务专家聊业务场景比什么都值第二特征工程别怕繁琐财务数据脏和乱才是常态特征质量的边际收益远高于调参第三上线前一定要跟业务方对齐评估口径让模型的效果能被业务语言清晰表达。工业异常检测、运维异常检测、财务异常检测方法论可能一脉相承但每个行业都有它自己的隐性规则悟透这些规则模型才真正有价值。
返回列表