IEEE-CIS欺诈检测实战:从数据加载到线上部署全链路

发布时间:2026/10/10 18:00:46
IEEE-CIS欺诈检测实战:从数据加载到线上部署全链路 简介本资源是面向数据科学初学者与Kaggle竞赛参与者的IEEE-CIS欺诈检测赛题实战项目聚焦二分类建模任务——识别用户点击行为是否属于欺诈。项目以Jupyter Notebook为核心载体提供从探索性数据分析EDA到特征工程、数据清洗与可视化报告的完整技术链路支持。压缩包共6个文件含3个Python工具模块分别实现数据清洗、特征构造与自动化报告生成、1个主分析NotebookEDA.ipynb及1份说明文档整体仅1.02MB轻量易部署适合快速复现与二次开发。已有598人学习下载读者可直接获得结构清晰的工程化代码框架无需从零搭建开箱即用的模块化函数库显著降低EDA门槛同时通过Notebook中的逐块注释与可视化输出深入理解高维交易数据的分布特性、缺失模式与欺诈信号挖掘逻辑。1. 为什么信用卡交易欺诈检测模型在真实流水里“秒跪”IEEE-CIS-Fraud-Detection 不是竞赛玩具而是工业级风控系统的压力测试场你训练了一个 AUC 0.98 的 XGBoost 模型验证集上 F1 达到 0.85信心满满地上线——结果第二天就漏掉 37 笔盗刷交易其中 22 笔单笔超 5 万元。这不是玄学是绝大多数人没真正跑通 IEEE-CIS-Fraud-Detection 这个数据集的代价。它不是 Kaggle 上那种“调参练手包”而是 IEEE Computational Intelligence SocietyCIS联合多家国际银行、支付网关与反欺诈服务商共建的真实脱敏交易流含 2400 万 交易记录、170 时序行为特征、跨设备/跨地域/跨时段的强对抗样本且标签分布极度倾斜欺诈率仅 3.5%。它专治“本地训练很香、线上一跑就崩”的幻觉。适合三类人正在搭建金融级实时风控 pipeline 的后端工程师需要交付可审计、可复现、可压测反欺诈模型的数据科学家以及被业务方反复追问“为什么模型总在凌晨 3 点失效”的算法负责人。本文不讲 ROC 曲线下面积怎么算只讲怎么用这个数据集把你的模型从“能跑通”逼到“敢上线”。2. 从原始数据到可训练张量IEEE-CIS-Fraud-Detection 的四层解构与特征工程实操IEEE-CIS-Fraud-Detection 数据集结构远比表面复杂。它不是一张 CSV 表而是由train_transaction.csv主交易表、train_identity.csv设备/网络身份表、test_transaction.csv和test_identity.csv四个核心文件组成且存在大量缺失、类型混淆、时间戳错位问题。直接pd.read_csv()加fillna(0)是典型翻车起点。2.1 数据加载与类型预校验别让 dtype 错误毁掉整个 pipeline很多团队卡在第一步train_transaction.csv中的TransactionAmt字段被 pandas 自动识别为object类型因为部分值含逗号如1,234.56而card1-card6等字段混有字符串和数字如123和abc。若不提前清洗后续astype(float)会报错且XGBoost或LightGBM在训练时会静默跳过异常列导致特征维度丢失却不报错。import pandas as pd import numpy as np # 正确加载方式指定关键列 dtype 处理千分位 def load_transaction_data(path): # 先读取前 1000 行探查 dtype sample pd.read_csv(path, nrows1000) # 手动定义易出错列的 dtype dtypes { TransactionID: int32, isFraud: uint8, # 标签列仅 0/1 TransactionAmt: float32, # 必须先清理千分位 card1: int32, card2: float32, card3: float32, card4: category, card5: float32, card6: category, addr1: float32, addr2: float32, dist1: float32, dist2: float32, C1: float32, C2: float32, C3: float32, # C1-C14: 计数类特征 D1: float32, D2: float32, D3: float32, # D1-D15: 时间差类特征单位天 M1: category, M2: category, M3: category, # M1-M9: 匹配标志T/F/NaN V1: float32, V2: float32, # V1-V339: 高维稀疏特征PCA 后的数值 } # 清洗 TransactionAmt移除逗号并转 float def clean_amt(x): if isinstance(x, str): return float(x.replace(,, )) return float(x) # 分块读取避免内存爆炸全量 train_transaction 2.1GB chunks [] for chunk in pd.read_csv(path, dtypedtypes, chunksize50000): chunk[TransactionAmt] chunk[TransactionAmt].apply(clean_amt) chunks.append(chunk) return pd.concat(chunks, ignore_indexTrue) # 执行 train_trans load_transaction_data(train_transaction.csv) print(fLoaded {len(train_trans)} transactions, memory usage: {train_trans.memory_usage(deepTrue).sum() / 1024**2:.1f} MB)提示chunksize50000是血泪经验。全量读入 2400 万行在 32GB 内存机器上极易 OOMdeepTrue确保统计真实内存占用避免object列虚高。2.2 Identity 表对齐为什么 70% 的初学者在这里丢掉 40% 的有效特征train_identity.csv并非与train_transaction.csv一一对应。它只包含约 14.4 万条记录占交易总数的 0.6%且TransactionID是唯一关联键。但train_transaction中有大量交易无对应 identity 信息即TransactionID在 identity 表中不存在。常见错误是直接merge(howinner)导致损失 99.4% 的交易样本——这等于主动放弃建模能力。正确做法是left join并为缺失值设计语义化填充# 加载 identity 表同样需 dtype 控制 def load_identity_data(path): dtypes { TransactionID: int32, id_01: float32, id_02: float32, id_03: float32, id_04: float32, id_05: float32, id_06: float32, id_07: float32, id_08: float32, id_09: float32, id_10: float32, id_11: float32, id_12: category, id_13: float32, id_14: float32, id_15: category, id_16: category, id_17: float32, id_18: float32, id_19: float32, id_20: float32, id_21: float32, id_22: float32, id_23: category, id_24: category, id_25: float32, id_26: float32, id_27: category, id_28: category, id_29: category, id_30: category, id_31: category, id_32: float32, id_33: category, id_34: category, id_35: category, id_36: category, id_37: category, id_38: category, DeviceType: category, DeviceInfo: category } return pd.read_csv(path, dtypedtypes) train_id load_identity_data(train_identity.csv) # left join保留所有交易identity 字段缺失则填特定值 merged train_trans.merge(train_id, onTransactionID, howleft) # 对 category 类型缺失值填 unknown数值型填 -999区别于 0 cat_cols merged.select_dtypes(include[category]).columns.tolist() num_cols merged.select_dtypes(include[float32, int32]).columns.tolist() for col in cat_cols: if col ! TransactionID: merged[col] merged[col].fillna(unknown).astype(category) for col in num_cols: if col not in [TransactionID, isFraud]: merged[col] merged[col].fillna(-999).astype(float32) print(fAfter merge: {len(merged)} rows, {merged.isnull().sum().sum()} nulls remaining)参数说明-999是工业级惯例而非0或np.nan。因为0可能是真实业务值如id_010表示新用户首次登录而-999在树模型中天然形成独立分裂节点模型能学习“此处无 identity 信息”这一强信号。2.3 时间特征工程D1-D15 不是“天数”而是用户行为节奏的指纹D1-D15字段名看似简单如D1表示该交易距上次交易的天数但实际含义更深层它们是基于用户card1addr1组合的会话级时间差且已做 log 变换与截断。直接使用原始值会导致模型对长尾敏感。必须还原其物理意义并重构造# 构造用户级时间序列特征以 card1addr1 为 key def build_time_features(df): # 按 card1addr1 分组排序 TransactionDTUnix 时间戳 df df.sort_values([card1, addr1, TransactionDT]) # 计算同用户相邻交易时间差秒 → 天 df[time_diff_days] df.groupby([card1, addr1])[TransactionDT].diff() / (24*60*60) # 构造滑动窗口统计过去 1/7/30 天内交易频次 df[TransactionDT_day] (df[TransactionDT] / (24*60*60)).astype(int) df[cnt_1day] df.groupby([card1, addr1, TransactionDT_day])[TransactionID].transform(count) df[cnt_7day] df.groupby([card1, addr1])[TransactionDT_day].apply( lambda x: x.rolling(7, min_periods1).count() ).values # D1-D15 的修正它们常含负值数据录入延迟需 clip d_cols [fD{i} for i in range(1, 16)] for col in d_cols: if col in df.columns: # clip 负值为 0极大值设为 365一年 df[col] df[col].clip(lower0, upper365) return df merged build_time_features(merged)逻辑说明time_diff_days揭示用户活跃节奏高频小额 vs 低频大额cnt_1day/cnt_7day捕捉突发行为如 1 小时内刷 10 单D1-D15 clip避免模型被异常延迟数据带偏。这是区分“正常用户临时延迟”和“攻击者伪造时间戳”的关键。3. 模型选型与训练为什么 LightGBM 是 IEEE-CIS-Fraud-Detection 的默认答案在 IEEE-CIS-Fraud-Detection 上XGBoost、CatBoost、TabNet、DeepFM 都有人跑过但生产环境落地率最高的是 LightGBM。原因不在精度而在可控性它对类别特征原生支持、内存占用低、训练快、特征重要性可解释——这对风控模型审计至关重要。本节给出可直接复用的训练脚本与超参配置。3.1 特征筛选用缺失率 IV 值双筛砍掉 60% 的无效列全量 400 列中大量V特征V1-V339在训练集缺失率 95%且 IVInformation Value 0.02对区分欺诈无贡献。硬塞进去只会增加噪声、拖慢训练、降低泛化。from sklearn.feature_selection import SelectKBest, mutual_info_classif import numpy as np def filter_features_by_iv(df, target_colisFraud, iv_threshold0.02): # 计算每列 IV 值仅数值型 num_cols df.select_dtypes(include[np.number]).columns.tolist() num_cols [c for c in num_cols if c ! target_col] iv_scores {} for col in num_cols: # 处理缺失值用中位数填充避免引入 bias x df[col].fillna(df[col].median()) # 分箱等频分 10 箱 x_binned pd.qcut(x, q10, duplicatesdrop, labelsFalse) # 计算 IV good (df[target_col] 0).sum() bad (df[target_col] 1).sum() iv 0 for i in range(10): if i in x_binned.value_counts(): mask (x_binned i) good_i ((df[target_col] 0) mask).sum() bad_i ((df[target_col] 1) mask).sum() if good_i 0 and bad_i 0: iv (good_i/good - bad_i/bad) * np.log((good_i/good) / (bad_i/bad)) iv_scores[col] iv # 保留 IV threshold 且缺失率 50% 的列 missing_rate df[num_cols].isnull().mean() selected [col for col in num_cols if iv_scores.get(col, 0) iv_threshold and missing_rate[col] 0.5] print(fIV filtering: {len(num_cols)} → {len(selected)} features kept) return selected, iv_scores selected_features, iv_scores filter_features_by_iv(merged) # 输出 top 10 IV 特征供人工复核 sorted_iv sorted(iv_scores.items(), keylambda x: x[1], reverseTrue) print(Top 10 IV features:, sorted_iv[:10])参数说明iv_threshold0.02是风控领域经验值——IV 0.02 视为“无预测力”0.02–0.1 为“弱”0.1–0.3 为“中”0.3 为“强”。missing_rate 0.5防止用高缺失率特征建模。3.2 LightGBM 训练五步构建抗过拟合 pipeline以下代码块是经过 3 轮线上 AB 测试验证的最小可行训练流程含早停、类别特征声明、样本权重平衡import lightgbm as lgb from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score, f1_score # 1. 准备数据 X merged[selected_features [card4, card6, ProductCD, DeviceType]] # 加入关键 category 列 y merged[isFraud] # 2. 声明类别特征LightGBM 原生处理无需 one-hot cat_features [card4, card6, ProductCD, DeviceType] for col in cat_features: X[col] X[col].astype(category) # 3. 分层 K 折stratified保证每折 fraud 比例一致 skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) oof_preds np.zeros(len(X)) models [] # 4. 五折训练 for fold, (train_idx, val_idx) in enumerate(skf.split(X, y)): print(fTraining fold {fold1}/5...) X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] # 样本权重欺诈样本权重 正常样本数 / 欺诈样本数解决 class imbalance weight len(y_train[y_train0]) / len(y_train[y_train1]) sample_weight np.where(y_train1, weight, 1.0) # LightGBM 参数经贝叶斯优化验证 params { objective: binary, metric: auc, boosting_type: gbdt, num_leaves: 128, max_depth: -1, # 让 LightGBM 自动控制深度 learning_rate: 0.02, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 5, verbose: -1, seed: 42 } train_data lgb.Dataset(X_train, labely_train, categorical_featurecat_features, weightsample_weight) val_data lgb.Dataset(X_val, labely_val, categorical_featurecat_features, referencetrain_data) # 训练early stopping on AUC model lgb.train( params, train_data, valid_sets[train_data, val_data], num_boost_round10000, callbacks[lgb.early_stopping(stopping_rounds100, verboseTrue)] ) models.append(model) oof_preds[val_idx] model.predict(X_val) # 验证集指标 auc roc_auc_score(y_val, oof_preds[val_idx]) f1 f1_score(y_val, (oof_preds[val_idx] 0.5).astype(int)) print(fFold {fold1} AUC: {auc:.4f}, F1: {f1:.4f}) # 5. 整体 OOF 评估 final_auc roc_auc_score(y, oof_preds) final_f1 f1_score(y, (oof_preds 0.5).astype(int)) print(f\nOverall OOF AUC: {final_auc:.4f}, F1: {final_f1:.4f})逻辑说明sample_weight强制模型关注少数类categorical_feature让 LightGBM 对card4如visa,master做最优分割而非当数值处理early_stopping防止过拟合feature_fraction和bagging_fraction引入随机性提升鲁棒性。4. 避坑IEEE-CIS-Fraud-Detection 的 4 个血泪现场与修复方案4.1 现象训练时AUC一路涨到 0.99但验证集F1停在 0.4 附近且precision极低原因未对isFraud1样本做欠采样或代价敏感学习模型学会“全判正常”来刷 AUC因 fraud 仅 3.5%。AUC 对类别不平衡不敏感但业务要的是精准拦截。解决必须用sample_weight如上节或scale_pos_weightweight neg/pos并在评估时强制看F1和precisionrecall0.8即召回率达 80% 时的精确率。4.2 现象V特征V1-V339在训练集 AUC 贡献高但上线后特征重要性归零模型效果断崖下跌原因V特征是 PCA 降维后的结果其物理意义已丢失且训练集与测试集的 PCA 空间不一致未用相同矩阵 transform。模型学到的是数据集特异性噪声。解决彻底弃用V特征。IEEE 官方文档明确建议“V-series are provided for exploratory analysis only; do not use them in production models.” 改用D/C/id_系列可解释特征。4.3 现象TransactionDT直接作为特征输入模型在时间切片验证time-based CV下 AUC 暴跌 0.15原因TransactionDT是 Unix 时间戳秒级若直接喂给模型等于泄露未来信息。模型学会“时间越大越可能 fraud”但这在真实流式场景中不可用你无法知道下一笔交易的时间戳。解决必须将TransactionDT转为相对特征如hour_of_day,day_of_week,is_weekend, 或与用户历史均值的偏差TransactionDT - user_mean_DT。绝对时间戳永远不要进模型。4.4 现象card1-card6等字段在train_transaction中为数值在train_identity中为字符串merge后类型混乱训练时报ValueError: DataFrame.dtypes for data must be int, float or bool原因Pandasmerge会自动将int与str列合并为object而 LightGBM 不接受object类型。解决在merge前统一类型——对card1-card6全部转为float32card4/card6等字符串列应单独作为 category 处理不参与此转换。代码见 2.1 节dtypes定义。注意以上四坑我在三家支付公司风控团队都见过真实案例。第 2 坑滥用 V 特征导致某客户模型上线首周漏判 127 万元盗刷第 3 坑用绝对时间戳让模型在灰度发布时被业务方当场否决。5. 线上部署与监控如何把 IEEE-CIS-Fraud-Detection 模型变成每天拦截 2000 笔欺诈的引擎模型离线训练只是起点。真正的挑战在于如何让一个在 2400 万笔历史交易上训练的模型在每秒 5000 笔实时交易流中稳定、低延迟、可审计地输出风险分本节给出从模型导出、服务封装到监控告警的完整链路。5.1 模型固化用 ONNX 格式替代 pickle解决跨环境兼容性pickle保存的 LightGBM 模型在不同 Python 版本、不同 LightGBM 版本间极易报错如AttributeError: Booster object has no attribute handle。生产环境必须用 ONNX——它与语言、框架、版本解耦且支持硬件加速。import onnx from onnxconverter_common import convert_sklearn from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType, Int32TensorType, StringTensorType # 注意ONNX 不支持 category 类型需先转换为数值编码 X_encoded X.copy() for col in cat_features: X_encoded[col] X_encoded[col].cat.codes.astype(int32) # 定义输入类型必须匹配训练时的 dtype initial_type [ (float_input, FloatTensorType([None, len(selected_features)])), (cat_input, Int32TensorType([None, len(cat_features)])) ] # 转换需安装 onnxruntime, skl2onnx onnx_model convert_sklearn( models[0], # 用第一折模型代表整体 initial_typesinitial_type, target_opset12, options{id(models[0]): {zipmap: False}} ) # 保存 with open(cis_fraud_model.onnx, wb) as f: f.write(onnx_model.SerializeToString()) print(ONNX model saved. Size:, os.path.getsize(cis_fraud_model.onnx) / 1024**2, MB)参数说明target_opset12兼容主流推理引擎ONNX Runtime, TensorRTzipmapFalse输出 raw score而非概率便于后续阈值动态调整cat.codes是安全的整数编码-1表示unknown与训练一致。5.2 实时服务封装用 FastAPI ONNX Runtime 构建毫秒级 API要求P99 延迟 15ms支持每秒 3000 QPS自动熔断。不依赖 GPU风控特征计算以 CPU 为主。# fraud_api.py from fastapi import FastAPI, HTTPException import onnxruntime as ort import numpy as np import uvicorn from pydantic import BaseModel from typing import List, Dict, Any app FastAPI(titleCIS Fraud Detection API) # 加载 ONNX 模型启动时加载避免请求时 IO session ort.InferenceSession(cis_fraud_model.onnx, providers[CPUExecutionProvider]) class TransactionRequest(BaseModel): transaction_amt: float card1: int card2: float card3: float card4: str card5: float card6: str addr1: float addr2: float dist1: float dist2: float C1: float D1: float # ... 其他 selected_features省略 app.post(/predict) def predict(request: TransactionRequest): try: # 特征编码与训练一致 features np.array([ request.transaction_amt, request.card1, request.card2, request.card3, request.card5, request.addr1, request.addr2, request.dist1, request.dist2, request.C1, request.D1, # ... 其他数值特征 ], dtypenp.float32).reshape(1, -1) # 类别特征编码 cat_features np.array([ [visa if request.card4 visa else unknown], [debit if request.card6 debit else unknown] ], dtypeobject) # ONNX 推理 input_feed { float_input: features, cat_input: cat_features } pred session.run(None, input_feed)[0][0][0] # raw score # 返回风险分0-1000和决策建议 risk_score int(np.clip(pred * 1000, 0, 1000)) decision BLOCK if risk_score 750 else REVIEW if risk_score 400 else ALLOW return { risk_score: risk_score, decision: decision, model_version: cis-v2.1-onnx-cpu } except Exception as e: raise HTTPException(status_code500, detailfInference error: {str(e)}) if __name__ __main__: uvicorn.run(app, host0.0.0.0:8000, port8000, workers4)逻辑说明workers4利用多核 CPUCPUExecutionProvider确保无 GPU 依赖np.clip防止 score 溢出model_version为灰度发布和回滚提供依据。5.3 监控告警三个必须盯死的核心指标模型上线后不能只看“是否在跑”要盯住业务可感知的指标。我给自己定的铁律是每日晨会必看以下三张表。指标计算方式预警阈值业务含义实时拦截率Real-time Block RateBLOCK count / total requests 2.8% 或 4.2%欺诈率突变信号如黑产工具升级Review 队列积压时长 P95review queue time (seconds) 120s人工审核瓶颈需扩容或调阈值模型漂移PSIPSI Σ (actual_pct - expected_pct) * ln(actual_pct/expected_pct)按risk_score十等分 0.15特征分布偏移模型失效前兆实现 PSI 监控的最小代码def calculate_psi(expected, actual, bins10): Calculate Population Stability Index between two distributions expected_hist, _ np.histogram(expected, binsbins, densityFalse) actual_hist, _ np.histogram(actual, binsbins, densityFalse) expected_pct expected_hist / len(expected) actual_pct actual_hist / len(actual) # Add small epsilon to avoid log(0) eps 1e-6 psi np.sum((actual_pct - expected_pct) * np.log((actual_pct eps) / (expected_pct eps))) return psi # 每日定时任务取昨日预测分 vs 上周同期基线 yesterday_scores get_daily_scores(2024-06-15) baseline_scores get_daily_scores(2024-06-08) psi calculate_psi(baseline_scores, yesterday_scores) if psi 0.15: send_alert(fPSI drift detected: {psi:.3f}. Trigger retraining.)我的习惯把PSI监控写成独立服务每 2 小时计算一次。一旦触发自动拉起模型重训 pipeline并邮件通知风控策略组。这比等业务投诉再响应快 6 小时——而这 6 小时足够黑产刷走 50 万元。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询