边哄睡娃边调模型:一个全职妈妈眼中的机器学习部署实战

半夜部署日记
2025-12-17 10:11
阅读 1510

凌晨1点23分,刚把哭闹的小祖宗哄睡,蹑手蹑脚回到书桌前,打开电脑继续调试昨天没跑完的模型。这大概就是我——一个坐标上海、租住在公司步行15分钟范围内的全职妈妈程序员的日常。白天带娃,晚上写代码,中间穿插着和产品经理“友好沟通”、和运维“互相甩锅”,以及无数次在ChatGPT里输入“为什么我的Docker镜像又崩了”。

最近被逼着搞了个机器学习模型上线项目,说是要优化我们电商平台的商品推荐效果。领导原话是:“双11快到了,你不是懂算法吗?赶紧上个模型,提升GMV!” 我心里翻了个白眼:懂算法 ≠ 会部署啊!但谁让我是个“能者多劳”的打工人呢?于是,这场从Jupyter Notebook到生产环境的长征,就此开启。


起点:别以为训练完模型就万事大吉

很多人(包括曾经的我)有个迷思:只要模型在本地跑得准,线上效果肯定差不了。天真!上周五晚上我就栽了个大跟头。

我们用的是经典的协同过滤+LightGBM融合方案,离线AUC做到0.89,看起来美滋滋。结果一部署到测试环境,前端同事反馈:“用户看到的全是袜子和拖鞋,哪怕他刚买了婴儿奶粉。” 我当场石化——这哪是推荐,这是精准踩雷!

后来排查发现,数据漂移惹的祸。训练用的是三个月前的历史日志,而线上用户行为已经因为618大促发生了结构性变化。更坑的是,特征工程里的时间窗口硬编码了last_30_days,但线上调度任务配置错了时区,实际只用了最近3天的数据……运维大哥还一脸无辜:“你没说要UTC+8啊?”

血泪教训:模型上线不是终点,而是运营闭环的起点。没有持续监控和反馈机制,再牛的算法也会变成“线上玄学”。


部署选型:快 vs 稳 vs 省钱

我们团队小(就我和另一个后端兼职搞ML),预算紧(老板说“先跑起来再说”),deadline近(双11倒计时45天)。这种情况下,我果断放弃了自建Kubernetes集群的幻想——别说搭了,光是看文档就能把我熬成地中海。

最终选了 FastAPI + Docker + AWS Lambda 的轻量组合:

  • FastAPI:Python系,写起来快,自动生成OpenAPI文档,前端同事夸我“终于有人考虑他们对接成本了”
  • Docker:隔离环境,避免“在我机器上能跑”的经典背锅现场
  • Lambda:按需计费,流量低谷期几乎不花钱,适合我们这种非核心推荐场景

当然,也试过Flask,但性能压测时QPS一高就内存泄漏,差点被运维拉黑。FastAPI的异步支持真香,尤其配合asyncio.gather批量处理请求,响应时间直接砍半。

# 简化版推理接口
from fastapi import FastAPI
import joblib

app = FastAPI()
model = joblib.load("model.pkl")

@app.post("/predict")
async def predict(user_features: dict):
    # 这里省略特征预处理...
    pred = model.predict([user_features])
    return {"recommendation": int(pred[0])}

不过要注意:别把整个scikit-learn塞进Lambda!冷启动延迟能让你怀疑人生。后来我把模型序列化成joblib(比pickle小40%),再配合Lambda的Provisioned Concurrency,首屏加载终于从3秒降到400ms。


和前端的“相爱相杀”:接口设计的艺术

说到前端,真是又爱又恨。爱的是他们总能第一时间发现UI bug(比如把商品ID显示成“None”);恨的是他们总想“顺便”加个需求:“能不能返回商品图片URL?现在只有ID不好展示。”

一开始我直接扔了个JSON过去:

{"item_id": "12345"}

