想象一个特别懂你的朋友小明。你最近连着看完了《甄嬛传》《延禧攻略》《如懿传》,今晚打开视频App,他随口来一句:“试试《锦绣未央》?你肯定爱看。”这不是玄学,而是他记住了你追剧的时间顺序——先看仙侠、再看宫斗,最近全扑在宫廷戏上。序列推荐模型干的就是这件事:把你的历史行为(看过什么、买过什么、听过什么)当作一条连续的时间线,用“自注意力”分析每个节点的前因后果,找出真正驱动你下一步选择的力量。它催生了两个明星模型——SAS4Rec(只往前看、预测下一步)和BERT4Rec(前后对照、补全空缺),已经成为电商、视频、音乐平台背后的“首席购物参谋”。

岗位关联

学完本节,你将理解序列推荐如何从“看完宫斗剧推下一部宫斗剧”的朴素规律中,提炼出注意力、掩码、位置编码、负采样等核心机制,这是从传统推荐进阶现代推荐系统的重要一步。

目录
  1. 可视化演示
  2. 序列推荐概述
  3. 自注意力机制
  4. SAS4Rec模型
  5. BERT4Rec模型
  6. 位置编码设计
  7. 负采样与训练
  8. 模型评测对比
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示SAS4Rec/BERT4Rec的Transformer编码器架构、因果掩码与双向掩码对比及用户行为序列预测过程,直观理解自注意力在序列推荐中的应用。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Transformer编码器架构 Embedding层将物品ID映射为稠密向量,位置编码注入时序信息 多层Transformer编码器通过自注意力捕获物品间依赖关系 h = TransformerEncoder(Emb(x) + PosEmb), pred = softmax(h·W)

序列推荐概述

小明第一次给你推荐剧时,只知道“你爱看古装剧”——这是传统推荐的做法:把你所有看过的剧塞进一个统计器,算出你的平均口味。但很快他发现不够用了:你上个月还在追仙侠,这个月却迷上了宫斗,平均口味根本跟不上你的变化。这就是传统推荐模型的局限——它把你的历史行为压缩成一个静态画像,丢掉了时间顺序。

于是小明换了个思路:不再求“你整体喜欢什么”,而是问“你最近一步步看了什么,下一步会看什么”。他把你的行为按时间排成一条线:剧1→剧2→剧3→……→现在。这条线称为行为序列,模型的任务就是读懂这条线、预测下一个节点。这就是序列推荐模型的核心使命。

形式上,序列推荐要学习的是历史条件下的条件概率:给定过去 t 个行为,找出最可能成为第 t+1 个行为的物品。这个概率不再是一个静态评分,而是一条“动态的、随每次行为更新”的预测。

把“预测下一部剧”想明白,后面 SAS4Rec、BERT4Rec 的一切设计——掩码、位置编码、负采样——都是在为把这个条件概率算准而服务。就像小明为了让推荐更准,开始研究你的追剧节奏、记笔记、学习你的口味迁移规律。

P(vt+1∣v1,v2,…,vt)P(v_{t+1} \mid v_1, v_2, \ldots, v_t)
通俗地说:知道你今天追了哪几部剧以及先后顺序,算出你明天最可能点开哪一部。
  1. 历史条件等号右边竖线后面是“已知信息”——用户过去按顺序发生的行为序列,每一部剧都扮演了上下文。
    v1,v2,…,vtv_1, v_2, \ldots, v_t
  2. 目标变量等号左边是“要预测的东西”——用户下一个最可能发生的动作。
    vt+1v_{t+1}
  3. 概率意义这个表达式读作“在给定历史序列的条件下,发生 v_{t+1} 的概率”,它不是孤立打分,而是带着顺序信息做推断。
    P(⋅∣⋅)P(\cdot \mid \cdot)

自注意力机制

小明是怎么从你的追剧历史里提炼线索的?他不会只盯着你最近看的一部剧,而是会“回顾”整个剧单:你去年看《琅琊榜》时喜欢权谋,今年看《庆余年》还是没变;但你昨晚刚看完一部甜宠剧,说明当下情绪想吃糖。他给每部剧分配一个“关注度”——近期看的、和主题相关的得分高,无关紧要的得分低。这个机制,就是自注意力。

