☰
医保欺诈智能监测系统从多维特征到规则模型融合的落地实践
2026/10/27 16:04:06 网站建设 项目流程

简介:面向机器学习与风控方向学习者的医保欺诈智能监测项目资料包,利用多维特征分析和人工智能算法识别可疑理赔行为,涵盖数据清洗、特征筛选、主成分降维、过采样处理、交叉验证与模型评估等完整流程,能够帮助读者建立医保风控场景下的建模落地认知。包体共180个文件、约63.38MB,以Python源码(py)、序列化模型(pkl)和文本说明(txt)为主,辅以CSS/JS等前端页面及地图与备份文件,可支撑从后端训练到前端展示的调试需求。已有30人学习下载,适合个人学习或课程设计参考。包内包含训练/测试数据、模型产物及可视化图表,可对照查看主成分降维、过采样与交叉验证在具体数据上的应用效果,理解不平衡样本处理和参数调优的实际操作;目录结构较完整,便于按流程拆解复现,也可为毕业设计或竞赛项目提供直接可参考的代码组织思路。

1. 为什么医保欺诈监测要从“事后抽查”转向“多维特征分析”

医保基金监管的日常,是审计人员每月面对几千条疑点数据,而规则库大部分命中在“重复报销”“超量开药”这类低水平欺诈上,对跨院频繁就诊、医患异常关联这类隐蔽行为反而很难抓住。基于人工智能与多维特征分析的医保欺诈智能监测系统,核心思路是先把医保结算数据打平成可计算的患者、机构、诊疗多维特征,再让规则引擎、无监督模型和可解释的决策融合一起工作,把审计人员的核查范围从几千条压到几十条。这个方向适不适合投入,关键看两个前提:数据能否按统一主索引对齐,业务侧是否愿意把反馈标签回流到模型。下面的内容按架构设计、特征工程、模型落地的顺序展开,能用代码复现的部分直接给出来。

2. 智能监测系统的总体架构与数据接入:先把数据底座打平

2.1 四层架构怎么分,各层承担什么

我见过不少团队拿到这个题目后,第一反应是“先写一个孤立森林脚本,跑一遍出个名单”。结果往往是模型做出来很快,上线后却没人愿意用,因为审计人员不知道分数怎么来的,也没法把审核结论回流给模型。一个能落地的医保欺诈智能监测系统,至少要分成四层,而不是把逻辑全塞进一个 Python 脚本里。

第一层是数据接入层,负责把医保结算流水、患者主索引、医师科室表、药品目录这些异构数据统一采集到分析库,同时完成脱敏和字典标准化。第二层是特征计算层,运行一套可重跑的定时任务,把原始流水加工成患者画像、时序行为、机构网络特征。第三层是模型推理层,部署规则引擎、异常检测模型、分类模型,并对外提供打分接口。第四层是业务应用层,面向审计人员提供预警工单、可视化下钻和反馈录入界面。

分层解决的最大问题是“重跑成本”。医保数据每周都可能因为结算差错、退费、补录而变化,如果特征计算和模型推理写在一个脚本里,每次数据修正都要重算全量,非常容易翻车。把特征计算单独拆成可重跑任务后,上游数据变了,只重算特征表,模型层照常读取最新特征推送结果。另一个常见做法是让规则引擎和模型推理以独立服务进程运行,避免模型库版本升级导致整个预警服务不可用。

2.2 数据接入与清洗的落地细节

医保数据的脏,往往不是字段为空,而是同一患者在不同医疗机构有不同主索引。有的机构用医保卡号,有的用身份证号,有的直接采病案号,导致按患者维度聚合时出现严重漏判。这里必须在接入层做统一主索引映射,常见做法是以身份证号为基础键,结合参保地代码和姓名哈希生成全局患者ID。

