【Pytorch基础教程31】YoutubeDNN模型解析

举报
野猪佩奇996 发表于 2022/06/24 22:04:45 2022/06/24
【摘要】 学习总结 youtubeDNN即一个用softmax loss损失函数训练,且训练模式为list-wise sample的经典DSSM双塔模型。 (1)候选集生成模型:用了Embedding MLP,...

学习总结

  • youtubeDNN即一个用softmax loss损失函数训练,且训练模式为list-wise sample的经典DSSM双塔模型。

(1)候选集生成模型:用了Embedding MLP,注意最后的多分类的输出层,预测的是用户点击了“哪个”视频。线上服务时,需要从输出层提取出【视频 Embedding】,从最后一层 ReLU 层得到【用户 Embedding】,然后利用最近邻搜索(如LSH等)快速得到某用户的候选集。这样能够提高模型服务的效率了,不用把模型推断的逻辑搬上服务器,只需要将用户 Embedding 和视频 Embedding 存到redis特征数据库就行了。

(2)排序模型:也是Embedding MLP 架构,但是有更多的用户和视频的特征输入层,输出层采用了 Weighted LR(逻辑回归) 作为输出层(预测是典型CTR),并且使用观看时长作为正样本权重,让模型能够预测出观看时长,这更接近 YouTube 要达成的商业目标。

(3)高效训练模型:作者借鉴了机器翻译中的Sampled Softmax方法, 即利用重要性采样出部分视频 V ′ V^{\prime} V,更多细节可参考该论文:通过重要性采样几千个负样本;经过importance weighting的方式进行修正。其他解决方法:

  • 利用其它的方法,去逼近softmax的概率,比如:基于noise-contrastive estimation (NCE)的方法,类似于word2vec中skip-gram的negative sampling
  • 层次softmax

一、YoutubeDNN模型

YouTube有很多用户原创内容,其商业模式和Netflix、国内的腾讯、爱奇艺等流媒体不同,后者是采购或自制的电影,并且YouTube的视频基数巨大,用户难以发现喜欢的内容。本文根据典型的两阶段信息检索二分法:首先描述一种深度候选生成模型,接着描述一种分离的深度排序模型。

YouTube在视频推荐的三大挑战:
规模大、更新快、噪声(用户历史行为稀疏;视频中很多数据是非结构化的)。

(1)数据规模:YouTube 的用户和视频量级都是十亿级别的。
(2)更新快:推荐系统需要及时对新上传的视频和用户的新行为作出响应。
(3)数据噪音:由于稀疏和外部因素影响,用户的历史行为很难预测。大部分 YouTube 视频只有隐式反馈(即用户对视频的观看行为),缺少显式反馈(即用户对视频的评分)。

YouTubeDNN的整体架构(召回层和排序层):
在这里插入图片描述
第一阶段,召回层:从百万级视频中筛选出小部分视频;要求速度快;召回层根据用户历史观看、搜索等记录进行召回,以满足用户泛化的兴趣。

第二阶段,排序层:为了更精准地推荐给用户,这阶段需要更加复杂的模型和特征进行排序。

第三阶段,线下评估:评估指标有precision、recall、ranking loss;最终线上做A/B测试(指标:点击率、观看时间)。

A/B测试即把被测对象随机分成 A、B 两组,通过对照测试的方法得出实验结论。在线上评估指标的制定过程中,要尽量保证这些指标与线上业务的核心指标保持一致,从而知道是否达成了公司的商业目标。

二、召回模型

2.0 传统召回的思路

先离线计算好商品的embedding和用户的embedding,在线上召回时,根据用户的embedding和所有商品的embedding内积,找到topN。这种传统方式需要解决几个问题:

  • 商品的embedding和用户embedding如何保证在 同一个空间
  • 用户embedding和所有商品的内积计算,存在性能问题
  • 如SVD奇异值分解的方法,输入协同矩阵,特征较为单一

2.1 问题建模和训练要点

