1. 项目概述:当模型走出笔记本,真正开始“呼吸”现实世界

你有没有经历过这样的时刻?模型在 Jupyter Notebook 里跑得飞起,AUC 0.92,F1 0.87,交叉验证稳如老狗;业务方点头如捣蒜,上线评审会顺利通过,庆祝邮件都发出去了;结果上线第三天,监控告警开始滴滴响,第四天用户投诉说“为什么我的贷款申请被拒了?上个月还批了”,第五天风控团队紧急拉会,发现模型打分突然集体右偏,而日志里只有一行模糊的 feature X missing for 37% of requests 。这不是段子,这是我去年在一家持牌消费金融公司落地反欺诈模型时的真实记录——而且不是第一次。

这篇内容讲的,就是那个被绝大多数教程、课程、甚至论文刻意绕开的阶段: 模型从开发环境走向真实生产系统后的生存状态 。它不叫“部署”,那太轻巧了;它也不叫“上线”,那太静态了。我更愿意把它称作“模型的呼吸期”——从那一刻起,它必须自己吸入数据、代谢特征、输出决策、承受压力、感知变化,并在出问题时自主“咳嗽”或“报警”。而支撑它完成这一切的,不是 PyTorch 的 autograd,也不是 Scikit-learn 的 Pipeline,而是一整套工程化、制度化、可问责的系统能力。

核心关键词“Towards AI - Medium”在这里不是平台标签,而是指向一种稀缺的实践视角:它不教你怎么调参,而是告诉你,当你的模型被嵌进支付网关、信贷审批流、实时反洗钱引擎时,哪些设计缺陷会在凌晨两点把你从床上拽起来;它不鼓吹“MLOps 平台”,而是拆解一个没有平台的团队,如何用 Bash 脚本+Prometheus+人工巡检,把模型服务的可用性从 92% 拉到 99.95%。它面向的不是刚学完《机器学习实战》的新人,而是已经亲手写过 5 个以上线上模型、正被“为什么模型越训越好,线上效果却越来越差”这个问题反复拷问的工程师、算法负责人、甚至技术风控主管。

如果你还在用 notebook 的运行成功来定义项目成功,那你离真正的 ML 工程师,可能就差这最后一步——不是把模型“放上去”,而是让它“活下来”。

2. 内容整体设计与思路拆解:为什么“部署”是整个链条中最危险的一环?

很多人误以为,模型上线最难的是“怎么把 .pkl 文件变成 API”。错了。真正的断崖式风险,恰恰发生在“API 能返回结果”这个表面成功的瞬间之后。因为那一刻,模型正式脱离了受控实验室,进入了混沌的现实系统。我们设计这套生产体系,核心逻辑就一条: 承认不确定性,并为每一种不确定性预设响应路径 。这不是悲观,而是对复杂系统的敬畏。

先看一个典型失败链:某银行信用卡额度模型上线后第 17 天,日均拒绝率从 18% 突然跳到 42%。排查发现,上游交易系统因版本升级,将原本“交易金额”字段从整型改为字符串格式,模型加载时自动做了 int() 强转,失败后默认填充为 0。于是所有用户的历史交易金额特征全变成 0,模型判定“无消费行为”,一律降额。问题根源不在模型,而在 特征管道缺乏类型契约(type contract)和空值熔断机制 。更讽刺的是,该模型在测试环境跑过 1000+ 条 mock 数据,但没人想到去测“传入字符串金额”的边界场景。

所以我们的整体架构,本质上是一个三层防御体系:

第一层是 契约层(Contract Layer) :它不关心模型多聪明,只强制约定“输入长什么样、缺失怎么办、超时怎么处理”。比如我们要求所有特征服务必须提供 /health 接口返回当前各特征的延迟 P95、缺失率、分布偏移指数(KS 统计量),且任何一项超过阈值,自动触发降级开关。这层不用 Python 写,用 OpenAPI Spec + Swagger UI 就能定义清楚,连前端都能看懂。

