This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
<audio id="audio" title="27 | 巧妇难为无米之炊:数据采集关键要素" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/66/c0/66d5d634d040d6cc10faac6d6cb1e5c0.mp3"></audio>
推荐系统离不开数据,数据就是推荐系统的粮食,要有数据就得收集数据。在自己产品中收集数据,主要还是来自日志。
## 日志和数据
数据驱动这个概念也是最近几年才开始流行起来的,在古典互联网时代,设计和开发产品完全侧重于功能易用和设计精巧上,并且整体驱动力受限于产品负责人的个人眼光,这属于是一种感性的把握,也因此对积累数据这件事就不是很重视。
在我经手的产品中,就有产品上线很久,需要搭建推荐系统时,却发现并没有收集相应的数据,或者收集得非常杂乱无章。
关于数据采集,按照用途分类又有三种:
1. 报表统计;
1. 数据分析;
1. 机器学习。
当然,这三种的用途并不冲突,而且反而有层层递进的关系。最基本的数据收集,是为了统计一些核心的产品指标,例如次日留存,七日留存等,一方面是为了监控产品的健康状况,另一方面是为了对外秀肌肉,这一类数据使用非常浅层,对数据的采集要求也不高。
第二种就是比较常见的数据采集需求所在了。在前面第一种用途基础上,不但需要知道产品是否健康,还需要知道为什么健康、为什么不健康,做对了什么事、做错了什么事,要从数据中去找到根本的原因。
这种数据采集的用途,驱动了很多多维分析软件应运而生,也驱动了多家大数据创业公司应运而生。
数据分析工作,最后要产出的是比较简明清晰直观的结论,这是数据分析师综合自己的智慧加工出来的,是有人产出的。
它主要用于指导产品设计、指导商业推广、指导开发方式。走到这一步的数据采集,已经是实打实的数据驱动产品了。
第三种,就是收集数据为了机器学习应用,或者更广泛地说人工智能应用。那么机器学习应用,主要在消化数据的角色是算法、是计算机,而不是人。
这个观点是我在专栏写作之初,讲解用户画像相关内容时就提到的,再强调一遍就是,所有的数据,不论原始数据还是加工后的数据都是给机器“看”的,而不是给人“看”的。
所以在数据采集上,可以说多多益善,样本是多多益善,数据采集的维度,也就是字段数多多益善,但另一方面,数据是否适合分析,数据是否易于可视化地操作并不是核心的内容。
当然,实际上在任何一款需要有推荐系统的产品中,数据采集的需求很可能要同时满足上述三种要求。
本文为了讨论方便,不会重点讨论多维数据分析的用途,而是专门看看为了满足推荐系统,你需要怎么收集日志、采集数据。
因为推荐系统就是一个典型的人工智能应用,数据是要喂给机器“吃”的。
下面我就开始给你详细剖析一下为推荐系统收集日志这件事。
## 数据采集
给推荐系统收集日志这件事,依次要讨论的是:日志的数据模型,收集哪些日志,用什么工具收集,收集的日志怎么存储。
### 1.数据模型
数据模型是什么?所谓数据模型,其实就是把数据归类。产品越复杂,业务线越多,产生的日志就越复杂。
如果看山是山,一个数据来源一个数据来源地去对待的话,那将效率非常低下,因此需要首先把要收集的日志数据归入几个模型。不同的数据应用,数据模型略有不同。
就推荐系统而言,推荐系统要做的事情就是预测那些最终会建立的人和物之间的连接,依赖的是已有的连接,以及人和物的属性,而且,其中最主要的是已有的连接,人和物的属性只不过是更加详细描述这些连接而已。
数据模型帮助梳理日志、归类存储,以方便在使用时获取。你可以回顾一下在前面讲过的推荐算法,这些推荐算法形形色色,但是他们所需要的数据可以概括为两个字:矩阵。
再细分一下,这些矩阵就分成了四种。
<img src="https://static001.geekbang.org/resource/image/38/e5/38a6108b28fbfcc195b5e5db3209a3e5.png" alt="" />
基于这个分析,可以给要收集的数据归纳成下面几种。
<img src="https://static001.geekbang.org/resource/image/c4/54/c4931dab5ca5373e37668a38cbb44e54.png" alt="" />
有了数据模型,就可以很好地去梳理现有的日志,看看每一种日志属于哪一种。并且,在一个新产品上线之初,该如何为将来的推荐系统记录日志也比较清楚了。这个数据模型当然不能概括全部数据,但是用来构建一个推荐系统就绰绰有余了。
接下来就是去收集数据了。收集数据,就是把散布在各个地方的数据聚拢,也包括那些还根本没有记录的数据的地方要开始记录。
### 2.数据在哪?
按照前面的数据建模,我们一起来看一下要收集的数据会怎么产生。主要来自两种,一种是业务运转必须要存储的记录,例如用户注册资料,如果不在数据库中记录,产品就无法正常运转。
另一种就是在用户使用产品时顺便记录下来的这叫做埋点。第一种数据源来自业务数据库通常都是结构化存储MySQL。第二种数据需要埋点埋点又有几种不同方法。
第一种SDK埋点。这个是最经典古老的埋点方法就是在开发自己的App或者网站时嵌入第三方统计的SDKApp如友盟等网站如Google Analytics等。
SDK在要收集的数据发生点被调用将数据发送到第三方统计第三方统计得到数据后再进一步分析展示。
这种数据收集方式对推荐系统的意义不大因为得不到原始的数据而只是得到统计结果我们可以将其做一些改动或者自己仿造做一些开发内部数据采集SDK从而能够收集到鲜活的数据。
第二种可视化埋点。可视化埋点在SDK埋点基础上做了进一步工作埋点工作通过可视化配置的方式完成一般是在App端或者网站端嵌入可视化埋点套件的SDK然后再管理端接收前端传回的应用控件树通过点选和配置指令前端收集那些事件数据。业界有开源方案实现可参考如Mixpanel。
第三种,无埋点。所谓无埋点不是不埋点收集数据,而是尽可能多自动收集所有数据,但是使用方按照自己的需求去使用部分数据。
SDK埋点就是复杂度高一旦埋点有错需要更新客户端版本可视化埋点的不足就是收集数据不能收集到非界面数据例如收集了点击事件也仅仅能收集一个点击事件却不能把更详细的数据一并返回。
上面是按照技术手段分,如果按照收集数据的位置分,又分为前端埋点和后端埋点。
这两个区别是这样的,举个例子,要收集用户的点击事件,前端埋点就是在用户点击时,除了响应他的点击请求,还同时发送一条数据给数据采集方。
后端埋点就不一样了,由于用户的点击需要和后端交互,后端收到这个点击请求时就会在服务端打印一条业务日志,所以数据采集就采集这条业务日志即可。
埋点是一项非常复杂繁琐的事情,需要前端工程师或者客户端工程师细心处理,不在本文讨论范围内。但是幸好,国内如神策数据等公司,将这些工作已经做得很傻瓜化了,大大减轻了埋点数据采集的困扰。
对于推荐系统来说,所需要的数据基本上都可以从后端收集,采集成本较低,但是有两个要求:要求所有的事件都需要和后端交互,要求所有业务响应都要有日志记录。这样才能做到在后端收集日志。
后端收集业务日志好处很多,比如下面几种。
1. 实时性。由于业务响应是实时的,所以日志打印也是实时的,因此可以做到实时收集。
1. 可及时更新。由于日志记录都发生在后端,所以需要更新时可以及时更新,而不用重新发布客户端版本。
1. 开发简单。不需要单独维护一套SDK。
归纳一下Event类别的数据从后端各个业务服务器产生的日志来Item和User类型数据从业务数据库来还有一类特殊的数据就是Relation类别也从业务数据库来。
### 3.元素有哪些?
后端收集事件数据需要业务服务器打印日志。需要打印哪些信息才算是一条完整的时间数据呢?大致需要包含下面的几类元素。
1. 用户ID唯一标识用户身份。
1. 物品ID唯一标识物品。这个粒度在某些场景中需要注意例如电商物品的ID就不是真正要去区别物和物之间的不同而是指同一类试题例如一本书《岛上书店》库存有很多本并不需要区别库存很多本之间的不同而是区别《岛上书店》和《白夜行》之间的不同。
1. 事件名称,每一个行为一个名字。
1. 事件发生时间,时间非常重要。
以上是基本的内容,下面再来说说加分项。
1. 事件发生时的设备信息和地理位置信息等等;
1. 从什么事件而来;
1. 从什么页面而来;
1. 事件发生时用户的相关属性;
1. 事件发生时物品的相关属性。
把日志记录想象成一个Live快照内容越丰富就越能还原当时的场景。
### 4.怎么收集?
一个典型的数据采集架构如下图所示。
<img src="https://static001.geekbang.org/resource/image/41/15/41b744f9da66824f39aa260d8fb23715.png" alt="" />
下面描述一下这个图。最左边就是数据源有两部分一个是来自非常稳定的网络服务器日志nginx或者Apache产生的日志。这类日志对推荐系统的作用是什么呢
因为有一类埋点在PC互联网时代有一种事件数据收集方式是放一个一像素的图片在某个要采集数据的位置。
这个图片被点击时,向服务端发送一个不做什么事情的请求,只是为了在服务端的网络服务器那里产生一条系统日志。 这类日志用logstash收集。
左边另外的数据源就是业务服务器,这类服务器会处理具体场景的具体业务,甚至推荐系统本身也是一个业务服务器。
这类服务器有各自不同的日志记录方式例如Java是Log4jPython是Logging等等还有RPC服务。这些业务服务器通常会分布在多台机器上产生的日志需要用Flume汇总。
Kafka是一个分布式消息队列按照Topic组织队列订阅消费模式可以横向水平扩展非常适合作为日志清洗计算层和日志收集之间的缓冲区。
所以一般日志收集后不论是Logstash还是Flume都会发送到Kafka中指定的Topic中。
在Kafka 后端一般是一个流计算框架上面有不同的计算任务去消费Kafka的数据Topic流计算框架实时地处理完采集到的数据会送往分布式的文件系统中永久存储一般是HDFS。
日志的时间属性非常重要。因为在HDFS中存储日志时为了后续抽取方便快速一般要把日志按照日期分区。当然在存储时按照前面介绍的数据模型分不同的库表存储也能够方便在后续构建推荐模型时准备数据。
5.质量检验
数据采集,日志收集还需要对采集到的数据质量做监控。数据质量通常需要数据中心的同学重点关注。推荐系统作为数据的使用方,虽然不用重点关注如何保证数据质量,但是需要能够发现数据质量问题,不然在错误的数据上无法训练出聪明的推荐模型的。
关注数据质量,大致需要关注下面几个内容。
1. 是否完整事件数据至少要有用户ID、物品ID、事件名称三元素才算完整才有意义。
1. 是否一致?一致是一个广泛的概念。数据就是事实,同一个事实的不同方面会表现成不同数据,这些数据需要互相佐证,逻辑自洽。
1. 是否正确该记录的数据一定是取自对应的数据源这个标准不能满足则应该属于Bug级别记录了错误的数据。
1. 是否及时?虽然一些客户端埋点数据,为了降低网络消耗,会积攒一定时间打包上传数据,但是数据的及时性直接关系到数据质量。由于推荐系统所需的数据通常会都来自后端埋点,所以及时性还可以保证。
## 总结
今天和你聊了数据采集的若干要点。数据是推荐系统做饭的米,没有数据就没有任何推荐策略的落地,因此采集数据是一个非常重要的工作。
采集数据需要首先梳理好自己的数据有哪些,本文不是帮你梳理你的自己的产品中有哪些数据,而是告诉你看推荐系统需要哪些数据。
另外有一点,数据采集的需求方有很多,推荐系统只是其中一个,通常数据分析对数据的需求,集中在多维数据分析,当然推荐系统也需要多维数据,只是推荐系统更关注事件。
我把这些数据全都看成矩阵,有了矩阵,无论是内容推荐还是协同过滤,矩阵分解,还是机器学习深度学习,就都有了输入。
我总结了推荐系统需要四种矩阵,对应四种数据,列表如下:
<img src="https://static001.geekbang.org/resource/image/99/ab/99681dd142984f29e11b5a51e8df0dab.png" alt="" />
为了构建推荐系统上面四类数据足够了其中除了Relation数据之外另外三种是必须的。所以你照着这个药方子去抓药就好了。
另外,我还介绍了日志收集系统的架构图,以及一些埋点技术的简要介绍,可以帮助理解埋点收集数据这件事。
最后,数据质量要过硬,好质量的数据胜似黄金,低质量的数据价值也就不高,收集到错误的数据除了带来存储和传输成本,还无法创造价值,所以检测数据质量也很重要。
今天就讲到这里,最后留一个思考问题,一个信息流产品,需要采集的数据具体有哪些?欢迎留言一起讨论。
感谢你的收听,我们下次再见。