在召回阶段,YouTube把推荐问题看作是一个超大规模多分类softmax问题,定义为基于特定用户 U U U 和其上下文 C C C,在 t t t 时刻,将视频库 V V V 中指定的视频 w t w_t wt划分为第 i i i 类的概率: P ( w t = i ∣ U , C ) = e v i u ∑ j ∈ V e v j u P\left(w_{t}=i \mid U, C\right)=\frac{e^{v_{i} u}}{\sum_{j \in V} e^{v_{j} u}} P(wt=iU,C)=jVevjueviu
其中:

  • u ∈ R N u \in \mathbb{R}^{N} uRN 是用户在上下文中的高维embedding;
  • v j ∈ R N v_{j} \in \mathbb{R}^{N} vjRN是每个候选视频的embedding
  • v j u v_j u vju是第 j j j 个视频的embedding

小结:给定用户历史行为和上下文,学习用户embedding,然后输入softmax,生成召回层的候选集结果。training多分类,serving最近邻。

(1)训练:多分类问题

  • youtubeDNN即一个用softmax loss损失函数训练,且训练模式为list-wise sample的经典DSSM双塔模型。
  • 训练的是一个多分类问题,使用负采样的softmax loss(1正,k负)

(2)embedding生成

  • 只有user特征和user塔,但是没有item塔
  • user embedding:user实时特征进行user塔的结果
  • item embedding:softmax层(Dense(dim, N)+softmax)权重矩阵

(3)使用example age

example age:消除样本时间带来的bias

  • 训练:用训练集中时间的最后一刻 - sample log产生的时间
  • 预测:设为0,保证预测是出于训练的最后一刻

2.2 召回层模型架构

视频召回的候选集生成模型如下图,其实本质上也是一个双塔模型:
在这里插入图片描述

图2 YouTube候选集生成模型架构

从下往上看网络:

(1)输入层

input特征:
(1)用户历史观看视频的embedding向量,还需要进行mean pooling。
(2)搜索词的embedding向量。
(3)用户地理特征和用户设备特征:均为一些离散特征,可以采用embedding方法或者直接采用one-hot方(当离散的维度较小时)。

YouTube是利用用户观看序列和搜索序列,通过类似Item2vec预训练生成。
特征向量中还包括用户的地理位置Embedding、年龄、性别等特征。

【注意】样本年龄这个特征,YouTube 不仅使用了原始特征值,还把经过平方处理的特征值也作为一个新的特征输入模型。该操作是为了挖掘特征非线性的特性
对连续型特征的处理方式不仅限于平方,其他诸如开方、Log、指数等操作都可以用于挖掘特征的非线性特性。

(4)example age:在视频推荐系统中要注意一点,用户更倾向于观看新视频,但是机器学习模型是基于历史观看视频记录进行学习,所以这和初衷有点违背,所以在文中构建了一个特征example age,可以理解为视频的年龄:初始值设为0,随着时间的增长,记录视频的年龄。加入后再进行实验发现效果明显:
在这里插入图片描述
(5)人口属性特征:提供先验,使得对于新用户也能做出合理的推荐(冷启动),即对用户的地理区域和使用

(2)ReLU层

将(1)中的特征通过concat层连接起来,然后输入到三层ReLU神经网络中训练。

使用常见的塔状设计,底层最宽,往上每层的神经元数目减半,直到 Softmax 输入层是 256 维(1024ReLU->512ReLU->256ReLU)。

(3)softmax层

基于特定用户 U U U 和其上下文 C C C,在 t t t 时刻,将视频库 V V V 中指定的视频 w t w_t wt划分为第 i i i 类的概率: P ( w t = i ∣ U , C ) = e v i u ∑ j ∈ V e v j u P\left(w_{t}=i \mid U, C\right)=\frac{e^{v_{i} u}}{\sum_{j \in V} e^{v_{j} u}} P(wt=iU,C)=jVevjueviu
其中:

  • u ∈ R N u \in \mathbb{R}^{N} uRN 是用户在上下文中的高维embedding;
  • v j ∈ R N v_{j} \in \mathbb{R}^{N} vjRN是每个候选视频的embedding
  • v j u v_j u vju是第 j j j 个视频的embedding

