调参调到怀疑人生?一个DBA转后端的AI炼丹实录
去年双11前两周,我们组接了个“紧急需求”:给商品推荐系统加个实时热度预测模块。产品经理拍着胸脯说:“就一个小模型,跑跑历史点击数据就行,三天能上线吧?” 我当时差点把咖啡喷他脸上——你当AI是拼夕夕9.9包邮的插件吗?
但没办法,老板点头了,deadline钉死在10月28号。作为团队里唯一碰过点机器学习皮毛的人(其实是被逼着啃了几本《Hands-On Machine Learning》),这锅自然落我头上。
先自我介绍一下:我是个从DBA转岗的后端开发,在公司熬了三年半,日常和MySQL索引、慢查询日志、Redis缓存打架。最近在研究Rust,觉得它那套所有权机制比某些同事的代码规范都严谨(笑)。但说实话,搞AI模型训练?纯属赶鸭子上架。
数据不干净,神仙也难救
第一天我就踩进第一个大坑:数据质量。
我们有张 user_click_log 表,按理说记录了用户对商品的点击行为。但当我用SQL查了一下才发现:
SELECT
COUNT(*) AS total,
COUNT(DISTINCT user_id) AS unique_users,
MIN(click_time), MAX(click_time)
FROM user_click_log
WHERE click_time BETWEEN '2023-09-01' AND '2023-10-15';
结果:总记录2.3亿,但有效用户才80万——剩下全是爬虫和测试账号!更离谱的是,有些 click_time 居然是 2025 年的(运维小哥手抖改错了系统时间,还敢不上报)。
教训一:别信业务方说的“数据很干净”。
我花了整整一天写清洗脚本,剔除异常时间、过滤机器人流量、补全缺失的 item_category 字段。最后喂给模型的数据,只有原始量的37%。但准确率直接从0.48飙到0.69——这哪是调参,这是在给数据做“洗胃”。
算法选型:别被论文忽悠瘸了
一开始我雄心勃勃,想上Transformer。GitHub上搜了一圈,找到个star 5k+的 recsys-transformer 项目,clone下来跑demo,结果本地GPU爆显存,连batch_size=2都跑不动。
冷静下来一想:我们场景其实很简单——预测未来2小时某个商品是否会被点击超过阈值。这本质是个二分类问题,特征维度也不高(用户画像+商品属性+时间窗口统计量)。
于是果断切回老朋友:XGBoost。
为什么?三点理由:
- 可解释性强:产品经理问“为啥推这个商品”,我能指着feature importance说“因为用户昨天看了同类目3次”;
- 训练快:在我们10核CPU+32G内存的测试机上,全量训练只要8分钟;
- 对脏数据鲁棒:不像神经网络那样动不动就梯度爆炸。
我在GitHub上翻了几个开源推荐系统的issue区,发现不少团队其实在生产环境还是用GBDT系模型打底。所谓“SOTA模型”,很多时候只是论文里的玩具。
超参数调优:Grid Search是反人类设计
早期我傻乎乎地用sklearn的 GridSearchCV,设置如下:
param_grid = {
'n_estimators': [100, 200, 300],
'max_depth': [3, 5, 7, 9],
'learning_rate': [0.01, 0.05, 0.1, 0.2]
}
算一下组合数:3×4×4 = 48组。每组5折交叉验证,意味着要跑240次训练。在我那台破笔记本上,预计耗时19小时——而离上线只剩48小时。
当场裂开。
后来改用 Optuna 做贝叶斯优化,代码简洁到哭:
import optuna
def objective(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 500),
'max_depth': trial.suggest_int('max_depth', 3, 12),
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3),
'subsample': trial.suggest_float('subsample', 0.6, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0)
}
model = XGBClassifier(**params, random_state=42)
return cross_val_score(model, X_train, y_train, cv=3, scoring='roc_auc').mean()
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50) # 只跑50次,智能探索
50次试验,2小时搞定,AUC比Grid Search的最佳结果还高0.023。Optuna甚至给我画出了参数重要性图——原来 max_depth 影响微乎其微,反而是 colsample_bytree 最关键。
建议:除非你是学术研究,否则别用暴力搜索。 工程师的时间比算力贵。
GitHub不是万能药,但没它真不行
整个过程中,GitHub成了我的救命稻草。举几个例子:
- 特征工程灵感:参考了 recbole 的时间窗口统计方法,把“过去1小时点击次数”、“过去24小时点击增长率”作为动态特征;
- 评估指标陷阱:一开始用accuracy,结果模型全预测0(因为正样本只有8%)。后来在某个issue里看到有人提到“用PR AUC代替ROC AUC处理不平衡数据”,立马改了;
- 部署踩坑:模型训练完要用Rust服务调用,发现Python pickle序列化后的模型在Rust里读不了。最后用了 Treelite 导出成C代码再编译,才搞定跨语言部署。
但也要警惕:别盲目follow最新项目。 有个叫 AutoRecSys 的库,README写得天花乱坠,结果跑起来依赖冲突,issue区三个月没人回复。这种“僵尸项目”千万别碰。
效果对比:数字不会骗人
最终上线前,我们做了AB测试,对比旧规则引擎和新模型的效果:
| 指标 | 规则引擎 | XGBoost模型 | 提升幅度 |
|---|---|---|---|
| CTR (点击率) | 2.1% | 3.4% | +61.9% |
| GMV贡献 | ¥18.2万 | ¥29.7万 | +63.2% |
| 推荐多样性 (Shannon) | 1.8 | 2.5 | +38.9% |
最让我欣慰的是:线上QPS稳定在1200+,P99延迟<45ms。要知道,这可是纯CPU推理,没上任何GPU加速。
写在最后:DBA思维救了我
回头看这段经历,其实很多“AI调优”问题,本质还是数据工程问题。我的DBA背景反而成了优势:
- 知道怎么高效抽样、聚合、窗口计算;
- 对数据分布敏感,一眼看出长尾、偏态、缺失模式;
- 习惯写可复现的SQL脚本,而不是在Jupyter里乱敲临时代码。
现在我已经把整个流程封装成一个GitHub仓库:click-predictor(名字瞎起的),包含数据清洗、特征生成、模型训练、评估报告四部分。虽然代码糙了点,但至少能跑通。
至于跳槽?嗯,简历上终于能写“主导AI模型落地,提升GMV 63%”了(笑)。不过下次再有人跟我说“就一个小模型”,我可能真的会砸键盘。
毕竟,炼丹容易,炼好丹难;调参容易,调对参更难。
P.S. 如果你也在用XGBoost做推荐,记得试试
scale_pos_weight参数处理样本不平衡——这玩意儿比SMOTE香多了。

评论 0