第二层是 韧性层(Resilience Layer) :它解决“当契约被打破时,系统如何不崩溃”。这里的关键不是“重试”,而是“有策略的妥协”。比如当用户画像特征缺失率达 60%,我们不会让模型硬算,而是启动“影子决策流”:用规则引擎兜底(如“近 30 天无登录用户,额度暂封”),同时将该请求标记为 shadow_mode=true ,进入独立队列供算法团队复盘。这样既保住了业务连续性,又把问题转化成了高质量的反馈数据。

第三层是 治理层(Governance Layer) :它回答“谁为这次降级负责、依据是什么、下次怎么避免”。我们强制每个模型上线包必须附带一份 governance_manifest.json ,里面明确写着:模型 owner(姓名+工号)、上次训练数据截止时间、特征来源系统 SLA 承诺、人工 override 的审批路径、以及最关键的—— 本次上线所依赖的全部外部系统接口文档链接及版本号 。去年一次重大故障复盘会上,正是靠这份清单,我们 15 分钟内定位到是上游征信查询接口变更未同步通知,而非模型本身问题。

这种设计思路,和 Kaggle 比赛截然不同。比赛中,你优化的是单点指标;而在生产中,你优化的是 系统熵减能力 ——即在数据漂移、依赖失效、流量突增等噪声干扰下,维持决策质量稳定性的能力。它不炫技,但极其消耗工程耐心。我见过最稳的生产模型,代码里 70% 是异常处理和日志埋点,核心预测逻辑不到 20 行。

3. 核心细节解析与实操要点:从“能跑”到“敢用”的七道生死线

很多团队卡在“模型已上线”和“业务敢用模型结果”之间,中间隔着七道必须亲手跨过的实操生死线。这些线没有高大上的名词,全是血泪换来的具体动作。下面我按优先级逐条拆解,每一条都配真实踩坑案例和可直接抄的检查清单。

3.1 生死线一:特征服务的“心跳契约”必须可量化、可告警

特征不是静态快照,而是流动的血液。但多数团队只关注“特征计算逻辑是否正确”,却忽略“特征能否按时、按质送达”。我们曾遇到一个经典案例:某推荐模型依赖用户实时点击序列,特征服务承诺 P95 延迟 < 200ms,但实际在晚高峰(19:00-21:00)P95 达到 1.2s。模型没报错,但所有推荐都基于 1 秒前的用户行为,导致“刚搜完手机,首页就推充电宝”这种低级错误频发。

实操要点:

  • 特征服务必须暴露 /metrics 接口,至少包含三项黄金指标:

    1. feature_latency_p95_ms :各特征的 P95 延迟(按特征名维度)
    2. feature_missing_rate_percent :各特征缺失率(按小时粒度滚动计算)
    3. feature_drift_ks_score :关键特征与基线分布的 KS 统计量(每日凌晨自动计算)
  • 这些指标必须接入统一监控平台(如 Prometheus + Grafana),并设置三级告警:

    提示级: feature_missing_rate_percent{feature="user_click_seq"} > 5 (持续 10 分钟)
    警告级: feature_latency_p95_ms{feature="user_click_seq"} > 500 (持续 5 分钟)
    严重级: feature_drift_ks_score{feature="user_click_seq"} > 0.3 (单日突变)

  • 独家技巧 :我们在特征服务 SDK 中内置了“契约校验中间件”。每次特征请求前,自动检查本地缓存的契约状态(如 last_update_time , current_missing_rate ),若发现异常,立即返回预设的 fallback 值(非 null!),并打点记录。这比等模型报错再降级快 300ms。

3.2 生死线二:模型服务的“熔断-降级-兜底”三阶响应必须预演过 10 次以上

模型服务不是孤岛。它依赖数据库、缓存、特征服务、甚至第三方 API。任何一环抖动,都可能引发雪崩。我们曾因 Redis 集群主从切换耗时 800ms,导致模型服务平均延迟从 80ms 涨到 1.2s,进而触发上游网关超时重试,最终压垮整个服务。

