Semantic ID(SID)是一种把物品(或用户)的多模态语义内容,通过量化(quantization)压缩成一组层次化整数编码的技术。它的最终形态是一个短短的 code 序列,比如 [5, 31, 142],可以直接被推荐系统当成"有语义的 ID"来用。
为什么需要 SID?
传统推荐系统用的 ID 特征(比如广告 ID = ad_12345678)本质上只是一个随机编号,不带任何语义信息。两个"内容相似"的广告可能 ID 相差甚远。
传统 ID 的问题
- 纯粹是数字编号,无语义
- 冷启动广告(新 ID)没有 embedding,无法被模型识别
- 内容相似的物品,ID 之间没有任何关联
- 不同模态(图文视频)的信息无法融合
SID 的优势
- 由多模态内容(图文视频)派生,携带语义
- 层次化结构:前缀相同 = 内容相关
- 离散稀疏 ID,可接入 embedding table,可学习
- 冷启动友好:内容相似就有相似 SID
- 可以和其他稀疏 ID 做 feature crossing
SID 长什么样?
以 DAS 论文为例,每个广告的 SID 是三层 code 序列($L=3$):
广告 I(空气炸锅)经过 SID 量化后得到:SID = [5, 31, 142]
- 第 1 层 code = 5:粗粒度语义,比如"厨房用品类"
- 第 2 层 code = 31:中粒度,比如"小型电器"
- 第 3 层 code = 142:细粒度,比如"空气炸锅具体款式"
接入下游 CTR 模型时,用 Prefix-Ngram 方式展开成三个稀疏特征:
ad_l1 = 5ad_l2 = "5_31"ad_l3 = "5_31_142"
每个特征都有一个 16 维可学习 embedding,粗粒度 + 细粒度同时建模。
生产一个 SID 需要两大步骤:① 提取多模态内容 embedding,② 量化成离散 code 序列。
图文/视频/标题/OCR
摘要 + 提炼
编码成连续 embedding $s$
量化成 SID $[c_1,c_2,c_3]$
2.1 内容 embedding 提取
量化之前,首先要把物品(广告/视频/商品)和用户的多模态内容压缩成一个固定维度的连续向量 $s$。
-
1多模态数据整理成文本 广告的标题、OCR 文字、ASR 字幕、视觉描述(图片描述)拼成一段结构化文本。用户的基本信息 + 历史行为也转成文本 prompt。
-
2LLM 精炼(可选但推荐) 把上面那段文本送进大语言模型(GPT / Qwen 等),让它生成一段质量更高的摘要,去掉噪声、突出重点。🧩 举例
广告原始数据:标题"超值特卖!网红空气炸锅限时折扣",OCR 识别到"容量 3.5L 定时功能 360°加热",图片描述"银色小型厨房电器"。
LLM 摘要后变成:"一款 3.5L 容量的小型家用空气炸锅,支持定时和 360 度均匀加热,主打厨房烹饪健康少油"。
这段摘要语义更纯净,后续 embedding 质量更好。
-
3PLM 编码成 embedding 把摘要文本送进一个预训练语言模型(比如 DAS 里用的 fine-tuned BGE M3),输出一个连续向量: $$s_i = \text{item\_encoder}(t_i) \in \mathbb{R}^{d}$$
其中 $d$ 通常是 256 或 1024。这个 $s_i$ 就是接下来要被量化的"原材料"。
- 不可学习:冻结 embedding 无法根据下游任务调整
- 维度太高:256 或 1024 维对大规模工业系统来说太重
- 无法做 feature crossing:稠密向量不能像 ID 特征那样和其他特征做交叉
2.2 VQ-VAE 原理:什么是向量量化?
在理解 RQ-VAE 之前,先搞懂最基础的 VQ-VAE(Vector Quantized VAE)。
连续向量
MLP/Transformer
连续
找最近 code
连续,离散 index $c$
重建 $\hat{s}_i$
Codebook(码本) 是一组可学习的向量集合 $\{e_1, e_2, \ldots, e_N\} \subset \mathbb{R}^d$,共 $N$ 个码字。
量化的操作是:找 Codebook 里和 $h_i$ 最近的那个码字:
选出来的 $c$(整数 index)就是 SID 的一位编码;选出来的 $e_c$(连续向量)用来后续 decoder 重建。
想象你有一张高清照片(连续 RGB 值),但你只能用 512 种固定颜色(Codebook)来画它。量化就是:对照片里的每个像素,找 512 种颜色里最接近的那个,用它的编号代替原始颜色值。
SID 量化也是一样:把一个 256 维的连续语义向量,映射到 512 个码字里最接近的那个,用它的 index 代替原始向量。
VQ-VAE 的局限:只有一层 Codebook,量化误差大,而且 $N$ 太小时表达能力不足,$N$ 太大时 codebook 会崩塌(大量码字没人用)。
2.3 RQ-VAE:层次化残差量化
RQ-VAE(Residual Quantization VAE) 是 VQ-VAE 的改进版,用多层 Codebook 逐步量化残差,精度更高、表达能力更强。
RQ-VAE 内部有一个 Encoder(MLP 或 CNN),负责把原始 MLLM embedding $s_i$(256 维)映射到量化空间的 latent 表示 $h_i$(与 codebook 码字同维度,32 维)。
量化(argmin)在 $h_i$ 上进行,不是直接在 256 维的 $s_i$ 上进行。原始 RQ-VAE 论文(CVPR 2022)的公式是:$Z = E(X)$,先 encode 再 quantize。DAS 论文的公式简写为 $r_0 = s$,实际含义是 $r_0 = h_i = \text{Encoder}(s_i)$。
因此:$s_i$ 是 256 维的内容语义向量;$h_i$ 是 encoder 输出的 32 维 latent;量化在 $h_i$ 维度的空间里发生。
↓ Encoder(MLP)
$h_i$(32维),$r_0 = h_i$
$c_1 = \text{argmin}\|r_0-e_k\|^2$
$r_1 = r_0 - e_{c_1}$
$c_2 = \text{argmin}\|r_1-e_k\|^2$
$r_2 = r_1 - e_{c_2}$
$c_3 = \text{argmin}\|r_2-e_k\|^2$
$r_3 = r_2 - e_{c_3}$
三个整数
三个 code 向量之和(sum-pool)
- $L=3$:层数(codebook 数量)
- $N=512$:每层 codebook 大小(512 个码字)
- $d=32$:每个码字向量的维度(也是 encoder 输出 latent $h_i$ 的维度)
- $r_0 = h_i = \text{Encoder}(s_i)$:初始残差,是 encoder 把 256 维 $s_i$ 映射成 32 维后的 latent
- $r_l = r_{l-1} - e_{c_l}$:每层的残差(上一层残差减去已量化码字向量)
- 总编码空间:$512^3 \approx 1.3$ 亿种 SID 组合
假设广告 I(空气炸锅)经过 PLM 得到 $s_i$(256 维),再经 RQ-VAE 内部 Encoder 得到 $h_i$(32 维):
- Encoder:$s_i$(256 维)→ Encoder(MLP)→ $h_i$(32 维)。$r_0 = h_i$。
- 第 1 层:在 512 个码字里找和 $r_0$ 最近的,选中 $e_5$(代表"厨房家电"类)。残差 $r_1 = r_0 - e_5$
- 第 2 层:对残差 $r_1$ 在 512 个码字里找最近的,选中 $e_{31}$(代表"小型电器")。残差 $r_2 = r_1 - e_{31}$
- 第 3 层:对残差 $r_2$ 在 512 个码字里找最近的,选中 $e_{142}$(代表该广告的具体款式特征)。
最终:
- 离散 SID:
[5, 31, 142](三个整数) - 连续量化表示:$z_i = e_5 + e_{31} + e_{142}$(三个 32 维向量相加,结果还是 32 维)
$z_i$ 是连续的(可以求梯度),SID 是离散的(传给下游模型的实际编号)。
- SID = 三个整数 index,比如
[5, 31, 142]。这是最终给下游模型用的"标签"。 - $z_i$ = 三个码字向量相加,是连续表示。这是训练时用来计算 loss、做对齐的中间量。
- $z_i$ 依赖 codebook 里选中的那三个向量 $e_5, e_{31}, e_{142}$。如果 codebook 更新了,$z_i$ 也会变。
可以理解为:SID 是"地址",$z_i$ 是"地址对应的内容"。
本节从训练哪些参数、损失函数、梯度如何流动、codebook 坍塌如何防止、初始化、EMA vs 梯度法、Rotation Trick、超参调优、训练监控九个维度全面讲解 RQ-VAE 的训练。内容综合自 VQ-VAE 原始论文、ICML 2023 STE 分析论文、Fifty et al. 2024 Rotation Trick 论文,以及 TIGER/DAS 的工业实践。
3.0 整体架构与训练目标
RQ-VAE 由三部分组成,训练时三者联合端到端优化:
(如 256 维 MLLM 向量)
(如 32 维)
量化向量 $z_q = \sum e_{c_l}$
3.1 训练哪些参数?
① Encoder 权重
把原始 embedding $s$(如 256 维)压缩到量化空间(如 32 维)的 MLP,含 Linear + BatchNorm + ReLU。
参与训练 由 重建损失 (STE 路径) 和 commitment loss 双路更新。
② Codebook 向量 $\{e_k\}$
每层 $K$(如 256)个 $d$ 维可学习向量($L \times K$ 个总共)。
参与训练 有两种更新方式:梯度法(codebook loss)或 EMA 法(不走 optimizer,见 §3.4)。
③ Decoder 权重
从量化向量 $z_q = \sum_{l} e_{c_l}$($d$ 维)重建回原始维度的对称 MLP。
参与训练 只由 重建损失 $\|s - \hat{s}\|^2$ 更新,commitment/codebook loss 对其无梯度。
④ argmin 本身
"找最近 code"$c_l = \arg\min_k \|r_{l-1} - e_k\|^2$ 是离散的、不可微的。
不可微 通过 STE 或 Rotation Trick 绕过,见 §3.3。
3.2 损失函数拆解
VQ-VAE / RQ-VAE 的标准训练损失分两部分(DAS 也沿用此框架):
- $s$:输入 embedding;$\hat{s}$:decoder 重建的 embedding
- $r_{l-1}$:第 $l$ 层量化的输入残差($r_0 = z_e = \text{Encoder}(s)$,$r_l = r_{l-1} - e_{c_l}$)
- $e_{c_l}$:第 $l$ 层 codebook 中被选中的码字向量
- $\text{sg}[\cdot]$:stop-gradient,代码中为
.detach() - $\beta$:commitment 系数,典型值 0.25~1.0(见 §3.7 超参调优)
量化损失包含两个独立的 sg-loss 项,梯度分别流向不同模块:
| 损失项 | 公式 | 更新谁 | 作用 |
|---|---|---|---|
| Codebook loss | $\|\text{sg}[r_{l-1}] - e_{c_l}\|^2$ | Codebook $e_{c_l}$ (encoder 被 sg 冻结) |
把 codebook 向量拉向 encoder 输出的均值中心 |
| Commitment loss | $\beta\,\|r_{l-1} - \text{sg}[e_{c_l}]\|^2$ | Encoder 参数 (codebook 被 sg 冻结) |
迫使 encoder 输出"承诺"靠近 codebook,防止乱漂 |
| Reconstruction loss | $\|s - \hat{s}\|^2$ | Encoder + Decoder (通过 STE/Rotation Trick) |
保证量化后能还原原始语义信息 |
原始 RQ-VAE 论文(CVPR 2022)的损失只有单向 commitment loss(只训 encoder),codebook 用 EMA 更新,不通过梯度 loss。
VQ-VAE(Van Den Oord 2017)则是双 sg-loss,codebook 和 encoder 各有一项,通过梯度联合更新。
DAS 采用 VQ-VAE 风格的双 sg-loss。两种方式实践中效果相近,EMA 法更稳定但需单独维护 moving average 状态。
3.3 梯度如何穿越不可微的量化操作?(STE vs Rotation Trick)
量化的核心是 $\arg\min$,这是离散的,梯度为零或未定义。有两种主流解法:
方法一:Straight-Through Estimator(STE)
VQ-VAE 的方案:前向传播用量化值 $e_c$,反向传播"假装" $e_c = z_e$(直通梯度)。
PyTorch 实现(一行):
# Straight-Through Estimator
z_q = z_e + (e_c - z_e).detach() # 前向=e_c,反向梯度从e_c跳到z_e
# 等价于: z_q.backward() 时,梯度直接流向 z_e,不经过 argmin
方法二:Rotation Trick(Fifty et al. 2024,更新方案)
Rotation Trick 用一个旋转矩阵 $R$ 来精确地把 $z_e$ "旋转"到 $e_c$ 的方向,从而实现真正连续可微的梯度估计,彻底解决 STE 的梯度误差问题。
Eugene Yan 的实战实验表明:用 Rotation Trick 替换 STE 后,codebook 利用率显著提升,unique ID 比例更高。目前 vector-quantize-pytorch 库已内置支持。
| 方案 | 梯度误差 | 稳定性 | 实现复杂度 | 推荐场景 |
|---|---|---|---|---|
| STE(传统) | 较大(与量化误差成比例) | 一般 | 极低(1行代码) | 快速实验基线 |
| EMA 更新 codebook | STE 估计 encoder 梯度 | 高(codebook 更新平滑) | 中等 | 工业落地首选 |
| Rotation Trick(新) | 最小(无近似误差) | 最高 | 中等 | 追求最优质量 |
3.4 Codebook 的两种更新方式:梯度法 vs EMA 法
方式 A:梯度下降(Gradient Descent)
把 codebook 向量当作普通的 nn.Parameter,通过 codebook loss $\|\text{sg}[z_e] - e_c\|^2$ 的梯度更新。
# 梯度法(标准 VQ-VAE 写法)
codebook_loss = F.mse_loss(z_e.detach(), e_c) # 对 e_c 有梯度,对 z_e 无梯度
commitment_loss = beta * F.mse_loss(z_e, e_c.detach()) # 对 z_e 有梯度
loss = recon_loss + codebook_loss + commitment_loss
方式 B:指数移动平均 EMA(工业界更常用)
EMA 法完全绕过 codebook loss 的梯度。不把 codebook 注册为 nn.Parameter,而是用滑动平均手动更新:
其中 $\gamma$(衰减因子 decay)通常取 0.99,$n_k$ 是当前 batch 中被分配到第 $k$ 个 code 的样本数。
# EMA 法(PyTorch 实现骨架)
# codebook 注册为 buffer(不参与 optimizer),EMA 手动更新
self.register_buffer('embeddings', torch.randn(K, D))
self.register_buffer('ema_cluster_size', torch.zeros(K))
self.register_buffer('ema_dw', torch.zeros(K, D))
# 训练时 forward:
if self.training:
one_hot = F.one_hot(indices, K).float() # (B, K)
self.ema_cluster_size = decay * self.ema_cluster_size + (1-decay) * one_hot.sum(0)
self.ema_dw = decay * self.ema_dw + (1-decay) * one_hot.T @ z_e
# 拉普拉斯平滑防止除零
n = self.ema_cluster_size.sum()
smooth_size = (self.ema_cluster_size + eps) / (n + K*eps) * n
self.embeddings = self.ema_dw / smooth_size.unsqueeze(1)
# 损失只剩 commitment loss(codebook 不走 optimizer)
loss = beta * F.mse_loss(z_e, e_c.detach())
3.5 最大难点:Codebook 坍塌(Index Collapse)
Codebook 坍塌是训练 VQ-VAE/RQ-VAE 最臭名昭著的失败模式:训练结束后,只有少数几个 code(甚至个位数)被实际使用,大量 code 永远"休眠"。
根本原因(ICML 2023 深度分析)
commitment loss 本质上是一个非对称散度:只对被选中的 code 有梯度,未被选中的 code 永远得不到更新。一旦某些 code 在训练初期没被选到,它们就会永远"死亡",而 encoder 会越来越倾向于只输出少数"安全"的几个方向。
工业实践中的防坍塌 checklist(综合多篇论文)
| 手段 | 原理 | 效果 | 注意事项 |
|---|---|---|---|
| ① K-Means 初始化 | 训练前先用 K-Means 聚类 encoder 输出,将聚类中心作为 codebook 初值 | ⭐⭐⭐⭐ 极有效,从源头保证初始覆盖率 | 需要先 forward 一轮收集数据;逐层初始化(第 l 层用第 l-1 层的残差做 K-Means) |
| ② Dead Code 重置 | 周期性检测长期未被使用的 code(如 20 个 iteration 内使用次数 = 0),随机替换为高密度区域的 encoder 输出 | ⭐⭐⭐⭐ Eugene Yan 实验验证有效 | LRU 策略(least-recently-used)效果最好;触发阈值需调 |
| ③ EMA 更新 codebook | 码字 = 映射到该 code 的 encoder 输出的滑动平均,更新平滑 | ⭐⭐⭐ 有效降低训练抖动 | decay=0.99 是常用值;需配合 commitment loss 训 encoder |
| ④ 大 batch size | batch 越大,每个 code 在一次 forward 中被选到的概率越高,梯度更稠密 | ⭐⭐⭐ Eugene Yan 实验验证 | 尽量用显存能支持的最大 batch,如 ≥ 1024 |
| ⑤ Rotation Trick | 替换 STE,减小量化误差导致的梯度偏差,让 encoder 能更准确地学习 | ⭐⭐⭐ 对 unique ID 比例提升明显 | 需要额外计算旋转矩阵,开销略高 |
| ⑥ L2 归一化 | 对 encoder 输出和 codebook 都做 L2 normalization,在单位球面上量化(cosine 距离而非 L2) | ⭐⭐⭐ 约束了 encoder 输出的"漂移"范围 | 可能牺牲幅度信息;不适合所有任务 |
| ⑦ 学习率 Warmup | 训练初期用小学习率,防止 encoder 输出分布突变导致 codebook 跟不上 | ⭐⭐ ICML 2023 推荐,配合 cosine decay | cosine warmup + decay 通常效果最好 |
| ⑧ Affine 重参数化 | 给 codebook 加一个共享的仿射变换(mean + scale),让未被选中的 code 也能随着分布漂移而移动 | ⭐⭐⭐ ICML 2023 核心贡献 | 需要调 affine lr scale 超参 |
3.6 完整训练流程(Step-by-Step)
-
0数据准备:收集所有 item 的 embedding
用冻结的大模型(如 MLLM、Sentence-BERT)对所有 item 的文本/多模态内容进行编码,得到高维 embedding $\{s_i\}$。此步骤不涉及 RQ-VAE 训练,仅离线跑一次。
-
1Codebook K-Means 初始化(关键!)
先用一部分数据 forward 一遍 encoder,收集所有 encoder 输出 $z_e$。对第 1 层 codebook 做 K-Means,用聚类中心初始化码字。然后计算残差 $r_1$,对第 2 层 codebook 做 K-Means,以此类推。
# 逐层 K-Means 初始化 residual = z_e for l, vq_layer in enumerate(rqvae.vq_layers): kmeans = KMeans(n_clusters=K).fit(residual.numpy()) vq_layer.embeddings.data.copy_(torch.tensor(kmeans.cluster_centers_)) with torch.no_grad(): e_c = vq_layer.get_nearest(residual) residual = residual - e_c # 残差传给下一层 -
2正常训练循环
使用 AdamW(lr ≈ 1e-4,配合 cosine LR warmup),在每个 batch 中:
- Forward:$s \to z_e \to [c_1,\ldots,c_L] \to z_q \to \hat{s}$
- 计算 $L_{\text{recon}} + L_{\text{vq}}$,backward
- 若用 EMA:手动更新 codebook(在 optimizer.step 之前或之后)
- 检测 dead code:统计本 batch 每个 code 的使用次数,未使用次数超阈值则重置
-
3训练监控(见 §3.8)
同步监控:recon loss、vq loss、codebook perplexity(信息熵)、unique ID 比例。其中 unique ID 比例是检测坍塌的最直接指标。
-
4推理:冻结 encoder + codebook,生成 SID
训练收敛后,encoder 和 codebook 冻结。对每个 item 做一次 forward,输出 $[c_1,\ldots,c_L]$ 即为该 item 的 Semantic ID。
3.7 关键超参数调优指南
| 超参数 | 典型值 | 影响 | 调优建议 |
|---|---|---|---|
| $\beta$(commitment cost) | 0.25 ~ 1.0 | encoder 与 codebook 的对齐强度 | 0.25 是经典默认值(VQ-VAE 原论文);Eugene Yan 实验发现 0.5 在重建和唯一 ID 之间取得最佳平衡;$\beta=1.0$ 等价于 EMA 更新 |
| codebook 大小 $K$ | 256 ~ 1024 | 词汇量 vs 坍塌风险 | 每层 256 是广泛验证的鲁棒选择;超大 codebook(>1024)在数据不足时容易坍塌 |
| 量化层数 $L$ | 2 ~ 4 | 精度 vs 计算量 | 3 层为工业常用(DAS、TIGER);层数越多,后面几层的残差越小,收益递减 |
| latent dim $d$ | 32 ~ 64 | 信息瓶颈宽度 | 压缩率约 8x~32x;过小导致信息丢失,过大量化难度增加 |
| 学习率 lr | 1e-4 ~ 1e-3 | 训练稳定性 | 配合 cosine warmup;过高的 lr 是 codebook 坍塌的首要元凶 |
| batch size | ≥ 1024 | codebook 更新的统计稳定性 | 尽量用显存能支持的最大值;小 batch 会导致 EMA 统计不稳定 |
| EMA decay $\gamma$ | 0.99 ~ 0.999 | codebook 更新的平滑程度 | 越接近 1.0 更新越慢越稳;过低则 codebook 跟随噪声 |
| dead code 阈值 | 20 ~ 100 个 iter | 何时重置死亡 code | LRU 策略(≥20 iter 未使用则重置)是 ICML 2023 推荐的最优策略 |
3.8 训练监控指标
📉 loss/reconstruction
重建损失,衡量 encoder→量化→decoder 链路的信息保真度。应持续下降并趋于稳定。
📉 loss/vq
codebook loss + commitment loss 之和。编码器输出与 codebook 的距离。正常训练中应下降;若 vq_loss 异常低但 recon_loss 高,可能已经坍塌。
📊 codebook perplexity(困惑度)
$\text{PPL} = 2^{H(p)}$,其中 $H(p)$ 是 code 使用分布的熵。接近 $K$ 表示均匀使用,接近 1 表示完全坍塌。这是最重要的健康指标。
📊 unique ID proportion
当前 batch 中不同 ID 的比例(Eugene Yan 推荐监控),直接衡量"不同 item 能否被区分"。低于 50% 要警惕。
📊 avg residual norm
所有量化层之后剩余残差的均值范数。越低说明 codebook 能更好地逼近 encoder 输出。
🔢 active code count
每隔若干步统计实际使用的 code 数量(≥1 次被选中)。健康状态应接近 $K$;如果只有 10% 被使用则已经严重坍塌。
- K-Means 初始化是最有效的单一手段,必做
- EMA 更新 codebook + dead code 重置是工业落地的标配组合
- Large batch(≥1024)对 EMA 统计稳定性至关重要
- β = 0.25 是安全起点,可根据 recon loss 和 perplexity 的平衡微调
- Rotation Trick 是 STE 的更优替代,追求质量时优先尝试
- 监控 perplexity 和 unique ID proportion,两者都高才算收敛良好
3.9 训练完成后:线上推理流程
训练收敛后,encoder 和 codebook 全部冻结,decoder 也可以丢弃(推理不需要重建)。推理只做两步:
-
1Encoder forward:$s \to z_e$ 把 item 的 MLLM embedding $s_i$ 送进冻结的 encoder,得到低维 latent $z_e$。
-
2逐层 argmin 查 codebook → 输出 SID也可同时输出连续表示 $z_q = \sum_l e_{c_l}$ 作为 dense feature 用于 CTR/精排。
@torch.no_grad() def encode_to_semantic_ids(rqvae, s): residual = rqvae.encoder(s) # z_e ids = [] for vq_layer in rqvae.vq_layers: distances = compute_distances(residual, vq_layer.embeddings) c = distances.argmin(dim=-1) # 选最近 code ids.append(c) residual = residual - vq_layer.embeddings[c] # 计算残差 return torch.stack(ids, dim=-1) # shape: (N, L)
4.1 为什么需要注入协同信号?
上面讲的 SID 生产过程,全程只用了内容(图文视频文本)。但推荐系统的核心是行为——用户到底点击了什么、购买了什么,这才是最有价值的信号。
🔴 没有 CF 信号的 SID
- 只反映"内容相似",不反映"被同一类用户喜欢"
- 内容相似但用户群完全不同的商品,会被量到同一个 code
- 下游 CTR 模型拿 SID 做特征,效果受限
No-Aligned SID DAS 论文称之为
🟢 注入 CF 信号后的 SID
- 同时反映内容相似 + 行为相似
- 被同一类用户点击的物品,SID 更接近
- SID 的"分桶"逻辑更符合推荐系统目标
Aligned SID 注入 CF 后得到
广告 A:空气炸锅(健康烹饪,家庭主妇购买)
广告 B:烤箱(烘焙爱好者购买,价格更高)
广告 C:空气炸锅配件(配件,买空气炸锅的人才会买)
纯内容 SID 的结果:A 和 C 内容语义最像 → SID 最接近。B 内容不像 → SID 最远。
但 CF 信号告诉我们:买 A 的人也经常买 B(都在买厨房电器);买 C 的人只买 C(配件场景不同)。
注入 CF 后理想的 SID:A 和 B 的 SID 应该更近(行为相似),C 的 SID 应该被推远。
接下来介绍三种注入方式:CF First、Alignment First、一阶段联合(DAS)。
4.2 CF First:先有 CF embedding,再监督量化——以 LETTER 为代表
训 CF 模型
$i_{cf}$(冻结)
内容 embedding $s_i$
→ $z_i$, SID
$\|z_i - W(i_{cf})\|^2$
详细步骤:
-
1先训练 CF 模型(阶段 1,独立完成) 只用用户 ID、物品 ID、点击/购买行为,训一个双塔召回模型(DSSM / GCN 等),得到每个物品的 CF embedding $i_{cf} \in \mathbb{R}^{d_{cf}}$。训练完后冻结不再更新。
-
2再训练 SID 量化器,加入 CF 对齐 loss(阶段 2) 输入内容 embedding $s_i$,经过 encoder + RQ-VAE 得到量化后的连续表示 $z_i$。 在量化训练的损失里加一项: $$\mathcal{L} = \mathcal{L}_{\text{Sem}} + \lambda \cdot \|z_i - W(i_{cf})\|^2$$ 其中 $W$ 是线性投影层(对齐维度),$i_{cf}$ 来自第 1 步的冻结 CF 模型。
不是直接对齐离散 SID 编号——因为整数 index 无法做连续空间的距离约束。
这个 loss 会反传梯度到:
- Codebook 向量(让码字靠近 CF 语义)
- Encoder 参数(让 encoder 输出靠近 CF 空间)
CF 模型里,广告 A(空气炸锅)和 B(烤箱)的 $i_{cf}$ 很近(行为相似)。
CF First 训练后,量化器被约束:$z_A$ 靠近 $i_{cf}(A)$,$z_B$ 靠近 $i_{cf}(B)$。
因为 $i_{cf}(A)$ 和 $i_{cf}(B)$ 本来就近 → $z_A$ 和 $z_B$ 也变近 → A 和 B 最终被量到相近的 code。
广告 C 的 $i_{cf}(C)$ 被推远 → 即使 C 内容像 A,最终也量到不同的 code 桶。
- 两阶段解耦:CF 模型训完冻结,quantizer 只能"追"老师,两边不联合更新,存在优化目标 gap
- CF embedding 有偏:CF 模型本身存在流行度偏差(20% 的热门广告占 80% 的交互),如果直接拿来对齐,会把偏差注入 SID codebook,导致冷门物品的 SID 质量差
- 互信息受限:单向 loss $\|z_i - i_{cf}\|^2$ 不能充分最大化 SID 和 CF 之间的互信息
4.3 Alignment First:先对齐内容 embedding,再量化——以 QARM 为代表
训 CF 模型 → 冻结
内容 $s_i$
$\tilde{s}_i = g(s_i)$
$\tilde{s}_i \approx i_{cf}$
RQ-VAE 量化 $\tilde{s}_i$
→ SID
详细步骤:
-
1预训 CF 模型,冻结(同 CF First)
-
2训练对齐网络 $g(\cdot)$,把内容 embedding 映射到 CF 空间 用一个 MLP 或轻量 Transformer 作为对齐器,目标是让: $$\tilde{s}_i = g(s_i) \approx W(i_{cf})$$ 也就是把原始内容向量 $s_i$ 改造成一个"更像 CF 空间"的向量 $\tilde{s}_i$。
-
3对 $\tilde{s}_i$ 做 RQ-VAE 量化 不再量化原始 $s_i$,而是量化已经对齐过的 $\tilde{s}_i$: $$sid_i = \text{RQ-VAE}(\tilde{s}_i)$$ 输入本身已带 CF 语义,量化出来的 SID 自然也更接近 CF 结构。
原始内容空间里:A 和 C 很近(都是炸锅类),B 较远(烤箱)。
经过对齐网络 $g(\cdot)$ 之后:$\tilde{s}_A$ 和 $\tilde{s}_B$ 被拉近(CF 行为相似),$\tilde{s}_C$ 被推远。
第 3 步对这组"已重排"的向量做 RQ-VAE,量化结果就自然反映了 CF 结构。
- 量化会丢信息:即使 $\tilde{s}_i$ 对齐得很好,后续 RQ-VAE 还是会引入量化误差,对齐效果打折
- 两步仍然割裂:对齐网络和量化器分开训练,优化目标不一致
- CF embedding 有偏:和 CF First 一样,直接用有偏 CF 会污染 SID
4.4 一阶段联合训练:同时优化量化和对齐——DAS 的做法
DAS 的核心洞察是:CF First 和 Alignment First 都在分两步做,两步之间的优化目标是割裂的。更好的做法是把量化、CF 建模、对齐放进同一个训练循环里联合优化。
双侧 RQ-VAE
$s_i \to z_i$, SID
去偏 CF 模型
$c_u^{int}$, $c_i^{pro}$
多视图对比对齐
$z_i \leftrightarrow c_i^{pro}$
$\mathcal{L}_{\text{Sem}} + \alpha\mathcal{L}_{\text{CF}} + \beta\mathcal{L}_{\text{Align}}$
DAS 的三个核心创新:
-
1CF 去偏(ICDM):用去偏的 CF 信号做对齐 普通 CF 模型里,热门广告(20% 的广告贡献 80% 的收入)的 embedding 会被反复更新,偏差严重。DAS 用因果图把 CF 表示拆成:
- 用户真实兴趣 $c_u^{int}$(去掉从众偏差)
- 广告内容质量 $c_i^{pro}$(去掉流行度偏差)
⚠️ 为什么不去偏会有问题? 如果直接拿有偏的 CF embedding 对齐,codebook 里大量码字会被热门广告"霸占",冷门广告的 code 质量变差,codebook 利用率降低(perplexity 下降)。DAS 实验显示,去偏后 codebook 使用率和 perplexity 均显著提升。 -
2多视图对比对齐(MDAM):三种 InfoNCE loss DAS 不是简单一个 $\|z_i - W(i_{cf})\|^2$,而是设计了三种对比对齐任务:
对齐任务 正例 含义 Dual U2I 用户实际点击的 <U, I> 对 $z_u \leftrightarrow c_i^{pro}$(用户 SID ↔ 广告 CF);$c_u^{int} \leftrightarrow z_i$(双向) Dual U2U / I2I 同一实体的 SID 表示 vs CF 表示 $z_i \leftrightarrow c_i^{pro}$:让同一广告的 SID 空间和 CF 空间一致 Co-occurrence 被同一用户点击的两个广告 行为共现 → SID 也应该相近;把行为聚类结构注入 SID 空间 这三类 InfoNCE loss 从不同角度最大化 SID 和 CF 信号之间的互信息。
-
3双侧量化(Dual Learning):用户和广告 SID 互相约束 DAS 不只对广告做 SID,也对用户做 SID(User Semantic Model, USM)。用户和广告的量化器同步训练、互相对齐,形成"用户 SID ↔ 广告 SID"的双向约束。
实验验证:双侧 SID(TIGER++)比单侧(TIGER+)在生成式推荐上效果更好。
- CF 信号不是冻结的"老师",而是和 SID 同步进化
- 量化器在对齐的同时做量化,不存在"先对齐再量化"的信息丢失
- 三种多视图对比任务充分最大化互信息,效果比单一 $\ell_2$ loss 更强
| 维度 | CF First(LETTER) | Alignment First(QARM) | 一阶段联合(DAS) |
|---|---|---|---|
| 训练阶段数 | 2 阶段 | 3 阶段 | 1 阶段 |
| 对齐发生在哪 | 量化后连续表示 $z_i$ 和冻结 CF embedding |
量化前,先对齐内容 embedding $s_i \to \tilde{s}_i$ | 量化和对齐同步进行,联合 loss |
| CF 模型是否更新 | ❌ 冻结 | ❌ 冻结 | ✅ 和 SID 联合更新 |
| 是否去偏 | ❌ 直接用有偏 CF | ❌ 直接用有偏 CF | ✅ ICDM 分离兴趣/流行度偏差 |
| 对齐方式 | $\ell_2$ loss(MSE) | $\ell_2$ loss(MSE) | InfoNCE 多视图对比(U2I + I2I/U2U + co-occ) |
| 用户侧 SID | ❌ 通常没有 | ❌ 通常没有 | ✅ 双侧量化(用户 + 广告) |
| 核心缺陷 | 解耦训练,互信息受限;CF 有偏污染 codebook | 量化会丢信息;两步仍然割裂;CF 有偏 | 训练复杂,超参多;工程成本高 |
| 代表论文 | LETTER(CIKM 2024) | QARM(快手 2024) | DAS(快手 2025) |
一句话总结三种方案
- CF First = 先有行为老师(CF embedding),再用老师塑形量化器的输出空间
- Alignment First = 先把内容 embedding 翻译成"行为语言",再对这个翻译好的表示做量化
- DAS(一阶段) = 量化器、CF 模型、对齐三者同时训练,老师也在更新,互信息最大化
完整 SID 生命周期
图文视频
$s_i \in \mathbb{R}^{256}$
(联合 CF 对齐训练)
+ $z_i$(连续)
稀疏 ID 特征
精排模型
Cross SID 特征
(TIGER++)