资讯动态

IEEE-CIS欺诈检测Notebook实战:特征工程与时间切分避坑指南

发布时间:2026/10/29 20:11:41 来源:尧图企业网站定制
简介面向数据科学竞赛学习者与风控建模初学者的IEEE-CIS欺诈检测EDA资源包围绕二分类欺诈识别任务展开帮助读者理解如何从原始交易数据中完成探索性分析与特征准备。压缩包共6个文件以3个Python脚本、1个Jupyter Notebook为主辅以说明文档与配置项整体约1.02MB体积轻量却覆盖数据清洗、可视化报告与特征工程三条主线。其中Notebook承载深入的EDA分析流程脚本文件分别封装数据清理、图表报告与特征整理函数便于按模块复用和二次改造。已有599人学习适合希望快速上手Kaggle欺诈检测赛题、梳理EDA思路并积累可复用工具函数的读者参考。1. 一份被反复回炉的欺诈检测 Notebook它到底能解决什么拿到 IEEE-CIS-Fraud-Detection 这个资源包时我第一反应不是看模型结构而是先翻它的数据读取和特征工程部分。原因很简单欺诈检测这类任务模型换三四个AUC 可能只动零点几个点但特征怎么拼、时间怎么切、类别怎么编码一步走错后面全白搭。这份以 Jupyter Notebook 为载体的资源核心价值不在某个炫技模型而在于它把「交易表 身份表」双表合并、时间特征拆解、类别高基数编码、交叉验证切分这几件脏活累活串成了一条能跑通的流水线。适合谁手上有一份结构化交易流水、想做二分类风险识别、又不想从零搭特征管道的从业者。新手能照着单元格顺序跑通熟手能直接拆出特征工程模块塞进自己的训练框架。它解决的不是「教你什么是欺诈检测」而是「给你一条能复现、能改参数、能定位问题的基线」。2. 双表合并与时间特征先把数据底座搭稳2.1 交易表与身份表的合并逻辑这份资源的数据结构是典型的「主表 辅表」交易表记录每笔交易的金额、产品类型、卡片信息、邮箱域名等身份表记录持卡人的设备信息、浏览器版本、屏幕分辨率等。两张表通过一个交易标识关联但身份表并不是每笔交易都有记录存在大量缺失。常见做法是左连接以交易表为主身份表字段缺失就保留 NaN交给后面的树模型自己处理。这里有个容易翻车的点有人图省事用内连接结果样本量直接掉一大截而且掉掉的往往正是信息不全的可疑交易模型学出来的分布就偏了。import pandas as pd # 读入两张表低内存模式避免大文件撑爆内存 train_txn pd.read_csv(train_transaction.csv, low_memoryFalse) train_idn pd.read_csv(train_identity.csv, low_memoryFalse) # 左连接以交易表为主身份信息缺失保留 NaN train train_txn.merge(train_idn, onTransactionID, howleft) print(train.shape) print(train[isFraud].mean()) # 正样本比例通常很低这段代码的关键在howleft和low_memoryFalse。前者保证不丢交易样本后者避免 pandas 分块推断类型时把某些列读成混合类型再报警告。合并后先看正样本比例这个数字决定了后面评估指标怎么选——正样本极低时准确率没有意义得看 AUC 和召回。2.2 时间戳拆解与周期性特征交易时间是一个连续数值直接丢进模型等于让树去硬切分效率低。合格的做法是把它转成「相对起始时间的天数 小时 星期几」这类可解释特征。欺诈行为往往有强时间模式深夜、凌晨、发薪日前后风险分布不一样。我一般会保留原始时间差特征同时派生小时和星期让模型自己选。# 以最小时间戳为基准转成相对天数 train[TransactionAmt] train[TransactionAmt].astype(float) start train[TransactionDT].min() train[day] (train[TransactionDT] - start) / (24 * 60 * 60) train[hour] (train[TransactionDT] // 3600) % 24 train[weekday] (train[TransactionDT] // (24 * 3600)) % 7TransactionDT单位是秒除以 86400 得到天数取模得到小时和星期。这里不要用真实日历时间因为数据本身是脱敏的相对时间硬套日期反而引入噪声。参数上day保留浮点hour和weekday取整后续如果做目标编码要注意按时间切分避免未来信息泄漏。2.3 高基数类别特征的编码取舍卡片品牌、邮箱域名、浏览器版本这些列取值动辄几百上千。直接 one-hot 会炸维度直接标签编码又会让树模型误以为类别间有大小关系。这份 Notebook 里常见的处理是对频次高的类别保留低频的归为「其他」再做标签编码或频次编码。我的经验是频次编码对树模型更友好因为它把「这个域名出现多少次」变成了一个连续风险信号。# 频次编码把类别出现次数作为新特征 for col in [card1, card2, addr1, P_emaildomain]: if col in train.columns: freq train[col].value_counts() train[f{col}_freq] train[col].map(freq)value_counts统计每个取值出现次数map回填。注意频次要在训练集上统计验证集和测试集用同一张映射表否则就是数据泄漏。这一步做完原始类别列可以保留也可以丢弃取决于模型对类别列的原生支持程度。3. 交叉验证与模型基线别让时间泄漏毁掉评估3.1 按时间切分而非随机切分欺诈检测最隐蔽的坑是随机切分。交易数据有时间顺序随机打乱会让「未来」的样本进入训练集评估分数虚高上线就崩。正确做法是按day排序后切分或者用时间序列交叉验证。这份资源里如果用的是随机 KFold我建议直接改成按时间分组的切分方式哪怕分数掉几个点那也是真实水平。from sklearn.model_selection import TimeSeriesSplit # 按时间排序后做时间序列交叉验证 train_sorted train.sort_values(TransactionDT).reset_index(dropTrue) tscv TimeSeriesSplit(n_splits5) for fold, (tr_idx, va_idx) in enumerate(tscv.split(train_sorted)): X_tr, y_tr train_sorted.iloc[tr_idx], train_sorted.iloc[tr_idx][isFraud] X_va, y_va train_sorted.iloc[va_idx], train_sorted.iloc[va_idx][isFraud] # 这里接模型训练TimeSeriesSplit保证训练集永远在验证集之前n_splits5表示切五折。参数上如果数据时间跨度大可以增大折数如果正样本在时间上分布极不均匀要检查每折的正样本比例必要时做分层时间切分。3.2 基线模型选型与评估指标这份 Notebook 通常以梯度提升树作为基线原因很直接结构化表格数据、大量缺失值、类别特征混合树模型对缺失和异常值天然鲁棒不需要精细归一化。评估指标用 AUC 为主辅以召回率在固定误报率下的表现。别只看 AUC业务上更关心「在可接受的误报量下能抓多少欺诈」。from sklearn.metrics import roc_auc_score import lightgbm as lgb # 简单基线只取数值特征缺失交给 LightGBM 处理 feat_cols [c for c in train_sorted.columns if train_sorted[c].dtype ! object and c not in [isFraud, TransactionID]] model lgb.LGBMClassifier(n_estimators500, learning_rate0.05, num_leaves64) model.fit(X_tr[feat_cols], y_tr) pred model.predict_proba(X_va[feat_cols])[:, 1] print(AUC:, roc_auc_score(y_va, pred))num_leaves64控制树复杂度learning_rate0.05配合 500 棵树是常见起点。如果 AUC 上不去先别调参回头检查特征里有没有泄漏列比如某些在交易发生后才能拿到的字段。3.3 缺失值比例与特征筛选身份表合并后会有大量列缺失率超过 90%。这些列不是直接删而是先看缺失本身有没有信号——某些设备信息缺失可能恰恰意味着交易环境异常。我的做法是保留缺失率极高的列但额外加一列「该行缺失字段数量」作为特征让模型自己判断。# 统计每行缺失字段数作为环境异常信号 idn_cols [c for c in train.columns if c.endswith(_idn) or c in train_idn.columns] train[missing_cnt] train[idn_cols].isna().sum(axis1)isna().sum(axis1)按行统计缺失数。这个特征在树模型里往往能排进重要性前列因为它隐式刻画了「这笔交易的信息完整度」。4. 避坑与排查五个让我回炉重跑的血泪记录4.1 现象本地 AUC 0.95换时间切分掉到 0.78原因随机切分导致时间泄漏模型记住了未来样本的分布。解决改用TimeSeriesSplit或按day阈值切分重新评估。别心疼掉下来的分数那才是上线后的真实预期。4.2 现象合并后样本量莫名减少原因误用了内连接或者关联键类型不一致一边是整数一边是字符串导致匹配失败。解决合并前统一TransactionID类型用howleft合并后对比行数是否等于主表行数。4.3 现象频次编码后验证集分数异常高原因频次统计用了全量数据验证集信息泄漏进训练特征。解决频次映射只在训练折上拟合验证折用map应用同一张表未出现的类别填 0 或全局默认值。4.4 现象LightGBM 训练报类别特征类型错误原因类别列里混了数值和字符串pandas 推断成 object但模型要求 category 类型或整数编码。解决显式转换astype(category)或者统一做标签编码后再喂给模型别让 pandas 自己猜。4.5 现象内存爆掉Notebook 跑到一半被杀原因一次性读入全部列且未指定数据类型浮点默认 float64几十万行乘几百列直接吃满内存。解决读入时用dtype参数字典把已知数值列降为 float32类别列设为 category或者分块读取后拼接。5. 从 Notebook 到可复用模块我的收尾习惯把这份资源跑通只是第一步真正省时间的是把它拆成可复用模块。我一般会做三件事第一把特征工程部分抽成独立函数输入原始两张表、输出特征矩阵函数内部固定「先合并、再时间拆解、后频次编码」的顺序避免每次手动改单元格顺序出错第二把时间切分的索引逻辑固化下来训练脚本和评估脚本共用同一套切分杜绝「训练用一套、评估用另一套」的玄学第三留一个最小验证脚本只跑前一万行确认管道不报错再上全量这个习惯帮我省过好几次半夜等结果的时间。def build_features(txn, idn): df txn.merge(idn, onTransactionID, howleft) start df[TransactionDT].min() df[day] (df[TransactionDT] - start) / 86400 df[hour] (df[TransactionDT] // 3600) % 24 for col in [card1, addr1, P_emaildomain]: if col in df.columns: df[f{col}_freq] df[col].map(df[col].value_counts()) return df这个函数刻意不包含模型训练只负责特征方便单独测试。参数上start用当前数据集的最小值如果训练和测试分开处理要保证测试集用训练集的start否则时间基准不一致。验证方法很简单拿一小批数据跑一遍检查输出列数、缺失比例、时间特征范围是否符合预期再上全量。从那以后我每次拿到这类双表结构化数据都强制先跑一遍「合并行数校验 时间切分校验 小样本冒烟」这三步再谈调参。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