一个外卖后端的机器学习初体验:从懵圈到跑通第一个模型
去年双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”,就想去搞深度学习。但在实际业务中,简单模型往往更稳。
我们对比了三种方案:
- 线性回归 + 特征交叉:快、可解释,但表达能力弱
- XGBoost:精度高、抗噪强、支持缺失值
- 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,别慌:
- 先搞懂业务目标:你的模型要优化什么指标?成本?体验?还是GMV?
- 从小模型开始:别一上来就搞BERT、Diffusion。XGBoost、LightGBM够用90%场景。
- 重视数据管道:模型再牛,喂垃圾数据=垃圾输出。
- 和产品对齐预期:告诉他们“AI不是魔法”,需要迭代验证。
- 保持Java人的务实:能用规则解决的,别硬上模型。稳定压倒一切。
现在我已经能和算法同事正常交流了(虽然还是听不懂他们的数学公式)。上周五晚上,看着监控大盘上平稳下降的配送时长曲线,终于觉得那几周熬夜查资料、调参数、被PM催的日子,值了。
顺便说一句,网易最近在招AI工程化的人,要是你也在杭州,搞不好哪天就在文三路咖啡馆碰上了。到时候,请我喝杯瑞幸,咱聊聊特征交叉那些事儿?

评论 0