结果前端小哥幽幽地说:“我们还要查数据库拿图片、价格、库存……你这等于让我多发三次请求。” 行吧,我认输。

于是重构接口,把运营需要的字段一次性打包

{
  "item_id": "12345",
  "name": "有机棉婴儿连体衣",
  "price": 89.9,
  "image_url": "https://cdn.xxx/12345.jpg",
  "in_stock": true,
  "algo_score": 0.92  // 方便AB测试追踪
}

这里有个小心机:保留algo_score字段。运营同学可以用它做灰度发布——比如只对score>0.9的用户展示新推荐,万一翻车也能快速回滚。事实证明这招救了我们:双11当天凌晨发现某类目CTR暴跌,立马切回旧策略,损失控制在5%以内。


算法与运营的“联合作战”

很多人觉得算法工程师只要调参就行,但现实是:不懂业务的算法都是耍流氓

我们最初用AUC评估模型,但上线后发现点击率涨了,转化率却没变。和运营复盘才发现:模型太“贪心”,总推高毛利商品(比如尿布台),但新妈妈们其实更需要高频消耗品(纸尿裤)。于是我们调整了损失函数,加入业务权重

# 自定义损失函数:高转化商品权重x2
def weighted_logloss(y_true, y_pred, weights):
    return -np.mean(
        weights * (y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
    )

# weights 根据商品类目动态设置

另外,AB测试必须做细粒度分桶。别信“随机分流50%用户”这种鬼话!我们按用户生命周期分层:新客、活跃老客、沉默用户各自独立实验。结果发现:新客对个性化推荐无感(可能还没行为数据),但老客CTR提升18%。运营立刻调整策略:新客走热门榜,老客走个性化——GMV直接拉高7%。


监控:别等线上炸了才看日志

最开始我只监控了服务是否存活(ping一下接口),直到某天半夜报警:模型输出全是0

原因是上游数据管道挂了,特征缺失导致模型fallback到默认值。但因为我们没监控输入特征分布,整整两天都在推“空气商品”。

痛定思痛,加了三层监控:

监控层级 工具 关键指标
服务健康 Prometheus + Grafana QPS、延迟、错误率
数据质量 Great Expectations 特征空值率、分布偏移
业务效果 自建Dashboard CTR、转化率、人均曝光数

特别安利 Evidently AI 这个库,一行代码生成数据漂移报告:

from evidently.report import Report
from evidently.metrics import DataDriftTable

drift_report = Report(metrics=[DataDriftTable()])
drift_report.run(reference_data=train_df, current_data=prod_df)
drift_report.save_html("drift.html")

现在每天早上第一件事就是看这份报告——比刷朋友圈还勤快。


给 fellow 妈妈程序员的碎碎念

写到这里,窗外天都快亮了。回头看这段部署经历,最大的感悟是:机器学习部署不是纯技术活,而是技术+运营+沟通的混合体

如果你也在带娃间隙啃代码,记住几点:

  1. 别追求完美架构:小团队先跑通MVP,再迭代。我见过太多人卡在“要不要上TF Serving”纠结一周,结果deadline到了模型还在本地。
  2. 让非技术角色参与进来:运营、前端、甚至客服,他们的反馈比任何指标都真实。我们客服小姐姐一句话点醒我:“用户投诉推荐重复”——原来去重逻辑漏了跨会话场景。
  3. 善用AI工具但别依赖:ChatGPT帮我写了80%的Dockerfile,但最后那个--no-cache-dir参数还是靠自己查文档搞定的。AI是副驾驶,方向盘得握在自己手里。

最后,分享一句深夜debug时的座右铭:“模型可以崩,头发不能秃;线上可以炸,娃不能哭。”

(完)

P.S. 刚写完这篇,娃又醒了。得去泡奶了——希望你们的部署之路,比我少踩几个坑。

评论 0

最热最新
暂无评论
半夜部署日记Lv.1
0
影响力
0
文章
0
粉丝