实操要点:

  • 熔断器(如 Hystrix 或 Resilience4j)必须配置三个核心参数:

    1. failureRateThreshold=50 (错误率超 50% 触发熔断)
    2. waitDurationInOpenState=60000 (熔断后保持 60 秒,期间所有请求直走 fallback)
    3. ringBufferSizeInHalfOpenState=20 (半开状态允许 20 个试探请求)
  • 降级策略必须具体到业务语义,而非简单返回 500:

    • 对风控模型:降级时调用轻量版规则引擎(如 Drools),执行 if (age<25 && income<5000) then reject
    • 对推荐模型:降级时返回“热门榜单”或“同品类历史 Top10”
    • 严禁 使用 return None return random.choice(items) ,这会让业务方无法归因
  • 兜底方案必须独立部署、零依赖: 我们用 AWS Lambda 部署了一套纯内存规则引擎,所有逻辑编译成字节码,启动时间 < 100ms,不连任何数据库。它只读取 S3 上的 fallback_rules.json (每日凌晨由算法团队更新),确保即使整个微服务集群宕机,兜底逻辑仍可用。

3.3 生死线三:决策日志的“全链路可追溯”必须覆盖从原始事件到最终动作

模型决策出错时,最痛苦的不是修复,而是“根本不知道哪一环出了问题”。我们曾花 3 天时间追查一笔误拒贷款,最终发现是:前端传参时把 income_monthly 错写成 income_montly (少了个 h),后端解析为 0,特征工程阶段未做字段存在性校验,模型照常打分,最终拒绝。整个链路日志里只有 score=0.12 ,没有上下文。

