从铜牌到洞察: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(dropna=True, normalize=True).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_train=X_train, df_test=X_test, fillna=True, usena=False): 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, inplace=True) df_test[new_column].fillna(-1, inplace=True) print(new_column)

3.3 创造性特征交叉方法

特征交叉是挖掘特征间交互作用的有力工具。我尝试了多种交叉方式:

最简单的数值特征加减乘除,比如交易金额与开卡天数的比值。这类特征往往能反映用户行为的相对模式。

分类特征的组合,如将card类型与addr地区交叉。这种组合能捕捉特定地区特定卡种的异常使用模式。

时间序列特征的滑动窗口统计,如用户最近3次交易金额的移动平均。这类特征对检测突发性欺诈行为特别有效。

def encode_CB(col1, col2, df1=X_train, df2=X_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, verbose=False) 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_fraction:0.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格式,加速后续加载

这次比赛让我深刻体会到,在数据竞赛中,精心设计的特征往往比复杂的模型结构更能带来实质性提升。特别是在数据含义不明确的情况下,系统性的特征探索和严谨的验证流程尤为重要。