View File

@@ -0,0 +1,203 @@
<audio id="audio" title="28 | 让你的推荐系统反应更快:实时推荐" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/34/78/346f933d3f42a1931ddd270b5ae77478.mp3"></audio>
更快,更高,更强,不只是奥林匹克运动所追求的,也是推荐系统从业者所追求的三个要素:捕捉兴趣要更快,指标要更高,系统要更健壮。
我今天就要说的就是这个“更快”。推荐系统是为了在用户和物品之间建立连接,手段是利用已有的用户物品之间的连接,然而任何事物都是有生命周期的,包括这里说的这个虚无的“连接”也是有的。
## 为什么要实时
一个连接从建立开始,其连接的强度就开始衰减,直到最后,可能用户不记得自己和那个物品曾经交汇过眼神。因此,推荐系统既然使用已有的连接去预测未来的连接,那么追求“更快”就成了理所当然的事情。
用户和物品之间产生的连接,不论轻如点击,还是重如购买,都有推荐的黄金时间。在这个黄金时间,捕捉到用户的兴趣并且给与响应,可能就更容易留住用户。
在业界,大家为了高大上,不会说“更快”的推荐系统,而是会说“实时”推荐系统。实际上,绝对的实时是不存在的,哪怕延迟级别在微秒的推荐,也是会有延迟的。但是为了顺应时代潮流,我还是会在后面的内容中说这是实时推荐,你就那么一听,知道就好。
关于到底什么是实时推荐,实际上有三个层次。
<li>
第一层,“给得及时”,也就是服务的实时响应。这个是最基本的要求,一旦一个推荐系统上线后,在互联网的场景下,没有让用户等个一天一夜的情况,基本上最慢的服务接口整个下来响应时间也超过秒级。达到第一层不能成为实时推荐,但是没达到就是不合格。
</li>
<li>
第二层,“用得及时”,就是特征的实时更新。例如用户刚刚购买了一个新的商品,这个行为事件,立即更新到用户历史行为中,参与到下一次协同过滤推荐结果的召回中。做到这个层次,已经有实时推荐的意思了,常见的效果就是在经过几轮交互之后,用户的首页推荐会有所变化。这一层次的操作影响范围只是当前用户。
</li>
<li>
第三层,“改得及时”,就是模型的实时更新。还是刚才这个例子,用户刚刚购买了一个新的商品,那需要实时地去更新这个商品和所有该用户购买的其他商品之间的相似度,因为这些商品对应的共同购买用户数增加了,商品相似度就是一种推荐模型,所以它的改变影响的是全局推荐。
</li>
## 实时推荐
好,下面就讲一下如何构建一个处在第三层次的实时推荐系统。
### 1.架构概览
按照前面的分析,一个处在第三层次的实时推荐,需要满足三个条件:
1. 数据实时进来
1. 数据实时计算
1. 结果实时更新
为此,下面给出一个基本的实时推荐框图。
<img src="https://static001.geekbang.org/resource/image/7a/ca/7a49fb338192bf1865670a42cb6c89ca.png" alt="" />
整体介绍一下这个图,前端服务负责和用户之间直接交互,不论是采集用户行为数据,还是给出推荐服务返回结果。
用户行为数据经过实时的消息队列发布,然后由一个流计算平台消费这些实时数据,一方面清洗后直接入库,另一方面就是参与到实时推荐中,并将实时计算的结果更新到推荐数据库,供推荐服务实时使用。
### 2.实时数据
实时流数据的接入在上一篇专栏中已经讲到过需要一个实时的消息队列开源解决方案Kafka已经是非常成熟的选项。
<img src="https://static001.geekbang.org/resource/image/1f/03/1f86402f1e313cbc34ca51f3c2e8e903.png" alt="" />
Kafka以生产者消费者的模式吞吐数据这些数据以主题的方式组织在一起每一个主题的数据会被分为多块消费者各自去消费互不影响Kafka也不会因为某个消费者消费了而删除数据。
每一个消费者各自保存状态信息所消费数据在Kafka某个主题某个分块下的偏移位置。也因此任意时刻、任意消费者只要自己愿意可以从Kafka任意位置开始消费数据一遍消费对应的偏移量顺序往前移动。示意图如下。
<img src="https://static001.geekbang.org/resource/image/a3/23/a30d673cd37de12ab88e1362f5c9bc23.png" alt="" />
一个生产者可以看做一个数据源,生产者决定数据源放进哪个主题中,甚至通过一些算法决定数据如何落进哪个分块里。示意图如下:
<img src="https://static001.geekbang.org/resource/image/b3/55/b330ae51299241bc0c4a33794eb1f555.png" alt="" />
因此Kafka的生产者和消费者在自己的项目中实现时都非常简单就是往某个主题写数据以及从某个主题读数据。
### 3.流计算
整个实时推荐建立在流计算平台上。常见的流计算平台有Twitter开源的Storm“Yahoo”开源的S4还有Spark中的Streaming。
不过随着Storm使用者越来越多社区越来越繁荣并且相比Streaming的MiniBatch模式Storm才是真正的流计算。因此在你构建自己的实时推荐时流计算平台不妨就选用Storm不过最新的流计算框架FLink表现强劲高吞吐低延迟如果你所在团队有人愿意尝试一下也很不错。
Storm是一个流计算框架它有以下几个元素。
1. Spout意思是喷嘴水龙头接入一个数据流然后以喷嘴的形式把数据喷洒出去。
1. Bolt意思是螺栓像是两段水管的连接处两端可以接入喷嘴也可以接入另一个螺栓数据流就进入了下一个处理环节。
1. Tuple意思是元组就是流在水管中的水。
1. Topology意思是拓扑结构螺栓和喷嘴以及之间的数据水管一起组成了一个有向无环图这就是一个拓扑结构。
注意Storm规定了这些基本的元素也是你在Storm平台上编程时需要实现的但不用关心水管在哪水管由Storm提供你只用实现自己需要的水龙头和水管连接的螺栓即可。
因此其编程模型也非常简单。举一个简单的例子看看如何用Storm实现流计算假如有一个字符串构成的数据流这个数据流恰好也是Kafka中的一个主题正在源源不断地在接入。
要用Storm实现一个流计算统计每一个字符的频率。你首先需要实现一个Spout也就是给数据流加装一个水龙头这个水龙头那一端就是一个Kafka的消费者从Kafka中不断取出字符串数据这头就喷出来然后再实现Bolt也就是螺栓。
当有字符串数据流进来时把他们拆成不同的字符并以字符1这样的方式变成新的数据流发射出去最后就是去把相同字符的数据流聚合起来相加就得到了字符的频率。
实际上如果你知道MapReduce过程的话你会发现虽然Storm重新取了名字仍然可以按照MapReduce来理解。
Storm的模型示意如下
<img src="https://static001.geekbang.org/resource/image/72/e1/72782647e4c2306bb16ce75f69cd1de1.png" alt="" />
Storm中要运行实时推荐系统的所有计算和统计任务比如有下面几种
1. 清洗数据;
1. 合并用户的历史行为;
1. 重新更新物品相似度;
1. 在线更新机器学习模型;
1. 更新推荐结果。
### 4.算法实时化
我在前面的文章里面,已经介绍过基于物品的协同过滤原理。下面我以基于物品的协同过滤算法为主线,来讲解一下如何实现实时推荐,其他算法你可以举一反三改造。
主要是两个计算,第一个是计算物品之间的相似度。
$$sim(i, j) = \frac{co_users(item_{i}, item_{j})}{\sqrt{count_users(item_{i})}\sqrt{count_users(item_{j})}}$$
计算了物品和物品之间的相似度之后,用另一个公式来计算推荐分数:
$$rec(u,i) = \frac{\sum_{j\in N_{i}}{sim(i,j)r_{uj}}}{\sum_{j\in N_{i}}{sim(i,j)}}$$
要做到前面说的第三层次实时推荐,首先就是要做到增量更新物品之间的相似度。相似度计算分成三部分:
1. 分子上的“物品对”,共同评分用户数;
1. 分母上左边是物品i的评价用户数
1. 分母上右边是物品j的评价用户数。
所以更新计算相似度就要更新三部分,实际上一种相似度增量更新策略是在收到一条用户评分事件数据时,然后取出这个用户的历史评分物品列表,因为所有的历史评分物品现在和这个新评分物品之间,就要增加一个共同评分了。
并且,这个新物品本身,也要给自己一个评分用户数。更新完三个后,就实时更新所有这些“物品对”的相似度了。
转换成Storm的编程模型你需要实现
1. Spout消费实时消息队列中的用户评分事件数据并发射成UserID , ItemID_i这样的Tuple
1. Bolt1接的是源头Spout输入了UserID和ItemID_i读出用户历史评分Item列表遍历这些ItemID_j逐一发射成((Item_i, Item_j), 1)和((Item_j, Item_i), 1)并将Item_i加进历史评分列表中
1. Bolt2接的是源头Spout输入了UserID和ItemID_i发射成(ItemID_i, 1)
1. Bolt3接Bolt1更新相似度所需的分子
1. Bolt4接Bolt2更新物品自己的评分用户数
把这个过程表示成公式就是:
$$sim(i, j) = \frac{co_users(item_{i}, item_{j}) + \Delta co_users}{\sqrt{count_users(item_{i}) + \Delta count_users(i)}\sqrt{count_users(item_j)) + \Delta count_users(j)}}$$
另外还有实时更新推荐结果也是作为Storm的一个Bolt存在接到用户行为数据重新更新推荐结果写回推荐结果数据中。
### 5.效率提升
上面展示了一个基于物品的协同过滤算法在实时推荐中的计算过程,那么随之而来的一些问题也需要解决。比如当用户历史行为数据有很多时,或者物品对是热门物品时,相似度实时更新就有些挑战了。对此可以有如下应对办法:剪枝,加窗,采样,缓存。
所谓剪枝就是,并不是需要对每一个“物品对”都做增量计算,为什么呢?
你想一想,两个物品之间的相似度,每更新一次得到的新相似度,可以看成一个随机变量,那么这个随机变量就有一个期望值,一旦物品之间的相似度可以以较高的置信度确认,它已经在期望值附近小幅度波动了,也就没必要再去更新了。
甚至如果进一步确定是一个比较小的相似度,或者可以直接干掉这个物品对,不被更新,也不参与计算。
那么问题就来了怎么确定什么时候可以不再更新这个物品对的相似度了呢这时候要用到一个不等式Hoeffding不等式。
Hoeffding不等式适用于有界的随机变量。相似度明显是有界的最大值是1最小值是0。所以可以用这个不等式Hoeffding不等式是这样一个统计法则随机变量的真实期望值不会超过 $\hat{x} + \epsilon$ 的概率是概率 1- $\delta$,其中 $\epsilon$ 的值是这样算的:
$$\epsilon = \sqrt{\frac{ln(\frac{1}{\delta})}{2n}}$$
公式中:$\hat{x}$ 是历次更新得到的相似度平均值n是更新过的次数。这样一来你选定 $\delta$ 和 $\epsilon$ 之后就知道更新多少次之后就可以放心大胆地使用了。
例如,下面这个表格是举的几个例子,这里设置 $\delta = 0.05$。
<img src="https://static001.geekbang.org/resource/image/56/8b/5621c1f1b381eca3daf793bd7171e48b.png" alt="" />
也就是在前面讲到的更新相似度的Bolt中如果发现一个物品对的更新次数已经达到最少更新次数则可以不再更新并且如果此时相似度小于设定阈值就可以斩钉截铁地说这两个物品不相似以后不用再参与推荐计算了。
这就是一项基于统计的剪枝方法,除此之外还有加窗、采样、合并三种常规办法。
首先,关于加窗。当我说,用户的兴趣会衰减,请你不要怀疑这一点,因为这是这篇文章的基本假设和出发点。
用户兴趣衰减,那么一个直接的推论就是,比较久远的用户历史行为数据所起的作用应该小一些。
所以,另一个剪枝技术就是:滑窗。设定一个时间窗口,时间窗口内的历史行为数据参与实时计算,窗口外的不再参与实时计算。这个窗口有两种办法:
1. 最近K次会话。用户如果反复来访问产品每次访问是一次会话那么实时计算时只保留最近K次会话信息。
1. 最近K条行为记录。不管访问多少次只保留最近K条历史行为事件参与到实时推荐中。
两种滑窗方法都可以有效保证实时计算的效率,同时不会明显降低推荐效果。
关于采样。当你的推荐系统遇到热门的物品或者异常活跃的用户,或者有时候就只是突然一个热点爆发了。
它们会在短时间产生大量的数据,除了前面的剪枝方法,还可以对这种短时间大量出现的数据采样,采样手段有很多,可以均匀采样,也可以加权采样,这在前面的专栏里已经详细介绍过方法。
关于合并计算。在前面介绍的增量计算中,是假设收到每一个用户行为事件时都要去更新相似度和推荐结果,如果在突然大量涌入行为数据时,可以不必每一条来了都去更新,而是可以在数据流的上游做一定的合并。
相似度计算公式的分子分母两部分都可以这样做,等合并若干事件数据之后,再送入下游去更新相似度和推荐结果。
最后,提高实时推荐的效率,甚至不只是推荐系统,在任何互联网应用的后端,缓存都是提高效率必不可少的部分。可以根据实际情况,对于高频访问的物品或者用户增加缓存,这可能包括:
1. 活跃用户的历史行为数据;
1. 热门物品的特征数据;
1. 热门物品的相似物品列表。
缓存系统一般采用Memcached或者Redis集群。缓存有个问题就是数据的一致性可能比较难保证毕竟它和真正的业务数据库之间要保持时时刻刻同步也是一项挑战。
好了,上面讲到的这些实时推荐有关的优化技巧,其实都是为了满足第三层次的实时推荐要求。
实时更新的推荐结果同步到推荐服务所依赖的线上数据库这个线上数据库还要定期被线下离线批量的推荐结果所替代。这样一来实时推荐和离线批量之间就形成了互为补充的作用这个模式也就是大数据架构最常见的Lambda架构。
## 总结
今天以协同过滤为例讲到了如何构建一个实时推荐,实际上,并不是每一种推荐算法都适合做实时推荐的,或者没必要。
幸运的是,很多机器学习算法,都可以使用一些在线学习方法更新模型,这些在线学习算法非常适合作为流计算的任务运行,属于我说的第三层次实时推荐。
另外还有一种算法天然就适合在线实时进行那就是前面讲到的Bandit算法通过和用户之间反复互动更新推荐。
实时推荐有三个层次,很多非工程师的朋友们常常脑海里想象的实时推荐实际上只是第二层次,也就是实时更新特征,并没实时更新模型,虽然两者的结果看上去都是推荐结果实时更新了,但是意义不同,难度不同,效果也不同。
今天的话题就聊到这里,留给你一个小小的问题,你能说一说你觉得哪些推荐策略不适合放在实时推荐中吗?欢迎留言一起讨论。