清洗逻辑我一般放在 SQL 层完成,先做明细去重,再做字典映射,最后做敏感字段脱敏。身份证号、手机号、详细住址不进特征计算库,统一替换成哈希值,这既是合规底线,也避免后续特征表被误用。下面是一条典型的结算表清洗语句:

CREATE TABLE dwd_settle_detail AS SELECT MD5(CONCAT(insure_id, id_card_no, patient_name)) AS patient_key, MD5(CONCAT(insure_id, org_no)) AS org_key, settle_time, diag_code, item_code, item_type, quantity, total_fee, self_fee, fund_fee FROM ods_settle_flow_raw WHERE settle_time >= '2023-01-01' AND total_fee >= 0 AND quantity IS NOT NULL GROUP BY insure_id, id_card_no, patient_name, org_no, settle_time, diag_code, item_code, item_type, quantity, total_fee, self_fee, fund_fee;

这段 SQL 做了三件事:通过 MD5 拼接生成患者主键,把机构表统一映射成 org_key,同时过滤掉总费用为负的退费脏数据和空数量记录。需要注意 GROUP BY 全字段的写法在实际生产里并不高效,但胜在可审计、易排查。如果产出表数据量巨大,再考虑用增量主键或基于 settle_time 的分区裁剪来优化,而不是在清洗阶段贸然去掉明细字段。

数据接入层最容易踩的坑是时间字段不规范。有的机构 settle_time 是 DATE,有的是 DATETIME,还有的是带时区的字符串,直接比较会漏掉当天最后一小时的数据。我一般会统一把时间字段转成 TIMESTAMP 并存储为 UTC,在特征计算层再按本地时区做窗口切分,避免跨时区导致的日切换错位。脱敏字段和主键生成逻辑要写进数据字典里,后面特征计算和模型排查只认这一套规范。

2.3 调度与时效设计:日批任务加实时接口的低延迟组合

医保欺诈监测不是纯离线场景。审计侧通常是每日凌晨重跑风险名单,但结算侧需要在患者刷卡结算时做风险拦截,拦截慢了会影响正常就医。常见做法是离线批量特征任务每天一次,为审计大屏和名单推送提供分钟级数据;实时接口则承担结算前风险评分,目标是把单次评分耗时控制在 200ms 以内,否则结算柜台会明显感知到卡顿。

实时接口不能直接查 Hive 特征表,一般会引入一层 Redis 特征缓存,内存里只保存最近 90 天的患者关键指标和当日累计金额。模型推理只读取缓存特征,加上少量实时参数比如“本机构本月新增就诊次数”,即可完成打分。这里要说清楚:实时拦截必须做降级策略,Redis 或模型服务不可用时,默认放行,并把请求日志留到离线补偿队列,避免医保结算主链路被拖垮。

3. 面向医保反欺诈的多维特征体系:从三个维度抓异常

3.1 患者画像维度:先搞清楚这个人“正常吗”

多维特征分析听起来抽象,落到医保数据上第一个维度就是患者画像。这个画像不是静态档案,而是基于滚动窗口统计出来的行为分布,常见特征是近 90 天就诊次数、近 90 天住院次数、次均费用分位数、跨机构就诊机构数、慢性病备案数量。这些指标单独看没什么意义,组合起来才是判断异常的基础。

特征计算我一般用 pandas 处理批次任务,窗口选 30 天、90 天、180 天三档,而不是按自然年做累计。原因是欺诈行为往往表现为短期内集中冲量,然后转移机构,自然年统计会把这种脉冲型异常平均成正常值。代码上用 groupby 加 rolling 是最直接的做法:

