ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

金融风控场景下的LSTM+Transformer双模型融合:PyTorch实现智能风险预警系统

金融风控场景下的LSTM+Transformer双模型融合:PyTorch实现智能风险预警系统 简介面向金融风控算法工程师与研究生的PyTorch实战文档以LSTMTransformer双模型融合为主线解决金融时序数据建模与风险预警落地难题覆盖从理论到代码的完整闭环。文档共63页开篇梳理信用风险、市场风险、操作风险等金融风控场景随后详解LSTM门控机制与Transformer自注意力原理对比二者结构与适用边界再阐释早期融合、晚期融合、混合融合等策略并给出特征级融合与决策级融合的具体思路后续按PyTorch环境搭建、数据清洗、归一化、特征选择与构造、模型训练评估调优的顺序逐步展开兼顾理论与可操作性。资源包内仅含1个PDF大小2.45MB支持目录章节跳转和阅读器左侧大纲快速定位便于按模块查阅。已有202人学习下载可直接作为智能风险预警系统设计、论文实验或课程项目的参考。 金融风控圈子里这几年有个特别明显的趋势过去大家提到风险预警默认方案就是XGBoost、LightGBM带一些手工特征工程顶多再叠加一个评分卡做解释性兜底。但业务做到后面你会发现一个很棘手的问题——用户的交易行为、设备操作轨迹、资金流转路径这些强时序特征在传统树模型里很难被完整地“吃进去”。你只能靠窗口统计、滑窗比例去近似表达本质上是在把序列信息压扁成一堆统计量信息损耗非常大。我最近在做的这个项目就是围绕“序列建模 长期依赖捕获”两条线同时展开用LSTM和Transformer做双模型融合在PyTorch里搭了一套面向金融风控场景的智能风险预警系统。项目标题就叫《金融风控场景下的LSTMTransformer双模型融合PyTorch实现智能风险预警系统》。这篇文章我想把这套方案从思路到落地讲清楚包括为什么是LSTM配Transformer、双模型融合具体怎么融、损失函数怎么设计、训练时踩了哪些坑以及线上推理时需要注意什么。无论你是刚接触序列模型的风控算法工程师还是已经在做深度学习但想往风控方向转的同学这套方案都可以当作一个不错的起点。1. 模型整体设计与方案选型思路1.1 为什么是LSTM Transformer而不是单模型一路走到黑先聊选型逻辑。LSTM擅长捕捉时序数据中的局部依赖和顺序变化比如一个用户在过去5分钟内连续触发的交易行为模式LSTM能够通过门控机制记住哪些历史信息该保留、哪些该遗忘。但LSTM有一个天生短板长序列场景下信息经过逐时间步的传递会产生衰减即使有记忆单元序列超过几百步之后早期的重要信号仍然容易被后期信息淹没。Transformer这边正好相反自注意力机制让任意两个位置之间的依赖距离都变成1不管前面隔了多长都能直接计算相互影响。但Transformer对局部时序模式天然不敏感——它没有内置的顺序感知能力必须靠位置编码去“喂”顺序信息而且它对局部窗口内细微的连续变化趋势建模效率并不高更擅长抽取全局关系。放在金融风控场景里这两种能力其实都缺一不可。一个异常交易通常伴随两个特征局部序列上突然出现的连续性异常波动比如短时间内高频小额试探后的大额操作以及全局关系上的异常关联比如某个账号忽然与多个历史上有风险的设备产生交叉。单独用LSTM全局长程特征容易丢单独用Transformer局部连续变化又抓不准。所以这项目的核心决策就是把两者串起来先用LSTM在时间维度上做一次序列特征压缩再把压缩后的隐藏状态序列喂给Transformer做全局关系建模最后融合两个分支的输出做风险评分。这个结构不是我拍脑袋定的而是对比了纯LSTM、纯Transformer以及LSTM输出直接拼接Transformer输出三种方案之后线上AUC和业务召回指标综合最优的选择。1.2 双模型融合的三种候选方案取舍提到“双模型融合”很多人第一反应是把LSTM和Transformer各训练一个模型最后把两个模型的预测分数取平均或者做Stacking。这种做法当然能涨点但它没有从结构上让两个模型特征交互。我在项目中试了三种融合方式这里直接对比一下。第一种是独立模型后融合LSTM和Transformer各跑各的最后把输出层的logits均值融合。好处是训练简单但坏处是两个模型各自关注的特征维度大量重叠融合收益有限而且推理时要跑两份模型线上延迟直接翻倍。第二种是特征拼接融合LSTM的最后一个时间步隐藏状态和Transformer编码器输出的CLS向量拼到一起再接全连接层。这个方案比第一种好一些模型内部能共享一些底层特征但仍然不够因为LSTM分支和Transformer分支在底层到高层整个路径上完全没有交互。第三种就是项目最终采用的串行级联 跳跃连接。具体来说输入序列先进LSTM层输出完整的隐藏状态序列而不是只取最后一步再把整条序列映射到Transformer的编码器输入维度进入Transformer编码器做全局注意力建模。Transformer的输出经过池化后与LSTM最后一个时间步的输出做门控融合再接分类头。这样做的好处是Transformer能看到LSTM加工过的序列特征而不是原始输入模型能先学好局部时序模式再学全局关系同时在末端把两路信息融合起来。这个结构有一个非常实际的好处——梯度回传路径短而稳定LSTM部分能够直接接收到来自分类头的监督信号不会因为中间隔着一个深层Transformer而导致梯度衰减。从实际训练曲线来看这个结构的收敛速度明显快于前两种而且训练过程更稳定。1.3 特征工程与序列窗口设计模型结构再复杂输入特征没做好照样白搭。这个项目的数据来自脱敏后的交易流水和用户行为日志每条样本是一个变长序列序列里的每个时间步包含三类特征。第一类是数值型行为特征包括交易金额、账户余额变化量、操作间隔、设备电量等。数值特征我做了两层处理先做缺失值填充用全局中位数兜底再做分位缩尾把98%分位数以外的极端值压缩回来避免个别极值主导梯度。第二类是离散型事件特征比如交易类型、设备类型、渠道来源、操作结果码。这些离散特征我做了embedding映射每个特征嵌入维度统一设为16在模型内部通过torch.nn.Embedding层动态查表。这里有个细节不同离散特征的取值空间差异非常大有的只有几个枚举值有的有上千种所以对高频值做了topN截断低频值统一映射到unkown区间。第三类是时间间隔特征这个很多人会忽略但在风控场景特别关键。用户上一次操作距离当前操作过去了3秒还是3小时含义完全不一样。我把相邻两步之间的时间差取对数后作为一维标量拼进特征向量。窗口设计方面项目采用固定长度截断策略取最近32步行为作为输入序列不足32步的在序列开头做zero-padding并在attention mask中屏蔽掉填充位置。为什么是32不是64或128我做过一组消融实验32步在信息覆盖度和模型推理耗时之间取得了最优平衡64步时AUC只涨了0.003但推理时间增加了近40%。对于绝大多数信贷欺诈和盗号场景32步以内的行为序列已经能够覆盖完整的异常操作链条。2. 核心细节解析与实操要点2.1 网络结构的PyTorch实现要点直接上一段核心的模型定义代码我在项目里用的就是下面这个结构基于PyTorch 1.13实现。import torch import torch.nn as nn class LSTMTTransformerFusion(nn.Module): def __init__(self, input_dim, d_model, nhead, num_layers, num_classes): super().__init__() # LSTM分支 self.lstm nn.LSTM( input_sizeinput_dim, hidden_sized_model // 2, num_layers2, batch_firstTrue, bidirectionalTrue, # 双向LSTM增强局部时序特征提取 dropout0.2 ) # 输入投影层把LSTM输出维度映射到Transformer输入维度 self.input_proj nn.Linear(d_model, d_model) # Transformer编码器 encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforwardd_model * 4, dropout0.1, activationgelu, batch_firstTrue ) self.transformer nn.TransformerEncoder(encoder_layer, num_layersnum_layers) # 门控融合层 self.gate nn.Sequential( nn.Linear(d_model * 2, d_model), nn.Sigmoid() ) self.fusion_proj nn.Linear(d_model * 2, d_model) # 分类头 self.classifier nn.Sequential( nn.Linear(d_model, 64), nn.ReLU(), nn.Dropout(0.2), nn.Linear(64, num_classes) ) def forward(self, x, maskNone): # x shape: (batch, seq_len, input_dim) lstm_out, (h_n, c_n) self.lstm(x) # 双向LSTM取最后一层的前后向隐藏状态拼接 h_n_final torch.cat((h_n[-2], h_n[-1]), dim-1) # (batch, d_model) # 送入Transformer前先做投影 transformer_input self.input_proj(lstm_out) # (batch, seq_len, d_model) # Transformer要求输入第0维是序列维度这里batch_firstTrue可以直接传 if mask is not None: # mask shape: (batch, seq_len)True的位置是padding transformer_out self.transformer(transformer_input, src_key_padding_maskmask) else: transformer_out self.transformer(transformer_input) # 全局池化取序列维度上的均值 transformer_pooled transformer_out.mean(dim1) # (batch, d_model) # 门控融合 concat_feat torch.cat([h_n_final, transformer_pooled], dim-1) gate self.gate(concat_feat) fused gate * h_n_final (1 - gate) * transformer_pooled # 再拼一次原始特征保证信息不丢失 final_feat torch.cat([fused, concat_feat], dim-1) out self.classifier(self.fusion_proj(final_feat)) return out几个关键设计说明一下。LSTM设定为双向hidden_size设为d_model的一半这样双向拼接后正好等于d_model。对金融行为序列来说当前行为的风险含义不仅取决于之前发生了什么也取决于后续紧接着发生了什么——比如一个高风险操作后面往往跟着几分钟的静默期前向LSTM能抓到“连续异常上升”反向LSTM能抓到“异常高峰后急剧冷却”两个方向的信息互补。Transformer部分我用了2层编码器而不是标准的6层。风控序列只有32步特征维度也不大过深的Transformer很容易在中小规模数据上过拟合。消融实验里4层相比2层几乎没有提升但训练时间和显存占用明显增加最终选定2层。多头注意力头数用了8embedding维度是128前馈网络维度是512。gelu激活函数在深层网络中比relu收敛更快实测大概能省10%到15%的迭代轮数。2.2 门控融合层的设计思路门控融合是这套结构的核心创新点思路借鉴了LSTM门控机制。两个分支的特征h和t被拼接后经过一个sigmoid层计算出对应的权重gg接近1时以LSTM特征为主接近0时以Transformer特征为主。理论上模型可以学习到“哪些样本该更依赖局部时序特征哪些该更依赖全局关系特征”的自适应策略。这里有个容易踩的坑就是门控融合之后如果直接把融合结果过分类头特征利用率其实不够。因为h和t拼接后的原始信息在门控运算中是有损耗的所以我在代码里做了一步“融合特征 原始拼接特征再次拼接”让分类头既能看到融合后的信号也能直接看到未经过门控的原始双分支特征。从实验结果看这个小改动让AUC提升了大约0.8个千分点不算多但对于风控模型来说千分之几的AUC在坏账率上可能意味着数百万的金额差异。2.3 损失函数与类别不平衡处理风控场景天然面临严重的样本不平衡正常交易占比通常超过98%风险交易往往不到2%。如果直接用CrossEntropyLoss模型会倾向把所有样本都预测成正常在极端的业务召回要求下根本没法用。我采用的方案是Focal Loss 困难样本加权的组合。Focal Loss的思想很简单对容易分类正确的样本降低其损失权重让模型把注意力集中在难分类的样本上。公式如下其中alpha是正负样本平衡系数gamma是困难样本聚焦参数。import torch.nn.functional as F class FocalLoss(nn.Module): def __init__(self, alpha0.75, gamma2.0): super().__init__() self.alpha alpha self.gamma gamma def forward(self, logits, targets): ce_loss F.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce_loss) focal_loss self.alpha * (1 - pt) ** self.gamma * ce_loss return focal_loss.mean()alpha设成0.75意味着正样本风险样本的损失权重更高因为正样本数量少每个样本的信息量更宝贵。gamma设成2.0是经过网格搜索得到的最优值gamma过大会导致简单样本的损失被压得过低模型在训练早期收敛很慢gamma过小又起不到抑制简单样本的作用。除了Focal Loss我还额外用了一个业务规则约束对近30天内有被人工审核标记为“确认欺诈”的样本在损失函数上额外乘上1.5倍的权重。这个设计是从业务侧倒推回来的因为人工确认的样本置信度远高于机器打标样本模型应该给予更高的学习优先级。注意Focal Loss虽然好用但它对超参数比较敏感。建议在项目启动阶段先用普通交叉熵跑通pipeline确认模型结构和数据处理没问题之后再切Focal Loss否则出问题时你很难判断是结构问题还是损失函数问题。3. 实操过程与核心环节实现3.1 数据预处理与序列样本构建这个项目用的数据集是内部脱敏的支付行为流水原始数据以日志形式存储在Hive表里每个用户是一张“事件流”按时间递增排列。数据预处理的第一件事是清洗去掉交易金额为负或者超过单笔限额的脏数据去掉设备信息全空的样本因为这类样本缺失信息量太大强行喂给模型只会引入噪声。清洗完之后做序列化切分。以用户维度聚合每个用户按时间排序后做成连续事件序列然后以固定窗口32步滑动切分。步长设为8也就是相邻两个训练样本之间有24步重叠。有人会问为什么不用步长1做全量滑窗数据量会大很多但相邻样本高度相似模型训练时大量重复信息实际上是在拖慢收敛。步长8既能保证训练样本量又能避免样本过度冗余。序列内特征具体排布如下表所示特征类别特征名称处理方式数值型交易金额、账户余额变化、操作间隔分位缩尾 标准化离散型交易类型、渠道来源、设备类型、操作结果码Embedding映射维度16时间型距当日0点秒数、星期几周期编码sin/cos变换标准化这一步我用了训练集的均值方差做fit再用同一套参数transform训练集和验证集避免信息泄漏。周期编码容易被忽略但交易行为周内效应非常明显——周一的异常交易模式和周日的完全不同只把“星期几”当成普通整数喂给模型模型学不到周期性的循环关系sin/cos编码能让模型天然理解周一和周日是相邻的。3.2 训练配置与超参数调优记录训练阶段我用的是AdamW优化器初始学习率3e-4权重衰减0.01。学习率调度采用warmup cosine退火前5个epoch线性warmup到3e-4之后按cosine曲线衰减到1e-5。这个策略对Transformer类模型尤其重要因为自注意力模块对学习率敏感冷启动直接用大学习率容易导致训练震荡。Batch size设为128最大训练轮数50轮早停容忍5轮验证集AUC不提升则回滚到最优checkpoint。模型参数量大约在250万左右单张V100显卡训练一个epoch大概需要40秒整个训练过程大约30分钟可以收敛。如果显存有限可以把batch size降到64配合梯度累积两步达到同样的效果。训练过程中我记录了每一轮的训练损失、验证AUC、验证KS和验证F1。一个值得注意的现象是LSTM分支先收敛Transformer分支收敛相对滞后前10轮主要由LSTM主导学习后10轮Transformer开始发力门控权重逐渐向Transformer倾斜。这说明级联结构的训练是分阶段的LSTM在前几轮快速捕获基础的局部时序模式Transformer随后在LSTM提取的特征之上补充全局关系信息。3.3 推理性能优化与线上部署风控模型在线上有一个硬指标单条样本推理延迟不能超过30毫秒。为此我做了三个层面的优化。第一是模型裁剪。去掉推理时用不到的训练辅助模块比如dropout层、focal loss相关变量然后把模型设置为eval模式并调用torch.jit.script做脚本化编译把动态图转为静态图实测推理速度提升约25%。第二是batch推理。线上请求虽然是流式进入的但Web服务端可以做微批量聚合把同时到达的32条样本拼成一个batch一次性前向充分利用GPU并行能力。对比单条逐次推理batch32时的平均单条延迟只有逐次推理的1/5左右。第三是int8量化。在验证集上做动态量化把Linear层和Transformer的Linear权重从float32压缩到int8。这个操作会带来约1%的AUC损失但模型体积从约10MB压缩到2.5MBCPU推理速度提升约3倍。如果业务对精度要求极高也可以选择半精度float16量化精度几乎无损但GPU推理速度提升相对有限。4. 常见问题与排查技巧实录4.1 训练过程Loss不下降这套结构在实际训练时最容易遇到的问题就是Loss怎么调都不降。遇到过三次原因各不相同。第一次是Transformer输入维度不匹配。LSTM输出维度是d_model没错但中间经过了双向拼接后维度是d_model的2倍input_proj层的输入维度写错导致参数初始化不良。排查方法很简单打印模型结构的参数shape逐层核对。第二次是学习率过大导致震荡。Transformer编码器在训练早期对学习率极其敏感3e-4直接冷启动会看到Loss在初始值附近来回跳。解决方案就是前面提到的warmup策略前5个epoch从0线性增长到目标学习率。第三次是数据问题。序列中padding的占比太高相当一部分样本32步里有20步是填充的模型学到的是对一个几乎全空的序列做预测。这个问题最终靠调整窗口大小和过滤短序列样本解决序列长度不足16步的样本直接丢弃。4.2 特征尺度不一致导致梯度爆炸LSTM对输入特征的尺度非常敏感尤其是第一层。不同数值特征如果量纲差异巨大——交易金额可能是几十万操作间隔可能是几百秒——初始化状态下梯度很容易被大数值特征主导导致梯度爆炸。我踩过这个坑之后把所有数值特征统一做了标准化到均值为0、方差为1并且对金额类长尾特征先做log1p变换再标准化。embreakdown一下就是log1p可以把[0, 1e6]的跨度压缩到[0, 14]左右再标准化后各个特征的尺度基本一致模型迭代稳定很多。4.3 线上推理与训练时行为不一致训练时模型输入的序列是完整的32步但线上做实时预警时用户可能只产生了几步操作就触发了风险规则序列长度不足32步。训练时的padding是在序列开头补零线上补零方式如果不一致模型看到的是一个完全不同的分布。解决方式是在训练阶段做了“变长序列模拟”随机截断部分样本让模型在训练时就见过不同长度的输入。这样线上遇到短序列时模型的表现不会出现明显的精度跳水。实测验证集上对长度在8到32步之间的样本AUC波动控制在0.5个千分点以内。4.4 常见问题速查表问题现象可能原因排查/解决方式Loss不下降学习率过大或过小使用warmup调度初始LR从1e-5到3e-4做小规模搜索验证AUC低于0.7特征标准化不一致检查训练集和验证集是否使用同一套fit参数训练震荡严重batch size过小增大batch或使用梯度累积门控权重几乎不变两个分支特征高度相关降低两个分支的共享层数增加LSTM和Transformer的独立深度推理延迟超标模型未做脚本化编译torch.jit.script batch推理 动态量化5. 效果分析与后续扩展方向这套模型最终在内部测试集上相对传统的XGBoost基线模型AUC从0.893提升到0.934KS从0.41提升到0.49在保持召回率不变的前提下把误报率降低了约18%。尤其在涉及跨设备、跨时间片的复杂欺诈模式识别上LSTMTransformer的融合结构优势非常明显纯粹依赖统计特征的树模型几乎无法捕捉这种多维交叉信息。从业务反馈来看模型的风险预警准确率提升后人工审核团队的工作量显著下降。过去需要逐笔审核的高疑交易清单现在可以按模型评分做优先级排序审核资源能集中投入到评分最高的那部分样本上。后续有几个方向值得继续尝试。一个是把用户关系图谱信息引入模型在Transformer的自注意力矩阵基础上叠加图邻接矩阵让模型不仅关注时间维度的依赖也能关注用户之间的社交关联。另一个方向是替换Transformer编码器为更轻量的线性注意力变体在保持精度的前提下把推理速度进一步提升。还有就是对模型输出的评分做可解释性增强通过注意力权重回溯到具体是哪些历史行为触发了风险判定这个对风控业务来说非常重要——模型说用户有风险你得能告诉审核人员“为什么”。我个人在实际操作中的体会是双模型融合不是简单地把两个模型堆在一起就完事关键还是在于理清楚两个模型各自擅长什么、在哪个环节融合、用什么方式融合。LSTM和Transformer的搭配逻辑放在金融风控场景下是自洽的前者管住行为序列里局部而连续的异常节奏后者抓住分散在不同时间位置的关联信号两者互补之后模型对复杂风险模式的整体感知能力确实不在一个量级。如果这篇文章能帮你绕开几个我踩过的坑那这波折腾就值了。本文还有配套的精品资源点击获取
返回列表