一个创业公司全栈的AI调优踩坑实录:从跑不通到上线产品
上周五晚上十点半,我盯着屏幕上那行“OOM killed”的报错,差点把咖啡杯捏碎。这已经是我这周第三次被模型训练搞崩溃了——而明天就是产品demo日。作为一名刚入职两个月的创业公司全栈开发(对,就是那种既要写前端、又要搭后端、还得临时客串算法工程师的“万金油”),我原本以为自己只是来维护个用户系统,结果因为老板一句“你不是会Python吗?试试搞个智能推荐”,就一脚踩进了AI的深水区。
说实话,两个月前我对“调参”这个词的理解还停留在调节空调温度。但现实很骨感:我们这个只有8个人的小团队,没有专职算法岗,产品经理又天天追着问“为什么竞品能猜中用户想买什么,我们连首页banner都推不准?”——于是,学习AI、训练模型、优化效果,就成了我这个“啥都会一点”的全栈不得不啃下的硬骨头。
今天这篇,不讲高大上的理论,就聊聊我这两个月在真实产品场景里摸爬滚打出来的实战经验。希望能帮到那些和我一样被逼上梁山的兄弟们。
项目背景:一个小电商的“智能”困境
我们做的其实是个垂直领域的轻量级电商平台,用户量不大,但老板坚信“AI是未来”。最初的需求很简单:基于用户浏览和购买历史,给首页做个性化商品推荐。听起来人畜无害,对吧?
但问题来了:
- 数据量小得可怜:两个月积累下来,有效行为日志不到10万条
- 标签稀疏:很多用户只点不买,正样本极少
- 资源有限:服务器是租的4核8G云主机,GPU?不存在的
产品经理最初甩过来一个“参考方案”:用BERT做用户意图理解。我当时就懵了——这玩意儿动辄上亿参数,在我的破机器上跑一轮预训练怕是要等到公司倒闭。于是我反手给了他一个白眼:“要不你先给我申请个A100集群?”
最后我们达成共识:从简单有效的模型开始,快速验证,再迭代优化。这才是小团队的生存之道。
第一步:别一上来就上大模型,先跑通再说
我选了最朴实的协同过滤(Collaborative Filtering) 作为baseline。理由很简单:实现快、解释性强、对数据要求低。用surprise库,30行Python代码就能跑起来:
from surprise import Dataset, Reader, SVD
from surprise.model_selection import train_test_split
# 构造数据
reader = Reader(rating_scale=(1, 5))
data = Dataset.load_from_df(df[['user_id', 'item_id', 'rating']], reader)
trainset, testset = train_test_split(data, test_size=0.2)
# 训练SVD模型
algo = SVD(n_factors=50, n_epochs=20, lr_all=0.005, reg_all=0.4)
algo.fit(trainset)
predictions = algo.test(testset)
结果?RMSE 1.2,勉强能用。但问题很快暴露:冷启动严重——新用户或新品根本推不出来。产品经理看了直摇头:“这跟随机推荐有啥区别?”
于是,我决定引入内容特征,转向混合推荐。这时候,LightGBM成了我的救星。它支持类别特征、训练快、内存友好,特别适合我们这种资源紧张的场景。
关键代码片段:
import lightgbm as lgb
# 特征工程:用户画像 + 商品属性 + 交叉特征
X_train = build_features(user_df, item_df, interaction_df) # 自定义特征构造函数
y_train = interaction_df['is_click'] # 二分类目标
train_data = lgb.Dataset(X_train, label=y_train)
params = {
'objective': 'binary',
'metric': 'auc',
'boosting_type': 'gbdt',
'num_leaves': 63,
'learning_rate': 0.05,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'verbose': -1
}
model = lgb.train(params, train_data, num_boost_round=500,
valid_sets=[train_data], early_stopping_rounds=50)
这里有个血泪教训:千万别直接用原始ID当特征! 我一开始把user_id和item_id直接喂进去,结果模型过拟合到飞起,线上AUC还不如随机。后来改用embedding预训练+平均池化,才稳住。
调优的核心:不是调参,而是调数据
很多人以为AI调优就是疯狂试learning_rate、batch_size这些超参。但在我这个项目里,最大的提升来自数据层面的处理。
1. 样本不平衡?来点SMOTE不行,试试负采样!
我们的点击率不到2%,典型的正负样本极度不平衡。我试过SMOTE过采样,结果模型在测试集上AUC飙升,一上线就崩——因为生成的“假正样本”根本不合理。
后来改用动态负采样:对每个正样本,从用户未交互但同品类的商品中随机采5个负样本。这样既保留了业务逻辑,又缓解了不平衡。
def generate_negative_samples(user_interactions, all_items, sample_ratio=5):
negatives = []
for user, pos_items in user_interactions.items():
candidate_negs = list(set(all_items) - set(pos_items))
sampled_negs = random.sample(candidate_negs, min(sample_ratio * len(pos_items), len(candidate_negs)))
for item in sampled_negs:
negatives.append((user, item, 0))
return negatives
AUC从0.72提升到0.81,效果立竿见影。
2. 特征工程比模型结构更重要
我花了一整周时间折腾Transformer,结果发现加一个“用户最近7天点击品类的多样性指数”特征,效果提升比换模型还大。这让我深刻体会到:在中小数据集上,好的特征 > 复杂的算法。
我们最终使用的特征包括:
- 用户侧:活跃天数、平均停留时长、偏好品类熵值
- 商品侧:价格分位数、上新天数、历史CTR
- 交叉特征:用户对该品类的历史转化率、商品在用户地域的热销度
超参调优:别手动试了,让机器干
早期我傻乎乎地手动改参数、跑实验、记Excel,效率低到令人发指。直到我发现了Optuna——一个超参自动优化库,简直是我的救命稻草。
import optuna
def objective(trial):
params = {
'objective': 'binary',
'metric': 'auc',
'boosting_type': 'gbdt',
'num_leaves': trial.suggest_int('num_leaves', 31, 127),
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.1),
'feature_fraction': trial.suggest_float('feature_fraction', 0.6, 0.9),
'bagging_fraction': trial.suggest_float('bagging_fraction', 0.6, 0.9),
'min_data_in_leaf': trial.suggest_int('min_data_in_leaf', 10, 100),
'verbose': -1
}
model = lgb.train(params, train_data, num_boost_round=1000,
valid_sets=[valid_data], early_stopping_rounds=50, verbose_eval=False)
auc = lgb.eval_valid(model, valid_data)['auc']
return auc
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
print("Best params:", study.best_params)
跑了一晚上,AUC又涨了0.03。虽然不多,但在推荐场景里,AUC每提升0.01,GMV可能涨好几个百分点——产品经理终于露出了满意的笑容。
上线后的监控:模型也会“变质”
本以为训练完就万事大吉,结果上线第三天,运维小哥冲过来喊:“推荐点击率暴跌!” 我一看监控,模型预测分布偏移了——原来是因为双11大促,用户行为模式突变。
这让我意识到:AI模型不是一劳永逸的产品,而是需要持续维护的服务。
我们紧急加了两道防线:
- 数据漂移检测:每天计算特征均值/方差变化,超过阈值告警
- 在线A/B测试:新模型必须经过7天对比实验才能全量
现在我们的部署流程是:
训练 → 离线评估(AUC, Recall@K) → 小流量A/B测试 → 监控7天 → 全量
效果对比:数字不说谎
| 阶段 | 模型 | AUC | 首页点击率 | 人均GMV |
|---|---|---|---|---|
| 初始 | 随机推荐 | 0.50 | 1.2% | ¥23.5 |
| V1 | 协同过滤 | 0.68 | 2.1% | ¥31.8 |
| V2 | LightGBM(基础特征) | 0.75 | 2.8% | ¥38.2 |
| V3 | LightGBM(优化特征+负采样) | 0.83 | 4.3% | ¥52.6 |
虽然离“精准推荐”还有距离,但对一个两人月投入的小项目来说,已经超额完成KPI。老板甚至说要给我加鸡腿(虽然最后只加了需求)。
写在最后:全栈搞AI,痛并快乐着
回看这两个月,我踩过的坑能填满一个游泳池:
- 因为没做特征归一化,导致GBDT训练发散
- 把测试集信息不小心泄露到训练集,AUC虚高
- 在Docker里跑训练,忘了挂载数据卷,跑完发现模型丢了……
但每一次debug成功,看到线上指标上涨,那种成就感是纯CRUD给不了的。
如果你也和我一样,是一个被逼着搞AI的全栈,我想说:别怕从简单开始,别迷信大模型,数据和业务理解才是核心。Python生态足够强大,LightGBM、XGBoost、Scikit-learn这些工具,配合合理的工程实践,完全能在资源有限的情况下做出有价值的产品。
至于那些高大上的Transformer、Diffusion Model?等我先把眼前这个推荐系统跑稳了再说吧。毕竟,产品经理又在群里@我:“能不能顺便加个AI客服?就用你们刚学的那个ChatGPT……”
(完)
P.S. 本文所有代码已在内部GitLab开源,欢迎同事提PR(求别写太烂,我还要维护)。下周技术分享会我会详细讲特征工程部分,带奶茶来听,管够。

评论 0