import pandas as pd # settle_df 是清洗后的结算明细,按 patient_key 和 settle_date 排序 settle_df = settle_df.sort_values(["patient_key", "settle_date"]) feature_df = pd.DataFrame() feature_df["patient_key"] = settle_df["patient_key"] # 近30天就诊次数:区分门诊和住院 for horizon in [30, 90]: for visit_type in ["门诊", "住院"]: sub = settle_df[settle_df["visit_type"] == visit_type] grouped = sub.groupby("patient_key")["settle_date"].apply( lambda x: x.diff().dt.days.rolling(horizon).sum() ) col_name = f"visit_count_{visit_type}_{horizon}d" feature_df[col_name] = grouped.reset_index(level=0, drop=True) # 次均费用近90天分位数:衡量患者在机构间的费用稳定性 pivot = settle_df.groupby("patient_key")["total_fee"].rolling(90, min_periods=1).quantile(0.9) feature_df["fee_p90_90d"] = pivot.reset_index(level=0, drop=True) feature_df = feature_df.fillna(0)

这套逻辑里最关键的是 rolling(horizon).sum() 处理时间序列。直接用 count 会忽略患者一段时间的自然流量,而 rolling 能捕捉“短期内密度突然升高”的信号。min_periods=1 的作用是允许前 30 天数据不足时用已有数据计算,对冷启动患者友好。填充缺失值固定用 0,不能均值填充,否则会把没就诊的人和正常低频就诊的人混在一起。

3.2 时序行为维度:盯住“短时间内的突变”

患者画像描述的是平均水平,时序行为维度描述的是节奏变化。骗保行为的典型特征不是整体次数特别多,而是在某个时段内突然密集就诊、突然跨多个机构抢药、节假日集中复诊。这些行为用累计次数看不出来,必须把时间当作一个维度来看。

我常用的三个指标是就诊间隔的滚动标准差、近 7 天与近 90 天就诊次数的比值、节假日就诊占比。就诊间隔标准差反映的是节奏稳定性,正常慢病患者复诊间隔相对均匀,异常患者会出现连续三天每天都就诊、然后间隔一个月不来的格局。近 7 天与近 90 天次数的比值用于捕捉“突然活跃”的脉冲特征,比值超过 0.4 时通常值得关注。

这些指标里,节假日就诊占比非常微妙。部分基层机构节假日只开急诊,正常患者很少节假日反复就诊,但“医保套现”群体为了避开工作日出院流程,常集中在节假日挂床。我在实际项目里会把节假日标志做成维表,用公历节假日、周末和本地特殊休假日三个字段拼接,避免只认周六周日导致漏判。

3.3 项目组合与机构网络维度:从个体嫌疑升级到群体发现

单个患者的行为异常再明显,也只是孤立线索。医保欺诈更常见的是“医患合谋”,即特定机构和特定医生拉拢一批患者重复检查、虚假住院。这类模式必须靠项目组合特征和机构网络特征才能暴露。

项目组合特征的思路是统计同一张处方里出现的“检查+用药”组合频次,比如“注射用头孢+地塞米松+葡萄糖输液”这种高套现组合。用 Apriori 或 FP-Growth 做频繁项集挖掘,输出支持度和提升度两个参数。实际操作需要考虑项目编码的目录版本,不同年份药品目录编码可能变化,项目组合表必须按当前目录版本动态生成。

机构网络特征则是把患者作为节点连接关系的桥梁。我通常用 networkx 构建一个“患者-机构”二部图,节点是患者和机构,边表示就诊关系,边的权重是就诊次数。在这个图上做 Louvain 社区划分,能发现某个社区里大量患者高频访问同一家机构。写法和关键参数如下:

import networkx as nx g = nx.Graph() # edges 是 (patient_key, org_key, weight) 三元组 g.add_weighted_edges_from(edges) # 社区划分,resolution 控制社区颗粒度 communities = nx.community.louvain_communities( g, weight="weight", resolution=0.8, seed=42 ) for idx, comm in enumerate(communities): if len(comm) > 20: # 记录社区内机构和患者的密度 org_set = [n for n in comm if n.startswith("org_")] patient_set = [n for n in comm if n.startswith("pat_")] density = len(edges_in_comm) / (len(patient_set) * len(patient_set)) print(idx, len(org_set), len(patient_set), density)

