短视频推荐系统这两年已经不算新鲜概念了,但真正从零开始用Python把它完整做出来,从数据清洗、特征工程、召回策略到排序模型,再到上线后的效果调优,整个链路里的细节远比想象中多。我最初以为核心难点就在算法选型上,做到后面才意识到,真正的门槛在于理解短视频场景下用户行为数据的稀疏性和强时序性,并据此设计一套既能快速迭代、又能保证稳定性的系统架构。
这篇文章更适合两类人看:一类是准备做推荐系统相关的课程设计或毕业设计的学生,可以把它当作一份完整的设计参考;另一类是想在公司内部从零搭建垂直场景推荐服务的开发同学,能从中找到工程落地的脚手架思路和避坑经验。我会从整体架构出发,按数据建模、召回、排序、工程实现、效果评估这条主线展开,重点讲清楚每一步为什么要这么做,以及实测中常见的坑在哪里。
1. 短视频推荐系统的整体定位:它和电商推荐、资讯推荐的本质差异
1.1 短视频场景的三个核心约束
先说结论:短视频推荐系统表面上是一套"物品推荐用户"的通用框架,但一旦进入具体设计,它的约束条件和传统电商或资讯推荐有明显差异。第一个约束是会话时长极短但频次极高。用户在刷视频时的单次交互可能只有几秒到十几秒,但一天内可能产生上百次曝光和反馈,这导致行为数据量上涨很快,但单条行为的信息密度很低。第二个约束是物品生命周期极短。一条短视频的热度窗口可能只有几小时到几天,两天后的推荐价值就急剧下降,这意味着系统必须对新鲜内容足够敏感,不能只依赖长期积累的静态标签。第三个约束是反馈信号强时序化。用户的兴趣变化可能在几分钟内发生,前一条视频是否完整看完直接决定了下一条视频的推荐走向,这要求推荐系统具备动态调整的能力,而非一次性算完静态列表。
1.2 设计目标:在"准"和"快"之间找平衡
基于上面三个约束,我在动手设计时把系统目标拆成四个维度:一是相关性,即推荐内容与用户当前兴趣的匹配程度;二是时效性,即能否把刚上传的优质视频迅速推给潜在感兴趣的人;三是多样性,避免用户刷十条视频全是同一个题材;四是冷启动友好度,新用户和新视频都不能被完全忽略。这四个维度其实是相互制衡的,比如过度追求相关性会导致多样性下降,过度追求时效性又可能让一些低质内容进入推荐流。
我在架构上最终选择了"召回-排序-重排"三段式结构,配合离线批量计算和在线轻量计算混跑的模式。离线部分负责处理用户长期偏好和内容的离线画像,在线部分处理实时行为和候选集的快速筛选。这个设计不是追求最前沿的算法效果,而是保证在单机或小集群条件下也能跑起来、能迭代、能看效果。
2. 数据基础:商品特征和用户行为怎么变成模型能吃的"向量"
2.1 短视频内容画像:不止是标题和标签
很多入门者会把内容画像简化成"分类加关键词",真正做下去才发现不够。短视频的内容特征至少包括三类:静态元信息、内容语义特征、统计消费特征。静态元信息就是标题、时长、清晰度、作者、发布时间、话题标签;内容语义特征需要从标题文本和封面描述里提取,我用的是基于中文分词后的关键词加权,得到类似"美食""教程""宠物"这类语义标签;统计消费特征则是这条视频在全体用户上的表现,比如总播放量、完播率、点赞率、评论率、分享率。
这三类特征在推荐中的作用完全不同。静态元信息解决的是基础过滤,比如你总不能把两小时的视频推给只想看短视频的用户;语义特征解决的是兴趣泛化,用户喜欢"做饭",那其他与"做饭"相关但不同分类的视频也有机会被召回;统计特征解决的是质量兜底,一个内容再对用户胃口,如果完播率极低、差评率极高,也不应该被推到前排。我在这块踩过一个明显的坑:最开始只用了静态标签作为内容特征,结果推荐结果永远停留在"用户看过的分类里",跨分类泛化能力很差。后来加上语义关键词和统计特征做加权融合,召回覆盖面才明显改善。
视频内容画像在工程上最终要落成一个向量,我用的方案是对每个特征字段分别编码再拼接。标签字段通过哈希映射成固定维度的稀疏向量,语义关键词通过预训练的文本向量模型转成稠密向量,统计特征经过归一化后直接拼接。这里要注意不同特征的量纲差异非常大,比如完播率是0到1之间的浮点数,播放量可能是几十万级别的整数,所以归一化是必须的,否则后续相似度计算会被大数特征主导。
2.2 用户行为建模:完播、点赞、评论的权重怎么定
用户行为是推荐系统里最宝贵的数据,但"行为"不等于"正反馈",我见过不少半路出家的项目把点赞、评论、收藏全部当成相同权重的正样本,这会导致推荐系统被极端用户带偏。我在实际项目中采用的策略是给行为分层:完播率视作弱正反馈,点赞和收藏视作中等正反馈,评论和分享视作强正反馈,而"滑过不看"和"点了不感兴趣"则视作负反馈。不同行为的权重差异通过加权公式体现,简单理解就是:
用户对视频的综合兴趣分 = 0.1×完播信号 + 0.3×点赞信号 + 0.4×收藏信号 + 0.2×评论分享信号 - 0.3×负反馈信号
这个权重并不是一成不变的,需要根据业务反馈做调整。比如某段时间发现评论区出现大量争吵或无关内容,说明评论信号被恶意使用,就要降低它的权重。
在实现这个加权公式时,我建议把行为日志先按用户和视频聚合,而不是直接一条条用原始日志训练模型。原因很简单:一条行为日志只反映一次曝光结果,而同一用户对同一视频可能有多条曝光记录,如果全量送进模型,会放大高频曝光视频的权重。聚合后再计算用户对某个视频的综合兴趣分,并附带行为时间戳,这样模型既能学到偏好强度,也能学到时间衰减特性。
2.3 用户画像的构建与更新策略
用户画像是召回阶段的重要依据,我采用了两层结构:长期画像和短期兴趣。长期画像来自过去30天甚至更久的行为聚合,主要通过兴趣标签权重体现,比如某用户最近一个月浏览了30条美食视频、15条旅行视频、5条游戏视频,那他的兴趣向量里美食权重就会明显高于游戏。短期兴趣则来自最近1小时内产生的行为序列,是为了捕捉"突发性"的兴趣迁移。举个例子,一个平时只看美食视频的用户,某天晚上连续看了几条"装修攻略",那短期兴趣应该立刻反映出他对装修内容的需求,而不是非要等到长期画像更新。
这两个画像的更新频率完全不同。长期画像用离线批处理实现,每天凌晨跑一次全量任务,更新到数据库;短期兴趣用在线实时更新,用户每产生一次有效行为就把对应的标签权重自增,再配合时间衰减因子,把旧行为的分值逐渐降低。这套设计的好处是简单且可控,坏处是短期兴趣对稀疏行为的处理不够精确,比如用户只看了一条装修视频,权重就陡然升高,可能会让推荐流短时间被装修内容覆盖。我的缓解办法是设置一个最低行为次数阈值,只有行为次数超过阈值时才更新短期兴趣向量,避免单次偶发行为产生过大扰动。
3. 召回层的设计与实现:多路召回是推荐系统的地基
3.1 协同过滤召回:ItemCF还是UserCF?
召回层的目标是从全量视频库中快速筛出几百个候选视频,给排序层做二次精排。我见过的教学项目里80%都会首选协同过滤,因为逻辑直观、实现简单。但在短视频场景下,单纯依赖协同过滤会出现几个问题:物品更新太快,新视频没有足够用户行为来计算相似度;用户行为稀疏,多数用户只会和几十个视频发生交互,相似度矩阵非常稀疏。
我在项目里采用了ItemCF和UserCF双路召回,再对结果做融合。ItemCF的核心逻辑是"如果一个用户喜欢物品A,而物品B和物品A在很多用户的行为里同时出现,那么这个用户也喜欢B";UserCF反之,"如果两个用户历史上喜欢过类似的物品,那么其中一个用户的新行为可以作为另一个用户的推荐依据"。短视频场景下,ItemCF的效果整体优于UserCF,因为视频更新快但物品关系相对稳定, ItemCF的实时性更好。UserCF则主要用于冷启动阶段,或者针对行为特别稀疏的新用户做兜底。
ItemCF的Python实现核心其实很短,我用的是"共现矩阵+余弦相似度"方案。先构建用户-视频交互矩阵,再通过矩阵乘法得到物品共现矩阵,最后对每个物品的向量计算余弦相似度。关键优化点在于:不要把整个矩阵都存下来,只需要保留每个视频最相似的TopK个邻居,否则存储和计算都会失控。在我的数据集里,约10万用户、5万视频,如果全量保存相似矩阵,内存占用接近数十G,但只保留Top50邻居后,内存占用能降到原来的十分之一以内。
3.2 内容召回:让新视频也有机会被看见
内容召回解决的是协同过滤解决不了的新物品冷启动问题。实现方案是基于前面构建的视频内容画像和用户长期兴趣画像,计算向量余弦相似度,把用户兴趣向量TopN相似的视频捞回来。这里有个细节值得单独说:视频内容画像和用户兴趣画像的向量维度必须对齐,否则算不了相似度。我的做法是让两者共用一套标签体系,用户兴趣向量里每个维度的含义和视频标签向量完全一致,用户向量是行为加权的标签累积,视频向量是内容本身的标签分布。
内容召回的优势是推荐结果可控性强,即便某个视频刚上传一分钟,只要它的标签和用户兴趣向量匹配,就能进入候选集。缺点是"同质化"严重,如果用户长期只看美食,内容召回的结果会全是美食,多样性不足。所以我在召回层做了很必要的兜底策略:热门召回和随机探索。
热门召回就是从全局热度榜取TopN,这部分保证了新注册用户或者画像信息不足的用户也能刷到质量不错的内容。随机探索是最容易被忽略的一路召回,它从候选池里随机抽取少量视频混入结果,用于积累用户对新内容的反馈。虽然随机探索在短期指标上会拉低点击率,但它是维持系统持续进化、避免信息茧房的重要机制。
3.3 召回融合与截断策略
多路召回的结果需要做融合排序,我采用的是简单的加权得分融合。每一路召回的结果给一个基础分,再乘以一个可配置的权重。权重不是拍脑袋定的,而是通过线上A/B实验逐步调整。初始配置是协同过滤权重0.4,内容召回0.3,热门0.2,随机探索0.1。这个比例在实验早期还算稳,后来发现新视频的曝光占比太低,就把内容召回的权重提到了0.4,把热门压到0.1。
另一个常见问题是多路结果里视频去重。同一个视频可能同时出现在协同过滤、内容召回、热门三个列表里,如果不处理,排序后的结果会大量重复。去重逻辑很简单:一个视频在最终候选集中只保留一次,得分取各路里的最高分。融合之后还需要控制候选集规模,我的经验是召回阶段控制在500个以内,因为排序模型在500个候选上做特征计算和打分,响应时间还能保持在可接受范围内,超过1000个就明显吃紧。
4. 排序层实战:从逻辑回归到轻量深度学习模型
4.1 排序模型要解决什么?为什么不用纯规则
召回层给出的是几百个可能相关的视频,排序层要在其中进一步预测用户对每个候选视频的点击、完播、点赞概率,然后按综合预估分从高到低排列。有些初入行的同学会觉得规则打分也行,比如"热度分乘以标签匹配度",但规则打分的瓶颈在于无法组合大量特征,更无法捕捉特征之间的非线性关系。一个简单的例子:年轻用户对某个视频的偏好可能只在晚上特定时段激活,这种"用户年龄×时段×内容类型"的联合关系,用规则几乎没法精确表达,但对模型来说只是一个特征交叉的问题。
我采用的排序方案分两个阶段:第一阶段用逻辑回归(LR)作为基线,先把整个链路跑通;第二阶段引入GBDT+LR的融合方案,让树模型自动挖掘特征交叉。之所以不直接上深度模型,是因为初期特征工程还没沉淀清楚,直接上深度模型很容易出现"模型效果不如LR"的尴尬情况。深度模型对特征表达的要求非常高,特征没梳理好,复杂的模型结构反而会成为负担。
4.2 特征工程:排序模型的生命线
排序模型效果的上限很大程度上由特征质量决定。我把排序特征分成四组:用户侧特征、物品侧特征、用户物品交叉特征、上下文特征。用户侧特征包括年龄、性别、活跃时段、长期兴趣向量主成分、短期兴趣强度;物品侧特征包括标签分布、发布时间距现在的小时数、历史完播率、历史点赞率;交叉特征是关键,比如"用户兴趣向量与物品标签向量的余弦相似度""用户历史观看同分类视频的占比";上下文特征则包括当前时间点是早上、中午还是晚上,以及请求来源是首刷还是下拉刷新。
这里我特别想强调一个容易忽略的细节:排序模型的训练样本要使用"曝光数据"而非"点击数据"。如果只用点击数据训练,模型只会学到"用户点击了什么",但学不到"用户为什么没点击那个被推荐但没点进去的视频"。因此训练样本至少应该包含两类:点击正样本和曝光未点击负样本。负样本的采样比例也需要注意,我试过1比1、1比5、1比10不同比例,最终还是1比3左右比较稳,负样本太多会压制正样本信号,太少又会让模型高估点击率。
4.3 LR与GBDT+LR的实现细节
LR部分我用的是常见的随机梯度下降训练,特征输入做了离散化和归一化处理。类别型特征全部做One-Hot编码,连续型特征先做分桶再转成One-Hot,这样做的好处是能引入一定的非线性。比如"观看时长"这个连续值,分桶成"0-5秒、5-15秒、15-30秒、30秒以上"四档后,模型就能学习不同时段区间的差异,而不会因为一个极端的时长值把线性模型的预测结果拉偏。
GBDT+LR的实现思路是:先用GBDT对原始特征做转换,让每棵树输出的叶子节点索引拼接成一个新的特征向量,再把新特征向量输入LR模型。GBDT在这里扮演的是"自动特征组合器"的角色,它能发现诸如"早上8点且用户画像中通勤标签权重高"这类两条特征共同作用的情况,这些在人工特征工程里很难一一枚举。实际训练时,我用的是梯度提升树库的Python接口,树的数量设为180棵,最大深度6,学习率0.05,训练完成后把每棵树的叶子索引输出,再灌给LR。这套方案在当时的团队里效果提升明显,AUC从0.71左右升到0.78,作为对比,单独用LR只有0.70左右。
5. 工程落地与性能优化:推荐服务不是只在离线跑跑模型
5.1 推荐接口的整体流程设计
推荐系统不能只在离线批量算,用户刷视频时请求的是在线推荐服务。我实现的推荐接口流程分四步:接收请求带上用户ID和上下文信息,先从本地缓存和Redis里取出用户画像,紧接着执行多路召回,得到500个候选视频,然后把候选视频和用户特征拼成排序特征矩阵,灌入排序模型打分,最后按得分做重排和多样性控制,返回最终推荐列表。整个流程要求响应时间控制在100毫秒以内,实测下来,召回阶段占了主要耗时,排序阶段相对快。
这个流程里有一个关键设计是"用户画像缓存"。不能每次请求都去查数据库拼画像,否则数据库压力会非常大。我的做法是用户画像按用户维度写入Redis,缓存有效期设为5分钟,过期后重新从离线计算结果加载。这5分钟的延迟对用户冷启动有一些影响,但相对于数据库压力的缓解,这点延迟完全可以接受。这里要提醒一点:缓存更新要设置失效时间,不能只更新不失效,否则用户已经发生兴趣偏移了,推荐结果还停留在旧画像上。
5.2 召回索引与候选集加速
召回环节最耗时的是计算用户向量和全量视频向量的相似度。如果每一路召回都全量扫描一遍视频库,性能完全不可接受。我的优化方案是给视频向量建FAISS索引,把全量视频向量预先加载进内存,查询时直接从索引里取近似最近邻。FAISS是开源的向量检索库,支持CPU和GPU,在我这个量级的项目里CPU版本的查询延迟已经足够低。只需要在视频画像更新后重建索引并加载到内存即可,一般间隔十分钟做一次增量更新。
整体架构中处理候选集时还会遇到一个性能瓶颈:视频特征拼接。候选视频每次都要从存储里查询特征字段,如果走数据库随机查询,500个候选视频可能要查500次,延迟很高。我的做法是把特征字段提前组装成"特征字典"放进Redis缓存,键为视频ID,值为特征字典序列化,按需批量拉取。这样一次批量拉取几百个视频特征的开销只在几十毫秒以内,实测性能提升非常明显。
5.3 冷启动、频率控制与实时反馈回环
工程上还有一个绕不开的话题是"实时反馈回环"。用户刷到一条视频,点击或者滑过后,这个信号应该尽快进入系统,影响后续推荐。我用的方案是先把行为日志写入消息队列,由消费者程序做实时统计,累加用户短期兴趣向量并更新Redis里的用户画像。整个链路从日志到达消息队列到画像更新完成,平均延迟在10秒左右。这就意味着用户今天突然对某个主题感兴趣,今天之内推荐流就会开始反映这个变化,而不是干等第二天的离线任务。
频率控制是我前期忽略、后期补上的模块,它的作用是防止同一条视频在同一次会话中被反复推荐,也防止某个作者的内容霸屏。实现方式很简单:在重排阶段做去重和打散,比如同一个作者的内容最多连续出现2条,同一视频24小时内最多推荐3次。这些逻辑虽然简单粗暴,但对用户体验的提升效果非常直接,某种意义上比模型调优给用户带来的体感更明显。
6. 效果评估与踩坑复盘:离线指标涨了,线上不一定涨
6.1 离线评估:AUC、Recall、覆盖率、多样性缺一不可
推荐系统的效果评估不能只看一个数。我在离线阶段至少会跑四类指标:准确率相关指标、召回率相关指标、覆盖率和多样性指数。AUC是最常用的排序模型评估指标,反映的是模型对正负样本排序能力的置信度,但我发现很多初学者容易陷入"AUC越高越好"的误区。AUC高只能说明模型在历史数据上的排序能力好,不代表线上用户满意度一定高,因为历史数据本身有偏差——推荐出去的内容本来就是旧策略筛过的,模型学到的是"旧策略下的最优",不代表"全空间内的最优"。
所以要配合看覆盖率和多样性。覆盖率就是推荐列表中不同视频数量占活跃视频总量的比例,这个指标低了说明系统越来越窄;多样性可以用推荐列表中不同主题标签的香农熵来衡量,熵值越高,多样性越好。我的经验是这三类指标要一起看:AUC负责判断排序模型质量,覆盖率负责判断系统的探索能力,多样性负责判断用户体验的长期健康度。
6.2 线上AB测试:小流量实验是最诚实的裁判
离线指标再怎么好看,最终都要经过线上小流量实验来验证。我踩过一个典型的坑:离线AUC提升了接近3个百分点,自认为效果稳了,结果小流量上线后用户的人均刷视频时长竟然下降了。原因复盘后才发现,离线AUC的提升主要来自对旧视频的偏好预测更准了,但推荐列表变窄,用户刷到的内容同质化严重,刷着刷着就没有继续看的欲望了。这直接印证了那句话:指标是分蛋糕的人,体验是吃蛋糕的人,不能只取悦前者。
从那之后,我对线上实验的评估指标做了调整:核心看人均观看时长、次均观看时长、分享率和次日留存,辅助看点击率和完播率。点击率高但观看时长低,说明标题或封面对用户的吸引力大于实际内容质量;观看时长高但分享率低,说明内容足够沉浸但缺乏社交传播价值。不同阶段优先关注不同指标,冷启动期更关注留存,成熟期更关注消费深度。
6.3 复盘几个最容易翻车的地方
第一个坑是训练样本偏差。最开始我把用户的全部行为日志都当作训练样本,结果发现一个严重的问题:曝光日志里大量包含"虽然展示但用户没点"的负样本,这是对的;但历史曝光本身就是旧推荐策略的结果,旧策略推荐得多的视频,就会被模型标记为"更受欢迎",形成自我强化的偏见。缓解思路是定期引入随机探索流量,让一部分流量完全不经过排序模型,纯随机推荐,用这部分流量收集无偏的用户反馈,再补充到训练集里。
第二个坑是特征穿越。训练排序模型时不小心把"用户是否点赞"这个特征用在了预测"用户是否点赞"上,AUC直接飙到0.98,当时还以为模型效果炸了,后来排查特征依赖才发现问题。做特征工程时,一定要反复确认训练阶段使用的特征在预测阶段是否能够实时获得。任何以"未来信息"为输入的特征,都会让离线评估变成一场自欺欺人的表演。
第三个坑是多样性控制的力度把握。曾有一次把打散逻辑加得太狠,同一个作者的内容强制间隔至少5条,结果推荐流变得非常割裂,用户刚看完美食教程,下一条就跳到游戏解说,体验反而变差。多样性控制不是越分散越好,而是在保证相关性的前提下适度打散,我当时最终把参数调整到同一个作者内容最多连续出现2条、同一主题最多连续3条才算比较舒服。
在我做过的几次类似项目里,唯一一个从一而终的建议没有变过:先跑通最小闭环,再谈模型优化。很多同学动手就奔着深度学习模型去,结果一二三周全耗在环境配置和特征梳理上,连一个能响应的推荐接口都没有。正确路径是先做出一个最简单的基于热门的推荐版本,让用户和视频数据可以正常流转,然后再逐步替换和增加召回、排序逻辑。这就像先把水管接通,再来研究水质净化,而不是先设计一套净水方案,结果水还没接进屋子。
如果你也准备做类似的推荐系统,我建议从本周开始做三件事:把用户行为日志的埋点字段定清楚,把视频内容画像的特征表建出来,再把协同过滤和内容召回跑通。这三件事做完,你的推荐系统就已经能运转了,剩下的排序模型、性能优化、AB实验,都是在它之上不断打磨的事。等到整个链路稳定运行,你回头看最初的决策,才能真正理解为什么"多路召回加轻量排序"这个组合,是绝大多数推荐系统起步阶段最务实的选择。