<audio id="audio" title="14 | 融会贯通：Sparrow RecSys中的电影相似推荐功能是如何实现的？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cc/57/cc197c38ac9e8343faac60d8f9b30457.mp3"></audio>

你好，我是王喆。

课程进行到这里，推荐系统架构的大部分知识点，包括特征工程、Embedding模型，到推荐服务的搭建，线上推荐过程的实现，我们都已经学习并且实践过了。如果你坚持跟着我一起学下来的话，可以说已经是“武功小成”了。

为了帮你巩固所学，今天，我就带你从头到尾地实现一个完整的推荐功能，**相似电影推荐**，来帮助你打通推荐系统的“任督二脉”。

## “清点技能库”，看看我们已有的知识储备有哪些

在开始实现相似电影推荐功能之前，我想先带着你一起清点一下自己的技能库。我喜欢把推荐的过程比喻成做菜的过程，接下来，我就按照做菜的四个关键步骤，带你回顾一下前面学过的重点知识。

**第一步，准备食材。** 准备食材的过程就是我们准备推荐所需特征的过程。在特征工程篇中，我们不仅学会了怎么挑选“食材”，怎么处理“食材”，而且还实践了“备菜”的高级技能Embedding技术。具体来说就是，我们能够利用物品序列数据，通过Item2vec方法训练出Embedding，也能够使用Deep Walk和Node2vec把图结构数据生成Graph Embedding。

总的来说，因为Embedding技术的本质就是利用了物品之间的相关性，所以Embedding是做好“相似推荐”这盘菜的关键。

**第二步，食材下锅。** 备好了菜，在正式开炒之前，我们肯定要把食材下锅。在推荐系统中“食材下锅”的过程有两个：一是把线上推荐所用的特征存储到数据库中，在之前的课程中我们已经实践过使用Redis作为特征数据库的方法，另一个是把模型部署到模型服务模块，我们也已讲过了预训练Embedding，Embedding加轻量级线上模型，TensorFlow Serving等多种模型服务方式，这节课我们将采用预训练Embedding的方式进行模型服务。

**第三步，做菜技术。** “做菜的技术”说的是推荐服务器线上推荐的整个流程是否合理。那回到推荐系统中就是指，召回层要快速准确，模型排序部分要精确。这些具体的实现都影响着最终的推荐效果。

对于召回层来说，我们已经学过单策略召回、多路召回和基于Embedding的召回。对于排序来说，我们会主要利用Embedding相似度来排序，后续我们还会学习基于多种推荐模型的排序。

**最后是菜品上桌的过程** ，也就是把推荐的结果呈现给用户的过程。这节课，我会带你一起实现这个过程。提前“剧透”一下，在Sparrow Recsys中，我们会先利用JavaScript异步请求推荐服务API获取推荐结果，再利用JavaScript+HTML把结果展现给用户”。因为，这一部分内容不是推荐系统的重点，所以我们这里只要做到界面清爽、逻辑清晰就可以了。

相信到这里，各位“大厨”已经准备好了所要用到的技能，下面就让我们一起来实现**Sparrow RecSys中的相似电影推荐功能吧！**

## 如何实现相似电影推荐功能？

在正式开始相似电影推荐功能之前，我们先来看看我总结的Sparrow Recsys相似电影推荐功能的详细技术架构图。细心的你可能已经发现了，这个架构图就是Sparrow Recsys架构的精简版。因为我们还没有学习深度学习推荐模型和模型评估的相关知识，所以把重点聚焦在已经学过的知识上就可以了。

<img src="https://static001.geekbang.org/resource/image/f4/4f/f408eeeeb04bccd127a6726b8bf91d4f.jpg" alt="" title="图1 Sparrow Recsys 相似电影推荐功能的技术架构图">

接下来，我就结合这个技术架构图，带你一步步地实现其中的每一个模块。并且，我还会给你讲解一些项目中没有实现的其他业界主流方法，如果你还学有余力，希望你能抓住这个机会，来扩展一下自己的知识面。

### 1.  数据和模型部分