Louvain 的 resolution 参数是这里最值得调的东西。默认 1.0 倾向于把整个区县医疗网络聚成一团,异常机构被淹没。我一般从 0.5 开始试,逐步调到 1.0,目标是让社区数量在 50 到 200 之间,同时每个社区的患者数量不要超过 3000。seed 固定是为了让社区划分结果可复现,审计人员对同一周数据下钻,不应该每次出现不同的社区分组。

4. 规则引擎与人工智能模型融合筛选:从预警到工单的决策链路

4.1 规则引擎先行:为审计人员提供可解释的第一跳

人工智能模型再准,在医保审计场景里也不能作为唯一判定依据,因为审计人员需要向机构说明“为什么盯上你”。规则引擎的最大价值是提供可解释、可追溯的判定理由。常见规则包括单次就诊项目数超过阈值、同一药品 7 天内重复取药超过 4 次、非精神科开具精神类药品、患者当日跨两家以上机构就诊等。

规则配置我习惯用 JSON 格式存储,每条规则带独立编号、风险等级和触发条件。这样一个好处是规则变更不需要改代码,业务人员调整阈值后直接发布。下面是一个简化的规则配置片段:

{ "rule_id": "R001", "rule_name": "单日跨机构就诊异常", "risk_level": "high", "condition": { "type": "count_distinct", "field": "org_key", "op": ">", "value": 2, "window": "1d" }, "action": "generate_alert", "description": "患者同一自然日在三家及以上机构发生结算,疑似分解处方" }

规则命中的结果直接生成预警工单,而不是自动拦截,这是原则。自动拦截会带来两个问题:误伤患者的正常就医,以及模型迭代时规则变化导致不可预期的连锁反应。规则引擎定位应是“第一跳筛选”,把规则命中的记录送入人工审核队列,模型则在规则覆盖不到的区域补充风险分。

4.2 无监督模型与弱标签训练怎么配合

医保欺诈的标签是非常稀缺的,因为只有被审计并最终认定为欺诈的案件才算正样本,而审计本身就是抽样过程,大量漏网之鱼存在于未抽检的记录中。直接做有监督分类很容易因为标签偏差导致模型学到“审计偏好”而非“欺诈本质”。这里常见的做法是先上无监督模型。

孤立森林是医保反欺诈里最常用的无监督模型,原因是它对高维稀疏特征适应好,训练快,且异常分数天然适合排序。实际使用时参数不能全吃默认,特别是 contamination。这个参数表示数据集中异常点的比例,默认 0.1 意味着 10% 的数据会被标成异常,医保真实欺诈率远低于这个数字。我一般先用历史审计违规率乘 10 倍做初值,再用网格搜索在 0.01 到 0.1 之间校准。下面是一个可运行的最小示例:

from sklearn.ensemble import IsolationForest iso = IsolationForest( n_estimators=400, max_samples=256, contamination=0.03, bootstrap=True, random_state=42 ) iso.fit(X_feature_matrix) # anomaly_score 是归一化后的异常分,负值越低越异常 anomaly_score = iso.decision_function(X_feature_matrix)

max_samples=256 是一个保守取值,对几十万级样本来说足够稳定,同时避免单棵子树的样本过大导致模型过于贴近全局分布。bootstrap=True 让每棵树抽样有放回,增加模型多样性。决策分数输出后,不直接设阈值,而是按每周全体患者的分数分布取 99% 分位作为预警线,避免因为整体行为变化导致本周围绕某阈值波动过大。

有监督部分我倾向于用 LightGBM,但把规则命中且人工复核确认的样本作为弱标签,而不是直接采审计结论。规则命中后被人工排除的记录也不能简单丢弃,可以标为低置信负样本,喂给模型时用 class_weight 压低其影响。这样既利用了业务经验,又避免了标签偏见被放大。

4.3 决策融合:场景化的分级输出