在模型里,每个行为位置会生成三个角色:查询(Query)——“我想找什么信息”;键(Key)——“我能提供什么信息”;值(Value)——“我实际给出的内容”。查询与每个键做点积,算出相似度,再经过 softmax 归一化成权重,最后用权重对所有位置的值做加权求和。哪个位置与当前“话题”越相关,它的值被采纳得越多。

用公式写出来就是:

小明“回顾剧单”的本事,体现在自注意力能一眼扫完所有位置、并行计算每对关系。相比循环网络要一个接一个地往后传话,自注意力的信息通路更短、视野更全——这为它成为序列推荐的主干架构打下了基础。你的追剧轨迹越长,这个优势越明显。

Attention(Q,K,V)=softmax(QK⊤dk)V\text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V
通俗地说:每部剧都举手说“我和当前线索有关!”,模型按相似度打分,把最相关的几部剧的信息加权汇成一条“综合线索”。
  1. 投影矩阵输入序列先分别乘上权重矩阵 W_Q、W_K、W_V,生成三个角色向量,形状都是序列长度 × 隐藏维度。
    Q=XWQ,K=XWK,V=XWVQ = XW_Q,\quad K = XW_K,\quad V = XW_V
  2. 相似度打分Q 与 K 的转置相乘,得到序列长度 × 序列长度的分数矩阵,每个位置与每个其他位置都算出一个匹配分。
    S=QK⊤S = QK^{\top}
  3. 缩放除以 \sqrt{d_k} 让分数保持稳定,避免点积值过大把 softmax 推向饱和区。
    S′=SdkS' = \frac{S}{\sqrt{d_k}}
  4. 权重归一化softmax 把每行分数变成一组和为 1 的权重,数值越大的位置获得越高关注。
    W=softmax(S′)W = \text{softmax}(S')
  5. 加权聚合用权重 W 对 V 加权平均,得到每个位置融合全序列信息后的新表示。
    O=WVO = WV

SAS4Rec模型

小明预测你要看什么时,有个铁打的规矩:只看你已经看过的,绝不偷看你未来还没看的内容。这套“只往前看”的推理方式,被 SAS4Rec 用一张因果掩码实现了。所谓掩码,就像一张贴在注意力分数矩阵上的挡板——把矩阵右上角(未来位置)全部挡住,让 softmax 在计算时看不到任何未来信息。

为什么要如此严格?因为序列推荐的训练场景是“根据过去预测下一部剧”——如果模型偷看了答案,训练出来的能力就是假的,上线后一遇到真实预测就会崩盘。SAS4Rec 把这一约束写死:在每个位置 i,注意力只能访问位置 1 到 i 的信息。它的编码器堆叠了多层这样的自注意力块,每层都加上因果掩码,输出序列最后一个位置的表示向量,用它来预测下一个物品。

从数学上看,SAS4Rec 把前面的条件概率具体化为:用编码器把历史行为变成隐藏表示 \mathbf{h}_t,再与所有物品的嵌入向量做内积,得到每个候选物品的得分,最后套一个 softmax 得到概率分布。

因果掩码是 SAS4Rec 的灵魂。小明因此能“诚实”地只用过去的剧单做推理——这也正是真实推荐场景的写照:你永远不会知道你用户明天会看什么,只能依据他今天之前的轨迹来猜。掩码让模型在训练和上线时保持动作一致,是一个“言行一致”的推理者。

P(vt+1∣v1,…,vt)=softmax(htW⊤)P(v_{t+1} \mid v_1, \ldots, v_t) = \text{softmax}\left(\mathbf{h}_t \mathbf{W}^{\top}\right)
通俗地说:把“你的追剧史”浓缩成一个向量 \mathbf{h}_t,再拿它和全站剧集做一轮相似度排名,排名第一的就是推荐答案。
  1. 历史编码自注意力层输出最后一个位置的隐藏状态 \mathbf{h}_t,它汇总了位置 1 到 t 的全部有效信息。
    ht=TransformerBlock(v1,…,vt)\mathbf{h}_t = \text{TransformerBlock}(v_1, \ldots, v_t)
  2. 候选打分\mathbf{h}_t 与物品嵌入矩阵 \mathbf{W} 相乘,等于用“历史精华向量”和每部剧的嵌入做内积,内积越大表示越匹配。
    s=htW⊤\mathbf{s} = \mathbf{h}_t \mathbf{W}^{\top}
  3. 概率归一softmax 把所有候选剧的得分归一化为概率,概率最高的物品即推荐结果。
    v^t+1=arg⁡max⁡vsoftmax(s)v\hat{v}_{t+1} = \arg\max_v \text{softmax}(\mathbf{s})_v

BERT4Rec模型

但小明发现,有时“只看过去”不够用。比如你剧单中间有一部剧打不开、没看完,他没记录到;又比如你现在的口味其实是被三个月前的某部冷门剧悄悄改变的,只顺着最近看会误判。于是他换了一种推理模式:像做“完形填空”一样,把你剧单中间的某部剧挖掉,根据前后文一起推断被挖掉的是什么。这就是 BERT4Rec 的双向掩码思路。

BERT4Rec 的训练方式很特别:训练时随机把序列中某些位置替换成特殊的 \[MASK\] 标记,让模型基于左右两侧的上下文去还原被遮住的物品。因为双向注意力可以看到全序列,模型学到的行为表示包含更丰富的前后关联——不仅能预测“下一个是什么”,还能理解“中间缺了什么”。上线做推荐时,再把某个待预测位置当作“被挖掉的位置”,模型输出该位置上所有候选物品的概率。

BERT4Rec 的预测目标可以写成:被掩码位置 i 的恢复概率,取决于除它之外的所有上下文行为。

SAS4Rec 和 BERT4Rec 的差别,就像“预言家”和“考古学家”的分工:预言家只朝一个方向推理,诚实可靠;考古学家左右开弓,能在残缺信息中还原真相。两者没有绝对优劣——需要严格按时间预测的场景 SAS4Rec 更稳,想从交互历史中学习丰富表征时 BERT4Rec 更强。实际工程中常常先用 BERT4Rec 练出用户表征,再用类 SAS4Rec 的模式做线上预测。

P(vi∣v\i)=softmax(hiW⊤)P(v_i \mid v_{\backslash i}) = \text{softmax}\left(\mathbf{h}_i \mathbf{W}^{\top}\right)
通俗地说:你的剧单里抽走一集,朋友看看你前后看的是什么,把缺的那集猜出来。
  1. 掩码上下文v_{\backslash i} 表示序列中除位置 i 外的所有行为,模型要利用它们推断位置 i 的内容。
    v\i=(v1,…,vi−1,[MASK],vi+1,…,vn)v_{\backslash i} = (v_1, \ldots, v_{i-1}, [\text{MASK}], v_{i+1}, \ldots, v_n)
  2. 双向编码BERT4Rec 的 Transformer 不加因果掩码,位置 i 的隐藏向量 \mathbf{h}_i 融合了左右两侧的信息。
    hi=BertBlock(v1,…,vi−1,[MASK],vi+1,…)i\mathbf{h}_i = \text{BertBlock}\left(v_1, \ldots, v_{i-1}, [\text{MASK}], v_{i+1}, \ldots\right)_i
  3. 恢复概率用 \mathbf{h}_i 对所有物品打分并归一化,得到被掩码位置上是各物品的概率。
    v^i=arg⁡max⁡vsoftmax(hiW⊤)v\hat{v}_i = \arg\max_v \text{softmax}\left(\mathbf{h}_i \mathbf{W}^{\top}\right)_v

位置编码设计

小明给你推荐时,顺序很关键:你周一看了《狂飙》、周二看了《漫长的季节》,和反过来看,意义完全不同。但如果把序列直接扔进自注意力,模型根本分不清先后——因为自注意力对输入做的是“无序的点积相似度”。假如把剧单顺序打乱,计算出的注意力分数一模一样。这意味着,行为序列中至关重要的“时间顺序”,必须由模型显式地补进去。

解决办法是给每个位置配一个“编号向量”,称为位置编码。SAS4Rec 和 BERT4Rec 采用的是可学习位置编码:模型准备一张位置嵌入表,位置 1 对应一个可训练向量,位置 2 对应另一个……训练时这些向量会不断调整,模型逐渐学会“位置 5 的行为在语义上不同于位置 1 的行为”。每个位置的输入都由“物品嵌入 + 位置嵌入”拼接融合而成。

写成公式就是:

位置编码的设计要点在于:位置表长度需要覆盖最长序列,超出最大长度的序列要做截断或分段处理;另外实践中也常看到“相对位置编码”“时间间隔编码”等变体,本质都是向模型注入“顺序感”。没有位置编码的序列推荐模型,就像朋友手里有一摞没写日期的剧单照片,再聪明的朋友也理不清时间线。

xp=evp+pp\mathbf{x}_p = \mathbf{e}_{v_p} + \mathbf{p}_p
通俗地说:每个行为贴上一张“这是第几步发生的”时间标签,模型读剧单时既知道看的是什么,也知道它的先后次序。
  1. 物品嵌入\mathbf{e}_{v_p} 把位置 p 上发生的物品 ID 映射成一个稠密向量,代表“看了什么”。
    evp=Embedding(vp)\mathbf{e}_{v_p} = \text{Embedding}(v_p)
  2. 位置嵌入\mathbf{p}_p 是位置 p 专属的可学习向量,代表“在第几步发生的”。
    pp=PositionEmbedding(p)\mathbf{p}_p = \text{PositionEmbedding}(p)
  3. 向量相加两个向量逐元素相加,得到模型真正的输入表示 \mathbf{x}_p,既有物品语义又含位置语义。
    xp=evp+pp\mathbf{x}_p = \mathbf{e}_{v_p} + \mathbf{p}_p

负采样与训练

训练小明推荐时,光给他看“正例”不够。你确实看了《锦绣未央》,但如果小明只知道这部剧好,不知道你其实对满屏的恐怖片毫无兴趣,他推荐的精度仍然上不去。模型需要有“负面教材”——从你从未看过的剧里挑出一些代表,明确告诉它:这些是低分选项。这个过程称为负采样。

为什么不能把所有你没看过的剧都当负例?因为物品库太大了——电商平台动辄百万件商品,全量计算 softmax 的时间和显存都不可接受。负采样的思路是:每次只随机抽 K 个(通常 5~20 个)未交互物品充当负例,配合一个正例组成迷你 softmax。为了保证负例质量,常用按热度采样:越是热门的物品你却没买过,越能说明你真的不喜欢,它当负例的信息量越大。

对应的损失函数是负采样后的交叉熵(多分类变体),公式如下:

训练策略上,SAS4Rec 会为序列中每个前缀构造一个训练样本——用前 1 个行为预测第 2 个、用前 2 个预测第 3 个……最大化利用每一条历史轨迹。BERT4Rec 则随机多次掩码序列中 10%~20% 的位置做还原任务,增强模型对上下文关联的建模能力。负采样让模型学会“说不”,训练策略则让每一条行为数据都物尽其用。

L=−log⁡exp⁡(s(vt+1))exp⁡(s(vt+1))+∑v−∈Nexp⁡(s(v−))\mathcal{L} = -\log \frac{\exp\left(s(v_{t+1})\right)}{\exp\left(s(v_{t+1})\right) + \sum_{v^{-} \in \mathcal{N}} \exp\left(s(v^{-})\right)}
通俗地说:把“你真正看的剧”和“随机抽出的几部你不会看的剧”放一起做一次小考试,训练模型把正例分打得最高。
  1. 正例得分s(v_{t+1}) 是模型给真实下一个行为的打分,分子保证这个分数尽量高。
    exp⁡(s(vt+1))\exp\left(s(v_{t+1})\right)
  2. 负例集合\mathcal{N} 是采样出的负例物品集合,每个负例得分为 s(v^{-})。
    N={v1−,v2−,…,vK−}\mathcal{N} = \{v^{-}_1, v^{-}_2, \ldots, v^{-}_K\}
  3. 迷你归一化分母把正例与所有负例的得分加在一起,softmax 变成“1 正 K 负”的小范围概率。
    exp⁡(s(vt+1))+∑v−∈Nexp⁡(s(v−))\exp\left(s(v_{t+1})\right) + \sum_{v^{-} \in \mathcal{N}} \exp\left(s(v^{-})\right)
  4. 负对数似然取负对数,正例概率越接近 1,损失越小;模型因此学会拉大正负例得分差距。
    −log⁡(⋅)-\log(\cdot)

模型评测对比

小明是怎么知道自己推荐得好不好的?他会拿历史数据做“模拟考”:把真实发生的下一个行为当作标准答案,看模型的推荐列表里有没有标准答案。如果每次都中,说明他真懂你;如果只是偶尔碰对,就得改进方法。这套“模拟考”在推荐系统里也有标准化试卷,其中最常用的是 HR@K(命中率)和 NDCG@K(归一化折损累积增益)。

HR@K 只管“推没推中”:把标准答案与模型给出的 Top-K 列表比对,在就在,不在就不在。NDCG@K 则更严格——不仅要求推中,还要求标准答案排在列表前面;排得越靠前,得分越高。两者结合,既看推荐名单的“广度命中”,也看排序的“质量”。

HR@K 的公式是:

在序列建模的选型上,自注意力模型与 RNN 家族的对比也常在评测中体现。RNN(如 GRU4Rec)严格按时间顺序逐个读入行为,天生的串行结构使其难以并行训练,且信息需要经过多个时间步传递,距离远的行为容易被“遗忘”。自注意力则一步到位:任意两个位置之间只有一次变换的距离,还支持并行计算,长序列上优势明显。当然,在极短的序列或强实时场景下,RNN 的增量更新依然有价值——选型要在数据规模、序列长度、响应速度之间权衡。

HR@K=1∣U∣∑u∈UI[vt+1(u)∈RuK]\text{HR}@K = \frac{1}{|\mathcal{U}|} \sum_{u \in \mathcal{U}} \mathbb{I}\left[v^{(u)}_{t+1} \in R_u^K\right]
通俗地说:让模型给每位用户开一张 Top-K 推荐清单,看真实答案是不是在清单里,算一下所有用户中的命中比例。
  1. 用户集合\mathcal{U} 是测试集里的全部用户,对每个用户各做一次评估。
    u∈Uu \in \mathcal{U}
  2. 推荐列表R_u^K 是模型为用户 u 生成的 Top-K 推荐物品集合。
    RuKR_u^K
  3. 命中指示函数\mathbb{I}[\cdot] 当真实下一行为出现在推荐列表中时取 1,否则取 0。
    I[vt+1(u)∈RuK]\mathbb{I}\left[v^{(u)}_{t+1} \in R_u^K\right]
  4. 全体平均对所有用户求平均,得到系统的整体命中率。
    1∣U∣∑u\frac{1}{|\mathcal{U}|} \sum_{u}

AI 应用场景

电商平台的“猜你喜欢”信息流

阶段:召回 + 排序

解决的问题:传统协同过滤只看用户整体偏好(“这个人爱买运动装备”),忽略了“买了帐篷后紧接着买睡袋和防潮垫”这种强顺序关联。序列推荐把浏览、加购、购买行为按时间排序建模,捕捉用户的连环购物动机。

具体案例:某电商平台用 BERT4Rec 处理用户近 30 天行为序列,随机掩码序列中间的商品做预训练,得到高质量用户行为表征。当用户在搜索“帐篷”后连续浏览“睡袋”“防潮垫”,模型识别出露营意图链,在首页信息流即时推荐露营灯、折叠椅,曝光转化率相比传统协同过滤提升约 12%。

视频平台的“自动连播”推荐

阶段:视频排流

解决的问题:长视频平台的完播顺序反映兴趣迁移——用户看完高燃悬疑剧后往往接一部轻松喜剧来调节心情。RNN 按顺序推流难以并行利用完整历史,且长距离信息容易丢失;SAS4Rec 的因果推理天然匹配“只看过去”的视频播放场景。

具体案例:某视频平台用 SAS4Rec 建模用户的观看序列(综艺 → 短剧 → 电影)。当识别到用户在深夜连续观看治愈系内容时,模型通过注意力权重捕捉到“情绪线索”,自动连播推荐节奏舒缓的纪录片,次日留存率提升 5%。

音乐App的“每日推荐”歌单

阶段:个性化召回

解决的问题:音乐推荐需要理解“单曲循环 → 换流派”的听歌顺序规律,以及“深夜听民谣、白天听电子”的时间模式。单一静态画像无法表达这种动态口味。

具体案例:某音乐 App 离线用 BERT4Rec 做预训练:随机掩码用户播放历史中的若干歌曲,让模型理解上下文关联;线上召回阶段切换为类 SAS4Rec 的因果推理,从最近播放记录出发生成候选歌单。混合策略既吸收了双向预训练的表征能力,又保持了在线推理的速度。

Python 演示

python68 行
import numpy as npnp.random.seed(42)  class SimplifiedSAS4Rec:    """Simplify SAS4Rec: self-attention + causal mask + negative sampling."""     def __init__(self, n_items, seq_len=6, d=16):        self.n_items = n_items        self.d = d        self.item_emb = np.random.randn(n_items, d) * 0.1        self.pos_emb = np.random.randn(seq_len, d) * 0.1        self.wq = np.random.randn(d, d) * 0.1        self.wk = np.random.randn(d, d) * 0.1        self.wv = np.random.randn(d, d) * 0.1        self.out_proj = np.random.randn(d, n_items) * 0.1     def attention(self, x, mask):        q = x @ self.wq        k = x @ self.wk        v = x @ self.wv        scores = q @ k.T / np.sqrt(self.d)        scores += mask        weights = np.exp(scores - scores.max(-1, keepdims=True))        weights = weights / weights.sum(-1, keepdims=True)        return weights @ v, weights     def forward(self, seq):        L = len(seq)        x = self.item_emb[seq] + self.pos_emb[:L]        mask = np.triu(np.ones((L, L)) * -1e9, k=1)        h, attn_w = self.attention(x, mask)        logits = h[-1] @ self.out_proj        return logits, attn_w     def train_step(self, seq, target, lr=0.05, neg_k=5):        logits, _ = self.forward(seq)        neg_ids = np.random.choice(            [j for j in range(self.n_items) if j != target],            size=neg_k, replace=False)        ids = np.concatenate([[target], neg_ids])        sub_logits = logits[ids] - logits[ids].max()        exp = np.exp(sub_logits)        probs = exp / exp.sum()        loss = -np.log(probs[0])        grad_out = probs.copy()        grad_out[0] -= 1.0        h_last = self.item_emb[seq[-1]] + self.pos_emb[len(seq) - 1]        g_proj = np.zeros_like(self.out_proj)        for i, item_id in enumerate(ids):            g_proj[:, item_id] += grad_out[i] * h_last        self.out_proj -= lr * g_proj        return loss, probs[0]  def demo():    n_items = 50    model = SimplifiedSAS4Rec(n_items)    seq = [3, 7, 15, 22, 8]    target = 12    for step in range(200):        loss, pos_prob = model.train_step(seq, target)        if step % 40 == 0:            print(f"step {step}: loss={loss:.3f}, P(target)={pos_prob:.3f}")  if __name__ == "__main__":    demo()
目的:用 NumPy 实现一个简化版 SAS4Rec,演示自注意力、因果掩码和负采样交叉熵损失的核心计算流程。运行后可以看到正例概率逐步上升、损失下降,说明模型在“学会推荐目标物品”。
  1. mask = np.triu(np.ones((L, L)) * -1e9, k=1)构造上三角为负无穷的矩阵,把未来位置的注意力分数屏蔽掉,实现因果掩码。
  2. q @ k.T / np.sqrt(self.d)查询与键做缩放点积,算出每对位置之间的相关度,再除以维度平方根防止数值过大。
  3. weights = weights / weights.sum(-1, keepdims=True)对注意力权重做归一化,让每一行的关注度之和为 1。
  4. logits = h[-1] @ self.out_proj取序列最后一个位置的隐藏表示,与物品嵌入矩阵相乘,给每个候选物品打分。
  5. np.random.choice([...], size=neg_k, replace=False)从非目标物品中不放回随机采样多个负例,构造“你没看过的剧”。
  6. grad_out[0] -= 1.0计算交叉熵损失对 softmax 输出的梯度:正例位置减 1,负例保持原概率。
  7. g_proj[:, item_id] += grad_out[i] * h_last把梯度从输出层反传到输出投影矩阵,只更新与参与 softmax 的物品相关的列。
  8. self.out_proj -= lr * g_proj执行一步梯度下降,让正例得分升高、负例得分降低。

常见误区

误区“SAS4Rec 和 BERT4Rec 都用了自注意力,应该就是换个名字炒作吧?学会一个另一个自然也就会了。”

正解两者核心区别在于掩码方式:SAS4Rec 用因果掩码,只能看过去预测未来;BERT4Rec 用双向掩码,随机挖掉序列中间位置、根据上下文还原。这就像“只往前看的预言家”和“前后都能看的考古学家”,推理方向完全不同,训练任务也不同。只学一个,无法理解另一种建模思路。

为何常见:两者结构骨架都是 Transformer 编码器,外观高度相似,初学者容易被表象迷惑。

误区“序列里已经有物品 ID 了,位置信息模型反正是能自己学出来的,加不加位置编码差别不大。”

正解自注意力本身对位置不敏感——把输入序列顺序打乱,注意力计算结果完全一样。就像朋友拿到一叠没有拍摄日期的照片,他无法判断哪张是最近拍的。位置编码是模型获得“时间感”的唯一来源,去掉它序列推荐会显著退化,这在 SAS4Rec 和 BERT4Rec 的论文实验中都有直接证据。

为何常见:自注意力的输入看起来是“一堆向量”,新手容易忽略这些向量里其实并不包含任何先后顺序信息。

误区“给注意力加个掩码挡住一些位置,数据量变少了,训练肯定更快吧?”

正解掩码的用途不是省计算,而是“控制注意力能看的信息范围”。因果掩码把未来位置设成负无穷、让 softmax 权重归零,保证预测时不发生信息泄漏;BERT4Rec 的掩码则是为了构造“完形填空”的监督信号。它本质是一种信息控制机制,而不是加速器。

为何常见:“mask“直译是”屏蔽”,初学者容易望文生义,以为是删掉部分数据来减少计算量。

误区“用户没点过的物品当然都是负样本,那就全部拿来训练,负例越多模型越准。”

正解把所有未交互物品当负例既不现实也没必要。物品库动辄百万级,全量 softmax 的计算和存储开销巨大。标准做法是每次随机采样少量负例(如 5~20 个),或用按热度采样——越热门的物品用户却没交互过,越能提供高信息量的“反面教材”。负采样本质是“用少数代表性反例逼近全量分布”。

为何常见:“没点过就是不喜欢”的直觉听起来合理,但忽略了真实推荐场景中数据规模与采样效率的硬约束。

误区“Transformer 最厉害,所有序列推荐场景无脑上 SAS4Rec 就行,RNN 已经过气了。”

正解自注意力在长序列建模与并行训练上优势明显,但并非万能:序列长度只有 3~5 时,两者差距不大;在线实时推荐场景中,RNN 的增量更新天然适合流式输入;自注意力的复杂度随序列长度平方增长,超长序列反而更慢。选模型要在序列长度、训练成本、在线延迟之间做权衡,就像再聪明的朋友也要分场合选择聊天方式。

为何常见:看到 SAS4Rec/BERT4Rec 在论文数据集上刷榜,容易误以为它们在所有工业场景都碾压 RNN,忽略了实验环境与真实业务环境的差异。

面试题

Q1. SAS4Rec 的因果掩码核心作用是?

单选题 · easy

  • A.让注意力只看过去行为← 正确答案
  • B.让注意力只看未来行为
  • C.让注意力同时看前后文
  • D.让注意力不参考任何行为

答案:A。因果掩码把注意力分数矩阵的未来位置置为负无穷,softmax 后权重归零,因此每个位置只能参考自己和更早的位置。这就像小明预测你下一部剧时只准看你已看过的剧单,绝不能偷看你还未看的剧。B 是反向掩码,C 是 BERT4Rec 的双向掩码,D 则是完全无注意力。

Q2. 下列哪个场景最适合用序列推荐建模?

单选题 · easy

  • A.分析用户历史浏览顺序← 正确答案
  • B.计算商品之间的相似度
  • C.对用户群体做画像归纳
  • D.对商品进行类目划分

答案:A。序列推荐的核心是利用“行为发生的先后顺序”来预测下一步行为,因此“历史浏览顺序”是它的直接适用场景。商品相似度通常用协同过滤或向量召回;用户画像归纳是聚类任务;类目划分是分类任务,三者都不依赖行为序列中的先后关系。

Q3. BERT4Rec 的训练任务本质上属于?

单选题 · mid

  • A.序列到序列的翻译任务
  • B.掩码行为的完形填空任务← 正确答案
  • C.二分类的点击率预估任务
  • D.多分类的商品聚类任务

答案:B。BERT4Rec 训练时随机掩码序列中的若干位置,让模型依据左右上下文还原被遮住的行为,这正对应完形填空任务。它不是翻译(输入输出是不同模态),不是二分类,也不是聚类。就像朋友看你剧单中间缺了一部剧,根据前后剧情猜你缺看了哪部。

Q4. 位置编码的核心作用是?

单选题 · mid

  • A.保留行为发生的先后次序← 正确答案
  • B.增加模型的非线性表达能力
  • C.对行为序列做长度对齐
  • D.把高维向量映射到低维空间

答案:A。自注意力本身对输入顺序不敏感,位置编码将“第几步发生”的信息注入模型,保留行为发生的先后次序。非线性表达靠激活函数实现,长度对齐靠 padding,维度变换靠投影矩阵,都与位置编码无关。记住:没有位置编码,模型认不出“先看 A 再看 B”和“先看 B 再看 A”的区别。

Q5. 与 GRU4Rec 相比,自注意力在长序列上的主要优势是?

单选题 · hard

  • A.可并行且能捕捉远距离依赖← 正确答案
  • B.参数更少且训练速度更快
  • C.不需要任何位置信息参与
  • D.天然支持增量式在线更新

答案:A。RNN 必须按时间步串行读入序列,长距离信息要经过多步传递,容易遗忘;自注意力让任意两个位置之间只有一次变换距离,且所有位置的注意力可以并行计算。参数多少并不绝对由结构决定,位置信息仍然需要位置编码补充,而 RNN 反而更适合增量更新,所以 B、C、D 都不对。

Q6. 关于负采样策略,描述正确的有?

多选题 · mid

  • A.随机挑选未交互物品当负例← 正确答案
  • B.负采样可以降低计算开销← 正确答案
  • C.负样本数量越多效果必然越好
  • D.按物品热度采样是一种常见做法← 正确答案

答案:ABD。负采样从用户未交互的物品里随机抽取代表作为负例,将全量 softmax 降为“1 正 K 负”的小规模计算,因此能显著降低开销,A、B 正确。但负样本数量不是越多越好——过多会引入噪声并增加训练成本,C 太绝对。按热度采样是公认的有效策略:用户没买热门物品是更强的“不喜欢”信号,D 正确。

Q7. 序列推荐与矩阵分解最核心的区别是?

单选题 · easy

  • A.是否考虑用户行为的先后顺序← 正确答案
  • B.是否使用深度学习网络
  • C.是否输出推荐概率值
  • D.是否使用梯度下降优化方法

答案:A。矩阵分解把用户行为压缩成一个静态向量,不区分“昨天看了什么”和“去年看了什么”;序列推荐则把行为按时间组织成序列并显式建模顺序。深度学习、概率输出、梯度下降都不是两者的本质差异——矩阵分解也可以配概率模型和优化算法。

Q8. BERT4Rec 双向注意力的特点包括?

多选题 · hard

  • A.每个位置可以参考全序列信息← 正确答案
  • B.掩码位置表示融合左右上下文← 正确答案
  • C.训练时每个位置都独立预测下一位
  • D.与 SAS4Rec 的掩码方式完全相同

答案:AB。BERT4Rec 不设因果掩码,注意力可以覆盖整个序列中的任意位置,因此掩码位置能同时利用左右两侧的上下文,A、B 正确。C 描述的是 SAS4Rec 的自回归式逐步预测,而非 BERT4Rec 的掩码还原;D 更是直接错误——两者掩码方式恰恰相反。

Q9. SAS4Rec 训练时如何构造训练样本?

单选题 · mid

  • A.取完整序列直接预测最后一位
  • B.用每个前缀预测后续对应位置← 正确答案
  • C.随机打乱序列后再做训练
  • D.只取序列前半段参与训练

答案:B。SAS4Rec 充分利用行为序列的每个前缀:用前 1 个行为预测第 2 个、前 2 个预测第 3 个……每个前缀位置都生成一个训练样本,最大化数据利用率。直接预测最后一位浪费了中间监督信号;打乱序列破坏因果顺序;只取前半段则丢弃了后半段信息。

Q10. 关于 SAS4Rec 与 BERT4Rec 对比,说法正确的有?

多选题 · extreme

  • A.自回归式预测下一个行为← 正确答案
  • B.双向编码加完形填空任务← 正确答案
  • C.因果掩码可以看未来信息
  • D.双向模型一次只训练一个位置

答案:AB。SAS4Rec 是自回归式建模,用过去的序列预测下一个行为,A 正确;BERT4Rec 采用双向编码,训练任务是掩码还原(完形填空),B 正确。因果掩码恰恰不允许看未来,C 错误;BERT4Rec 每轮训练可以同时掩码多个位置、并行还原,D 错误。二者互补:一个适合严格时序推理,一个适合学习丰富表征。