简介一项基于人工智能的社会保险反欺诈分析完整实战项目面向计算机相关专业毕设学生及需要项目练习的学习者可作课程设计或期末大作业源码与数据下载链接一并打包。项目采用ipynb多模块协作开发覆盖特征构造、特征选择、特征变换、参数调优与建模全流程标签1代表欺诈用户、0代表正常用户data目录含训练集、测试集及多份衍生处理文件train.tsv与test_A.tsv下载解压后放入data根目录即可复现。资源共12个文件以5个ipynb源码为主辅以md说明、py脚本、tsv/csv数据集与xml工程配置整体15.21MB目录结构清晰适合分模块研读。目前已有75人学习下载所有代码均测试运行成功答辩评审平均96.5分模块间以文件交互体现多人协作路径配合文档可掌握从数据探索到模型调优的完整思路运行有疑问可联系作者远程教学适合需要高分模板或快速跑通反欺诈分析流程的学习者。1. 开头社会保险反欺诈项目源码能跑通比什么都重要人工智能正从尝鲜工具变成日常帮手但对学生来说最实际的形态还是一套能跑通的完整 Python 源码。这套“基于人工智能的社会保险反欺诈分析”就是这样的东西大三课程作业经导师指导后高分通过评审分 96.5任务也很具体——根据参保人的领取记录判断是否存在欺诈行为标签里 1 代表欺诈用户、0 代表正常用户。整个项目用 ipynb 文件开发多个人各负责一个模块通过文件交接数据、代码、中间产物全在压缩包里。适合正在做毕设的学生、需要期末大作业或项目实战练习的人。下载解压后把 test_A.tsv 和 train.tsv 放进 data 根目录跟着 README.MD 的顺序跑一遍就能看到从原始数据到预测结果的全流程。2. 先摸清数据再动手TSV 读取、目录结构与样本画像拿到别人的项目第一件事不是跑代码而是看数据长什么样。这套资源的 data 目录下放了七份文件train.tsv、train_id.tsv、test_A.tsv、train_derive.csv、train_selection.csv、train_transform.csv、predict_A.csv。从命名顺序就能看出项目的数据流tsv 是原始数据derive 是衍生特征selection 是特征选择后的结果transform 是变换后的建模输入predict_A 是预测输出。2.1 目录结构先分清谁是谁我拆这类项目时习惯先画一张数据流图不过这里直接用表格说明更直观。文件阶段作用train.tsv原始训练集包含标签1 为欺诈、0 为正常test_A.tsv原始测试集需要预测欺诈概率train_id.tsv辅助带用户 ID 的训练数据方便做身份维度特征train_derive.csv中间产物特征衍生后的结果由 1 号脚本产出train_selection.csv中间产物特征选择裁剪后的结果train_transform.csv建模输入变换完成、可直接训练的特征矩阵predict_A.csv输出对测试集的预测结果含概率或标签从文件命名和分工看这个项目是标准的多人协作流水线每个人负责一个 ipynb产出物作为下一个人的输入。这种组织方式优点是模块边界清楚缺点是衔接处特别容易出问题后面避坑章节会细说。data 目录里还有 .idea 和 vcs.xml说明原作者是在 PyCharm 里开发的版本控制也开着这算是个隐含提示——按 Git 思路管理整个实验过程。2.2 读取 TSV 与标签分布第一段能跑的代码tsv 本质是 tab 分隔的表格pandas 直接读就行。常见做法是这样import pandas as pd train pd.read_csv(data/train.tsv, sep\t) test pd.read_csv(data/test_A.tsv, sep\t) print(train.shape, test.shape) print(train[label].value_counts()) print(train[label].value_counts(normalizeTrue))读取逻辑不复杂但有两个细节值得说。sep\t 是必须的默认的逗号分隔符在这种文件上只会读出一列。value_counts(normalizeTrue) 会把数量转成占比直接看出正负样本是否平衡。社保反欺诈这种场景欺诈用户通常是极少数如果占比低于 5%后面的建模就要考虑类别不平衡处理而不是拿到数据直接训练。如果 train.tsv 里没有 label 列那标签可能在 train_id.tsv 里需要合并train_id pd.read_csv(data/train_id.tsv, sep\t) train train.merge(train_id[[id, label]], onid, howleft)这里用 howleft 保留训练集的全部样本避免 merge 时把行数弄丢。做完这步再跑一次 describe 看数值分布重点看有没有异常值——比如领取金额出现负数、领药次数出现几千次这种明显不合理的记录。2.3 数据质量检查缺失、异常与欺诈特征初探原始数据一般不会太干净。我检查这类数据时会看三样东西缺失率、重复行、单值列。missing train.isnull().mean().sort_values(ascendingFalse) print(missing[missing 0.1]) duplicated_rows train.duplicated().sum() print(重复行数:, duplicated_rows) low_cardinality [col for col in train.columns if train[col].nunique() 1] print(单值列:, low_cardinality)缺失率超过 10% 的列要决定是删还是填重复行在用户维度数据里经常出现比如同一次就诊被录了两遍单值列对整个模型没有任何区分度直接删掉。这三个检查做完才算是能放心进入特征工程。3. 特征工程四连击衍生、分析、选择、变换的完整链路特征工程是这套项目最厚实的部分对应了四个独立 ipynb1_feature_derive、0_feature_analysis、2_feature_selection、3_feature_transform。从文件名看分析脚本编号是 0说明原作者是先写特征分析、再写衍生脚本这是迭代开发的正常痕迹——先摸清数据规律再针对性地造特征。3.1 特征衍生把“领药记录”变成“行为画像”原始数据里如果一行是一次领取记录那建模时不能直接把每一行送给模型要按用户聚合。衍生特征的核心思路是把这个人一段时间内的行为压缩成统计量。grouped train.groupby(user_id) derive_df pd.DataFrame({ claim_count: grouped[claim_amount].count(), claim_sum: grouped[claim_amount].sum(), claim_mean: grouped[claim_amount].mean(), claim_std: grouped[claim_amount].std(), claim_max: grouped[claim_amount].max(), claim_min: grouped[claim_amount].min(), claim_range: grouped[claim_amount].max() - grouped[claim_amount].min(), unique_hospital: grouped[hospital_id].nunique(), }) derive_df derive_df.reset_index()注意这里 count 和 sum 的区别count 是领取次数sum 是金额合计。社保欺诈常见特征就是短时间内高频次领取、金额波动大、或者频繁跨机构开药。claim_std 和 claim_range 抓的就是波动性unique_hospital 抓的是分散度——正常参保人一般固定在一两家医院拿药欺诈者往往频繁换机构。衍生完之后做一次验证性检查分组统计不同 label 下这些新特征的均值差异。如果欺诈组的 claim_std 明显高于正常组说明这个特征有效如果两组几乎一样那就删掉别让它进模型添乱。3.2 特征分析与选择相关性矩阵、IV 值与特征重要性特征分析阶段主要回答一个问题这么多特征哪些真的有用常用的有两类手段相关性矩阵和 IV 值。corr derive_df.corr() high_corr_pairs (corr.abs() 0.8) (corr.abs() 1.0) print(corr[high_corr_pairs].stack())相关性大于 0.8 的特征对一般留一个就够两个都进模型只会增加共线性对树模型影响不大但对逻辑回归这类线性模型影响明显。另一个更针对分类问题的指标是 IVInformation Value用来衡量特征对二分类目标的区分度import numpy as np def calc_iv(df, feature, targetlabel): df df[[feature, target]].dropna() total_good (df[target] 0).sum() total_bad (df[target] 1).sum() iv 0 for cut in np.percentile(df[feature], [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]): pass # 简化实现按十分位分箱后计算 WOE 和 IV df[bin] pd.qcut(df[feature], 10, duplicatesdrop) grouped df.groupby(bin)[target].agg([sum, count]) grouped[bad_rate] grouped[sum] / grouped[count] grouped[good_rate] 1 - grouped[bad_rate] grouped[woe] np.log(grouped[bad_rate] / grouped[good_rate].replace(0, 1e-6)) grouped[iv_part] (grouped[bad_rate] - grouped[good_rate]) * grouped[woe] return grouped[iv_part].sum()IV 值的经验判断标准小于 0.02 基本无用0.02 到 0.1 区分度弱0.1 到 0.3 中等大于 0.3 很强。注意我这里简化了分箱逻辑实际项目里可以用 qcut 分位分箱对异常值更鲁棒。WOE 计算时要防止分母为 0replace 和 1e-6 就是干这个的。选特征还有一个更省事的路子直接用树模型的重要性排序。用 LightGBM 或随机森林训练一次取 feature_importances_ 排序保留累计贡献前 80% 的特征。这个思路在这个项目的 2_feature_selection 脚本里很常见因为树模型自带非线性交互能力重要性排序在工程上比 IV 计算更快。3.3 特征变换log1p、分箱与标准化特征变换这一环最容易被新手跳过但它直接影响模型的收敛和效果。社保数据里的金额字段往往严重右偏直接用会放大极值影响log1p 是处理长尾分布最常用的手段。import numpy as np for col in [claim_sum, claim_mean, claim_max]: train_transform[col _log] np.log1p(train_derive[col])np.log1p 等价于 log(x 1)好处是 x 为 0 的时候结果还是 0不会出现负无穷。除了 log 变换分箱也是一种有效手段把连续值按分位数切成区间转成类别特征。比如把年龄按 [0, 30, 45, 60, 100] 切成五段能缓解年龄与欺诈概率之间的非线性关系。标准化这一步要注意时机如果后面用的是 XGBoost、LightGBM 这类树模型标准化可有可无如果用到逻辑回归或神经网络必须做。标准做法是from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)这里有个原理性问题必须强调scaler 只能用训练集 fit测试集只 transform。很多人在这一步踩坑把两个数据集合在一起 fit导致测试集信息泄漏到训练过程里。3.4 按文件交互的工作流每个 ipynb 怎么衔接这个项目把流程拆成多个 ipynb每个脚本读上一个脚本的输出 csv再写出自己的结果。这么做的好处是单步可回溯——特征选择改个参数不用重跑特征衍生。衔接的关键是文件命名和列名约定比如 1 号脚本必须输出 train_derive.csv且里面要保留 user_id 和 label 两列后面的脚本才能 merge 回去。实操上我建议跑完一个脚本就检查一次输出列的完整性别等最后一个脚本报错才回头找。原作者用 .idea/vcs.xml 开了版本控制说明每个脚本的产出都有记录可查这也是多人协作该有的习惯。4. 建模与调参BuildModel.py 跑通全流程XGBoost 参数怎么设特征工程做完数据已经变成 train_transform.csv 可以直接建模了。项目里负责建模的是 BuildModel.py这是一个独立脚本而不是 ipynb说明建模步骤比较稳定、适合脚本化执行。训练完成后输出 res.txt 到 output 目录保存验证集表现或预测结果。4.1 BuildModel.py 的主流程拆解打开这种脚本结构基本上逃不出五个步骤加载数据、划分验证集、定义模型、训练、评估输出。常见组织方式如下import pandas as pd import xgboost as xgb from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score df pd.read_csv(data/train_transform.csv) X df.drop(columns[user_id, label]) y df[label] X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) model xgb.XGBClassifier( n_estimators300, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, scale_pos_weight(y_train 0).sum() / (y_train 1).sum(), eval_metricauc ) model.fit(X_train, y_train) val_pred model.predict_proba(X_val)[:, 1] print(AUC:, roc_auc_score(y_val, val_pred))几个参数值得展开。stratifyy 保证切分后训练集和验证集的欺诈比例一致类别不平衡时这步不做切出来的验证集可能全是正常用户评估就失真了。scale_pos_weight 是正负样本比例的倒数用在这里相当于把少数类的错误惩罚放大是处理不平衡最直接的手段。最终输出用 predict_proba 的概率而不是 predict 的硬标签因为后面调阈值时还需要原始概率。脚本末尾通常会把验证集的 AUC 和特征重要性写进 res.txtwith open(output/res.txt, w, encodingutf-8) as f: f.write(fval_auc: {roc_auc_score(y_val, val_pred):.4f}\n) importance pd.Series(model.feature_importances_, indexX.columns) f.write(importance.sort_values(ascendingFalse).to_string())res.txt 在整个项目里承担“可读日志”的角色把每次实验的关键指标固化下来比在终端里看输出更持久也更方便团队里其他人查看。4.2 参数网格长什么样XGBoost 与 LightGBM 的实战参数对结构化表格数据XGBoost 和 LightGBM 基本是默认主力。社保反欺诈这类任务我一般优先试 LightGBM训练速度快类别不平衡处理也更友好。下面是常用的参数网格参数取值范围调参方向说明n_estimators100 ~ 1000配合早停使用不是越大越好learning_rate0.01 ~ 0.1学习率小需要的树多训练慢但稳max_depth3 ~ 7深度太深容易过拟合特别是小数据集num_leaves15 ~ 63LightGBM 专用控制树的复杂度subsample0.6 ~ 0.9行采样降低方差colsample_bytree0.6 ~ 0.9列采样增强泛化min_child_weight1 ~ 10值越大越保守防过拟合scale_pos_weight类别比倒数~倒数×3直接调控少数类权重调参有一个玄学点要提醒学习率和 n_estimators 必须配对调。把 learning_rate 从 0.1 降到 0.03n_estimators 得翻倍才能达到相近效果。只看单参微调、不看组合很容易掉进局部最优。4.3 从调参到出结果参数调优脚本怎么用项目里的 4_parameter_tune_lihongwang.ipynb 是负责参数调优的模块。常见做法是先用粗网格跑一遍锁定最有潜力的参数区间再细化网格。我用 GridSearchCV 时习惯先固定 scale_pos_weight调完结构参数再回来调它——因为结构参数和样本权重会互相影响混在一起调会让搜索空间爆炸。from sklearn.model_selection import GridSearchCV import lightgbm as lgb param_grid { num_leaves: [15, 31, 63], max_depth: [3, 5, 7], learning_rate: [0.03, 0.05, 0.1], } model lgb.LGBMClassifier( n_estimators300, subsample0.8, colsample_bytree0.8, scale_pos_weight(y_train 0).sum() / (y_train 1).sum(), verbose-1 ) grid GridSearchCV(model, param_grid, scoringroc_auc, cv5, n_jobs-1) grid.fit(X_train, y_train) print(grid.best_params_)scoring 用 roc_auc 而不是 accuracy原因很简单欺诈样本占比本来就低模型全预测 0 也能拿到 95% 以上的 accuracy但一个欺诈用户都抓不出来这个模型在生产里毫无价值。cv5 是五折交叉验证比单次划分的评估更稳定。n_jobs-1 让所有 CPU 核心一起跑省时间。调参脚本跑完把最优参数回填到 BuildModel.py重新跑一遍整个流程对比 res.txt 里的 AUC 变化确认提升不是随机波动。5. 避坑记录多人协作开发里最容易翻车的五个环节这套项目是多人各写一个 ipynb 再接力的模式文件交互带来一个问题每个环节单独跑都对一连起来就挂。下面五条都是这种协作模式里的典型踩坑记录读一遍比自己撞一遍省时间。5.1 列名对不上KeyError 翻车现场现象2 号脚本读 train_derive.csv报 KeyError提示找不到某个列名。原因1 号脚本输出时列名是 claim_sum2 号脚本读的时候写成了 claim_total。各写各的命名约定只口头沟通没有落到文件里。这是多人协作最高频的翻车点。解决约定每个脚本输出 csv 的第一行必须是列名清单读数据的脚本开头加一行断言校验。写 assert set(required_cols).issubset(df.columns)缺列直接报错别等训练到一半才发现。5.2 训练测试一起预处理信息泄漏的隐形坑现象验证集和测试集上 AUC 很高但提交预测结果后线下评测分数明显偏低。原因标准化或填补缺失值时把 train 和 test 拼在一起 fitscaler 看到了测试集的统计信息相当于测试集的分布信息被偷看。这类泄漏不会报错但会让所有评估结果虚高。解决所有预处理都遵循“训练集 fit测试集仅 transform”的原则。代码里写成 scaler.fit(X_train) 后再 X_test scaler.transform(X_test)独立测试集在建模完成前不参与任何统计量计算。5.3 路径写死与中文编码换台机器就崩现象原作者机器上跑得好好的换台电脑重建环境后脚本报 FileNotFoundError 或 UnicodeDecodeError。原因源码里用了绝对路径比如 C:/Users/xxx/data/train.tsv换机器后路径失效另外数据文件里有中文字段名读入时默认编码不是 UTF-8 就乱码。解决统一改成相对路径脚本和数据放在同一个父目录下用 os.path.join(data, train.tsv) 拼路径。读文件时指定 encodingutf-8读不进去就试 gbk。我在项目根目录先跑一遍所有脚本能过才继续往下走。5.4 类别不平衡AUC 挺高但召回率是 0现象模型报告 AUC 0.85看起来不错打印分类报告才发现欺诈类的召回率是 0一个欺诈用户都没抓到。原因没做任何不平衡处理模型把所有样本都判成多数类。AUC 对不平衡不敏感0.85 的 AUC 可能对应着极差的少数类召回——典型的“平均分很高但偏科”状况。解决加 scale_pos_weight 或 class_weight把少数类的错误惩罚权重加大评估时同时看 AUC、召回率、F1-score单独看 AUC 会骗人。我习惯把 validation 集的欺诈样本单独拎出来数一遍看模型到底捞回来几个。5.5 res.txt 格式不统一后续解析全乱现象BuildModel.py 输出的 res.txt 没有固定格式有人写 val_auc0.85有人写 AUC 0.85 换行后期做实验对比时根本没法自动汇总。原因没有人定义日志格式每个人按自己习惯写。看起来是小问题攒到十几个实验记录后就成灾难了。解决约定统一格式每行一个键值对比如 metric: auc value: 0.8345。写完用脚本批量解析所有 res.txt对比各版本实验的提升幅度。血泪经验日志格式这种小事定义得越早后面越省心。6. 验证阶段的一个关键技巧不要用 0.5 硬切概率阈值模型训练完、AUC 达标项目看起来收工了。但如果直接拿 predict 输出的硬标签去交作业大概率会在答辩被追问“为什么用 0.5 切”。这里有一个所有分类项目都该做、但很多人没做的步骤看预测概率的分布再选阈值。6.1 用概率分布决定切点import pandas as pd import numpy as np pred pd.read_csv(data/predict_A.csv) proba_col fraud_prob # 看概率分布按 0.1 间隔统计 pred[bucket] pd.cut(pred[proba_col], binsnp.arange(0, 1.1, 0.1)) print(pred.groupby(bucket, observedTrue).size())正常情况下的输出应该类似绝大多数样本概率集中在 0.1 以下中间是长尾不是均匀分布。如果所有样本概率都堆在 0.4 到 0.6 之间说明模型区分度不行这时候调阈值意义不大问题出在特征或模型本身。分布长尾明显时把阈值从 0.5 降到 0.3能多捞出一批高概率欺诈用户代价是多一些误报。社保反欺诈这类场景误报一个正常人顶多是复核漏报一个欺诈者就是资金损失阈值往低走是常见做法。6.2 卡召回率还是卡坏账率提前想清楚医保和社保场景里上线前真正要回答的问题是这批预测结果里欺诈概率最高的 Top 5% 用户值不值得人工复核。这时候要画的是查全率-查准率曲线from sklearn.metrics import precision_recall_curve precision, recall, thresholds precision_recall_curve(y_val, val_pred) df_pr pd.DataFrame({ threshold: list(thresholds) [1], precision: precision, recall: recall }) print(df_pr[df_pr[recall] 0.8].head(10))查全率在 0.8 附近对应的最低阈值就是切入点保证抓回八成欺诈用户的前提下找到误报率最低的切点。这个数字比 AUC 更贴近业务答辩时拿出来说比空谈“准确率 95%”有说服力得多。调完阈值再回头和 BuildModel.py 里固定输出的测试概率对比一下确认没有偏差整套流程才算闭环。这套项目里最大的坑不是算法而是多脚本交接和数据泄漏。从那以后我每次做分类项目都强制自己走一遍“概率分布 阈值曲线 召回率”三件套而不是只看 AUC 就收工。这套流程对任何表格类二分类问题都能复用希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?