【推荐系统4】序列召回
目录
MIND (Multi-Interest Network with Dynamic Routing)模型
本文为funrec指导学习笔记,并整合网络资料,个人理解等完成。
序列召回就是要利用用户行为的时间顺序信息来进行推荐。它的基本想法是:用户的当前兴趣不仅取决于他过去喜欢什么,还取决于他最近在做什么,以及这些行为的顺序。
深化用户的兴趣表示
改进用户的兴趣表示方法
MIND (Multi-Interest Network with Dynamic Routing)模型
不用一个向量来概括用户的复杂性,而应该用一组向量,其中每个向量代表一个独立的兴趣维度。
MIND 模型在常规 Embedding 层基础上,核心新增两个组件:
- 多兴趣提取层:解决用户兴趣多样性问题,通过B2I(Behavior to Interest)动态路由算法,将用户历史行为(行为胶囊)聚合为多个兴趣向量(兴趣胶囊)
- 使用共享变换矩阵:所有行为胶囊共用一个双线性映射矩阵,确保兴趣向量处于同一表示空间,便于后续计算。
- 动态兴趣数量:根据用户历史行为数量动态调整兴趣数量,行为少的用户用少量兴趣节省资源,活跃用户用多兴趣更精准表示。
- Label-aware 注意力层:应用于训练阶段,从用户的多个兴趣向量中,筛选出与目标商品(标签)最相关的兴趣向量,为训练提供明确指引
- 注意力集中度超参数p:p值越大,注意力越集中于与目标商品最相似的兴趣向量;当p=0时,所有兴趣向量的权重相同
- 使用 sampled softmax 损失函数:针对 “正样本 + 采样出的负样本”做softmax归一化
SDM
融合长短期兴趣,捕捉动态变化
SDM模型 的核心思想是分别建模用户的短期即时兴趣和长期稳定偏好,然后智能地融合它们。

左下角借助LSTM捕捉短期兴趣,左上角讲长期兴趣按照特征分为多个子集,最终利用类似LSTM门控机制的原理融合二者
代码练习:MIND
B2I类
class B2I(nn.Module):
def __init__(self, embedding_dim, num_behaviors):
super(B2I, self).__init__()
self.embedding = nn.Embedding(num_behaviors, embedding_dim)
self.linear = nn.Linear(embedding_dim, embedding_dim)
def forward(self, behaviors):
# 获取行为的嵌入表示
behaviors_embedding = self.embedding(behaviors)
# 进行双线映射
return self.linear(behaviors_embedding)
MIND类
class MIND(nn.Module):
def __init__(self, num_behaviors, embedding_dim):
super(MIND, self).__init__()
self.b2i = B2I(embedding_dim, num_behaviors)
# 用于注意力机制
self.linear = nn.Linear(embedding_dim, 1)
def forward(self, behaviors):
# 获取多个兴趣向量
interest_vectors = self.b2i(behaviors)
# 计算注意力权重
weights = F.softmax(self.linear(interest_vectors), dim=0)
# 加权求和获得用户最终的兴趣表示
user_interest_representation = torch.sum(weights * interest_vectors, dim=0)
return user_interest_representation
运行结果

生成式召回方法
把推荐看作序列预测问题,直接对序列中物品与物品之间的动态依赖关系进行建模,关注“用户接下来会做什么”而非“用户是怎样的人”
SASRec
启发于Transformer,将用户的行为序列视为一个句子来处理,主要包含自注意力层和前馈网络层(增强非线性变换的能力)

