学习总结

(1)学习推荐系统中召回层的功能特点(要快速准确地过滤出相关物品,缩小候选集)和实现召回层的三个技术方案:简单快速的单策略召回、业界主流的多路召回、深度学习推荐系统中最常用的 Embedding 召回。

(2)Embedding 召回却可以把 Embedding 间的相似度作为唯一的判断标准,因此它可以随意限定召回的候选集大小(在线上服务的过程中,Embedding 相似性可通过余弦相似度计算)。

【王喆-推荐系统】线上服务篇-(task3)召回层_推荐系统

文章目录


记得前段时间百度CEO李彦宏在北大的演讲提到工程的思维很重要,就说到用户搜索关键字时,百度并不用对成百上千的网页进行排序再提供给用户(因为这样用户也不可能全部看完所有网页),所以只对1000个网页进行排序就能极大降低难度。

而同样道理:如果利用比较复杂的推荐模型,特别是深度学习推荐模型,对 500 万个短视频打分,这个过程是非常消耗计算资源的。每秒几十万甚至上百万的用户同时请求服务器,逐个候选视频打分产生的计算量,是任何集群都承受不了的。

召回层:在推荐物品候选集规模非常大的时候,召回层用来快速又准确地筛选掉不相关物品,从而节约排序时所消耗的计算资源。

一、召回层和排序层的功能特点

“召回层”处于推荐系统的线上服务模块之中,推荐服务器从数据库或内存中拿到所有候选物品集合后,会依次经过召回层、排序层、再排序层(也被称为补充算法层),才能够产生用户最终看到的推荐列表。

【王喆-推荐系统】线上服务篇-(task3)召回层_推荐系统_02


召回层就是要快速、准确地过滤出相关物品,缩小候选集;

排序层则要以提升推荐效果为目标,作出精准的推荐列表排序。

【王喆-推荐系统】线上服务篇-(task3)召回层_推荐系统_03


在设计召回层时,要同时权衡【计算速度】和【召回率】。

为了提高计算速度,我们需要使召回策略尽量简单,而为了提高召回率或者说召回精度,让召回策略尽量把用户感兴趣的物品囊括在内,这又要求召回策略不能过于简单,否则召回物品就无法满足排序模型的要求。

二、3种召回方式

2.1 单策略召回

单策略召回:通过制定一条规则或者利用一个简单模型来快速地召回可能的相关物品。
缺点:不能满足用户的多元化兴趣的需求。

这里的规则其实就是用户可能感兴趣的物品的特点,如在推荐电影的时候,我们首先要想到用户可能会喜欢什么电影。按照经验来说,很有可能是这三类,分别是大众口碑好的、近期非常火热的,以及跟我之前喜欢的电影风格类似的。

快速实现一个单策略召回层。比如在 SparrowRecSys 中,可以制定了这样一条召回策略:如果用户对电影 A 的评分较高,比如超过 4 分,那么我们就将与 A 风格相同,并且平均评分在前 50 的电影召回,放入排序候选集中。

//详见SimilarMovieFlow class
public static List<Movie> candidateGenerator(Movie movie){
ArrayList<Movie> candidates = new ArrayList<>();
//使用HashMap去重
HashMap<Integer, Movie> candidateMap = new HashMap<>();
//电影movie包含多个风格标签
for (String genre : movie.getGenres()){
//召回策略的实现
List<Movie> oneCandidates = DataManager.getInstance().getMoviesByGenre(genre, 100, "rating");
for (Movie candidate : oneCandidates){
candidateMap.put(candidate.getMovieId(), candidate);
}
}
//去掉movie本身
if (candidateMap.containsKey(movie.getMovieId())){
candidateMap.remove(movie.getMovieId());
}
//最终的候选集
return new ArrayList<>(candidateMap.values());
}

2.2 多路召回

多路召回策略:指采用不同的策略、特征或简单模型,分别召回一部分候选集,然后把候选集混合在一起供后续排序模型使用的策略。
多路召回策略是在计算速度和召回率之间进行权衡的结果。

