目录
- 推荐系统基础理论
- 协同过滤
- 矩阵分解
- 深度学习推荐模型
- 序列推荐
- 多任务学习
- 知识图谱推荐
- 图神经网络推荐
- 评估指标与实践
- 工业应用与前沿
1. 推荐系统基础理论
1.1 什么是推荐系统
推荐系统的目标:
根据用户的历史行为和偏好,预测用户对物品的喜好
为用户推荐可能感兴趣的物品
┌─────────────────────────────────────────────────────────────────┐
│ 推荐系统应用场景 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 电商推荐: │
│ ├── 商品推荐: "猜你喜欢" │
│ ├── 相关推荐: "买了又买" │
│ └── 个性化首页: 千人千面 │
│ │
│ 内容推荐: │
│ ├── 新闻推荐: 今日头条 │
│ ├── 短视频推荐: 抖音/TikTok │
│ ├── 长视频推荐: YouTube/B站 │
│ └── 音乐推荐: Spotify/网易云音乐 │
│ │
│ 社交推荐: │
│ ├── 好友推荐: "你可能认识的人" │
│ └── 内容推荐: 朋友圈/微博 │
│ │
│ 广告推荐: │
│ ├── 个性化广告 │
│ └── 竞价排名 │
│ │
└─────────────────────────────────────────────────────────────────┘
1.2 推荐系统的形式化
推荐问题的形式化:
给定:
用户集合 U = {u₁, u₂, ..., u_m}
物品集合 I = {i₁, i₂, ..., i_n}
交互历史 R = {(u, i, r, t)} (用户-物品-评分-时间)
目标:
学习预测函数 f: U × I → R̂
预测用户 u 对物品 i 的评分/点击概率
矩阵视角:
用户-物品交互矩阵 R ∈ ℝ^{m×n}
R[u, i] = 用户 u 对物品 i 的评分/交互
大多数元素缺失 (稀疏)
推荐 = 矩阵补全 (预测缺失值)
1.3 推荐系统分类
┌─────────────────────────────────────────────────────────────────────┐
│ 推荐系统方法分类 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 协同过滤 (Collaborative Filtering, CF) │
│ ├── 基于用户的 CF: 相似用户喜欢的物品 │
│ ├── 基于物品的 CF: 相似物品 │
│ └── 矩阵分解: 学习用户/物品隐向量 │
│ │
│ 2. 基于内容的推荐 (Content-Based) │
│ └─ 根据物品特征推荐相似物品 │
│ │
│ 3. 混合推荐 (Hybrid) │
│ └─ 结合多种方法 │
│ │
│ 4. 深度学习推荐 (Deep Learning) │
│ ├── Wide & Deep: 记忆 + 泛化 │
│ ├── DeepFM: FM + DNN │
│ ├── DIN: 注意力机制 │
│ └── DIEN: 兴趣演化 │
│ │
│ 5. 序列推荐 (Sequential Recommendation) │
│ └─ 建模用户行为序列 │
│ │
│ 6. 图推荐 (Graph-Based) │
│ └─ 用户-物品交互建模为图 │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.4 推荐系统架构
工业推荐系统架构:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 召回 (Recall) 粗排 (Pre-Ranking) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 多路召回 │ │ 简单模型 │ │
│ │ - CF 召回 │───────►│ - 双塔模型 │ │
│ │ - 热门召回 │ │ - 轻量 DNN │ │
│ │ - 向量召回 │ │ │ │
│ │ - 图召回 │ │ 数百→数千 │ │
│ │ │ └──────┬───────┘ │
│ │ 数万→数千 │ │ │
│ └──────────────┘ ▼ │
│ 精排 (Ranking) │
│ ┌──────────────┐ │
│ │ 复杂模型 │ │
│ │ - DeepFM │ │
│ │ - DIN/DIEN │ │
│ │ - 多任务模型 │ │
│ │ │ │
│ │ 数百→数十 │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ 重排 (Re-Ranking) │
│ ┌──────────────┐ │
│ │ 策略调整 │ │
│ │ - 多样性 │ │
│ │ - 新鲜度 │ │
│ │ - 业务规则 │ │
│ │ │ │
│ │ 最终展示 │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
各阶段的作用:
召回: 从海量物品中快速筛选候选 (速度优先)
粗排: 初步排序,减少候选数量
精排: 精细排序,质量优先
重排: 考虑多样性、新鲜度等因素
2. 协同过滤
2.1 基于用户的协同过滤
核心思想:
相似的用户可能喜欢相似的物品
步骤:
1. 找到与目标用户相似的用户 (邻居)
2. 用邻居的评分预测目标用户的评分
预测公式:
r̂(u, i) = r̄_u + Σ_{v∈N(u)} sim(u, v) · (r_{v,i} - r̄_v) / Σ |sim(u, v)|
其中:
r̄_u: 用户 u 的平均评分
N(u): 用户 u 的邻居集合
sim(u, v): 用户 u 和 v 的相似度
相似度计算:
1. 余弦相似度:
sim(u, v) = (r_u · r_v) / (‖r_u‖ · ‖r_v‖)
2. 皮尔逊相关系数:
sim(u, v) = Σ_i (r_{u,i} - r̄_u)(r_{v,i} - r̄_v) / (√Σ(r_{u,i}-r̄_u)² · √Σ(r_{v,i}-r̄_v)²)
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
class UserBasedCF:
"""
基于用户的协同过滤
理论:
相似用户喜欢相似物品
用邻居的评分加权预测
优点: 简单直观
缺点: 用户数多时计算量大
"""
def __init__(self, k=20):
self.k = k # 邻居数量
def fit(self, ratings):
"""
ratings: 用户-物品评分矩阵 [n_users, n_items]
"""
self.ratings = ratings
self.user_mean = np.true_divide(
ratings.sum(1),
(ratings != 0).sum(1),
where=(ratings != 0).sum(1) != 0
)
# 计算用户相似度矩阵
# 使用评分向量的余弦相似度
ratings_normalized = ratings - self.user_mean[:, None] * (ratings != 0)
self.sim_matrix = cosine_similarity(ratings_normalized)
np.fill_diagonal(self.sim_matrix, 0) # 排除自己
def predict(self, user, item):
"""
预测用户对物品的评分
"""
# 找到对物品有评分的邻居
rated_users = np.where(self.ratings[:, item] != 0)[0]
if len(rated_users) == 0:
return self.user_mean[user]
# 选择最相似的 k 个邻居
similarities = self.sim_matrix[user, rated_users]
top_k_idx = np.argsort(similarities)[-self.k:]
top_k_users = rated_users[top_k_idx]
top_k_sims = similarities[top_k_idx]
# 加权平均
if np.sum(np.abs(top_k_sims)) == 0:
return self.user_mean[user]
pred = self.user_mean[user] + np.sum(
top_k_sims * (self.ratings[top_k_users, item] - self.user_mean[top_k_users])
) / np.sum(np.abs(top_k_sims))
return pred
"""
基于用户 CF 的问题:
1. 稀疏性: 用户-物品矩阵很稀疏
2. 冷启动: 新用户没有历史数据
3. 可扩展性: 用户数多时计算量大
解决: 基于物品的 CF、矩阵分解
"""
2.2 基于物品的协同过滤
核心思想:
相似的物品会被相似的用户喜欢
步骤:
1. 计算物品之间的相似度
2. 用用户历史交互的相似物品预测评分
预测公式:
r̂(u, i) = Σ_{j∈I(u)} sim(i, j) · r_{u,j} / Σ |sim(i, j)|
其中:
I(u): 用户 u 交互过的物品集合
sim(i, j): 物品 i 和 j 的相似度
优势:
物品数量通常比用户数量少
物品相似度可以离线计算
更稳定 (物品变化比用户慢)
class ItemBasedCF:
"""
基于物品的协同过滤
理论:
相似物品会被相似用户喜欢
用用户历史中的相似物品预测
优势:
- 物品数通常少于用户数
- 物品相似度更稳定
- 可以离线计算
"""
def __init__(self, k=20):
self.k = k
def fit(self, ratings):
self.ratings = ratings
# 计算物品相似度矩阵
self.sim_matrix = cosine_similarity(ratings.T) # [n_items, n_items]
np.fill_diagonal(self.sim_matrix, 0)
def predict(self, user, item):
# 用户交互过的物品
rated_items = np.where(self.ratings[user] != 0)[0]
if len(rated_items) == 0:
return 0
# 选择最相似的 k 个物品
similarities = self.sim_matrix[item, rated_items]
top_k_idx = np.argsort(similarities)[-self.k:]
top_k_items = rated_items[top_k_idx]
top_k_sims = similarities[top_k_idx]
# 加权平均
if np.sum(np.abs(top_k_sims)) == 0:
return 0
pred = np.sum(top_k_sims * self.ratings[user, top_k_items]) / np.sum(np.abs(top_k_sims))
return pred
"""
ItemCF vs UserCF:
UserCF:
适合用户数 << 物品数的场景
推荐结果新颖性高
ItemCF:
适合物品数 << 用户数的场景
推荐结果更稳定
实践中 ItemCF 更常用
"""
3. 矩阵分解
3.1 基本矩阵分解
论文: "Matrix Factorization Techniques for Recommender Systems" (Koren et al., 2009)
核心思想:
将用户-物品评分矩阵分解为用户矩阵和物品矩阵的乘积
R ≈ U · V^T
其中:
R ∈ ℝ^{m×n}: 用户-物品评分矩阵
U ∈ ℝ^{m×k}: 用户隐因子矩阵
V ∈ ℝ^{n×k}: 物品隐因子矩阵
k: 隐因子维度 (通常 10-200)
理论:
每个用户用 k 维向量表示
每个物品用 k 维向量表示
评分 = 用户向量与物品向量的点积
r̂(u, i) = u_u · v_i = Σ_f u_{u,f} · v_{i,f}
优化目标:
min Σ_{(u,i)∈observed} (r_{u,i} - u_u · v_i)² + λ(‖u_u‖² + ‖v_i‖²)
最小化观测评分的平方误差 + L2 正则化
import torch
import torch.nn as nn
class MatrixFactorization(nn.Module):
"""
矩阵分解模型
R ≈ U · V^T
理论:
将稀疏的评分矩阵分解为稠密的用户/物品向量
用向量点积预测评分
"""
def __init__(self, num_users, num_items, embedding_dim=64):
super().__init__()
# 用户嵌入
self.user_embedding = nn.Embedding(num_users, embedding_dim)
# 物品嵌入
self.item_embedding = nn.Embedding(num_items, embedding_dim)
# 用户和物品偏置
self.user_bias = nn.Embedding(num_users, 1)
self.item_bias = nn.Embedding(num_items, 1)
# 全局偏置
self.global_bias = nn.Parameter(torch.zeros(1))
# 初始化
nn.init.normal_(self.user_embedding.weight, std=0.01)
nn.init.normal_(self.item_embedding.weight, std=0.01)
nn.init.zeros_(self.user_bias.weight)
nn.init.zeros_(self.item_bias.weight)
def forward(self, user_ids, item_ids):
"""
user_ids: [B] 用户 ID
item_ids: [B] 物品 ID
返回: [B] 预测评分
"""
# 嵌入查找
user_emb = self.user_embedding(user_ids) # [B, D]
item_emb = self.item_embedding(item_ids) # [B, D]
user_b = self.user_bias(user_ids).squeeze() # [B]
item_b = self.item_bias(item_ids).squeeze() # [B]
# 预测: 全局偏置 + 用户偏置 + 物品偏置 + 用户·物品
pred = (self.global_bias + user_b + item_b +
(user_emb * item_emb).sum(dim=1))
return pred
"""
矩阵分解的理论分析:
1. 隐因子的含义:
每个维度可以解释为某种"特质"
例如: 动作程度、喜剧程度、文艺程度
2. 几何解释:
用户和物品在同一隐空间中
距离近 = 喜好度高
3. 泛化能力:
即使两个用户没有共同评分的物品
也可以通过隐向量比较相似度
"""
3.2 SVD++
论文: "Factorization Meets the Neighborhood: A Multifaceted Collaborative
Filtering Model" (Koren, 2008)
改进:
在矩阵分解的基础上,加入隐式反馈信息
预测公式:
r̂(u, i) = μ + b_u + b_i + v_i · (u_u + |N(u)|^{-1/2} Σ_{j∈N(u)} y_j)
其中:
N(u): 用户 u 交互过的物品集合 (隐式反馈)
y_j: 物品 j 的辅助嵌入
理论:
不仅用用户的显式评分向量
还用用户的隐式行为 (点击、浏览等)
class SVDPlusPlus(nn.Module):
"""
SVD++ 模型
在 MF 基础上加入隐式反馈
理论:
用户表示 = 显式嵌入 + 隐式行为聚合
捕获用户的历史行为模式
"""
def __init__(self, num_users, num_items, embedding_dim=64):
super().__init__()
# 显式嵌入
self.user_embedding = nn.Embedding(num_users, embedding_dim)
self.item_embedding = nn.Embedding(num_items, embedding_dim)
# 隐式反馈嵌入
self.item_auxiliary = nn.Embedding(num_items, embedding_dim)
# 偏置
self.user_bias = nn.Embedding(num_users, 1)
self.item_bias = nn.Embedding(num_items, 1)
self.global_bias = nn.Parameter(torch.zeros(1))
def forward(self, user_ids, item_ids, history_items, history_mask):
"""
user_ids: [B]
item_ids: [B]
history_items: [B, L] 用户历史物品
history_mask: [B, L] 历史掩码
"""
# 显式嵌入
user_emb = self.user_embedding(user_ids)
item_emb = self.item_embedding(item_ids)
# 隐式反馈聚合
history_emb = self.item_auxiliary(history_items) # [B, L, D]
history_emb = history_emb * history_mask.unsqueeze(-1) # 掩码
implicit_feedback = history_emb.sum(dim=1) / (history_mask.sum(dim=1, keepdim=True) + 1e-8).sqrt()
# 用户表示 = 显式 + 隐式
user_combined = user_emb + implicit_feedback
# 预测
user_b = self.user_bias(user_ids).squeeze()
item_b = self.item_bias(item_ids).squeeze()
pred = self.global_bias + user_b + item_b + (user_combined * item_emb).sum(dim=1)
return pred
"""
SVD++ 的优势:
1. 利用隐式反馈:
即使没有显式评分
点击、浏览等行为也有价值
2. 更好的用户表示:
显式评分 + 隐式行为
3. 冷启动改善:
新用户可能有浏览行为
"""
4. 深度学习推荐模型
4.1 Wide & Deep
论文: "Wide & Deep Learning for Recommender Systems" (Cheng et al., 2016)
核心思想:
Wide 部分: 记忆历史模式 (线性模型)
Deep 部分: 泛化到未见模式 (DNN)
┌─────────────────────────────────────────────────────────────┐
│ │
│ Wide (记忆) Deep (泛化) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 线性模型 │ │ Embedding │ │
│ │ │ │ ↓ │ │
│ │ 特征交叉 │ │ Dense Layers │ │
│ │ │ │ ↓ │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ └───────┬───────────────┘ │
│ ↓ │
│ 输出层 (联合训练) │
│ │
└─────────────────────────────────────────────────────────────┘
理论:
Wide: 学习特征组合的记忆
例: user_installed_app=Netflix & impression_app=Pandora → 点击
Deep: 学习未见过的特征组合的泛化
通过低维嵌入学习语义相似性
class WideAndDeep(nn.Module):
"""
Wide & Deep 模型
Wide: 线性模型,记忆历史模式
Deep: DNN,泛化到新组合
理论:
记忆 + 泛化 = 更好的推荐
"""
def __init__(self, num_users, num_items, embedding_dim=32,
deep_layers=[256, 128, 64]):
super().__init__()
# Wide 部分 (线性)
self.wide_linear = nn.Linear(num_users + num_items, 1)
# Deep 部分
self.user_embedding = nn.Embedding(num_users, embedding_dim)
self.item_embedding = nn.Embedding(num_items, embedding_dim)
deep_input_dim = embedding_dim * 2
layers = []
for dim in deep_layers:
layers.extend([
nn.Linear(deep_input_dim, dim),
nn.ReLU(),
nn.BatchNorm1d(dim),
nn.Dropout(0.2)
])
deep_input_dim = dim
self.deep_mlp = nn.Sequential(*layers)
self.deep_output = nn.Linear(deep_layers[-1], 1)
# 输出层
self.output = nn.Sigmoid()
def forward(self, user_ids, item_ids, wide_features):
"""
user_ids: [B]
item_ids: [B]
wide_features: [B, wide_dim] Wide 特征 (one-hot)
"""
# Wide
wide_out = self.wide_linear(wide_features)
# Deep
user_emb = self.user_embedding(user_ids)
item_emb = self.item_embedding(item_ids)
deep_input = torch.cat([user_emb, item_emb], dim=1)
deep_out = self.deep_output(self.deep_mlp(deep_input))
# 联合
pred = self.output(wide_out + deep_out)
return pred
4.2 DeepFM
论文: "DeepFM: A Factorization-Machine based Neural Network for CTR Prediction"
(Guo et al., 2017)
核心思想:
FM 部分: 建模二阶特征交互
Deep 部分: 建模高阶特征交互
FM: 自动学习特征交叉 (不需要手工设计)
Deep: 学习更复杂的模式
class DeepFM(nn.Module):
"""
DeepFM 模型
FM: 二阶特征交互
Deep: 高阶特征交互
理论:
FM 自动学习特征交叉
Deep 学习更复杂的模式
两者共享嵌入
"""
def __init__(self, num_features, embedding_dim=16, deep_layers=[256, 128]):
super().__init__()
# 特征嵌入
self.embedding = nn.Embedding(num_features, embedding_dim)
# FM 一阶权重
self.first_order = nn.Embedding(num_features, 1)
# Deep 部分
deep_input_dim = num_features * embedding_dim
layers = []
for dim in deep_layers:
layers.extend([
nn.Linear(deep_input_dim, dim),
nn.ReLU(),
nn.BatchNorm1d(dim),
nn.Dropout(0.2)
])
deep_input_dim = dim
self.deep_mlp = nn.Sequential(*layers)
self.deep_output = nn.Linear(deep_layers[-1], 1)
self.bias = nn.Parameter(torch.zeros(1))
def forward(self, feature_ids):
"""
feature_ids: [B, F] 特征 ID
"""
# 嵌入
emb = self.embedding(feature_ids) # [B, F, D]
# FM 一阶
first_order = self.first_order(feature_ids).sum(dim=1) # [B, 1]
# FM 二阶
# (Σ v_i x_i)² - Σ (v_i x_i)² = Σ_{i≠j} <v_i, v_j> x_i x_j
sum_of_emb = emb.sum(dim=1) # [B, D]
sum_of_square = (emb ** 2).sum(dim=1) # [B, D]
fm_second_order = 0.5 * ((sum_of_emb ** 2 - sum_of_square).sum(dim=1, keepdim=True))
# Deep
deep_input = emb.flatten(1) # [B, F*D]
deep_out = self.deep_output(self.deep_mlp(deep_input))
# 联合
pred = torch.sigmoid(self.bias + first_order + fm_second_order + deep_out)
return pred
"""
FM 的理论:
二阶特征交互:
y = w_0 + Σ w_i x_i + Σ_{i<j} <v_i, v_j> x_i x_j
直接计算: O(n²) 复杂度
优化计算: O(n) 复杂度
Σ_{i<j} <v_i, v_j> x_i x_j = 0.5 * (||Σ v_i x_i||² - Σ ||v_i x_i||²)
"""
4.3 DIN (Deep Interest Network)
论文: "Deep Interest Network for Click-Through Rate Prediction" (Zhou et al., 2018)
核心思想:
用户兴趣是多样的
不同的历史行为对当前预测的重要性不同
使用注意力机制自适应地加权历史行为
理论:
传统方法: 用户表示 = 所有历史行为的平均/求和
DIN: 用户表示 = 注意力加权的历史行为
注意力 = f(历史行为, 候选物品)
不同的候选物品,激活不同的历史行为
class DIN(nn.Module):
"""
Deep Interest Network (DIN)
核心创新:
注意力机制加权历史行为
理论:
用户兴趣是多样的
不同候选物品激活不同的历史行为
注意力机制自适应地选择相关行为
"""
def __init__(self, num_items, embedding_dim=32, attention_hidden=64):
super().__init__()
# 物品嵌入
self.item_embedding = nn.Embedding(num_items, embedding_dim)
# 注意力网络
self.attention = nn.Sequential(
nn.Linear(embedding_dim * 4, attention_hidden),
nn.ReLU(),
nn.Linear(attention_hidden, 1)
)
# DNN
self.dnn = nn.Sequential(
nn.Linear(embedding_dim * 3, 256),
nn.ReLU(),
nn.BatchNorm1d(256),
nn.Dropout(0.2),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 1)
)
def forward(self, candidate_ids, history_ids, history_mask):
"""
candidate_ids: [B] 候选物品
history_ids: [B, L] 历史物品
history_mask: [B, L] 历史掩码
"""
# 嵌入
candidate_emb = self.item_embedding(candidate_ids) # [B, D]
history_emb = self.item_embedding(history_ids) # [B, L, D]
# 注意力计算
# 将候选物品广播到每个历史物品
candidate_expand = candidate_emb.unsqueeze(1).expand_as(history_emb)
# 拼接特征
attention_input = torch.cat([
history_emb,
candidate_expand,
history_emb - candidate_expand,
history_emb * candidate_expand
], dim=-1) # [B, L, 4D]
# 注意力权重
attn_weights = self.attention(attention_input).squeeze(-1) # [B, L]
attn_weights = attn_weights.masked_fill(history_mask == 0, -1e9)
attn_weights = torch.softmax(attn_weights, dim=-1)
# 加权历史表示
user_interest = (history_emb * attn_weights.unsqueeze(-1)).sum(dim=1) # [B, D]
# DNN
dnn_input = torch.cat([candidate_emb, user_interest, candidate_emb * user_interest], dim=1)
pred = torch.sigmoid(self.dnn(dnn_input))
return pred
"""
DIN 的注意力机制:
输入: [历史行为, 候选物品, 差异, 乘积]
理论:
差异: 捕获历史行为与候选的差异
乘积: 捕获历史行为与候选的相似性
效果:
对于不同的候选物品
激活不同的历史行为
例:
候选: 运动鞋 → 注意力集中在运动装备历史
候选: 书籍 → 注意力集中在阅读历史
"""
4.4 DIEN (Deep Interest Evolution Network)
论文: "Deep Interest Evolution Network for Click-Through Rate Prediction"
(Zhou et al., 2019)
改进:
不仅考虑用户的历史行为
还考虑兴趣的演化过程
理论:
用户兴趣是随时间变化的
兴趣有演化轨迹
使用 GRU 建模兴趣演化
class DIEN(nn.Module):
"""
Deep Interest Evolution Network (DIEN)
核心创新:
兴趣演化建模
使用 GRU 捕获兴趣变化轨迹
理论:
用户兴趣是动态的
兴趣有演化过程
GRU 建模时序依赖
"""
def __init__(self, num_items, embedding_dim=32):
super().__init__()
self.item_embedding = nn.Embedding(num_items, embedding_dim)
# 兴趣抽取层 (GRU)
self.interest_extractor = nn.GRU(
input_size=embedding_dim,
hidden_size=embedding_dim,
batch_first=True
)
# 兴趣演化层 (注意力 GRU)
self.evolution_gru = nn.GRU(
input_size=embedding_dim,
hidden_size=embedding_dim,
batch_first=True
)
# 注意力
self.attention = nn.Sequential(
nn.Linear(embedding_dim * 2, 64),
nn.ReLU(),
nn.Linear(64, 1)
)
# 输出
self.output = nn.Sequential(
nn.Linear(embedding_dim * 2, 128),
nn.ReLU(),
nn.Linear(128, 1)
)
def forward(self, candidate_ids, history_ids, history_mask):
# 嵌入
history_emb = self.item_embedding(history_ids)
candidate_emb = self.item_embedding(candidate_ids)
# 兴趣抽取
interest_seq, _ = self.interest_extractor(history_emb)
# 兴趣演化 (注意力 GRU)
evolved_seq = self.attention_gru(interest_seq, candidate_emb, history_mask)
# 最终兴趣表示
final_interest = evolved_seq[:, -1, :]
# 预测
pred = torch.sigmoid(self.output(torch.cat([candidate_emb, final_interest], dim=1)))
return pred
def attention_gru(self, interest_seq, candidate, mask):
"""注意力 GRU"""
outputs = []
hidden = None
for t in range(interest_seq.shape[1]):
# 注意力权重
attn_input = torch.cat([interest_seq[:, t], candidate], dim=-1)
attn_weight = torch.sigmoid(self.attention(attn_input))
# 加权输入
weighted_input = interest_seq[:, t] * attn_weight
# GRU 步
output, hidden = self.evolution_gru(
weighted_input.unsqueeze(1),
hidden
)
outputs.append(output)
return torch.cat(outputs, dim=1)
"""
DIEN 的理论:
兴趣演化:
t1: 对电子产品感兴趣
t2: 开始关注手机
t3: 深入研究 iPhone
t4: 准备购买
GRU 捕获这种演化轨迹
注意力机制关注与候选物品相关的演化阶段
"""
5. 序列推荐
5.1 序列推荐概述
序列推荐 (Sequential Recommendation):
输入: 用户的历史行为序列 [i₁, i₂, ..., i_t]
输出: 预测下一个物品 i_{t+1}
与传统 CF 的区别:
CF: 静态的用户-物品交互
序列推荐: 考虑行为的顺序
应用:
- 下一个视频推荐
- 下一个商品购买预测
- 音乐播放列表预测
5.2 SASRec (Self-Attentive Sequential Recommendation)
论文: "Self-Attentive Sequential Recommendation" (Kang & McAuley, 2018)
核心思想:
使用 Transformer 的自注意力机制建模序列
每个位置关注所有历史位置
优势:
- 并行计算
- 长距离依赖
- 灵活的注意力模式
class SASRec(nn.Module):
"""
SASRec (自注意力序列推荐)
使用 Transformer 编码器建模行为序列
理论:
自注意力: 每个位置关注所有历史位置
位置编码: 保留序列顺序信息
因果掩码: 只关注过去
"""
def __init__(self, num_items, embedding_dim=64, max_len=50,
num_heads=2, num_layers=2):
super().__init__()
self.embedding_dim = embedding_dim
# 物品嵌入
self.item_embedding = nn.Embedding(num_items + 1, embedding_dim, padding_idx=0)
# 位置嵌入
self.position_embedding = nn.Embedding(max_len, embedding_dim)
# Transformer 编码器
encoder_layer = nn.TransformerEncoderLayer(
d_model=embedding_dim,
nhead=num_heads,
dim_feedforward=embedding_dim * 4,
dropout=0.1,
batch_first=True
)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
# 输出层
self.output = nn.Linear(embedding_dim, num_items)
# 因果掩码
self.register_buffer('causal_mask',
torch.triu(torch.ones(max_len, max_len), diagonal=1).bool()
)
def forward(self, item_ids):
"""
item_ids: [B, L] 物品序列
"""
B, L = item_ids.shape
# 嵌入
item_emb = self.item_embedding(item_ids)
position_ids = torch.arange(L, device=item_ids.device).unsqueeze(0)
pos_emb = self.position_embedding(position_ids)
x = item_emb + pos_emb
# 因果掩码
mask = self.causal_mask[:L, :L]
padding_mask = (item_ids == 0) # padding 位置
# Transformer
x = self.transformer(x, mask=mask, src_key_padding_mask=padding_mask)
# 预测下一个物品
# 使用每个位置的输出预测下一个
logits = self.output(x) # [B, L, num_items]
return logits
"""
SASRec 的训练:
输入: [i₁, i₂, ..., i_t]
目标: [i₂, i₃, ..., i_{t+1}]
损失: 交叉熵
理论:
每个位置预测下一个物品
因果掩码保证只看到历史
"""
5.3 BERT4Rec
论文: "BERT4Rec: Sequential Recommendation with Bidirectional Encoder Representations
from Transformer" (Sun et al., 2019)
核心思想:
使用 BERT 的掩码语言模型思想
随机掩码历史行为,预测被掩码的物品
与 SASRec 的区别:
SASRec: 单向 (只看过去)
BERT4Rec: 双向 (看整个序列)
class BERT4Rec(nn.Module):
"""
BERT4Rec
使用 BERT 的 MLM 思想进行序列推荐
理论:
随机掩码历史物品
预测被掩码的物品
双向注意力捕获上下文
"""
def __init__(self, num_items, embedding_dim=64, max_len=50,
num_heads=2, num_layers=2, mask_ratio=0.2):
super().__init__()
self.mask_ratio = mask_ratio
self.mask_token_id = num_items # 特殊的掩码 token
# 嵌入
self.item_embedding = nn.Embedding(num_items + 1, embedding_dim) # +1 for mask
self.position_embedding = nn.Embedding(max_len, embedding_dim)
# Transformer
encoder_layer = nn.TransformerEncoderLayer(
d_model=embedding_dim,
nhead=num_heads,
dim_feedforward=embedding_dim * 4,
batch_first=True
)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
# 输出
self.output = nn.Linear(embedding_dim, num_items)
def forward(self, item_ids):
"""
item_ids: [B, L] 物品序列
"""
B, L = item_ids.shape
# 随机掩码 (训练时)
if self.training:
mask = torch.rand(B, L) < self.mask_ratio
mask = mask & (item_ids != 0) # 不掩码 padding
input_ids = item_ids.clone()
input_ids[mask] = self.mask_token_id
else:
input_ids = item_ids
mask = torch.ones(B, L, dtype=torch.bool)
# 嵌入
item_emb = self.item_embedding(input_ids)
pos_emb = self.position_embedding(torch.arange(L, device=item_ids.device))
x = item_emb + pos_emb
# Transformer (双向,无因果掩码)
padding_mask = (input_ids == 0)
x = self.transformer(x, src_key_padding_mask=padding_mask)
# 预测
logits = self.output(x) # [B, L, num_items]
return logits, mask
"""
BERT4Rec vs SASRec:
SASRec:
单向注意力 (因果掩码)
自回归预测下一个
BERT4Rec:
双向注意力 (无因果掩码)
掩码预测被遮挡的物品
BERT4Rec 的优势:
双向上下文更丰富
训练效率更高 (并行预测多个掩码)
"""
6. 多任务学习
6.1 多任务学习概述
多任务学习在推荐中的应用:
推荐系统通常需要同时优化多个目标:
- 点击率 (CTR)
- 转化率 (CVR)
- 停留时长
- 点赞/收藏
- 分享
多任务学习:
一个模型同时预测多个目标
共享底层特征,各自的任务头
理论:
不同任务之间有相关性
共享特征可以互相帮助
缓解过拟合
6.2 Shared-Bottom 架构
class SharedBottom(nn.Module):
"""
Shared-Bottom 多任务架构
所有任务共享底层网络
每个任务有自己的任务头
理论:
底层共享: 学习通用特征
任务头: 学习任务特定特征
"""
def __init__(self, input_dim, shared_dim, task_dims, num_tasks):
super().__init__()
# 共享底层
self.shared_bottom = nn.Sequential(
nn.Linear(input_dim, shared_dim),
nn.ReLU(),
nn.BatchNorm1d(shared_dim),
nn.Linear(shared_dim, shared_dim),
nn.ReLU()
)
# 任务头
self.task_heads = nn.ModuleList([
nn.Sequential(
nn.Linear(shared_dim, task_dim),
nn.ReLU(),
nn.Linear(task_dim, 1),
nn.Sigmoid()
)
for task_dim, _ in zip(task_dims, range(num_tasks))
])
def forward(self, x):
# 共享特征
shared_features = self.shared_bottom(x)
# 各任务预测
predictions = [head(shared_features) for head in self.task_heads]
return predictions
"""
Shared-Bottom 的问题:
任务冲突: 不同任务的梯度可能冲突
负迁移: 一个任务可能损害另一个任务
解决: MMoE, PLE 等门控机制
"""
6.3 MMoE (Multi-gate Mixture-of-Experts)
论文: "Modeling Task Relationships in Multi-task Learning with Multi-gate
Mixture-of-Experts" (Ma et al., 2018)
核心思想:
使用多个专家网络
每个任务有自己的门控网络
自适应地选择专家组合
理论:
不同任务可能需要不同的特征
门控网络学习任务相关的专家组合
缓解任务冲突
class MMoE(nn.Module):
"""
MMoE (Multi-gate Mixture-of-Experts)
多个专家网络 + 任务特定门控
理论:
每个任务有自己的门控
门控学习选择哪些专家
不同任务可以有不同的专家组合
"""
def __init__(self, input_dim, expert_dim, num_experts, num_tasks):
super().__init__()
self.num_experts = num_experts
self.num_tasks = num_tasks
# 专家网络
self.experts = nn.ModuleList([
nn.Sequential(
nn.Linear(input_dim, expert_dim),
nn.ReLU(),
nn.Linear(expert_dim, expert_dim),
nn.ReLU()
)
for _ in range(num_experts)
])
# 门控网络 (每个任务一个)
self.gates = nn.ModuleList([
nn.Sequential(
nn.Linear(input_dim, num_experts),
nn.Softmax(dim=-1)
)
for _ in range(num_tasks)
])
# 任务头
self.task_heads = nn.ModuleList([
nn.Sequential(
nn.Linear(expert_dim, 64),
nn.ReLU(),
nn.Linear(64, 1),
nn.Sigmoid()
)
for _ in range(num_tasks)
])
def forward(self, x):
"""
x: [B, input_dim]
"""
# 专家输出
expert_outputs = [expert(x) for expert in self.experts]
expert_outputs = torch.stack(expert_outputs, dim=1) # [B, E, D]
# 各任务的门控和预测
predictions = []
for task_idx in range(self.num_tasks):
# 门控权重
gate_weights = self.gates[task_idx](x) # [B, E]
# 加权专家输出
task_input = (expert_outputs * gate_weights.unsqueeze(-1)).sum(dim=1) # [B, D]
# 任务预测
pred = self.task_heads[task_idx](task_input)
predictions.append(pred)
return predictions
"""
MMoE 的理论:
专家网络: 学习不同的特征表示
门控网络: 学习任务相关的专家选择
对于任务 A: 可能主要使用专家 1, 3
对于任务 B: 可能主要使用专家 2, 4
效果:
缓解任务冲突
提升多任务性能
"""
7. 知识图谱推荐
7.1 知识图谱概述
知识图谱 (Knowledge Graph, KG) 在推荐中的应用:
知识图谱: 实体和关系的网络
例:
(电影, 属于, 动作片)
(电影, 导演, 诺兰)
(电影, 演员, 小李子)
(用户, 喜欢, 动作片)
优势:
1. 丰富物品的语义信息
2. 提供可解释性
3. 缓解冷启动问题
7.2 KGAT (Knowledge Graph Attention Network)
论文: "KGAT: Knowledge Graph Attention Network for Recommendation"
(Wang et al., 2019)
核心思想:
在知识图谱上使用注意力机制
聚合邻居信息
学习实体和关系的表示
class KGAT(nn.Module):
"""
KGAT (知识图谱注意力网络)
在知识图谱上使用图注意力
理论:
实体表示 = 聚合邻居信息
注意力权重 = 基于关系和实体
"""
def __init__(self, num_entities, num_relations, embedding_dim=64):
super().__init__()
# 实体和关系嵌入
self.entity_embedding = nn.Embedding(num_entities, embedding_dim)
self.relation_embedding = nn.Embedding(num_relations, embedding_dim)
# 注意力网络
self.attention = nn.Sequential(
nn.Linear(embedding_dim * 3, 64),
nn.ReLU(),
nn.Linear(64, 1)
)
def forward(self, entity_ids, neighbor_ids, relation_ids):
"""
entity_ids: [B] 实体 ID
neighbor_ids: [B, N] 邻居实体 ID
relation_ids: [B, N] 关系 ID
"""
# 嵌入
entity_emb = self.entity_embedding(entity_ids) # [B, D]
neighbor_emb = self.entity_embedding(neighbor_ids) # [B, N, D]
relation_emb = self.relation_embedding(relation_ids) # [B, N, D]
# 注意力计算
entity_expand = entity_emb.unsqueeze(1).expand_as(neighbor_emb)
attn_input = torch.cat([entity_expand, neighbor_emb, relation_emb], dim=-1)
attn_weights = torch.softmax(self.attention(attn_input).squeeze(-1), dim=-1)
# 聚合邻居信息
aggregated = (neighbor_emb * attn_weights.unsqueeze(-1)).sum(dim=1)
# 更新实体表示
updated_entity = entity_emb + aggregated
return updated_entity
"""
知识图谱推荐的优势:
1. 丰富语义:
物品有丰富的属性和关系
2. 可解释性:
推荐理由: "因为你喜欢动作片,推荐这部"
3. 冷启动:
新物品可以通过知识图谱获得信息
"""
8. 图神经网络推荐
8.1 用户-物品交互图
用户-物品交互可以建模为二部图:
节点: 用户 + 物品
边: 交互 (点击、购买、评分)
┌─────────────────────────────────────────────────────────────┐
│ │
│ 用户节点 物品节点 │
│ ○ u₁ ─────────────── ○ i₁ │
│ │ ╲ │ │
│ │ ╲ ○ i₂ │
│ ○ u₂ ────────────────│ │
│ │ ○ i₃ │
│ │ ╱ │ │
│ ○ u₃ ───╱────────── ○ i₄ │
│ │
└─────────────────────────────────────────────────────────────┘
GNN 在图上传播信息:
用户表示 = 聚合交互过的物品信息
物品表示 = 聚合交互过的用户信息
8.2 LightGCN
论文: "LightGCN: Simplifying and Powering Graph Convolution Network for
Recommendation" (He et al., 2020)
核心思想:
简化 GCN,去掉特征变换和非线性激活
只保留邻域聚合
消息传播:
e_u^{(k+1)} = Σ_{i∈N(u)} (1/√|N(u)|·√|N(i)|) · e_i^{(k)}
e_i^{(k+1)} = Σ_{u∈N(i)} (1/√|N(i)|·√|N(u)|) · e_u^{(k)}
最终表示:
e_u = Σ_{k=0}^K α_k · e_u^{(k)}
理论:
去掉不必要的复杂性
邻域聚合本身就是有效的
class LightGCN(nn.Module):
"""
LightGCN
简化的图卷积网络
理论:
去掉特征变换和非线性
只保留邻域聚合
更简单,效果更好
"""
def __init__(self, num_users, num_items, embedding_dim=64, num_layers=3):
super().__init__()
self.num_users = num_users
self.num_items = num_items
self.num_layers = num_layers
# 嵌入
self.user_embedding = nn.Embedding(num_users, embedding_dim)
self.item_embedding = nn.Embedding(num_items, embedding_dim)
nn.init.normal_(self.user_embedding.weight, std=0.1)
nn.init.normal_(self.item_embedding.weight, std=0.1)
def forward(self, user_ids, item_ids, adj_matrix):
"""
user_ids: [B]
item_ids: [B]
adj_matrix: [num_users+num_items, num_users+num_items] 邻接矩阵
"""
# 初始嵌入
all_user_emb = self.user_embedding.weight # [M, D]
all_item_emb = self.item_embedding.weight # [N, D]
# 拼接所有嵌入
all_emb = torch.cat([all_user_emb, all_item_emb], dim=0) # [M+N, D]
# 多层传播
emb_list = [all_emb]
for _ in range(self.num_layers):
all_emb = torch.sparse.mm(adj_matrix, all_emb)
emb_list.append(all_emb)
# 聚合各层
final_emb = torch.stack(emb_list, dim=0).mean(dim=0)
# 分离用户和物品
user_emb = final_emb[:self.num_users]
item_emb = final_emb[self.num_users:]
# 获取特定用户的嵌入
user_feat = user_emb[user_ids]
item_feat = item_emb[item_ids]
# 预测
pred = (user_feat * item_feat).sum(dim=1)
return pred
"""
LightGCN 的优势:
1. 简单: 没有复杂的非线性变换
2. 高效: 只有矩阵乘法
3. 有效: 效果优于复杂的方法
理论:
邻域聚合本身就是强大的操作
不需要额外的复杂性
"""
9. 评估指标与实践
9.1 评估指标
┌─────────────────────────────────────────────────────────────────────┐
│ 推荐系统评估指标 │
├─────────────────┬───────────────────────────────────────────────────┤
│ 类别 │ 指标 │
├─────────────────┼───────────────────────────────────────────────────┤
│ 准确率 │ RMSE, MAE (评分预测) │
│ │ Precision@K, Recall@K (Top-K 推荐) │
├─────────────────┼───────────────────────────────────────────────────┤
│ 排序质量 │ NDCG@K (归一化折损累积增益) │
│ │ MAP@K (平均精度均值) │
│ │ MRR (平均倒数排名) │
├─────────────────┼───────────────────────────────────────────────────┤
│ 分类质量 │ AUC, LogLoss (CTR 预测) │
├─────────────────┼───────────────────────────────────────────────────┤
│ 覆盖率 │ Coverage (推荐物品的覆盖率) │
│ │ Diversity (多样性) │
├─────────────────┼───────────────────────────────────────────────────┤
│ 其他 │ 新颖度, 惊喜度, 可解释性 │
└─────────────────┴───────────────────────────────────────────────────┘
class RecommendationMetrics:
"""推荐系统评估指标"""
@staticmethod
def precision_at_k(predictions, labels, k=10):
"""
Precision@K
前 K 个推荐中相关物品的比例
"""
top_k = torch.topk(predictions, k).indices
relevant = labels[top_k].sum()
return relevant / k
@staticmethod
def recall_at_k(predictions, labels, k=10):
"""
Recall@K
相关物品中被推荐的比例
"""
top_k = torch.topk(predictions, k).indices
relevant = labels[top_k].sum()
total_relevant = labels.sum()
return relevant / (total_relevant + 1e-8)
@staticmethod
def ndcg_at_k(predictions, labels, k=10):
"""
NDCG@K (归一化折损累积增益)
NDCG = DCG@K / IDCG@K
DCG = Σ rel_i / log2(i+1)
理论:
考虑排序位置的影响
排在前面的物品更重要
"""
top_k = torch.topk(predictions, k).indices
# DCG
relevance = labels[top_k]
positions = torch.arange(1, k + 1, dtype=torch.float)
dcg = (relevance / torch.log2(positions + 1)).sum()
# IDCG (理想排序)
ideal_relevance = torch.sort(labels, descending=True).values[:k]
idcg = (ideal_relevance / torch.log2(positions + 1)).sum()
return dcg / (idcg + 1e-8)
@staticmethod
def hit_rate_at_k(predictions, labels, k=10):
"""
Hit Rate@K
前 K 个推荐中是否包含相关物品
"""
top_k = torch.topk(predictions, k).indices
hit = (labels[top_k] > 0).any().float()
return hit
@staticmethod
def auc(predictions, labels):
"""
AUC (ROC 曲线下面积)
衡量排序质量
正样本排在负样本前面的概率
"""
from sklearn.metrics import roc_auc_score
return roc_auc_score(labels.numpy(), predictions.numpy())
"""
评估指标的选择:
评分预测: RMSE, MAE
Top-K 推荐: Precision@K, Recall@K, NDCG@K
CTR 预测: AUC, LogLoss
实践中:
NDCG@K 最常用 (考虑排序质量)
离线评估 + 在线 A/B 测试
"""
9.2 A/B 测试
A/B 测试:
在线评估推荐系统的金标准
流程:
1. 将用户随机分为实验组和对照组
2. 实验组使用新算法
3. 对照组使用旧算法
4. 比较关键指标
关键指标:
- 点击率 (CTR)
- 转化率 (CVR)
- 用户停留时长
- 用户留存率
- 收入
注意事项:
- 样本量要足够大
- 测试时间要足够长
- 考虑用户的新奇效应
10. 工业应用与前沿
10.1 工业推荐系统架构
工业推荐系统的挑战:
1. 规模: 数亿用户,数十亿物品
2. 实时性: 毫秒级响应
3. 新鲜度: 实时更新模型
4. 多样性: 避免信息茧房
解决方案:
- 多级架构: 召回 → 粗排 → 精排 → 重排
- 分布式训练: 参数服务器
- 实时特征: 流式计算
- 在线学习: 增量更新
10.2 前沿方向
推荐系统的前沿:
1. 大模型推荐:
LLM 作为推荐系统的 backbone
用 prompt 做推荐
2. 多模态推荐:
结合文本、图像、视频
CLIP 等模型的应用
3. 因果推荐:
去除偏差 (位置偏差、曝光偏差)
因果推断
4. 联邦推荐:
保护用户隐私
联邦学习
5. 强化推荐:
长期收益优化
探索与利用
6. 可解释推荐:
解释推荐理由
提升用户信任
附录
A. 推荐系统发展时间线
2006 ──┬── 矩阵分解 (Netflix Prize)
│
2014 ──┼── Word2Vec (物品嵌入)
│
2016 ──┼── Wide & Deep
│
2017 ──┼── DeepFM
│
2018 ──┼── DIN (注意力机制)
│
2019 ──┼── DIEN (兴趣演化)
│
2020 ──┼── LightGCN (图推荐)
│
2021 ──┼── SASRec, BERT4Rec (序列推荐)
│
2023+ ──┴── LLM + 推荐
B. 核心公式速查
| 公式 |
含义 |
| r̂ = u·v |
矩阵分解预测 |
| y = w₀ + Σwᵢxᵢ + Σ<vᵢ,vⱼ>xᵢxⱼ |
FM 预测 |
| α = softmax(QK^T/√d) |
注意力权重 |
| NDCG = DCG / IDCG |
归一化折损累积增益 |
C. 推荐资源
- Koren, Y., et al. (2009). Matrix Factorization Techniques
- Cheng, H., et al. (2016). Wide & Deep Learning
- Zhou, G., et al. (2018). Deep Interest Network
- He, X., et al. (2020). LightGCN