代码
用ai生成了简单的例子学习。
FFN
class PointWiseFeedForward(nn.Module):
"""
点向的前馈网络 (FFN),增强非线性变换的能力。
在 SASRec 中,通常是 线性 -> 激活 -> 线性 的结构。
"""
def __init__(self, hidden_size, dropout_rate):
super(PointWiseFeedForward, self).__init__()
self.conv1 = nn.Conv1d(hidden_size, hidden_size, kernel_size=1)
self.dropout = nn.Dropout(dropout_rate)
self.relu = nn.ReLU()
self.conv2 = nn.Conv1d(hidden_size, hidden_size, kernel_size=1)
def forward(self, inputs):
# inputs 形状: (batch_size, seq_len, hidden_size)
# Conv1d 需要 (N, C, L) 形状,所以需要转置
outputs = self.conv1(inputs.transpose(1, 2))
outputs = self.relu(outputs)
outputs = self.dropout(outputs)
outputs = self.conv2(outputs).transpose(1, 2)
# outputs 形状: (batch_size, seq_len, hidden_size)
return outputs
单个 Transformer 编码器块
class SASRecBlock(nn.Module):
"""
单个 Transformer 编码器块,包含 Multi-Head Attention 和 FFN。
"""
def __init__(self, hidden_size, num_heads, dropout_rate):
super(SASRecBlock, self).__init__()
# 自注意力层
self.attn_layernorm = nn.LayerNorm(hidden_size, eps=1e-8)
self.attn_layer = nn.MultiheadAttention(hidden_size, num_heads, dropout=dropout_rate, batch_first=True)
self.attn_dropout = nn.Dropout(dropout_rate)
# 前馈网络层
self.ffn_layernorm = nn.LayerNorm(hidden_size, eps=1e-8)
self.ffn_layer = PointWiseFeedForward(hidden_size, dropout_rate)
self.ffn_dropout = nn.Dropout(dropout_rate)
def forward(self, inputs, attention_mask):
q_norm = self.attn_layernorm(inputs)
seq_len = inputs.shape[1]
mask_2d = attention_mask[0, :, :].float() * -1e9 # 0->0, 1->-inf
# Q, K, V 都是 q_norm
attn_outputs, _ = self.attn_layer(q_norm, q_norm, q_norm, attn_mask=mask_2d)
# 残差连接 + Dropout
outputs = inputs + self.attn_dropout(attn_outputs)
# 2. 前馈网络部分
# LayerNorm -> FFN -> Dropout -> Add(残差连接)
outputs = self.ffn_layernorm(outputs) # inputs -> outputs
ffn_outputs = self.ffn_layer(outputs)
# 残差连接 + Dropout
outputs = outputs + self.ffn_dropout(ffn_outputs)
# outputs 形状: (batch_size, seq_len, hidden_size)
return outputs
模型主体
负责嵌入、位置编码、堆叠 Block 和 Masking
class SASRec(nn.Module):
def __init__(self, item_num, max_len, hidden_size, num_heads, num_blocks, dropout_rate):
super(SASRec, self).__init__()
self.item_num = item_num # 物品总数 (包括PAD=0)
self.max_len = max_len # 最大序列长度
self.hidden_size = hidden_size
# 嵌入层
# Item 嵌入 (item_num 包含了一个用于 padding 的 0)
self.item_emb = nn.Embedding(item_num, hidden_size, padding_idx=0)
# Positional 嵌入 (位置编码)
self.pos_emb = nn.Embedding(max_len, hidden_size)
self.emb_dropout = nn.Dropout(dropout_rate)
# Transformer 编码器堆叠
self.blocks = nn.ModuleList([
SASRecBlock(hidden_size, num_heads, dropout_rate)
for _ in range(num_blocks)
])
self.final_layernorm = nn.LayerNorm(hidden_size, eps=1e-8)
def forward(self, item_seq):
"""
:param item_seq: 用户行为序列 (batch_size, seq_len)
:return: 预测向量 (batch_size, seq_len, hidden_size)
"""
item_vectors = self.item_emb(item_seq)
positions = torch.arange(item_seq.shape[1], dtype=torch.long, device=item_seq.device)
pos_vectors = self.pos_emb(positions)
# 嵌入相加: 物品嵌入 + 位置嵌入
outputs = item_vectors + pos_vectors
outputs = self.emb_dropout(outputs)
seq_len = item_seq.shape[1]
causality_mask = torch.tril(torch.ones((seq_len, seq_len), dtype=torch.bool, device=item_seq.device)).unsqueeze(0)
# 形状 (1, seq_len, seq_len), True 表示可见,False 表示不可见 (需要设置成 -inf)
# item_seq == 0 表示 padding 部分 (需要一同 mask 掉,以免影响计算)
padding_mask = (item_seq != 0).unsqueeze(1).repeat(1, seq_len, 1)
# padding_mask: (B, 1, L) -> (B, L, L), True 表示非 padding
# 最终 Attention Mask: 既不能是未来物品,也不能是 padding
# (B, L, L) 形状,False 表示需要 mask
attention_mask = causality_mask & padding_mask
for block in self.blocks:
# attention_mask: False 的位置在 MultiheadAttention 中会被忽略/设为 -inf
outputs = block(outputs, attention_mask)
outputs = self.final_layernorm(outputs)
return outputs
def predict(self, item_seq):
hidden_states = self.forward(item_seq)
return hidden_states[:, -1, :] # (batch_size, hidden_size)
运行结果

结果分析
这个向量是从 (4, 50, 64) 中提取出来的,代表了模型认为最能概括用户当前兴趣的总结向量。
-
在训练时, 模型用这个向量来计算损失,指导模型学习下一个应该推荐的物品是什么。
-
在召回时, 这个 向量就是用户的 “查询向量 (Query Vector)”。系统将它与所有物品的嵌入向量进行对比,找到相似度最高的物品,作为推荐结果。
HSTU(提升特征输入侧)
SASRec 仅聚焦于由物品ID构成的序列
HSTU 学习这个复杂的句子(融入了用户的属性,行为,点击等等),并预测下一个可能的事件。
- 特征处理:类别特征按时间 “拉平” 成事件序列,数值特征通过行为模式隐式建模。
- HSTU 单元:用统一模块替代传统推荐模型的多个异构组件,包含点向投影、点向聚合、点向转换三个子模块,分别处理特征提取、交互和转换。
- 替换 softmax:放弃传统 softmax 归一化,用点向聚合保留用户兴趣的 “绝对强度”,避免偏好信号被削弱。
TIGER(提升特征输出侧)
将推荐目标从 “匹配 ID” 升级为 “生成物品语义描述”,解决了输出内容低效的问题,通过语义 ID 重塑推荐终点
更多推荐


所有评论(0)