
我在风控算法岗上待了三年带过不下十个实习生。每次给新人布置的第一道实战题基本都是信用卡欺诈检测。原因很简单这个项目数据量不大笔记本电脑就能跑完整个流程问题定义是清晰的二分类可它背后牵扯的行业认知、数据处理手法、样本不平衡处理、模型评估逻辑每一项都是真实业务建模里的高频考点。做完整一遍等于把“从数据到模型”的完整链路踩熟了。Kaggle上的信用卡欺诈检测主题也常年活跃公开数据集、Notebook、竞赛版本都有动手成本几乎为零。这篇文章就按我自己实际跑这个项目的路径来写从拿到数据、做EDA、处理不平衡、建模评估到在Kaggle上提交结果每一步都讲清楚“为什么这么干”同时也把平台上容易卡住的地方比如注册、自动运行、提交格式这些细节一并说透。适合已经会一点Python和sklearn、想找个完整项目练手的新人也适合准备转数据方向、想拿项目经验充实简历的职场人。1. 选这个项目前先把场景和数据搞清楚1.1 信用卡欺诈检测是一个什么业务问题信用卡欺诈检测本质上是在海量交易里找出“假的”那几笔。银行或者支付平台每时每刻都在处理交易每一笔只有极短的时间窗口可以判断是放行还是拦截。如果漏掉一笔欺诈银行要赔付用户要承受损失如果误拦太多用户体验变差投诉率飙升。所以这不是一个普通的二分类问题而是一个典型的成本敏感问题漏判和误判的成本完全不一样模型的目标不是“分对就行”而是“在可接受的误报范围内尽可能多地抓住欺诈”。理解了这一点你就明白为什么后面那么多处理步骤存在。纯粹追求准确率、追求把多数类正常交易分对在这个场景里不仅没意义还会把模型带偏。1.2 数据集的基本信息与原始样貌Kaggle上最经典的数据集是Credit Card Fraud Detection通常是一个creditcard.csv文件来自2013年欧洲持卡人的真实交易记录。整体情况如下共284807笔交易记录每笔包含31个字段。特征V1到V28是经过PCA变换后的数值特征原始信息为了隐私保护被做了主成分提取所以不要纠结“V1到底代表什么”它已经被脱敏处理过。Time字段表示交易时间距第一笔交易的秒数。Amount字段表示交易金额。Class字段是标签0代表正常交易1代表欺诈交易。文件本身在150MB左右对个人电脑来说不算大在Kaggle Notebook上也能直接读入。我建议你把这份数据当成一个标准的“工业数据样本”来看特征经历过脱敏、标签严重不平衡、样本量在几十万级别这三个特点你在真实公司里也经常碰到只是表现形式不同。1.3 为什么这个项目适合作为练手项目我看过不少新人一上来就选那种动辄几个GB、几十个特征、需要大规模特征工程的比赛结果卡在环境配置和内存优化上连模型长什么样都没见着。信用卡欺诈检测这个项目的优势就是“麻雀虽小、五脏俱全”数据量合适逻辑回归、随机森林、LightGBM都能在几分钟内跑完。不平衡问题非常典型足够你把过采样、欠采样、类别权重这些方法认真对比一遍。评估指标的取舍极其讲究能逼你想清楚业务到底要什么。Kaggle上有大量公开Notebook可以对照卡住了有地方看答案。而且这个题目在面试里出现的频率极高属于标准考纲内容。把这份数据吃透很多风控相关的面试题你都能接得住。2. 数据探索把问题“看”出来2.1 第一步不是建模是查数据质量我见过很多新手拿到CSV就直接train_test_split这是最容易踩坑的地方。先花几分钟做基础检查能避免后面一系列问题。import pandas as pd import numpy as np df pd.read_csv(/kaggle/input/creditcardfraud/creditcard.csv) print(df.shape) # (284807, 31) print(df.info()) print(df.isnull().sum().sum()) # 缺失值总和这个数据集质量其实很好没有缺失值字段类型也干净这算是Kaggle数据集“被处理过”的优势。但你在真实业务里千万别默认数据是干净的。哪怕这里没有缺失我也劝你养成习惯每次拿到数据先看缺失值、看字段类型、看describe()的分布有没有异常值尤其是金额、时间这类数值字段的极值。另一个容易忽略的检查是重复样本。欺诈检测场景中同一张卡在极短时间内重复出现可能本身就是一个风险信号但数据集里的重复记录也可能是采集错误。可以跑一下df.duplicated().sum()看一眼我在这个数据集上跑出来是0说明原始数据已经做过清洗。2.2 类别分布一眼看穿极度不平衡接下来看标签分布print(df[Class].value_counts()) print(df[Class].value_counts(normalizeTrue))正常交易约284315笔欺诈交易只有492笔占比大约是0.172%。也就是说10000笔交易里大概有17笔是欺诈极度不平衡。这个数字意味着什么呢如果你写一个“无脑预测全部为0”的模型准确率能到99.8%以上看起来模型强得要命实际一点用都没有。所以从这一刻开始你就要在心里把“准确率”这三个字划掉换成“召回率、精确率、AUC、混淆矩阵”这些更诚实的指标。这也是这个项目最核心的学习点之一业务目标倒推评估指标评估指标倒推数据处理方式。2.3 从Time和Amount里能挖出什么V1到V28已经是PCA处理后的特征本身能直接喂给模型。但Time和Amount这两列通常是需要额外处理的。Time字段是相对秒数。原始数据是“距离第一笔交易过去的秒数”这个字段直接作为连续值输入模型很难学到有意义的模式。我一般会做两件事一是把秒数转换成小时df[hour] (df[Time] // 3600) % 24二是按天划分因为这份数据覆盖了大约两天可以加一个“第几天”的特征df[day] df[Time] // (24 * 3600)。别小看这种简单的时间加工欺诈交易在深夜时段的比例往往是不同的模型能捕捉到这个规律。Amount字段是交易金额。欺诈交易的金额分布和正常交易差异很大正常交易小额居多但偶尔有极端大额欺诈交易可能会集中在某个区间。因为V1到V28都已经做了标准化Amount的量级明显偏离所以建议单独做标准化或稳健标准化。我习惯用RobustScaler它对异常值更不敏感from sklearn.preprocessing import RobustScaler df[scaled_amount] RobustScaler().fit_transform(df[Amount].values.reshape(-1, 1))3. 样本不平衡的解法和特征工程的几个操作3.1 准确率在欺诈检测里是骗人的先把这件事说透。假设你训练出一个模型准确率99.8%听起来很好但把所有交易都预测成正常也能达到这个数字。在欺诈检测里真正重要的是在所有真实欺诈里模型抓住了多少在被模型拦截的交易里有多少是真的欺诈。用术语说就是召回率和精确率。召回率高了意味着漏网的欺诈少精确率高了意味着被误拦的好交易少。现实情况是这两个指标此消彼长你要根据业务成本去找平衡点。所以在动手建模前先接受一个观念这不是准确率竞赛这是在成本和风险之间做权衡。3.2 过采样、欠采样、类别权重怎么选处理不平衡样本主流方案有三类过采样、欠采样、类别权重。我建议你都试一遍然后横向对比。过采样最常用的是SMOTE。它的原理是在少数类样本之间做线性插值对每个少数类样本找到它的K个近邻然后在样本与近邻的连线上随机生成新样本。我强调一下SMOTE必须在训练集上单独做而且要在划分训练集之后做否则会把合成的样本同时带到训练集和测试集里造成数据泄漏测试结果虚高。这一点极其关键。欠采样则是从多数类里随机抽取一部分让它和少数类数量接近。优点是不会引入伪造数据缺点是会丢掉大量多数类信息模型上限可能受影响。我以前拿这个数据集做过实验纯欠采样训练出来的逻辑回归AUC通常比SMOTE低2到3个百分点而且模型方差更大。类别权重最简单直接在模型里设置class_weightbalanced让模型在计算损失时给少数类更高的权重。它不需要改数据分布也不会增加计算量适合作为第一个试水的方案。scikit-learn里逻辑回归、随机森林等都支持这个参数LightGBM里对应的参数是is_unbalanceTrue。我的实操顺序一般是先跑class_weightbalanced的基线模型再用SMOTE增强最后用边界组合或代价敏感调参来优化。先把基线跑出来后面每一步才有对比的锚点。3.3 特征工程的三个实用操作在特征工程这一步我总结了三个这个项目里最实用的操作。第一标准化和稳健标准化。对于逻辑回归这类线性模型特征的量级直接影响权重的可解释性和收敛速度。Amount要标准化Time如果你不想加工成小时也可以标准化但我更建议把它加工成小时和天再喂给模型。第二交易金额的分组统计。你可以按“小时”分组计算每个小时窗口的平均交易金额、交易笔数、最大金额然后把统计量merge回原数据相当于给模型补充了“当前时间段整体交易是否异常”的上下文。这一步在做类别特征丰富时很常用但要注意只能基于训练数据计算防止泄漏。第三删除或保留Time字段的取舍。经过PCA的V1到V28已经很强了Time有时加进去反而引入噪声。我会做一个小实验分别用“原始Time”“Time在一天中的小时”“删除Time”三组特征跑同一个模型看验证集AUC变化。不要凭感觉直接用结果说话。4. 模型训练与评估把每一步都跑完4.1 从逻辑回归基线开始建模先跑基线是行业惯例。逻辑回归跑得足够快结果稳定而且它给出的是概率方便后续做阈值调整。先划分数据注意用分层采样保住测试集里的少数类比例from sklearn.model_selection import train_test_split X df.drop([Class, Time, Amount], axis1) X[scaled_amount] df[scaled_amount] y df[Class] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 )如果你要用SMOTE就在这一步之后做from imblearn.over_sampling import SMOTE smote SMOTE(random_state42) X_train_res, y_train_res smote.fit_resample(X_train, y_train)然后训练逻辑回归from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score, classification_report, confusion_matrix model LogisticRegression(max_iter1000) model.fit(X_train_res, y_train_res) y_prob model.predict_proba(X_test)[:, 1] y_pred model.predict(X_test) print(roc_auc_score(y_test, y_prob)) print(classification_report(y_test, y_pred))我实测下来SMOTE加逻辑回归的AUC通常在0.96左右这已经是一个可以接受的基线了。不要看不起线性模型它在这个数据集上表现并不差而且可解释性好作为第一步已经达到目的。4.2 换成树模型注意LightGBM的unbalance参数基线性跑通之后再换集成树模型一般能再往上涨一点。XGBoost和LightGBM都是常见选择。LightGBM在这个数据级上训练很快而且自带类别不平衡处理开关import lightgbm as lgb model_lgb lgb.LGBMClassifier( is_unbalanceTrue, n_estimators300, learning_rate0.05, num_leaves31, random_state42 ) model_lgb.fit(X_train, y_train)如果你用了is_unbalanceTrue就不需要手动做SMOTE二者选一个就可以。当然你也可以把SMOTE和树模型结合起来用但要注意树模型本身对不平衡相对不敏感加不加SMOTE的提升幅度通常没有线性模型大反而增加了过拟合风险。对比实验做下来你会发现LightGBM直接开is_unbalance往往最省事、效果最好。我还会多看一个东西特征重要性。LightGBM训练完用model_lgb.feature_importances_导出特征得分重点看V1到V28里哪些特征贡献大这会直接影响后续要不要做特征筛选。很多时候你会发现排序靠前的就是那么五六个特征说明这个数据集的信息高度集中。4.3 评估指标和混淆矩阵比分数重要评估阶段我最常被问到的问题就是“你模型分数多少”其实在欺诈检测里一个分数远不够。要重点看这几个指标AUC-ROC综合评估模型排序能力0.5等于瞎猜1是完美。通常在0.9以上可以接受。PR曲线下的面积Average Precision在极端不平衡数据里PR曲线比ROC更能反映模型对少数类的表现。ROC在多数类很多时容易显得乐观PR曲线则直接以少数类为焦点。混淆矩阵直接看有多少欺诈被漏掉、多少正常交易被误拦。召回率真实欺诈里被抓到的比例。精确率被拦截交易里真正是欺诈的比例。我建议每次训练完把混淆矩阵打出来看一眼cm confusion_matrix(y_test, y_pred) print(cm)比如[[56800, 312], [43, 55]]意思是有43笔真实欺诈漏掉了55笔欺诈被抓住。这个数字比任何单一指标都直观。4.4 阈值调优业务需求决定阈值默认情况下模型把预测概率大于0.5的判为欺诈。但在欺诈检测里这个阈值往往不合适因为欺诈比例才0.17%模型输出的概率普遍偏低0.5的阈值会漏掉很多欺诈。阈值不是固定的它取决于业务愿意承受多少误报。比如银行觉得漏一笔欺诈损失1000元误拦一笔正常交易损失10元用户体验成本那漏报的成本是误报的100倍就应该降低阈值多抓一些。降阈值会提高召回率、降低精确率升阈值则相反。实操里我会画一条阈值调优曲线看不同阈值下的精确率和召回率变化。比如from sklearn.metrics import precision_recall_curve precisions, recalls, thresholds precision_recall_curve(y_test, y_prob)然后遍历阈值找到“召回率不低于某个要求同时精确率尽量高”的那个点。我个人在欺诈检测项目里通常更偏向召回率因为漏掉欺诈的代价更大。但这个偏好必须在业务沟通后确定没有通用答案。5. Kaggle实操从数据集到提交的全流程细节5.1 怎么把数据集拉进Notebook在Kaggle上做项目最舒服的一点是数据和算力都给你准备好了。进入信用卡欺诈检测的竞赛页面或数据集页面创建一个新的Notebook右侧的“Add Data”按钮里能直接检索并挂载数据集。挂载后文件路径就像我前面代码里写的一样在/kaggle/input/creditcardfraud/下面。如果你习惯用API也可以用一行命令下载kaggle competitions download -c credit-card-fraud-detection不过对新手来说Notebook里的可视化添加方式更友好。需要注意Competition和Dataset是两种不同的入口。Dataset适合自己做实验Competition则意味着有官方排行榜和提交机制你要按照主办方要求的格式提交预测结果。5.2 设置Notebook自动运行每天帮你跑模型Kaggle的Notebook是免费的但长时间挂机不行所有会话都会在一定时间后断开。解决这个问题的方式是“Schedule”功能我也看到很多人在问“怎么设置项目自动运行”。逻辑很简单在Notebook编辑界面右侧的“Schedule”栏里新增一个定时任务选择运行频率比如每天一次或每周一次设置好并保存。之后Kaggle会自动启动一个新的会话执行你的代码并把输出结果保存下来。这个功能特别适合两类场景一类是数据集每天更新、你要定时重跑训练和更新预测另一类是训练时间较长、你希望避开白天高峰期运行。另外要注意自动运行同样消耗每月的GPU或TPU配额配置时要看清。5.3 提交预测结果与排行榜机制提交有两种常见方式。第一种是提交Notebook本身Kaggle会在后台重跑你的代码并读取你保存的提交文件第二种是在代码里生成CSV再手动上传到Submission页。新手我建议用第二种更可控。提交文件格式必须和sample_submission一致。信用卡欺诈检测竞赛通常要求两列id和Class。注意这里的Class要么是0/1标签要么是概率具体要看竞赛说明。如果用概率一般文件头保持相同只是数值变成0到1之间的小数。提交后你会看到Public Leaderboard分数但那个只是测试集的一部分。等竞赛结束后系统会用另一部分数据算出Private Leaderboard分数所以Public榜分数高并不代表最终排名高不要沉迷Public榜。5.4 参加Kaggle竞赛对求职的真正帮助关于参加Kaggle竞赛对求职有没有帮助我的看法是可以有但要看你做了什么。拿个Top 10%的排名写在简历上是加分项但面试官更在意的是你怎么做这个项目你的思路、你的取舍、你踩过的坑。比起只挂一个排名我建议你在这份欺诈检测项目的代码之外把整个过程的文档整理出来包括问题定义、EDA结论、不平衡处理对比、为什么选这个模型、最后怎么调阈值。面试的时候能把这个链条讲清楚比一个空洞的奖牌有说服力得多。很多转行的人就是靠这类完整项目展示出自己的数据思维才拿到的第一份数据岗位Offer。6. 常见问题与避坑实录6.1 Notebook内存爆炸怎么办这个数据集虽然只有150MB但如果你做SMOTE、复制DataFrame、再用V1到V28浮点型计算内存也会吃紧。我的经验是先按需读取只加载需要训练的列中间变量及时释放。可以在不使用的变量后面加del df_copy, X_train_tmp再用gc.collect()回收内存。Kaggle Notebook免费版内存不算大养成释放变量的习惯很有必要。6.2 SMOTE之后过拟合SMOTE合成的是插值样本如果测试集来自同样的分布效果确实不错。但你如果对整个数据集做SMOTE再划分训练集和测试集测试集里就会混入根据测试样本合成的数据这叫“测试集污染”会导致验证结果异常乐观。解决方法是先切分、再采样切分时务必stratifyy。另外交叉验证时要在每一折内部再做SMOTE只对训练部分采样验证部分保持原始分布。这个顺序问题我在新手代码里见过太多次属于必须养成肌肉记忆的坑。6.3 提交后评分很低最常见的原因是提交文件里行的顺序和原始测试集顺序不一致。很多人在处理过程中会shuffle但提交时忘了换回去。另一个常见问题是把标签预测成了概率但竞赛要求的是类别或者反过来。提交前我习惯写一行代码核对行数和id顺序submission pd.DataFrame({id: test_ids, Class: y_prob}) assert len(submission) len(test_ids) submission.to_csv(submission.csv, indexFalse)先把格式跑通再追求分数这能避免很多无意义的重跑。6.4 数据泄漏是隐藏杀手这个项目里最典型的数据泄漏来自标准化和SMOTE的时机。RobustScaler要先用训练集拟合再transform测试集不能对整个数据集一次性fit否则测试集的信息已经“看”到了。这一点做特征工程时容易忽略但影响很大。另一个泄漏点是用全量数据做分组统计特征。比如你计算每个小时的平均交易金额如果统计量来自所有数据包括测试集那测试集信息必然渗入了训练过程。正确做法是先对训练集分组计算再map到测试集。这些细节直接影响模型在真实环境里的表现也是面试官最愿意深挖的提问点。我在实际带人的时候每次都强调同一句话模型分数可以慢慢调但数据处理的顺序一旦错了后面所有结果都站不住脚。顺序对了哪怕模型简单一点结果也是可信的。Kaggle比赛比到最后往往不是比谁的模型更花哨而是比谁更细心、过程更严谨。这份信用卡欺诈检测项目的代码我陆陆续续复用到了很多真实的金融风控场景里换成其他数据集时框架基本不用大改只调整特征和阈值就够了这也是我推荐你亲手完完整整做一遍的原因。