规则引擎输出规则命中情况,模型输出异常分数,两者怎么合并成最终结论,是整个系统设计中容易被低估的一环。简单地把规则命中当高风险、模型分高当高风险,会导致两个信号都高的患者重复打扰审计人员,两个信号都低但存在隐蔽模式的患者被漏掉。

我在生产里用的是分级加条件复核策略。风险等级分成三级,绿色放行、黄色复核、红色拦截。红色拦截要求规则和模型同时指向高风险,且满足至少一个规则命中高等级规则;黄色复核则是模型分高但规则未命中,或规则命中但模型分中等,这类场景全部进入人工复核池。模型分高但规则未命中的情况尤其值得人工看一眼,因为这说明当前规则库存在盲区,可能是新的欺诈模式。

决策融合的判定代码可以写成独立函数,便于审计和单测覆盖:

def decision_fusion(rule_score, model_score, rule_list): if model_score >= 0.95 and rule_score >= 0.8: return "RED", "规则+模型双高,建议拦截" if model_score >= 0.7 or rule_score >= 0.8: return "YELLOW", "进入人工复核池" if model_score <= 0.3 and rule_score <= 0.2: return "GREEN", "自动放行" return "YELLOW", "边界情况,人工复核" rule_score = max(rule_weights) # 规则命中数加权 model_score = anomaly_prob # 模型输出的欺诈概率 risk_level, reason = decision_fusion(rule_score, model_score, hit_rules)

这段逻辑的关键是 RED 条件必须两个信号同时达标。真实医保场景里,模型单方面的误报还有解释空间,但如果规则粒度不够细,宁可放行也不要硬拦截。YELLOW 的数量必须控制在审计人力能处理的范围内,一般一个中等城市医保中心的日复核量在 50 到 100 条比较合适。

5. 医保欺诈监测落地避坑:5 个值得记录的翻车现场

5.1 特征全零,预警列表一夜之间空了

现象是某天早上审计大屏上的预警数从日常的 80 多条直接降到 0,排查发现特征表全部是空值。原因是上游结算表做了分区切换,历史分区被清理任务覆盖,而特征计算任务没有配置分区依赖,拉起时读到了空分区,却因为表结构正常没有报错,直接输出全零特征。

解决方式是在特征计算任务里增加最小完整性校验。计算完特征后,检查关键指标均值是否为 0、非空记录数是否低于前一天阈值的 50%,如果触发则熔断整个任务,不让空数据流向预警工单。另外要在调度系统里显式配置上游分区依赖,而不是靠 SQL 里的时间过滤碰运气。

5.2 规则命中“高风险”,模型只给三十分,两边打架

规则引擎判定某患者当日跨三家机构就诊,命中高危规则,但孤立森林给出的异常分只有 0.3 分。原因是该患者是高频率的透析患者,每月固定多次跨院治疗,数据量很大但行为节奏稳定,模型认为这是正常模式,规则却忽略了病种备案信息。

解决方法是把规则命中结果作为弱标签喂给模型训练,同时改造规则条件。透析、放化疗、精神疾病这类病种需要增加豁免名单,规则判定前先匹配患者备案病种。更根本的调整是把“跨机构数”与“病种复杂度”交叉组合,而不是只盯次数这一个字段。

5.3 字典表一更新,特征分布整体漂移

某年药品目录切换版本后,一批旧编码被整体停用,特征计算里的项目组合表没有同步更新,导致所有涉及药品组合的特征突然稀疏化,模型分整体上移,预警工作量增加三倍。根源是业务字典维表由外部维护,特征计算直接引用编码字符串,而不是通过维表服务做统一映射。

解决方式是所有特征口径全部通过维表服务取码,维表变更后自动触发依赖任务重算特征,并在重算期间输出旧特征版本供模型比对。同时每次字典更新前跑一遍特征分布对比脚本,发现分布偏移超阈值就暂停模型发布,先做特征适配。

5.4 孤立森林误报率高得离谱,排查发现 contamination 没调

