【项目背景】
1:我们的客户是银行,金融机构。主要业务就是卖金融系统,包括决策引擎,规则引擎,业务系统,数仓等等;
2:之前客户风控其实一直有个痛点:太依赖结构化的财务数据了。但其实像客户经理的尽调访谈记录、客户的银行流水摘要、还有各种购销合同,这些非结构化数据里藏着大量真正的风险线索。
3:靠人工去看这些文本,一笔业务要花40多分钟,不仅效率低,而且面对现在越来越专业的包装欺诈,人眼很容易疲劳漏判。
4:客户认可。想要做数仓,但是前提是要解决这个问题。
最终,这个系统不仅把审批时间从40分钟压缩到了15分钟以内,更重要的是,它实打实地拦截了数百笔包装欺诈,为公司避免了数千万的潜在坏账。
它的输入和输出到底是什么?
- 输入:具体的非结构化文本片段。比如:某客户近3个月的某条银行流水摘要、某段尽调访谈的录音转写文本、或者某份购销合同中的“违约责任”段落。
- 输出:细粒度的风险标签和置信度。
- 例如:针对流水摘要,输出标签
[疑似过桥资金, 置信度0.92];针对访谈记录,输出标签[实控人提及涉诉, 置信度0.85]。
- 例如:针对流水摘要,输出标签
- 下游一:对接规则引擎(规则触发)
- 对于某些极度危险的信号,我们直接配置硬性规则。比如规则引擎设定:“如果 NLP 识别出流水包含‘民间借贷’且置信度 > 0.9,直接触发拒件,或强制转交高级审批员”。这就实现了自动化拦截。
- 下游二:赋能审批员工作台(人机协同)
- 在审批员的系统界面上,NLP 会将识别出的风险点高亮显示(比如把合同里“担保物处置权受限”的条款标红),并附上 Attention 权重最高的关键词。审批员不需要再看几十页的合同,直接看高亮部分,这就是为什么审批时间能从 40 分钟缩短到 15 分钟的原因。
项目分为4个阶段
第一阶段:立项阶段【确认数据来源和口径】
金融文本最大的痛点是“乱、敏感”,且正负样本极度不平衡(欺诈样本通常不到1%)
- 数据清洗与脱敏:正则表达式和金融词典上千个 {"VX": "微信", "ZFB": "支付宝", "网贷": "网络借贷", "小贷": "小额贷款", "过桥": "过桥资金"}AC自动机(Aho-Corasick 算法,使用
pyahocorasick库)进行多模式匹配替换。这使得在处理百万级流水摘要时,词典替换的耗时从小时级降低到了分钟级。 如统一大小写、繁简转换,并严格脱敏敏感信息(如身份证、银行卡号)。银行卡替换为[BANK_CARD],手机号替换为[PHONE] - 标签体系与标注:联合风控业务专家,梳理出了“流水异常、合同瑕疵、访谈风险”等3大类、40多个细粒度标签。
购销/租赁合同【按照条款/章节正则拆分】
- 业务痛点:充斥着大量法言法语和标准模板,风险隐藏在特定条款中。
- 切分策略:先通过 PDF 解析工具(如 PyMuPDF)结合正则,提取合同的目录和层级标题(如“第一条 定义”、“第五条 违约责任”)。然后,按“条款/章节”进行物理切分。
- 打标粒度(条款级): 一个合同会被拆分成几十个文本块。我们只针对特定条款打标。例如:
- 将“第八条 担保与抵押”这个文本块,打上
[抵押物处置权受限]标签。
- 将“第八条 担保与抵押”这个文本块,打上
银行流水【按照时间窗口和交易对手聚合】
- 业务痛点:单条流水极短(十几个字),但一次打几百上千条,整体很长。
- 切分策略(基于时间窗口与交易对手的聚合切分): 按“周”为时间窗口切分;或者将“与同一个交易对手”的所有流水摘要聚合在一个文本块里。
- 打标粒度(聚合块级): 针对聚合后的文本块打标。比如,模型对“某客户近30条与XX小贷公司的流水摘要”这个文本块,打出
[疑似民间借贷还款]标签。
尽调访谈报告【滑动窗口拆分】
- 业务痛点:通常是客户经理下户后写的几千字小作文,或者语音转写的长文本,信息密度不均。
- 切分策略(滑动窗口重叠切分): 因为访谈报告没有明显的章节,我们采用 NLP 经典的滑动窗口切分。设定窗口大小(如 512 tokens),关键是设置重叠区(如 100 tokens)。为什么要重叠?为了防止一句话或一个逻辑(如“虽然...但是...”)被硬生生切断在两个窗口里,导致上下文丢失。
- 打标粒度(事件/维度级): 对每个窗口进行推理。如果某个窗口内包含了“老板最近换人了”、“找不到法人”,则在该窗口打上
[实控人异常]标签。
数据口径问题
- 观察点:统一设定为客户提交进件申请的那个时间点。我们只提取该时间点及之前产生的文本数据(如申请前6个月的银行流水、当次的尽调报告)。
- 好坏样本定义:
- 坏样本高风险:放款后 6 个月或 12 个月发生逾期 30 天以上,或被确认为欺诈、进入法诉阶段的客户。
- 好样本正常:在表现期内,正常还款,且无任何逾期或风险预警记录的客户。
- 我们的口径:我们拉取全量进件数据,既包含最终放款的,也包含被拒绝的。
- 对于被拒绝的客户,我们根据其被拒的真实原因(如:信审员手动标记的“流水造假”、“空壳公司”),将其作为强负样本(高风险样本)纳入训练集。这极大提升了模型在贷前拦截阶段的敏锐度。
- 按客户隔离:同一个客户的所有记录,要么全在训练集,要么全在测试集。绝不能出现同一个客户的流水在训练集,而他的合同在测试集,这会导致模型“作弊”记住该客户。
- 按时间隔离:这是最贴近真实生产环境的口径。例如,我们用 2019.01 - 2020.12 的数据做训练和验证,强制预留 2022.01 - 2022.06 的数据作为独立的 OOT(跨时间)测试集。这能真实检验模型在面对未来新数据、新欺诈手法时的泛化能力,而不是仅仅在历史数据上自嗨。
第三阶段:工程部署与风控引擎集成
这是项目从“实验室”走向“生产环境”最关键的一步。
- 模型压缩与加速:BERT 系列模型推理太慢,无法满足风控核心链路 <50ms 的 SLA。我们采用了知识蒸馏,以 BERT 为 Teacher,轻量级的 TextCNN 为 Student 进行蒸馏。结合 ONNX 格式转换和动态量化,在精度损失不到 1.5% 的前提下,吞吐量提升了 6 倍。
- 大小模型协同架构:我们设计了路由机制。90% 的常规短文本(如流水摘要)走蒸馏后的小模型,进行同步实时推理;10% 的超长文本(如几十页的租赁合同)走大模型,进行异步离线分析。
- 置信度分流与人机协同:模型输出结果和置信度后,直接对接公司的 Drools 规则引擎。高置信度结果自动打标流转;低置信度或高风险样本,系统会自动高亮 Attention 权重词,推送给审批员进行人工复核。
第四阶段:线上监控与持续迭代
模型上线不是结束,金融环境在变,模型一定会衰退。
- 分布监控:我们实时监控模型预测结果的PSI(群体稳定性指标)。如果发现线上推理数据的分布和训练集发生严重偏移(比如突然出现了新的欺诈话术),系统会自动报警。
- 闭环重训:我们将人工复核纠正的 Bad Case 自动回流到标注库,触发每周的自动化微调流水线(CI/CD for ML),让模型越用越聪明。
为什么不用深度学习,而是用 TF-IDF + 随机森林?
金融风控对可解释性的要求高于推荐系统。我们需要快速证明‘文本数据是有价值的’。
TF-IDF + RF 训练极快,且 RF 可以直接输出Feature Importance。
我们可以拿着结果告诉业务方:‘模型判定这笔流水高风险,是因为高频出现了‘过桥’、‘代付’这几个词
为什么升级到 FastText。它比 RF 好在哪里?
OOV问题与高并发:“随着业务推进,我们发现了两个致命痛点:
第一,客户经理录入的流水摘要非常不规范,大量存在错别字、拼音缩写(比如把‘微信’打成‘VX’,‘租赁’打成‘ZL’)。TF-IDF 遇到这种 OOV(未登录词)就直接失效了。还有就是并发跟不上。
FastText它利用了 n-gram 子词信息,即使遇到拼写错误,字符 n-gram 学到相似的表示。
比如where词典里没有。我们用3元拆分。 得到 whe here 。我们找到了when there 。之后求平均拿到向量。但是之前找不到就直接为0。导致这个维度就没有。
极简的网络结构(隐藏层只有一层,并且没有激活函数)。
如果分类类别有 1 万种,普通 Softmax 每次推理要算 1 万次指数运算,直接卡死。
FastText 利用霍夫曼树(Huffman Tree),将 O(N) 的计算复杂度降到了O(logN)。推理时只需要走树的路径,速度提升百倍。
然后通过哈希函数直接映射到固定大小的向量空间。省去了巨大的词表内存占用。
既然 FastText 又快又好,为什么还要BERT?
深层语义依赖:“FastText 本质上还是高级的词袋模型,缺乏对深层上下文的理解。 金融文本中充满了语义反转和隐蔽逻辑。
比如一段访谈记录:‘虽然客户近期单月流水有所下滑,但是其下半年核心订单已全额锁定,且预付款已到账’。FastText 可能会因为捕捉到‘流水下滑’而直接给出高风险标签;
但 BERT 的 Self-Attention 机制能捕捉到‘虽然...但是...’的转折关系,理解其实际风险可控。对于这种强上下文依赖的复杂场景。”
BERT 推理太慢,在企业级风控引擎里怎么落地?
风控核心链路要求毫秒级响应,直接跑 BERT 确实不现实,且算力成本太高。
我们采用了**‘大小模型协同 + 知识蒸馏’**的架构。
具体做法是:用 BERT 作为 Teacher 模型,对海量无标签数据进行伪标签打标,或者蒸馏出一个轻量级的 Student 模型(如 TextCNN 或压缩版 FastText)部署在实时推理节点。对于 90% 的常规请求,由 Student 模型在 <20ms 内完成分类;
只有当 Student 模型输出的置信度低于阈值,或者文本长度超过 512 token 时,才异步调用 BERT 进行深度分析。结合 ONNX Runtime 和量化技术,我们最终在精度损失不到 1.5% 的情况下,将吞吐量提升了 6 倍,完美满足了 SLA。”
BERT 最大只支持 512 个 Token。几十页的合同你怎么处理的?直接截断吗?”
- 优化方案:“我们采用了chunk优化策略。先用规则或提取合同中的关键段落(如违约责任、担保条款),再将这些关键段落拼接后输入 BERT;
- “真实的欺诈样本可能不到 1%,你的模型 F1 达到 0.89,是不是因为负样本(正常样本)太多刷出来的?你怎么解决类别不平衡?”
- 优化方案:1. 损失函数层面,使用了Focal Loss焦点损失函数。降低易分类样本的权重,让模型专注难分样本;
- Focal Loss 的本质,就是给那些“数量庞大且极易分类”的简单样本“自动降权”,强迫模型把注意力(Loss)集中在那些“数量稀少且极难分类”的困难样本上。
ce_loss = F.cross_entropy(logits, targets, reduction='none')
pt = torch.exp(-ce_loss)
# 核心公式
fl_loss = (1 - pt) ** self.gamma * ce_loss
- 2. 评估指标上,我们不看准确率,主要看PR-AUCPR-AUC?
- ROC-AUC 在正负样本极度不平衡时,会显得过于乐观(因为 FPR 的分母是巨大的负样本)。
- PR-AUC(Precision-Recall 曲线下面积)只关注正样本(黑产)。
- PR-AUC 越高,说明模型在“尽量抓全黑产(高 Recall)”的同时,能“尽量少抓错好人(高 Precision)”。它衡量的是模型对少数类的整体排序能力。
precisions, recalls, thresholds = precision_recall_curve(y_true, y_scores)
欺诈者是会研究你的模型的。如果他们故意把‘过桥资金’写成‘过乔资金’或同音字,你的 FastText/BERT 还能识别吗?”
- 优化方案:“我们引入了对抗训练(Adversarial Training),如 FGM (Fast Gradient Method),在 Embedding 层加入微小扰动进行训练,提升模型鲁棒性。同时,系统保留了‘拼音/同音字映射词典’作为前置规则兜底,确保模型被绕过时仍有规则拦截。”
SLA 指标包括:
- 可用性 (Availability):如 99.9%(全年宕机时间不超过 8.76 小时)。
- 首字延迟 (TTFT - Time To First Token):如 P99 < 200ms(99%的请求在200ms内吐出第一个字)。
- 生成吞吐率 (TPOT / Tokens per second):如 > 50 tokens/s。
- 错误率:如 < 0.1%。
Feature Importance 输出
一句话破题:随机森林用来告诉你“哪些特征对预测结果贡献最大”的机制。
- 核心原理(两种主流计算方法):
- 基于不纯度(Gini Importance / 默认方法):在建树的过程中,每次用某个特征进行节点分裂时,都会计算它带来的“基尼不纯度”的减少量。把所有树中该特征带来的减少量累加并平均,就是它的 Importance。值越大,说明这个特征越能把数据分得越开。
- 基于排列(Permutation Importance / 更严谨):在验证集上,把某个特征的值随机打乱,然后看模型的预测精度下降了多少。如果打乱后精度暴跌,说明模型极度依赖这个特征,它的 Importance 就高。