一、清点技能库
“猜你喜欢”功能几乎会用到所有的推荐系统模块(如离线的特征工程、模型训练以及线上的模型服务和推荐逻辑的实现)。
1.1 模型特征工程
为了训练推荐模型,需要准备好模型所需的样本和特征。在进行模型线上推断的时候,推荐服务器也需要线上实时拼装好包含了用户特征、物品特征、场景特征的特征向量,发送给推荐模型进行实时推断。
之前通过 Spark 处理好了 TensorFlow 训练所需的训练样本,并把 Spark 处理好的特征插入了 Redis 特征数据库,供线上推断使用。
1.2 模型离线训练
为了在线上做出尽量准确或者说推荐效果尽量好的排序,需要在离线训练好排序所用的推荐模型。虽然之前学的深度推荐模型的结构各不相同,但它们的输入、输出都是一致的,输入是由不同特征组成的特征向量,输出是一个分数,这个分数的高低代表了这组特征对应的用户对物品的喜好程度。
Embedding MLP、Wide&Deep、NeuralCF、双塔模型、DeepFM 等几种不同的深度推荐模型,它们中的任何一个都可以支持“猜你喜欢”的排序功能。
其他模型的上线方法与 NeuralCF 几乎一致。唯一的区别是,对于不同的模型来说,它们在模型服务的部分需要载入不同的模型文件,并且在线上预估的部分也要传入模型相应的输入特征。
1.3 模型服务
推荐系统中连接线上环境和线下环境的纽带——模型服务和特征数据库。
在离线训练好模型后,为了让模型在线上发挥功能(做出实时的推荐排序),我们通过【模型服务】模块将推荐模型部署上线,和之前一样使用tensorflow serving。
1.4 推荐服务器内部逻辑实现
模型服务虽然可以做到“猜你喜欢”中电影的排序,但要进行排序,仍然需要做大量的准备工作,比如候选集的获取,召回层的构建,特征的获取和拼装等等。这些推荐逻辑都是在推荐服务器内部实现的。推荐服务器就像推荐系统的线上的心脏,是所有线上模块的核心。
这次关注之前没提到的特征的拼装,以及从推荐服务器内部请求模型服务 API 的方法。
二、"猜你喜欢"推荐功能的技术架构
图1 “猜你喜欢”功能的技术架构图
2.1 数据和模型部分
左上角是我们使用的数据集 MovieLens,它经过 Spark 的处理之后,会生成两部分数据,分别从两个出口出去:
(1)特征部分会存入 Redis 供线上推断时推荐服务器使用,
(2)样本部分则提供给 TensorFlow 训练模型。
2.2 模型服务
TensorFlow 完成模型训练之后,会导出模型文件,然后模型文件会载入到 TensorFlow Serving 中,接着TensorFlow Serving 会对外开放模型服务 API,供推荐服务器调用。
2.3 推荐服务器部分
在这部分里,基于 MovieLens 数据集生成的候选电影集合会依次经过候选物品获取、召回层、排序层这三步,最终生成“猜你喜欢”的电影推荐列表,然后返回给前端,前端利用 HTML 和 JavaScript 把它们展示给用户。
整个过程中,除了排序层和 TensorFlow Serving 的实现,其他部分在之前的实战中实现过。所以这次重点讲解推荐服务器排序层和 TensorFlow Serving 的实现(第三大点)。
三、排序层 +TensorFlow Serving 的实现
在推荐服务器中,经过召回层后,得到的几百量级的候选物品集,丢到排序层。在业界的实际应用中,往往交由评估效果最好的深度推荐模型来处理。
整个的排序过程可以分为三个部分:
准备线上推断所需的特征,拼接成 JSON 格式的特征样本;
把所有候选物品的特征样本批量发送给 TensorFlow Serving API;
根据 TensorFlow Serving API 返回的推断得分进行排序,生成推荐列表。
3.1 特征样本的拼接
首先,第一步的实现重点在于特征样本的拼接。
因为实践例子里,选用了 NeuralCF 作为排序模型,而 NerualCF 所需的特征只有 userId 和 itemId ,所以特征是比较好准备的。
如何拼接特征形成模型推断所需的样本:
代码参考:com.wzhe.sparrowrecsys.online.recprocess.RecForYouProcess
/** * call TenserFlow serving to get the NeuralCF model inference result * @param user input user * @param candidates candidate movies * @param candidateScoreMap save prediction score into the score map */ public static void callNeuralCFTFServing(User user, List<Movie> candidates, HashMap<Movie, Double> candidateScoreMap){ if (null == user || null == candidates || candidates.size() == 0){ return; } //保存所有样本的JSON数组 JSONArray instances = new JSONArray(); for (Movie m : candidates){ JSONObject instance = new JSONObject(); //为每个样本添加特征,userId和movieId instance.put("userId", user.getUserId()); instance.put("movieId", m.getMovieId()); instances.put(instance); } JSONObject instancesRoot = new JSONObject(); instancesRoot.put("instances", instances); //请求TensorFlow Serving API String predictionScores = asyncSinglePostRequest("http://localhost:8501/v1/models/recmodel:predict", instancesRoot.toString()); //获取返回预估值 JSONObject predictionsObject = new JSONObject(predictionScores); JSONArray scores = predictionsObject.getJSONArray("predictions"); //将预估值加入返回的map for (int i = 0 ; i < candidates.size(); i++){ candidateScoreMap.put(candidates.get(i), scores.getJSONArray(i).getDouble(0)); } }
在代码中:
先把 userId 和 movieId 加入了 JSON 格式的样本中,然后再把样本加入到 Json 数组中。
接下来,我们又以 http post 请求的形式把这些 JSON 样本发送给 TensorFlow Serving 的 API,进行批量预估。
在收到预估得分后,保存在候选集 map 中,供排序层进行排序。
3.2 建立起 TensorFlow Serving API
第二步的重点在于如何建立起 TensorFlow Serving API
通过之前的模型服务部分,搭建起一个测试模型的 API 了。想要搭建起我们自己的 TensorFlow Serving API,只需要把之前载入的测试模型文件换成我们自己的模型文件就可以了。这里以 NerualCF 模型为例,看一看模型文件是如何被导出和导入的。
(1)模型的导出
首先是模型的导出。在 NeuralCF 的 TensorFlow 实现中,我们已经把训练好的模型保存在了 model 这个结构中,接下来需要调用 tf.keras.models.save_model 这一函数来把模型序列化。
下面的代码中,这一函数需要传入的参数有要保存的 model 结构,保存的路径,还有是否覆盖路径 overwrite 等等。
注意保存路径。可以看到在保存路径中加上了一个模型版本号 002,这对于 TensorFlow Serving 是很重要的,因为 TensorFlow Serving 总是会找到版本号最大的模型文件进行载入,这样做就保证了我们每次载入的都是最新训练的模型。详细代码参考 NeuralCF.py。
tf.keras.models.save_model( model, "file:///Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/modeldata/neuralcf/002", overwrite=True, include_optimizer=True, save_format=None, signatures=None, options=None )
(2)模型的导入
其次是模型的导入,导入命令就是 TensorFlow Serving API 的启动命令,我们直接看下面命令中的参数。
docker run -t --rm -p 8501:8501 -v "/Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/modeldata/neuralcf:/models/recmodel" -e MODEL_NAME=recmodel tensorflow/serving &
这里面最重要的参数,就是指定载入模型的路径和预估 url,而载入路径就是我们刚才保存模型的路径:/Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/modeldata/neuralcf。但是在这里,我们没有加模型的版本号。这是为什么呢?因为版本号是供 TensorFlow Serving 查找最新模型用的,TensorFlow Serving 在模型路径上会自动找到版本号最大的模型载入,因此不可以在载入路径上再加上版本号。
除此之外,冒号后的部分“/models/recmodel”指的是 TensorFlow Serving API 在这个模型上的具体 url,刚才我们是通过请求 http://localhost:8501/v1/models/recmodel:predict 获取模型预估值的,请求连接中的 models/recmodel就是在这里设定的。
在正确执行上面的命令后,就可以在 Docker 上运行起 TensorFlow Serving 的 API 了。
3.3 获取返回得分和排序
最后,我们来看第三步的实现重点:获取返回得分和排序。
先来看一下 TensorFlow Serving API 的返回得分格式。它的返回值也是一个 JSON 数组的格式,数组中每一项对应着之前发送过去的候选电影样本,所以我们只要把返回的预估值赋给相应的样本,然后按照预估值排序就可以了。
参考 com.wzhe.sparrowrecsys.online.recprocess.RecForYouProcess 中全部排序层的代码。
{ "predictions": [[0.824034274], [0.86393261], [0.921346784], [0.957705915], [0.875154734], [0.905113697], [0.831545711], [0.926080644], [0.898158073]... ] }
如果已经正确建立起了 Redis 和 TensorFlow Serving API 服务,并且已经分别导入了特征数据和模型文件,我们就可以启动 Sparrow Recsys Server,查看“猜你喜欢”的结果了。用户 ID 为 6 的用户在 NerualCF 模型下的推荐结果(http://localhost:6010/user.html?id=6&model=nerualcf),注意通过在连接中设置 model 变量为 nerualcf,来决定产生结果的模型。
四、思维导图
4.1 基础模型
4.2 进阶模型
4.3 前沿模型
五、作业
推荐系统的特征预处理是一项很重要的工作,比如一些连续特征的归一化,分桶等等。那么这些预处理的过程,我们应该放在线上部分的哪里完成呢?是在 Tensorflow Serving 的部分,还是在推荐服务器内部,还是在离线部分完成?你有什么好的想法吗?
【答】推荐服务器内部专门开发特征加工模块,进行一些人工的处理。比如点击率特征,实际上“点击”会包含多种点击行为,各种行为如何融合,需要灵活配置。既不能放在离线存(更新不便),也不能放在tf serving里(逻辑多了太慢)
1、tf serving只负责简单的模型运算;
2、离线redis等负责通用特征数据的存储;
3、推荐系统服务器进行数据加工
线上服务篇的task2:
:
六、课后答疑
(1)Sparrow项目代码中新增了pyspark推荐系统的工程实现部分,对于大数据量的特征工程处理,选择用scala还是python版的spark实现有没有什么建议?
【答】现实工作中建议最好还是用scala来维护,毕竟是spark原生支持的语言,真正的大数据工程师一般会使用scala。但是也不反对python来维护,跟其他python项目在一起维护会方便些。
spark+tf 正常算法工程师主要会这两个。实时计算和推荐引擎应该有其他的同事负责。如果这几个部分还没拆分开的话,可能你们还没有达到必须要用推荐的时候,可以将实时计算给忽略。都去做的话,就什么也不会精通,也只有在小公司里了。个人(ID:范思思)最近一段时间面试的结论,仅供参考。你可以什么都学过一点,但一定要有一个十分精通的技术,作为下一步发展的目标
(2)特征处理这部分,应该是离线计算好得到每个特征的map数据,在推荐服务器内部加载这些map数据,直接将原始特征映射成深度学习需要的向量,将得到的向量送入Tensorflow Serving得到推荐结果
如果在Tensorflow Serving做特征预处理的工作,会导致推荐服务的响应时间边长
【答】最好是可以在推荐服务器内部把特征都准备好,处理好。tf serving只做inference,不承担太多特征预处理压力。
(3)现在边缘计算或端智能,多大程度解决了用户特征更新的问题,端智能的应用前景如何?
【答】王喆大佬说看好边缘计算的发展,一直是近两年看好的方向。至于多大程度解决了特征更新的问题,觉得他们会一直共存。边缘计算永远也替代不了服务器端的特征更新设施。