从“踩坑”到“填坑”:我的一次技术探索与实践之旅
起因:一个看似简单的推荐功能升级

去年我在一家内容社交平台做推荐系统的研发工作。当时我们团队接到一个需求:在用户的首页信息流中,新增一个「根据用户兴趣动态生成的长文精选」板块。这个功能听起来不算复杂——不就是把用户可能喜欢的优质长文挑出来展示嘛?
但当我们真正开始落地时,才发现事情没那么简单。原本的设计是基于标签匹配和热度加权打分来实现排序,上线后效果还不错。但一段时间之后,用户反馈说“文章都太相似了”,甚至有些人觉得推送的内容越来越低质。运营也发现,点击率虽然稳定,但互动率却在下滑。
这说明什么呢?说明我们的推荐逻辑太过依赖已有特征,没有很好地捕捉到用户深层次的兴趣变化,也没有对内容质量进行有效过滤。于是,我们决定尝试引入语义模型来增强推荐系统的内容理解能力。
抉择:要不要自己训练模型?

最开始,我们考虑直接接入一个大厂提供的API服务,比如阿里云、腾讯云、百度AI平台等,这些都有现成的语义向量接口。好处是上手快、维护少;坏处也很明显:成本高、响应延迟波动大,而且模型不可控,难以适配自己的业务场景。
最终我们决定:“要不咱们试试自己训练个轻量级的语义理解模型吧”。毕竟,在公司内部我们有自己的数据积累和用户画像资源,如果能结合业务场景定制模型,说不定可以更贴合实际需求。
目标很明确:构建一个能够理解用户阅读行为和文章语义之间的关联模型,帮助现有推荐系统提升多样性与相关性。
实践过程:从0到1搭建语义推荐模块

第一阶段:语义编码器的选型
我们选择了Hugging Face上的Sentence-BERT作为基底模型,它的预训练版本paraphrase-MiniLM-L6-v2体积小、推理快、准确度也不错,适合部署在线上环境。
我们在其基础上做了两件事:
- Fine-tuning:使用我们平台的热门高质量文章及其阅读路径数据(用户浏览A→B→C)进行微调。
- 蒸馏+压缩:用知识蒸馏方法将模型进一步压缩为仅含前4层Transformer的小模型,用于线上低延迟推理。
代码片段示例(加载与微调):
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
train_samples = [
InputExample(texts=[sent1, sent2], label=1.0) # 示例数据对
for (sent1, sent2) in pairs_data
]
train_dataloader = DataLoader(train_samples, shuffle=True, batch_size=16)
train_loss = losses.CosineSimilarityLoss(model)
model.fit(
train_objectives=[(train_dataloader, train_loss)],
epochs=3,
warmup_steps=100,
output_path='./fine_tuned_model/'
)
第二阶段:模型如何接入线上系统?
模型训练好只是第一步,真正的挑战在于工程化落地。我们需要处理以下几个问题:
1. 模型服务化部署
我们选择使用Python + FastAPI + ONNX Runtime的方式构建了一个本地的语义向量服务。
from fastapi import FastAPI
from model_service import EmbeddingModel
app = FastAPI()
embedding_model = EmbeddingModel('./onnx_model/')
@app.post("/encode")
async def encode_text(items: List[str]):
vectors = embedding_model.encode_batch(items)
return {"vectors": vectors.tolist()}
2. 缓存机制优化性能
考虑到每次请求都重新跑模型效率太低,我们在Redis中做了缓存策略,缓存键是文章正文的MD5值。这样即使相同的文章重复出现,也能快速提取向量。
3. 和线上推荐系统集成
我们将新模型的输出结果作为推荐排序中的一个新的feature维度(和其他特征一起喂给GBDT/XGBoost),提升了整体的召回质量和多样性指标。
踩坑记录:那些你不得不面对的“魔鬼细节”