如电影推荐中使用多路召回策略,包括热门电影、风格类型、高分评价、最新上映以及朋友喜欢等等。可以把一些推断速度比较快的简单模型(比如逻辑回归,协同过滤等)生成的推荐结果放入多路召回层中,形成综合性更好的候选集。具体就是分别执行这些策略,让每个策略选取 Top K 个物品,最后混合多个 Top K 物品,就形成了最终的多路召回候选集。

【王喆-推荐系统】线上服务篇-(task3)召回层_相似度_04


在 SparrowRecsys 中,就实现了由风格类型、高分评价、最新上映,这三路召回策略组成的多路召回方法:

public static List<Movie> multipleRetrievalCandidates(List<Movie> userHistory){
HashSet<String> genres = new HashSet<>();
//根据用户看过的电影,统计用户喜欢的电影风格
for (Movie movie : userHistory){
genres.addAll(movie.getGenres());
}
//根据用户喜欢的风格召回电影候选集
HashMap<Integer, Movie> candidateMap = new HashMap<>();
for (String genre : genres){
List<Movie> oneCandidates = DataManager.getInstance().getMoviesByGenre(genre, 20, "rating");
for (Movie candidate : oneCandidates){
candidateMap.put(candidate.getMovieId(), candidate);
}
}
//召回所有电影中排名最高的100部电影
List<Movie> highRatingCandidates = DataManager.getInstance().getMovies(100, "rating");
for (Movie candidate : highRatingCandidates){
candidateMap.put(candidate.getMovieId(), candidate);
}
//召回最新上映的100部电影
List<Movie> latestCandidates = DataManager.getInstance().getMovies(100, "releaseYear");
for (Movie candidate : latestCandidates){
candidateMap.put(candidate.getMovieId(), candidate);
}
//去除用户已经观看过的电影
for (Movie movie : userHistory){
candidateMap.remove(movie.getMovieId());
}
//形成最终的候选集
return new ArrayList<>(candidateMap.values());
}

其他改进:多线程并行、建立标签 / 特征索引、建立常用召回集缓存等方法。

多路召回策略缺点:
比如,在确定每一路的召回物品数量时,往往需要大量的人工参与和调整,具体的数值需要经过大量线上 AB 测试来决定。此外,因为策略之间的信息和数据是割裂的,所以我们很难综合考虑不同策略对一个物品的影响。

2.3 基于 Embedding 的召回方法

利用物品和用户 Embedding 相似性来构建召回层,是深度学习推荐系统中非常经典的技术方案。优势有三:

  • 多路召回中使用的“兴趣标签”、“热门度”、“流行趋势”、“物品属性”等信息都可以作为 Embedding 方法中的附加信息(Side Information),融合进最终的 Embedding 向量中,相当于考虑到了多路召回的多种策略。
  • Embedding 召回的评分具有连续性。多路召回中不同召回策略产生的相似度、热度等分值不具备可比性,所以我们无法据此来决定每个召回策略放回候选集的大小。但是,Embedding 召回却可以把 Embedding 间的相似度作为唯一的判断标准,因此它可以随意限定召回的候选集大小。
  • 在线上服务的过程中,Embedding 相似性的计算也相对简单和直接。通过简单的点积或余弦相似度的运算就能够得到相似度得分,便于线上的快速召回。
public static List<Movie> retrievalCandidatesByEmbedding(User user){
if (null == user){
return null;
}
//获取用户embedding向量
double[] userEmbedding = DataManager.getInstance().getUserEmbedding(user.getUserId(), "item2vec");
if (null == userEmbedding){
return null;
}
//获取所有影片候选集(这里取评分排名前10000的影片作为全部候选集)
List<Movie> allCandidates = DataManager.getInstance().getMovies(10000, "rating");
HashMap<Movie,Double> movieScoreMap = new HashMap<>();
//逐一获取电影embedding,并计算与用户embedding的相似度
for (Movie candidate : allCandidates){
double[] itemEmbedding = DataManager.getInstance().getItemEmbedding(candidate.getMovieId(), "item2vec");
double similarity = calculateEmbeddingSimilarity(userEmbedding, itemEmbedding);
movieScoreMap.put(candidate, similarity);
}

List<Map.Entry<Movie,Double>> movieScoreList = new ArrayList<>(movieScoreMap.entrySet());
//按照用户-电影embedding相似度进行候选电影集排序
movieScoreList.sort(Map.Entry.comparingByValue());


//生成并返回最终的候选集
List<Movie> candidates = new ArrayList<>();
for (Map.Entry<Movie,Double> movieScoreEntry : movieScoreList){
candidates.add(movieScoreEntry.getKey());
}
return candidates.subList(0, Math.min(candidates.size(), size));
}