一个常见翻车是直接用默认参数跑孤立森林,默认 contamination=0.1 直接导致 10% 的医保结算记录被标为异常。医保实际欺诈率通常不足 1%,即使放大到可疑范围也就在 1% 到 3% 之间,按 10% 出预警就是灾难。

解决方法是先按历史审计确认率反推一个初值,比如过去一年审计确认的欺诈金额占比是 0.8%,初值取 0.01。然后用 0.005 到 0.03 的区间做网格搜索,对比每个阈值下的预警采纳率,选出既不淹没审计人员又不放过明显案件的参数。这是无监督模型落地时最该花时间的一步。

5.5 网络特征完全解释不了业务,社区划分切错了对象

有一次用机构-患者二部图做社区划分,结果出来的社区把全市三甲医院和周边基层机构全部聚到了一个社区里,业务人员根本没法看。原因是构图时把不同科室、不同病种混在一起,所有患者只按机构连边,权重又用的是就诊次数,导致最常见的慢病患者把机构紧紧绑在一起。

解决方法是按科室维度分别建图,或者至少把肿瘤、透析、精神类慢病患者单独抽出构建子图。Louvain 的 resolution 参数降到 0.5 到 0.8 区间,社区颗粒度更细,异常机构更容易从正常机构群里分离出来。构图完成后必须让业务方确认社区内机构是否具有“业务语义”,没有业务语义的社区再显著也不可信。

6. 可视化下钻与效果验证:让系统从“跑得出名单”到“用得起工具”

6.1 预警名单要能下钻到“患者-项目-机构”链路

模型再准确,最终面向的也是审计人员,而不是算法工程师。我见过不少系统的预警名单是一张表,只有患者ID、风险分、规则命中数,审计人员点进去什么都查不到,最终还是回到手工查流水。真正的下钻至少要支持从机构红榜进入该机构的高危患者列表,再点进单个患者看近 90 天就诊时间线、项目明细、相关医生和共现患者。可视化不用做得花哨,但链路必须闭合。

6.2 用 SHAP 给每条预警一个“可解释片段”

法律和审计场景都要求预警理由可解释,完全黑匣子的模型输出很难作为正式核查依据。对 LightGBM 模型,我习惯在生成预警时同步计算 SHAP 值,取出贡献度最高的三个特征,连同特征取值一起写入工单。比如“近 90 天跨机构数=5(贡献度 +0.25,基线 0.12)”,业务人员看到这个片段就知道预警是基于什么信息生成的。

SHAP 计算的耗时不小,不适合在实时接口里同步做。常见做法是离线批量算好存表,实时接口只返回风险分,详情页再异步拉取解释片段。解释片段生成要固化进工单表,避免预警处理完成后模型版本升级导致历史记录解释不一致。

6.3 闭环验证的三个指标

上线后我真正长期盯的是三个指标:预警采纳率、审计确认率、漏报追踪数。预警采纳率是审计人员实际点开工单的比例,低于 20% 说明预警名单质量差或者工单可读性差,需要回看特征口径;审计确认率是人工复核后确认疑似欺诈的比例,目标定在 30% 到 50% 之间比较合理;漏报追踪则是每月随机抽取 50 条未命中预警但金额排名靠前的结算记录,人工复核是否存在明显欺诈。这一条往往被忽略,但它才是检验模型盲区的唯一办法。

从我的经验看,先追求模型 AUC 再上线的思路在医保场景里并不成立。真正让这个系统被业务接纳的转折点,是反欺诈平台接入了审核工单系统、审计人员可以在原系统里直接处理预警并反馈结果那一刻。技术侧需要做的就是把规则、模型、解释片段和反馈标签完整串起来,让每一次人工复核都变成模型的训练数据。希望这篇笔记能帮你把系统设计从“跑通一个模型”推进到“跑得赢真实业务”,少踩几个我已经踩过的坑。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询