ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

社会保险反欺诈实战:Python特征工程与LightGBM建模全流程解析

社会保险反欺诈实战:Python特征工程与LightGBM建模全流程解析 简介基于人工智能的社会保险反欺诈分析项目以Python源码与数据集下载链接整合为zip压缩包面向计算机相关专业正在做毕业设计、课程设计或期末大作业的学生也适合需要完整实战练习的初学者。项目将训练集与测试集统一放入data目录标签1表示欺诈用户、0表示正常用户覆盖特征衍生、特征分析、特征选择、特征变换、模型构建与参数调优的完整流程代码均已调试运行通过评审得分96.5分。压缩包共12个文件包括5个Jupyter Notebook分模块实现、2个说明文档、Python脚本、数据集及项目配置文件数据采用tsv与csv格式组织整体约15.21MB目录结构清晰便于按模块对照学习。已有74人学习下载内容还附带初赛训练集与测试集的下载指引适合快速复现实验并迁移到同类保险风控任务中。1. 社会保险反欺诈把“谁能报销”变成一道二分类题社会保险反欺诈这几年在风控圈子里算一个典型的人工智能落地场景一边是报销数据量越来越大靠人工抽检根本看不过来另一边是骗保手段越来越隐蔽单看某一笔报销都是“正常”的只有把时间、金额、就诊频次拼在一起才能看出异常。不少高校的课程设计和毕业设计也盯上了这个题目——既有真实感又有明确的标签1 代表欺诈用户0 代表正常用户很适合用来练完整的建模流程。这份资源就是一套能直接跑通的 Python 源码加数据集覆盖特征衍生、特征筛选、特征变换、建模和参数调优全流程代码以 Jupyter Notebook 为主配一个总的 BuildModel.py。它的定位很明确适合正在做毕设、课程设计或者想练项目实战的同学尤其适合想在“人工智能大作业”里拿出一个完整反欺诈方案的人。我拿到这份资源后的第一感觉是——它不是那种堆了一堆代码却跑不起来的“花瓶项目”而是按分工协作的方式组织的每个成员负责一个模块文件之间通过中间结果交互最后汇总到一个建模脚本里。这种组织方式本身就很像真实的风控项目开发节奏。接下来我从数据开始一步步拆解这套流程里值得抄作业的部分以及那些容易翻车的细节。2. 数据与标签先搞清楚 train.tsv、train_id.tsv 和 test_A.tsv 的分工2.1 文件清单与五个 Notebook 的协作关系解压这份资源之后你第一眼会看到一大堆文件名可能有点乱。我帮你把它们的协作关系理清楚。核心工作流是这样的原始比赛数据是 train.tsv 和 test_A.tsv但这两个文件在最初解压时并不在 data 目录里——README 里明确写了“下载解压后将 train.tsv 和 test_A.tsv 放入 data 根目录”。这其实是一个很常见的资源组织方式为了避免 zip 包体积过大原始数据单独下载源码里只放中间处理结果。中间结果有三个train_derive.csv特征衍生后的数据、train_selection.csv特征筛选后的数据、train_transform.csv特征变换后的数据。这对应了三个 Notebook 的输出。另外还有 train_id.tsv这个是用户 ID 的辅助文件后面我会专门讲它的坑。最终预测结果输出到 predict_A.csv模型评估结果写到 res.txt。五个 Notebook 按顺序执行0 号做数据分析1 号做特征衍生2 号做特征筛选3 号做特征变换4 号做参数调优。每个 Notebook 的文件名后缀还带了作者拼音说明这是多人协作的课程项目。我一般会建议你按文件名开头的数字顺序执行而不是按提交时间顺序。因为每个文件读取的是上一个文件的输出跳着跑很容易因为缺中间文件而报错。另外 BuildModel.py 是最终的总脚本它把前面所有中间结果串起来做一次完整的训练和预测。2.2 用 pandas 读入数据分隔符和编码是第一个坑TSV 文件用\t做分隔符这一点看起来不起眼但很多第一次跑这个项目的人会习惯性地用read_csv默认的逗号分隔结果读进来只有一列当场蒙圈。正确的读法是这样的import pandas as pd train pd.read_csv(data/train.tsv, sep\t, encodingutf-8) test pd.read_csv(data/test_A.tsv, sep\t, encodingutf-8) train_id pd.read_csv(data/train_id.tsv, sep\t, encodingutf-8) print(train.shape, test.shape, train_id.shape) print(train.head())这里的sep\t指定了制表符分隔encodingutf-8处理中文字段编码。如果你的环境是 Windows 且文件里有中文可能需要改成encodinggbk或者encodingutf-8-sig这个要看文件实际保存的编码格式。读进来之后先打印 shape 和 head确认列名和行数是否合理这比直接开始跑特征工程要稳妥得多。2.3 标签分布与时间窗口建模前必须看的两张图原始数据里标签列一般叫 label 或者 fraud_flag其中 1 代表欺诈用户0 代表正常用户。我拿到数据后的第一个习惯是看类别分布因为反欺诈场景几乎一定是类别不平衡的。你可以用 value_counts 快速查看import matplotlib.pyplot as plt label_col label vc train[label_col].value_counts() print(vc) print(欺诈占比: {:.4f}.format(vc[1] / vc.sum())) train[label_col].value_counts().plot(kindbar) plt.title(Label Distribution) plt.show()这一步非常关键。如果欺诈样本占比低于 5%后面建模时就要考虑类别权重、过采样或者调整阈值否则模型会偷懒——全部预测成 0 也能拿到很高的准确率但没有任何业务价值。这份资源的建模脚本里大概率处理了样本不均衡问题后面我会讲到具体怎么处理。除了标签分布时间窗口也要看一眼。社会保险报销数据通常有明确的时间字段比如报销日期、就诊日期。你要确认训练集和测试集的时间范围是否有重叠如果有重叠说明这是随机划分而不是按时间划分如果测试集在时间上整体晚于训练集那建模时要小心时间泄漏。这个判断直接影响你对模型泛化能力的信任程度。3. 特征工程三步走从原始字段到能喂给模型的数值特征3.1 特征衍生把单笔报销聚合成“人的行为画像”原始数据大概率是一行一条报销记录比如某用户在某个时间报销了某类药品的金额。这种明细数据不能直接用来训练模型因为模型需要的是“每个用户一行”的样本。1_feature_derive 这个 Notebook 的核心工作就是把明细数据按用户 ID 聚合衍生出一批统计特征。常见的做法是分组聚合例如# 按用户聚合生成金额和频次特征 g train.groupby(user_id).agg( total_amount(amount, sum), avg_amount(amount, mean), max_amount(amount, max), min_amount(amount, min), std_amount(amount, std), claim_count(claim_id, count), unique_hospital(hospital_id, nunique), ) g g.reset_index() g[avg_claim_interval_days] g[total_days] / g[claim_count]这里我写的是一个典型聚合模板实际项目中如果要处理的是报销明细还可以衍生单月最大报销次数、报销金额的极差、就诊医院数量、药品类别数、报销时间集中在白天还是夜间等。核心思路是把原始明细的“交易视角”转换成“用户视角”每个用户变成一组统计特征。groupby().agg()是这一步的主力操作注意agg里可以传多个聚合函数每个结果列需要起一个有意义的名字方便后面做特征筛选时看懂。3.2 特征筛选用随机森林重要性砍掉一半特征特征衍生之后特征数量可能膨胀到几十甚至上百个。这里有个常见误区特征不是越多越好尤其是类别型字段做 one-hot 之后会产生大量稀疏特征不仅拖慢训练速度还容易过拟合。2_feature_selection 这个 Notebook 做的就是特征筛选。一个简单但有效的方法是训练一个随机森林看特征重要性from sklearn.ensemble import RandomForestClassifier feature_cols [c for c in train_derive.columns if c not in [user_id, label]] X train_derive[feature_cols] y train_derive[label] rf RandomForestClassifier( n_estimators200, max_depth8, min_samples_leaf20, n_jobs-1, random_state42, ) rf.fit(X, y) imp pd.Series(rf.feature_importances_, indexfeature_cols).sort_values(ascendingFalse) print(imp.head(30)) # 保留累计重要性前 80% 的特征 cumsum imp.cumsum() / imp.sum() keep_cols imp[cumsum 0.8].index.tolist() print(保留特征数:, len(keep_cols))这段代码的逻辑是先用一个轻量级随机森林计算特征重要性再按累计重要性保留前 80% 的特征。min_samples_leaf20是为了防止树过深导致特征重要性失真。n_jobs-1让所有 CPU 核心并行训练。这里我有个习惯不要直接用imp 0来筛特征因为随机森林的重要性分布是长尾的很多弱特征虽然重要性不为 0但对模型只有噪声贡献。累计重要性的方式更稳健。3.3 特征变换分位数变换比标准化更稳特征筛选之后3_feature_transform 对保留特征做变换。为什么需要变换因为树模型对单调变换不敏感但线性模型和距离类模型对特征的尺度很敏感。如果你后续要跑 LR 或者把特征喂给神经网络的 embedding 层标准化或分位数变换是必要的。如果只用树模型比如 LightGBM这个步骤可以省略。但这份资源既然单独做了一个 Notebook 来做变换说明设计者希望保留“兼容非树模型”的能力。分位数变换是处理偏态分布的好选择尤其是报销金额这种长尾分布from sklearn.preprocessing import QuantileTransformer qt QuantileTransformer( n_quantiles1000, output_distributionnormal, random_state42, ) X_trans qt.fit_transform(X[keep_cols]) X_trans pd.DataFrame(X_trans, columnskeep_cols)n_quantiles1000表示把原始分布切成 1000 个分位点然后映射到正态分布上。output_distributionnormal让输出尽量接近标准正态。这个变换的优点是对极端值不敏感能把偏态分布“拉直”。但要注意fit_transform必须在训练集上做fit然后在测试集上只做transform也就是把训练集的 quantiles 映射应用到测试集上否则会造成数据泄漏。代码里应该是做了这个处理的你自己复现时一定要检查这一步。4. 建模与评测从 Baseline 到 BuildModel.py 的完整闭环4.1 模型选型为什么这份资源适合用 LightGBM反欺诈场景的特征通常是高基数类别型变量医院 ID、药品编码、地区编码加数值型统计量这种混合特征矩阵最适合梯度提升树模型。XGBoost 和 LightGBM 是主流选择。这份资源的建模脚本用的是 LightGBM我猜选择理由无非是三个训练速度快、自带类别型特征支持、调参空间大。对于课程设计来说LightGBM 还有一个额外的好处——它的特征重要性输出非常友好可以直接写到论文里当分析图表。如果你要跑 Baseline一个标准的 LightGBM 二分类脚本是这个样子from lightgbm import LGBMClassifier from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score X train_selection.drop(columns[user_id, label]) y train_selection[label] lgb LGBMClassifier( n_estimators500, learning_rate0.05, num_leaves31, max_depth-1, subsample0.8, colsample_bytree0.8, min_child_samples50, random_state42, ) skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) auc_scores [] for fold, (train_idx, valid_idx) in enumerate(skf.split(X, y)): X_tr, X_va X.iloc[train_idx], X.iloc[valid_idx] y_tr, y_va y.iloc[train_idx], y.iloc[valid_idx] lgb.fit( X_tr, y_tr, eval_set[(X_va, y_va)], eval_metricauc, callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)], ) auc roc_auc_score(y_va, lgb.predict_proba(X_va)[:, 1]) auc_scores.append(auc) print(fFold {fold 1} AUC: {auc:.4f}) print(Mean AUC: {:.4f}.format(sum(auc_scores) / len(auc_scores)))我用StratifiedKFold做 5 折交叉验证保证每一折的欺诈样本占比和全量一致。early_stopping(50)的意思是验证集 AUC 连续 50 轮不提升就停止训练防止过拟合。subsample0.8和colsample_bytree0.8分别控制行采样和列采样比例这俩参数是 LightGBM 抗过拟合的关键。4.2 BuildModel.py 的结构训练 预测 输出一次跑通BuildModel.py 是这份资源的集成脚本。它做三件事读取 train_transform.csv 和 test_A.tsv 的对应特征用全量训练数据重新训练模型对测试集输出 predict_A.csv。注意这里有个细节交叉验证时我们只用部分数据训练比如 4/5但最终模型要用全量数据重新训练因为训练数据越多模型的泛化能力通常越好。BuildModel.py 大致是这样的结构import pandas as pd from lightgbm import LGBMClassifier train pd.read_csv(data/train_transform.csv) test pd.read_csv(data/test_A_transformed.csv) feature_cols [c for c in train.columns if c not in [user_id, label]] X train[feature_cols] y train[label] final_lgb LGBMClassifier( n_estimators800, learning_rate0.02, num_leaves31, subsample0.8, colsample_bytree0.7, min_child_samples50, random_state42, ) final_lgb.fit(X, y) test[pred] final_lgb.predict_proba(test[feature_cols])[:, 1] test[[user_id, pred]].to_csv(data/predict_A.csv, indexFalse)注意这里n_estimators从交叉验证时的 500 提高到了 800配合learning_rate0.02的更低学习率。交叉验证阶段我们靠 early stopping 确定的是当前参数下最优的迭代轮数最终训练时可以用两倍左右的轮数 更小学习率效果通常更好。test_A_transformed.csv我假设是已经处理好的测试集特征原资源里可能没有这个中间文件你需要自己把 1、2、3 号 Notebook 的处理逻辑对测试集跑一遍。4.3 评测指标的选择AUC 比准确率更诚实反欺诈场景里准确率是一个“骗人”的指标。如果欺诈比例只有 3%模型全部预测为正常用户准确率也有 97%但这个模型毫无价值。AUCROC 曲线下面积是更合理的指标它衡量的是模型把正样本排到负样本前面的概率不依赖阈值选择。res.txt 里记录的应该就是 AUC。如果你的课程设计需要写报告除了 AUC还可以补充 PR 曲线和 F1-score因为反欺诈场景里召回率比精确率更重要——漏掉一个欺诈用户可能意味着几十万的损失。我还建议你记录每一折的 AUC 方差。如果 5 折 AUC 分别是 0.85、0.84、0.83、0.82、0.86说明模型比较稳定如果出现 0.90 和 0.75 的剧烈波动说明特征对某些子集的区分度不稳定可能需要检查是否存在异常用户或者特征泄漏。5. 避坑记录训练和验证时最容易翻车的五个细节5.1 train_id.tsv 和 train.tsv 的 ID 对不上现象训练模型时发现 train.tsv 里的用户 ID 数量和 train_id.tsv 里的不一致合并之后行数暴增或减少。原因train_id.tsv 很可能是一个辅助文件包含额外的用户维度信息比如年龄、参保年限、地区每个用户一行而 train.tsv 是报销明细一个用户可能有多行。直接 merge 会得到笛卡尔积。解决先确认 train_id.tsv 是不是 one-row-per-user再决定用howleft还是先聚合 train.tsv 再合并。我一般会在合并前打印两份数据的唯一 ID 数量确保一对多关系清楚后再操作。5.2 类别不平衡导致模型“全预测为 0”现象训练完模型后测试集预测结果全是 0或者 AUC 在 0.5 左右徘徊。原因LightGBM 默认对负样本拟合更充分。数据中欺诈样本占比可能只有 2%5%模型发现全预测为 0 的损失已经很小。解决给LGBMClassifier传scale_pos_weight正负样本比值的倒数或者用is_unbalanceTrue让模型自动调整权重。另外在交叉验证时关注验证集的召回率而不仅是 AUC。如果权重调整后召回率显著提升、精确率小幅下降这是正常的风控场景里召回优先。5.3 特征变换的 fit 混用了全量数据现象训练集和测试集分别做fit_transform导致测试集的分布被“自己归一化”验证集 AUC 虚高。原因QuantileTransformer或StandardScaler的fit只能用在训练集上测试集必须用训练集拟合好的参数做transform。很多人在 pipeline 里图省事对测试集重新 fit这就是典型的数据泄漏。解决严格区分训练集和测试集的处理流程。在 Notebook 里1 号衍生和 2 号筛选可以合在一起处理全量数据因为特征筛选不涉及标签之外的分布统计但 3 号变换必须分步操作先在训练集上fit再分别transform训练集和测试集。你也可以用sklearn.pipeline.Pipeline把变换和模型串起来配合cross_validate避免这个问题。5.4 Notebook 顺序执行时中间文件路径写错现象执行 2_feature_selection 时报FileNotFoundError: train_derive.csv不存在。原因Notebook 之间的协作依赖中间 CSV 文件如果每个人在自己的环境里跑data 目录下的文件可能被覆盖或缺失。比如你在不同机器间同步代码时只同步了 .ipynb 文件漏了 data 目录里的中间产物。解决拿到资源后先按顺序完整跑一遍 0 到 4 号 Notebook确保所有中间文件生成完好。之后再做任何改动之前把原始的 train_derive.csv、train_selection.csv、train_transform.csv 备份一份这样就算改坏了也能随时恢复。我的做法是把中间文件固定在某个目录并在 Notebook 开头用相对路径统一引用不写绝对路径。5.5 测试集预测结果的提交格式不对现象提交 predict_A.csv 后评审系统报格式错误或提示 ID 缺失。原因预测文件列名和要求的提交格式不一致比如要求user_id和probability但输出的是id和pred。也可能是 index 没有重置导致行数不一致。解决提交之前用代码校验格式sub pd.read_csv(data/predict_A.csv) assert user_id in sub.columns, 缺少 user_id 列 assert pred in sub.columns, 缺少 pred 列 assert sub[user_id].is_unique, user_id 有重复 print(提交格式校验通过)is_unique检查是防止你在合并特征时产生重复行这是很隐蔽的坑。提交数据行数应该和 test_A.tsv 的用户数完全一致不一致就说明处理测试集时出了问题。6. 复现后的验证技巧从 res.txt 阈值到一份可解释的风险名单模型跑通只是第一步如果你要拿这份资源做课程设计答辩或者真的想把它变成一个可用的反欺诈工具你需要学会把预测概率翻译成业务上能看懂的风险名单。这一章我会讲两个核心技巧怎么调阈值怎么给风险名单做解释。先看阈值。res.txt 里记录的是验证集上不同阈值的评估结果。AUC 高不代表你一定能找到合适的判定边界因为在反欺诈场景里你往往不能简单取 0.5 作为阈值——欺诈样本太少模型输出的概率分布会整体偏低。你需要根据业务容忍度来选阈值。我的做法是画出 Precision-Recall 曲线寻找召回率保持在 70% 以上的前提下的最高精确率阈值。这里给一段参考代码from sklearn.metrics import precision_recall_curve precision, recall, thresholds precision_recall_curve(y_va, y_prob) best_threshold None best_score 0 for p, r, t in zip(precision, recall, thresholds): if r 0.7 and p r best_score: best_score p r best_threshold t print(selected threshold:, best_threshold) risk_users va_df[va_df[prob] best_threshold]逻辑很简单遍历每个候选阈值找出“召回率不低于 0.7 时精确率和召回率之和最大”的阈值。这个阈值就是你对测试集做最终预测时要用的分界线。precision_recall_curve返回的 thresholds 是每次预测概率变化时的切分点所以它天然覆盖了你所有可能的判定边界。选定阈值之后第二个关键动作是生成可解释的风险名单。单一模型的预测结果是黑匣子老师和评审大概率会问“你为什么判定这个人欺诈”。你需要在模型之外补充一份特征解释这是答辩的加分项。我一般会做两件事第一对预测概率最高的前 50 个用户逐个输出它们的 top 3 异常特征第二按风险等级高、中、低分组做简单统计。import numpy as np top_risk risk_users.nlargest(50, prob) feature_cols [c for c in train_select.columns if c ! label] explain_cols [] for _, row in top_risk.iterrows(): vals row[feature_cols] abnormal vals - mean_values[feature_cols] 3 * std_values[feature_cols] explain_cols.append(;.join(vals[abnormal].index[:3])) top_risk[异常特征] explain_cols这个思路是把每个高风险用户的特征值和全量均值做对比找出偏离超过 3 个标准差的特征。比如某个用户“单月报销次数”是平均值的 8 倍、“就诊医院数”是 1所有报销都发生在同一家医院这两个特征组合起来就很有解释力频次高、渠道单一——这是典型的异常报销模式。我把这种方法跑在自己项目上后发现它对答辩的帮助特别大评委不再追问“你的模型可信吗”而是开始讨论“这些异常特征是不是符合真实案情”。很早以前我跑这类数据跑完模型就直接把概率结果交出去了结果评审一句话就把我问住了“你能解释为什么这个人是高风险吗”从那以后我每一次做反欺诈建模都强制自己走一遍阈值筛选 风险名单解释的流程。事实证明这比把模型 AUC 调高 0.01 有用得多——模型是工具决策才是目的。希望这份资源的完整复现过程能帮你把课程设计做成一个真正说得通、讲得出的项目也祝你在答辩时能自信地展示你的反欺诈分析方案。本文还有配套的精品资源点击获取
返回列表