View File

@@ -0,0 +1,155 @@
<audio id="audio" title="29 | 让数据驱动落地,你需要一个实验平台" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f7/bc/f7140f36125e59dbf7456cc13804babc.mp3"></audio>
数据驱动这个口号喊了很多年了,这个口号也几乎成为了行业共识,但是数据驱动又像鬼一样,人人都在说,但几乎没人见过它长什么样子。
## 数据驱动和实验平台
要做到数据驱动,就要做到两点:第一点是数据,第二点是驱动。这听上去似乎像是废话,实际上不是。
这第一点的意思是,要采集数据,全方位,数据像是石油一样,没有它就谈不上驱动;第二点的意思是要让大家看数据,光采集了没有用,还需要让所有人盯着数据看。
而要做到驱动需要一个AB实验平台。数据驱动的重点是做对比实验通过对比让模型、策略、设计等不同创意和智慧结晶新陈代谢不断迭代更新。
对比实验也常常被大家叫做ABTest这个意思就是一个A实验一个B实验这样说可能有些模糊所以我需要先和你说说什么叫做对比实验然后再说说一个对比实验平台应该长什么样子。
你都可以把任何一家个性化推荐产品想象成一个函数,这个函数有很多参数影响它工作,函数的输出就是推荐物品列表。这些函数参数可以有各种组合,通过其中一种参数组合去面对一小股用户的考验,这就是一个实验。
要做实验,要做很多实验,要很快做很多实验,要很多人同时很快做很多实验,就需要实验平台。
要讨论实验平台,先要认识实验本身。互联网实验,需要三个要素。
1. 流量:流量就是用户的访问,也是实验的样本来源。
1. 参数:参数就是各种组合,也是用户访问后,从触发互联网产品这个大函数,到最后返回结果给用户,中间所走的路径。
1. 结果:实验的全过程都有日志记录,通过这些日志才能分析出实验结果,是否成功,是否显著。
把互联网产品想象一个有向无环图,每个节点是一个参数,不同的分支是参数的不同取值,直到走到终点,这一条路径上所有经过的参数取值,构成了服务的调用路径。
具体在推荐系统中,可能这些参数就是不同的模型与策略名称。每当一个用户经过这一系列的调用路径后,就为每一个分支产生了一条实验样本。
于是问题来了,每一个用户到来时,如何为他们决定要走哪条路径呢?这就要先经过实验对照来看。
实验要观察的结果就是一个随机变量,这个变量有一个期望值,要积累很多样本才能说观察到的实验结果比较接近期望值了,或者要观察一定时期才能说对照实验之间有区别或者没区别。
因为只有明显有区别并且区别项好,才能被进一步推上全线。
在设计一个实验之初,实验设计人员总是需要考虑下面这些问题。
1. 实验的起止时间。这涉及到样本的数量,关系到统计效果的显著性,也涉及能否取出时间因素的影响。
1. 实验的流量大小。这也涉及了样本的数量,关系到统计效果的显著性。
1. 流量的分配方式。每一个流量在为其选择参数分支时希望是不带任何偏见的也就是均匀采样通常按照UUID或者Cookie随机取样。
1. 流量的分配条件。还有一些实验需要针对某个流量的子集,例如只对重庆地区的用户测试,推荐时要不要把火锅做额外的提升加权。
1. 流量如何无偏置。这是流量分配最大的问题,也是最难的问题。同时只做一个实验时,这个问题不明显,但是要同时做多个实验,那么如何避免前面的实验给后面的实验带来影响,这个影响就是流量偏置,意思是在前面实验的流量分配中,有一种潜在的因素在影响流量分配,这个潜在的因素不易被人察觉,潜在的因素如果会影响实验结果,那么处在这个实验后面获得流量的实验,就很难得到客观的结论。这个无偏置要求,也叫做“正交”。
这些问题也是实验平台在设计之初要考虑的。试想一下,推荐系统中,算法工程师总是在尝试很多模型,或者在线下给出很多的模型调参,线下评测时,各种指标都是一片锣鼓喧天、红旗招展,恨不得立即上线去验验真实效果。
每一个算法工程师都这么想,但是线上流量有限,因此需要重叠实验,废水循环,最好能够做到洗脸的水冲马桶,这样灵活的实验平台长什么样子。
Google公司的实验平台已经成为行业争相学习的对象所以今天我会以Google的实验平台为主要对象深入浅出地介绍一个重叠实验平台的方方面面。
## 重叠实验架构
所谓重叠实验,就是一个流量从进入产品服务,到最后返回结果呈现给用户,中间设置了好几个检查站,每个检查站都在测试某些东西,这样同时做多组实验就是重叠实验。
前面说了,重叠实验最大的问题是怎么避免流量偏置。为此,需要引入三个概念。
1. 域:是流量的一个大的划分,最上层的流量进来时首先是划分域。
1. 层:是系统参数的一个子集,一层实验是对一个参数子集的测试。
1. 桶:实验组和对照组就在这些桶中。
层和域可以互相嵌套。意思是对流量划分例如划分出50%这50%的流量是一个域这个域里面有多个实验层每一个实验层里面还可以继续嵌套域也就是可以进步划分这50%的流量。下面这两个图示意了有域划分和没有域划分的两种情况。
<img src="https://static001.geekbang.org/resource/image/0d/12/0dcdda2f87d9fdad3ba73f2f108bbd12.png" alt="" />
图中左边是一个三层实验但是并没有没有划分域。第一层实验要测试UI相关第二层要测试推荐结果第三层要测试在推荐结果插入广告的结果。
三层互不影响。图中的右边则添加了域划分,也就是不再是全部流量都参与实验中,而是被分走了一部分到左边域中。剩下的流量和左边的实验一样。
这里要理解一点,为什么多层实验能做到重叠而不带来流量偏置呢?
这就需要说桶的概念。还是上面示意图中的左图假如这个实验平台每一层都是均匀随机分成5个桶在实际的实验平台上可能是上千个桶这里只是为了举例。
示意图如下:
<img src="https://static001.geekbang.org/resource/image/cc/c5/cc4cac4185abb37c5aefa334928c29c5.png" alt="" />
这是一个划分域的三层实验。每一层分成5个桶一个流量来了在第一层有统一的随机分流算法将Cookie或者UUID加上第一层ID均匀散列成一个整数再把这个整数对5取模于是一个流量就随机地进入了5个桶之一。
每一个桶均匀得到20%的流量。每一个桶里面已经决定好了为你展示什么样的UI流量继续往下走。每一个桶的流量接着依然面对随机进入下一层实验的5个桶之一原来每个桶的20%流量都被均分成5份每个桶都有4%的流量进入到第二层的每个桶。
这样一来第二层每个桶实际上得到的依然是总流量的20%,而且上一层实验带来的影响被均匀地分散在了这一层的每一个桶中,也就是可以认为上一层实验对这一层没有影响。同样的,第三层实验也是这样。
这就是分层实验最最基本的原理。在这个基础上,增加了域的概念,只是为了更加灵活地配置更多实验。
关于分层实验,有几点需要注意:
1. 每一层分桶时不是只对Cookie或者UUID散列取模而是加上了层ID是为了让层和层之间分桶相互独立
1. Cookie或者UUID散列成整数时考虑用均匀的散列算法如MD5。
1. 取模要一致,为了用户体验,虽然是分桶实验,但是同一个用户在同一个位置每次感受不一致,会有损用户体验。
Google的重叠实验架构还有一个特殊的实验层叫做发布层优先于所有其他的实验层它拥有全部流量。这个层中的实验通常是已经通过了ABtest准备全量发布了。示意图如下
<img src="https://static001.geekbang.org/resource/image/03/f2/03f0316d80742ce27ab9b2da653cfef2.png" alt="" />
前面举例所说的对用户身份ID做散列的流量分配方式只是其中一种还有三种流量分配方式一共四种
1. Cookie+层ID取模
1. 完全随机;
1. 用户ID+层ID取模
1. Cookie+日期取模。
在实验中,得到流量后还可以增加流量条件,比如按照流量地域,决定要不要对其实验,如果不符合条件,则这个流量不会再参与后面的实验,这样避免引入偏置,那么这个流量会被回收,也就是使用默认参数返回结果。
在Google的架构中由于层和域还可以嵌套所以在进入某个层时可能会遇到一个嵌套域这时候需要按照域划分方式继续下沉直到遇到实验或者被作为回收流量返回。整个实验平台工作的示意图如下所示
<img src="https://static001.geekbang.org/resource/image/0b/d6/0b221fe6cce28255e4444c882b7a96d6.png" alt="" />
说明如下:
1. 图中涉及了判断的地方,虚线表示判断为假,实线表示判断为真。
1. 从最顶端开始不断遍历域、层、桶最终输出一个队列Re其中记录对每一个系统参数子集如何处理取实验配置的参数还是使用默认参数其中无偏流量表示使用默认参数也就是在那一层不参与实验流量被回收。
1. 拿到Re就得到了全部的实验在去调用对应的服务。
## 统计效果
除了分层实验平台之外,还存在另一个问题,每一个实验需要累计获得多少流量才能得到实验结论呢?
这涉及了一点统计学知识。实验得到的流量不够,可以说实验的结论没有统计意义,也就浪费了这些流量,而实验在已经具有统计意义之后,如果还占用流量做测试,则也是在浪费流量。
如何确定实验规模呢Google给出了如下公式
$$N &gt;= 10.5(\frac{s}{\theta})^2$$
公式中:
1. $s$ 是实验指标的标准差。
1. $\theta$ 是希望检测的敏感度比如想检测到2%的CTR变化。
上面这个公式计算出来的实验规模表示以90%的概率相信结果的显著性也就是有90%的统计功效。
## 对比实验的弊端
AB测试实验平台是产品要做到数据驱动必不可少的东西但是这种流量划分的实验方式也有自己的弊端列举如下
1. 落入实验组的流量在实验期间可能要冒着一定的风险得到不好的用户体验在实验结束之前这部分流量以100%的概率面对这不确定性;
1. 要得得到较高统计功效的话,就需要较长时间的测试,如果急于看到结果全面上线来说有点不能接收;
1. 下线的实验组如果不被人想起,就不再有机会得到测试。
诸如此类弊端也可以考虑在实验平台中用Bandit算法替代流量划分的方式通过Bandit算法选择不同的参数组合、策略动态实时地根据用户表现给出选择策略一定程度上可以避免上面列举的弊端。
## 总结
实验平台是推荐系统要做到数据驱动必不可少的东西,但是如何做到科学高效快速地做实验呢?
常见的做实验只是简单地选择一个尾号的用户ID作为实验组再选择另一个尾号作为对照组甚至选择剩下所有的用户ID作为对照组。
这样做出来的实验,显然是有问题,因为并不知道通过用户尾号来分组是不是能做到无偏?另一个问题是,这样就只能在一个时期只能做一个实验,非常低效。
本文以Google开放的实验平台架构作为原型对其核心技术做了详细介绍。这个实验平台做到了同时无偏地做多组对照实验。因为它巧妙地引入了三个概念的嵌套结合
1. 域;
1. 层;
1. 桶。
三个概念层层相扣,流量划分得到了一个可行的方案。这个实验平台方案已经应用在很多公司中,你不妨在自己的公司尝试做一下。
最后留给你一个问题,关于分层实验的原理,你是否已经理解了为什么层和层之间可以做到毫不影响,欢迎给我留言讨论。