数据和模型部分的实现，其实和我们[第8讲](https://time.geekbang.org/column/article/296932)讲的Embedding的实战思路是一样的，我们可以选用Item2vec、Deep Walk等不同的Embedding方法，来生成物品Embedding向量。考虑到大数据条件下，数据处理与训练的一致性，在Sparrow Recsys中，我们会采用Spark进行数据处理，同时选择Spark MLlib进行Embedding的训练。这部分内容的代码，你可以参考项目中的`_com.wzhe.sparrowrecsys.offline.spark.embedding.__Embedding_`对象，它定义了所有项目中用到的Embedding方法。

对于一些比较复杂的Embedding方案，比如特征种类很多，网络结构也更多样化的Embedding模型，业界也多采用Spark进行原始数据处理，生成训练样本后交由TensorFlow、PyTorch训练的方案。

但是不论训练平台是怎样的，Embedding方法的产出都是一致的，就是物品ID对应的Embedding向量。那为了方便线上服务使用，我们还需要在生成Embedding后，把它们存入某个高可用的数据库。Sparrow Recsys选择了最主流的内存数据库Redis作为实现方案，这一部分的具体实现，你可以参照`com.wzhe.sparrowrecsys.offline.spark.embedding.Embedding`对象中trainItem2vec函数的Redis存储操作。当然，业界也会使用Cassandra+缓存，RocksDB等不同的存储方案来实现Embedding向量的高效读取，但我们现阶段只要学会Redis存储和读取操作就够用了。

到这里，Redis成为了连接线下和线上的关键节点，那我们的线上服务部分又是怎么利用Redis中的Embedding数据进行相似电影推荐的呢？

### 2. 线上服务部分

线上服务部分是直接接收并处理用户推荐请求的部分，从架构图的最左边到最右边，我们可以看到三个主要步骤：候选物品库的建立、召回层的实现、排序层的实现。我们逐个来讲一讲。

首先是候选物品库的建立。Sparrow Recsys中候选物品库的建立采用了非常简单的方式，就是直接把MovieLens数据集中的物品数据载入到内存中。但对于业界比较复杂的推荐业务来说，候选集的选取往往是有很多条件的， 比如物品可不可用，有没有过期，有没有其他过滤条件等等，所以，工业级推荐系统往往会通过比较复杂的SQL查询，或者API查询来获取候选集。

第二步是召回层的实现。我们在[第11讲](https://time.geekbang.org/column/article/299494)曾经详细学习了召回层的技术，这里终于可以学以致用了。因为物品的Embedding向量已经在离线生成，所以我们可以自然而然的使用Embedding召回的方法来完成召回层的实现。同时，Sparrow Recsys也实现了基于物品metadata（元信息）的多路召回方法，具体的实现你可以参照`com.wzhe.sparrowrecsys.online.recprocess.SimilarMovieProcess`类中的multipleRetrievalCandidates函数和retrievalCandidatesByEmbedding函数。

第三步是排序层的实现。根据Embedding相似度来进行“相似物品推荐”，是深度学习推荐系统最主流的解决方案，所以在Sparrow Recsys中，我们当然也是先根据召回层过滤出候选集，再从Redis中取出相应的Embedding向量，然后计算目标物品和候选物品之间的相似度，最后进行排序就可以了。

这里“相似度”的定义是多样的，可以是余弦相似度，也可以是内积相似度，还可以根据你训练Embedding时定义的不同相似度指标来确定。因为在Word2vec中，相似度的定义是内积相似度，所以,这里我们也采用内积作为相似度的计算方法。同样，具体的实现，你可以参照com.wzhe.sparrowrecsys.online.recprocess.SimilarMovieProcess类中的ranker函数。

经历了这三个主要的线上服务步骤，Sparrow Recsys就可以向用户返回推荐列表了。所以接下来，我们要解决的问题就是，怎么把这些结果通过前端页面展示给用户。

### 3. 前端部分

Sparrow Recsys的前端部分采用了最简单的HTML+AJAX请求的方式。AJAX的全称是Asynchronous JavaScript and XML，异步JavaScript和XML请求。它指的是不刷新整体页面，用JavaScript异步请求服务器端，更新页面中部分元素的技术。当前流行的JavaScript前端框架React、Vue等等也大多是基于AJAX来进行数据交互的。

但前端毕竟不是我们课程的重点，你知道我在上面提到的基本原理就可以了。如果你已经在本地的6010端口运行起了Sparrow Recsys，那直接点击这个链接：[http://localhost:6010/movie.html?movieId=589](http://localhost:6010/movie.html?movieId=589) ， 就可以看到电影《终结者2》的详情页面和相似电影推荐结果了（如图2）。

<img src="https://static001.geekbang.org/resource/image/a3/yy/a36a1ba15f4c464c84797fc87caf85yy.jpg" alt="" title="图2 终结者2的相似电影推荐结果">

## 相似电影推荐的结果和初步分析

到这里，我相信你已经串联起来了Sparrow Recsys相似电影推荐的所有实现，看到了推荐结果。那么问题来了，推荐结果的好坏到底是如何判断的呢？关于这个问题，我们也会在后面的“模型评估篇”中进行系统性的学习。不过，这里我也想先跟你聊聊这个话题，让你对它有一个大体认识，这对你建立后续的模型评估体系是非常有帮助的。

首先提醒你的是，Sparrow Recsys开源项目中自带的MovieLens数据集是经过我采样后的缩小集，所以基于这个数据集训练出的模型的准确性和稳定性是比较低的。如果你有兴趣的话可以去[MovieLens官网](https://grouplens.org/datasets/movielens/)选择**MovieLens 20M Dataset**下载并重新训练，相信会得到更准确的推荐结果。

其次，针对相似物品推荐这个推荐场景，我们其实很难找到一个统一的衡量标准。比如，你能说出《功夫熊猫》这部电影是跟《玩具总动员》更相近，还是跟《飞屋环游记》更相近吗？好在，工程师们还是总结出了一些有效的评估方法。这里，我挑出了三个最常用的来给你讲讲。

**方法一：人肉测试（SpotCheck）。** 在一种Embedding结果新鲜出炉的时候，你作为创造它们的工程师，应该第一时间做一个抽样测试，看一看基于Embedding的相似推荐结果是不是符合你自己的常识。比如说，我在Embedding训练完之后，随便在Sparrow Recsys中翻了翻，看到了两个页面，一个是儿童电影《Free Willy》（《人鱼童话》）的相似电影推荐页面（图3左），另一个是著名动画电影《Toy Story》（《玩具总动员》）的相似电影推荐页面（图3右）。

<img src="https://static001.geekbang.org/resource/image/43/87/43be7ebfd05yye98f9c432d4bb113987.jpg" alt="" title="图3 随机测试">

直观上来看，《Free Willy》的推荐结果就非常不错，因为你可以看到相似电影中都是适合儿童看的，甚至这些电影都和动物相关。但是《玩具总动员》就不一样了，它的相似电影里不仅有动画片，还有《真实的谎言》（《True Lies》）、《阿甘正传》这类明显偏成人的电影。这明显不是一个非常好的推荐结果。

为什么会出现这样的结果呢？我们来做一个推测。事实上，《玩具总动员》本身是一部非常流行的电影，跟它近似的也都是类似《真实的谎言》、《阿甘正传》这类很热门的电影。这就说明了一个问题，热门电影其实很容易跟其他大部分电影产生相似性，因为它们会出现在大多数用户的评分序列中。

针对这个问题，其实仅利用基于用户行为序列的Embedding方法是很难解决的。这需要我们引入更多内容型特征进行有针对性的改进，比如电影类型、海报风格，或者在训练中有意减少热门电影的样本权重，增大冷门电影的样本权重等等。总的来说，遇到推荐结果不合理的情况，我们需要做更多的调查研究，发掘这些结果出现的真实原因，才能找到改进方向。

**方法二：指定Ground truth（可以理解为标准答案）。** 虽然我们说，相似影片的Ground truth因人而异。但如果只是为了进行初步评估，我们也可以指定一些比较权威的验证集。比如，对于相似影片来说，我们可以利用IMDB的more like this的结果去做验证我们的相似电影结果。当然要补充说明的是，要注意有些Ground truth数据集的可用范围，不能随意在商业用途中使用未经许可的数据集。

**方法三：利用商业指标进行评估。** 既然相似影片比较难以直接衡量，那我们不如换一个角度，来思考一下做相似影片这个功能的目的是什么。对于一个商业网站来说，无非是提高点击率，播放量等等。因此，我们完全可以跃过评估相似度这样一个过程，直接去评估它的终极商业指标。

举个例子，我们可以通过上线一个新的相似电影模型，让相似电影这个功能模块的点击率提高，假设提高了5%，那这就是一个成功的模型改进。至于相似电影到底有没有那么“相似”，我们反而不用那么纠结了。

## 小结

这节课，我们使用Embedding方法准备好了食材，使用Redis把食材下锅，做菜的步骤稍微复杂一点，分为建立候选集、实现召回层、实现排序层这3个步骤。最后我们用HTML+Ajax的方式把相似电影推荐这盘菜呈现出来。

既然有做菜的过程，当然也有品菜的阶段。针对相似物品推荐这一常见的功能，我们可以使用人肉测试、Ground truth和商业指标评估这三种方法对得到的结果进行评估。也希望你能够在实际的业务场景中活学活用，用评估结果指导模型的下一步改进。

我希望，通过这节课的总结和实战，能让你融会贯通的厘清我们学过的知识。所以我把你需要掌握的重要知识点，总结在了一张图里，你可以利用它复习巩固。

<img src="https://static001.geekbang.org/resource/image/dc/9f/dcbb6cf20283ee362235255841b00c9f.jpg" alt="">

好了，那到这里，我们线上服务篇的内容就全部结束了。通过这一篇的学习，我相信你已经清楚了推荐系统的全部技术架构，以及深度学习核心技术Embedding的运用方法。

但盛宴还未开始，下一篇我们将进入深度推荐模型的学习和实践。我曾经说过，深度推荐模型是深度学习推荐系统这个王冠上的明珠，正是它对推荐模型的革命，让深度学习的浪潮席卷推荐系统领域。希望你再接再厉，让我们一起把这颗明珠摘下吧！

## 课后思考

刚才我说到，《玩具总动员》的相似电影推荐结果并不好，我认为可能是因为热门电影的头部效应造成的。你认同这一观点吗？你觉得还有其他可能的原因吗？如果让你去做一些Embedding方法上的改进，你还有什么好的想法吗？

欢迎把你的成果和优化想法分享到留言区，也欢迎你能把这节课转发出去，让更多人从我们的实践中受益，我们下节课见！
