资讯动态

医保欺诈智能监测系统:多维特征与AI实时预警实战

发布时间:2026/10/27 16:04:24 来源:尧图企业网站定制
简介面向医保欺诈智能监测的完整系统设计与实现资源适合机器学习、数据挖掘方向的中高级学习者及风控领域从业者。内容围绕理赔数据异常检测展开覆盖数据清洗、特征构建、主成分降维、过采样处理、交叉验证与模型调优等关键环节并配有业务价值评估思路帮助理解从海量数据中提取关键指标、平衡非均衡样本以及验证模型泛化能力的完整流程。包内共180个文件以Python源码、pyc编译文件、pkl模型文件为主配合CSS/JS前端资源、XML配置文件、zbak数据备份及地图等辅助文件压缩包约63.38MB目录结构清晰有序。已有30人学习下载。对照代码与模型文件可复现欺诈识别系统的搭建过程掌握特征筛选、样本均衡和模型评估的实操方法为保险反欺诈项目落地提供有效参考。1. 医保欺诈监测为什么难做不是算法不够是坏人太会“做人”医保基金是典型的“池子型”资金钱不多但面广、场景杂、利益方多。一个参保人、一家定点药店、一个科室、一个耗材供应商各自的合理行为边界都不同。过去靠人工抽审和举报线索效率低、滞后严重欺诈行为往往已经结算拨款才发现钱追不回来。而医保欺诈本身是“多维行为在时间上的叠加”——单看一次开药是合规的拉长到三个月看购药频次和同类诊断复现率就明显不自然。这就是基于人工智能与多维特征分析的医保欺诈智能监测系统的价值点在欺诈发生前或结算时点用模型打标签、定风险、拦异常而不是等举报。本文面向的是医保信息系统的开发者和运维人员——可能是做大数据平台的人也可能是做医保结算接口的团队。我会把这条系统设计与实现路线拆成四层特征构造、模型选型、工程框架、踩坑实录。整体思路是一套可以落地的实时监测子系统配合离线批处理做模型再训练。核心不是某个高深的算法而是把医保业务里那些看得见、算得出的“行为痕迹”组织成可学习的数据结构。2. 多维特征体系先搞懂医保欺诈在数据里长什么样2.1 医保数据的五类可用特征来源任何欺诈监测系统都绕不开一个现实原始数据全是业务流水——结算记录、处方明细、诊疗项目、药品目录、医师操作日志。要把它变成机器学习能用的东西第一件事就是分层定义特征。我一般按五个来源拆个人信息特征参保人年龄、参保类型、是否退休、既往历史结算次数行为频率特征单位时间就医次数、购药频次、跨机构量项目组合特征某个诊断对应药品种类、每张处方金额分布、重复项目占比时间规律特征两次结算间隔、短周期内同类诊断复发、节假日就医模式社会网络特征同一医师、同一科室、同一地址关联的参保人数量这五类里面前两类最容易想到也最好算但实际区分度有限。欺诈团伙恰恰会把低频、单点行为伪装得很干净。真正有价值的反而在组合特征和时间规律上。比如一个参保人经常在非常规时间到同一诊所且开具的药品高度集中在某几个高单价品种同时医师是同一个人——这种多重信号一起出现时才叫“多维”单看任何一个都不足以判断。2.2 特征构造把流水表压成“人—月”宽表做医保欺诈监测最常见的数据组织方式是按“参保人月份”切宽表。每行代表一个参保人在一个月内的全部行为汇总所有衍生特征都在这一行里计算。下面是我常用的一套特征构建核心代码用 Python 的 pandas 实现直接跑在 Hive 导出的结算流水表上。import pandas as pd import numpy as np # 读取医保结算流水要求至少包含person_id, bill_date, diag_code, # drug_code, drug_amount, med_inst_name, doc_name df pd.read_csv(settle_records.csv, parse_dates[bill_date]) df[year_month] df[bill_date].dt.to_period(M) # 生成“人—月”聚合层 grouped df.groupby([person_id, year_month]) person_month pd.DataFrame({ total_bills: grouped.size(), total_drug_amount: grouped[drug_amount].sum(), unique_hospitals: grouped[med_inst_name].nunique(), unique_diag: grouped[diag_code].nunique(), unique_drugs: grouped[drug_code].nunique(), avg_bill_amount: grouped[drug_amount].mean(), max_drug_amount: grouped[drug_amount].max(), }).reset_index() # 构建短周期重复特征同一个人在相邻月份里的就诊间隔 df_sorted df.sort_values([person_id, bill_date]) df_sorted[prev_date] df_sorted.groupby(person_id)[bill_date].shift(1) df_sorted[gap_days] (df_sorted[bill_date] - df_sorted[prev_date]).dt.days gap_feature df_sorted.groupby([person_id, year_month]).agg( min_gap_days(gap_days, min), avg_gap_days(gap_days, mean), short_repeat_bills(gap_days, lambda x: (x 3).sum()) ).reset_index() final_features person_month.merge(gap_feature, on[person_id, year_month])这段代码要做的事很直接先把每一笔结算账单聚合到人的月度维度算出金额、机构数、诊断数这类基础特征再算出相邻两次就医的时间间隔。short_repeat_bills统计一个人在一月内有多少次就医间隔小于等于3天——如果一个人频繁隔天就医开药这本身不违规但叠加到高价药物和固定医师特征后就是典型的“就诊频率异常”信号。参数说明gap_days的天数阈值不是拍脑袋定的我会在特征上线后用分位数分布做一次校准。正常慢病参保人平均间隔在15到30天之间如果一类人群的间隔中位数低于5天说明其中混入了不正常行为。阈值设成3天是为了控制异常样本的召回率——太宽会把合规群体卷进来。2.3 为什么必须引入“医师—药品”交叉特征个人行为特征再丰富也有致命盲区它看不到集体配合的骗保行为。比如一个诊所批量收集医保卡安排“患者”轮流刷高价中成药然后药师统一操作——从单个患者看每个月就诊一到两次金额合规没有高频完全正常。只有把医师也作为特征维度拉进来才能发现“某医师开出的某药品量占了全市总量的四分之一”这种极端失衡。所以我在特征体系里固定保留一组交叉衍生特征医师维度的开药总额、医师维度的平均客单价、医师关联的参保人数量、同一参保人关联的医师数量。这些特征不需要额外采集数据直接对已有流水做 GROUP BY 就出来了成本很低但效果极好。第一次跑 XGBoost 的时候特征重要度排名前五里通常能看到这一组交叉项。3. 模型选型和预警策略有监督和无监督必须一起用3.1 有监督模型反欺诈样本从哪来为什么不能直接用召回做欺诈检测的第一反应通常是用历史已查实欺诈案件当正样本跑一个二分类。这里有两个现实问题。第一样本极度不平衡——一个城市的月结算量几百万笔查实欺诈案件可能只有几十条。第二查实案件是从人工抽审中发现的抽审比例不足5%也就是说大量未被发现的欺诈案件其实混在“负样本”里。拿这种标签训练模型模型学到的不是“欺诈特征”而是“被抽查到的概率”。我一般会用一个保守策略只把有明确行政处罚决定书或法院判决书的案例当正样本负样本从经过两轮人工审核且异常评分极低的记录里抽。正样本宁可少也不要拿不确定的标签去污染模型。模型选型上没有太多花招XGBoost 或者 LightGBM 就够用了关键是概率校准。LightGBM 输出的分数是相对风险排序不是真实概率所以要额外加一个 isotonic regression 或者 Platt scaling 把分数校准成可解释的“风险分”。3.2 无监督模型孤立森林找“前所未见”的异常有监督模型只能识别它见过的欺诈模式。但骗保手法一直在变——新的药品组合、新的结算机构、新的时间套路。这些新玩法在历史标签里根本没有对应样本有监督模型必漏。所以必须搭一个无监督的异常检测层作为第一道哨兵让有监督模型跑在其上做甄别。在医保场景我常用两个无监督算法孤立森林和自编码器。孤立森林的好处是快、内存友好、不需要做复杂的分布假设特别适合字段多但缺失多的宽表。自编码器的优势在于能重建出“哪些特征导致异常”——通过计算每个特征的重建误差能直接定位到异常来源。from sklearn.ensemble import IsolationForest import joblib # final_features 来自上一章节构造的人-月宽表 feature_cols [c for c in final_features.columns if c not in [person_id, year_month]] model IsolationForest( n_estimators200, max_samples256, contamination0.01, random_state42 ) model.fit(final_features[feature_cols]) # 找出孤立森林打出的 top 1% 危险样本 final_features[iso_score] model.decision_function(final_features[feature_cols]) risk_group final_features[final_features[iso_score] -0.5]参数说明contamination是估计的异常比例医保场景我实测设 0.01 比较稳定——即大约1%的人月记录会被标记为异常候选。这个值不宜调高否则后续人工复核量太大调低到 0.005 则容易漏掉中期团伙型欺诈。max_samples控制在256是兼顾训练速度和特征稳定性的经验值样本量非常庞大的时候不用盲目加大。这里挖出来的risk_group不会直接进入人工处理而是和下一步有监督模型的结果做交叉融合。3.3 两层模型怎么融合出“可解释”的预警无监督模型的责任是“发现问题”有监督模型的责任是“识别已知欺诈模式”两张网的输出要做融合判断。我的典型融合策略是三通道评分通道A有监督模型输出的欺诈概率 P通道B孤立森林输出的异常分数 S通道C规则层的硬性拦截比如“单月同一参保人结算超过30次且开药金额超过2万”最终预警级别不是简单加权而是判断式的P大于0.9且S显著偏低先人工复核大概率是误报P中等但S极高优先看新手法P和S双高直接转稽核工单。这种设计的好处是智能监测系统能做“决策解释”给稽核人员呈现场景化的证据链而不是一个冰冷的风险数字。4. 工程系统设计与落地实现离线训练与实时预警两条流水线4.1 系统整体架构从结算库到预警工单的四段式结构做医保欺诈监测不能只有模型必须是一条完整的工程链路。我按四段式来搭数据接入层从医保核心结算系统接出流水经 Kafka 实时通道传送到处理系统同时每日 T1 从数仓拉全量数据做离线训练特征计算层实时流上做轻量特征次数、金额滑动窗口离线跑全量宽表特征模型推理层实时推理用 PMML 或 ONNX 加载已训练模型离线训练用 Python 全量重跑预警与工单层预警结果写入 MySQL/Elasticsearch通过企业微信或短信推送稽核人员工单附带特征详情和模型分数这个系统的一个关键点是“实时和离线不要混在一套代码里跑”。实时通道对延迟敏感、对数据完整性不敏感离线通道则相反。混在一起会导致两个问题离线训练出的特征口径和实时推理不一致模型上线即衰减实时链路被离线批任务拖垮预警卡片迟迟推不出去。我的做法是离线特征脚本和实时特征逻辑共用一份特征定义 JSON用同一套配置去生成两套代码从源头上保证口径一致。4.2 实时预警的轻量特征与降级策略实时推理不能直接复用离线宽表——宽表要求看一个完整月份的行为而实时推理往往是在结算动作发生后的几秒内判断。所以实时通道的特征是滑窗式的需要单独写一段轻量逻辑。import redis import json import time # 滑窗变量使用 Redis Hash 存储key 为 person_idfield 为窗口计数 r redis.Redis(hostlocalhost, port6379, db0) WINDOW_DAYS 7 MAX_BILLS_LIMIT 5 MAX_AMOUNT_LIMIT 8000 def evaluate_settle(person_id, bill_amount): day_key time.strftime(%Y-%m-%d) # 窗口滑动只保留最近7天数据 key fperson_window:{person_id} r.zadd(key, {day_key: bill_amount}) r.zremrangebyscore(key, -inf, time.time() - WINDOW_DAYS * 86400) # 窗口内累计次数与金额 count r.zcard(key) total_amount sum(float(v) for _, v in r.zrange(key, 0, -1, withscoresTrue)) risk_hits [] if count MAX_BILLS_LIMIT: risk_hits.append(short_cycle_bills) if total_amount MAX_AMOUNT_LIMIT: risk_hits.append(amount_overlimit) # 返回风险标识给上层服务决定是否进入预警队列 if risk_hits: alert_record { person_id: person_id, bill_amount: bill_amount, risk_type: risk_hits, score_cache: None } return json.dumps(alert_record) return None这段代码用 Redis 的有序集合维护每个参保人最近7天的结算轨迹。zremrangebyscore自动淘汰过期记录保证内存不膨胀。最新的结算进入集合后如果7天内次数超过5次或者金额累计超过8000元输出风险标记。这里的阈值同样不是拍脑袋8000元对应的是当地城镇居民人均门诊年度总支出的一个高分位按四个季度的实际分位数动态调整而5次对应的是“每周去医院超过0.7次”——对慢病参保人来说正常对普通人群来说可疑。实时通道的拦截目标不是精准定罪而是把人送进“待复核队列”由后面的有监督模型打分。实时链路如果 Redis 挂了要做降级处理直接跳过实时预警数据落本地日志离线补算绝不能因为监测模块故障阻断正常医保结算。4.3 离线模型再训练与特征漂移监控离线训练通常是每周一次或每月一次。跑模型之前先要做“特征漂移检查”模型训练时的特征分布和当前真实数据分布如果已经差了很多谨慎的做法是重训模型而不是继续用旧模型硬跑。特征漂移检测我常用 PSIPopulation Stability Index。以“人均月结算金额”为例把模型训练期数据分位数当作基准区间当前月份每个区间的占比和基准区间的占比做比较。PSI 小于0.1说明分布稳定0.1到0.25之间要关注超过0.25强制触发重训。医保场景里如果出现突发事件——比如集采药品目录调整、门诊统筹政策改变——全维度特征都会漂移这时唯一正确做法是放弃旧模型重新训练并对新模型做一段时间的影子运行。import pandas as pd import pickle # 从 Hive 读取最新三个月结算数据 sql SELECT person_id, year_month, total_bills, total_drug_amount, unique_hospitals, unique_diag, unique_drugs, avg_bill_amount, max_drug_amount, min_gap_days, avg_gap_days, short_repeat_bills, label FROM dwd_settle_feature_monthly WHERE year_month 2024-01 AND year_month 2024-03 df_new spark.sql(sql).toPandas() # 与新训练数据对齐的标签来自稽核确认的查实案件 threshold_pos 1 # 查实欺诈 threshold_neg 0 # 双轮复核通过的合规记录 df_model df_new[(df_new[label] threshold_pos) | (df_new[label] threshold_neg)] from sklearn.model_selection import train_test_split feat_cols [c for c in df_model.columns if c not in [person_id, year_month, label]] X_train, X_test, y_train, y_test train_test_split( df_model[feat_cols], df_model[label], test_size0.2, random_state42, stratifydf_model[label] ) import xgboost as xgb model xgb.XGBClassifier( n_estimators300, max_depth6, learning_rate0.05, scale_pos_weight20 ) model.fit(X_train, y_train) with open(model/mi_fraud_model.pkl, wb) as f: pickle.dump(model, f)参数说明scale_pos_weight设为20是因为查实正样本和合规负样本的比例通常在1:20到1:50之间直接训练会严重偏向多数类。先设为20跑完看召回率和精确率的 PR 曲线再向上下调整到一个对稽核人员可用的平衡点。max_depth保持6以内——医保特征大部分是表格型数据深度太大会导致特征交互复杂化难以向稽核人员解释“为什么这个人是风险对象”。5. 避坑指南做医保欺诈监测最容易踩的五个坑5.1 坑一标签样本太干净模型学成了“查案方式”而不是“欺诈特征”现象模型训练完效果极好AUC达到0.98上线后每周推给稽核人员的名单一点用也没有。原因正样本全是“被举报查实”的案例而这些案例被查实的原因大部分是群众举报不是数据异常。模型学的是“被举报的人的特征”不是“欺诈的人的特征”。正好很多正常就诊的患者因为金额大被怀疑过于是这类人被反复标记为高风险。解决正样本只保留“有行政处罚决定书且审计报告有明确资金流向造假”的重点案件数量少也没关系宁可让召回率难看一点也要保住标签纯度。上线后用一个月时间做“模型预警结果 vs 人工抽审结果”的交叉比对再回头调标签。5.2 坑二实时特征和离线特征口径漂移模型像在黑匣子里跑现象离线重训后的模型在测试集上效果很好但上线到实时推理服务后每天预警名单和离线批量跑出来的结果完全对不上。原因离线特征脚本用的是“截至月末全量数据聚合”实时推理用的是“结算发生时点之前的数据”时间窗口边界天然不同。解决把特征定义统一到一份 YAML 配置文件里离线脚本和 Flink 实时任务共同读这份配置。配置里明确每个特征的“统计截止点”是当天零点还是实时滑动窗口如果同一特征在离线和实时无法完全对齐就保证至少排序是单调的——实时算出来的量比离线小但样本间相对顺序一致。5.3 坑三季节性波动被当成异常年节前后误报率飙升现象每年春节和寒暑假期间系统日均预警量翻三倍稽核人员大量时间浪费在无效工单上。原因医保结算有非常明显的季节性——返乡人员集中就医、儿童假期就诊、流感季购药——这些因素导致购药频次和金额特征普遍抬高模型分整体上升。解决强制在特征层加入“同期基准差”而不是绝对值。比如“当月购药金额相比去年同期同月的变化率”而不是“当月购药金额”。模型训练时也要把“月份”作为特征维度传入让模型学会在不同季节下动态调整基线。5.4 坑四保险公司或医院接口数据结构一变特征全部断裂现象某次医保系统升级后药品编码从7位扩到9位特征计算直接报错或者算出来的值全部偏向新编码。原因上游字段一旦变化如字典映射失效、机构编码合并、药品编码升级下游特征脚本需要同步改但经常没有及时处理。解决在数据接入层加“数据质量监控六项”字段缺失率、枚举分布偏差、主键唯一性、时间字段合法性、金额负数比例、机构映射命中率。六项里任何一项超阈值就不放行数据进入特征层。这个环节看起来不性感但是在医保这类强业务依赖的场景里它就是整条链路的保险丝。5.5 坑五稽核人员不信任模型输出系统沦为摆设现象预警工单生成后打开率低稽核人员宁愿自己翻流水。原因模型只给了一个分数没给理由。稽核人员没法向领导解释“为什么这个人风险分高”更没法拿着分数去约谈医师。解决每次预警必须附带特征解释。常用的做法是用 SHAP 值把该样本的顶部5个权重特征输出为中文标签——比如“近30天关联同一医师次数超正常人群5倍”“开药金额高于该诊断分位数第99位”。稽核人员拿到的是一段业务人能看懂的文字记录而不是tensor张量。6. 验证与提升技巧用U形回流法让系统越跑越准模型上线不是终点。我自己的习惯是每季度做一次“U形回流”把过去90天模型预警但稽核确认无问题的样本打回训练集同时把预警遗漏但在后续人工抽审或外部协查中暴露出的欺诈案底也打回训练集。打回正样本和负样本的比例按实际稽核反馈校准然后重训模型并且在新旧模型之间做一场A/B测试。A/B测试的做法是双通道并行运行两周旧模型照常出工单新模型影子运行只记录不发送两周后比较两者的精确率和“新发现案件数”。这里我注意到一个经验教训——不要只比精度。旧模型通常精度更高因为它的误报模式稽核人员已经熟悉了新模型真正的价值是“多抓到的增量案件”。只看离线测试指标就上线新模型是对“泛化能力”的过度信任。两个模型并行期间还要关注人工复核时效。一个工单如果拖了10天才有复核结果那这条反馈样本就已经失去训练时效性了——欺诈模式可能已经换了一轮。所以预警工单要设超时提醒超过5个工作日未处理自动升级督办。只有快速回流模型才能跟上骗保手法的迭代速度。说到底医保欺诈监测不是一次性交付的项目而是一条需要业务侧、数据侧和稽核侧持续打补丁的链路。模型只是把人的稽核经验加快、放大它替代不了调查员对真实世界的判断。希望这套设计思路能帮你在自己的数据环境里少走几步弯路先把地基打稳再谈智能化。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