
简介基于人工智能与多维特征分析的医保欺诈智能监测系统设计与实现是一套面向机器学习与医保风控方向学习者的完整项目资料包。压缩包内Python源码占据主体包含21个py脚本与18个pyc编译文件辅以12个js、22个css等前端资源可支撑Web交互界面另含10个pkl模型文件、9个txt说明文档及若干map源映射与图片便于复盘数据处理与模型构建过程。全部文件共180个压缩后约63.38MB目录结构清晰覆盖数据清洗、特征筛选、主成分降维、过采样、交叉验证及业务价值评估等模块。已有30人浏览学习适合具备一定Python与机器学习基础、正在设计医保欺诈识别系统或想了解智能监测落地流程的中高级开发者从中可获得完整代码、模型持久化文件、前端展示方案与算法调优思路理解从原始理赔数据到欺诈风险预测的端到端实现路径。1. 医保欺诈智能监测为什么单一规则引擎总在团伙骗保面前失灵医保稽核场景里有个让老稽查都挠头的反直觉现象真正的欺诈大案单看每一张单据几乎全是“合规”的。处方在限额内频次没超阈值药品在目录里住院天数正常——但把参保人、医师、机构放在一起看同一个参保人短期内跨 5 家机构开同适应症药物三家机构共用同一批参保人处方里的药和药房进销存对不上。这就是团伙型欺诈骗保的典型形态也是传统规则引擎系统性失灵的根源。一套基于人工智能与多维特征分析的医保欺诈智能监测系统要解决的就是把这些散落在多张表里的弱信号聚合成强证据。这篇文章顺着“特征怎么构造、模型怎么选、系统怎么搭、上线怎么避坑”这条线把这类系统的设计与实现的关键环节完整讲一遍给正准备做同类课题或项目的读者一条能直接落地的路径。2. 多维特征分析怎么落地从医保三流拆出三类特征体系“多维特征分析”这六个字在不同项目里含义差得很远。有的方案堆了上百列特征模型 AUC 很高上线就崩——因为特征里混着“事后才知道”的字段。要做到能落地的多维特征分析第一步不是写代码而是把医保业务的数据结构拆清楚知道哪些表里有信号哪些表里的字段只是账面上的数字。我一般先做一件事把数据画成三流。2.1 先拆业务医保数据里的“人、货、钱”三流医保结算数据大致有这几类结算主表一条记录一次结算含参保人、就诊机构、医师、费用总额、基金支付额费用明细表每一笔药品或诊疗项目机构注册与医师资质表以及容易被忽略的进销存数据定点药店和医院药房的药品流向。欺诈的多发形态是“三流分离”也就是信息流说用了这个药、资金流照单拨付、物流上却没有对应的出库记录或者出库的是另一个品种。一个典型的门诊慢性病团伙案参保人甲在一周内分别在 4 家机构开了 4 张互不冲突的慢病处方每张都不超量机构乙的进销存里同一时段出库的却是完全不同的中成药。单看任何一张结算单都是正常的但把信息流、资金流、物流对齐后处方互斥、跨机构密度、进销存差异同时异常。多维特征分析的第一个作用就是把这些跨单据、跨表的弱信号聚合成可计算的数值。2.2 三类特征体系个体行为、机构画像、关系网络拿到数据后我通常按三个实体粒度建特征参保人、医师、机构。时间窗口覆盖 30、90、180 天三档短期看行为突变长期看慢性病用药趋势。窗口边界必须卡在结算时间之前否则就是“用未来预测过去”的数据泄露这个问题后面的避坑章节会专门展开。特征族实体粒度典型特征计算口径行为频次参保人30/90/180天就诊次数、处方次数、跨机构数count(distinct 机构)费用分布参保人次均费用、基金支付占比、90天费用增速sum(费用) / count(次数)时间规律参保人就诊时间间隔熵、同机构连续就诊比例熵值计算药品组合参保人高互斥组合命中、限定支付适应症匹配药品编码分组比对机构画像机构单方均值分位数、慢病处方占比、参保人重叠度机构维度聚合关系网络参保人/机构二部图度、共用参保人机构数、社群聚类系数networkx特征工程的实现上保底的写法是 pandas 分组聚合。真实生产数据量大建议直接写 SQL 窗口函数但先看伪代码能把语义讲清楚# 以参保人为粒度按窗口天数滚动计算行为特征 import pandas as pd def build_member_features(settle_df: pd.DataFrame, window_days: int 30) - pd.DataFrame: # settle_df 必须包含: member_id, inst_id, settle_date, amount, fund_pay, drug_code # 关键约束窗口内只能使用 settle_date 早于当前结算日期的记录禁止未来信息 settle_df settle_df.sort_values([member_id, settle_date]) agg ( settle_df.groupby(member_id) .apply( lambda g: pd.Series({ visit_cnt: g[settle_date].nunique(), # 窗口内就诊天数 inst_cnt: g[inst_id].nunique(), # 窗口内跨机构数挑高值 avg_amount: g[amount].mean(), # 次均费用观察费用突变 fund_ratio: g[fund_pay].sum() / g[amount].sum(), # 基金支付占比 }) ) ) return aggwindow_days是这里最核心的超参30 天抓短期内频繁就诊的突变行为90 天覆盖一个慢病开药周期180 天用于观察参保人是否在多个机构间“迁徙”。groupby apply在数据量超过几百万行时会很慢真实链路一般用 SQL 的ROWS BETWEEN滑动窗口替代。还有一个冷启动细节参保人刚进入观察窗口时特征为空要做补零处理否则模型会把“新参保人”误判成“异常低活跃人群”。2.3 为什么“多维特征分析”在这里不是锦上添花规则引擎的阈值是公开的或者很容易被试探出来。机构会用拆分处方、换机构开药、换医师身份来规避单维规则这也是“规则引擎越改越厚、欺诈却越来越多”的原因。多维特征分析的对抗性优势在于攻击者要同时控制频次、费用分布、时间熵、机构重叠度等十几个维度才可能绕过模型避规成本指数级上升。同时多维特征的价值在于交叉信号。单看“就诊次数 40 次”可能只是慢病管理得好但“就诊次数 40 次 跨机构 6 家 药品组合互斥命中 2 次”就是一个强欺诈信号。XGBoost 这类树模型会自动寻找这些交叉组合不需要人工写“次数 N 且 机构 M”的规则。这也是把多维特征分析放在人工智能模型框架下做的根本原因——特征工程负责把业务信号数字化模型负责在组合空间里找模式。3. 欺诈监测模型怎么选孤立森林查异常、XGBoost 打分、图算法挖团伙模型层我按项目阶段拆成三道防线数据早期没有可靠标签用孤立森林做无监督批量筛查有了稽核回流的标签之后用 XGBoost 做精细打分再叠加图算法把团伙结构挖出来。这个顺序不是随意的它对应着数据资产逐步完善的过程。3.1 无标签阶段的第一道防线孤立森林批量筛查很多医保项目起步时根本没有“确认欺诈”的标签只有历史稽核记录里零星的几个案例。这个阶段不适合直接训练监督模型常见做法是用孤立森林做无监督筛查。选它而不是 one-class SVM是因为孤立森林是线性复杂度、天然支持高维特征sklearn 实现可以直接吃特征宽表几百万行数据也能在几分钟内跑完。# 无监督阶段用孤立森林对特征宽表做批量筛查 from sklearn.ensemble import IsolationForest # X 为特征宽表需先完成缺失值填充建议做标准化 model IsolationForest( n_estimators200, # 子树数量数据量大时 200~500 max_samples256, # 每棵子树采样数256 够用不是越大越好 contamination0.03, # 预估异常比例医保欺诈通常在 1%~5% max_features1.0, # 默认全特征参与 random_state42, # 固定随机种子保证可复现 n_jobs-1 # 并行核数 ) model.fit(X) # score_samples 返回负值绝对值越大越异常 X[iso_score] model.score_samples(X) topk X.nlargest(100, iso_score).index.tolist()contamination在这里是伪标签比例不是精确的欺诈率。我一般先设 0.02~0.05跑完看分数分布再调。注意孤立森林理解的“异常”不等于“欺诈”它会同时把罕见病的高费用病例、新入驻机构、数据录入错误都标出来。所以这一步的输出不能直接进工单要接一层业务规则做过滤。如果是在做毕业设计或课题这一层的输出可以直接作为“待稽核建议名单”交付配合几十个抽查确认案例就能把无监督产出的故事讲完整。3.2 有标签后的第二道防线XGBoost 欺诈打分随着稽核工单流转系统会积累一批“已确认欺诈”和“已确认正常”的标签这时候就可以上监督模型。医保欺诈标签的正负比例通常悬殊欺诈样本可能只有 1%~3%AUC在这种分布下会显得虚高。我训练时会把评估指标换成aucpr精确率-召回率曲线下面积它对正样本极少的情况更敏感。import xgboost as xgb # X_train: 特征宽表y_train: 0/1 标签1 为稽核确认欺诈 dtrain xgb.DMatrix(X_train, labely_train) dvalid xgb.DMatrix(X_valid, labely_valid) params { objective: binary:logistic, eval_metric: aucpr, # 正样本极少不用 auc 用 aucpr max_depth: 5, # 特征上百列时不要超过 7防过拟合 eta: 0.05, # 小步长配合早停 subsample: 0.8, # 行采样提高泛化 colsample_bytree: 0.7, # 列采样削弱对单特征的依赖 min_child_weight: 50, # 叶子最小样本权重压住标签噪声 scale_pos_weight: 20, # 正负样本比约 1:20 时按此设置 nthread: 8, # 并行线程数 } model xgb.train( params, dtrain, num_boost_round800, evals[(dvalid, valid)], early_stopping_rounds50, # 验证集 50 轮不升即停 )scale_pos_weight的值我习惯直接取负样本数除以正样本数比如负样本 20000、正样本 1000就填 20。注意不要再叠加sample_weight否则正样本权重会被重复放大。min_child_weight调大是我对付稽核标签噪声的主要手段——稽核员确认的标签里本身有误判把这个参数抬高叶子节点要凑够更多样本才分裂模型就不会死记某几条错标签。3.3 团伙欺诈的第三道防线图特征与社群挖掘单点模型抓不到团伙因为团伙里每个成员的单个行为可能完全正常“多机构共享同一批参保人”“机构与医师形成闭环转介”这些结构特征是单条结算记录里算不出来的。所以我一般会构一张机构-参保人二部图再投影到机构侧做社群发现。import networkx as nx # 构造机构-参保人二部图边来自结算聚合(inst_id, member_id, 结算次数) B nx.Graph() B.add_weighted_edges_from(edges) # 投影到机构侧两家机构共享的参保人越多越可能有关联 inst_graph nx.projected_graph(B, set(inst_ids)) # 连通分量同一分量的机构共享同一批参保人是团伙候选 for comp in nx.connected_components(inst_graph): if len(comp) 3: # 至少 3 家机构才够成团伙 alert_pool.append(comp)投影图有一个典型的工程坑参保人有几十万时机构投影图的边数会爆炸。常见做法是先过滤“共享参保人数 3”的机构对再建边。社区发现可以用python-louvain或leidenalg跑完社区可以抽取子图做可视化。图算法产出的连通分量本身是团伙告警机构节点的度、聚类系数、二部图局部密度还能作为图特征回流到 XGBoost 特征表形成“图特征 监督模型”的融合。3.4 时间切分验证与效果口径这里有一个必须强调的验证方式不要随机切分训练集和测试集。同一参保人、同一机构的结算记录高度相关随机切分会把同一条记录拆进训练集和测试集模型看到的“测试样本”和训练样本几乎一样AUC 虚高到离谱上线就现原形。正确做法是按结算时间排序前 70% 做训练、后 30% 做验证且验证集标签只取已经确认过的单据。评估口径上生产环境更该看的是precisionk和lift曲线系统给稽核人员推送 100 个工单其中被人工确认属实的比例就是precision100。AUC 是全局指标稽核人员实际只看排名靠前的几十条所以“top 命中率”比 AUC 更贴近真实价值。4. 智能监测系统架构从离线 T1 到实时拦截的工程落地模型和特征只是内核要让这套监测系统真正被稽核人员用起来工程链路不能少。我按“数据汇聚 → 特征宽表 → 决策路由 → 工单闭环 → 标签回流”五段来讲这也是系统设计里最容易出问题的部分。4.1 数据汇聚与特征宽表三源数据如何进数仓医保场景的数据源通常有三个结算库关系型数据库结算主表和费用明细、影像系统处方单 OCR 出来的文本、基金拨付流水。常见做法是 DataX 做全量加增量同步落到数仓 ODS 层特征计算分两条链路离线 T1 的任务用 SQL 批处理需要实时响应的场景用 Flink SQL 做窗口聚合。-- 实时计算参保人近30天就诊相关特征的滚动窗口 CREATE TABLE settle_source ( member_id STRING, inst_id STRING, settle_time TIMESTAMP(3), amount DECIMAL(10,2), WATERMARK FOR settle_time AS settle_time - INTERVAL 5 SECOND ) WITH ( connector kafka, topic ods_settle, format json ); CREATE VIEW member_30d AS SELECT member_id, COUNT(DISTINCT inst_id) AS inst_cnt_30d, COUNT(*) AS visit_cnt_30d, SUM(amount) AS amount_30d FROM settle_source GROUP BY member_id, HOP(settle_time, INTERVAL 1 DAY, INTERVAL 30 DAY);Flink SQL 里的HOP是滚动窗口INTERVAL 1 DAY表示每天滑动一次INTERVAL 30 DAY表示窗口长度 30 天。WATERMARK用来处理乱序到达的结算数据延迟 5 秒以内的记录还能进窗口超过的就丢弃。离线特征和实时特征必须共用同一份特征注册中心按同一个口径计算否则会出现“实时模型和离线报表对不上”的尴尬。4.2 双引擎与决策路由规则兜底、模型打分规则和模型不是对立关系而是路由关系。规则引擎负责两类事一类是强否决比如黑名单机构、限定支付药品超范围使用直接拦截一类是白名单比如罕见病用药永远不打高分。模型只负责在规则放过的那部分数据里做风险排序。这种分层设计的核心是“规则不进模型”——规则一旦变成训练特征规则调整后模型必须跟着重训迭代成本很高。决策路由可以用轻量配置管理让稽核人员调整优先级时不改代码。本质上这是策略模式的落地每个任务是一个策略实现优先级调整只改配置不改主干逻辑decision_route: tasks: - name: blacklist_deny priority: 1 action: deny # 强否决不进模型 - name: whitelist_skip priority: 2 action: skip # 白名单直接跳过 - name: model_score priority: 3 action: route_to_review # 模型分进入人工稽核 threshold: 0.75 # 阈值按月度监控调整4.3 风险工单闭环从告警到稽核反馈再回流训练监测系统的输出不是一张分数列表而是一张可操作的工单表。工单至少要包含这些字段缺了任何一项稽核人员都很难把系统用起来CREATE TABLE risk_case ( case_id BIGINT PRIMARY KEY, member_id STRING, inst_id STRING, doc_id STRING, risk_score DOUBLE, top_features STRING, -- JSON 数组如 [avg_amount_30d,inst_cnt_90d] status STRING, -- pending / confirmed / dismissed review_org STRING, reviewed_at TIMESTAMP, feedback_label INT -- 1确认异常 0正常 );top_features字段是给稽核人员看的点开工单能直接看到“为什么查这个人”这是第 6 章可解释模块落到生产环境的关键。稽核员确认或否认后feedback_label回流到训练集经过清洗和采样成为下一轮 XGBoost 的标签。这个闭环跑通系统才算真正“长出了自己的判断力”。4.4 为什么不用“一个模型吃遍所有险种”住院、门诊、药店、大病、异地就医这几类结算结构差异太大住院有床位费、手术材料费药店只有药品大病险结算频率低但单次金额高。把全险种混在一个模型里特征是同一个名字分布完全不同模型会被住院费用主导门诊和药店的欺诈特征直接被淹没。常见做法是分险种建模每个险种一个精排模型前面再挂一个通用粗筛模型做召回。如果项目或课题阶段数据量不够至少也要做险种特征归一化或者把险种作为特征放进模型并观察特征重要度——但分险种永远是最省心的方向。5. 医保欺诈监测系统避坑指南数据泄露、标签噪声与阈值漂移模型和架构讲完接下来这段是血泪经验最集中的地方。这套系统我在不同数据集上迭代过三轮把翻车记录整理成五条避坑记录每条按“现象 → 原因 → 解决”来写正在复现的读者可以直接当排查清单用。5.1 特征来自未来AUC 高得吓人一上线就失灵现象离线验证 AUC 0.97稽核人员按名单查了一圈命中率不到两成。原因特征宽表里混进了“结算后才会产生”的字段比如医保智能审核的初审结果、投诉工单状态、后续退费信息。模型用这些字段等于把答案抄进了特征上线后特征取不到精度瞬间崩塌。解决每个特征登记“可用时间戳”特征只能使用预测时点之前已存在的数据验证集强制用时间切分不用随机切分。数据字典里加一行“预测时是否可得”的标记每次新特征上线先问一句这个字段在预测那一刻存在吗5.2 负样本不干净把“还没查的”当成“正常的”现象模型召回率很高但稽核反馈的确认率不升反降。原因很多方案把“没有异常标签”的单据当作负样本。实际上稽核队列里的待查单、积压未处理的单据里混着大量欺诈样本模型学到的是“没被查过 正常”而不是“确认正常 正常”。解决负样本只取“已经稽核并确认无问题”的单据和正样本一样需要人工确认正负样本都从已稽核子集里取宁可样本少也不能脏。这个做法会让训练集缩小一大截但模型分数的业务含义是干净的。5.3 机构 ID 变更导致图特征断裂现象图特征上线两周某机构的特征突然归零连带周边机构特征也跟着掉。原因药店转让、换执照、改经营主体机构主键变了图里的旧节点变成孤立点与之相连的参保人关系全部断裂整个机构社群被拦腰切断。解决建一张机构对齐表把统一社会信用代码、机构名称、父机构关系维护起来图特征按固定实体 ID 计算而不是按原始表主键计算新机构进入时用冷启动策略降权等积累足够结算数据再恢复正常权重。5.4 标签噪声与稽核偏见模型学会了“稽核员觉得谁有问题”现象某个月模型对某个科室的全体医师打高分稽核员一看名单就摇头。原因稽核反馈标签不是客观真值它带着稽核员的注意力偏见——重点检查过的机构标签密度高模型学到的是“这个机构被查得多”而不是“这个机构欺诈概率高”。这是一种典型的模型偏见它把人的主观注意力当成了业务规律而且会随着反馈闭环自我强化。解决训练时按机构维度做样本重采样或者做跨机构交叉验证阈值上线前查看分数是否集中在某几个机构如果分布失衡先排查是不是标签偏见。这个问题不只是公平性议题更直接影响模型在未覆盖机构上的泛化能力。5.5 阈值定死不动模型没变业务变了现象上线半年后每天告警工单数从 20 涨到 70稽核人员开始批量标记“正常”。原因骗保团伙会反复试探阈值边界把行为压到分数线下业务季节变化比如门诊统筹额度结转也会让群体分数整体漂移。解决月度重算分数分位数把阈值定义为“当日 top-k%”而不是固定值同步监控 top-k 命中率命中率连续两周下跌就触发重新训练。模型和阈值分开管理调阈值不重训模型这是成本最低的维护方式。6. 用 SHAP 打开模型黑匣子让稽核人员愿意签收模型的最后一公里模型上线前的最后一个坎是稽核部门那边的“黑匣子”质疑。我做过一个项目稽核组长指着一行高分名单问我你凭什么说这家药店有问题当时我只能报出一个模型分那个项目最后没有通过验收。从那以后我所有模型都强制加一个可解释模块这个习惯帮我省掉了大量解释成本。树模型做 SHAP 解释不需要额外的近似计算直接用 TreeExplainer 就行import shap import xgboost as xgb # model 为 xgb.train 返回的 Booster explainer shap.TreeExplainer(model) shap_vals explainer(X_sample).values # shape: (n_samples, n_features) # 取每个样本最重要的前3个特征写进工单 top_features 字段 for i in range(len(X_sample)): feat_imp sorted( zip(X_sample.columns, shap_vals[i]), keylambda x: -abs(x[1]) ) top3 [feat for feat, _ in feat_imp[:3]] print(fcase {i}: {top3})生产环境里我会把 SHAP 归因翻译成业务可读的语言比如“就诊机构数90天”贡献最大工单上就显示“该参保人 90 天内跨 5 家机构就诊显著高于同区县同病种人群”。稽核人员看到的是结论和依据而不是一个干巴巴的特征名。SHAP 还有一个反向用途排查特征偏见。把某个机构的样本单独拉出来看 SHAP 值在机构维度上的分布如果某个特征对这个机构的打分贡献远高于其他机构那大概率是标签偏见或数据质量问题而不是真实业务差异。这个检查我建议每次模型迭代都做一遍。我的习惯是把“能否把模型解释给稽核员听”当作上线的前置条件解释不清楚的模型再准也不放。哪怕在课题阶段也建议在论文里放一张 SHAP 摘要图它比任何指标都更能说明模型学到的业务规律。这套系统真正常态跑起来之后每天新增的稽核反馈会不断刷新标签和特征分布定期重训和阈值监控并重比一次性调优更关键。希望帮到你。本文还有配套的精品资源点击获取