aniljayakar/AMLProject
GitHub: aniljayakar/AMLProject
该项目基于IBM合成数据集进行反洗钱检测研究,通过对比多种机器学习算法与特征工程,解决高度不平衡交易数据中的精准率与召回率权衡问题。
Stars: 8 | Forks: 0
# AML 交易监控
**5,078,345 笔合成交易。每 981 笔中有 1 笔是非法的。目标是在不因误报淹没调查人员的情况下,尽可能多地发现非法交易。**
`5.1M 笔交易` | `0.102% 洗钱率` | `65+ 个工程化特征` | `最佳 F1: 0.57 (XGBoost, 100:1)`
## 问题陈述
人工审查每天数百万笔交易在操作上是不可行的。基于规则的系统会产生过多的警报,并且会遗漏基于模式的洗钱行为。本项目回答的问题很具体:对于具有时间约束的 510 万行不平衡合成交易,哪种模型在哪种类别平衡比例下,能为固定人力的合规团队生成最有用的警报队列?
数据集是 IBM 的 IT-AML 合成数据集的 HI-Small 子集。涵盖十天的交易。洗钱率为 0.102%。包含八种不同的拓扑类型,包括分散型、聚集-分散型、循环型和二分图模式。

## 业务背景
据估计,洗钱每年给全球经济造成 8000 亿至 2 万亿美元的损失。金融机构在法律上有义务检测和报告可疑活动,但在大规模下人工审查数百万笔交易是不可能的。
构建 AML 检测系统的核心挑战不在于准确性。而在于 **精确率-召回率权衡**:
- 误报过多:运营团队不堪重负,正常客户受到干扰
- 漏报过多:非法交易未被检测到,引发监管和声誉风险
本项目通过系统性地比较模型、重采样策略和可解释性工具,直接解决了这一权衡问题。
## 结果
本项目涵盖了完整的检测流水线:云端摄取、多方法离群点诊断、针对 65+ 个特征的拓扑感知特征工程、系统性重采样扫描,以及用于 SAR(可疑活动报告)支持的 LIME/SHAP 可解释性层。
| 模型 | 精确率 | 召回率 | F1 | ROC-AUC |
|---|---|---|---|---|
| XGBoost (调优, 100:1) | 0.66 | 0.51 | **0.57** | **0.99** |
| Random Forest (调优, 100:1) | 0.85 | 0.37 | 0.52 | 0.95 |
| XGBoost (正常数据) | 0.53 | 0.50 | 0.51 | 0.98 |
| Random Forest (正常数据) | 0.89 | 0.33 | 0.48 | 0.97 |
| LightGBM | 0.02 | 0.53 | 0.03 | 0.80 |
| Decision Trees | 0.27 | 0.20 | 0.23 | 0.60 |
| SGD / SVM 变体 | <0.01 | <0.10 | <0.02 | N/A |
一个预测所有交易均为合法的模型准确率为 99.9%。准确性不是衡量指标。F1 分数能同时保持精确率和召回率可见。线性模型无条件失效:交易网络数据中的非线性关系无法通过超平面捕捉。LightGBM 实现了高召回率但精确率接近零,使其在默认阈值下操作上无用。
## 流水线架构
```
Raw Data (Azure ML Datastore)
|
v
Profiling + Datatype Corrections
[IQR + Z-score + Isolation Forest on amounts. No row removal. log1p transforms applied.]
|
v
Temporal Split [Train: Sep 1-8 | Test: Sep 9-18]
[train_stats propagated to test set. Global stat fallback for unseen accounts.]
|
v
Feature Engineering [65+ features across 3 typology-aware categories]
Velocity windows (24H/7D/14D rolling)
Network centrality (DiGraph, 6 node metrics)
Structuring indicators (NearThreshold, StructuringScore, IsPotentialSmurfing)
|
v
Resampling Sweep [1:1 to 100:1 downsampling + 10x upsampling]
[sklearn.utils.resample. No SMOTE. Each ratio evaluated independently across all model families.]
|
v
Model Comparison [7 families: DT, RF, XGBoost, LGBM, SGD, LR, SVM]
[RandomizedSearchCV + StratifiedKFold for XGBoost. Best: XGBoost 100:1.]
|
v
Explainability [SHAP (global) + LIME (per-transaction)]
[Investigator audit trail for SAR filing.]
```
## 数据流水线
### 摄取
使用 SDK v1 直接从 Azure ML `Datastore` 加载数据:
```
from azureml.core import Workspace, Dataset, Datastore
workspace = Workspace(subscription_id, resource_group, workspace_name)
datastore = Datastore.get(workspace, "finalproject")
dataset = Dataset.Tabular.from_delimited_files(path=(datastore, 'HI-Small_Trans.csv'))
df = dataset.to_pandas_dataframe()
```
这是一个云端连接的实验设置。所有计算均在本地 notebook 内核中运行。没有 `Experiment`、`Run` 或 `ScriptRunConfig` 跟踪。`Workspace` 和 `Datastore` 对象提供了可重现的、版本控制的数据访问。
### 分析与完整性
**数据类型修正**在任何计算之前运行。`Timestamp` 被转换为 `datetime64`。银行标识符和货币转换为 `category`。账户 ID 转换为 `string`。金额转换为 `float64`。在 510 万行数据上,滚动窗口操作中静默的 dtype 强制转换会产生错误的结果。这些修正防止了这种情况。
**离群点诊断**按顺序使用三种方法应用于 `Amount Received`(接收金额)和 `Amount Paid`(支付金额):
- IQR 方法:标记超出 `Q1 - 1.5*IQR` / `Q3 + 1.5*IQR` 的值
- Z-score:标记 `|z| > 3` 的值
- Isolation Forest:`contamination=0.05`,同时对两列进行多元检测
没有删除任何行。极端交易金额携带了 AML 数据中的信号。对两列应用 `np.log1p` 变换以压缩分布而不丢弃数据。
**工程后空值** 在 `handle_null_values` 中处理:`TimeDifference`(时间差)填充列平均值,`AvgTimeDifference`(平均时间差)从 `TimeDifference` 链式衍生,`RelativeFrequency`(相对频率)填充 1。将 `RelativeFrequency` 填充为 1 是一个有意为之的假设:账户正以其自己的平均节奏进行交易。`StructuringScore`(结构化评分)在插补后重新计算以保持一致性。
## 时间划分与泄漏控制
```
Training: 2022-09-01 to 2022-09-08
Test: 2022-09-09 to 2022-09-18
```
在这里使用随机划分是错误的。未来的交易不应影响过去案例的评分。两个泄漏控制机制直接写入特征函数中。
**`train_stats` 传播。** `create_derived_features` 将训练集中的 `TotalAmountReceived` 和 `AvgTimeDifference` 存储在一个字典中。测试集调用会接收该字典:
```
train_df, train_stats = create_derived_features(train_df)
test_df, _ = create_derived_features(test_df, train_stats)
```
测试集上的 `RelativeAmount`(相对金额)由训练集的总交易量进行归一化。测试集上的 `AvgTimeDifference` 使用训练期间的节奏,而不是测试期间自身的节奏。
**全局统计回退。** `add_amount_received_features` 接受来自训练的 `global_mean`、`global_median` 和 `global_std`。出现在测试集中但未出现在训练集中的账户会回退到这些值,而不是根据测试期数据计算其自身的统计数据。
**已知限制。** `compute_pattern_based_features` 是在完整划分前的数据帧上调用的。7 天和 14 天的滚动平均值以及 24 小时速度窗口是在强制执行 9 月 8/9 日边界之前计算的。切点处的交易会产生轻微的时间泄漏。在 10 天的数据集上这是一个很小的重叠,但这确实是泄漏,并且是在生产重写中首先需要修正的地方。
## 特征工程
### 速度与突发特征
在带有时间索引的数据帧上,使用 `groupby().rolling(window='24H')` 进行时间感知滚动窗口计算。代码库中任何地方均未使用基于行计数的滚动计算。
| 特征 | 逻辑 | 目标拓扑 |
|---|---|---|
| `small_transactions_24h` | 每个账户在 24 小时窗口内低于 $3,000 的交易计数 | 拆分洗钱 |
| `unique_banks_24h` | 每个账户在 24 小时窗口内不同的收款银行 | 分层 |
| `AvgAmountReceived_7days` | 每个账户的 7 天滚动平均值 | 基线偏差 |
| `AvgAmountReceived_14days` | 每个账户的 14 天滚动平均值 | 基线偏差 |
| `RapidTransactions` | 二元值:交易间隔 < 5 分钟 | 自动化分层 |
| `RelativeFrequency` | 每个账户的 `TimeDifference / AvgTimeDifference` | 偏离自身历史的节奏 |
| `TimeSinceFirstTransaction` | 距离账户首笔交易的秒数 | 账户年龄代理 |
### 网络特征
基于账户到账户的边构建有向 `networkx.DiGraph`。所有节点指标均在完整划分前的图上计算,并通过账户映射回交易。
| 特征 | 实现 | 捕获内容 |
|---|---|---|
| `DegreeCentrality` | `nx.degree_centrality(G)` | 位于交易网络中心的账户 |
| `WeightedDegree` | `G.degree(weight='Amount Received')` | 按交易量缩放的度数 |
| `LocalClusteringCoefficient` | `nx.clustering(G)` | 紧密的子网络,循环流 |
| `AvgNeighborDegree` | `nx.average_neighbor_degree(G)` | 与结构中心节点的接近度 |
| `EgoNetworkSize` | 每个节点的 `len(nx.ego_graph(G, node))` | 1 跳邻域的大小 |
| `IsIsolate` | 每个节点的 `nx.is_isolate(G, node)` | 异常的单例账户 |
图特征是本模型中最强的单一预测类别。一个在孤立状态下看起来不起眼的账户,往往位于数十个大额流量的交汇处。
`EgoNetworkSize` 是通过 Python 循环调用每个节点的 `nx.ego_graph` 计算的。这是计算瓶颈。直接从边列表计算入度和出度会更快,并且无需遍历每个节点的图即可产生相关结果。
### 结构化与阈值特征
| 特征 | 逻辑 | 目标拓扑 |
|---|---|---|
| `NearThreshold` | 二元值:接收金额 $9,000 至 $10,000 | 规避 CTR(货币交易报告) |
| `Nearly_10K_Transactions` | 每个账户的近阈值事件计数 | 重复结构化 |
| `IsPotentialSmurfing` | 二元值:接收金额 < $1,000 | 阈值下的微额支付 |
| `StructuringScore` | 等权和:`RelativeAmount + Amount Ratio + TimeDifference + NearThreshold + incoming_to_outgoing_ratio + amount_discrepancy` | 综合风险评分 |
| `IsPotentialStructuring` | 二元值:`StructuringScore > 95th percentile` | 高综合风险 |
`StructuringScore` 存在一个值得直接指出的设计缺陷:所有六个分量的权重均为 1,且 `TimeDifference` 是以原始秒数为单位的。它在数值上主导了合成评分。该评分在方向上是有用的,但未经校准。根据拓扑标签对分量进行加权是显而易见的下一步。
### 银行聚类
大约 40,000 家独特的银行基于对数转换后的交易总额、网络度数、聚类系数和方向流向(仅发送、仅接收或两者兼有)进行分析。特征经过标准化,并通过肘部法选定 k=5,使用 K-Means 进行聚类。聚类标签按风险水平进行序数编码,并作为 `From Bank Cluster` 和 `To Bank Cluster` 连接到交易中。这将高基数的银行标识符转换为模型可用的序数风险代理。
### 编码
`Receiving Currency`(接收货币)、`Payment Currency`(支付货币)和 `Payment Format`(支付格式)使用 `category_encoders.TargetEncoder` 进行目标编码。编码器在训练集上拟合,并应用于两个集合,因此不会通过编码步骤发生标签泄漏。
周期性时间特征通过对小时和周应用 sin/cosine 变换来处理。这保留了循环距离:23:00 和 01:00 被视为接近,而不是最大距离。
## 类别不平衡与模型选择
### 重采样策略
不使用 SMOTE。所有重采样均使用 `sklearn.utils.resample`。
一种上采样配置:少数类有放回地增加 10 倍。七种下采样配置,多数类无放回地减少:
| 比例 | 多数类样本数 |
|---|---|
| 1:1 | 等于少数类数量 |
| 10:1 | 少数类数量的 10 倍 |
| 20:1 | 少数类数量的 20 倍 |
| 30:1 | 少数类数量的 30 倍 |
| 60:1 | 少数类数量的 60 倍 |
| 80:1 | 少数类数量的 80 倍 |
| 100:1 | 少数类数量的 100 倍 |
每个比例在所有模型系列中独立评估。目的是使精确率-召回率权衡变得明确且可配置。在合规环境中,操作点是资源配置决策,而非建模决策。有能力每天审查 500 个警报的团队需要一个针对该预算进行调优的模型。
### 超参数搜索
**Random Forest:** `RandomizedSearchCV`,`cv=3`,`n_iter=10`,`scoring=f1_macro`。调用未显式传递 `StratifiedKFold`,因此在 0.1% 正样本率下无法保证折级别的类别分布。这是比 XGBoost 对应方法更弱的验证设计,也是实验中的一个缺口。
搜索的参数:`n_estimators` [50, 100, 150],`max_features` ['sqrt'],`max_depth` [10, 20, 30],`min_samples_split` [2,5, 10],`min_samples_leaf` [1, 2, 4],`bootstrap` [True, False]。
**XGBoost:** `RandomizedSearchCV` 并显式传递 `StratifiedKFold(n_splits=3, shuffle=True, random_state=42)`。`n_iter=10`,`scoring=f1_macro`。显式的分层保持了极端不平衡比例下各折之间类别比例的稳定。
搜索的参数:
- `n_estimators`: [50, 100, 200]
- `learning_rate`: [0.01, 0.05, 0.1]
- `max_depth`: [3, 4, 5, 6]
- `subsample`: [0.8, 0.9, 1.0]
- `colsample_bytree`: [0.8, 0.9, 1.0]
- `gamma`: [0, 0.1]
- `alpha` (L1 正则化): [0, 0.1, 0.5]
- `lambda` (L2 正则化): [1, 1.5, 2]
搜索空间中的 L1 和 L2 正则化项直接控制了少数类上的过拟合,这是仅靠深度和子采样无法做到的。
## 可解释性与调查员审计追踪
模型评分不足以提交 SAR。合规官员需要知道交易为何被标记,然后才能做出可辩护的升级决策。
**SHAP** (`shap.TreeExplainer`) 应用于调优后的 Random Forest。SHAP 值是可加的,其总和等于模型输出:归因在数学上是精确的,而非近似。汇总图按平均绝对 SHAP 值对特征进行排序,给出了驱动模型的信号的总体视图。单个实例的力图精确显示了哪些特征将特定分数推高至阈值之上以及推高的幅度。
**LIME** (`LimeTabularExplainer`) 生成逐笔交易的解释:
```
explainer = LimeTabularExplainer(
X_train_np, feature_names=X_train.columns.tolist(),
class_names=['Negative', 'Positive'], mode='classification'
)
exp = explainer.explain_instance(X_train_np[i], rf_best.predict_proba)
```
LIME 在目标实例周围扰动输入,并拟合局部线性近似。输出是该特定交易的特征贡献排序列表。对于 SAR 提交,这是具有操作价值的输出:调查员将其附加到案例记录中,并精确记录哪些信号驱动了升级决策。
特征名称直接映射到调查员已经识别的拓扑。`NearThreshold` 是结构化。`unique_banks_24h` 是分层。`IsPotentialSmurfing` 是不言自明的。这种命名是有意为之的。
## 已知限制
- `compute_pattern_based_features` 是在划分前计算的。9 月 8/9 日边界处的滚动窗口会产生轻微泄漏。
- `StructuringScore` 的分量权重相等且未调优。原始秒数单位的 `TimeDifference` 占主导地位。
- `EgoNetworkSize` 是在逐节点循环中计算的。如果不重写,无法扩展到超出此数据集大小的规模。
- Random Forest 搜索中 `cv=3` 而没有显式分层,是比 XGBoost 对应方法更弱的验证设计。
- 所有结果均在合成数据上。对真实交易流的泛化能力尚未确立。
## 未来工作
- 使用训练衍生的基线将 `compute_pattern_based_features` 重新实现为划分后的函数
- 使用逻辑回归系数根据拓扑标签校准 `StructuringScore` 权重
- 用向量化的入度/出度计算替换 `EgoNetworkSize` 循环
- 将 `StratifiedKFold` 显式添加到 Random Forest 超参数搜索中
- 在 IT-AML 的 Medium 和 Large 子集上进行评估以进行规模测试
- 针对定义的警报预算(precision-at-k)而非全局 F1 进行阈值优化
- 将特征流水线打包为带有单元测试的可重用模块
## 设置
```
git clone https://github.com/aniljayakar/AMLProject.git
cd AMLProject
pip install -r requirements.txt
jupyter notebook
```
数据集:IBM IT-AML,HI-Small 子集。从 [Kaggle](https://www.kaggle.com/datasets/ealtman2019/ibm-transactions-for-anti-money-laundering-aml) 下载。如果在本地运行,请将 Azure ML 数据加载单元格替换为指向数据目录的 `pd.read_csv` 调用。
*数据科学硕士,伦敦米德尔塞克斯大学,2023 年。卓越奖。*
标签:AML, Apex, BSD, F1-Score, IBM AML 数据集, LGBM, LightGBM, Python, SGD, SVM, XGBoost, 交易监控, 决策树, 反洗钱, 合规, 异常检测, 支持向量机, 数据挖掘, 数据科学, 无后门, 时间序列分析, 机器学习, 欺诈检测, 特征工程, 特权检测, 类别不平衡, 精确率与召回率, 资源验证, 逆向工具, 重采样, 金融科技, 随机梯度下降, 随机森林, 风控