《深度學習推薦系統實戰》 學習筆記 3月Day 15
11 | 召回層:如何快速又準確地篩選掉不相關物品?
召回層和排序層的功能特點
如何理解“單策略召回”方法?
單策略召回指的是,通過制定一條規則或者利用一個簡單模型來快速地召回可能的相關物品。 這裡的規則其實就是使用者可能感興趣的物品的特點,我們拿 SparrowRecSys 裡面的電影推薦為例。在推薦電影的時候,我們首先要想到使用者可能會喜歡什麼電影。按照經驗來說,很有可能是這三類,分別是大眾口碑好的、近期非常火熱的,以及跟我之前喜歡的電影風格類似的。基於其中任何一條,我們都可以快速實現一個單策略召回層。比如在 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());
}
如何理解“多路召回”方法
所謂“多路召回策略”,就是指採用不同的策略、特徵或簡單模型,分別召回一部分候選集,然後把候選集混合在一起供後續排序模型使用的策略。其中,各簡單策略保證候選集的快速召回,從不同角度設計的策略又能保證召回率接近理想的狀態,不至於損害排序效果。所以,多路召回策略是在計算速度和召回率之間進行權衡的結果。這裡,我們還是以電影推薦為例來做進一步的解釋。下面是我給出的電影推薦中常用的多路召回策略,包括熱門電影、風格型別、高分評價、最新上映以及朋友喜歡等等。除此之外,我們也可以把一些推斷速度比較快的簡單模型(比如邏輯迴歸,協同過濾等)生成的推薦結果放入多路召回層中,形成綜合性更好的候選集。具體的操作過程就是,我們分別執行這些策略,讓每個策略選取 Top K 個物品,最後混合多個 Top K 物品,就形成了最終的多路召回候選集。整個過程就如下所示:
在 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());
}
基於 Embedding 的召回方法
一方面,多路召回中使用的“興趣標籤”“熱門度”“流行趨勢”“物品屬性”等資訊都可以作為 Embedding 方法中的附加資訊(Side Information),融合進最終的 Embedding 向量中 。因此,在利用 Embedding 召回的過程中,我們就相當於考慮到了多路召回的多種策略。另一方面,Embedding 召回的評分具有連續性。我們知道,多路召回中不同召回策略產生的相似度、熱度等分值不具備可比性,所以我們無法據此來決定每個召回策略放回候選集的大小。但是,Embedding 召回卻可以把 Embedding 間的相似度作為唯一的判斷標準,因此它可以隨意限定召回的候選集大小。最後,在線上服務的過程中,Embedding 相似性的計算也相對簡單和直接。通過簡單的點積或餘弦相似度的運算就能夠得到相似度得分,便於線上的快速召回。在 SparrowRecsys 中,我們也實現了基於 Embedding 的召回方法。我具體程式碼放在下面,你可以參考一下。
```
public static List
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));
}
```
這裡,我再帶你簡單梳理一下整體的實現思路。總的來說,我們通過三步生成了最終的候選集。第一步,我們獲取使用者的 Embedding。第二步,我們獲取所有物品的候選集,並且逐一獲取物品的 Embedding,計算物品 Embedding 和使用者 Embedding 的相似度。第三步,我們根據相似度排序,返回規定大小的候選集。在這三步之中,最主要的時間開銷在第二步,雖然它的時間複雜度是線性的,但當物品集過大時(比如達到了百萬以上的規模),線性的運算也可能造成很大的時間開銷。那有沒有什麼方法能進一步縮小 Embedding 召回層的運算時間呢?這個問題我們留到下節課來討論。
小結
今天,我們一起討論了推薦系統中召回層的功能特點和實現方法。並且重點講解了單策略召回、多路召回,以及深度學習推薦系統中常用的基於 Embedding 的召回。為了方便你對比它們之間的技術特點,我總結了一張表格放在了下面,你可以看一看。