(4)输出层

三层ReLU网络后,YouTube这里是使用softmax作为输出层,注意此处输出层不是做点击率预估,而是预测用户会点击哪个视频。输出层的维度和视频ID的embedding向量维度相同,最终得到用户向量u。

通过该网络结构的学习,最终可以得到所有视频的embedding向量V,维度为pool_size × k,其中:

  • pool_size为训练集视频资源的大小,
  • k为embedding的维度。
  • 还可以得到所有用户的输出向量u,其中每个用户向量的维度为k,和视频的embedding向量维度一致。

在这里插入图片描述
在输出层中有两个问题需要注意:

问题1:【用视频ID作为预测label】
模型服务中,如果每次推荐请求都需要端到端地运行一遍model(处理一遍候选集),因为model参数量巨大,所以推断过程开销也大。这里为了提高模型服务效率,输出层这样搞后,就能将得到的用户和视频embedding,并且预存到线上的特征数据库中,最后通过embedding最近邻搜索方法(如局部敏感哈希LSH等)匹配。
在这里插入图片描述


问题2:【embedding的产生】

(1)视频embedding
视频向量的softmax层的参数本质上是一个 m × n 的矩阵,其中:

  • m 指的是最后一层红色的 ReLU 层的维度 m
  • n 指的是分类的总数,也就是 YouTube 所有视频的总数 n。
  • 视频 Embedding 就是这个 m x n 维矩阵的各列向量。(这样的 Embedding 生成方法其实和 word2vec 中词向量的生成方法是相同的)

(2)用户embedding向量
因为input的特征向量都是用户相关的特征,如在使用某用户的u的特征向量作为模型input,最后一层ReLU层的output可以作为该用户的embedding向量。

2.3 Heterogeneous Signals

异构信号。
使用深度学习作为矩阵分解的泛化,优势在于,可以容易地将任意连续和分类特征添加到模型中。搜索历史记录与观看历史记录的处理方式相似,每个查询都被标记为unigram和bigrams,并且每个标记都被嵌入。取平均后,用户的标记化嵌入查询代表了汇总的密集搜索历史记录。

人口统计特征对于提供先验信息很重要,因此推荐对于新用户(冷启动)而言行为合理。用户的地理区域和设备已嵌入并连接在一起。简单的二进制和连续特征(如用户的性别,登录状态和年龄)将以归一化为[0, 1]的实际值直接输入到网络中。

2.4样本选择和上下文选择

正负样本和上下文选择。
在有监督学习中,最重要的是选择label,这个也决定了模型效果的上限:

  • 使用更广的数据源:不仅仅使用推荐场景的数据进行训练,其他场景比如搜索等的数据也要用到,这样也能为推荐场景提供一些explore。

  • 为每个用户生成固定数量训练样本:我们在实际中发现的一个practical lessons,如果为每个用户固定样本数量上限,平等的对待每个用户,避免loss被少数active用户domanate,能明显提升线上效果。

  • 抛弃序列信息:我们在实现时尝试的是去掉序列信息,对过去观看视频/历史搜索query的embedding向量进行加权平均。这点其实违反直觉,可能原因是模型对负反馈没有很好的建模。

  • 不对称的共同浏览(asymmetric co-watch)问题:所谓asymmetric co-watch指的是用户在浏览视频时候,往往都是序列式的,开始看一些比较流行的,逐渐找到细分的视频。下图所示图(a)是hled-out方式,利用上下文信息预估中间的一个视频;图(b)是predicting next watch的方式,则是利用上文信息,预估下一次浏览的视频。我们发现图(b)的方式在线上A/B test中表现更佳。而实际上,传统的协同过滤算法,都是隐含采样图(a)的held-out的方式,忽略了不对称的浏览模式。

在这里插入图片描述

2.5 Experiments with Features and Depth

在这里插入图片描述

四、YouTubeDNNN模型代码

import torch
from ...basic.layers import MLP, EmbeddingLayer

