一个外卖后端的机器学习初体验:从懵圈到跑通第一个模型

生产环境勿扰
2025-12-27 11:11
阅读 1100

去年双11前夜,我正盯着美团外卖订单系统的监控大盘,突然被拉进一个叫“智能调度预研”的钉钉群。产品经理老张发了句:“兄弟们,咱们得搞点AI,提升骑手调度效率!”——我当时差点把咖啡喷在键盘上。我是干Java高并发的,不是搞算法的啊!

但领导一句“你不是喜欢折腾新技术吗?”,直接把我按在了火线上。行吧,谁让我平时在GitHub上刷Transformer源码装逼呢……结果现实狠狠打了脸:连sklearn的fit()方法都调不明白。

不过三个月下来,还真跑通了第一个能用的模型。今天就以一个杭州普通Java后端的身份,和大家聊聊我在工作之余摸爬滚打学机器学习的经历。不讲数学推导,只说人话,尤其适合像我这种“被迫转AI”的后端仔。

为什么外卖系统需要机器学习?

先说背景。我们团队负责的是订单-骑手匹配的综合调度系统。传统做法是基于规则:比如距离最近、负载最轻、历史接单率高等。但这些规则在大促期间(比如618、双11)经常失效——因为真实世界太复杂了。

举个例子:某个商圈突然涌入5000单,系统按“最近原则”派给附近3公里内的骑手。结果呢?骑手全堵在路上,超时率飙升,用户差评如潮。PM又来找我:“能不能预测一下未来15分钟哪个区域会爆单?提前调骑手过去?”

这需求,纯靠if-else写规则根本扛不住。于是,我们决定引入时间序列预测 + 分类模型,做区域热度预测和骑手意愿预估。

第一个坑:数据比代码难搞十倍

我原以为,机器学习就是调库、训练、部署。结果光数据清洗就花了两周。

我们的原始数据包括:

  • 历史订单(时间、经纬度、品类、价格)
  • 骑手GPS轨迹
  • 天气API数据
  • 节假日标记

但这些数据分散在Hive、Kafka、MySQL里,格式五花八门。更惨的是,有些字段缺失率高达40%(比如用户取消原因),还有大量异常值(比如骑手速度显示300km/h)。

我一度想放弃,直到运维大哥甩给我一句:“你不是天天吹微服务治理牛逼吗?数据治理不也是治理?”——扎心了。

最后,我们用Spark做ETL,把特征工程封装成一个独立模块,输出标准化的DataFrame。关键特征包括:

特征类型 具体字段 说明
时间特征 小时、星期几、是否节假日 捕捉周期性
空间特征 网格ID(将城市划分为500m×500m格子) 降维+泛化
订单特征 过去15分钟订单量、平均客单价 反映区域热度
外部特征 温度、降雨概率、PM2.5 影响骑手/用户行为

注:这里没用原始经纬度,而是做了空间离散化。否则模型容易过拟合,而且线上推理慢。

选模型:别一上来就搞Transformer

很多新人(包括当初的我)一听说“AI”,就想去搞深度学习。但在实际业务中,简单模型往往更稳

我们对比了三种方案:

  1. 线性回归 + 特征交叉:快、可解释,但表达能力弱
  2. XGBoost:精度高、抗噪强、支持缺失值
  3. LSTM:理论上适合时序,但训练慢、调参玄学

最终选了XGBoost。为啥?因为我们是产品驱动型团队,第一版必须快速上线验证效果。XGBoost训练快(单机10分钟搞定),特征重要性还能反哺产品逻辑(比如发现“雨天对奶茶订单影响极大”)。

代码其实就几行(用Python,别骂我背叛Java):

import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error

# 加载处理好的特征数据
X, y = load_preprocessed_data()

# 划分训练/测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 训练模型
model = xgb.XGBRegressor(
    n_estimators=300,
    max_depth=6,
    learning_rate=0.1,
    subsample=0.8
)
model.fit(X_train, y_train)

# 评估
preds = model.predict(X_test)
mae = mean_absolute_error(y_test, preds)
print(f"MAE: {mae:.2f} 单/15分钟")  # 实际业务指标

重点来了:不要只看accuracy! 在外卖场景,我们更关心绝对误差(MAE)高负载区域的预测偏差。因为少预测100单可能只是多等5分钟,但多预测100单会导致骑手闲置、成本上升。

调优心得:特征比模型更重要

调参一周后,我发现调max_depth从5到7,MAE只降了0.5。但加了一个“周边3公里竞对门店数”特征,MAE直接降了3!

这让我想起阿里P8说过的话:“在工业界,80%的效果来自特征工程,15%来自数据质量,5%才是模型本身。

所以我们花了大力气做特征交叉,比如:

  • 是否雨天 × 是否晚餐时段 → 捕捉极端场景
  • 历史接单率 × 当前在线骑手数 → 反映供需关系

还用SHAP值分析特征贡献:

import shap
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test[:100])
shap.summary_plot(shap_values, X_test[:100])

结果发现,“过去15分钟订单增速”比“绝对订单量”更重要——这直接改变了产品策略:调度系统不再只看当前单量,而是看增长趋势。

上线后的效果与反思

模型上线后,我们在杭州西溪园区做了A/B测试:

指标 规则系统 ML模型 提升
平均配送时长 28.5 min 26.1 min ↓8.4%
骑手空驶率 22.3% 18.7% ↓16.2%
用户差评率 1.8% 1.5% ↓16.7%

虽然数字看起来不错,但初期也翻过车。有一次模型把“暴雨预警”误判为“小雨”,导致骑手调度不足,当天超时订单暴涨。后来我们加了人工兜底规则:当天气API置信度<0.7时,强制走保守策略。

这也让我明白:ML不是银弹,而是产品综合能力的一环。它必须和现有系统融合,有监控、有回滚、有业务兜底。

给后端同学的建议

如果你和我一样,是个Java后端,被赶鸭子上架搞AI,别慌:

  1. 先搞懂业务目标:你的模型要优化什么指标?成本?体验?还是GMV?
  2. 从小模型开始:别一上来就搞BERT、Diffusion。XGBoost、LightGBM够用90%场景。
  3. 重视数据管道:模型再牛,喂垃圾数据=垃圾输出。
  4. 和产品对齐预期:告诉他们“AI不是魔法”,需要迭代验证。
  5. 保持Java人的务实:能用规则解决的,别硬上模型。稳定压倒一切。

现在我已经能和算法同事正常交流了(虽然还是听不懂他们的数学公式)。上周五晚上,看着监控大盘上平稳下降的配送时长曲线,终于觉得那几周熬夜查资料、调参数、被PM催的日子,值了。

顺便说一句,网易最近在招AI工程化的人,要是你也在杭州,搞不好哪天就在文三路咖啡馆碰上了。到时候,请我喝杯瑞幸,咱聊聊特征交叉那些事儿?

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