2.3.3. YouTubeDNN:从匹配到预测用户下一行为

YouTube深度神经网络推荐系统 (Covington et al., 2016) 代表了双塔模型演进的一个重要里程碑。YouTubeDNN在架构上延续了双塔设计,但引入了一个关键的思想转变:将召回任务重新定义为“预测用户下一个会观看的视频”。

../../_images/youtubednn_candidate.png

图2.3.2 YouTubeDNN候选生成模型架构

YouTubeDNN采用了“非对称”的双塔架构:用户塔集成了观看历史、搜索历史、人口统计学特征等多模态信息,用户观看的视频ID通过嵌入层映射后进行平均池化聚合,模型还引入了“Example Age”特征来建模内容新鲜度的影响;物品塔则相对简化,本质上是一个巨大的嵌入矩阵,每个视频对应一个可学习的向量,避免了复杂的物品特征工程。

这种“预测下一个观看视频”的任务设定,本质上类似于NLP中的next token预测,可以自然地建模为一个极端多分类问题:

(2.3.11)\[P(w_t=i|U,C) = \frac{e^{v_i \cdot u}}{\sum_{j \in V} e^{v_j \cdot u}}\]

这里\(w_t\)表示用户在时间\(t\)观看的视频,\(U\)是用户特征,\(C\)是上下文信息,\(V\)是整个视频库。由于视频库规模庞大,直接计算全量Softmax不可行,因此采用Sampled Softmax进行高效训练。

2.3.3.1. 关键的工程技巧

YouTubeDNN的成功不仅来自于模型设计,更来自于一系列精心设计的工程技巧:

非对称的时序分割:传统协同过滤通常随机保留验证项目,但这种做法存在未来信息泄露问题。视频消费具有明显的不对称模式——剧集通常按顺序观看,用户往往从热门内容开始逐步深入小众领域。因此,YouTubeDNN采用时序分割策略:对于作为预测目标的用户观看记录,只使用该目标之前的历史行为作为输入特征。这种“回滚”机制更符合真实的推荐场景。

../../_images/youtubednn_temporal_split.png

图2.3.3 非对称共同观看模式

负采样策略:为了高效处理数百万类别的Softmax,模型采用重要性采样技术,每次只对数千个负样本进行计算,将训练速度提升了100多倍。

用户样本均衡:为每个用户生成固定数量的训练样本,避免高活跃用户主导模型学习。这个看似简单的技巧,对提升长尾用户的推荐效果至关重要。

YouTubeDNN的成功在于建立了一套可扩展、可工程化的推荐系统范式——训练时使用复杂的多分类目标和丰富的用户特征,服务时通过预计算物品向量和实时计算用户向量,配合高效的ANN检索完成召回。这种设计实现了训练复杂度和服务效率的有效平衡,至今仍被广泛借鉴。

2.3.3.2. 代码实践

YouTubeDNN的用户塔设计体现了“非对称”的思想,它整合了多种用户特征和历史行为序列:

# 整合用户特征和历史行为序列
user_feature_embedding = concat_group_embedding(
    group_embedding_feature_dict, "user_dnn"
)  # B x (D * N)

if "raw_hist_seq" in group_embedding_feature_dict:
    hist_seq_embedding = concat_group_embedding(
        group_embedding_feature_dict, "raw_hist_seq"
    )  # B x D
    user_dnn_inputs = tf.concat(
        [user_feature_embedding, hist_seq_embedding], axis=1
    )  # B x (D * N + D)
else:
    user_dnn_inputs = user_feature_embedding

# 构建用户塔:输出归一化的用户向量
user_dnn_output = DNNs(
    units=dnn_units + [emb_dim], activation="relu", use_bn=False
)(user_dnn_inputs)
user_dnn_output = L2NormalizeLayer(axis=-1)(user_dnn_output)

物品塔则采用简化设计,直接使用物品Embedding表:

# 物品Embedding表(从特征列配置中获取)
item_embedding_table = embedding_table_dict[label_name]

# 为评估构建物品模型
output_item_embedding = SqueezeLayer(axis=1)(
    item_embedding_table(input_layer_dict[label_name])
)
output_item_embedding = L2NormalizeLayer(axis=-1)(output_item_embedding)

训练时采用Sampled Softmax优化,将百万级的多分类问题转化为高效的采样学习:

# 构建采样softmax层
sampled_softmax_layer = SampledSoftmaxLayer(item_vocab_size, neg_sample, emb_dim)
output = sampled_softmax_layer([
    item_embedding_table.embeddings,
    user_dnn_output,
    input_layer_dict[label_name]
])

这种设计的核心优势在于:用户塔可以根据业务需求灵活扩展特征和模型复杂度,而物品塔保持简洁高效,易于离线预计算和实时检索。

下面训练YouTubeDNN并评估召回效果。

from funrec import run_experiment

run_experiment('youtubednn')
+---------------+--------------+-----------+----------+----------------+---------------+
|   hit_rate@10 |   hit_rate@5 |   ndcg@10 |   ndcg@5 |   precision@10 |   precision@5 |
+===============+==============+===========+==========+================+===============+
|        0.0151 |        0.001 |    0.0048 |   0.0004 |         0.0015 |        0.0002 |
+---------------+--------------+-----------+----------+----------------+---------------+