class YoutubeDNN(torch.nn.Module):
    """The match model mentioned in `Deep Neural Networks for YouTube Recommendations` paper.
    It's a DSSM match model trained by global softmax loss on list-wise samples.
    Note in origin paper, it's without item dnn tower and train item embedding directly.

    Args:
        user_features (list[Feature Class]): training by the user tower module.
        item_features (list[Feature Class]): training by the embedding table, it's the item id feature.
        neg_item_feature (list[Feature Class]): training by the embedding table, it's the negative items id feature.
        user_params (dict): the params of the User Tower module, keys include:`{"dims":list, "activation":str, "dropout":float, "output_layer":bool`}.
        sim_func (str): similarity function, includes `["cosine", "dot"]`, default to "cosine".
        temperature (float): temperature factor for similarity score, default to 1.0.
    """

    def __init__(self, user_features, item_features, neg_item_feature, user_params, sim_func="cosine", temperature=1.0):
        super().__init__()
        self.user_features = user_features
        self.item_features = item_features
        self.neg_item_feature = neg_item_feature
        self.sim_func = sim_func
        self.temperature = temperature
        self.user_dims = sum([fea.embed_dim for fea in user_features])

        self.embedding = EmbeddingLayer(user_features + item_features)
        self.user_mlp = MLP(self.user_dims, output_layer=False, **user_params)
        self.mode = None

    def forward(self, x):
        user_embedding = self.user_tower(x)
        item_embedding = self.item_tower(x)
        if self.mode == "user":
            return user_embedding
        if self.mode == "item":
            return item_embedding
        if self.sim_func == "cosine":
            y = torch.cosine_similarity(user_embedding, item_embedding, dim=-1)  #[batch_size, 1+n_neg_items, embed_dim]
        elif self.sim_func == "dot":
            y = torch.mul(user_embedding, item_embedding).sum(dim=1)
        else:
            raise ValueError("similarity function only support %s, but got %s" % (["cosine", "dot"], self.sim_func))
        y = y / self.temperature
        return y

    def user_tower(self, x):
        if self.mode == "item":
            return None
        input_user = self.embedding(x, self.user_features, squeeze_dim=True)  #[batch_size, num_features*deep_dims]
        user_embedding = self.user_mlp(input_user).unsqueeze(1)  #[batch_size, 1, embed_dim]
        if self.mode == "user":
            return user_embedding.squeeze(1)  #inference embedding mode -> [batch_size, embed_dim]
        return user_embedding

    def item_tower(self, x):
        if self.mode == "user":
            return None
        pos_embedding = self.embedding(x, self.item_features, squeeze_dim=False)  #[batch_size, 1, embed_dim]
        if self.mode == "item":  #inference embedding mode
            return pos_embedding.squeeze(1)  #[batch_size, embed_dim]
        neg_embeddings = self.embedding(x, self.neg_item_feature,
                                        squeeze_dim=False).squeeze(1)  #[batch_size, n_neg_items, embed_dim]
        return torch.cat((pos_embedding, neg_embeddings), dim=1)  #[batch_size, 1+n_neg_items, embed_dim]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

工程上的问题:全局负采样

五、YouTube双塔模型

在这里插入图片描述
论文:Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations,2019(2016版的升级版)

在这里插入图片描述
在这里插入图片描述

六、模型训练trick

6.1 In-Batch负采样(纠编)

6.2 余弦相似度求相似度

等价于:为啥加L2 norm

在这里插入图片描述

6.3 温度系数

温度系数
复习:torch.nn.CrossEntropyLoss = LogSoftmax + NLLLoss(可以参考官方文档)。

(1)多分类场景下的损失函数:

分布和API
在这里插入图片描述
法一:把每一个类别的确定看作是一个二分类问题。利用交叉熵。

为了解决抑制问题,就不要输出每个类别的概率,且满足每个概率大于0和概率之和为1的条件。(二分类我们输出的是分布,求出一个然后用1减去即可,多分类虽然也可以这样,但是最后1减去其他所有概率的计算,还需要构建计算图有点麻烦)。
之前二分类中的交叉熵的两项中只能有一项为0.