坑点1:模型预测结果忽好忽差?
我们初期部署后的线上实验效果不稳定,有时效果很好,有时反而不如原来的老模型。排查发现,是因为训练数据中存在大量低质量噪声样本(例如爬取的乱七八糟的文本段落)。这导致模型学偏了。
解决办法:
- 使用平台已有的人工审核文章+用户收藏数据,构造高质量pair;
- 对输入文本做标准化清洗(去除HTML标签、广告词、垃圾字符等);
- 引入置信度评分,低于某个阈值的自动丢弃或降权。
坑点2:服务吞吐量上不去,CPU爆满?
模型服务一开始只用了CPU推理,每个batch 8条左右的并发就撑不住了。后来我们通过以下方式优化:
- 将模型转成ONNX格式,利用ONNX Runtime自带的优化功能;
- 加入异步批处理任务队列(如Celery + Redis broker);
- 在Kubernetes中部署多个副本实现负载均衡。
坑点3:模型更新频率不好控制?
模型训练好后,我们发现如果不定期更新,会因为热点话题的变化导致语义漂移。为此,我们制定了一套自动化流程:
- 每周从线上抓取最新的阅读序列数据;
- 自动打标并筛选正负样本;
- 启动定时训练Job,训练完成后推送到测试环境评估;
- 评估达标后灰度上线,逐步扩大流量覆盖范围。
效果总结:从踩坑到见成效
经过大约两个月的迭代与打磨,整个语义推荐模块终于落地并取得了不错的成果:
| 指标 | 上线前 | 上线后 |
|---|---|---|
| 推荐多样性(Shannon熵) | 1.2 | 1.9 |
| 用户点击率CTR | 5.7% | 6.1% |
| 单次停留时间 | 35s | 42s |
| 用户反馈负面评价数 | 明显较多 | 显著减少 |
最重要的是,用户画像的准确性有了显著提升,系统可以更智能地识别出他们潜在感兴趣的内容方向,而不再局限于过去的行为统计。
经验分享:写给正在“路上”的你
如果你也在做类似的技术探索,想用一些新的模型或者算法来优化现有的产品体验,我有几点建议想跟你分享:
✅ 1. 一定要结合业务场景训练模型
很多时候,通用模型的表现并不够好,尤其当你面对的是非标准文本时。别怕折腾,哪怕做个简单的Fine-tune,也可能带来意想不到的效果。
✅ 2. 工程化落地比模型本身更重要
再好的模型,如果没有良好的服务架构支撑,很难发挥价值。建议尽早设计缓存、异步处理、容错机制,并预留监控入口。
✅ 3. 数据质量永远是第一位的
无论你多牛的模型,喂的数据不行,结果也会“烂”。花点时间去筛选、标注、校验数据,值得!
✅ 4. 要敢于试错、也要及时止损
我们在过程中曾尝试过其他模型,比如Contriever、SimCSE等,但最终还是回归到了SBERT的生态。选型的时候要广撒网,但一旦发现不合适就要果断砍掉,别浪费时间和资源。
✅ 5. 技术不是万能的,人机协同才更有效
即使是现在,我们也保留了一部分运营侧的人工干预机制。有些冷启动内容、特殊节日专题,还是要靠编辑参与才能更好触达用户。
写在最后:每一份“坑”,其实都是成长的礼物

回顾这段经历,确实充满了各种不确定性和技术挑战。有时候半夜还在查日志、改参数、看埋点,也曾因为一次AB测试没达标而灰心丧气。但正是这些真实的“坑”,逼着我们去深入思考每一个环节,也让我们真正理解了“落地难”的含义。
技术这条路,从来都不是平坦无阻的。但我相信,只要保持热情、坚持实践、持续学习,我们终将在一次次“踩坑”中成长,成为那个能把想法变成现实的开发者。
如果你也有类似的经历,欢迎留言交流。愿我们都成为既能写代码,也能讲故事的技术人。

评论 0