View File

@@ -0,0 +1,186 @@
<audio id="audio" title="30 | 推荐系统服务化、存储选型及API设计" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f4/16/f4892bf1542c0b3ee7b703254ce1fd16.mp3"></audio>
在过往的文章中,我讲到了推荐系统方方面面的相关概念。那么说,对于认识一个推荐系统来说,还差最后一个问题需要解决,那就是:万事俱备,如何给用户提供一个真正的在线推荐服务呢?
## 服务化是最后一步
其实一个推荐系统的在线服务,和任何别的在线服务相比,也没有什么本质区别,只是仍然还有一些特殊性。
提供一个在线服务需要两个关键元素数据库和API。今天我就来专门说一说推荐系统中大家常常用到的数据库并会谈谈推荐系统的API应该如何设计。
## 存储
这里注意一下,今天这里讲到的存储,专指近线或者在线部分所用的数据库,并不包括离线分析时所涉及的业务数据库或者日志数据库。
近线和在线的概念我在前面已经讲到过。推荐系统在离线阶段会得到一些关键结果,这些关键结果需要存进数据库,供近线阶段做实时和准实时的更新,最终会在在线阶段直接使用。
首先来看一下,离线阶段会产生哪些数据。按照用途来分,归纳起来一共就有三类。
1. 特征。特征数据会是最多的,所谓用户画像,物品画像,这些都是特征数据,更新并不频繁。
1. 模型。尤其是机器学习模型,这类数据的特点是它们大都是键值对,更新比较频繁。
1. 结果。就是一些推荐方法在离线阶段批量计算出推荐结果后,供最后融合时召回使用。任何一个数据都可以直接做推荐结果,如协同过滤结果。
如果把整个推荐系统笼统地看成一个大模型的话,它依赖的特征是由各种特征工程得到的,这些线下的特征工程和样本数据共同得到模型数据,这些模型在线上使用时,需要让线上的特征和线下的特征一致,因此需要把线下挖掘的特征放到线上去。
特征数据有两种,一种是稀疏的,一种是稠密的,稀疏的特征常见就是文本类特征,用户标签之类的,稠密的特征则是各种隐因子模型的产出参数。
特征数据又常常要以两种形态存在:一种是正排,一种是倒排。
正排就是以用户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的数据模型组织形式如下图所示
<img src="https://static001.geekbang.org/resource/image/b9/3c/b9b12de5134a6a3c88ba8a97f6a3333c.png" alt="" />
从这个图可以看出来,可以通过行主键及列名就可以访问到数据矩阵的单元格值。
前面也说过用户和物品的画像数据适合存储在Cassandra中。也适合存储模型数据如相似度矩阵还可以存储离线计算的推荐结果。
### 2.键值数据库
除了列式数据库外还有一种存储模式就是键值对内存数据库这当然首推Redis。Redis你可以简单理解成是一个网络版的HashMap但是它存储的值类型比较丰富有字符串、列表、有序列表、集合、二进制位。
并且Redis的数据放在了内存中所以都是闪电般的速度来读取。
在推荐系统的以下场景中常常见到Redis的身影
1. 消息队列List类型的存储可以满足这一需求
1. 优先队列比如兴趣排序后的信息流或者相关物品对此sorted set类型的存储可以满足这一需求
1. 模型参数,这是典型的键值对来满足。
另外Redis被人诟病的就是不太高可用对此已经有一些集群方案有官方的和非官方的可以试着加强下Redis的高可用。
### 3.非数据库
除了数据库外在推荐系统中还会用到一些非主流但常用的存储方式。第一个就是虚拟内存映射称为MMAP这可以看成是一个简陋版的数据库其原理就是把磁盘上的文件映射到内存中以解决数据太大不能读入内存但又想随机读取的矛盾需求。
哪些地方可以用到呢?比如你训练的词嵌入向量,或者隐因子模型,当特别大时,可以二进制存在文件中,然后采用虚拟内存映射方式读取。
另外一个就是PMML文件专门用于保存数据挖掘和部分机器学习模型参数及决策函数的。当模型参数还不足以称之为海量时PMML是一个很好的部署方法可以让线上服务在做预测时并不依赖离线时的编程语言以PMML协议保存离线训练结果就好。
## API
除了存储推荐系统作为一个服务应该以良好的接口和上有服务之间交互因此要设计良好的API。
API有两大类一类数据录入另一类是推荐服务。数据录入API可以用于数据采集的埋点或者其他数据录入。
<img src="https://static001.geekbang.org/resource/image/6e/15/6e88ceb14c4e28e8e113393f147de615.png" alt="" />
推荐服务的API按照推荐场景来设计则是一种比较常见的方式下面分别简单说一下API的样子。
### 1.猜你喜欢
**接口:**
/Recommend
**输入:**
* UserID 个性化推荐的前提<br />
 * PageID 推荐的页面ID关系到一些业务策略<br />
 * FromPage 从什么页面来<br />
 * PositionID 页面中的推荐位ID<br />
 * Size 请求的推荐数量<br />
 * Offset 偏移量,这是用于翻页的
