信用卡欺诈检测实战:从匿名数据到LightGBM风控模型
发布时间:2026/9/25 2:27:49来源:尧图网络
简介针对IEEE-CIS欺诈检测赛题这份资源以JupyterNotebook为中心配套多个Python工具脚本构成一套完整的探索性数据分析EDA方案。赛题本质是二分类任务——判断用户是否点击欺诈资源从数据读取、缺失值与类别分布分析、可视化报告到特征工程均给出可复用的处理流程适合Kaggle新手、金融风控方向学生及相关从业者参照学习。包体精简实用共6个文件zip压缩包仅1.02MB含EDA.ipynb深度分析笔记本、数据清洗工具、可视化报告脚本和特征工程整理脚本另附README说明目录结构清晰便于按模块拆解复用。目前已有597人学习。作者还分享了Kaggle内核演示如何简化EDA流程学习者可据此获得竞赛数据处理思路、常用清洗与特征构造代码以及一套可扩展的探索分析框架对独立上手该类反欺诈赛题富有参考价值。1. 为什么 IEEE-CIS-Fraud-Detection 值得认真做脱敏数据里的一条明线拿到 IEEE-CIS-Fraud-Detection 这个赛题的人第一反应通常是看数据量训练集 59 万行测试集 50 万行特征 434 列不算大。但真正把它当成一个 Fraud-Detection 落地项目来做时问题就来了——大部分特征被匿名化只剩 V1 到 V339 这种毫无业务含义的编号少数保留原义的列还错漏百出。这套数据不是让你上来就调模型的它逼你先回答一个问题在看不到字段含义的前提下怎么判断哪些特征值得信任、哪些特征只会把模型带偏。这个赛题能解决的问题也很直接帮你建立一套适用于真实风控场景的表格数据建模流程。信用卡交易欺诈检测里常见的高基数类别、时间戳陷阱、脱敏特征筛选、时间序列下的验证切分在这份数据里全都能遇到。适合的人群是已经在用 XGBoost 或 LightGBM 做过分类、但没系统处理过匿名特征和交易时间序列的工程师也适合想评估梯度提升树在反欺诈场景里到底还有多大空间的技术团队。下面这些内容会从数据结构拆起一路走到特征工程、模型参数和验证策略最终交付一个可以照做的方案。2. 数据解剖TransactionDT、cardX、V 列分别是什么决定了建模策略2.1 交易时间戳先判断单位再决定要不要做时间特征TransactionDT 是这份数据里最容易被误用的字段。它的取值大概在 7.8e8 到 9.5e8 之间有人第一眼会当成常规 Unix 秒级时间戳直接转成 datetime。但要注意这个数字对应的时间区间在 2017 年底到 2018 年底附近用常见时间戳转换工具解出来的年份是准的可一旦你把单位当成毫秒去处理所有时间窗口计算都会放大一千倍聚合特征直接报废。判断单位的方法是固定一个锚点日期用 Excel 或 Python 粗算跨度。训练集里 TransactionDT 的最小值约为 780000000最大值约为 908000000差值约 1.28 亿。这个跨度按秒算是约 1480 天按小时算是约 3.5 万小时都不符合一个赛题数据集该有的时间长度按分钟算是约 889 天接近两三年其实已经偏长按小时数再除 24 也不是常见区间。我一般直接用秒做单位、以 2017-12-01 为锚点偏移得到的时间范围约 2017-12 到 2018-12这个尺度对交易数据是合理的。代码层面时间特征这样起手import pandas as pd import numpy as np df pd.read_csv(train_transaction.csv) # 锚点取 2017-12-01时间戳单位为秒偏移得到真实时间轴 anchor pd.Timestamp(2017-12-01) df[dt] anchor pd.to_timedelta(df[TransactionDT], units) # 基础时间特征先只取能稳定复现的部分 df[hour] df[dt].dt.hour df[dayofweek] df[dt].dt.dayofweek df[dayofyear] df[dt].dt.dayofyear # 观察欺诈率随时间的变化判断是否要做分段特征 daily_fraud df.groupby(df[dt].dt.date)[isFraud].mean() print(daily_fraud.head(20))这段代码锚点了时间轴生成了小时、星期、年内天数三个基础特征。做欺诈检测时小时和星期通常有用因为夜间时段的异常交易率往往偏高dayofyear 则用于捕捉季节性但如果测试集的时间窗口和训练集不连续这类特征可能过拟合。需要注意TransactionDT实际起点选择不同hour的前后分布会有整体偏移但小时之间的相对关系不变。如果你观察到欺诈率在某个小时突然陡升先回去确认锚点是否合理再决定是否保留。2.2 card 系列是身份指纹不能当普通数值用card1 到 card15 这组字段代表卡片的各类属性包括发卡行编号、卡类别、卡号段、到期年份、账单邮编等但赛题不会告诉你每一列具体是什么。它们的共同特点是高基数、偏名义型、存在大量相同值重复出现。把 card1 直接喂给梯度提升树虽然树模型能自动切分但切分结果利用的是数值大小顺序比如 card1 取 116 和 117 会被视为相邻区间而类别型特征的取值之间本来没有距离概念。更安全的方式是保留原始值但对高基数列做类别编码以后再入模。这里推荐两种处理一类是 label encoding 加 count encoding 混合另一类是把 card 列当成类别型走 LightGBM 的 categorical_feature。两种在本地验证差异不大但 count encoding 更容易解释。# 对 card 系列做 count 编码保留出现频率信息 for col in [card1, card2, card3, card5, card6]: freq df[col].map(df[col].value_counts()) df[f{col}_count] freq # 号码段类字段建议单独看头几位 # card4 是卡组织card6 是卡类别它们取值少直接保留原始值 print(df[card1].nunique(), df[card2].nunique(), df[card6].nunique())card 系列里card1的基数超过 10000直接 label encoding 会引入噪声count 编码则更稳定因为模型学到的是“这张卡出现的次数”。而card4、card6这样的低基数枚举字段保留原始数值让树自行切分即可。注意count 编码要在训练集上计算频率后统一映射到验证集和测试集避免使用全量数据造成标签泄漏。2.3 V 列394 列匿名特征是大多数人的第一道门槛V 列从 V1 到 V339加上部分 V 开头的衍生列总共有 300 多列。这些特征是脱敏后的统计量可能包含交易金额的分箱、设备指纹的编码、卡号段的哈希等。它们最大的特点是稀疏某些列 90% 以上的值是 NaN在 pandas 里读进来全变成缺失值。很多人的第一个做法是统一填充 0这其实不算错因为稀疏列里的非空值往往才是异常信号。比如某张卡的设备指纹只在极少数交易里出现那它本身就是可用的欺诈信号。对这类列我一般分成三步处理# 统计 V 列缺失率按阈值过滤 v_cols [c for c in df.columns if c.startswith(V)] miss_rate df[v_cols].isnull().mean() # 保留缺失率低于 80% 的 V 列其余的暂时搁置 keep_v miss_rate[miss_rate 0.8].index.tolist() print(保留 V 列数量:, len(keep_v)) # 对保留列做均值填充并记录是否缺失 for col in keep_v: df[col _na] df[col].isnull().astype(int) df[col] df[col].fillna(df[col].median())先用缺失率筛掉噪声列再对保留列填充并生成缺失标记。缺失标记本身在风控场景里非常重要因为一个字段缺失往往意味着信息不足而信息不足往往和欺诈相关。V 列里部分列如 V1、V2、V3缺失率很低属于可以直接使用的核心特征像 V112、V125 这种缺失率超过 95% 的列即使入模也只是给模型增加噪声。2.4 用 LightGBM 原生接口快速检验特征有效性数据结构了解完以后先不要着急做复杂特征而是用 LightGBM 跑一个基线看哪些列初步有效。这一步能快速暴露问题比如某些 V 列的 feature importance 极高你就要怀疑它是不是间接泄漏了标签比如 card 系列的编码方式不对导致 importance 异常。import lightgbm as lgb from sklearn.model_selection import train_test_split feature_cols [TransactionAmt, hour, dayofweek] keep_v[:60] X df[feature_cols] y df[isFraud] # 按时间切分验证集避免随机切分导致未来信息泄漏 split_time df[TransactionDT].quantile(0.8) train_idx df[TransactionDT] split_time valid_idx df[TransactionDT] split_time d_train lgb.Dataset(X[train_idx], y[train_idx]) d_valid lgb.Dataset(X[valid_idx], y[valid_idx], referenced_train) params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 63, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, verbose: -1, } model lgb.train( params, d_train, num_boost_round300, valid_sets[d_valid], callbacks[lgb.early_stopping(50), lgb.log_evaluation(50)], )这里的关键是用时间分位点做切分而不是随机切分。随机切分在时间序列数据上会让验证集包含训练集未来的样本导致本地 AUC 虚高而按时间切分得到的结果才更接近真实线上环境。LightGBM 输出 feature importance 之后观察 V 列和 card 列的重要性排名如果某些匿名列明显异常高说明填充或编码方式有误需要回去检查这就是一个能直接指导后续特征工程的方向。3. 特征工程从原始表到可训练特征的具体做法3.1 交易金额与距离方向一致的强特征TransactionAmt 是少数保留完整语义的字段直接对应交易金额。欺诈交易在金额上最常见的模式是集中在某个区间而不是整体偏大或偏小所以单独把金额作为连续特征还不够。需要加入它和用户历史交易金额的比值、以及和整百整五十的接近程度这两类偏差往往比金额本身更能捕捉异常。addr1、addr2、addr3 是买卖双方地址相关的脱敏编码dist1、dist2 是交易距离特征。它们的主要价值在于和 TransactionAmt 组合同一个人短时间内跨地理距离消费大量金额就是很强的风险信号。# 金额除以用户历史均值得到相对消费水平 user_amount_mean df.groupby(card1)[TransactionAmt].transform(mean) df[amt_user_ratio] df[TransactionAmt] / user_amount_mean # 是否接近整百金额模拟人工测试小额交易的模式 df[amt_round100] (df[TransactionAmt] % 100 0).astype(int) # 距离字段缺失时用缺失标记代替很有价值 df[dist1_na] df[dist1].isnull().astype(int) df[dist2_na] df[dist2].isnull().astype(int)金额比值特征对欺诈检测的提升通常很明显因为欺诈者洗钱或测试卡号时消费金额会显著偏离该卡主的历史消费习惯。整百标记捕捉的是人工测试交易的特征正常消费者偶尔也会出现整百金额但频率远低于自动化程序。需要注意groupby在构造特征时属正常操作但如果参与聚合的列包含未来信息比如整个数据集的统计值而不是历史累计值会造成隐性泄漏。3.2 用户历史聚合特征频率、均值、中位数这一节是特征工程里提升最大的部分。对交易数据用户的刷卡习惯通常用滑动窗口聚合表示比如过去 1 小时、24 小时、7 天的交易次数和平均金额。由于赛题没有给出明确的用户 ID实际做法是用 card1 或 card1addr1 的组合作为用户近似标识。聚合特征的写法需要注意窗口的计算方式。如果直接用全量数据做 groupby transform得到的是整个训练集上的统计量会包含该用户未来的交易信息。正确做法是保持特征工程在训练和测试阶段的时序一致性默认用全量 transform 时需要意识到它统计的是整体统计量而非历史回看量这对树模型的影响通常可接受但严谨做法是用累积窗口。# 按 card1addr1 近似用户维度构造交易频率 df[card_addr] df[card1].astype(str) _ df[addr1].astype(str) df[freq_total] df.groupby(card_addr)[TransactionID].transform(count) # 过去一小时的交易量用时间差判断 # 先按用户和交易时间排序 df df.sort_values([card_addr, TransactionDT]).reset_index(dropTrue) df[prev_time] df.groupby(card_addr)[TransactionDT].shift(1) df[time_gap] df[TransactionDT] - df[prev_time] # 时间间隔短说明短时间内连续交易是风险信号 df[short_gap] (df[time_gap] 3600).astype(int) print(df[[card_addr, TransactionDT, time_gap, short_gap]].head(10))这里用shift(1)构造了同一用户相邻两笔交易的时间间隔时间差小于 3600 秒的标记为 1。短时间间隔连续交易是信用卡欺诈检测里的经典强特征尤其是多笔小额交易在很短时间内由同一张卡发起的情况。同时 frequency 特征能有效区分正常高频用户和突然开始活跃的可疑卡。3.3 ID 类与类别特征高基数用 count 编码而不是 label 编码数据里还有 id_01 到 id_38 这一组身份信息类特征它们表示设备、IP、邮箱等身份标识的脱敏编码。这类字段的基数差异极大有的列取值几十个有的列取值几十万。对超高基数列如果直接用 label encodingLightGBM 会优先在出现次数最多的取值上反复切分模型对稀有取值几乎不学习。count 编码是这类高基数列的标准处理方式。它把每个取值替换成其出现次数本质上是把稀有取值映射到低频率区间让树模型可以基于频率做切分同时避免 label encoding 带来的虚假顺序。# 对 id 列做联合频率编码 id_cols [id_01, id_02, id_03, id_12, id_13] for col in id_cols: df[f{col}_count] df[col].map(df[col].value_counts()) # 对取值数较少的 ID 列做标签编码并保留原始列 for col in [id_04, id_05, id_06, id_07, id_08]: df[col] pd.factorize(df[col])[0]这里把 count 编码放在循环里执行训练集和测试集要分别用合并后的全量数据进行频率统计否则测试集中出现频率不同的新取值时count 值会失真。对于取值数少于 100 的列label 编码问题不大因为基数低时树模型不会产生明显的顺序偏差基数超过 1000 的列则优先 count 编码。3.4 特征筛选用 Sparsity 阈值和空值率把 V 列压到 50 个以内V 列有 300 多个如果全部保留模型训练时间增加、过拟合风险上升而且很多列之间高度相关。特征筛选在风控场景里比在其他表格任务里更重要因为匿名特征之间的相关性无法用业务直觉判断只能靠统计方法。常见做法是双重过滤先按缺失率过滤掉大部分稀疏列再按与标签的相关性保留一小部分。V 列里缺失率低于 80% 的大概在 100 列左右再按 AUC 单变量筛选能压到 50 列以内。from sklearn.metrics import roc_auc_score # 单变量 AUC 筛选快速评估每个 V 列的区分度 v_scores [] for col in keep_v: if df[col].nunique() 1: continue auc roc_auc_score(y, df[col].fillna(df[col].median())) v_scores.append((col, auc)) v_scores.sort(keylambda x: x[1], reverseTrue) # 保留单变量 AUC 高于 0.55 的前 50 个 V 列 top_v [c for c, s in v_scores[:50] if s 0.55] print(筛选后 V 列数量:, len(top_v)) print(top_v[:20])用单变量 AUC 筛选特征时AUC 高于 0.6 的列要特别谨慎。正常情况下脱敏特征与标签的单变量 AUC 很难超过 0.65如果出现异常高的列大概率存在两种可能一种是该特征恰好是标签生成过程中的直接产物另一种是数据预处理阶段无意引入的信息泄漏。在 IEEE-CIS 这个赛题里确实有少数 V 列与欺诈标签的相关性异常高这属于数据本身的特性但如果不加控制地全量使用验证集表现会远好于真实场景。特征筛选完成后特征集合基本稳定在 80 到 120 列。记住特征数量不是越多越好多加 50 个弱相关特征只会让模型过拟合训练集对线上泛化没有帮助。4. 模型选型与参数LightGBM 和 XGBoost 的取舍4.1 为什么梯度提升树仍然优于神经网络系这个赛题的阶段性结论几乎一致在大量匿名特征和高基数类别特征面前梯度提升树的表现在绝大多数情况下优于神经网络。原因是树模型天然处理缺失值、对特征尺度不敏感、能自动捕获非线性交互而神经网络在表格数据上需要精细的嵌入层设计和大量调参收益却不一定明显。虽然 TabTransformer 这类的深度模型在 Kaggle 上有成功案例但从工程落地角度看提供稳定 AUC、能解释特征重要性、训练速度快的仍然是 LightGBM 和 XGBoost。这里不是否认深度模型而是强调投入产出比。对风控团队来说模型上线后需要频繁重训和特征排查树模型的可解释性让这个过程更容易而神经网络的黑匣子特性会让“为什么这个交易被判欺诈”变得很难回答。4.2 LightGBM 核心参数与训练脚本LightGBM 在 IEEE-CIS 这类数据上的最优参数区间是比较稳定的。learning_rate设在 0.01 到 0.05 之间num_leaves不宜超过 128min_data_in_leaf通常要调大一些防止过拟合。需要重点调整的参数是feature_fraction和bagging_fraction它们控制特征采样和行采样比例在匿名特征场景下能显著降低过拟合。params { objective: binary, metric: auc, boosting: gbdt, learning_rate: 0.02, num_leaves: 64, max_depth: -1, min_data_in_leaf: 300, feature_fraction: 0.6, bagging_fraction: 0.7, bagging_freq: 1, lambda_l1: 0.5, lambda_l2: 1.0, cat_smooth: 10, # 降低高基数类别特征过拟合 verbose: -1, } d_train lgb.Dataset(X_train, y_train) d_valid lgb.Dataset(X_valid, y_valid, referenced_train) model lgb.train( params, d_train, num_boost_round2000, valid_sets[d_valid], callbacks[lgb.early_stopping(100), lgb.log_evaluation(100)], )关键参数说明min_data_in_leaf设到 300 是这类数据的最佳区间过小容易让叶子节点学到单一 ID 对应单一标签的模式feature_fraction设为 0.6 而不是默认的 1.0因为在 100 多列特征里有大量弱相关匿名列列采样是天然的防过拟合机制cat_smooth对 card 系列这类高基数类别特征有平滑作用能防止某些只在训练集出现少数几次的取值主导切分。训练完成后务必保存模型并用验证集计算 AUC。如果训练集 AUC 超过 0.95 而验证集只有 0.75说明过拟合应该增大min_data_in_leaf或减小num_leaves。如果验证集 AUC 低于 0.85先回去检查特征工程而不是继续调参。4.3 XGBoost 参数差异与适用场景XGBoost 和 LightGBM 在这个任务上的差异主要体现在速度和细节处理上。XGBoost 的max_depth控制树深度默认 6 在这个任务上偏浅它对类别特征没有原生支持需要先做编码。XGBoost 的优势是在数据量不大时训练更稳定、对噪声特征的容忍度更好。IEEE-CIS 这份数据 60 万行XGBoost 训练一轮的时间是 LightGBM 的两到三倍但换来的是更平滑的验证集分数。import xgboost as xgb dtrain xgb.DMatrix(X_train, labely_train) dvalid xgb.DMatrix(X_valid, labely_valid) xgb_params { objective: binary:logistic, eval_metric: auc, eta: 0.02, max_depth: 7, min_child_weight: 100, subsample: 0.8, colsample_bytree: 0.6, lambda: 2.0, alpha: 0.5, } bst xgb.train( xgb_params, dtrain, num_boost_round2000, evals[(dvalid, valid)], early_stopping_rounds100, verbose_eval100, )XGBoost 里的min_child_weight对应 LightGBM 的min_data_in_leaf同样需要调大。这个任务中设为 100 或更高能明显抑制过拟合。colsample_bytree对应feature_fraction0.6 是比较安全的起点。XGBoost 在处理缺失值时自动学习最优方向这对 V 列的大量 NaN 是友好的如果你已经做了填充两种模型的行为差异不大。4.4 集成两个模型的分数怎么合并实际落地时我更常用的做法是 LightGBM 和 XGBoost 各自训练然后在预测概率上做简单平均或加权平均。这种集成方式收益稳定调参成本低。但要注意两个模型的验证集 AUC 如果差异较大加权时要给高 AUC 的模型更高权重而不是盲目五五开。# 两个模型分别预测验证集 lgb_pred model.predict(X_valid) xgb_pred bst.predict(xgb.DMatrix(X_valid)) # 线性加权融合权重按验证集 AUC 比例确定 valid_auc_lgb roc_auc_score(y_valid, lgb_pred) valid_auc_xgb roc_auc_score(y_valid, xgb_pred) w_lgb valid_auc_lgb / (valid_auc_lgb valid_auc_xgb) w_xgb 1 - w_lgb final_pred w_lgb * lgb_pred w_xgb * xgb_pred print(融合后 AUC:, roc_auc_score(y_valid, final_pred))加权融合的核心是权重来自验证集而非训练集避免过拟合。如果融合后的 AUC 比两个单独模型都低说明模型之间的预测相关性过高做平均的意义不大这时候应该回到特征层面找差异而不是继续调权重。5. 避坑指南IEEE-CIS 赛道上的五个典型翻车点5.1 TransactionDT 的单位判断错误时间戳单位都错了时间特征全废现象有人把 TransactionDT 当作毫秒时间戳直接用 pd.to_datetime 转换结果得到的时间全部落在 1970 年左右交易的小时分布完全错位所有时间窗口特征变成随机噪声。原因TransactionDT 不是标准 Unix 时间戳它的起点被平移过且数值跨度对应的是秒级偏移。没有先验证时间范围和业务可解释性就直接转格式是典型的惯性操作。解决先取一条交易记录用锚点加偏移秒数的方式生成时间轴再验证时间跨度是否与交易数据合理。做法是先打印 min 和 max算出差值再配合已知的时间范围推断单位。这一步放在所有特征工程之前。5.2 card 系列全按数值编码高基数类别被数值顺序骗了现象card1 到 card15 直接进模型后LightGBM 的 feature importance 里 card1 排名第一但验证集 AUC 反而低于不使用 card 特征的基线。原因card1 是类别型 ID取值之间没有顺序关系。树模型在数值上切分时会利用“值大小”关系比如把 card 值小于 1000 的划为一组这种划分没有业务含义只是碰巧拟合了训练集。解决对高基数的 card 列使用 count 编码或转为 category 类型交给 LightGBM 的 categorical_feature 处理。低基数的 card 枚举字段保留原始值即可。card 列里如果出现取值和欺诈率几乎完全对应的字段要警惕它是标签泄漏还是业务无关巧合先做单变量分析再决定是否使用。5.3 时间特征用错了信息源从交易时间推出生日与注册时长现象有人根据 TransactionDT 和 card 系列里的有效期年份构造出“持卡人年龄”“卡片已使用时长”这类特征本地 AUC 提升显著但换一份数据或做时间切分验证后特征失效。原因card 系列中的年份字段并非真实生日或注册时间而是脱敏后的一个分段标识。强行从脱敏编码中推导时间语义只是找到了一个训练集内的统计巧合不具备泛化能力。解决对匿名特征只做统计变换不做业务语义推断。card 列之间的差值、比值这类衍生特征同样要谨慎先确认它们在验证集上的稳定性。检查方法是把样本按时间前后分成两半分别计算该特征与标签的相关系数若差异过大则放弃它。5.4 模型看到未来不按时间切分验证本地分数虚高现象随机切分验证集时 AUC 在 0.95 左右换成按时间切分后降到 0.87。线上表现大概率接近后者。原因交易数据具有时间相关性同一张卡或同一个设备的交易在时间上聚集。随机切分会把同一用户的交易同时分到训练集和验证集模型相当于见过这个用户的交易模式后去预测它的下一笔属于间接泄漏。解决验证集必须按时间排序后取最后 20% 或 30% 的样本模拟线上“用历史预测未来”的场景。如果使用了滑窗聚合特征还要确保训练阶段的聚合统计不包含验证集时间区间内的数据。这也是为什么特征工程阶段就要明确时间逻辑而不是等建模时才想起来。5.5 V 列里的稀疏值当成普通缺失统一填充现象对 V 列全部填充 0 后模型收敛很快但 AUC 波动剧烈换一个随机种子结果差异明显。原因V 列中大量 NaN 本身携带有意义的信息它表示这个特征在该样本上不可用而不可用状态往往与欺诈相关。统一填充 0 会抹掉这种信号同时让填充值 0 在树模型中成为一个聚集大量样本的节点导致对某些 V 列的切分失效。解决填充方式改为“填充一个远离正常分布的常数 生成缺失标记列”。缺失标记列单独作为特征入模让模型自己学习“缺失”是否对应高风险。填充值用中位数而不是 0能减少分布偏移对树模型的影响。6. 最后一道工序验证策略与落地检查6.1 用时间滑窗做交叉验证不要迷信单次切分单次时间切分只能得到一个分数而样本时间段不同模型表现可能有波动。更稳妥的做法是滑窗用前 60% 训练、后 20% 验证然后逐步扩展训练集再做第二次验证。这样能看到分数是不是稳定而不是某一段特定时间碰巧好。滑窗验证还能暴露特征时间稳定性的问题。如果第一次验证 AUC 是 0.88第二次只有 0.84说明某些特征只在特定时间段内有效需要重新筛选或剔除。训练阶段花在这个验证上的时间远小于模型上线后因为分数虚高而返工的时间。6.2 从验证策略反推特征上限验证切分时间点决定了哪些特征可用。比如你用时间滑窗验证训练集覆盖到 2018 年中验证集覆盖 2018 年下半年那么训练集的用户历史聚合特征不能使用验证集时间段内的数据。这时要么接受全量统计带来的轻微泄漏要么把聚合计算严格限定在滑窗内。实际项目里我通常采用后者虽然代码复杂度增加但模型结果更可信。一个自查清单是最终模型使用的每个特征在训练和预测时的可用信息是否一致。如果一个特征在验证阶段能拿到未来信息那它在真实上线时必然失效因为线上预测时没有未来数据可用。特征工程的目的不是做出单点分数最高的模型而是做出上线后依然可靠的模型。6.3 我的习惯把验证集留到最后再碰我最后说一个自己的习惯验证集不要从头到尾反复使用。每改一次特征就去看验证集分数会导致你在验证集上隐性地过拟合。常见做法是保留一份完全隔离的 holdout 数据只在所有特征和参数确定后评估一次日常开发中用时间切分的小验证集即可。这样最终那份 holdout 分数才接近真实水平。IEEE-CIS-Fraud-Detection 这套流程走下来你会发现最大的收获不是某个高分而是你终于能说清楚“为什么这个特征有效、为什么这个模型会翻车”。希望这份从数据结构到验证策略的完整过程能帮到你照着做一遍风控类特征的建模思路基本就顺了。本文还有配套的精品资源点击获取
网站建设高端定制企业官网