
简介基于人工智能的社会保险反欺诈分析项目包面向计算机相关专业毕设学生、课程设计及需要实战练习的初学者也适合对保险风控、反欺诈建模感兴趣的研究者参考。项目为导师指导下高分通过的课程作业评审分96.5分代码经完整测试可直接运行并提供远程辅导渠道。压缩包共12个文件含5个Jupyter Notebook源码模块、1个Python构建脚本、2个Markdown说明文档及TSV/CSV格式的建模数据集整体大小15.21MB。内容覆盖特征分析、特征衍生、特征选择、特征变换与参数调优等完整环节数据集中标签1代表欺诈用户、0代表正常用户配套训练/测试及预测结果文件可完整体验保险反欺诈场景从数据预处理到模型调优的端到端流程。源码按模块划分、以ipynb形式组织各步骤通过文件交互衔接能清晰看到特征工程与模型优化之间的依赖关系对学习多人协作开发与复现竞赛流程都有帮助。目前已有75人学习下载适合作为课程设计、期末大作业的高分参考模板也可用作保险风控入门项目的实战起点。1. 社保反欺诈为什么必须上AI从人工抽检到全量预警的转变社保反欺诈很多人印象里还是“抽病历、查票据”的体力活但现实里的骗保早就黑产化了虚构住院、挂床骗保、冒用身份开药同一套话术在同一批医院反复操作人工抽检覆盖率通常只有几个百分点漏掉的是大头。把人工智能里的异常检测和分类模型用到社保结算流水上用Python从数据集清洗、特征工程到模型训练跑完整条链路稽核范围就能从抽检变成全量预警——这个标题里的python源码加数据集就是为这条链路准备的。这篇笔记写给三类人社保稽核岗位的技术负责人、刚接手反欺诈项目的算法工程师以及拿这个方向做人工智能大作业或毕设选题的学生。下面按数据、特征、模型、评估、上线的顺序把可复现的路径和踩过的坑拆开讲。2. 先把数据搞干净社会保险反欺诈数据集构造与标签定义2.1 欺诈样本长什么样业务规则驱动的标签设计我见过不少第一次接触这个领域的工程师拿到zip解压出数据集后第一句话就问“label列在哪”这是反欺诈项目里最容易被误导的地方。信用卡欺诈有公开的带标签数据集但社保领域几乎没有因为结算数据涉及个人隐私能脱敏放出来的样本量本身就不大。所以能做的是用业务规则先圈出“高度疑似”样本再结合人工稽核结果把标签定下来。这也就意味着标题配套的源码和数据集核心价值不是“现成的标签”而是“怎么从原始流水里生成标签”的完整流程。社保反欺诈的常见骗保类型就那么几类数据上的表现可以整理成一张对照表骗保类型数据表现可用字段虚构住院住院天数超长但费用结构单一住院天数、费用明细、药品占比挂床住院夜间无诊疗记录、费用集中在床位费入院/出院时间、日清单高频开药就诊间隔短、重复开同类药就诊时间、药品编码诊断不符诊断编码与药品或检查项目不匹配诊断编码、药品编码标签设计的第一原则不要把“是否被稽核处罚过”直接当成label。被处罚的只是被发现的没被发现的欺诈仍然藏在未标记样本里这种标签偏差会让模型学歪。我的做法是先用规则打“疑似”标记再由稽核人员按比例复核复核确认的作为正样本复核否定的剔除。规则也不用太复杂住院天数超过30天且费用是同级医院中位数3倍以上、同一参保人30天内入院超过3次这两条在大多数社保数据集上都能圈出第一批可疑名单。2.2 用Python读zip、清洗数据集四步流程标题里的zip包通常长这样一套python源码、一个数据集目录、一份字段说明。我一般不会先把zip手工解压到桌面而是直接在代码里用zipfile读取这样数据版本可控清洗脚本跑完能追溯原始文件。import pandas as pd import numpy as np import zipfile # 替换成你实际下载到的包名claim.csv只是占位文件名 zip_path social_security_fraud_analysis.zip with zipfile.ZipFile(zip_path) as z: # 数据集文件名看包内说明常见是claim.csv或settlement.csv with z.open(claim.csv) as f: df pd.read_csv(f, encodingutf-8-sig, dtype{member_id: str, hospital_code: str}) print(原始样本量:, len(df)) print(字段列表:, df.columns.tolist())这里的dtype要留意member_id和hospital_code如果默认读取Pandas会把超长数字ID转成int或科学计数法后面做字符串拼接直接翻车。编码用utf-8-sig是为了兼容Windows Excel导出的带BOM的CSV不然第一列列名会多一个看不见的字符。清洗是四个固定动作去重、过滤异常、统一字典、转时间格式。# 第一步去重。同一参保人同一医院同一结算日期只能留一条 df df.drop_duplicates(subset[member_id, hospital_code, settle_date]) # 第二步过滤物理上不可能的数据 df df[(df[days] 0) (df[total_fee] 0)] # 第三步诊断编码统一成大写去掉空格避免同码不同写 df[icd_code] df[icd_code].str.upper().str.strip() # 第四步时间列转datetime后续按月份做滚动统计 df[settle_date] pd.to_datetime(df[settle_date]) print(清洗后样本量:, len(df)) print(日期范围:, df[settle_date].min(), -, df[settle_date].max())提示去重粒度取决于数据表的粒度。先确认一行是“一次结算”还是“一条明细”再决定subset字段组合。如果是结算单明细表按member_idhospital_codesettle_date去重会误删真实记录。过滤条件里days 0和total_fee 0看着简单实际上能挡掉一大批接口脏数据比如测试库导出的负数冲正记录。缺失值处理上不要无脑fillna(0)天数缺失和费用缺失含义完全不同天数为空可能是门诊数据混进来了费用为空才是真缺失。我会分列处理数值型费用用同级医院中位数填充诊断编码空就单独标记为UNKNOWN作为一档因为缺失本身可能就是一种特征——骗保团伙故意漏填的情况真实存在。清洗完第一步先别急着做特征花一小时把字段字典列出来字段名、含义、粒度、缺失率、单位。这一步最耗时但最值后面特征工程出的每一个问题八成都能回到字段字典里找到答案。3. 特征工程与不平衡处理让模型真正看懂骗保线索3.1 反欺诈特征三件套个体行为、关系网络、时间模式模型能不能识别欺诈七成靠特征。我用下来最有效的是三个维度的特征个体行为统计、关系网络聚集度、时间节律模式。个体行为统计按参保人聚合过去12个月就诊次数、累计费用、平均住院天数、药品费用占比。药品费用占比是个很灵的特征正常住院的药品占比通常在20%到40%而挂床住院的费用结构里床位费、护理费占大头药品占比会异常偏离。关系网络特征要跨表聚合同一参保人是否在短时间内在多家医院反复就诊同一医院同一科室是否反复出现同一批参保人。医保领域的“医托”是成批运作的单个参保人的行为可能不突出但按医院-科室聚合后会发现一批人的就诊时间高度重叠。时间节律特征关注反常识的规律凌晨入院、节假日入院、两次就诊间隔的标准差极小。正常人不会每周一都去住院但团伙作案会固定批量提交。# 个体行为统计 member_stats df.groupby(member_id).agg( visit_count(settle_date, count), total_fee(total_fee, sum), avg_days(days, mean), max_days(days, max), avg_interval(settle_date, lambda x: x.sort_values().diff().dt.days.mean()) ).reset_index() # 药品占比单独计算避免groupby的lambda里引用外部df造成索引错位 drug_total df.groupby(member_id)[drug_fee].sum() fee_total df.groupby(member_id)[total_fee].sum() member_stats[drug_ratio] (drug_total / fee_total).values # 关系网络同一医院-科室维度的人数聚集度 hospital_stats df.groupby([hospital_code, dept_code]).agg( patient_num(member_id, nunique), avg_fee(total_fee, mean) ).reset_index() # 把医院-科室聚集度merge回个人特征 member_stats member_stats.merge( hospital_stats[[hospital_code, patient_num]], onhospital_code, howleft )avg_interval的lambda是取每个参保人的就诊日期排序后做一阶差分再取均值一年只就诊一次的人算出来是NaN后面需要fillna一个大值比如365。drug_ratio单独算是因为在groupby的agg里引用外部df容易出现索引对齐问题分开算更稳。patient_num统计的是该医院去重后的参保人数衡量的是“这家医院是不是在批量拉人住院”。特征合并到member_stats之后建议顺手过滤一遍近零方差特征比如某列超过95%的值都相同它对模型就是噪声。特征相关性的坑也得提一句把total_fee和drug_fee同时放进去两者高度线性相关相关系数常超过0.7树模型不敏感但逻辑回归的系数会被分布震荡。我一般会先跑一个df.corr()把相关性超过0.8的成对特征留一个。3.2 类别不平衡三种处理策略和它们的适用边界社保欺诈样本占比通常低于5%甚至不到1%。直接训练模型的后果是模型把所有样本预测为“正常”准确率95%以上但毫无用处。三种常见策略欠采样随机丢弃正常样本让正负比到1:1。信息损失大适合数据量很大、训练耗时的场景。过采样/SMOTE用插值生成欺诈样本。SMOTE在特征维度低、特征间关系稳定时效果不错但特征维度高且大量重叠时插值会造出“四不像”样本模型学到的是人工痕迹。代价敏感在损失函数里给少数类加权逻辑回归的class_weight、XGBoost的scale_pos_weight都是这个思路。不改变样本分布最稳。我的选择逻辑数据量大、特征简单用欠采样跑基线树模型直接上scale_pos_weightSMOTE只在小数据集上试且评价时必须用时间切分验证否则很容易被训练集的假AUC骗住。# 记录正负样本比例为后续设定scale_pos_weight pos y.sum() neg len(y) - pos scale_pos int(neg / max(pos, 1)) print(f正样本数: {pos}, 负样本数: {neg}, 推荐scale_pos_weight: {scale_pos})scale_pos_weight的取值不需要太精确正负比1:10就设101:50就设50上下浮动20%对结果影响不大。真正影响大的是建模粒度按参保人聚合还是按单次结算建模。聚合后一条记录代表一个人特征稳定但样本量骤减按结算记录建模样本量够但同一个参保人的多条记录同时进训练集和测试集会造成前面说的信息泄露。后面第5章会细讲这个坑。4. 模型选型与训练从逻辑回归到XGBoost的落地对比4.1 基线模型逻辑回归为什么没被淘汰反欺诈场景里算法工程师容易一上来就堆模型但稽核部门不一定买账。稽核人员要的不是黑匣子而是“为什么把他列入嫌疑名单”的可解释说明。逻辑回归在这个场景里没被淘汰恰恰因为它每一维特征的权重都能直接念给业务听特征“药品占比”的权重是2.1意思就是该值每升高一个标准差欺诈概率显著上升。这种解释虽然粗糙但稽核人员愿意用。逻辑回归的训练要注意两件事特征标准化和正则强度。社保特征里total_fee的量级可能是days的几百倍不标准化的话梯度下降会非常慢而且L2正则对量级小的特征惩罚更大权重会被压得不公平。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score # member_stats里已经通过merge带上了label列 X member_stats.drop(label, axis1) y member_stats[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) scaler StandardScaler() X_train_s scaler.fit_transform(X_train) X_test_s scaler.transform(X_test) # C越小正则越强社保特征相关性高我习惯从0.1起步 lr LogisticRegression(max_iter1000, C0.1, class_weightbalanced) lr.fit(X_train_s, y_train) train_auc roc_auc_score(y_train, lr.predict_proba(X_train_s)[:, 1]) test_auc roc_auc_score(y_test, lr.predict_proba(X_test)[:, 1]) print(f逻辑回归 train_auc{train_auc:.4f}, test_auc{test_auc:.4f})stratifyy保证正负样本在训练和测试集里的比例一致不然随机切分时小概率出现测试集里一个欺诈样本都没有的情况。C0.1是正则强度社保表格特征普遍相关性高正则给重一点防止权重互相抵消。class_weightbalanced自动按样本比例调权等价于给少数类加上正比于负正样本比的权重。注意逻辑回归的系数只能解释为统计相关性不能直接断言因果。给稽核人员看的时候要说明这是风险信号而不是事实结论否则业务会把“模型说他是欺诈”当成“他一定是欺诈”来办。4.2 进阶模型XGBoost的关键参数与Python训练代码表格数据的反欺诈任务XGBoost至今是最稳的选择之一。它能处理特征间的非线性交互比如“药品占比异常”只在“住院天数长”的时候才是强信号这种条件关系逻辑回归表达不了。先回答一个经常被问的问题为什么不用深度学习深度网络擅长处理图像、序列这类原始高维输入社保表格数据经过特征工程后通常只有几十到几百维树模型在中小规模表格数据上精度不输深度模型且训练快、可解释性强、对小样本友好。反欺诈场景正样本少深度学习动辄需要大量数据硬上只会过拟合。from xgboost import XGBClassifier xgb XGBClassifier( n_estimators300, max_depth5, learning_rate0.05, subsample0.8, colsample_bytree0.8, scale_pos_weightscale_pos, eval_metricauc ) xgb.fit( X_train, y_train, eval_set[(X_test, y_test)], verboseFalse ) test_auc roc_auc_score(y_test, xgb.predict_proba(X_test)[:, 1]) print(fXGBoost test_auc{test_auc:.4f})四个最值得先调的参数max_depth从5起步社保数据没有图像那么深的结构超过8容易过拟合learning_rate0.05配n_estimators300小步长多走几步比大学习率少加树的组合更稳subsample和colsample_bytree各0.8对行和列都做随机采样减少过拟合scale_pos_weight直接用上一章统计的正负比。eval_set传测试集训练时就能看到每轮auc变化如果eval auc连续20轮不涨直接early stopping不用等300棵树跑完。另外装上XGBoost之后动手前先看一眼版本老版本在fit时会弹一堆废弃参数警告新版本的话use_label_encoder和eval_metric的写法已经变了。代码里不写use_label_encoderFalse升级到1.6以上版本就不会有这个问题。输出时注意predict_proba()[:, 1]取的是正类概率这个值本身就是风险分后面推送稽核名单就按它排序不需要再套一层规则。如果觉得单模型不稳可以把逻辑回归和XGBoost的概率分分别排序后取rank平均抗波动能力强不少。5. 反欺诈模型评估与避坑别被准确率骗了5.1 评估指标选择召回率、精确率与业务成本反欺诈项目里最危险的一句话是“我这个模型准确率99%”。当欺诈率只有1%时一个把所有样本判为正常的模型准确率就是99%但它的召回率是0一个欺诈都抓不到。做评估必须用二分类进阶三件套召回率、精确率、PR曲线。ROC曲线在这个场景里也偏乐观因为负样本量巨大把假正率摊得很低PR曲线直接看正类的精确率和召回率更贴近业务。业务成本和指标的对应关系要讲清楚一次漏报把欺诈判成正常的损失是骗保金额本身加继续作案的成本一次误报把正常判成欺诈的损失是稽核人力消耗和参保人投诉风险。稽核人力充足策略是尽量拉高召回率人力是瓶颈就得设精确率下限比如精确率不低于50%在这个前提下最大化召回率。指标计算公式在这个场景里的意义准确率(TPTN)/(PN)几乎无参考价值类别不平衡下虚高召回率TP/(TPFN)骗保资金被拦截的比例精确率TP/(TPFP)稽核人员跑一趟能坐实的比例PR-AUCPR曲线下面积全阈值下的综合表现最适合不平衡数据5.2 踩坑记录五条高频问题与排查方法第一条随机切分导致测试集虚高。现象训练集AUC 0.96、测试集AUC 0.91上线后月度真实召回率不到一半。原因随机切分让同一个参保人不同时间的就诊记录同时出现在训练和测试集里模型学会了“认人”而不是“认欺诈”。解决按结算日期排序后切分时间靠前的做训练时间靠后的做测试中间留一个月间隔。第二条标签本身有偏。现象模型预测的高风险名单里稽核复核后一半不是欺诈。原因标签最初是规则圈出来的疑似样本规则只盯大额费用小额多次型欺诈根本没进正样本。解决建标签时找稽核人员一起复核两类样本——规则命中的和规则未命中但被稽核实际处罚过的后者是负样本里藏着的“正样本”。第三条特征里混入数据泄露。现象加入“是否被稽核”字段后AUC暴涨到0.99但线上完全失效。原因这个字段是事后稽核产生的结果预测时点根本不存在。解决把每个特征问一遍“预测这一刻我拿得到这个值吗”事后字段一律剔除。第四条SMOTE制造了假信号。现象SMOTE后训练AUC升到0.98但时间切分测试AUC只有0.75。原因插值样本在特征重叠区域造出了实际不存在的数据簇模型学的不是业务规律而是插值痕迹。解决换用scale_pos_weight做代价敏感训练或者SMOTE后坚持用时间切分做验证不被训练集的高分迷惑。第五条分数集中在慢病患者。现象模型每天推送的Top50名单里有30个是长期慢性病患者稽核人员没法接。原因慢病患者的就诊频次、费用天然偏高被模型当成高频欺诈。解决在特征里加入慢病标识字段或者对慢病群体单独建模也可以把模型输出分按参保人类型做分层排名各类型分别取Top。注意前三条规定了“按时间切分、复核标签、排查泄露”这是反欺诈建模的底线。任何一条不满足后面的调参都是在错误的地基上盖楼。6. 从实验到预警模型上线前的验证与规则兜底6.1 时间序列切分与影子运行人工智能在社保稽核这条线上正从尝鲜工具变成稽核人员的日常帮手但落地不能一步到位。我习惯在模型正式进生产之前先做一轮影子运行模型在后台跑输出不打到业务系统而是和当周人工稽核的名单做对比。对照标准就两条模型发现的欺诈里有多少是人工名单里没有的模型漏掉的人人工查出来的原因是什么。这一轮能暴露标签偏差和缺失特征是花钱最少、信息量最大的验证方式。# 按时间切分前18个月训练后6个月测试中间留1个月gap df_train df[df[settle_date] 2023-01-01] df_test df[(df[settle_date] 2023-02-01) (df[settle_date] 2023-08-01)]时间切分比随机切分残酷得多社保跨年结算、季度考核这些周期性因素都会让测试集更难也让评估结果更可信。gap的一个月是防止月末结算延迟这种跨期记录泄底。6.2 TOP-N名单与阈值选取技巧模型分数本身没有业务含义必须翻译成“每天稽核几个人”。用预测概率的分位数代替硬阈值取TOP 5%作为待稽核名单稽核人力刚好能消化就固定这个分位人力不足就缩到TOP 2%。# 给测试集打分后按分位数圈定名单 score pd.Series(xgb.predict_proba(X_test)[:, 1], indexX_test.index) top_n score.sort_values(ascendingFalse).head(int(len(score) * 0.05))阈值选择有个血泪教训不要用分数绝对值做阈值。模型学的是相对区分能力数据分布一偏移分数整体就漂按月更新分位数圈人稳定得多。跑完影子运行后拉一张对账透视表——“模型命中且人工确认的、模型命中但人工否定的、人工发现但模型漏掉的”看一次就明白模型真实水平。这张表我建议一直留到正式上线后每周拉一次前三个月盯着它调阈值。希望帮到你。本文还有配套的精品资源点击获取