gitbook/推荐系统三十六式/docs/6803.md
2022-09-03 22:05:03 +08:00

187 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 30 | 推荐系统服务化、存储选型及API设计
在过往的文章中,我讲到了推荐系统方方面面的相关概念。那么说,对于认识一个推荐系统来说,还差最后一个问题需要解决,那就是:万事俱备,如何给用户提供一个真正的在线推荐服务呢?
## 服务化是最后一步
其实一个推荐系统的在线服务,和任何别的在线服务相比,也没有什么本质区别,只是仍然还有一些特殊性。
提供一个在线服务需要两个关键元素数据库和API。今天我就来专门说一说推荐系统中大家常常用到的数据库并会谈谈推荐系统的API应该如何设计。
## 存储
这里注意一下,今天这里讲到的存储,专指近线或者在线部分所用的数据库,并不包括离线分析时所涉及的业务数据库或者日志数据库。
近线和在线的概念我在前面已经讲到过。推荐系统在离线阶段会得到一些关键结果,这些关键结果需要存进数据库,供近线阶段做实时和准实时的更新,最终会在在线阶段直接使用。
首先来看一下,离线阶段会产生哪些数据。按照用途来分,归纳起来一共就有三类。
1. 特征。特征数据会是最多的,所谓用户画像,物品画像,这些都是特征数据,更新并不频繁。
2. 模型。尤其是机器学习模型,这类数据的特点是它们大都是键值对,更新比较频繁。
3. 结果。就是一些推荐方法在离线阶段批量计算出推荐结果后,供最后融合时召回使用。任何一个数据都可以直接做推荐结果,如协同过滤结果。
如果把整个推荐系统笼统地看成一个大模型的话,它依赖的特征是由各种特征工程得到的,这些线下的特征工程和样本数据共同得到模型数据,这些模型在线上使用时,需要让线上的特征和线下的特征一致,因此需要把线下挖掘的特征放到线上去。
特征数据有两种,一种是稀疏的,一种是稠密的,稀疏的特征常见就是文本类特征,用户标签之类的,稠密的特征则是各种隐因子模型的产出参数。
特征数据又常常要以两种形态存在:一种是正排,一种是倒排。
正排就是以用户ID或者物品ID作为主键查询倒排则是以特征作为主键查询。
两种形态的用途在哪些地方呢在需要拼凑出样本的特征向量时如线下从日志中得到曝光和点击样本后还需要把对应的用户ID和物品ID展开成各自的特征向量再送入学习算法中得到最终模型这个时候就需要正排了。
另一种是在需要召回候选集时,如已知用户的个人标签,要用个人标签召回新闻,那么久就需要提前准备好标签对新闻的倒排索引。
这两种形态的特征数据需要用不同的数据库存储。正排需要用列式数据库存储倒排索引需要用KV数据库存储。前者最典型的就是HBase和Cassandra后者就是Redis或Memcached。稍后再介绍这几个数据库。
另外对于稠密特征向量例如各种隐因子向量Embedding向量可以考虑文件存储采用内存映射的方式会更加高效地读取和使用。
模型数据也是一类重要的数据,模型数据又分为机器学习模型和非机器学习模型。
机器学习模型与预测函数紧密相关。模型训练阶段,如果是超大规模的参数数量,业界一般采用分布式参数服务器,对于达到超大规模参数的场景在中小公司不常见,可以不用牛刀。
而是采用更加灵活的PMML文件作为模型的存储方式PMML是一种模型文件协议其中定义模型的参数和预测函数稍后也会介绍。
非机器学习模型,则不太好定义,有一个非常典型的是相似度矩阵,物品相似度,用户相似度,在离线阶段通过用户行为协同矩阵计算得到的。相似度矩阵之所算作模型,因为,它是用来对用户或者物品历史评分加权的,这些历史评分就是特征,所以相似度应该看做模型数据。
最后是预先计算出来的推荐结果或者叫候选集这类数据通常是ID类召回方式是用户ID和策略算法名称。这种列表类的数据一般也是采用高效的KV数据库存储如Redis。
另外还要介绍一个特殊的数据存储工具ElasticSearch。这原本是一个构建在开源搜索引擎Lucene基础上的分布式搜索引擎也常用于日志存储和分析但由于它良好的接口设计扩展性和尚可的性能也常常被采用来做推荐系统的简单第一版直接承担了存储和计算的任务。
下面我逐一介绍刚才提到的这些存储工具。
### 1.列式数据库
所谓列式数据库,是和行式数据库相对应的,这里不讨论数据库的原理,但是可以有一个简单的比喻来理解这两种数据库。
你把数据都想象成为矩阵行是一条一条的记录例如一个物品是一行列是记录的各个字段例如ID是一列名称是一列类似等等。
当我们在说行和列的时候,其实是在大脑中有一个抽象的想象,把数据想象成了二维矩阵,但是实际上,数据在计算机中,管你是行式还是列式,都要以一个一维序列的方式存在内存里或者磁盘上。
那么有意思的就来了,是按照列的方式把数据变成一维呢,还是按照行的方式把数据变成一维呢,这就是列式数据库和行式数据库的区别。
当然实际上数据库比这复杂多了,这只是一个简单形象的说明,有助于你去理解数据的存储方式。
列式数据库有个列族的概念,可以对应于关系型数据库中的表,还有一个键空间的概念,对应于关系型数据库中的数据库。
众所周知列式数据库适合批量写入和批量查询因此常常在推荐系统中有广泛应用。列式数据库当推Cassandra和HBase两者都受Google的BigTable影响但区别是Cassandra是一个去中心化的分布式数据库而HBase则是一个有Master节点的分布式存储。
Cassandra在数据库的CAP理论中可以平滑权衡而HBase则是强一致性并且Cassandra读写性能优于HBase因此Cassandra更适合推荐系统毕竟推荐系统不是业务逻辑导向的对强一致性要求不那么强烈这和我在一开始建议“你要建立起不确定思维”是一脉相承的。
Cassandra的数据模型组织形式如下图所示
![](https://static001.geekbang.org/resource/image/b9/3c/b9b12de5134a6a3c88ba8a97f6a3333c.png)
从这个图可以看出来,可以通过行主键及列名就可以访问到数据矩阵的单元格值。
前面也说过用户和物品的画像数据适合存储在Cassandra中。也适合存储模型数据如相似度矩阵还可以存储离线计算的推荐结果。
### 2.键值数据库
除了列式数据库外还有一种存储模式就是键值对内存数据库这当然首推Redis。Redis你可以简单理解成是一个网络版的HashMap但是它存储的值类型比较丰富有字符串、列表、有序列表、集合、二进制位。
并且Redis的数据放在了内存中所以都是闪电般的速度来读取。
在推荐系统的以下场景中常常见到Redis的身影
1. 消息队列List类型的存储可以满足这一需求
2. 优先队列比如兴趣排序后的信息流或者相关物品对此sorted set类型的存储可以满足这一需求
3. 模型参数,这是典型的键值对来满足。
另外Redis被人诟病的就是不太高可用对此已经有一些集群方案有官方的和非官方的可以试着加强下Redis的高可用。
### 3.非数据库
除了数据库外在推荐系统中还会用到一些非主流但常用的存储方式。第一个就是虚拟内存映射称为MMAP这可以看成是一个简陋版的数据库其原理就是把磁盘上的文件映射到内存中以解决数据太大不能读入内存但又想随机读取的矛盾需求。
哪些地方可以用到呢?比如你训练的词嵌入向量,或者隐因子模型,当特别大时,可以二进制存在文件中,然后采用虚拟内存映射方式读取。
另外一个就是PMML文件专门用于保存数据挖掘和部分机器学习模型参数及决策函数的。当模型参数还不足以称之为海量时PMML是一个很好的部署方法可以让线上服务在做预测时并不依赖离线时的编程语言以PMML协议保存离线训练结果就好。
## API
除了存储推荐系统作为一个服务应该以良好的接口和上有服务之间交互因此要设计良好的API。
API有两大类一类数据录入另一类是推荐服务。数据录入API可以用于数据采集的埋点或者其他数据录入。
![](https://static001.geekbang.org/resource/image/6e/15/6e88ceb14c4e28e8e113393f147de615.png)
推荐服务的API按照推荐场景来设计则是一种比较常见的方式下面分别简单说一下API的样子。
### 1.猜你喜欢
**接口:**
/Recommend
**输入:**
\* UserID 个性化推荐的前提
 \* PageID 推荐的页面ID关系到一些业务策略
 \* FromPage 从什么页面来
 \* PositionID 页面中的推荐位ID
 \* Size 请求的推荐数量
 \* Offset 偏移量,这是用于翻页的
**输出:**
\* Items  推荐列表通常是数组形式每一个物品除了有ID还有展示所需的各类元素
 \* Recommend\_id 唯一ID标识每一次调用也叫做曝光ID标识每一次曝光用于推荐后追踪推荐效果的很重要
 \* Size 本次推荐数量
 \* Page —— 用于翻页的
### 2.相关推荐
**接口:**
/Relative
**输入:**
\* UserID 个性化推荐的前提
 \* PageID 推荐的页面ID关系到一些业务策略
 \* FromPage 从什么页面来
 \* PositionID 页面中的推荐位ID
 \* ItemID 需要知道正在浏览哪个物品导致推荐相关物品
 \* Size 请求的推荐数量
 \* Offset 偏移量,这是用于翻页的
**输出:**
\* Items  推荐列表通常是数组形式每一个物品除了有ID还有展示所需的各类元素
 \* Recommend\_ID 唯一ID标识每一次调用也叫做曝光ID标识每一次曝光用于推荐后追踪推荐效果的很重要
 \* Size 本次推荐数量
 \* Page —— 用于翻页的
### 3.热门排行榜
**接口:**
/Relative
**输入:**
\* UserID 个性化推荐的前提
 \* PageID 推荐的页面ID关系到一些业务策略
 \* FromPage 从什么页面来
 \* PositionID 页面中的推荐位ID
 \* Size 请求的推荐数量
 \* Offset 偏移量,这是用于翻页的
**输出:**
\* Items  推荐列表通常是数组形式每一个物品除了有ID还有展示所需的各类元素
 \* Recommend\_id 唯一ID标识每一次调用也叫做曝光ID标识每一次曝光用于推荐后追踪推荐效果的很重要
 \* Size 本次推荐的数量
 \* Page —— 用于翻页的
相信你看到了吧,实际上这些接口都很类似。
## 总结
今天我主要讲解了推荐系统上线的两大问题一个是线上数据存储另一个是推荐系统的API有哪些。
虽然实际情况肯定不是只有这点问题,但是这些也足以构建出一个简单的推荐系统线上版了。
你还记得在前几篇专栏中我提到统一考虑搜索和推荐的问题吗那么说如果要把推荐和搜索统一考虑的话API该如何设计呢欢迎留言一起讨论。感谢你的收听我们下期再见。