1 YoutubeDNN

YouTube是世界上最⼤的视频创作及分享平台,其视频推荐的⾯临的主要问题有:

    1. 海量数据:现有的推荐算法通常在⼩数据集有良好的表现,但是在YouTube这样规模的数据集上未必表现良好,同时YouTube⽤户基数庞⼤,对服务系统的响应时间有严格要求。
    1. 新鲜度:YouTube系统每秒钟都有⼤量新视频上传,推荐系统应该能够快速的对视频及⽤户⾏为作出反馈,并平衡新⽼视频的综合推荐。
    1. 噪声: ⽤户通常多种外界因素⼲扰,推荐系统通常不能够准确获得⽤户对视频的反馈信息,所以推荐算法必须对数据⾜够鲁棒。

Deep Neural Networks for YouTube Recommendations
论文通过深度召回模型(candidate generation model)和深度排序模型(deep ranking model)搭建推荐系统,为YouTube用户推荐视频。文章中还详细介绍了在实践应用中的一些小tricks.

1.1 推荐系统架构

image.png
系统中有两个网络,分别用于召回(candidate generation)和排序。
召回网络从用户行为历史中拿到event作为输入,最终从一个巨大的video集中拿到几百个video。这一网络仅通过协同过滤来提供粗糙的个性化。用户之间的相似性通过观看video的id,查询token和一些人口统计学特征来表征。
在调整网络的过程中我们利用了一些离线指标,例如精度、召回、ranking loss等等。我们最终利用了A/B测试,通过观察点击率、video观看时长等指标来证明模型的有效性。

1.2 召回部分

将推荐视作一个巨多类别的多分类问题,即给出user U 和上下文C,从百万量级的video集V中确定时刻t的video取值Task03 召回模型:YoutubeDNN、DSSM - 图2
Task03 召回模型:YoutubeDNN、DSSM - 图3
其中Task03 召回模型:YoutubeDNN、DSSM - 图4表示(user, context)的高维度embedding,Task03 召回模型:YoutubeDNN、DSSM - 图5表示每个候选video的embedding。这部分网络的任务就是学习到用户历史和上下文构成的用户embedding u
尽管YouTube有很多显式反馈,我们还是选择了隐式反馈来训练模型,即用户看完一个video是一个正样本。这种选择有利于我们对于长尾video的推荐。
为有效地对模型进行训练,我们选择了candidate sampling并对权重进行了修正。对每个样本,目标是使正确label和采样到的负类别的交叉熵最小化。
召回模型
image.png

1.3 排序模型

image.png

  • 输入:video embedding,language embedding,上次观看时间等一系列特征
  • 输出:利用逻辑回归进行打分,返回各个推荐的得分(根据得到的点击概率进行排名)
  • 目标变量:期望观看时间

    1.4 模型tricks

  • 模型特征:即使神经网络已经在一定程度上解决了特征工程的问题,本篇文章仍然花费经历建立大量有效的特征,如:用户从这个频道观看的video个数;用户最后一次浏览该话题的时间;推荐的来源;推荐来源的得分;是否推荐过但是没看;用户是否登录;用户看的最后N个视频embedding;用户上次搜索序列embedding;用户特征、商品特征等

  • 分类变量映射到embedding
  • 连续特征[0,1]归一化
  • 目标变量:观看时间;利用加权逻辑回归,正例(观看)用观看时间加权,负例(未点击)用单位权重加权,则逻辑回归几率(odds)为 Task03 召回模型:YoutubeDNN、DSSM - 图8 ,其中N是样本数量,k是正样本个数,Ti是样本i的观看时间
  • 增加模型深度可以减少损失

    1.5 代码实现

    ```python class YoutubeDNN(torch.nn.Module): def init(self, user_features, item_features, neg_item_feature, user_params, temperature=1.0):

    1. super().__init__()
    2. self.user_features = user_features
    3. self.item_features = item_features
    4. self.neg_item_feature = neg_item_feature
    5. self.temperature = temperature
    6. self.user_dims = sum([fea.embed_dim for fea in user_features])
    7. self.embedding = EmbeddingLayer(user_features + item_features)
    8. self.user_mlp = MLP(self.user_dims, output_layer=False, **user_params)
    9. self.mode = None

    def forward(self, x):

    1. user_embedding = self.user_tower(x)
    2. item_embedding = self.item_tower(x)
    3. if self.mode == "user":
    4. return user_embedding
    5. if self.mode == "item":
    6. return item_embedding
    7. # 计算相似度
    8. y = torch.mul(user_embedding, item_embedding).sum(dim=2)
    9. y = y / self.temperature
    10. return y

    def user_tower(self, x):

    1. # 用于inference_embedding阶段
    2. if self.mode == "item":
    3. return None
    4. input_user = self.embedding(x, self.user_features, squeeze_dim=True)
    5. user_embedding = self.user_mlp(input_user).unsqueeze(1)
    6. user_embedding = F.normalize(user_embedding, p=2, dim=2)
    7. if self.mode == "user":
    8. return user_embedding.squeeze(1)
    9. return user_embedding

    def item_tower(self, x):

    1. if self.mode == "user":
    2. return None
    3. pos_embedding = self.embedding(x, self.item_features, squeeze_dim=False)
    4. pos_embedding = F.normalize(pos_embedding, p=2, dim=2)
    5. if self.mode == "item":
    6. return pos_embedding.squeeze(1)
    7. neg_embeddings = self.embedding(x, self.neg_item_feature, squeeze_dim=False).squeeze(1)
    8. neg_embeddings = F.normalize(neg_embeddings, p=2, dim=2)
    9. return torch.cat((pos_embedding, neg_embeddings), dim=1)

```