
简介这份资源是面向计算机相关专业学生与项目实战学习者的社会保险反欺诈分析完整方案基于人工智能方法构建欺诈识别模型适合用作毕业设计、课程设计或期末大作业的参考实现。项目采用ipynb文件分模块开发每位成员负责一个模块并以文件形式交互标签中1代表欺诈用户、0代表正常用户数据目录涵盖训练集、测试集及特征衍生、特征选择、特征转换等中间结果文件完整呈现从特征分析到建模调参的流程。压缩包共12个文件包含5个ipynb、2个md、1个py脚本以及tsv、csv、xml等数据与配置类型整体约15.21MB结构清晰便于按模块检索。已有75人学习下载作者为RuanJian_GC。项目代码均经测试运行成功答辩评审平均分达96.5分读者可据此掌握反欺诈建模思路、特征工程方法与参数调优经验并可直接复现实验流程。1. 社保反欺诈模型到底在算什么从一笔异常报销说起某地社保经办系统在月度对账时发现一位参保人短期内跨三家医院、五个科室频繁就诊报销金额是同类人群均值的十几倍。人工核查要翻几十页单据而一个训练好的异常检测模型可以在几秒内把这笔记录标红。这就是「基于人工智能的社会保险反欺诈分析」要解决的核心问题用机器学习从海量医保结算、参保缴费、待遇领取数据中自动识别出可疑的欺诈、滥用、浪费行为。这套方案适合两类人一是手里有社保或医保脱敏数据、想做风险建模的数据分析从业者二是想拿一个完整 Python 项目练手异常检测、特征工程和模型评估的算法工程师。它不要求你懂社保政策细节但要求你能读懂结构化数据、会跑 sklearn 和 pandas。下面我从数据长什么样、特征怎么造、模型怎么选、结果怎么验证一步步拆开讲。2. 反欺诈数据集长什么样字段、标签与三类核心表2.1 先搞清楚数据从哪来、有哪些字段社会保险反欺诈的数据通常来自三个方向医保结算记录就诊时间、医院、科室、诊断编码、费用明细、参保人基础信息年龄、性别、参保类型、缴费基数、以及历史违规标签是否被查实欺诈。真实数据受隐私保护无法直接获取所以公开项目一般提供模拟数据集或脱敏样本。常见做法是构造一张主表加若干维表的结构。主表claims一般包含这些字段字段名含义类型备注claim_id结算单编号string唯一键member_id参保人编号string脱敏后provider_id医疗机构编号string脱敏后service_date就诊日期datediagnosis_code诊断编码stringICD 类编码total_amount总费用float报销前reimburse_amount报销金额float预测重点item_count明细项目数intis_fraud是否欺诈int0/1 标签维表包括members参保人信息和providers机构信息。加载时用 pandas 合并import pandas as pd # 读取三张表注意编码和日期解析 claims pd.read_csv(claims.csv, parse_dates[service_date]) members pd.read_csv(members.csv) providers pd.read_csv(providers.csv) # 左连接保留所有结算记录 df claims.merge(members, onmember_id, howleft) \ .merge(providers, onprovider_id, howleft) # 检查缺失和标签分布 print(df.shape) print(df[is_fraud].value_counts(normalizeTrue))这段代码的关键点是howleft保证不丢结算记录parse_dates让后续按时间聚合不出错。标签分布通常极度不平衡欺诈样本可能只占 1% 到 5%这直接决定后面不能用准确率当指标。2.2 标签从哪来为什么不能直接用原始字段训练is_fraud标签一般来自历史稽核结论但真实场景里标签有两个坑一是滞后性一笔违规可能半年后才被认定二是选择性偏差只有被抽查到的才可能有标签。所以训练时不能把reimburse_amount直接当特征又当标签来源否则模型学到的只是「金额大就是欺诈」这种伪相关。常见做法是标签只用于监督学习的目标变量所有特征必须来自就诊行为本身不能包含任何事后信息。比如「是否被稽核」这种字段必须剔除。我一般会先做一次特征可用性审查# 剔除泄漏字段和 ID 类字段 leak_cols [is_fraud, audit_result, claim_id, member_id, provider_id] feature_cols [c for c in df.columns if c not in leak_cols] # 确认没有未来信息混入 print([c for c in feature_cols if audit in c or fraud in c])如果打印出非空列表说明还有泄漏字段没清干净必须手动确认每个字段的生成时间。2.3 数据质量检查的三个必做动作第一看缺失率。参保人年龄、诊断编码经常缺失缺失率超过 30% 的字段要么弃用要么单独做缺失指示特征。第二看重复结算。同一参保人同一天同一机构出现多条记录可能是拆分报销这本身就是欺诈信号不能简单去重。第三看金额分布。用分位数而不是均值因为欺诈金额会把均值拉偏。# 缺失率 missing df[feature_cols].isnull().mean().sort_values(ascendingFalse) print(missing[missing 0.1]) # 重复结算检测 dup df.groupby([member_id, provider_id, service_date]).size() print(dup[dup 1].head()) # 金额分位数 print(df[reimburse_amount].describe(percentiles[0.5, 0.9, 0.99]))这三步做完你才对数据有个基本判断后面造特征才不会拍脑袋。3. 特征工程怎么做把就诊行为变成模型能吃的数字3.1 个体行为聚合特征人均、频次、金额比反欺诈模型最有效的特征往往不是单条记录而是「这个人相对于他自己和同类人是否异常」。常见做法是按参保人聚合出统计量# 按参保人聚合 member_agg df.groupby(member_id).agg( visit_count(claim_id, count), total_reimburse(reimburse_amount, sum), avg_reimburse(reimburse_amount, mean), max_reimburse(reimburse_amount, max), distinct_providers(provider_id, nunique), distinct_diagnosis(diagnosis_code, nunique) ).reset_index() # 计算人均值的偏离度 member_agg[reimburse_per_visit] member_agg[total_reimburse] / member_agg[visit_count] member_agg[provider_per_visit] member_agg[distinct_providers] / member_agg[visit_count]distinct_providers和provider_per_visit是抓「跨机构频繁就诊」的关键。一个人短期内跑很多家医院provider_per_visit会明显偏高。参数上聚合窗口可以按自然月或滚动 90 天滚动窗口更能捕捉突发行为。3.2 时间窗口特征7 天、30 天、90 天滚动统计静态聚合会掩盖时间模式。欺诈行为往往集中在某段时间所以要做滚动窗口# 按参保人、按天聚合后再滚动 daily df.groupby([member_id, service_date]).agg( day_amount(reimburse_amount, sum), day_count(claim_id, count) ).reset_index().sort_values([member_id, service_date]) # 7 天滚动金额和次数 daily[amount_7d] daily.groupby(member_id)[day_amount] \ .transform(lambda x: x.rolling(7D, ondaily.loc[x.index, service_date]).sum())实际写的时候更稳妥的方式是先设置日期索引再 rolling避免on参数在部分 pandas 版本上的兼容问题。滚动窗口的窗口长度建议至少覆盖 7、30、90 三个尺度分别对应急性、中期、长期异常。3.3 机构与诊断维度的交叉特征单看个人不够还要看「这个人在这个机构看这个病」是否异常。做法是构造member_id provider_id和member_id diagnosis_code的组合频次再算它占该人总次数的比例。比例过高说明行为集中可能是串通或虚假就诊。# 个人-机构组合频次 pair df.groupby([member_id, provider_id]).size().reset_index(namepair_count) member_total df.groupby(member_id).size().reset_index(namemember_total) pair pair.merge(member_total, onmember_id) pair[pair_ratio] pair[pair_count] / pair[member_total] # 取每个人最大占比作为特征 pair_max pair.groupby(member_id)[pair_ratio].max().reset_index(namemax_provider_ratio)max_provider_ratio接近 1 说明该人几乎只在一家机构就诊结合金额异常就有嫌疑。诊断维度同理可以构造max_diagnosis_ratio。3.4 特征缩放与不平衡处理金额类特征量纲差异大树模型不敏感但逻辑回归和神经网络必须做标准化。用StandardScaler或RobustScaler后者对异常值更稳。类别不平衡用class_weightbalanced或 SMOTE 过采样但要注意 SMOTE 只能在训练集上做不能碰验证集。from sklearn.preprocessing import RobustScaler from sklearn.model_selection import train_test_split X member_agg.drop(columns[member_id]) y member_agg[is_fraud] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) scaler RobustScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)stratifyy保证训练测试集标签比例一致random_state固定后结果可复现。缩放器只能 fit 训练集这是新手最容易翻车的地方之一。4. 模型选型与训练从逻辑回归到孤立森林4.1 为什么先跑逻辑回归当基线逻辑回归可解释性强系数直接反映特征方向适合向业务方解释「为什么这笔被标红」。虽然它拟合非线性能力弱但作为基线能快速验证特征有没有信号。训练时用class_weightbalanced处理不平衡from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, roc_auc_score lr LogisticRegression(class_weightbalanced, max_iter1000, random_state42) lr.fit(X_train_scaled, y_train) pred lr.predict(X_test_scaled) proba lr.predict_proba(X_test_scaled)[:, 1] print(classification_report(y_test, pred)) print(AUC:, roc_auc_score(y_test, proba))max_iter调到 1000 是因为不平衡数据下默认 100 可能不收敛。看结果重点看召回率和 AUC不要只看准确率。4.2 树模型与集成方法随机森林、XGBoost 的取舍树模型能自动处理非线性和特征交互是不平衡表格数据的首选。随机森林稳、调参少XGBoost 精度高但需要调scale_pos_weight。常见做法是两个都跑用交叉验证比 AUC 和召回。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators300, max_depth8, class_weightbalanced_subsample, random_state42, n_jobs-1 ) rf.fit(X_train, y_train) proba_rf rf.predict_proba(X_test)[:, 1] print(RF AUC:, roc_auc_score(y_test, proba_rf))max_depth8是防止过拟合的常用起点class_weightbalanced_subsample在每棵树内部再平衡。树模型不需要标准化所以直接用原始特征。4.3 无监督补充孤立森林抓未知模式有标签样本少的时候无监督方法能补盲区。孤立森林假设异常点是「容易被孤立的少数点」不需要标签from sklearn.ensemble import IsolationForest iso IsolationForest( n_estimators200, contamination0.05, random_state42 ) iso.fit(X_train) anomaly_score iso.decision_function(X_test)contamination0.05表示假设 5% 是异常这个值要结合业务先验调。孤立森林的输出是异常分数不是概率通常和有监督模型做融合比如取两者交集作为高置信可疑名单。4.4 阈值怎么定不是 0.5 就完事欺诈检测里阈值直接决定人工核查量。阈值低召回高但误报多阈值高漏报多。做法是画 PR 曲线按业务能承受的核查量反推阈值from sklearn.metrics import precision_recall_curve precision, recall, thresholds precision_recall_curve(y_test, proba_rf) # 找召回 0.8 时精确率最高的阈值 mask recall[:-1] 0.8 best_idx mask.argmax() print(阈值:, thresholds[best_idx], 精确率:, precision[best_idx])这个逻辑是先定业务能接受的召回下限再在该召回下选精确率最高的阈值。比拍脑袋定 0.5 靠谱得多。5. 避坑与排查反欺诈建模最容易翻车的五件事5.1 标签泄漏AUC 0.99 往往是假的现象模型 AUC 高到 0.99上线后完全失效。原因特征里混入了事后信息比如「是否已稽核」「稽核结论」这类字段。解决训练前逐字段确认生成时间任何在结算之后才产生的字段一律剔除并用时间切分而不是随机切分验证。5.2 时间切分做错随机切分让模型偷看未来现象交叉验证分数很好实际部署效果差。原因随机切分让训练集包含未来数据模型学到了时间趋势。解决按service_date排序前 70% 做训练、后 30% 做测试确保测试集时间晚于训练集。5.3 不平衡处理越界SMOTE 用在了验证集现象验证集指标虚高。原因过采样在切分之前做了合成样本泄漏到验证集。解决先切分只在训练集上做 SMOTE 或调class_weight验证集保持原始分布。5.4 特征量纲没统一逻辑回归系数不可解释现象逻辑回归系数大小和直觉相反。原因金额特征没标准化系数被量纲放大。解决逻辑回归和神经网络必须标准化树模型不需要标准化器只在训练集 fit。5.5 阈值一刀切不同机构用同一个阈值现象大机构误报爆炸小机构漏报严重。原因不同机构的就诊量和金额分布差异大。解决按机构分层定阈值或者把机构历史均值作为特征让模型自己学。6. 进阶技巧用 SHAP 把黑匣子变成可解释名单模型给出可疑名单后业务方一定会问「为什么是这个人」。SHAP 能把每个样本的预测拆成特征贡献直接生成解释。对树模型用TreeExplainerimport shap explainer shap.TreeExplainer(rf) shap_values explainer.shap_values(X_test) # 取第一个可疑样本的解释 sample_idx 0 shap.force_plot( explainer.expected_value[1], shap_values[1][sample_idx], X_test.iloc[sample_idx], matplotlibTrue )shap_values[1]是正类欺诈的贡献值force_plot会显示哪些特征把预测推高、哪些拉低。实际用的时候我一般会批量导出每个可疑人的 top3 贡献特征拼成一张核查表业务方看到「跨机构数偏高、7 天滚动金额异常」这种具体理由配合度会高很多。还有一个实用技巧是模型融合把有监督的随机森林概率和无监督的孤立森林分数做秩平均再排序取 top N。这样既能抓住已知模式也能捞到没见过的异常。验证时不要只看 AUC要看 top 100 名单里命中多少真实欺诈这个指标业务方最认。我自己踩过最深的坑是早期太迷信 AUC忽略了名单的精确率结果交上去 200 个可疑人只有 10 个是真的被业务方追着问了一下午。后来养成习惯每次出名单前先看 top 50 的精确率低于 30% 就回去查特征和阈值。希望帮到你。本文还有配套的精品资源点击获取