1. 先说我为什么决定动这套推荐架构
做推荐系统的时间一长,你就会发现一个很微妙的现象:模型越来越准,业务方却越来越不满意。尤其是我之前在某个对标Instacart模式的即时零售平台,负责前台信息流的推荐算法时,这种错位感几乎每周都在晨会上出现一次。
问题不在模型精度,而在"部门孤岛"这四个字上。算法团队手里的用户画像,是基于全站行为训练出来的,粒度细到用户昨天几点搜索过什么品类、最近三次点击之间隔了多久;而运营和类目团队手里的主题认知,是围绕大促、节气、场景搭配组织的,粒度粗到"本周主推火锅食材""夜宵时段突出卤味拼盘"。两边都在为用户服务,但产出物放在同一个前台页面里时,往往各说各话。
用户看到的结果是什么?信息流第一屏是算法根据他最近三十分钟的浏览算出的"高频复购款",第二屏切到运营配置的"贡菜、响铃卷、牛油锅底"主题榜单,第三屏又跳回算法推荐的"虾滑"。你说这两者冲突吗?表面不冲突,都是吃的。但用户感知很直接:这个App到底是懂我还是不懂我?有时候他刚搜过"低脂鸡胸肉沙拉",隔两分钟却看到"深夜油炸小食专场",这种体验做不了假。
我们当时尝试过很多局部优化:运营配置主题时参考算法侧的用户分群,算法侧把运营主题ID当特征拼进排序模型。效果都有一点,但离"融合"差得远。本质原因是两套逻辑的系统目标不一样,特征体系不一样,甚至数据表都不在同一层。你硬把它们拼在一起,只是把孤岛之间搭了一座桥,岛还是岛。
所以那一年我们下决心做了件事:重新设计一版推荐框架,核心思路就是从用户微观行为画像出发,级联生成宏观主题表达,让每一屏内容既是算法算出来的,也是运营想要的那种"有主题感"的样子。这套框架在我们内部代号叫"模拟项目X",跑了大半年,前后覆盖了首页信息流、品类频道页和搜索结束后的相关推荐三个场景。今天这篇我就把这套东西的完整思路、落地方案和踩过的坑一次性讲清楚,给同样被"算法和运营打架"折磨的团队一个参考。
2. 级联生成框架的骨架设计:微观画像与宏观主题的衔接方式
2.1 两级生成的关系不是拼接,而是约束
很多团队听到"融合微观和宏观",第一反应是把两类特征拼进同一个模型,或者做两路召回再混合排序。这两种做法我都试过,效果都不理想。
拼接特征的问题在于:微观特征维度过高、信号太强,主题特征很容易被稀释。运营配置的"圣诞节烘焙专场"这个ID,在模型里跟用户过去一周的几百个点击行为特征放在一起,权重几乎可以忽略不计。混合排序的问题反着来:两路结果各排各的,最后强行merge,中间得有大量人工规则兜底,否则榜单和个性化推荐永远像两条平行线。
级联生成框架换了一个思路。我把它理解成一个"先生成主题,再在主题内部做微观匹配"的过程,反过来也可以,关键是两级之间不是并列关系,而是约束关系。用户进页面时,系统先根据他的微观行为判断当前场景意图,生成一个或多个候选主题方向;然后再根据用户更细粒度的偏好,在主题内部筛选、排序具体的商品。主题约束了候选空间,微观偏好决定了最终排序。
这个设计最初参考了文本生成领域里的级联思路——先定大纲,再写句子。推荐场景虽然没有那么强的序列依赖,但"先定基调、再填细节"的层级关系是通用的。用户进入页面的第一反应,是先决定"这一屏跟我有没有关系",然后才逐个判断"里面哪个商品我想点"。
2.2 用户微观画像层:从实时行为序列中提炼"此刻要什么"
这一层解决的核心问题是:用户此刻到底处于什么状态。
传统用户画像喜欢给用户贴长期标签,比如"火锅爱好者""夜宵党""宝妈",这类标签稳定但不敏感。问题在于,同一个"火锅爱好者",周日晚上的诉求和周三中午的诉求完全不同。周日晚上他可能是约了朋友在家涮锅,需要牛羊肉卷、毛肚、锅底底料;周三中午他可能是自己一个人想吃顿快火锅,只需要一盒肥牛和一包速食小锅。你给他推同样一套东西,只能说方向没错,颗粒度太粗。
即时零售场景下,我们更关注的是短周期意图,甚至分钟级的实时意图。微观画像层输入的数据源主要有三条:用户最近一段时间的点击行为序列、搜索词序列、加购和下单记录。输出的是几个维度的向量化表达:品类偏好、价格带偏好、品牌偏好、场景意图,以及一个很重要的"探索度"信号——用户今天是想快速解决一餐,还是愿意花时间逛逛看有什么新鲜的。
这一层我建议用序列模型来做,不一定要上多复杂的结构,Transformer或者GRU都可以,关键是时间衰减怎么设计。我们内部用的是分段指数衰减,最近15分钟的行为权重是1,15分钟到1小时的衰减到0.5,超过4小时的历史行为衰减到0.1以下。做过一个很直观的对照实验:用全量历史行为做用户向量,和用时间衰减加权后的用户向量分别去预测用户下一次点击,后者的点击率预测AUC稳定高出3到5个点。说明离得越近的行为,对"此刻意图"的贡献越大。
2.3 宏观主题层:从运营意图中抽象"这一屏讲什么"
宏观主题层的输入不是用户,而是业务。它的核心是回答:这一屏想给用户传达什么信息。
运营团队思考主题的方式是"商品集合+文案+场景",例如"深夜馋嘴补给站""周三会员日火锅食材满减"。这套语言非常人性化,但机器没法直接用。你需要把它转译成机器可处理的表征——主题向量,或者主题分布。
我们做主题层的第一步,是把历史上所有运营配置过的主题卡片做了一次聚类。聚类特征用的是主题内商品的类目分布、价格带分布、品牌集中度,以及主题文案的语义向量。聚出来的结果很有意思,大概有七类:家庭囤货型、即时满足型、尝鲜探索型、场景仪式型(节日、聚会)、健康管理型、便利速食型、中产品质型。每一类主题在商品构成上都有非常明显的区分度。
聚类只是底座,真正上线时还需要运营同学的"人工种子主题"参与。比如"世界杯观赛零食桶"这种主题,聚类里不一定分得出来,但运营的一句话描述就可以生成一个主题向量:把"世界杯""观赛""零食""分享装"这几个关键词的语义向量加权平均,再加上类目分布约束,就能得到一个可用的主题表征。
这里有个容易忽略的点:主题层的抽象粒度直接决定后续微观匹配的难度。主题向量如果只包含类目信息,那"火锅专场"和"麻辣烫专场"在向量空间里会非常接近,用户可能被匹配到根本不感兴趣的主题里。所以我们把主题向量的维度拓展到包括品类、价格带均值、品牌偏好强度、场景标签、以及一个"内容温度"特征——偏促销导向还是偏品类教育导向。这样主题之间在向量空间里的间距更合理,后面的匹配才谈得上精准。
2.4 级联融合的位置选择:特征拼接、打分约束还是重排?
这是我被问得最多的问题:两级框架都建好了,到底在哪个环节做融合?
我先后试过三种方案,可以负责任地说:选择融合位置是这个项目里最影响最终效果的技术决策。
第一种是特征拼接,把主题向量作为特征拼进排序模型。简单,但效果最差,原因前面说过,反馈信号一强,主题就成陪衬。第二种是打分约束,在排序模型的loss里加一项"主题匹配度"的正则项,强行把排序结果往主题方向拉。效果中等,但调参很难,约束强了用户个性化被牺牲,约束弱了等于没约束。
第三种是重排——级联生成再加最后一道重排层的二次打分,也是我们最终采用的方案。流程是这样的:第一级排序模型输出一个候选商品序列,第二级根据当前页面的主题向量,从上一步的候选池里重新打分排序,打分函数综合考虑用户微观匹配度和商品与当前主题的匹配度,用一个系数来调节两者的权重。这样做的好处是,主题约束发生在排序结果已经产生之后,模型跳过了"数量级完全不同的特征在同一模型里互搏"的问题。我们把这个系数叫作"主题约束强度",实际调参范围在0.25到0.75之间,根据页面场景动态调整。
后来我们又迭代了一版,把重排层从规则加权升级成一个小模型,输入是用户向量、商品向量、主题向量三者的交叉特征,输出一个融合分。这也是目前线上在跑的版本。效果比手动加权稳定得多,最重要是它给了我们一个可学习的参数空间,后续可以针对不同频道、不同用户群自动调优。
3. 落地实现的关键细节:从特征工程到样本构造
3.1 微观特征层的构建:行为序列、时间衰减与短期意图
说完了框架,聊点实在的——这一套东西在工程上到底怎么落地。先从微观画像层谈起。
我们线上落地的微观特征分为三块。第一块是行为序列特征,包括点击序列、加购序列、下单序列、搜索序列四类,每类序列取最近50条记录,用itemID和categoryID双粒度编码。做序列编码时有一个产品细节:点击序列和下单序列分开建模,不要混合成一个序列,因为点击是弱意图信号,下单是强意图信号,混在一起模型很容易被弱信号带偏。
第二块是时间衰减特征。每个行为都带一个时间戳,做序列模型的时候直接在attention里注入相对时间差。这里有个实操细节:相对当前时间的时间差,而不是相对序列里上一个行为的时间差。有些人用后者,结果模型学到了"用户行为间隔"的特征,那个信号跟意图关系不大,反而引入了偏差。
第三块是短期意图特征。我们通过一个简单的规则+模型混合方式:规则部分是把最近15分钟内的行为按类目聚合,统计类目占比,模型部分是上面那个序列模型学到的用户向量,取最后一步的隐状态作为"当下意图向量"。这两部分拼接后过一个MLP,输出每个一级类目上的意图分布。
有个细节值得特别说明:行为序列里的商品ID,一定不要直接做embedding,要用类别和价格带的组合信息编码。因为线上商品更新迭代很快,新品的ID在历史行为里完全没有出现,如果你直接用itemID embedding,冷启动问题会非常严重。用"类目+价格带+品牌"的组合编码,即使是一个从来没见过的商品,也能通过特征组合匹配到用户历史行为里相近的商品簇。这个小改动让我们新品曝光到点击的转化率提升了不少。
3.2 主题表征的构建:聚类、种子主题与动态更新机制
主题层落地时,最麻烦的问题不是建模,而是数据来源和更新频率。运营配置主题的节奏通常是按周,一周大概上线几十个主题卡片,但有大量的历史主题是过期下架的。如果主题表征完全依赖运营输入,那么模型学到的主题空间就会很稀疏,而且更新不及时。
我们的做法是双轨并行。第一轨:历史主题聚类。把所有历史上线过的主题商品列表拉出来,构造每个主题的"商品画像向量":类目分布、价格带分布、品牌集中度、平均评分、平均复购周期。用这些向量做聚类,得到稳定的主题基类。这部分作为冷启动的底座,更新频率很低,一个月重跑一次就可以。
第二轨:实时种子主题。运营同学新建主题时,系统实时生成主题向量。具体方式是:主题文案做语义编码,商品列表做画像向量,两者加权平均,再加上规则约束——比如主题内商品数不低于15个、类目标签不超过3个、价格带跨度不超过两档。这组规则约束非常重要,因为它避免出现"主题描述讲火锅,商品列表里却混进了洗衣液"这种类目混乱的情况。
两条轨道的向量会做一个周期性的合并更新:运营新建主题当天用第二轨的临时向量,每晚离线任务重算一次全量主题向量,把临时向量平滑进聚类底座。用户侧感受不到这个过程,但运营同学在后台看到的排序效果会更稳定,且不会因为新建主题导致推荐结果突变。
3.3 级联训练:两阶段损失与样本权重,以及一个关键陷阱
这一节我想重点讲训练细节,因为框架设计得再好,训练方法不对就会前功尽弃。
级联框架的训练思路是:微观画像层单独训练一个序列模型,输出用户意图向量;主题层单独训练一个主题表征模型,输出主题向量;真正需要联合训练的是最后的重排模型,它的输入特征是用户向量、商品向量、主题向量的交叉。前两个模型训练完就冻结,重排模型只在冻结向量的基础上做拟合。
训练重排模型时,样本怎么构造是个容易踩坑的地方。主题层的样本不是"用户点了某个商品"这种常规样本,而是"用户在当前场景下看到了这个主题,然后点击了主题下的某个商品"。这意味着样本必须包含主题上下文信息。我们最开始就踩了坑:只拿用户和商品做pair输入训练,主题向量丢掉了,结果模型效果跟普通排序模型没区别。后来把样本组织成(user, topic, item)三元组,效果才真正体现出来。
损失函数用了两阶段设计。第一阶段损失是商品维度的点击/加购/下单多任务loss,保证基础排序能力;第二阶段损失是主题命中度loss——正样本里的商品应该跟当前主题匹配度高,负样本里随机抽取的商品匹配度应该低。两个loss加权合并,权重比例大概是0.7比0.3。实测下来,主题命中度的权重不能超过0.4,否则排序模型的个性化能力会明显下降。
训练数据的时间窗口也要特别注意。主题生命周期短,一般就一周,训练样本里如果混入已经下架主题的数据,相当于给模型喂了过期标签。我们的做法是只在样本构造时取"主题仍在上架期"的时间范围内的数据,同时把主题自身的发布时间距离当前时间的间隔作为一个特征,效果比直接不做过滤要好。
3.4 线上服务链路:实时性和稳定性的权衡
讲了半天训练,再讲线上怎么跑。级联框架的服务链路比普通推荐链路多一层,每一层多出的RT(响应时间)都可能是用户体验的杀手。
我们的线上链路分四步:第一步,用户请求进来,实时特征服务在50毫秒内返回微观画像向量;第二步,主题服务根据请求场景(首页还是品类页)和用户意图向量,在主题库里做一次ANN检索,返回最匹配的几个主题及对应的商品候选池;第三步,粗排模型对候选池商品快速打分,过滤到百级别;第四步,重排模型融合主题向量做精排,输出最终几十个商品。
这里最耗时的其实是第二步的ANN检索加第三步的粗排,因为主题库虽然不大,但每个主题关联的商品数量可能上千。我们做了一件事:在主题检索和商品排序之间加了层"场景过滤"——首页场景过滤掉需要多步操作才能理解的主题,品类频道页过滤掉与当前频道无关的品类。这一层规则过滤看似简单,实际上极大减轻了粗排和重排的压力。
稳定性方面一个容易忽略的点:微观画像向量很强地依赖实时行为序列,一旦行为序列服务抖动,向量输出是空的或者过期的,级联框架整个就崩了。我们给画像服务加了兜底逻辑:实时向量拿不到时,回退到小时级聚合画像,再拿不到回退到天级。每一级回退都伴随主题约束强度系数的自动下调——因为画像越粗糙,越不应该让主题约束占据主导。这个回退机制上线后,我们的推荐服务可用率从99.2%提升到99.6%。
4. 上线前后的踩坑血泪:我在排查链路中学到的事
4.1 第一次AB实验:主题层被微观层完全淹没
框架开发用了六个星期,上线练兵一个周末,然后我们信心满满地开了AB实验。结果周一上午看数据,差点没把咖啡喷到屏幕上:实验组的点击率对比对照组,不仅没涨,反而跌了1.8%。
这是我最想展开讲的一段。因为如果你正在做类似的架构改造,你大概率也会撞上这个问题——新框架跑起来了,各个模块单测、离线指标都正常,但一上线就拉胯。
我们先分析了链路里的每一步。离线重放显示,重排模型产出的序列里,主题内的商品排序确实比对照组更有"主题感",比如用户进入"火锅主题"后,看到的第一个商品是毛肚而不是冰淇淋。但用户的点击行为数据显示,用户在前两个位置的点击率几乎没差别,真正的衰减出现在第三到第十个位置。换句话说,用户被前两三个商品吸引进去了,但后续的商品没有持续跟上他的真实偏好,整体浏览深度变浅了。
问题出在哪?主题约束把候选空间的多样性压掉了。我们当时主题约束强度设成了0.6,这个值在离线评测里表现得很好,但线上用户的行为比离线评测要复杂得多——用户对"火锅主题"感兴趣,不代表他愿意连续看八个火锅相关商品。一旦前两个商品没有完全命中他的具体偏好,后续商品的放弃概率会急剧上升。主题层抢了微观层的戏,但没有微观层的精准度来兜底。
4.2 排查链路:从特征一致性到样本穿越,逐步复现问题
找到方向后,我们做了一次完整的排查链路,大概花了四天时间,从三个角度逐层确认。
第一步,排查特征一致性。离线训练的样本里,用户向量是行为序列模型输出的,主题向量是离线算好的。但线上服务的向量是不是同一套?我们对齐后发现,用户向量的实时版本和离线版本差异极大——实时版的输入行为序列比离线版短很多,因为用户会话刚启动时,风云实时行为只有前几分钟的数据。这导致实时用户向量和训练时的用户向量不在一分布里,模型自然表现不稳。这个问题的解决办法是给用户向量加一个"行为充分度"特征,行为量太少时自动降低用户向量在重排打分中的权重。
第二步,排查样本穿越。训练样本里的主题向量来自主题商品列表聚合,商品列表是主题全量配置数据。但一个很隐蔽的问题是:那些在上线后才加入主题的商品,在训练样本里已经以"未来商品"的身份参与过聚合计算。这相当于模型在训练阶段就已经见过未来要推荐的新品,上线后新品真正出现时模型就懵了。修复方式是严格按时间切分:训练样本只使用截至样本时间点之前已配置的主题商品集合计算主题向量。这个版本离线指标恢复了预期水平。
第三步,排查目标不一致。重排模型的训练目标是"点击+加购",但主题层的业务目标是"主题整体转化率"。点击率高并不代表主题内商品卖得好。我们重新定义了第二阶段损失的目标:用主题内商品整体的转化率作为正样本标签,纯点击率作为辅助标签。这步调整之后,效果才真正稳下来。
4.3 修复方案:级联约束的强度调节与退场策略
问题定位清楚了,修复就不难了。核心调整两个:一是约束强度系数动态化,二是增加内容多样性保护。
约束强度从固定值改成按场景动态计算。首页场景用户浏览意愿高,主题约束强度设为0.4到0.5之间;品类频道页用户主动表达意愿更强,主题约束反而可以重一点,设到0.55到0.65。实现方式是在重排模型里加一个场景感知门控:场景特征和用户活跃度特征过一个MLP,输出一个0到1的系数,再乘上基础约束值。
多样性保护则是加了一个简单的MMR惩罚项。重排打分时,如果候选序列里前N个商品的类目熵低于阈值,自动下调后续同品类商品的排序分,强制插入一些关联品类来保持页面呼吸感。这个机制上线后,主题页的浏览深度和整体转化率同时上涨,也印证了我对第一次AB失败的判断:过度收敛的主题表达让用户失去了探索空间。
修复版上线后,实验组点击率和对照组持平,但整体转化率(下单用户数/曝光用户数)稳定提升2.3%,主题页停留时长提升8%。更重要的是,运营同学终于看到自己配置的主题能"按照主题的样子"呈现在不同用户面前了——同一个"深夜食堂"主题,给深夜加班的程序员看到的是速食和卤味,给带娃妈妈看到的是半成品菜和健康零食。这是框架从"能用"到"好用"的质变。
5. 这套框架的复用价值与进一步扩展
5.1 适用场景判断:什么时候值得引入级联生成
做总结之前,我想先说清楚一个判断标准——不是所有推荐系统都适合级联生成框架。我在外面做分享时经常有人来问,第一句就是"我们要不要上这套框架"。
我自己判断一个业务适不适合这套架构,就看三个条件。第一,是否存在强"主题运营"的场景,比如大促、节日、季节、场景化策划。如果业务完全没有主题概念,纯是"用户搜什么就给什么",那级联生成等于画蛇添足。第二,用户行为是否具备短周期意图切换的特点。外卖、即时零售、直播电商这类用户意图快速变化的场景,微观层才有足够的信息量去支撑主题选择。第三,算法和运营团队之间是否已经出现了"各推各的"的割裂问题。如果团队协作本身顺畅,现有架构运转良好,没必要为了框架而重构。
拿京东到家、朴朴超市这类同城零售业务举例,第一和第二个条件是天然成立的。但对传统电商的搜索场景,这套框架的收益就没那么大——因为用户搜索时,他本人的搜索词已经是最好的主题了。
5.2 不同业务形态下的差异化配置
即使决定用级联生成,不同业务形态下的配置差异也很大。我把踩过的经验整理成了几组对照,方便你直接用。
首页信息流场景:用户意图最分散,主题约束系数建议最低,多样性保护权重最高;商品特征里价格带权重应该提高,因为用户在这个场景对价格更敏感。
品类频道页场景:用户主动表达类目意图,主题约束系数可以上调,但微观画像里的品牌偏好权重需要降低——用户进频道页是为了逛品类,而不是为了追某个品牌。
搜索后相关推荐场景:用户意图最明确,主题选择几乎可以被搜索词直接替代。这时候级联生成的重点不在打算生成什么主题,而在于主题内部的排序如何兼顾"相关性"和"惊喜感"。我建议搜索场景里主题向量与搜索词向量做一个融合,融合比例大概搜索词七成、主题三成,效果最稳。
还有一个跨业务通用的经验:新用户和老用户的配置必须分开。新用户没有足够的行为序列,微观画像层本来就弱,主题约束强度设置过高会伤害冷启动。我们线上把新用户的主题约束系数强制压到0.3以下,同时把主题基类里的"大众热门主题"优先推荐给新用户,用了一周时间的观察,新用户次日留存率提升了1.5%。
5.3 个人实操体会与后续迭代方向
最后说说我自己的几个体会,算给想做这件事的团队一些心理准备。
第一个体会是:这个项目的关键瓶颈往往不在算法,而在组织协同。级联生成框架让算法团队和运营团队第一次共用一套"主题表征"的语言,但前提是运营同学愿意把主题配置从"人工填商品"改成"描述主题画像、系统来填商品"。这件事推行起来比想象中难得多。我的建议是从一个小的品类频道页试点,让运营看到"我描述一句话,系统自动生成一整页主题商品"的反馈速度,慢慢建立信任。
第二个体会是:离线的完美不代表线上成功。我们踩过最大的一次坑就来自离线AUC的盲目乐观。后来我们建立了一个规矩——任何新功能上线前,除了看模型指标,还强制跑一轮模拟用户路径的规则检查:从进入页面、浏览前三屏、中途换主题、退出,每一步都用人写规则卡一遍,专门抓"模型自洽但用户不自洽"的问题。
第三个体会关乎迭代方向。我们后续在做的有几个方向:一是把主题生成从"检索匹配"升级成"生成式",参考大模型的做法,直接生成主题描述,再用主题描述生成商品集合,这条路还在探索,难度不小但上限高;二是把微观画像层的序列特征升级成多模态,加入用户对图文的浏览、视频内容的互动等信号;三是把级联框架从"页面级"扩展到"会话级",让用户在连续多屏浏览之间感受到主题推进的叙事感——就像逛一个线下超市,从入口的促销堆头慢慢走到深处的品类货架,这个体验才是主题推荐最终该有的样子。
如果你也在做类似的事情,我建议你从最小的主题场景切入,先让一屏内容"讲清楚一件事",再慢慢铺开。级联生成的本质不是炫技,而是让技术回到业务逻辑里,帮用户省掉"这个页面跟我有什么关系"的思考时间。这个目标,值得花几个月的时间去磨。