实操要点:

  • 强制实施“决策 ID 全链路透传”:

    • 用户请求入口生成唯一 decision_id (UUID v4)
    • 该 ID 必须作为 HTTP Header( X-Decision-ID )贯穿所有下游服务
    • 每个服务在日志中必须打印 decision_id + 关键变量(如 feature_income=0, feature_age=32
  • 日志结构必须标准化(我们用 JSON 格式):

{
  "decision_id": "a1b2c3d4-...",
  "timestamp": "2026-04-15T14:22:33.123Z",
  "service": "risk-model-v2",
  "stage": "prediction",
  "input_features": {"income": 0, "age": 32, "city_tier": "tier2"},
  "model_version": "20260410-1.2.3",
  "output_score": 0.12,
  "is_fallback": false
}
  • 独家技巧 :我们在 Nginx 层做了日志增强。所有入站请求,Nginx 自动提取 X-Request-ID 和关键参数(如 user_id , product_type ),拼接到 access log 中。这样即使应用层日志丢失,也能通过 Nginx 日志快速定位问题时段。

3.4 生死线四:数据漂移检测的“双轨制”必须同时监控输入与输出

只监控模型准确率是自欺欺人。准确率下降往往是结果,而漂移才是原因。我们曾上线一个营销响应模型,首月 AUC 保持 0.78,但第二周起,营销活动 ROI 下降 35%。排查发现:训练数据中“用户最近 7 天打开 App 次数”均值为 4.2,而线上该特征均值跌至 1.8(因 App 新版推送策略调整),但模型输出分数分布几乎没变——它把低活跃用户也判为高响应,导致无效触达激增。

实操要点:

  • 输入漂移检测(Input Drift):

    • 关键特征:对数值型用 KS 检验(阈值 0.2),对类别型用 PSI(Population Stability Index,阈值 0.1)
    • 实现:用 Evidently AI 库每日定时扫描,结果写入 ClickHouse,Grafana 看板实时展示 TOP10 漂移特征
  • 输出漂移检测(Output Drift):

    • 监控模型打分分布:计算每日 score 的 P10/P50/P90,与基线对比(Δ>15% 告警)
    • 监控决策分布:如风控模型的“通过率”、“拒绝率”、“人工复核率”,设置 ±5% 波动阈值
  • 独家技巧 :我们给每个特征配置了“漂移敏感度权重”。例如,“用户地理位置”漂移权重设为 0.8(高敏感),而“设备型号”设为 0.2(低敏感)。当综合漂移指数 = Σ(单特征漂移值 × 权重) > 0.3 时,才触发高级别告警。这避免了因低价值特征波动引发的误报。

3.5 生死线五:模型验证的“压力测试”必须包含三类真实攻击场景

监管机构不看你的 CV 分数,他们只问:“如果输入全是 0,模型会不会崩溃?”、“如果用户年龄填 200,会不会给出荒谬额度?”、“如果所有特征缺失,系统如何保证不误伤?” 这就是压力测试的核心: 用最坏但合理的输入,检验模型的鲁棒性边界

实操要点:

  • 场景一: 数据污染攻击 (Data Poisoning)

    • 构造输入:将训练集中 5% 的样本,其 income 字段替换为 rand(0, 1000000)
    • 验证目标:模型在污染数据上 AUC 下降 < 0.05,且无异常内存占用
  • 场景二: 对抗扰动攻击 (Adversarial Perturbation)

    • 使用 TextFooler(NLP)或 FGSM(CV)生成扰动样本
    • 验证目标:Top3 预测类别不变,且置信度下降 < 20%
  • 场景三: 极端边界攻击 (Edge Case)

    • 构造输入: age=0 , age=150 , income=-1000 , credit_score=0 , credit_score=1000
    • 验证目标:模型必须返回有效分数(非 NaN/Inf),且分数在合理区间(如 0~100)
  • 独家技巧 :我们把压力测试做成 CI 流水线一环。每次模型训练完成后,自动触发测试,生成 stress_test_report.html ,包含所有攻击样本、模型输出、对比图表。报告必须由算法负责人和风控负责人双签确认,才能进入上线流程。

3.6 生死线六:人工干预的“审计闭环”必须确保每一次 override 都可回溯、可归因

再好的模型也会犯错。关键不是“不犯错”,而是“犯错后能快速修正并防止复发”。我们曾有个致命漏洞:风控模型误拒一位 VIP 客户,客户经理手动 override 通过,但该 override 未记录到特征库,导致模型后续仍对该客户打低分,形成恶性循环。

实操要点:

  • 强制实现“override 即新训练样本”:

    • 每次人工 override,系统自动生成一条标注数据: {"features": {...}, "label": 1, "override_by": "zhangsan", "reason": "VIP_client"} ,写入专用 Kafka Topic
    • 每日凌晨,特征平台自动消费该 Topic,将 override 样本加入下一轮训练集,并标记 source=override
  • 审计日志必须包含四要素:

    1. override_decision_id (关联原始决策)
    2. override_timestamp (精确到毫秒)
    3. override_user_id (不可匿名)
    4. override_reason_code (预设枚举: vip_exception , data_error , policy_override , other
  • 独家技巧 :我们在 override 页面加了“影响范围预估”功能。当客户经理点击 override 时,系统实时查询:该客户所属客群(如“25-30 岁白领”)近 7 天被拒率、该客群模型打分均值、以及同类 override 历史成功率。这倒逼人工干预更审慎,也让我们发现了“某客群模型系统性低估”的深层问题。

3.7 生死线七:模型迭代的“灰度发布”必须控制在 5% 流量、24 小时内可回滚

“全量上线”是生产环境最大的禁忌。我们曾因一次全量发布新版本模型,导致 3 小时内拒绝率飙升至 65%,损失潜在客户超 2 万人。事后复盘,问题出在新模型对“小微企业主”客群过度保守,而该客群占总流量的 12%,恰好在全量发布时集中涌入。

实操要点:

  • 灰度策略必须满足“三可”:

    • 可切分 :按用户 ID 哈希(如 user_id % 100 < 5 )分配 5% 流量,确保客群分布均匀
    • 可监控 :灰度流量单独打标( traffic_tag=gray_v2 ),所有指标按此维度聚合
    • 可回滚 :回滚操作必须 < 60 秒。我们用 Kubernetes ConfigMap 存储模型版本号, kubectl set env deploy/model-service MODEL_VERSION=v1.2.3 即可秒级生效
  • 灰度观察期必须包含核心业务指标:

    • 主指标: gray_v2_reject_rate vs baseline_reject_rate (Δ<±2%)
    • 次指标: gray_v2_avg_score (波动 <±5%), gray_v2_p95_latency_ms (< baseline × 1.3)
    • 业务指标:灰度用户转化率、客单价、投诉率(需业务方确认)
  • 独家技巧 :我们开发了“灰度决策对比工具”。运营人员可输入任意用户 ID,工具实时返回:该用户在旧模型下的决策、新模型下的决策、差异原因(如“新模型对‘经营年限’特征权重提升 30%,导致评分下降”)。这极大提升了灰度期的问题定位效率。

4. 实操过程与核心环节实现:一个真实风控模型的 72 小时上线全记录

现在,让我们把前面所有原则,放进一个真实的作战场景。这是我在某城商行落地“小微企业贷前风控模型”的完整 72 小时实操记录。没有虚构,所有时间、命令、配置均来自生产环境。

4.1 第 0 小时:上线前夜的最终校验(2026-04-15 20:00)

此时模型代码、特征管道、服务镜像均已准备就绪,但真正的考验才开始。我们执行一套标准化的“Go/No-Go CheckList”:

  1. 契约校验

    # 检查特征服务健康状态
    curl -s "http://feature-svc:8080/metrics" | grep -E "(missing_rate|latency_p95)"
    # 预期输出:feature_missing_rate_percent{feature="biz_revenue_3m"} 0.12
    #         feature_latency_p95_ms{feature="biz_revenue_3m"} 187
    
  2. 熔断器压测

    # 模拟特征服务超时(注入故障)
    kubectl exec -it pod/feature-svc -- bash -c "echo 'sleep 2' > /app/fault.sh"
    # 发送 1000 次请求,验证熔断器是否在 500 次错误后开启,且 fallback 响应时间 < 100ms
    
  3. 日志链路验证

    # 发送带 X-Decision-ID 的测试请求
    curl -H "X-Decision-ID: test-20260415-001" \
         -H "Content-Type: application/json" \
         -d '{"user_id":"U123456","biz_revenue_3m":120000}' \
         http://model-svc:8000/predict
    # 检查 Elasticsearch 中是否存在该 decision_id 的完整日志链
    
  4. 漂移基线确认

    -- 查询 ClickHouse 中过去 7 天特征漂移报告
    SELECT feature_name, drift_score, last_updated 
    FROM feature_drift_daily 
    WHERE date >= '2026-04-08' AND drift_score > 0.25
    -- 预期:无结果,或仅有低敏感特征(如 device_os)
    

提示:任何一项未通过,立即 halt 上线流程。宁可延期,不冒风险。

4.2 第 1 小时:灰度发布与首波监控(2026-04-16 00:00)

我们选择午夜发布,避开业务高峰。Kubernetes 配置如下(关键部分):

# model-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: model-service-v2
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: model-service
        image: registry.example.com/ml/model-service:v2.1.0
        env:
        - name: MODEL_VERSION
          value: "20260415-1.2.3"  # 模型版本号
        - name: TRAFFIC_PERCENTAGE
          value: "5"               # 灰度流量比例
        # ... 其他配置

发布后,立即打开 Grafana 看板,重点关注三组对比曲线:

指标 灰度流量(v2) 全量流量(v1) 差异容忍
reject_rate 22.3% 21.8% ±2%
avg_score 68.2 67.9 ±5%
p95_latency_ms 92 88 < 120ms

实操现场记录
00:15, reject_rate 灰度曲线突然上扬至 24.1%,超出容忍上限。我们立刻暂停灰度,检查日志。发现 biz_revenue_3m 特征在灰度流量中缺失率高达 18%(全量仅 0.3%)。根因是灰度流量中某类企业注册渠道未上报该字段。解决方案:临时将该渠道流量从灰度池剔除,并给特征服务增加 biz_revenue_3m 的兜底逻辑(取行业均值)。00:45,重新开启灰度,指标回归正常。

4.3 第 24 小时:首次漂移预警与人工介入(2026-04-16 23:59)

凌晨,Evidently AI 的定时任务生成日报,发现关键特征 tax_payment_12m 的 PSI 值达 0.32(阈值 0.1),远超警戒线。我们立即执行:

  1. 定位漂移源

    -- 查询漂移时段的特征分布
    SELECT 
      toStartOfHour(event_time) as hour,
      avg(tax_payment_12m) as avg_tax,
      countIf(tax_payment_12m = 0) * 100.0 / count() as zero_rate
    FROM feature_log 
    WHERE event_time >= '2026-04-16 00:00:00' 
    GROUP BY hour 
    ORDER BY hour DESC
    

    结果显示:04:00-05:00 小时段, zero_rate 从 5% 飙升至 42%,且 avg_tax 跌至 1200(基线为 8500)。

  2. 业务归因 : 联系税务系统负责人,确认:04:00 正是税务局年度汇算清缴系统维护窗口,导致该时段所有企业纳税数据无法获取,特征服务返回默认值 0。

  3. 应急响应

    • 立即更新特征服务配置,将 tax_payment_12m 的 fallback 策略从 0 改为 industry_avg (按企业所属行业取均值)
    • 同步更新模型服务的 feature_contract.json ,将该特征的缺失率容忍阈值从 5% 提升至 15%
    • 在 Grafana 看板添加备注:“04:00-05:00 税务系统维护,特征临时降级”

注意:这不是模型问题,而是系统协同问题。我们的响应速度,决定了业务受损时长。

4.4 第 48 小时:首次人工 override 分析(2026-04-17 23:59)

灰度运行满 24 小时后,共收到 17 次人工 override 请求。我们导出 override_log 表,进行聚类分析:

SELECT 
  reason_code,
  count(*) as cnt,
  round(avg(score_before), 2) as avg_score_before,
  round(avg(score_after), 2) as avg_score_after
FROM override_log 
WHERE created_at >= '2026-04-17 00:00:00'
GROUP BY reason_code
ORDER BY cnt DESC

结果令人警醒:

  • data_error (数据错误)占比 65%(11 次),其中 8 次指向 biz_revenue_3m 字段为空
  • vip_exception (VIP 例外)占比 24%(4 次),全部集中在“高新技术企业”客群

行动项

  • 紧急修复 biz_revenue_3m 数据采集链路(与上游 ERP 团队协同)
  • 启动“高新技术企业”客群专项建模,为其定制特征权重
  • 将本次 override 数据,全部加入下一轮训练集

4.5 第 72 小时:全量发布与长效机制建立(2026-04-18 23:59)

灰度 48 小时数据确认稳定后,我们执行全量发布。但真正的结束,是建立长效机制:

  1. 自动化巡检脚本 (每日 06:00 执行):

    # check_production_health.py
    if get_drift_score("biz_revenue_3m") > 0.25:
        send_alert("biz_revenue_3m 漂移严重,请检查 ERP 接口")
    if get_override_rate("data_error") > 0.05:
        send_alert("数据错误类 override 超阈值,触发数据质量复盘")
    
  2. 模型健康度月报 (自动生成 PDF):

    • 包含:各特征漂移趋势图、override 原因分布饼图、灰度/全量指标对比表、下月优化计划
    • 自动邮件发送至算法团队、风控部、科技管理部
  3. 知识沉淀

    • 将本次上线所有 CheckList、应急预案、沟通记录,归档至 Confluence “风控模型上线 SOP” 页面
    • 更新内部 Wiki:“小微企业贷前模型” 特征字典,明确每个字段的 SLA、fallback 策略、owner

这个 72 小时,不是一次上线,而是一次组织能力的淬炼。它证明: 生产 ML 的稳定性,不取决于某个天才算法,而取决于团队对每一个细节的敬畏和对每一次异常的肌肉记忆

5. 常见问题与排查技巧实录:那些凌晨三点教会我的事

在真实生产环境中,问题从不按教科书出现。下面是我和团队在过去三年里,被反复暴击后总结的“高频故障速查表”。每一条,都带着咖啡渍和黑眼圈。

5.1 故障一:模型服务 P95 延迟突增 300%,但 CPU/Memory 一切正常

现象 :监控显示 model-service-p95-latency-ms 从 85ms 飙升至 420ms,持续 2 小时。服务器资源(CPU、内存、网络)无异常,日志无 ERROR。

排查路径

  1. 首先排除网络: curl -w "@curl-format.txt" -o /dev/null -s http://model-svc/predict ,查看 time_connect time_starttransfer 。若 time_connect 高,则是 DNS 或服务发现故障。
  2. 若网络正常,检查特征服务: curl http://feature-svc:8080/metrics | grep latency 。我们曾因此发现特征服务的 Redis 连接池耗尽( redis_pool_wait_time_ms P95 达 300ms)。
  3. 最终根因:特征服务 SDK 的 max_connections 配置为 10,而模型服务并发请求为 50,导致 40 个请求排队等待连接。

解决方案

  • 立即扩容: kubectl scale deploy/feature-svc --replicas=5
  • 长期修复:SDK 配置中 max_connections 设为 min(100, 2 * expected_qps) ,并加入连接池使用率监控

提示:永远先查依赖服务,再查自身。模型服务只是“最后一公里”,问题往往藏在上游。

5.2 故障二:模型打分分布整体左移,但 AUC 未明显下降

现象 score_p50 从 65 降至 52, score_p90 从 88 降至 75,但离线 AUC 仍为 0.78。业务方抱怨“模型变保守了”。

排查路径

  1. 检查输入数据分布: SELECT percentileCont(score, 0.5) FROM model_output WHERE date = today() vs yesterday() 。确认是整体偏移,非局部异常。
  2. 检查关键特征漂移:重点看 biz_revenue_3m tax_payment_12m 等强相关特征。我们发现 biz_revenue_3m 的 P50 从 150000 降至 98000。
  3. 深入业务:联系客户经理,得知近期经济下行,小微企业营收普遍下滑。模型“诚实”地反映了这一现实,但业务策略未同步调整。

解决方案

  • 短期:调整决策阈值(如将拒绝阈值从 60 降至 55),缓冲业务影响
  • 长期:启动“经济周期适配”项目,在特征工程中加入宏观指标(如 PMI 指数)作为调节因子

注意:模型没有错,错的是我们期望它“无视现实”。生产 ML 的本质,是让模型成为业务的镜子,而非滤镜。

5.3 故障三:人工 override 后,模型下次仍对同一用户打低分

现象 :客户经理 override 通过一位客户,但 2 小时后该客户再次申请,模型依然拒绝。

排查路径

  1. 检查 override 日志:确认 override_decision_id 是否正确关联原始决策。
  2. 检查特征更新: SELECT * FROM feature_store WHERE user_id = 'U123456' AND feature_name = 'biz_revenue_3m' 。我们发现 override 未触发特征更新。
  3. 根因:override 逻辑只写入 override_log 表,但未调用特征平台的 update_feature API。

解决方案

  • 紧急修复:补发特征更新请求
  • 长期加固:在 override 服务中,将“写日志”和“更新特征”封装为原子事务(使用 Saga 模式)

提示:任何人工干预,必须同步刷新模型的认知。否则,系统永远在“重复犯错”。

5.4 故障四:新模型上线后,某小众客群(如“西藏地区个体户”)拒绝率飙升 300%

现象 :全量发布后,监控发现 region=tibet 的拒绝率从 15% 涨至 62%,而该客群仅占总流量 0.3%,未触发全局告警。

排查路径

  1. 启用“多维下钻”:在 Grafana 中,将 reject_rate region business_type age_group 维度切分。
  2. 定位问题组合: region=tibet AND business_type=individual 的拒绝率 98%。
  3. 检查训练数据:发现训练集中该组合样本仅 12 例(< 0.01%),模型未学会其模式。

解决方案

  • 立即对该客群启用规则兜底(如“西藏个体户,若经营年限>3年,则自动通过”)
  • 启动专项数据收集

更多推荐