在这里插入图片描述
(1)NLLLoss函数计算如下红色框:
在这里插入图片描述
(2)可以直接使用torch.nn.CrossEntropyLoss(将下列红框计算纳入)。注意右侧是由类别生成独热编码向量。
在这里插入图片描述
交叉熵,最后一层网络不需要激活,因为在最后的Torch.nn.CrossEntropyLoss已经包括了激活函数softmax。
(1)交叉熵手写版本

import numpy as np
y = np.array([1, 0, 0])
z = np.array([0.2, 0.1, -0.1])
y_predict = np.exp(z) / np.exp(z).sum()
loss = (- y * np.log(y_predict)).sum()
print(loss)
# 0.9729189131256584

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(2)交叉熵pytorch栗子
在这里插入图片描述
交叉熵损失和NLL损失的区别(读文档):

  • https://pytorch.org/doc s/stable/nn.html#crossentropyloss
  • https://pytorch.org/docs/stable/nn.html#nllloss
  • 搞懂为啥:CrossEntropyLoss <==> LogSoftmax + NLLLoss

(2)一个场景栗子

场景:采用List wise的训练方式,1个正样本,3个负样本,cosine相似度作为训练过程中的衡量指标。

假设当前的模型完美的预测了一条训练数据, 即输出的 logits 为 ( 1 , − 1 , − 1 , − 1 ) (1,-1,-1,-1) (1,1,1,1),则loss理应很非常小。但此时如果采用 CrossEntropyLoss, 得到的 Loss 是:
− log ⁡ ( exp ⁡ ( 1 ) / ( exp ⁡ ( 1 ) + exp ⁡ ( − 1 ) ∗ 3 ) ) = 0.341 -\log (\exp (1) /(\exp (1)+\exp (-1) * 3))=0.341 log(exp(1)/(exp(1)+exp(1)3))=0.341
但此时如果对 logits 除上一个温度系数 temperature = 0.2 =0.2 =0.2, 即 logits 为 ( 5 , − 5 , − 5 (5,-5,-5 (5,5,5, -5), 经过 CrossEntropyLoss, 得到的 Loss 是:
− log ⁡ ( exp ⁡ ( 5 ) / ( exp ⁡ ( 5 ) + exp ⁡ ( − 5 ) ∗ 3 ) ) = 0.016 -\log (\exp (5) /(\exp (5)+\exp (-5) * 3))=0.016 log(exp(5)/(exp(5)+exp(5)3))=0.016
这样就会得到一个很小到可以忽略不计的 Loss了。对 logits 除上一个 temperature 的作用是扩大 logits 中每个元素中的上下限, 拉回 softmax 运算的敏感范围。业界一般 L2 Norm 与 temperature 搭配使用。

七、youtube双塔模型代码

八、Facebook双塔模型

在这里插入图片描述

九、工程实现的注意点

9.1 负样本构造总结

在这里插入图片描述

9.2 召回和排序区别

在这里插入图片描述

9.3 其他双塔模型和未来发展

在这里插入图片描述
在这里插入图片描述

Reference

[1] SysRec2016 | Deep Neural Networks for YouTube Recommendations
[2] 借Youtube论文,谈谈双塔模型的八大精髓问题
[3] Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations,2019(2016版的升级版)
[4] YouTube采样修正的双塔模型论文精读
[5] https://github.com/datawhalechina/torch-rechub/tree/main/torch_rechub/models/matching
[6] 双塔那些事:https://www.bilibili.com/video/BV1Tt4y1W7qE?spm_id_from=333.999.0.0&vd_source=3d707248207c841de4a64a0ffce83324
[7] youtube双塔:https://datawhalechina.github.io/fun-rec/#/ch02/ch2.1/ch2.1.2/YoutubeTwoTower
[8] 浅梦deepmatch代码复现:https://github.com/shenweichen/AlgoNotes
[9] Youtube推荐双塔模型——SBCNM
[10] 召回模型 YoutubeDNN, DSSM

文章来源: andyguo.blog.csdn.net,作者:山顶夕景,版权归原作者所有,如需转载,请联系作者。

原文链接:andyguo.blog.csdn.net/article/details/125416842

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。