**输出:**
* Items  推荐列表通常是数组形式每一个物品除了有ID还有展示所需的各类元素<br />
 * Recommend_id 唯一ID标识每一次调用也叫做曝光ID标识每一次曝光用于推荐后追踪推荐效果的很重要<br />
 * Size 本次推荐数量<br />
 * Page —— 用于翻页的
### 2.相关推荐
**接口:**
/Relative
**输入:**
* UserID 个性化推荐的前提<br />
 * PageID 推荐的页面ID关系到一些业务策略<br />
 * FromPage 从什么页面来<br />
 * PositionID 页面中的推荐位ID<br />
 * ItemID 需要知道正在浏览哪个物品导致推荐相关物品<br />
 * Size 请求的推荐数量<br />
 * Offset 偏移量,这是用于翻页的
**输出:**
* Items  推荐列表通常是数组形式每一个物品除了有ID还有展示所需的各类元素<br />
 * Recommend_ID 唯一ID标识每一次调用也叫做曝光ID标识每一次曝光用于推荐后追踪推荐效果的很重要<br />
 * Size 本次推荐数量<br />
 * Page —— 用于翻页的
### 3.热门排行榜
**接口:**
/Relative
**输入:**
* UserID 个性化推荐的前提<br />
 * PageID 推荐的页面ID关系到一些业务策略<br />
 * FromPage 从什么页面来<br />
 * PositionID 页面中的推荐位ID<br />
 * Size 请求的推荐数量<br />
 * Offset 偏移量,这是用于翻页的
**输出:**
* Items  推荐列表通常是数组形式每一个物品除了有ID还有展示所需的各类元素<br />
 * Recommend_id 唯一ID标识每一次调用也叫做曝光ID标识每一次曝光用于推荐后追踪推荐效果的很重要<br />
 * Size 本次推荐的数量<br />
 * Page —— 用于翻页的
相信你看到了吧,实际上这些接口都很类似。
## 总结
今天我主要讲解了推荐系统上线的两大问题一个是线上数据存储另一个是推荐系统的API有哪些。
虽然实际情况肯定不是只有这点问题,但是这些也足以构建出一个简单的推荐系统线上版了。
你还记得在前几篇专栏中我提到统一考虑搜索和推荐的问题吗那么说如果要把推荐和搜索统一考虑的话API该如何设计呢欢迎留言一起讨论。感谢你的收听我们下期再见。