从铜牌到洞察:IEEE-CIS反欺诈竞赛中的特征工程实战复盘

📅 2026/6/30 15:32:53
从铜牌到洞察:IEEE-CIS反欺诈竞赛中的特征工程实战复盘
1. 竞赛背景与数据挑战Kaggle的IEEE-CIS欺诈检测竞赛是一个典型的二分类问题参赛者需要从加密的交易数据中识别潜在的欺诈行为。这个比赛吸引了全球6000多名数据科学家参与竞争异常激烈。我最终获得了Top 9%的成绩虽然不算顶尖但在这个过程中积累的特征工程经验非常宝贵。原始数据包含两个主要部分交易数据(transaction)和身份信息(identity)。交易数据有59万条记录其中欺诈交易约占3.5%。数据维度高达434个特征包括交易金额、产品类型、卡片信息、地址、时间戳等。身份信息数据集则包含设备类型、网络连接信息等辅助特征。最大的挑战在于数据的高度加密和匿名化处理。所有特征都被重命名为V1-V339这样的代号真实含义完全未知。这种情况下传统的领域知识无法直接应用我们必须完全依赖数据本身的统计特性来构建特征。2. 探索性数据分析(EDA)的关键发现在开始特征工程前我花了大量时间进行EDA这是理解数据的基础。有几个重要发现对后续工作产生了深远影响首先通过分析TransactionDT(时间戳)发现数据时间跨度为183天(约6个月)。但有趣的是时间因素对欺诈检测的影响并不像预期那么重要。更关键的是用户行为模式的变化——测试集中有68.2%的用户在训练集中从未出现过这意味着模型需要具备良好的泛化能力。其次通过分析card1、addr1和D1(用户开卡天数)这三个字段的组合可以构建出相对稳定的用户唯一标识(UID)。这个发现成为后续构建聚合特征的基础。但直接使用UID作为特征效果很差因为测试集中有大量新用户。最后通过可视化分析发现欺诈交易和非欺诈交易在TransactionAmt(交易金额)的分布上存在明显差异。欺诈交易的平均金额更高且分布更分散。这个观察促使我对金额字段进行了多种变换和分箱处理。3. 特征工程的核心策略3.1 基础特征编码技术面对加密数据我主要采用了五种特征编码方法频率编码(Frequency Encoding)是最基础也最有效的方法之一。它将分类变量的每个取值替换为该值在数据集中出现的频率。这种方法特别适合高基数分类变量能保留有价值的信息而不导致维度爆炸。def encode_FE(df1, df2, cols): for col in cols: df pd.concat([df1[col], df2[col]]) vc df.value_counts(dropnaTrue, normalizeTrue).to_dict() vc[-1] -1 nm col FE df1[nm] df1[col].map(vc) df1[nm] df1[nm].astype(float32) df2[nm] df2[col].map(vc) df2[nm] df2[nm].astype(float32) print(col)标签编码(Label Encoding)则直接将分类变量转换为数值索引。虽然简单但对于树模型来说效果不错。我特别针对card系列特征使用了这种方法因为它们有明显的层级关系。3.2 高级聚合特征构建基于UID的聚合特征是这次比赛提分的关键。我主要计算了以下几类聚合统计量用户历史交易金额的平均值、标准差用户使用特定产品的频率用户在特定时间段(如最近7天)的交易次数用户常用设备的类型分布def encode_AG(main_columns, uids, aggregations[mean], df_trainX_train, df_testX_test, fillnaTrue, usenaFalse): for main_column in main_columns: for col in uids: for agg_type in aggregations: new_column main_column _ col _ agg_type temp_df pd.concat([df_train[[col, main_column]], df_test[[col, main_column]]]) if usena: temp_df.loc[temp_df[main_column] -1, main_column] np.nan temp_df temp_df.groupby([col])[main_column].agg([agg_type]).reset_index().rename( columns{agg_type: new_column}) temp_df.index list(temp_df[col]) temp_df temp_df[new_column].to_dict() df_train[new_column] df_train[col].map(temp_df).astype(float32) df_test[new_column] df_test[col].map(temp_df).astype(float32) if fillna: df_train[new_column].fillna(-1, inplaceTrue) df_test[new_column].fillna(-1, inplaceTrue) print(new_column)3.3 创造性特征交叉方法特征交叉是挖掘特征间交互作用的有力工具。我尝试了多种交叉方式最简单的数值特征加减乘除比如交易金额与开卡天数的比值。这类特征往往能反映用户行为的相对模式。分类特征的组合如将card类型与addr地区交叉。这种组合能捕捉特定地区特定卡种的异常使用模式。时间序列特征的滑动窗口统计如用户最近3次交易金额的移动平均。这类特征对检测突发性欺诈行为特别有效。def encode_CB(col1, col2, df1X_train, df2X_test): nm col1 _ col2 df1[nm] df1[col1].astype(str) _ df1[col2].astype(str) df2[nm] df2[col1].astype(str) _ df2[col2].astype(str) encode_LE(nm, verboseFalse) print(nm, , , end)4. 模型构建与优化4.1 LightGBM的核心优势在尝试了XGBoost、CatBoost和LightGBM后我最终选择了LightGBM作为主模型。它在处理高维稀疏特征时表现尤为出色GOSS(Gradient-based One-Side Sampling)算法让它能更高效地利用计算资源专注于那些对梯度贡献更大的样本。EFB(Exclusive Feature Bundling)技术则有效降低了特征维度这对我们构建的数百个特征特别重要。直方图算法将连续特征离散化为bin不仅加速了训练过程还带来了一定的正则化效果。4.2 关键参数调优经验经过反复实验我总结出几个对模型性能影响最大的参数num_leaves控制在50-70之间效果最好太大容易过拟合 min_data_in_leaf设置在100-300之间能有效防止过拟合 feature_fraction0.7-0.8的采样比例在保持多样性的同时提升训练速度 learning_rate初始设为0.05配合早停策略params { objective: binary, metric: auc, boosting_type: gbdt, num_leaves: 64, learning_rate: 0.05, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 5, min_data_in_leaf: 200, max_depth: -1, reg_alpha: 0.3, reg_lambda: 0.3, verbose: -1 }4.3 对抗过拟合的策略在比赛中后期过拟合成为最大挑战。我采用了多种应对措施时间序列交叉验证严格按时间划分训练集和验证集模拟真实场景中新用户不断出现的情况。特征重要性筛选定期检查特征重要性删除那些在验证集上表现不稳定的特征。早停策略监控验证集AUC当连续10轮没有提升时停止训练。模型融合最终提交时融合了三个不同随机种子训练的LightGBM模型提升了稳定性。5. 实战经验与避坑指南5.1 特征工程的迭代过程特征工程不是一蹴而就的过程我经历了多次迭代第一轮基础特征处理包括缺失值填充、简单编码和基础统计量 第二轮构建UID相关的聚合特征这是AUC提升最明显的一步 第三轮创造性特征交叉挖掘特征间的交互作用 第四轮特征选择和精简去除冗余和噪声特征每次迭代后我都会在验证集上评估效果确保新特征确实带来提升而非过拟合。5.2 常见陷阱与解决方案内存管理构建大量特征时容易耗尽内存。我养成了及时删除中间变量和主动调用gc.collect()的习惯。数据泄露时间序列数据特别容易出现未来信息泄露。我确保所有聚合特征都严格按时间先后计算。特征稳定性有些特征在训练集和测试集分布差异很大。对抗性验证帮助我识别并移除了这类特征。5.3 效率优化技巧对于大规模特征工程效率至关重要批量处理将相似的特征处理逻辑封装成函数避免重复代码 并行计算使用joblib并行处理独立特征 内存映射对于超大数据使用dask或内存映射技术 缓存机制中间结果保存为feather格式加速后续加载这次比赛让我深刻体会到在数据竞赛中精心设计的特征往往比复杂的模型结构更能带来实质性提升。特别是在数据含义不明确的情况下系统性的特征探索和严谨的验证流程尤为重要。