三步生成了最终的候选集。
(1)第一步,我们获取用户的 Embedding。
(2)第二步,我们获取所有物品的候选集,并且逐一获取物品的 Embedding,计算物品 Embedding 和用户 Embedding 的相似度。开销最大的也是这一步(当物品集过大时(比如达到了百万以上的规模),线性的运算也可能造成很大的时间开销)。
(3)第三步,我们根据相似度排序,返回规定大小的候选集。

三、作业

(1)在 SparrowRecsys 中实现一个多线程版本的多路召回策略吗?

  1. 在类​​SimilarMovieProcess中​​​实例化一个线程池​​ThreadPoolExecutor​​作为静态成员变量
  2. 方法​​multipleRetrievalCandidates​​​中的候选集​​candidateMap​​​使用​​ConcurrentHashMap​​替代HashMap
  3. 风格类型、高分评价、最新上映三种召回的过程使用线程池实例(共三个线程)去执行
  4. 判断三个线程执行完后返回结果

(2)对于 Embedding 召回来说,怎么做才能提升计算 Embedding 相似度的速度?
【答】可以用局部敏感哈希。

四、课后答疑

(1)如果基于兴趣标签做召回,同一个物品,有多个标签,而用户也计算了出了多个兴趣标签,那么怎么做用户的多兴趣标签与物品的最优匹配呢?还有物品的标签有多层,那么怎么利用上一层的标签呢?

【答】简单的做法是把兴趣标签转换成multihot向量,然后就可以计算出用户和物品的相似度了。
复杂一点也可以计算每个兴趣标签的​​​TF-IDF​​​,为标签分配权重后,再转换成​​multihot​​​向量。
如果标签有多层,也不妨碍把多层标签全部放到​​​multihot​​向量中,高层标签的权重可以适当降低。这也是思路之一。

(2)阿里的​​EGES​​算法的论文,那个论文里面的EGES模型图的最底层是Sparse feature,论文中是说这一层是比较倾向于用one- hot编码的,不管side information的one-hot编码,一般item的数量会很多很多,对item本身的向量表示直接用one-hot的话,那embedding matrix是不是会很大,这样是合适的吗?

【答】one-hot特征肯定会让embedding matrix很大,但就是这样使用的,因为我们就是想求解每一个维度上对应item的embedding。
这也就是为什么说embedding层是神经网络中最耗时的部分的原因。

(3)在电商领域下,如何解决 EGES 训练非常慢的问题?

EGES 指的是阿里提出的一种 Graph Embedidng 方法,全称是 Enhanced Graph Embedding with Side Information,补充信息增强图 Embedding。它是一种融合了经典的 Deep Walk Graph Embedding 结果和其他特征的 Embedding 方法。

第一条是我们可以把商品 Embedding 进行预训练,再跟其他 side information 特征一起输入 ​​EGES​​,不用直接在 EGES 中加 Embedding 层进行 End2End 训练。

第二条是我们可以把商品进行聚类后再输入 ​​EGES​​ 网络,比如非常类似的商品,可以用一个商品聚类 id 替代,当作一个商品来处理。事实上,这种方法往往可以大幅减少商品数量的量级,AirBnb 就曾经非常成功地应用了该方法,用一些特征的组合来代替一类商品或用户,不仅大幅加快训练速度,而且推荐效果也没有受到影响。

Reference

(1)https://github.com/wzhe06/Reco-papers
(2)《深度学习推荐系统实战》,王喆