30-推荐系统三十六式

开篇词 | 用知识去对抗技术不平等

时至今日，推荐系统已然成了一门显学，个性化推荐成了互联网产品的标配。为此，我知道，好学的你肯定在收藏着朋友圈里流传的相关文章，转发着微博上的相关讨论话题，甚至还会不断奔走在各种大小行业会议之间，听着大厂职工们讲那些干货。

同样，我也知道，这样碎片化的吸收，增加了知识的同时，也增加了焦虑。因为技术的不平等广泛存在于业界内，推荐系统也不例外。

推荐系统从搜索引擎借鉴了不少技术和思想，比如内容推荐有不少技术就来自搜索引擎， 由 Amazon 发扬光大的，基于用户（ User-based ）和基于物品（ Item-based ）的协同过滤将推荐系统技术从内容延伸到协同关系，超越了内容本身。

后来 Netflix 搞了一个瓜分百万美元的土豪比赛，以矩阵分解为代表的评分预测算法如雨后春笋般出现。至此，机器学习和推荐系统走得越来越近，最近十年，深度学习和强化学习又将推荐系统带向了新的高度。

推荐系统也是现在热门的 AI 分支之一，但凡 AI 类的落地，都需要具备这几个基本元素才行：数据、算法、场景、计算力。推荐系统也不例外，而刚好，现在的时代，这些元素的获得成本相比十年前已经小了很多。未来随着各种硬件设备越来越智能，万物互联得越来越紧密，人们的个性化需求、场景的多样性、数据的复杂性都对推荐系统提出了更高的要求。

有一个趋势我是确信无疑的：世界在向网状发展，万事万物倾向于相互连接构成复杂网络。复杂网络具有无尺度特点，表现是：少数节点集聚了大量连接。这个现象不陌生，叫做马太效应，社交网络粉丝数、网页链接引用量、电商网站商品销量等等，无不如此。推荐系统的使命，就是要用技术来对抗这种不平等。

在复杂网络中，雄踞顶端的节点无法体会长尾的疾苦。推荐系统的技术应用现状也如此，大厂们一骑绝尘，感觉分分钟就要达到奇点的节奏，然而更普遍的是：太多中小厂、工程师们还不知道一个推荐系统如何才能从 0 到 1 诞生，这需要去了解哪些知识？

这样的知识鸿沟，需要有人去填平，需要让成熟的技术走进每一个可以采用的产品中和愿意学习的人大脑中，让整个社会一起提高效率，享受时代赐予的技术红利。

于是，我在极客时间的邀请下，开了这个专门介绍推荐系统知识的专栏，系统地为你整理推荐系统的相关知识和常识，来对抗技术本身的不平等。

面临现状，你其实需要这样的知识：

能解决系统起步阶段 80% 的问题；

已被无数产品验证过有用的东西；

遇到问题能够找到人或者社区交流，而非曲高和寡的前沿技术；

知识之间有层次递进关系，也有分门别类的整理。

这样的力气活儿，你就不用管了，交给我来。我能真切地体会到你的诉求，我在上市公司、传统行业转型互联网的公司、中小型公司、创业公司都构建过推荐系统，能帮你分辨出哪些内容是为了 PR 而发，哪些是真诚地分享知识。

为此，我力图从纷繁复杂的全部内容中去掉一些，虽然酷炫但是大多数公司和个人暂时不需要的；也力图保留并详细讲解一些，不但适用于大公司也适用于中小公司的。当然，我也力图让枯燥的技术内容不要那么枯燥，让技术更有趣一些。

我是刑无刀（本名陈开江），是“刑”，不是“邢”。“刑”与“无刀”，就是我本名里面的“开”，江湖上有人会用“邢无耳”等方式山寨我。我的读书和工作经历，关键词就是“算法、推荐系统”。

读研时从事句法分析研究，工作后我先在微博负责数据挖掘、自动问答、推荐系统等研发，后加入考拉 FM，带领四五个人的小团队一起开发了考拉 FM 的推荐系统。

2015 年的一个春天，我和几个朋友在北京画了一个圈，开始创业，先做了两个 APP，即 Wave（社交电商）和边逛边聊（短视频晒单），也都是以推荐系统为产品的主要功能。目前我已加入链家网，从事算法类产品的研发，希望帮助大家买到或租到便宜的房子。

我不是一个典型的技术男，我喜欢从各种维度去思考推荐系统，产品、技术、商业等，也喜欢借鉴不同学科去思考其背后的本质规律。我希望能把这些思考带给你，也希望和你碰撞出新的思想来。

本专栏共包含 36 篇文章，分成五个模块详细介绍推荐系统的相关知识。

概念篇：介绍一些推荐系统有关的理念、思考、形而上的内容，虽然务虚但是必要。

原理篇：推荐算法的原理介绍，是俗称的干货。知道推荐系统背后技术的基本原理后，你可以更快地开发自己的系统，更好地优化自己的系统，并且更容易去学习专栏中未涉及的内容。

工程篇：推荐算法的实践内容。系统落地时需要一些纯工程上的大小事情，架构、选型、案例等。

产品篇：推荐系统要成功，还要考虑产品理念及其商业价值，因此这部分介绍一些产品知识和一点浅显的商业思考。

团队篇：从个人来说，就是该怎么学习和成长；从团队来说，就是该招多少人，该有哪些人，以及产品经理和工程师该如何合作等问题。

我的专栏每周更新三篇，持续大约三个月。这三个月我会陪你去完整了解推荐系统常见的方方面面，也期待你给我提出有意思的问题，这样我们就实现了共同进步，一起去对抗技术本身的不平等。

“推荐系统三十六式”专栏 3 月 5 日正式更新，敬请期待。

【概念篇】个性化推荐系统那些绕不开的经典问题

推荐系统发展到了今天，已经出现了一些常见的问题，一部分已经有很好的解决方案，另外一部分却还没有通用解决方案，需要根据实际情况做一下具体的分析。

今天我来和你聊一聊这些问题。我会首先讲讲一些推荐系统中的问题模式，然后再专门说一些需要面对的具体问题。

推荐系统的问题模式

我们知道，推荐系统的使命是为用户和物品建立连接，建立的方式是提前找出那些隐藏的连接呈现给用户，这是一个预测问题；所以推荐系统的预测问题模式，从达成的连接目标角度区分，有两大类：

评分预测；

行为预测。

因为评分和行为是用户对推荐结果的两类反馈，我们给他们推荐了一个或多个物品，目的是希望他们“消费”，这种消费反应在用户行为上，比如“点击查看”，信息咨询类的还有“阅读完成”，视频音乐类的有“播放完成”，电商类的“加入购物车”等。

整个行为呈现一个漏斗形状，从曝光到最终消费完成。最后在用户完成消费后，产品方一般还希望他们告诉自己消费的体验，这时候就有评分了；所以不同推荐系统的任务也不同，有的直接去预测用户如果消费完之后会给多少评分，更多的推荐系统则会分层，致力于预测用户的行为。下面我分别详细说一下这两类问题。

评分预测

评分预测相关算法模型研究的兴盛，最大的助攻是 Netflix 举办的推荐算法大赛。

评分预测要干的事情是这样的：假如用户消费完一个物品之后会给出一个打分，比如通常是 1~5 分，或者有的网站用星星的颗数表示，也是一样。

我们就想能不能提前预测一个用户对每一个物品会打多少分，找出那些他可能会打高分，但是还没消费的物品，然后装作若无其事地呈现在他面前，惊不惊喜，意不意外?

说干就干，怎么干呢？正如王小波给李银河写的信那样：不能胡干。一个朴素的思想是：建立一个模型，这个模型会给用户历史上打过分的物品去预测分数。

预测分数和实际分数之间会有误差，我们根据这个误差去调整模型参数，让这个误差越来越小，最后得到的这个模型理论上就可以为我们干活了。事实上，这其实就是个机器学习里面的回归问题。

Netflix 比赛的评判标准就是 RMSE ，即均方根误差，怎么算的呢？

这个公式中的 t 表示每一个样本，n 表示总共的样本数，有帽子的 yt 就是模型预测出的分数，是我们交的作业，秃顶的 yt 就是用户自己打的分数，是标准答案，然后一个样本一个样本地对答案，模型预测分数和用户自己打分相减，这就是我们预测的误差。

由于误差有正数也有负数，而我们只关心绝对值大小，所以再给误差求平方，这就是名字中的“方”的来源，再对所有样本的误差平方求平均值，这就是名字中“均”的来源，因为我们对误差都平方了，所以最后再对均值开方根，这就是名字中的“根”的来源。这个过程就是求均方根误差。

评分预测问题常见于各种点评类产品（如：书影音的点评），但评分类推荐存在以下问题：

数据不易收集，我刚才说过，用户给出评分意味着他已经完成了前面所有的漏斗环节；

数据质量不能保证，伪造评分数据门槛低，同时真实的评分数据又处在转化漏斗最后一环，门槛高；

评分的分布不稳定，整体评分在不同时期会差别很大，个人评分在不同时期标准不同，人和人之间的标准差别很大。

用户爸爸们给产品施舍的评分数据，我们又叫做显式反馈，意思是他们非常清晰明白地告诉了我们，他们对这个物品的态度；与之相对的还有隐式反馈，通常就是各类用户行为，也就是另一类推荐系统问题：行为预测。

行为预测

实际上，用户爸爸们每天要在不同的 App 或者网站之间不停批阅奏章，日理万机，非常忙，所以能够提交的像评分这种显式反馈数据很少。

但是没关系，只要用户来了，就会有各种行为数据产生，从登录刷新，到购买收藏，都是用户行为，这类数据是用户们在自觉自愿的情况下产生的，数据量比显式反馈多很多。

用户的行为通常呈现漏斗关系，我希望用户最终达成的行为可能不是那么容易得到的，比如购买，比如建立一个社交关系，比如完整消费一个长内容，通常是从登录刷新开始，逐层经历漏斗流失。

而推荐系统肩负的使命自然是达成用户行为，也就是连接越多越好。这也是这一类推荐系统问题的关注点。

推荐系统预测行为方式有很多，常见的有两种：直接预测行为本身发生的概率，和预测物品的相对排序。直接预测用户行为这一类技术，有一个更烂大街的名字，叫做 CTR 预估。这里的 C 原本是点击行为 Click，但这个解决问题的模式可以引申到任何其他用户行为，如收藏、购买。

CTR 意思就是 Click Through Rate，即“点击率”。把每一个推荐给用户的物品按照“会否点击”二分类，构建分类模型，预估其中一种分类的概率，就是 CTR 预估。

行为预测说白了，就是利用隐式反馈数据预测隐式反馈的发生概率；也因此，各家互联网产品要高度重视隐式反馈，归纳起来有以下几点原因。

数据比显式反馈更加稠密。诚然，评分数据总体来说是很稀疏的，之前 Netflix 的百万美元挑战赛给出的数据稀疏度大概是 1.2%，毕竟评分数据是要消耗更多注意力的数据。

隐式反馈更代表用户的真实想法，比如你不是很赞成川普的观点，但还是想经常看到他的内容（以便吐槽他），这是显式反馈无法捕捉的。而人们在 Quora 上投出一些赞成票也许只是为了鼓励一下作者，或者表达一些作者的同情，甚至只是因为政治正确而投，实际上对内容很难说真正感兴趣。

隐式反馈常常和模型的目标函数关联更密切，也因此通常更容易在 AB 测试中和测试指标挂钩。这个好理解，比如 CTR 预估当然关注的是点击这个隐式反馈。

用户给出较高评分的先决条件是用户要有“评分”的行为，所以行为预测解决的是推荐系统的 80% 问题，评分预测解决的是最后那 20% 的问题，行为预测就像是我们剁手买买买后，可爱的商品要先乘坐飞机，飞跃千山万水到所在区域来，而评分预测则是快递员最终将东西递交到你手上这个过程。

几个常见顽疾

讨论了两大类推荐系统的问题后，我们再来看几个推荐系统的隐藏顽疾。之所以说这些是隐藏顽疾，是因为它们还没有很好的通用解决方案，并且不容易被重视，这几个顽疾分别是：

冷启动问题；

探索与利用问题；

安全问题。

1 冷启动问题

推荐系统是数据贪婪型应用，所谓数据贪婪型应用，就是对数据的需求绝无足够的那一天。冷启动问题广泛存在于互联网产品中，但我们这里仅仅限于推荐系统的冷启动。

新用户或者不活跃用户，以及新物品或展示次数较少的物品，这些用户和物品，由于缺乏相关数据，很是空虚寂寞冷，因此就是冷启动问题的关注对象。

关于“如何解决冷启动”本身，有伪命题的嫌疑，因为通常的解决方式就是给它加热：想办法引入数据，想办法从已有数据中主动学习（一种半监督学习）。我们会在后面的文章中详细讨论冷启动的问题。

2 探索与利用问题

探索与利用，行话又叫做 EE 问题。假如我们已经知道了用户的喜好，一般有三种对待方式：

全部给他推荐他目前肯定感兴趣的物品；

无视他的兴趣，按照其他逻辑给他推荐，如编辑推荐、随机推荐、按时间先后推荐等等；

大部分给他推荐感兴趣的，小部分去试探新的兴趣，如同一边收割长好的韭菜，一边播种新的韭菜。

哪一种更科学和持久呢？显然是第三种，那么如何平衡这里的“大部分”和“小部分”呢？

这就是 Exploit 和 Explore 问题的核心了。Exploit 意为“开采”，对用户身上已经探明的兴趣加以利用，Explore 意为“探索”，探明用户身上还不知道的兴趣。

我们会在后面的文章中详细讨论 EE 问题。

3 安全问题

凡是系统就有漏洞，凡是漏洞有利可图，就一定有人去图，推荐系统也不例外。如果你正在一款流量非常大的产品上构建推荐系统，那么一定要考虑推荐系统攻击问题。推荐系统被攻击的影响大致有以下几个：

给出不靠谱的推荐结果，影响用户体验并最终影响品牌形象；

收集了不靠谱的脏数据，这个影响会一直持续留存在产品中，很难完全消除；

损失了产品的商业利益，这个是直接的经济损失。

所以推荐系统的安全问题：有哪些攻击手段，以及对应的防御办法，这些我们也会在后面的文章中予以讨论。

总结

今天，我从两个角度总结了推荐系统中的常见问题。

第一个角度是模型的角度，将推荐系统的模型分成了预测评分和预测行为，这样一来，自己有什么数据就选择什么样的模型。另一个角度是看看推荐系统中一些永恒存在但是潜在的问题，包括冷启动、EE 问题、安全问题。

好了，说到这里，你可以去观察一下你用过的推荐系统，看看它们的模型是预测评分还是预测行为，你可以在下面留言，我们一起讨论。感谢你的收听，我们下次再见。

想迅速了解并掌握推荐系统的同学，可以购买《推荐系统三十六式》专栏，作者在推荐系统方面有 8 年的经验，为推荐系统学习者架构起整体的知识脉络，并在此基础上补充实践案例与经验，力图解决系统起步阶段 80% 的问题。

【概念篇】你真的需要个性化推荐系统吗?

个性化推荐的历史和我们国家的改革开放历史差不多，已经有些年头了。它已经从一个非常小众的方向，演变成了今天互联网产品的常见“配件”。

再加上一些以“个性化”为品牌卖点的互联网产品的成功，也给相关从业者打了一剂“强心针”，并让更多人跃跃欲试，想给自己的产品加上个性化元素。

但是别急，在问“该怎么做”之前，先来回答一下“要不要做”，毕竟谁都不想去实现一个无脑 PM 拍脑袋给出的需求不是？我先帮你更加透彻地理解“推荐系统”这个概念，只有彻底理解了事物的本质，才能轻松做出“适不适合”的判断，毕竟知根知底再相爱更容易到白头。

什么是推荐系统？

到底什么是推荐系统？按照维基百科的定义：它是一种信息过滤系统，手段是预测用户（User）对物品（Item）的评分和偏好。这个定义不是很好理解，也不恰当。它用“怎么做”来定义了“是什么”，这相当于变相规定了推荐系统的实现路径。

让我们来换一个角度回答三个问题，从而重新定义什么是推荐系统：

它能做什么；

它需要什么；

它怎么做。

对于第一个问题“它能做什么”，我的回答是：推荐系统可以把那些最终会在用户（User）和物品（Item）之间产生的连接提前找出来。

这里简单说一下“连接”这个词，这个词含义非常广泛，凡是能够产生关系的都是连接，比如用户对物品做出了一个行为，或者用户的某些属性和物品的属性一样等等，有关系就是连接。

为什么这么说呢？这是基于这么一个事实：万事万物有相互连接的大趋势，比如人和人倾向于有更多社会连接，于是有了各种社交产品；比如人和商品有越来越多的消费连接，于是有了各种电商产品；人和资讯有越来越多的阅读连接，于是有了信息流产品。

这还只是纯数字世界，随着各种物理实体智能化趋势越来越明显，万物互联还会进一步强化。世界是一个数字化的大网，但里面只有两类节点：人和其他。

人是互联的终极意义，“其他”统称为物品，物品可能是人、资讯、消费品、服务等。推荐系统就是要在这张巨大的网中，不断去发现那些很可能会和人发生连接的另一类物品节点，让它们和用户真的建立连接。

提炼一下上述逻辑：

世界的发展趋势是万物倾向于建立越来越多的连接；

人是这一切趋势的意义所在，为人建立连接是要义；

根据已有的连接预测和人有关的连接，就是推荐系统。

为了更形象，我再举几个例子。

1 一个社交产品，比如脸书（Facebook），如果它的 20 亿活跃用户之间已经都有社交关系了，那么它的“感兴趣的人”这一推荐系统就该寿终正寝了。

从已经建立社交关系的用户身上去推测你还可能对哪些人感兴趣，本质上就是提前把那些可能的用户连接找出来，然后再按照用户分别呈现在每一个人面前。

2 一个信息流资讯阅读产品，比如今日头条，只有当用户不断点进源源不断的内容物品中，每一次点击，就是一个连接，每一次阅读也是一个连接，不同层次不同重要性的连接在推荐系统的帮助下不断建立，所主要依据的就是那些已经存在的连接，即：用户过去都点击阅读了哪些内容。

3 一个电商平台，用户刚买过什么，常买什么，你正在浏览什么，这些都是用户和物品之间已经存在的连接，用这些连接去预测还会买什么，还会看什么也是推荐系统。

按照上面的分析，我也同时回答了第二个问题“它需要什么”：推荐系统需要已经存在的连接，从已有的连接去预测未来的连接。

第三个问题：怎么做？

维基百科的定义提供了一个说法：预测用户评分和偏好。这是推荐系统背后相关算法和技术的两大分类，在后面的专栏内容中我会讲到；但比这个定义更抽象的实现方式分类是：机器推荐和人工推荐，也就是通常说的“个性化推荐”和“编辑推荐”。

两者之间还存在现在最常见的领域专家推荐，也就是网红推荐，如何为用户找到适合他的网红也属于推荐系统范畴，编辑推荐偏玄学了，我在这个专栏里不会重点讨论。

总结一下推荐系统就是：用已有的连接去预测未来用户和物品之间会出现的连接。

你需要推荐系统吗？

我已经根据“能做什么”“需要什么”“怎么做”三个方面，讨论了什么是推荐系统。那么只要前两个条件成熟，你就需要一个推荐系统，至于“怎么做”的问题则简单得多，否则的话就是暂时不需要。那么，如何判断条件是否成熟了呢？

我们可以考虑两点。

第一，看看产品的目的。如果一款产品的目的是建立越多连接越好，那么它最终需要一个推荐系统。有哪些产品的目的不是建立连接呢？一种典型的产品就是工具类，如果是单纯提高人类某些工作的效率而存在的产品，比如一个视频编辑器，则不需要。虽然如今很多产品都从工具切入最后做成社区了，至少在工具属性很强时不需要推荐系统。

第二，看看产品现有的连接。如果你的产品中物品很少，少到用人工就可以应付过来，那么用户产生的连接肯定不多，因为连接数量的瓶颈在于物品的数量，这时候不适合搭建推荐系统。

或者用户和物品数量在某些手段下也变得很多，但是用户和物品之间的连接很少，表现就是用户的留存回访很低，这时候也不是很需要一个推荐系统。

你应该是想办法找到用户流失的原因，直到他们能贡献第一批连接才行。当然，用户很少时，人工完全可以应付一对一服务时，也是不需要推荐系统的。

关于第二点，“长尾理论”可以帮助我们理解，如何把用户和物品各种可能的连接汇总，包括用户属性、物品属性等，应该要有长尾效应才可能让推荐系统发挥效果。

这里我介绍一个简单指标，用于判断是不是需要推荐系统：

分子是增加的连接数，分母是增加的活跃用户数和增加的有效物品数。

这个简单的指标我解释一下：

如果增加的连接数主要靠增加的活跃用户数和增加的物品数贡献，则该值会较小，不适合加入推荐系统；如果增加的连接数和新增活跃用户和物品关系不大，那说明连接数已经有自发生长的趋势了，适合加入推荐系统加速这一过程。

不过，具体并没有判断标准，因产品而异。

总结

到底要不要上推荐系统，如果仅仅从战术上来看，是一个关乎投入产出比的问题，搭建一个推荐系统的前期投入不小，你需要：组建团队、购置计算资源、积累数据、花费时间优化。

这些成本在早期不必须或者不成熟的情形下投入，显然投入产出比不是很优；但如果是战略问题，那就不在本文的讨论范围内了，至于哪些算是战略问题，我列举几个例子：

产品要有这个属性，方便产品融资；

团队要有相关人才，因为不好招聘，先提前屯着；

要培养这样的思维，从而形成数据导向的产品文化。

类似等等，都不属于战术问题，具体问题具体分析。

现在我们来回顾一下，今天我先从三个角度定义了什么是推荐系统，然后从定性和定量两个角度分析了到底是否需要给自己的产品加上推荐系统。

那么，你可以试着从这两个角度分析一下，你自己的产品是不是需要推荐系统呢？欢迎留言，我们一起讨论。

【概念篇】这些你必须应该具备的思维模式

在开始讲解一些比较硬的知识之前，我先来给你洗洗脑，传达一些形而上、务虚但是重要的内容；所以，今天我主要带你认识两方面的内容：一个是重新认识推荐系统关键元素的重要性，另一个是要建立起两个思维模式。

这两个方面的内容如果理解不到位，尤其是当你去负责整个推荐产品的时候，那真是害苦了整个团队所有的兄弟姐妹。

对关键元素重要性的认识

要开发一个推荐系统产品，有这么四个关键的元素需要注意：

UI 和 UE；

数据；

领域知识；

算法。

他们的重要性依次递减，权重大致是 4-3-2-1，是不是知道真相的你眼泪掉下来。因为，推荐系统的效果不是你想买就能买，而是要正确认识不同阶段不同因素的重要性。

最先优化的一定是产品的 UI 和 UE ，即人机交互设计和用户体验设计。“颜值即正义”的法则放在推荐系统中也是成立的，不能因为你的产品是具有 AI 属性的个性化推荐就不看脸了，用户对产品的体验，视觉是否符合目标用户审美，交互逻辑是否简单明了，这些会在最大程度上决定用户是否会持续使用。

只有当用户不断回来，才有推荐系统的用武之地。个性化推荐产品首先还是一个产品，按照一款优秀产品的标准来要求它始终是正确的事情，所以请继续善待你们的 UI 和 UE 设计师们。

数据与 UI、UE 是几乎同等重要的元素，它是推荐系统的食材，巧妇难为无米之炊，多少算法工程师因为加入了一家没有历史数据积累的公司，那种“拔剑四顾心茫然”的无力感，谁去谁知道。

数据贯穿了产品始终，对数据的尊重就是对理性和科学的尊重。UI、UE、数据是一个产品的基石，不论其有没有推荐系统存在都是基石。

领域知识，与之对应的是常识和通识。可以这样说，没有哪个产品不涉及领域知识，每一个产品存在于市场上，总是有一部分价值是大多数其他产品无法替代的，这部分就涉及了领域知识。

电商产品有自己的领域知识，比如普通用户更在意的是价格而不是兴趣；音乐产品也有自己的领域知识。

比如一个歌手的死忠粉，你推荐给他该歌手的任意一首歌对他来说都是徒劳——因为他早就听过了。

至于新闻类产品，更是有自己的领域知识：新闻更新很快，可能上个月的内容都得从候选池中拿掉了。

类似这些在一个领域总结出来的普适规律，对于推荐系统的效果提升非常有用：有的是防止闹笑话自毁品牌形象，有的是大幅提高某些指标，有的是缩短模型训练周期。

我们对算法的爱是复杂的，大多数人最不了解的似乎就是算法，以至于本专栏会用最大的篇幅去讲解各种算法原理，但你一定要认识到，算法的左右没有你想象的那么大，但也一定不是可有可无。

一种对算法的常见误会就是：短期高估，长期低估。如果你不是算法工程师，比如产品经理或者运营人员，那么可能你要尤其注意，在一款个性化产品诞生之初，算法所起到的作用可以忽略，我们不能指望它能让产品起死回生、一飞冲天，但就此抛出“算法无用论”也是很愚蠢的。

这四个元素，都不是“天亮以后说分手”的那种，而是需要长期陪伴呵护，不断打磨。这里阐明其重要性的高低，是为了让你在资源有限，精力很少的前提下抓大放小。

目标思维和不确定性思维

四个元素的重要性认识清楚后，我再给你掏心掏肺地传达两个思维模式：目标思维和不确定性思维。

我个人对于软件产品有一个粗略分类。传统的软件是一个信息流通管道，从信息生产端到信息消费端的通道，比如一款内容 App，写内容的可以正常记录，读内容的可以流畅加载，无论多大的并发量都扛得住，这就是一个正常的产品了。

但推荐系统这种产品，如果是一个产品的话，它和作为信息流通管道的本质不一样，它是一个信息过滤工具，要解决的问题不是信息流通本身，而是如何让流通更有效率。

这两个本质不同的软件产品，决定了我们要以不同视角去对待推荐系统。传统的软件产品追求的是稳定和满足预期，背后思想强调的是逻辑和因果链条，软件体验上设定好行为和响应，软件设计上强调分层以应对无比复杂的操作逻辑。

核心词可以表述为：逻辑、因果、分层。反观推荐系统这种信息过滤系统，追求的是指标的增长，背后思想强调是目标和不确定性：我们并不能很确定地模拟每个人将会看到什么，也不能很好地复现一些操作过程，充满了不确定性，但是在推荐系统未动的情形下，目标先行则是常识。

关于目标思维，如果了解机器学习的人会很好理解。通常来说，训练机器学习模型是一个不断最小化（或者最大化）目标函数的过程，先设定一个目标函数，然后通过不断迭代让这个函数值到最小值（或者最大值）。

我们把一个推荐系统也看做一个函数，这个函数的输入有很多：UI、UE、数据、领域知识、算法等等，输出则是我们关注的指标：留存率、新闻的阅读时间、电商的 GMV、视频的 VV 等等。

这些指标就是函数值，我们做任何事情：加新的策略、替换现有的推荐算法、修改 UI、甚至一些文案的调整，都是在改变这个函数的参数，是否有效就要看看函数的输出值，输出值在增长，说明修改就有效，就继续沿着那个方向修改，一旦无效或者起反作用就立即需要回滚。

目标思维背后是“量化一切”的价值取向。最先要量化的就是目标本身，整个团队才能知道在为什么而战，才能知道自己所做的动作是不是有意义，才能让团队自发地去寻找优化方向，一定不能停留在“感觉推荐很精准”或者“感觉推荐得很不准”这样的玄学层面。

接下来要量化的是所有的优化改进动作，知道 Logo 放置位置往上移动了多少，知道文案字数减少了多少，知道 Push 少发了几次，发给了谁，谁点进来了。量化一切的价值取向和前面说的数据的重要性是一体两面，要量化就要收集数据，数据收集对了才能得到正确的量化结果。

盯着量化后的目标去行动后，还需要你具备另一个思维：不确定性思维。这个思维是开发传统软件类产品后较难转变形成的。

什么是确定性思维？举个例子说，原来的产品我们能知道某个人的界面上看到的是什么，因为它是完全依靠逻辑和因果链条引发而成的，只要初始条件给定，那么结果就是一定的，也因此在出现 Bug 时可以很容易复现，这是确定性思维，就是对于结果有预期，可以提前推演出来。

反之，不确定性思维就是：不用因果逻辑严丝合缝地提前推演，而是用概率的眼光去看结果。

比如说，出现了一个不是很合适的推荐，通常老板们会立即责问：“为什么出现这个”，这就是确定性思维在作祟，如果是不确定性思维，就会问：“出现这个的可能性有多大”。

为什么负责推荐系统产品的人一定要有不确定性思维呢？原因有以下几个。

绝大多数推荐算法都是概率算法，因此本身就无法保证得到确切结果，只是概率上得到好的效果；

推荐系统追求的是目标的增长，而不是一城一池的得失；

如果去花时间为了一个 Case 而增加补丁，那么付出的成本和得到的收益将大打折扣；

本身出现意外的推荐也是有益的，可以探索用户的新兴趣，这属于推荐系统的一个经典问题：EE 问题，我也会在后面的内容中专门讲。

总结

今天，我主要说了两个内容：

推荐系统构成元素的相对重要性，搞清楚重点，才能快速前进；

建立起目标思维和不确定性思维，抓住目标，不择手段地增长目标，不纠结一城一池的得失。

这两个事情虽然有点务虚，但的确是我所经历过的团队教给我最重要的事，有的是教训，有的是经验。现在，你能说说你对目标不确定性的理解吗？你们在现在的工作中哪些工作属于确定性思维？ 你可以给我留言，我们一起讨论。

【MAB问题】如何将Bandit算法与协同过滤结合使用

推荐系统中最经典的算法是什么？对，是协同过滤，你已经学会抢答了。

是的，协同过滤是推荐系统发展史上浓墨重彩的一笔，其背后的思想简单深刻，在万物互联的今天，协同过滤的威力更加强大。与其说协同过滤是一门技术，不如说是一种方法论，不是机器在为你推荐，而是“集体智慧”在为你推荐。

协同过滤生动地诠释了什么是“物以类聚，人以群分”，你的圈子决定了你能见到的物品，这一点在前面的专栏中已经详细讲过了。但是这背后隐藏了一个重要的问题：是不是会存在信息茧房的问题？

信息茧房

其实作为一名对推荐系统略懂一二的普通海淀群众，我个人就会时常担心，是不是还能看到新的东西，是不是有惊喜。时不时乱点一通，是不是叉掉所有的推荐，让物品的推荐系统崩溃一下，这一切就是为了避免进入信息茧房，在眼前的圈子里苟且。

那么作为推荐系统的开发者，是不是应该做点什么呢？是的，在技术上，Bandit 算法就是一个权衡探索和利用的好方法。如果把它结合传统的协同过滤来做推荐，那么在一定程度上就可以延缓信息茧房的到来，偶遇诗和远方。

我已经和你聊了两篇关于 Bandit 算法的内容，我介绍过普通的 Bandit 算法，也介绍过加入特征信息的 LinUCB 算法，今天，我要介绍的是一个新方法，如何结合协同过滤的群体智慧，与 Bandit 的走一步看一步一起，让两种思想碰撞，也许可以让你的推荐系统与众不同。

这就是 2016 年有人提出的 COFIBA 算法，下面我就开始与你聊聊这种算法。

COFIBA 算法

1 思想

很多的推荐场景中都有两个规律。

相似的用户对同一个物品的反馈可能是一样的。也就是对一个聚类用户群体推荐同一个 item，他们可能都会喜欢，也可能都不喜欢，同样的，同一个用户会对相似的物品反馈也会相同。这实际上就是基于用户的协同过滤基本思想。

在使用推荐系统过程中，用户的决策是动态进行的，尤其是新用户。这就导致无法提前为用户准备好推荐候选，只能“走一步看一步”，是一个动态的推荐过程。这是 Bandit 的算法基本思想。

每一个推荐候选物品，都可以根据用户对其偏好的不同，将用户分成不同的群体。

然后下一次，由用户所在的群体集体帮他预估可能的收益及置信区间，这个集体就有了协同的效果，然后再实时观察真实反馈，回来更新用户的个人参数用于下次调整收益和置信区间，这就有了 Bandit 的思想在里面。

举个例子，如果你的父母给你安排了很多相亲对象，要不要见面去相一下？那需要提前看看每一个相亲对象的资料，每次大家都分成好几派，有说好的，有说再看看的，也有说不行的。

你自己也会是其中一派的一员，每次都是你所属的那一派给你集体打分，因为他们是和你“三观一致的人”“诚不欺我”；这样从一堆资料中挑出分数最高的那个人，你出去见 TA，回来后把实际感觉说给大家听，同时自己心里的标准也有些调整，重新再给剩下的其它对象打分，打完分再去见，

如果要推荐的候选物品较多，需要对物品聚类，就不用按照每一个物品对用户聚类，而是按照每一个物品所属的类簇对用户聚类，如此一来，物品的类簇数目相对于物品数就要大大减少。

2. 细节

基于上述的思想，COFIBA 算法要点摘要如下。

在时刻 t，有一个用户来访问推荐系统，推荐系统需要从已有的候选池子中挑一个最佳的物品推荐给他，然后观察他的反馈，用观察到的反馈来更新挑选策略。

这里的每个物品都有一个特征向量，所以这里的 Bandit 算法是 context 相关的，只不过这里虽然是给每个用户维护一套参数，但实际上是由用户所在的聚类类簇一起决定结果的。

这里依然是用岭回归去拟合用户的权重向量，用于预测用户对每个物品的可能反馈（payoff），这一点和我们上一次介绍的 LinUCB 算法是一样的。

对比上一次介绍的 LinUCB 算法，COFIBA 的不同有两个：

基于用户聚类挑选最佳的物品，即相似用户集体动态决策；

基于用户的反馈情况调整用户和物品的聚类结果。

整体算法过程如下。

在针对某个用户 i，在每一次推荐时做以下事情。

首先计算用户 i 的 Bandit 参数 W，做法和 LinUCB 算法相同，但是这个参数并不直接参与到选择决策中，注意这和 LinUCB 不同，只是用来更新用户聚类。

遍历候选物品，每一个物品已经表示成一个向量 x 了。

每一个物品都对应一个物品聚类类簇，每一个物品类簇对应一个全量用户聚类结果，所以遍历到每一个物品时，就可以判断出当前用户在当前物品面前，自己属于哪个用户聚类类簇，然后把对应类簇中每个用户的 M 矩阵 (对应 LinUCB 里面的 A 矩阵)，b 向量（表示收益向量，对应 LinUCB 里面的 b 向量）加起来，从而针对这个类簇求解一个岭回归参数（类似 LinUCB 里面单独针对每个用户所做），同时计算其收益预测值和置信区间上边界。

每个待推荐的物品都得到一个预测值及置信区间上界，挑出那个上边界最大的物品作为推荐结果。

观察用户的真实反馈，然后更新用户自己的 M 矩阵和 b 向量，只更新每个用户，对应类簇里其他的不更新。

以上是 COFIBA 算法的一次决策过程。在收到用户真实反馈之后，还有两个计算过程：

更新 user 聚类；

更新 item 聚类。

如何更新 user 和 item 的聚类呢？我在这里给出了一个示意图。

解释一下这个图。

(a) 示意图中有 6 个用户，8 个物品，初始化时，用户和物品的类簇个数都是 1。

(b) 在某一轮推荐时，推荐系统面对的用户是 4。推荐过程就是遍历 1～8 每个物品，然后在面对每个物品时，用户 4 在哪个类簇中，把对应类簇中的用户聚合起来为这个物品集体预测收益值置信上边界。这里假设最终物品 5 胜出，被推荐出去了。

在时刻 t，物品一共有 3 个聚类类簇，需要更新的用户聚类是物品 5 对应的用户 4 所在类簇。

更新方式：看看该类簇里面除了用户 4 之外的用户，对物品 5 的预期收益是不是和用户 4 相近，如果是，则保持原来的连接边，否则删除原来的连接边。删除边之后相当于就重新构建了聚类结果。

这里假设新的聚类结果由原来用户 4 所在的类簇分裂成了两个类簇：4 和 5 成一类，6 单独自成一类。

(c) 更新完用户类簇后，被推荐出去的物品 5，它对应的类簇也要更新。

更新方式是：对于每一个和物品 5 还存在连接边的物品，假如叫做物品 j，都有一个对这个物品 j 有相近收益预估值的近邻用户集合，然后看看近邻用户集合是不是和刚刚更新后的用户 4 所在的类簇相同。

是的话，保留物品 5 和物品 j 之间的连接边，否则删除。这里示意图中是物品 3 和物品 5 之间的连接边被删除。

物品 3 变成了孤家寡人一个，不再和任何物品有链接，独立后就给他初始化了一个全新的用户聚类结果：所有用户是一个类簇。

简单来说就是这样：

用协同过滤来少选可以参与决策的用户代表，用 LinUCB 算法来实际执行选择；

根据用户的反馈，调整基于用户和基于物品的聚类结果，即对物品和用户的群体代表做换届选举；

基于物品的聚类如果变化，又进一步改变了用户的聚类结果；

不断根据用户实时动态的反馈来调整用户决策参数，从而重新划分聚类结果矩阵。

COFIBA 算法也很容易实现，GitHub 上就有。原始论文也从理论和实验两方面都证明了它的有效性。

再谈 EE 问题

整个专栏的 Bandit 算法系列，主要是解决推荐系统中的冷启动和 EE 问题。探索和利用这一对矛盾一直客观存在，而 Bandit 算法是公认的一种比较好的解决 EE 问题的方案。

除了 Bandit 算法之外，还有一些其他的探索兴趣的办法，比如在推荐时，随机地去掉一些用户历史行为（特征）。

解决兴趣探索，势必要冒险，势必要面对用户的未知，而这显然就是可能会伤害当前用户价值的：明知道用户肯定喜欢 A，你还偏偏以某个小概率给推荐非 A。

实际上，很少有公司会采用这些理性的办法做探索，反而更愿意用一些盲目主观的方式。究其原因，可能是因为：

互联网产品生命周期短，而探索又是为了提升长期利益的，所以没有动力做；

用户使用互联网产品时间越来越碎片化，探索的时间长，难以体现出探索的价值；

同质化互联网产品多，用户选择多，稍有不慎，用户用脚投票，分分钟弃你于不顾；

已经成规模的平台，红利杠杠的，其实是没有动力做探索的。

基于这些，我们如果想在自己的推荐系统中引入探索机制，需要注意以下几点：

用于探索兴趣的物品，要保证其本身质量，纵使用户不感兴趣，也不至于引起其反感，损失平台品牌价值；

探索兴趣的地方需要产品精心设计，让用户有耐心陪你玩儿；

深度思考，这样才不会做出脑残的产品，产品不会早早夭折，才有可能让探索机制有用武之地。

总结

今天，我介绍完成了 MAB 问题和推荐系统之间的千丝万缕联系。Bandit 算法是一种不太常用在推荐系统的算法，究其原因，是它能同时处理的物品数量不能太多。

但是，在冷启动和处理 EE 问题时，Bandit 算法简单好用，值得一试。当然，这个专栏介绍的所有推荐算法都不是单打独斗最好，而是与其他算法结合使用才能相映生辉，Bandit 算法亦是如此。

今天介绍的 COFIOBA 算法，原理很简单，就是把协同过滤思想引入到了 Bandit 算法中，不再是用户独立决策，而是用户所在的群体共同决策推荐结果。

这样比较问题，也可以加速收敛。不知道对你有没有启发呢？欢迎留言一起讨论。感谢你的收听，我们下次再见。

【MAB问题】简单却有效的Bandit算法

我在之前的文章中表达过，推荐系统的使命就是在建立用户和物品之间的连接。建立连接可以理解成：为用户匹配到最佳的物品；但也有另一个理解就是，在某个时间某个位置为用户选择最好的物品。

推荐就是选择

生活中，你我都会遇到很多要做选择的场景。上哪个大学，学什么专业，去哪家公司，中午吃什么等等。这些事情，都让选择困难症的我们头很大。头大在哪呢？主要是不知道每个选择会带来什么后果。

你仔细想一下，生活中为什么会害怕选择，究其原因是把每个选项看成独一无二的个体，一旦错过就不再来。推荐系统中一个一个单独的物品也如此，一旦选择呈现给用户，如果不能得到用户的青睐，就失去了一个展示机会。如果跳出来看这个问题，选择时不再聚焦到具体每个选项，而是去选择类别，这样压力是不是就小了很多？

比如说，把推荐选择具体物品，上升到选择策略。如果后台算法中有三种策略：按照内容相似推荐，按照相似好友推荐，按照热门推荐。每次选择一种策略，确定了策略后，再选择策略中的物品，这样两个步骤。

那么，是不是有办法来解决这个问题呢？当然有！那就是 Bandit 算法。

MAB 问题

Bandit 算法来源于人民群众喜闻乐见的赌博学，它要解决的问题是这样的。

一个赌徒，要去摇老虎机，走进赌场一看，一排老虎机，外表一模一样，但是每个老虎机吐钱的概率可不一样，他不知道每个老虎机吐钱的概率分布是什么，那么想最大化收益该怎么整？

这就是多臂赌博机问题 (Multi-armed bandit problem, K-armed bandit problem, MAB)，简称 MAB 问题。有很多相似问题都属于 MAB 问题。

假设一个用户对不同类别的内容感兴趣程度不同，当推荐系统初次见到这个用户时，怎么快速地知道他对每类内容的感兴趣程度？这也是推荐系统常常面对的冷启动问题。

假设系统中有若干广告库存物料，该给每个用户展示哪个广告，才能获得最大的点击收益，是不是每次都挑收益最好那个呢？

算法工程师又设计出了新的策略或者模型，如何既能知道它和旧模型相比谁更靠谱又对风险可控呢？

这些问题全都是关于选择的问题。只要是关于选择的问题，都可以简化成一个 MAB 问题。

我在前面的专栏中提过，推荐系统里面有两个顽疾，一个是冷启动，一个是探索利用问题，后者又称为 EE 问题：Exploit－Explore 问题。针对这两个顽疾，Bandit 算法可以入药。

冷启动问题好说，探索利用问题什么意思？

利用意思就是：比较确定的兴趣，当然要用啊。好比说我们已经挣到的钱，当然要花啊。

探索的意思就是：不断探索用户新的兴趣才行，不然很快就会出现一模一样的反复推荐。就好比我们虽然有一点钱可以花了，但是还得继续搬砖挣钱啊，要不然，花完了就要喝西北风了。

Bandit 算法

Bandit 算法并不是指一个算法，而是一类算法。现在就来介绍一下 Bandit 算法家族怎么解决这类选择问题的。

首先，来定义一下，如何衡量选择的好坏？Bandit 算法的思想是：看看选择会带来多少遗憾，遗憾越少越好。在 MAB 问题里，用来量化选择好坏的指标就是累计遗憾，计算公式如图所示。

简单描述一下这个公式。公式有两部分构成：一个是遗憾，一个是累积。求和符号内部就表示每次选择的遗憾多少。

Wopt 就表示，每次都运气好，选择了最好的选择，该得到多少收益，WBi 就表示每一次实际选择得到的收益，两者之差就是“遗憾”的量化，在 T 次选择后，就有了累积遗憾。

在这个公式中：为了简化 MAB 问题，每个臂的收益不是 0，就是 1，也就是伯努利收益。

这个公式可以用来对比不同 Bandit 算法的效果：对同样的多臂问题，用不同的 Bandit 算法模拟试验相同次数，比比看哪个 Bandit 算法的累积遗憾增长得慢，那就是效果较好的算法。

Bandit 算法的套路就是：小心翼翼地试，越确定某个选择好，就多选择它，越确定某个选择差，就越来越少选择它。

如果某个选择实验次数较少，导致不确定好坏，那么就多给一些被选择机会，直到确定了它是金子还是石头。简单说就是，把选择的机会给“确定好的”和“还不确定的”。

Bandit 算法中有几个关键元素：臂，回报，环境。

臂：是每次选择的候选项，好比就是老虎机，有几个选项就有几个臂；

回报：就是选择一个臂之后得到的奖励，好比选择一个老虎机之后吐出来的金币；

环境：就是决定每个臂不同的那些因素，统称为环境。

将这个几个关键元素对应到推荐系统中来。

臂：每次推荐要选择候选池，可能是具体物品，也可能是推荐策略，也可能是物品类别；

回报：用户是否对推荐结果喜欢，喜欢了就是正面的回报，没有买账就是负面回报或者零回报；

环境：推荐系统面临的这个用户就是不可捉摸的环境。

下面直接开始陈列出最常用的几个 Bandit 算法。

1. 汤普森采样算法

第一个是汤普森采样算法。这个算法我个人很喜欢它，因为它只要一行代码就可以实现，并且数学的基础最简单。

简单介绍一下它的原理：假设每个臂是否产生收益，起决定作用的是背后有一个概率分布，产生收益的概率为 p。

每个臂背后绑定了一个概率分布；每次做选择时，让每个臂的概率分布各自独立产生一个随机数，按照这个随机数排序，输出产生最大随机数那个臂对应的物品。听上去很简单，为什么这个随机数这么神奇？

关键在于每个臂背后的概率分布，是一个贝塔分布，先看看贝塔分布的样子：

贝塔分布有 a 和 b 两个参数。这两个参数决定了分布的形状和位置：

当 a+b 值越大，分布曲线就越窄，分布就越集中，这样的结果就是产生的随机数会容易靠近中心位置；

当 a/(a+b) 的值越大，分布的中心位置越靠近 1，反之就越靠近 0，这样产生的随机数也相应第更容易靠近 1 或者 0。

贝塔分布的这两个特点，可以把它分成三种情况：

曲线很窄，而且靠近 1；

曲线很窄，而且靠近 0；

曲线很宽。

这和前面所讲的选择有什么关系呢？你把贝塔分布的 a 参数看成是推荐后得到用户点击的次数，把分布的 b 参数看成是没有得到用户点击的次数。按照这个对应，再来叙述一下汤普森采样的过程。

取出每一个候选对应的参数 a 和 b；

为每个候选用 a 和 b 作为参数，用贝塔分布产生一个随机数；

按照随机数排序，输出最大值对应的候选；

观察用户反馈，如果用户点击则将对应候选的 a 加 1，否则 b 加 1；

注意，实际上在推荐系统中，要为每一个用户都保存一套参数，比如候选有 m 个，用户有 n 个，那么就要保存 2 m n 个参数。

汤普森采样为什么有效呢？解释一下。

如果一个候选被选中的次数很多，也就是 a+b 很大了，它的分布会很窄，换句话说这个候选的收益已经非常确定了，用它产生随机数，基本上就在中心位置附近，接近平均收益。

如果一个候选不但 a+b 很大，即分布很窄，而且 a/(a+b) 也很大，接近 1，那就确定这是个好的候选项，平均收益很好，每次选择很占优势，就进入利用阶段，反之则几乎再无出头之日。

如果一个候选的 a+b 很小，分布很宽，也就是没有被选择太多次，说明这个候选是好是坏还不太确定，那么用它产生随机数就有可能得到一个较大的随机数，在排序时被优先输出，这就起到了前面说的探索作用。

用 Python 实现汤普森采样就一行：

choice = numpy.argmax(pymc.rbeta(1 + self.wins, 1 + self.trials - self.wins))

2.UCB 算法

第二个常用的 Bandit 算法就是 UCB 算法，UCB 算法全称是 Upper Confidence Bound，即置信区间上界。它也为每个臂评分，每次选择评分最高的候选臂输出，每次输出后观察用户反馈，回来更新候选臂的参数。

每个臂的评分公式为.

公式有两部分，加号前面是这个候选臂到目前的平均收益，反应了它的效果，后面的叫做 Bonus，本质上是均值的标准差，反应了候选臂效果的不确定性，就是置信区间的上边界。t 是目前的总选择次数，Tjt 是每个臂被选择次数。

观察这个公式，如果一个候选的被选择次数很少，即 Tjt 很小，那么它的 Bonus 就会较大，在最后排序输出时有优势，这个 Bonus 反映了一个候选的收益置信区间宽度，Bonus 越大，候选的平均收益置信区间越宽，越不确定，越需要更多的选择机会。

反之如果平均收益很大，就是说加号左边很大，也会在被选择时有优势。

这个评分公式也和汤普森采样是一样的思想：

以每个候选的平均收益为基准线进行选择；

对于被选择次数不足的给予照顾；

选择倾向的是那些确定收益较好的候选。

3. Epsilon 贪婪算法

这是一个朴素的算法，也很简单有效，思想有点类似模拟退火，做法如下。

先选一个 (0,1) 之间较小的数，叫做 Epsilon，也是这个算法名字来历。

每次以概率 Epsilon 做一件事：所有候选臂中随机选一个，以 1-Epsilon 的概率去选择平均收益最大的那个臂。

是不是简单粗暴？Epsilon 的值可以控制对探索和利用的权衡程度。这个值越接近 0，在探索上就越保守。

和这种做法相似，还有一个更朴素的做法：先试几次，等每个臂都统计到收益之后，就一直选均值最大那个臂。

4. 效果对比

以上几个算法，可以简单用模拟试验的方式对比其效果，如图所示。

横坐标是模拟次数增加，可以看成随着时间推移，纵坐标就是累积遗憾，越高说明搞砸的次数越多。在模拟后期，基本上各种算法优劣一目了然。从上到下分别是下面几种。

完全随机：就是不顾用户反馈的做法。

朴素选择：就是认准一个效果好的，一直推。

Epsilon 贪婪算法：每次以小概率尝试新的，大概率选择效果好的。

UCB：每次都会给予机会较少的候选一些倾向。

汤普森采样：用贝塔分布管理每一个候选的效果。

UCB 算法和汤普森采样都显著优秀很多。

冷启动

我想，你已经想到了，推荐系统冷启动问题可以用 Bandit 算法来解决一部分。

大致思路如下：

用分类或者 Topic 来表示每个用户兴趣，我们可以通过几次试验，来刻画出新用户心目中对每个 Topic 的感兴趣概率。

这里，如果用户对某个 Topic 感兴趣，就表示我们得到了收益，如果推给了它不感兴趣的 Topic，推荐系统就表示很遗憾 (regret) 了。

当一个新用户来了，针对这个用户，我们用汤普森采样为每一个 Topic 采样一个随机数，排序后，输出采样值 Top N 的推荐 Item。注意，这里一次选择了 Top N 个候选臂。

等着获取用户的反馈，没有反馈则更新对应 Topic 的 b 值，点击了则更新对应 Topic 的 a 值。

总结

今天给你介绍了一种走一步看一步的推荐算法，叫做 Bandit 算法。Bandit 算法把每个用户看成一个多变的环境，待推荐的物品就如同赌场里老虎机的摇臂，如果推荐了符合用户心目中喜欢的，就好比是从一台老虎机中摇出了金币一样。

今天重点介绍的 Bandit 算法有汤普森采样，UCB 算法，Epsilon 贪婪，并且用模拟的方式对比了它们的效果，汤普森采样以实现简单和效果显著而被人民群众爱戴，你需要时不妨首先试试它。

同时，这里留下一个问题给你，今天讲到的 Bandit 算法有哪些不足？欢迎留言和我一起讨论。

【MAB问题】结合上下文信息的Bandit算法

上一篇文章我说到，Bandit 算法用的是一种走一步看一步的思路，这一点看上去非常佛系，似乎一点都不如机器学习深度学习那样厚德载物，但是且慢下结论，先看看我在前面介绍的那几个 Bandit 算法。

UCB 回顾

这些 Bandit 算法，都有一个特点：完全没有使用候选臂的特征信息。特征可是机器学习的核心要素，也是机器学习泛化推广的依赖要素。

没有使用特征信息的 Bandit 算法，问题就在于只能对当前已有的这些候选臂进行选择，对于新加入的候选只能从 0 开始积累数据，而不能借助已有的候选泛化作用。

举个例子，假如有一个用户是鹿晗的粉丝，通过 Bandit 算法有两个鹿晗的广告得到展示，并得到了较好的收益。

那么对于一个新的广告，如果具有鹿晗这个特征，直觉上前两个鹿晗广告的收益信息可以泛化到当前新广告上，新广告就不是完全从 0 开始积累数据，而是有了一定的基础，这样的收敛会更快。

UCB 和汤普森采样这两个 Bandit 算法在实际中表现很好。于是，前辈们就决定送 UCB 去深造一下，让它能够从候选臂的特征信息中学到一些知识。

UCB 就是置信上边界的简称，所以 UCB 这个名字就反映了它的全部思想。置信区间可以简单直观地理解为不确定性的程度，区间越宽，越不确定，反之就很确定。

每个候选的回报均值都有个置信区间，随着试验次数增加，置信区间会变窄，相当于逐渐确定了到底回报丰厚还是可怜。

每次选择前，都根据已经试验的结果重新估计每个候选的均值及置信区间。

选择置信区间上界最大的那个候选。

“选择置信区间上界最大的那个候选”，这句话反映了几个意思：

如果候选的收益置信区间很宽，相当于被选次数很少，还不确定，那么它会倾向于被多次选择，这个是算法冒风险的部分；

如果候选的置信区间很窄，相当于被选次数很多，比较确定其好坏了，那么均值大的倾向于被多次选择，这个是算法保守稳妥的部分；

UCB 是一种乐观冒险的算法，它每次选择前根据置信区间上界排序，反之如果是悲观保守的做法，可以选择置信区间下界排序。

LinUCB

“Yahoo!”的科学家们在 2010 年基于 UCB 提出了 LinUCB 算法，它和传统的 UCB 算法相比，最大的改进就是加入了特征信息，每次估算每个候选的置信区间，不再仅仅是根据实验，而是根据特征信息来估算，这一点就非常的“机器学习”了。

在广告推荐领域，每一个选择的样本，由用户和物品一起构成，用户特征，物品特征，其他上下文特征共同表示出这个选择，把这些特征用来估计这个选择的预期收益和预期收益的置信区间，就是 LinUCB 要做的事情。

LinUCB 算法做了一个假设：一个物品被选择后推送给一个用户，其收益和特征之间呈线性关系。在具体原理上，LinUCB 有一个简单版本以及一个高级版本。简单版本其实就是让每一个候选臂之间完全互相无关，参数不共享。高级版本就是候选臂之间共享一部分参数。

先从简单版本讲起。

还是举个例子，假设现在两个用户，用户有一个特征就是性别，性别特征有两个维度，男，女。现在有四个商品要推荐给这两个用户，示意如下。

两个用户就是 Bandit 算法要面对的上下文，表示成特征就是下面的样子。

每一次推荐时，用特征和每一个候选臂的参数去预估它的预期收益和置信区间。

xi×θjxi×θj，这就是给男性用户推荐剃须刀，给女性用户推荐口红，即使是新用户，也可以作出比随机猜测好的推荐，再观察用户是否会点击，用点击信息去更新那个被推荐了的候选臂的参数。

这里的例子还简化了一个地方，就是没有计算置信区间，这是 UCB 的精髓。下面来补上。

假如 D 是候选臂是候选臂在 m 次被选择中积累的特征，相当于就是 m 条样本，特征维度是 d，所以 D 是一个矩阵，维度是 m x d。

这 m 次被选择，每次得到用户的点击或者没点击，把这个反馈信息记录为一个 m x 1 的向量，叫做 C。所以这个候选臂对应的参数就是 d x 1 的向量，d 就是特征维度数，记录为一个戴帽子的西塔，θ^θ^。

按照 LinUCB 认为，参数和特征之间线性相乘就应该得到收益：

Dm×d×θd×1^=Cm×1Dm×d×θd×1^=Cm×1

你看 D 也知道，C 也知道，要求 θθ ，这就很简单了。

θ^d×1=(DTm×d)−1Cm×1θ^d×1=(Dm×dT)−1Cm×1

但是由于数据稀疏，实际上求参数西塔时不会这样简单粗暴，而是采用岭回归的方法，给原始特征矩阵加上一个单位对角矩阵后再参与计算：

θ^d×1=(DTm×dDm×d+Id×d)−1DTm×dCm×1θ^d×1=(Dm×dTDm×d+Id×d)−1Dm×dTCm×1

每一个候选臂都像这样去更新它的参数，同时，得到参数后，在真正做选择时，用面对上下文的特征和候选臂的参数一起。

除了估算期望收益，还要计算置信区间的上边界，如果 x 是上下文特征，则期望收益和置信上边界的计算方法分别是下面的样子。

期望收益：

r^=xTd×1θ^d×1r^=xd×1Tθ^d×1

置信区间上边界：

b^=αxTd×1(DTm×dDm×d+Id×d)−1xd×1−−−−−−−−−−−−−−−−−−−−−−−−−√b^=αxd×1T(Dm×dTDm×d+Id×d)−1xd×1

这两个计算结果都是标量数值。置信区间计算公式虽然看起来复杂，实际上反应的思想也很直观，随着被选择次数的增加，也就是 m 增加，这个置信上边界是越来越小的。

每一次选择时给每一个候选臂都计算这两个值，相加之后选择最大那个候选臂输出，就是 LinUCB 了。

刚才说到了岭回归（ridge regression），这里多说一句，岭回归主要用于当样本数小于特征数时，对回归参数进行修正。对于加了特征的 Bandit 问题，正好符合这个特点：试验次数（样本）少于特征数。

信息量有点大，我在这里再一次列出 LinUCB 的重点。

LinUCB 不再是上下文无关地，像盲人摸象一样从候选臂中去选择了，而是要考虑上下文因素，比如是用户特征、物品特征和场景特征一起考虑。

每一个候选臂针对这些特征各自维护一个参数向量，各自更新，互不干扰。

每次选择时用各自的参数去计算期望收益和置信区间，然后按照置信区间上边界最大的输出结果。

观察用户的反馈，简单说就是“是否点击”，将观察的结果返回，结合对应的特征，按照刚才给出的公式，去重新计算这个候选臂的参数。

当 LinUCB 的特征向量始终取 1，每个候选臂的参数是收益均值的时候，LinUCB 就是 UCB。

说完简单版的 LinUCB，再看看高级版的 LinUCB。与简单版的相比，就是认为有一部分特征对应的参数是在所有候选臂之间共享的，所谓共享，也就是无论是哪个候选臂被选中，都会去更新这部分参数。

构建特征

LinUCB 算法有一个很重要的步骤，就是给用户和物品构建特征，也就是刻画上下文。

在“Yahoo！”的应用中，物品是文章。它对特征做了一些工程化的处理，这里稍微讲一下，可供实际应用时参考借鉴。

首先，原始用户特征有下面几个。

人口统计学：性别特征（2 类），年龄特征（离散成 10 个区间）。

地域信息：遍布全球的大都市，美国各个州。

行为类别：代表用户历史行为的 1000 个类别取值。

其次，原始文章特征有：

URL 类别：根据文章来源分成了几十个类别。

编辑打标签：编辑人工给内容从几十个话题标签中挑选出来的。

原始特征向量先经过归一化，变成单位向量。

再对原始用户特征做第一次降维，降维的方法就是利用用户特征和物品特征以及用户的点击行为去拟合一个矩阵 W。

ϕTuWϕTaϕuTWϕaT

就用逻辑回归拟合用户对文章的点击历史，得到的 W 直觉上理解就是：能够把用户特征映射到物品特征上，相当于对用户特征降维了，映射方法是下面这样。

ψu=ϕTuWψu=ϕuTW

这一步可以将原始的 1000 多维用户特征投射到文章的 80 多维的特征空间。

然后，用投射后的 80 多维特征对用户聚类，得到 5 个类，文章页同样聚类成 5 个类，再加上常数 1，用户和文章各自被表示成 6 维向量。

接下来就应用前面的 LinUCB 算法就是了，特征工程依然还是很有效的。

总结

今天我和你分享了一种上下文有关的 Bandit 算法，叫做 LinUCB，它有这么几个优点：

由于加入了特征，所以收敛比 UCB 更快，也就是比 UCB 更快见效；

各个候选臂之间参数是独立的，可以互相不影响地更新参数；

由于参与计算的是特征，所以可以处理动态的推荐候选池，编辑可以增删文章；

当然，LinUCB 以及所有的 Bandit 算法都有个缺点：同时处理的候选臂数量不能太多，不超过几百个最佳。因为每一次要计算每一个候选臂的期望收益和置信区间，一旦候选太多，计算代价将不可接受。

LinUCB 只是一个推荐框架，可以将这个框架应用在很多地方，比如投放广告，为用户选择兴趣标签，你还可以发挥聪明才智，看看它还能用来解决什么问题，欢迎留言一起交流。

【其他应用算法】实用的加权采样算法

今天来讲一个非常轻松的话题，这个话题看似和推荐系统没什么关系，但肯定有用，只是在别的推荐系统相关话题里都没人会提。

一些场景

还记得前面讲到的用户画像吗？想象一个场景：你经过辛辛苦苦抓数据，清洗数据，收集用户行为，目的就是给用户计算兴趣标签。

这时候你可能会遇到一个两难的问题：如果给用户计算出兴趣标签的权重了，那应该保留多少标签呢？

保留太多的话，每次召回候选集时，计算复杂度可不低，只保留少部分吧，那真是手心手背都是肉，生怕丢弃的标签才是用户的真爱。

怎么办？这时候，你需要的一个简单的加权采样算法，每次召回时并不使用全部用户标签，而是按照权重采样一部分标签来使用，这样做的好处当然很明显：

大大减少召回时的计算复杂度；

可以保留更多的用户标签；

每次召回计算时还能有所变化；

虽然有变化，但是依然受标签的权重相对大小约束。

加权采样的应用不只这一个地方，比如在热门排行榜展示时，也可以用加权采样，而不仅仅按照排行榜分数顺序展示，采用加权采样的展示方法，会让排行榜每次刷新都略有变化，人民群众也会更加喜闻乐见。

下面介绍几种常用的加权采样算法及其原理，供你日常随手拿来使用。

加权采样

加权采样有两种情况，一种是能够已知全部样本的个数。这需要遍历整个样本，比如说用户标签采样输出，那么每次采样时仍然需要遍历所有的标签，来依次决定每一个标签输出的概率。

另一种是不知道总量样本是多大，或者总量很大，以至于你不愿意全部遍历之后再输出采样结果，这样的数据就是数据流，对应的就是流采样。

下面分别讲这两种采样方法。

1. 有限数据集

等概率采样的方法非常简单，任意编程语言中都有伪随机数实现，就不在本文讨论范围内了。

现在假设你有用户标签若干，每一个标签都有个权重 w，权重高低反映了用户对这个标签的感兴趣程度高低。你希望每次输出一部分标签用于召回推荐候选集，每次输出时都不一样，但是又能反映用户标签的权重，输出的概率和权重成正比。

这时候你需要一个公式：

Si=R1wiSi=R1wi

解释一下这个公式：

wi 是每个样本的权重，比如用户标签权重；

R 是遍历每个样本时产生的 0 到 1 之间的随机数；

Si 就是每个样本的采样分数

遍历之后，按照采样分数排序，输出前 k 个结果就是你得到的采样结果。可以编程简单做个模拟，比如下面有这样几个简单样本。

模拟 10000 次后，三个样本被采样次数如下：

你可以看到，每个样本采样概率和它的权重成正比。

还有另一种加权采样方法，是利用指数分布。

我先给忘记了指数分布的人复习一下什么是指数分布。

假设你到银行去办业务，每个人办理业务的时间是不确定的，那每个人办理业务时间的概率分布就是指数分布，用教科书上的话说，就是两个事件发生的时间间隔。

指数分布的概率密度函数是：

指数分布的参数 Lambda，它的倒数，1λ1λ 就是事件发生时间间隔的期望。把指数分布的这个意义放进标签中来考虑，标签的权重其实反映一个直觉：权重越大的标签，用户消费它就越频繁，也就是间隔时间就会短。

所以根据这个原理，就有另一个加权采样的办法：为每一个标签构造一个指数分布随机数，这个指数分布的参数 Lambda 就是标签权重，然后用这个指数分布的产生一个随机数，再输出随机数最大的 k 个标签作为采样结果, 是不是很完美？

还是上面的权重，再来模拟 10000 次。

依然完美符合权重的相对大小。

2. 无限数据集

上面的两种采样都是针对有限数据集的，也就是采样之前都要遍历一遍所有样本。那么如果面对的数据集无限大，或者不知道多大时，该怎么做加权采样呢？这就要讲到另一个采样算法了，名字叫蓄水池采样（也叫蓄水池抽样）。

蓄水池采样可以用在推荐系统的哪些地方呢？比如可以再模型融合之后加一层蓄水池抽样，或者在召回阶段加一层蓄水池采样，这样在不影响整个推荐流程和转化概率的前提下，降低计算复杂度和提升推荐多样性。

或者，在线阶段要使用用户的反馈行为做实时推荐，对于不同的用户，活跃程度不同，产生的反馈行为数量不同，你也可以用蓄水池采样，为每个用户取出固定数量的行为用于更新推荐结果。

下面，我先讲蓄水池采样，再讲加权蓄水池采样。

假如有一个数据集合，一共有 n 条，要从中采样取出 k 个，那么每个样本被选中的概率就是 knkn 。蓄水池采样的做法是：

直接先取出前 k 个样本留着，这 k 个就是随时准备最终要输出的；

从第 k+1 个开始，每个都以 knkn 的概率去替换那留着的 k 个样本中的一个。

这个过程，随时可以取用那个 k 个集合作为输出结果，任意时刻，当总样本遍历了 n 个时，他们的概率都是 knkn 。这就是蓄水池采样，蓄水池采样，顾名思义，k 个元素的样本集合就是个蓄水池，是任意时刻的采样结果，可以随时取用。

现在回到我们今天的主题来，实际上更需要的是加权蓄水池采样。加权蓄水池采样利用的依然是在前面说的第一种加权采样方法，只不过结合了蓄水池采样的思想。

要从大数据集中采样 k 个，其具体做法是这样的：

为每一个样本生成一个分数，分数还是用这个公式 Si=R1wiSi=R1wi;

如果结果不足 k 个，直接保存到结果中；

如果结果中已经有 k 个了，如果 SiSi 比已有的结果里最小那个分数大，就替换它。

总结

今天介绍的算法非常简单，但是在推荐系统中有很多的用途。尤其是面对的数据需要采样、需要有所变化时，加权采样本质上来说就是让权重影响采样概率。

前面的几种加权采样算法，都是让采样概率和权重成正比，这意味着你的样本权重之间的关系要合理。

那么，请思考另一个问题，如果你的样本权重有正有负，该如何加权采样呢？欢迎留言一起讨论。

感谢你的收听，我们下次再见。

【其他应用算法】推荐候选池的去重策略

今天依然要讲到两个问题，它们看似和推荐系统没有必然关系，但实际上，在你构建自己的推荐系统的时候，不可避免地会遇到这两个问题。

去重是刚需

在推荐系统中，有一个刚需就是去重，那么说在哪些地方有去重的需求呢？

主要是在两个地方：一个是内容源去重，另一个是不重复给用户推荐。

先说说内容源的去重，这部分以前几年的图文信息流推荐为典型的例子。

如果一个平台自己不生产内容，只是做内容搬运和聚合分发，那么从大量第三方的内容生产处抓取内容，就难免遇到相似甚至重复的内容。这就需要对内容做一个重复检测了。

对内容做重复检测，直观的思路是分词，然后提取关键词，再两两计算词向量之间的距离，距离小于一定阈值后就判定为重复。然而，这对于海量内容，比如几千万以上的内容来说简直就是灾难。

其实，内容源去重并不是仅在推荐系统中才首次出现，这早在搜索引擎时代就是一个刚需了，搜索引擎把整个互联网的网页都下载到自己的服务器上，这时，重复冗余的内容就需要被检测出来。

另一个需求是在内容阅读类推荐场景下，给用户推荐的内容不要重复，推荐过的内容就不再出现在推荐候选集中。

在你刷一个信息流产品时，不断看到重复的内容，想必不是使用感很好的一件事。因为以抓取作为主要内容来源的信息流产品，不同于社交网站上用户自发产生内容，除非遇到用户恶意发送，否则后者是不容易重复的。

以上两个场景，需要在你打造自己的推荐系统时予以考虑和应对。今天就介绍两种最常见的去重算法，两者有相通之处也有不同的之处，听我慢慢说来。

Simhash

内容重复检测，是搜索引擎公司最先遇到的，所以 Google 在 07 年公开了他们内部的内容重复检测算法，这个算法简单有效，甚至造福了今天的信息流推荐产品。

对于很长的内容，如果只是检测绝对重复，也就是说完全一模一样的那种情况，那么使用 MD5 这样的信息指纹方法非常高效，无需再去分词、提取关键词和计算关键词向量之间的距离。

我们直接将原始的内容映射为一个短字符串，这个短字符串就是原始内容的指纹，虽然不是绝对保证和原始内容一一映射，但是不同内容能得到相同指纹的概率非常小。

只是这种信息指纹的方法有个非常明显的坏处就是，哪怕原始内容改一个字，得到的信息指纹就会截然不同。

这就没法愉快地玩耍了，你一定希望的是只要主要内容不变，就算一些不太重要的词句不同，也仍然可以得到相近甚至相同的指纹。这才能更加灵活地进行内容重复检测。是否有这样的算法？有，就是 Simhash。

Simhash 核心思想也是为每个内容生成一个整数表示的指纹，然后用这个指纹去做重复或者相似的检测。下面这个示意图说明了 Simhash 如何把一个原始内容表示成一个整数指纹。

好，现在详细说明一下这个过程。

首先，对原始内容分词，并且计算每个词的权重；

对每个词哈希成一个整数，并且把这个整数对应的二进制序列中的 0 变成 -1，1 还是 1，得到一个 1 和 -1 组成的向量；

把每个词哈希后的向量乘以词的权重，得到一个新的加权向量；

把每个词的加权向量相加，得到一个最终向量，这个向量中每个元素有正有负；

把最终这个向量中元素为正的替换成 1，为负的替换成 0，这个向量变成一个二进制位序列，也就是最终变成了一个整数。

最终这个整数就代表了原始的内容。这个 Simhash 奇妙在哪呢？

看这个示意图中，我故意加了一个不太重要的词“了”，它的权重是 1，对应的加权向量元素不是 1 就是 -1，在上述的第四步中，如果这个词对应的向量缺少了，其实根本不影响最终得到那个整数，因为它很难改变最终向量元素的正负。这就是为什么那些不太重要的词不影响内容之间的重复检测。

Simhash 为每一个内容生成一个整数指纹，其中的关键是把每个词哈希成一个整数，这一步常常采用 Jenkins 算法。这里简单示意的整数只有 8 个二进制位，实际上可能需要 64 个二进制位的整数，甚至范围更大。

得到每个内容的 Simhash 指纹后，可以两两计算汉明距离，比较二进制位不同个数，其实就是计算两个指纹的异或，异或结果中如果包含 3 个以下的 1，则认为两条内容重复。

为了高效，也可以直接认为指纹相同才重复，视情况而定。

Bloomfilter

除了内容重复检测，还有一个需求是防止已经推荐的内容被重复推荐。这个刚需和上述内容重复相比，最大的不同就是过滤对象不同，上述 Simhash 过滤对象是内容本身，而这里则一般是内容的 ID。

内容的 ID 一般是用一个 UUID 表示，是一个不太长的字符串或者整数。

对于这类形如模式串的去重，显然可以用单独专门的数据库来保存，为了高效，甚至可以为它建上索引。

但对于用户量巨大的情况下，这个做法对存储的消耗则不可小看。实际上，解决这类看一个字符串在不在一个集合中的问题，有一个有点老但很好用的做法，就是 Bloomfilter，有时候也被称为布隆过滤器。

布隆过滤器的原理也要用到哈希函数。它包含两部分：一个很长的二进制位向量，和一系列哈希函数。Bloomfilter 是一个很巧妙的设计，它先把原始要查询的集合映射到一个长度为 m 的二进制位向量上去，它映射的方法是：

设计 n 个互相独立的哈希函数，准备一个长度为 m 的二进制向量，最开始全是 0；

每个哈希函数把集合内的元素映射为一个不超过 m 的正整数 k，m 就是二进制向量的长度；

把这个二进制向量中的第 k 个位置设置为 1；也就是一个元素会在二进制向量中对应 n 个位置为 1。

我放了一个示意图。

这个示意图中，原始的模式串经过三个互相独立的哈希函数，映射到 8 位二进制向量中的三个位置了。

原始的模式串集合经过这样的处理后，就得到一个很大的二进制向量。在应用阶段时，假如来了一个模式串 s，需要查询是否在这个集合中，也需要经过同样的上述步骤。

每个哈希函数对这个模式串 s 哈希后都得到一个整数，看看这个整数在二进制向量中所指示的位置是不是 1，如果每个哈希函数所指示的位置都是 1，就说明模式串 s 已经在集合中了。

需要说明的是，Bloomfilter 也并不是百分之百保证的，有很小的概率把原本不存在集合中的模式串判断为存在。这样就会造成那些明明还没有推荐给用户的内容 ID 就再也不会推荐给用户了，当然，这个小概率是可以承受的。

总结

好了，今天介绍了两种去重算法。在推荐系统中，虽然我们十分关心推荐匹配的效果，但是别忘了，对原始内容的挖掘和清洗往往更加重要。这其中就包括对重复内容的检测。

两种去重策略都是牺牲一点误伤的概率换得大幅度的效率提升，具体的做法都是要借助哈希函数。只是哈希函数的结果在两个算法中有不同的处理手段，Simhash 是加权，Bloomfilter 则是用来做寻址。

最后，留给你一个思考题，由于今天的内容比较简单，留给你思考题也简单，请你想一想，如果要从 Bloomfilter 中去掉一个元素，该怎么做？欢迎给我留言，我们一起讨论。

感谢你的收听，我们下次再见。

【其他应用算法】构建一个科学的排行榜体系

前面的专栏文章中，我从最常见的内容推荐开始讲起，直到讲到了最复杂的深度学习在推荐系统中的应用原理，这些推荐算法都有一个特点：智能。

所谓智能，就是带有学习性质，能够和复杂的用户端形成互动，在互动过程中，算法参数得到更新和进化。

但是，智能这个高大上的词语，一定要以数据为前提的，我在专栏的第二篇文章中就和你透露过，推荐系统中有一个顽疾就是冷启动，冷启动就是没有数据，没有数据怎么和用户玩呢？

一个新用户来了，什么数据都还没有，推荐系统对其一无所知。这时候，你就需要一个排行榜了。

为什么要排行榜

排行榜，又名热门榜，听上去似乎是一个很常见的东西，原来它也算是推荐算法的一员？是的，它不但是，并且非常重要，而且其中也有不少的学问。

那么说排行榜到底有哪些用处呢？

排行榜可以作为解决新用户冷启动问题的推荐策略。这个不难理解，当一个新用户刚注册时，可以把最近产品中热门的物品推荐给他。

排行榜可以作为老用户的兴趣发现方式。即使是老用户，也可以在享受个性化推荐的同时去浏览热门的物品，从中看看哪些感兴趣，哪些不感兴趣，这些行为都是补充或者更新用户兴趣的数据来源。

排行榜本身就是一个降级的推荐系统。推荐系统本身是一个软件，因此也会有出现问题的时候，也会有推荐不出来的时候，这个时候考虑到服务的可用性，用排行榜作为一种兜底策略，可以避免推荐位开天窗。

今天，我就和你聊聊如何根据自己的产品特点构建一个合理的排行榜。

排行榜算法

最简单的排行榜，就是直接统计某种指标，按照大小去排序。在社交网站上，按照点赞数、转发数、评论数去排序，这是一种最常见、最朴素的排行榜。

类似的做法还有，在电商网站上按照销量去排序。

这样的做法也算是推荐算法？当然我确实很难说它不是，因为确实简单，容易上线运行，但我只能说这样做不靠谱，不靠谱的原因在于以下的几个问题。

非常容易被攻击，也就是被刷榜；

马太效应一直存在，除非强制替换，否则一些破了纪录的物品会一直占据在榜单中；

不能反映出排行榜随着时间的变化，这一点和马太效应有关。

既然朴素的排行榜有这些弊端，那么就针对他们来一一设计应对措施。

1. 考虑时间因素

接下来，我要把用户给物品贡献的行为看做是用户在投票，这个很容易理解，好像热门的东西都是大多数人投票民主选举出来的。

排行榜中的物品，你可以想象它们每一个都是炙手可热的，都有一定的温度，那么这个温度按照热力学定律来讲，随着时间推移就一定会耗散到周围，温度就会下降。

或者，把排行榜想象成一个梯子，每个物品都在奋力往上爬，他们的动力来自用户的手动投票，物品本身都要承受一定的重力，会从梯子上掉下来，用户投票可以抵挡部分重力，投票数不及时或者不够，排行榜上的物品就会掉下来。

把这个规律反映在排行榜分数计算公式中，就比简单统计数量，再强制按照天更新要科学得多。Hacker News 计算帖子的热度就用到了这个思想，它们的做法用公式表达是下面这个样子。

P−1(T+2)GP−1(T+2)G

公式中三个字母分别代表如下意义：

P：得票数，去掉帖子作者自己投票。

T：帖子距离现在的小时数，加上帖子发布到被转帖至 Hacker News 的平均时长。

G：帖子热度的重力因子。

公式中，分子是简单的帖子数统计，一个小技巧是去掉了作者自己的投票。分母就是将前面说到的时间因素考虑在内，随着帖子的发表时间增加，分母会逐渐增大，帖子的热门程度分数会逐渐降低。

其中，重力因子的选择根据情况而定，重力因子越大，帖子的热度衰减越快，不同的重力因子对比如下图所示。

可以看到，重力因子越大，衰减越快。

再看一下，相同重力因子选择的情形下，不同的得票数的对比。

这这个示意图可以看到，这个公式仍然能够反映出相同时间的帖子之间的相对热度差别。

另一个考虑时间因素的排行榜算法是牛顿冷却定律。物品受关注度如同温度一样，不输入能量的话它会自然冷却，而且物体的冷却速度和其当前温度与环境温度之差成正比。将这一定律表述为公式就是下面的样子：

T(t)=H+Ce−αtT(t)=H+Ce−αt

公式中字母的意义如下。

H：为环境温度，可以认为是平均票数，比如电商中的平均销量，由于不影响排序，可以不使用。

C：为净剩票数，即时刻 t 物品已经得到的票数，也就是那个最朴素的统计量，比如商品的销量。

t：为物品存在时间，一般以小时为单位。

αα ：是冷却系数，反映物品自然冷却的快慢。

问题来了，这个反映物品自然冷却快慢的 αα 该如何确定呢？有一个更直观的办法。假如一个物品在时间过去 B 个单位后，因为增加了 A 个投票数，而保持了热门程度不变，那这样的话 αα 应该是多少呢？简单把这个描述列成方程就是下面的样子。

Ce−αt=(C+A)e−α(t+B)Ce−αt=(C+A)e−α(t+B)

可以解得。

α=1Bln(1+AC)α=1Bln(1+AC)

用这个公式加上自己产品的要求来确定 alphaalpha 就容易得多，假如按照 B = 24，也就是过一天来看，我来举几个例子。

你可以在自己的产品中，设定一个假设，然后计算出相应的 αα 来。

2. 考虑三种投票

前面的热度计算方法，只考虑用户投票和用户弃权两种，虽然这种情况很常见，但是还有一些产品会存在运行用户投反对票的情形，比如问答网站中对答案的投票，既可以赞成，又可以反对。在这样的情形下，一般这样来考虑：

同样多的总票数，支持赞成票多的，因为这符合平台的长期利益；

同样多的赞成票数，支持最有价值的，同样这符合平台长期利益。

以国外某著名程序员问答网站为例，你就不要打听到底是哪个网站了，这个不重要，下面看一下他们对热门问题的热度计算公式：

(log10Qviews)×4+Qanswers×Qscore5+∑iAscorei(Qage2+Qupdated2+1)1.5(log10Qviews)×4+Qanswers×Qscore5+∑iAscorei(Qage2+Qupdated2+1)1.5

这个公式有点复杂，其中的元素意义如下：

Qviews: 问题的浏览次数。

Qanswers: 问题的回答数。

Qscore：问题的得分（赞成数 - 反对数）。

Ascore：答案的得分。

Qage: 问题发布距离当前的时间。

Qupdated: 问题最后一次修改距离当前的时间。

这个问题热门程度计算方式，也考虑了时间因素。分母反映了问题的陈旧程度，修改问题可以让问题不要衰老过快。分子有三部分构成：

左边是问题的浏览量，反映了问题的受关注程度；

中间是问题的回答量和问题本身的质量分数的乘积，高质量、回答多的问题占优势；

右边是答案的总质量分。

3. 考虑好评的平均程度

前面两种排行榜分数计算法，都是以用户投票的绝对数量作为核心的，那么换个思路来看，从比例来看也是可以的。这也是一些点评网站常常采纳的模式，比如电影点评网站通常会有一个 Top250，这也是一种排行榜，以好评比例作为核心来计算排行榜分数。下面来看看这种排行榜。

一个经典的好评率估算公式，叫做威尔逊区间，它这样估算物品的好评率：

p^+12nz21−α2±z1−α2p^(1−p^)n+z21−α24n2−−−−−−−−−−−√1+1nz2_1−α2p^+12nz1−α22±z1−α2p^(1−p^)n+z1−α224n21+1nz2_1−α2

实在是对不起你啊，又给你搞出了一个超级复杂的公式。实际上，你照着公式中所需的元素去统计就可以计算出排行榜了。我解释一下这个公式中所需的元素，你就可以照着去搬砖了，可以不必理解其中的原理。

p^p^ 就是好评率，比如一百个点评的商品，99 个给了好评，那么这个值就是 0.99

z1−α2z1−α2 是一个置信水平为 alphaalpha Z 统计量，这个查表就可以得到。

威尔逊区间考虑了评价的样本数，样本不足时，置信区间很宽，样本很足时，置信区间很窄。那么这个统计量有哪些应用呢，比如说下面的几个情况。

多大比例的人们会采取某种行为？

多大比例的人认为这是一个 Spam？

多大比例的人认为这是一个“值得推荐的”物品呢？

当你为每一个物品都计算一个威尔逊区间后，你可以采用前面讲到的 Bandit 算法，类似 UCB 的方式取出物品，构建成一个略带变化的排行榜。

最后，为你呈上某电影点评网站为电影排行榜计算分数的公式，它是另一种对好评率的应用，针对评分类型数据的排行榜。

vv+mR+mv+mCvv+mR+mv+mC

这个排行榜计算公式，也有一个响当当的名字，叫做“贝叶斯平均”。其中的元素意义描述如下：

R，物品的平均得分，这个很简单，有多少人评分，把他们评分加起来除以人数就是了；

v，参与为这个物品评分的人数；

m，全局平均每个物品的评分人数；

C，全局平均每个物品的平均得分；

别看这个公式简单，它反映了这么几个思想在里面：

如果物品没多少人为它投票，也就是评价人数不足，那么 v 就很小，m 就很大，公式左边就很小，右边就很大，于是总分算出来很接近右边部分，也就是接近全局平均分 C；

如果物品投票人数很多，那么 v 很大，m 很小，分数就接近它自己的平均分 R。

这个公式的好处是：所有的物品，不论有多少人为它评分，都可以统一地计算出一个合理的平均分数，它已经被国内外电影评分网站采纳在自己的排行榜体系中，当然，它们肯定各自都有根据实际情况的修改。

总结

今天，我主要讲到了三种构建排行榜分数的算法，因为排行榜的意义重大，所以不可以太随便对待，甚至应该比常规的推荐算法更加细心雕琢。

一个最最朴素的排行榜就是统计一下销量、阅读量等，但要让排行榜反映出热度的自然冷却，也要反映出用户赞成和反对之不同，还要反映出用户评价的平均水平。

你不要被前面那个非常大的一坨公式所吓倒，实际上，它统计起来很方便。这些公式都是在实际生产中演化而来的，你根据这些原理结合自己实际遇到的问题，也可以设计出符合自己业务要求的排行榜公式。

最后，提一个小问题给你，对于最后一个排行榜公式，如何改进才能防止水军刷榜？你可以给我留言，我们一起讨论。感谢你的收听，我们下次再见。

【内容推荐】从文本到用户画像有多远

前面，我和你聊过了不要把用户画像当成银弹，也不要觉得一无是处。对于一个早期的推荐系统来说，基于内容推荐离不开为用户构建一个初级的画像，这种初级的画像一般叫做用户画像（User Profile），一些大厂内部还习惯叫做 UP，今天我就来讲一讲从大量文本数据中挖掘用户画像常常用到的一些算法。

从文本开始

用户这一端比如说有：

注册资料中的姓名、个人签名；

发表的评论、动态、日记等；

聊天记录（不要慌，我举个例子而已，你在微信上说的话还是安全的）。

物品这一端也有大量文本信息，可以用于构建物品画像（ Item Profile ），并最终帮助丰富 用户画像（User Profile），这些数据举例来说有：

物品的标题、描述；

物品本身的内容（一般指新闻资讯类）；

物品的其他基本属性的文本。

文本数据是互联网产品中最常见的信息表达形式，数量多、处理快、存储小，因为文本数据的特殊地位，所以今天我专门介绍一些建立用户画像过程中用到的文本挖掘算法。

构建用户画像

要用物品和用户的文本信息构建出一个基础版本的用户画像，大致需要做这些事：

1. 把所有非结构化的文本结构化，去粗取精，保留关键信息；
2. 根据用户行为数据把物品的结构化结果传递给用户，与用户自己的结构化信息合并。

第一步最关键也最基础，其准确性、粒度、覆盖面都决定了用户画像的质量。仿佛如果真的要绘制一个用户的模样，要提前给他拍照，这个拍照技术决定了后面的描绘情况，无论是采用素描、油画、工笔还是写意。这一步要用到很多文本挖掘算法，稍后会详细介绍。

第二步会把物品的文本分析结果，按照用户历史行为把物品画像（ Item Profile ）传递给用户。你也许会问：传递是什么意思？没关系，这个稍后我会介绍。

一、结构化文本

我们拿到的文本，常常是自然语言描述的，用行话说，就是“非结构化”的，但是计算机在处理时，只能使用结构化的数据索引，检索，然后向量化后再计算；所以分析文本，就是为了将非结构化的数据结构化，好比是将模拟信号数字化一样，只有这样才能送入计算机，继续计算。这个很好理解，不多解释。

从物品端的文本信息，我们可以利用成熟的 NLP 算法分析得到的信息有下面几种。

关键词提取：最基础的标签来源，也为其他文本分析提供基础数据，常用 TF-IDF 和 TextRank。

实体识别：人物、位置和地点、著作、影视剧、历史事件和热点事件等，常用基于词典的方法结合 CRF 模型。

内容分类：将文本按照分类体系分类，用分类来表达较粗粒度的结构化信息。

文本 ：在无人制定分类体系的前提下，无监督地将文本划分成多个类簇也很常见，别看不是标签，类簇编号也是用户画像的常见构成。

主题模型：从大量已有文本中学习主题向量，然后再预测新的文本在各个主题上的概率分布情况，也很实用，其实这也是一种聚类思想，主题向量也不是标签形式，也是用户画像的常用构成。

嵌入：“嵌入”也叫作 Embedding，从词到篇章，无不可以学习这种嵌入表达。嵌入表达是为了挖掘出字面意思之下的语义信息，并且用有限的维度表达出来。

下面我来介绍几种常用的文本结构化算法。

1 TF-IDF

TF 全称就是 Term Frequency，是词频的意思，IDF 就是 Inverse Document Frequency 是逆文档频率的意思。TF-IDF 提取关键词的思想来自信息检索领域，其实思想很朴素，包括了两点：在一篇文字中反复出现的词会更重要，在所有文本中都出现的词更不重要。非常符合我们的直觉，这两点就分别量化成 TF 和 IDF 两个指标：

TF，就是词频，在要提取关键词的文本中出现的次数；

IDF，是提前统计好的，在已有的所有文本中，统计每一个词出现在了多少文本中，记为 n，也就是文档频率，一共有多少文本，记为 N。

IDF 就是这样计算：计算过程为：词出现的文档数加 1，再除总文档数，最后结果再取对数。

IDF 的计算公式有这么几个特点：

所有词的 N 都是一样的，因此出现文本数越少 (n) 的词，它的 IDF 值越大；

如果一个词的文档频率为 0，为防止计算出无穷大的 IDF，所以分母中有一个 1；

对于新词，本身应该 n 是 0，但也可以默认赋值为所有词的平均文档频率。

计算出 TF 和 IDF 后，将两个值相乘，就得到每一个词的权重。根据该权重筛选关键词的方式有：

给定一个 K，取 Top K 个词，这样做简单直接，但也有一点，如果总共得到的词个数少于 K，那么所有词都是关键词了，显然这样做不合理；

计算所有词权重的平均值，取在权重在平均值之上的词作为关键词；

另外，在某些场景下，还会加入以下其他的过滤措施，如：只提取动词和名词作为关键词。

2 TextRank

TextRank 这个名字看上去是不是和著名的 PageRank 有点亲戚关系？是的，TextRank 是 PageRank 的私生子之一，著名的 PageRank 算法是 Google 用来衡量网页重要性的算法，TextRank 算法的思想也与之类似，可以概括为：

文本中，设定一个窗口宽度，比如 K 个词，统计窗口内的词和词的共现关系，将其看成无向图。图就是网络，由存在连接关系的节点构成，所谓无向图，就是节点之间的连接关系不考虑从谁出发，有关系就对了；

所有词初始化的重要性都是 1；

每个节点把自己的权重平均分配给“和自己有连接“的其他节点；

每个节点将所有其他节点分给自己的权重求和，作为自己的新权重；

如此反复迭代第 3、4 两步，直到所有的节点权重收敛为止。

通过 TextRank 计算后的词语权重，呈现出这样的特点：那些有共现关系的会互相支持对方成为关键词。

3 内容分类

在门户网站时代，每个门户网站都有自己的频道体系，这个频道体系就是一个非常大的内容分类体系，这一做法也延伸到了移动互联网 UGC 时代，图文信息流 App 的资讯内容也需要被自动分类到不同的频道中，从而能够得到最粗粒度的结构化信息，也被很多推荐系统用来在用户冷启动时探索用户兴趣。

在门户时代的内容分类，相对来说更容易，因为那时候的内容都是长文本，长文本的内容分类可以提取很多信息，而如今 UGC 当道的时代，短文本的内容分类则更困难一些。短文本分类方面经典的算法是 SVM ，在工具上现在最常用的是 Facebook 开源的 FastText。

4 实体识别

命名实体识别（也常常被简称为 NER，Named-Entity Recognition）在 NLP 技术中常常被认为是序列标注问题，和分词、词性标注属于同一类问题。

所谓序列标注问题，就是给你一个字符序列，从左往右遍历每个字符，一边遍历一边对每一个字符分类，分类的体系因序列标注问题不同而不同：

分词问题：对每一个字符分类为“词开始”“词中间”“词结束”三类之一；

词性标注：对每一个分好的词，分类为定义的词性集合的之一；

实体识别：对每一个分好的词，识别为定义的命名实体集合之一。

对于序列标注问题，通常的算法就是隐马尔科夫模型（HMM）或者条件随机场（CRF），我们在推荐系统中主要是挖掘出想要的结构化结果，对其中原理有兴趣再去深入了解。

实体识别还有比较实用化的非模型做法：词典法。提前准备好各种实体的词典，使用 trie-tree 数据结构存储，拿着分好的词去词典里找，找到了某个词就认为是提前定义好的实体了。

以实体识别为代表的序列标注问题上，工业级别的工具上 spaCy 比 NLTK 在效率上优秀一些。

5 聚类

传统聚类方法在文本中的应用，今天逐渐被主题模型取代，同样是无监督模型，以 LDA 为代表的主题模型能够更准确地抓住主题，并且能够得到软聚类的效果，也就是说可以让一条文本属于多个类簇。

作为初创公司或初创产品，我知道你的时间宝贵，也知道你的公司处处节俭，以至于没有业务专家为你的应用制定分类体系，这时候如果能在文本数据上跑一个 LDA 模型，那世界就显得非常美好了。

LDA 模型需要设定主题个数，如果你有时间，那么这个 K 可以通过一些实验来对比挑选，方法是：每次计算 K 个主题两两之间的平均相似度，选择一个较低的 K 值；如果你赶时间，在推荐系统领域，只要计算资源够用，主题数可以尽量多一些。

另外，需要注意的是，得到文本在各个主题上的分布，可以保留概率最大的前几个主题作为文本的主题。LDA 工程上较难的是并行化，如果文本数量没到海量程度，提高单机配置也是可以的，开源的 LDA 训练工具有 Gensim，PLDA 等可供选择。

6 词嵌入

关于嵌入，是一个数学概念。以词嵌入为例来说吧。

词嵌入，也叫作 Word Embedding。前面讲到的结构化方案，除了 LDA，其他都是得到一些标签，而这些标签无一例外都是稀疏的，而词嵌入则能够为每一个词学习得到一个稠密的向量。

这样说可能很抽象，换个说法，一个词可能隐藏很多语义信息，比如北京，可能包含“首都、中国、北方、直辖市、大城市”等等，这些语义在所有文本上是有限的，比如 128 个，于是每个词就用一个 128 维的向量表达，向量中各个维度上的值大小代表了词包含各个语义的多少。

拿着这些向量可以做以下的事情：

计算词和词之间的相似度，扩充结构化标签；

累加得到一个文本的稠密向量；

用于聚类，会得到比使用词向量聚类更好的语义聚类效果。

这方面当然就属大名鼎鼎的 Word2Vec 了。Word2Vec 是用浅层神经网络学习得到每个词的向量表达，Word2Vec 最大的贡献在于一些工程技巧上的优化，使得百万词的规模在单机上可以几分钟轻松跑出来，得到这些词向量后可以聚类或者进一步合成句子向量再使用。

二、标签选择

前面说到，用户端的文本，物品端的文本如何结构化，得到了诸如标签（关键词、分类等）、主题、词嵌入向量。接下来就是第二步：如何把物品的结构化信息给用户呢？

我们想一想，用户在产品上看到了很多我们用各种逻辑和理由展示给他的物品，他只从中消费了一部分物品。现在问题就是，到底是那些特性吸引了他消费呢？

一种简单粗暴的办法是直接把用户产生过行为的物品标签累积在一起，但是这里要说的是另一种思路。

我们把用户对物品的行为，消费或者没有消费看成是一个分类问题。用户用实际行动帮我们标注了若干数据，那么挑选出他实际感兴趣的特性就变成了特征选择问题。

最常用的是两个方法：卡方检验（CHI）和信息增益（IG）。基本思想是：

把物品的结构化内容看成文档；

把用户对物品的行为看成是类别；

每个用户看见过的物品就是一个文本集合；

在这个文本集合上使用特征选择算法选出每个用户关心的东西。

1 卡方检验

CHI 就是卡方检验，本身是一种特征选择方法。

前面的 TF-IDF 和 TextRank 都是无监督关键词提取算法，而卡方检验（CHI）则是有监督的，需要提供分类标注信息。

为什么需要呢？在文本分类任务中，挑选关键词就得为了分类任务服务，而不仅仅是挑选出一种直观上看着重要的词。

卡方检验本质上在检验“词和某个类别 C 相互独立”这个假设是否成立，和这个假设偏离越大，就越说明这个词和类别 C 暗中有一腿，那当然这个词就是关键词了。

计算一个词 Wi 和一个类别 Cj 的卡方值，需要统计四个值：

类别为 Cj 的文本中出现词 Wi 的文本数 A；

词 Wi 在非 Cj 的文本中出现的文本数 B ；

类别为 Cj 的文本中没有出现 Wi 的文本数 C ；

词 Wi 在非 Cj 的文本中没有出现的文本数 D 。

听起来有点绕，我把它画成一个表格更加一目了然。

然后按照如下公式计算每一个词和每一个类别的卡方值：

关于这个卡方值计算，我在这里说明几点：

每个词和每个类别都要计算，只要对其中一个类别有帮助的词都应该留下；

由于是比较卡方值的大小，所以公式中的 N 可以不参与计算，因为它对每个词都一样，就是总的文本数；

卡方值越大，意味着偏离“词和类别相互独立”的假设越远，靠“词和类别互相不独立”这个备择假设越近。

2 信息增益

IG 即 Information Gain，信息增益，也是一种有监督的关键词选择方法，也需要有标注信息。要理解信息增益，理解了信息熵就差不多了。

信息熵，一批文本被标注了类别，那么任意挑出一条文本问你，“猜猜这是什么类别？”如果原来每个类别的文本数量都一样，那你肯定最不好猜，如果其中一个类别的文本 C 数远远多于其他类别，那么你猜这条文本属于类别 C 就很可能猜对。这两个情况区别就在于信息熵不同：

各个类别的文本数量差不多时，信息熵就比较大。

其中少数类别的文本数量明显较多时，信息熵就较小。

进一步再想一件事，如果从这一堆文本中再挑出包含有词 W 的文本数，再来猜任意一条文本的类别时，仍然会有上面两种情况。这时候考虑一个情况：如果在整个文本上的情况是 1，但挑出包含词 W 后的情况变成 2 了，那么你想，这个词 W 是不是非常有用？因为有了它，我们就能以较高的成功率猜对任意一条文本的类别了。

对，上面这个思考过程就是信息增益的思想，信息增益计算也是分成三步：

统计全局文本的信息熵；

统计每个词的条件熵，就是知道了一个词后再统计文本的信息熵，只不过这里要分别计算包含词和不包含词两部分的信息熵，再按照各自文本比例加权平均；

两者相减就是每个词的信息增益。

信息增益应用最广就是数据挖掘中的决策树分类算法，经典的决策树分类算法挑选分裂节点时就是计算各个属性的信息增益，始终挑选信息增益最大的节点作为分裂节点。

卡方检验和信息增益不同之处在于：前者是针对每一个行为单独筛选一套标签出来，后者是全局统一筛选。

这些方法都是在离线阶段批量完成的，把用户的画像生成配置成离线任务，每天更新一遍，次日就可以使用新的用户画像。

那么对于一个新用户，能不能在他刚刚进入产品时就能够快速生成一个简单的画像呢？答案是：当然可以。这在后面的专栏中会讲到的 MAB 问题。

总结

用户画像对于推荐系统还是非常必要的，而产品中属文本数据最多，那如何用文本数据构建出用户的画像内容呢？

本文按照如下步骤梳理了这一过程：

分析用户的文本和物品的文本，使其结构化；

为用户挑选有信息量的结构化数据，作为其画像内容。

其中，我们提出了把为用户挑选画像标签看成是特征选择问题，除了卡方检验和信息增益，你还知道有哪些特征选择方法呢？欢迎留言一起讨论。

感谢你的收听，我们下次再见。

【内容推荐】画鬼容易画人难：用户画像的“能”和“不能”

做好一个推荐系统，总共分三步：

认识每一个用户；

给他推荐他感兴趣的东西；

坐等各项指标上升。

开个玩笑，如果这么简单的话，那么你和我都要失业了；但是话说回来，认识用户是必须的，不过不用担心，认识用户不用请他们吃饭，这就是我们常常听说的“用户画像”这个词。今天，我就来跟你聊一聊：用户画像的那些事儿。

用户画像比较抽象，就像每个人都听说过鬼，但很少有人见过。事实上，它也没有那么神秘，只是大家对它有误解，要么觉得没什么用，要么觉得它是“银弹”，可能相信后者的人略多一些，但实际上这两种看法都不准确。

什么是用户画像

先说说“用户画像”这个词，它对应的英文有两个：Personas 和 User Profile。Personas 属于交互设计领域的概念，不在本文讨论范围内，请出门右转去找交互设计师们聊，留下来的人，我们聊聊 User Profile 这种用户画像。

User Profile 原本用于营销领域。营销人员需要对营销的客户有更精准的认识，从而能够更有针对性地对客户和市场制定营销方案。

这个理念本身没有错，但是有一个问题：传统营销领域，是以市场销售人员为第一人称视角去看待客户的，也就是用户画像为营销人员服务。

在这种用途下谈论的用户画像，和我们即将在推荐系统领域谈论的相差有点大；但是很遗憾，今天在媒体上看到的大多数“用户画像”案例分享，都停留在这个意思上。

比如最常见的用户画像出现在高大上的 PPT 上：用标签云的方式绘制一个人的形状，或者在一个人物形象旁边列出若干人口统计学属性，以此来表达“用户画像”这个概念。

看上去非常酷炫，但是我得悄悄告诉你一个赤裸裸的真相：越酷炫的用户画像越没什么用。

为什么会这样？根本原因是：用户画像应该给机器看，而不是给人看。

既然是给机器看的，那么画像是不是酷炫、是不是像、维度是不是人类可读，都不重要。那它到底是个什么样子呢？先别急，听我慢慢讲。

一个推荐系统来到这个世界上，它只有一个使命，就是要在用户（User）和物品（Item）之间建立连接。

一般方式就是，对用户和物品之间的匹配评分，也就是预测用户评分或者偏好。推荐系统在对匹配评分前，则首先就要将用户和物品都向量化，这样才能进行计算。

而根据推荐算法不同，向量化的方式也不同，最终对匹配评分的做法也不同，在后面讲到具体推荐算法时你会看到这一点。

用户向量化后的结果，就是 User Profile，俗称“用户画像”。所以，用户画像不是推荐系统的目的，而是在构建推荐系统的过程中产生的一个关键环节的副产品。

另外，通常大型推荐系统一般都分为召回和排序两个阶段，这个在后面我会专门讲到。

因为全量物品通常数量非常大，无法为一个用户（User）逐一计算每一个物品（Item）的评分，这时候就需要一个召回阶段，其实就是预先筛选一部分物品（Item），从而降低计算量，用户画像除了用于最终匹配评分，还要用在召回。所以，构建用户画像就要以这两个阶段为目的。

用户画像的关键因素

举个例子，我想去吃点夜宵，楼下有五家大排档，那么从推荐系统的思路来看，我怎么选择呢？

首先就是将五家大排档向量化，我暂定向量的维度有：

价格，1~5 分，最贵的 1 分，最便宜的 5 分；

种类，1~5 分，只烤馒头片的是 1 分，天上飞的、海里游的、地上跑的、地里种的都有就是 5 分；

味道，1~5 分，根据以前吃的，最难吃的是 1 分，最好吃的是 5 分。

现在每一个大排档都有一个向量，我自己也要有一个对应的向量，就是你有多看中这三个元素：

价格： 1~5 分，土豪不差钱就是 1 分，囊中羞涩就是 5 分。

种类： 1~5 分，早就想好吃什么了不在乎选择多不多 1 分，看看再说就是 5 分

味道： 1~5 分，只是果腹就是 1 分，资深吃货就是 5 分

这样一来就可以对五家大排档做匹配打分了，你很容易得出哪家大排档最适合。

假如我的向量是：

价格： 3
种类： 5
味道： 5

这就是一个大排档推荐系统的简单用户画像了，是不是很简单！

这里可以简单计算一下：每一个因素相乘后再相加，就得到每一个大排档的评分了。

接下来我来围绕这个大排档推荐系统的用户画像，看看建立用户画像的关键因素：第一个是维度，第二个是量化。

首先我先来说说“维度”。

看前面这个例子，我定下来的几个维度：价格、种类、味道。这几个维度有三个特点：

1 每个维度的名称都是可理解的。

当我们去给每一个大排档计算评分时，想象你是一台计算机，你读取了用户画像的“价格”取值为 3，再去取出一个大排档的“价格”评分，两者相乘，用户画像的维度“价格”和大排档的“价格”天然匹配上了。

因为是同一个名字；但是计算机很傻，你把大排档的这个维度换成“价钱”，它就不知道该如何是好了。

另一方面，对这三个维度，把两边同时换成 1、 2、3 或者 a、b、c 都是可以的，也不影响计算结果，计算机依然能够匹配上；所以用户画像的维度不一定需要人类能够理解，只要计算机能把两边对应上就可以了。

2 维度的数量是我拍脑袋定的。

假如是根据用户的阅读历史挖掘阅读兴趣标签，那么我们无法提前知道用户有哪些标签，也就不能确定用户画像有哪些维度，所以第二点也不是必须的。

3 有哪些维度也是我拍脑袋确定的。

因为这一点也不是必须的，用户画像的维度个数可以不用确定。理论上来说维度越多，画像越精细，但带来的计算代价也是很大的，需要权衡。

虽然这里以标签作为例子，但是你要注意，用户画像是向量化结果，而不是标签化。标签化只是向量化的一种，因为向量的维度不一定需要人理解。

其次，我来说说量化。

我们这里的量化都是主观的，而在实际生产系统上，用户画像每个维度的量化，应该交给机器，而且以目标为导向，以推荐效果好坏来反向优化出用户画像才有意义，像这里这个简单的例子，没有去管推荐效果而先行主观量化了每一个维度，是大忌。

所以用户画像的量化是和第三个关键元素“效果”息息相关的。前面已经说过，不要为了用户画像而用户画像，它只是推荐系统的一个副产品，所以要根据使用效果（排序好坏、召回覆盖等指标）来指导用户画像的量化。

用户画像构建的方法

再来整体说说怎么构建用户画像，按照对用户向量化的手段来分，用户画像构建方法分成三类：

1. 第一类就是查户口。

直接使用原始数据作为用户画像的内容，如注册资料等人口统计学信息，或者购买历史，阅读历史等，除了数据清洗等工作，数据本身并没有做任何抽象和归纳。这就跟查户口一样，没什么技术含量，但通常对于用户冷启动等场景非常有用。

2. 第二类就是堆数据。方法就是堆积历史数据，做统计工作，这是最常见的用户画像数据，常见的兴趣标签，就是这一类，就是从历史行为数据中去挖掘出标签，然后在标签维度上做数据统计，用统计结果作为量化结果。这一类数据贡献了常见的酷炫用户画像。

3. 第三类就是黑盒子。就是用机器学习方法，学习出人类无法直观理解的稠密向量，也最不被非技术人员重视，但实际上在推荐系统中承担的作用非常大。

比如使用潜语义模型构建用户阅读兴趣，或者使用矩阵分解得到的隐因子，或者使用深度学习模型学习用户的 Embedding 向量。这一类用户画像数据因为通常是不可解释，不能直接被人看懂。

我会在后面专门讲解这些技术手段，以及它们在推荐系统中的实际使用。

总结

现在总结一下今天的内容：

用户画像到底是什么？它是对用户信息的向量化表示，为什么不向量化表示不行呢？因为没办法交给计算机计算，而且，用户画像是给机器看的，而不是给人看的。

用户画像的关键元素有哪些？维度、量化。用户画像是跟着使用效果走的，用户画像本身并不是目的。

通常构建用户画像的手段有哪几类？有三类，第一类只会查户口做记录，第二类就是堆数据做统计，第三类就是黑盒子看不懂。

你可以分享一下你现在正在经历的用户画像是什么样的，它有哪些优点和哪些问题，是不是为了展示给人看而构建的酷炫用户画像呢？欢迎留言和我一起讨论。感谢你的收听，我们下次再见。

【内容推荐】超越标签的内容推荐系统

我曾在不同公司里都听到过，他们的产品经理或者大佬问过这样的问题：我们的推荐系统标签够不够？

相信你也遇到过类似的问题。这其实是一个很大的误区：基于内容的推荐系统，标签只是很小一部分。

而且就算是标签，衡量质量的方式也不是数目够不够；所以，今天我要讲的内容，就是说一说脱离标签定式思维的内容推荐。

为什么要做好内容推荐

所谓的基于内容推荐，通俗一点来讲，就是一个包装成推荐系统的信息检索系统。这听上去有点残酷，但通常一个复杂的推荐系统很可能是从基于内容推荐成长起来的。

可以说，基于内容的推荐系统是一个推荐系统的孩童时代，所以，我们不能让自己的推荐系统输在起跑线上，得富养才行。那么，首先我就来讲一讲如何养成一个基于内容的推荐系统。

为什么基于内容的推荐系统这么重要呢？因为内容数据非常易得，哪怕是在一个产品刚刚上线，用心找的话总能找到一些可以使用的内容，不需要有用户行为数据就能够做出推荐系统的第一版。

内容数据尤其是文本，只要深入挖掘，就可以挖掘出一些很有用的信息供推荐系统使用。

另外，著名的流媒体音乐网站 Pandora，其音乐推荐系统背后的“音乐基因工程”，实质上就是人工为音乐标注了各种维度的属性，这样，即便使用基于内容推荐的方式，也做出了很好的推荐效果。

听上去，上面这段话特别像是在安慰还处在冷启动阶段的你，事实上呢，其实并不全是，内容推荐的方式还有它的必要性。推荐系统总是需要接入新的物品，这些新的物品在一开始没有任何展示机会，显然就没有用户反馈，这时候只有内容能帮它。

基于内容的推荐能把这些新物品找机会推荐出去，从而获得一些展示机会，积累用户反馈、走上巅峰、占据热门排行榜。

要把基于内容的推荐做好，需要做好“抓、洗、挖、算”四门功课。它们分别是对应了下面的内容。

抓：大厂们从来不公开说的一件事是，他们一直在持续抓数据丰富自己的内容，所以做好一个基于内容的推荐，抓取数据补充内容源，增加分析的维度，两者必不可少。

洗：抓来的数据，相当于捡别人掉地上的东西吃，我们也得注意卫生，洗洗更健康，数据也一样，冗余的内容、垃圾内容、政治色情等敏感内容等等都需要被洗出去。

挖：不管是抓来的数据，还是自家的数据，如果不深入挖掘，那就和捧着金饭碗去要饭一样，浪费了大好资源。可以说，很多推荐系统提升效果并不是用了更复杂的推荐算法，而是对内容的挖掘做得更加深入。

算：匹配用户的兴趣和物品的属性，计算出更合理的相关性，这是推荐系统本身的使命，不仅仅是基于内容的推荐才要做的。

那么，这四门功课到底如何分布在基于内容的推荐系统中呢？

下面我和你一起来看看，基于内容推荐的框架

在文稿中，我放了一张图，一个典型基于内容推荐的框架图是下面这样的：

简要介绍一下这张图的流程和基本元素。

内容这一端：内容源经过内容分析，得到结构化的内容库和内容模型，也就是物品画像。用户这一端：用户看过推荐列表后，会产生用户行为数据，结合物品画像，经过用户分析得到用户画像。

以后对于那些没有给用户推荐过的新内容，经过相同的内容分析过程后就可以经过推荐算法匹配，计算得到新的推荐列表给用户。如此周而复始，永不停息。

内容源

在互联网中，抓数据是一件可做不可说的事情，哪怕是市值几千亿的大厂，也有专门的小分队抓数据，补充推荐系统每天的内容消耗。因为，只有当内容有多样性了，一个推荐系统才有存在的合法性，所以大厂职工们抓数据也是为了保住自己的饭碗。

爬虫技术本身非常复杂、非常有学问，比推荐算法难多了，这里就不展开讲了。

不论是抓来的数据还是自家用户产生的数据，都离不开清洗数据。由于各家都在相互借鉴来借鉴去，所以抓到重复的内容也是很有可能的，去重与识别垃圾内容、色情内容、政治敏感内容等都是必修课。

关于这个环节的边角算法，我们在后面的文章中会专门花一些篇幅来讲。

内容分析和用户分析

基于内容的推荐，最重要的不是推荐算法，而是内容挖掘和分析。内容挖掘越深入，哪怕早期推荐算法仅仅是非常硬的规则，也能取得不俗的效果。举个例子，如果推荐物品是短视频，我们分几种情况看：

如果短视频本身没有任何结构化信息，如果不挖掘内容，那么除了强推或者随机小流量，没有别的合理曝光逻辑了；

如果对视频的文本描述，比如标题等能够有内容分类，比如是娱乐类，那么对于喜欢娱乐的用户来说就很合理；

如果能够进一步分析文本的主题，那么对于类似主题感兴趣的用户就可能得到展示；

如果还能识别出内容中主角是吴亦凡，那就更精准锁定一部分用户了；

如果再对内容本身做到嵌入分析，那么潜藏的语义信息也全部抓住，更能表达内容了。

举这个例子是为了说明：随着内容分析的深入，能抓住的用户群体就越细致，推荐的转化率就越高，用户对产品的好感度也就增加了。上一篇中我列举了文本数据——这也是内容数据最常见形式的分析方法。

内容分析的产出有两个：

结构化内容库；

内容分析模型。

结构化的内容库，最重要的用途是结合用户反馈行为去学习用户画像，具体的方法在上一篇中已经介绍了。容易被忽略的是第二个用途，在内容分析过程中得到的模型，比如说：

分类器模型；

主题模型；

实体识别模型；

嵌入模型。

这些模型主要用在：当新的物品刚刚进入时，需要实时地被推荐出去，这时候对内容的实时分析，提取结构化内容，再于用户画像匹配。

内容推荐算法

对于基于内容的推荐系统，最简单的推荐算法当然是计算相似性即可，用户的画像内容就表示为稀疏的向量，同时内容端也有对应的稀疏向量，两者之间计算余弦相似度，根据相似度对推荐物品排序。

你别嫌糙，如果你内容分析做得深入的话，通常效果还不错，而且基于内容的推荐天然有一个优点：可解释性非常强。

如果再进一步，要更好地利用内容中的结构化信息，因为一个直观的认识是：不同字段的重要性不同。

比如说，一篇新闻，正文和标题中分析出一个人物名，评论中也分析出其他用户讨论提及的一些人物名，都可以用于推荐。直观上新闻的正文和标题中更重要。

那么，我们可以借鉴信息检索中的相关性计算方法来做推荐匹配计算：BM25F 算法。常用的开源搜索引擎如 Lucene 中已经实现了经典的 BM25F 算法，直接拿来使用即可。

前面提到的两种办法虽然可以做到快速实现、快速上线，但实际上都不属于机器学习方法，因为没有考虑推荐的目标，而我们在之前的专栏中就专门强调了目标思维，那么，按照机器学习思路该怎么做呢？

一种最典型的场景：提高某种行为的转化率，如点击、收藏、转发等。那么标准的做法是：收集这类行为的日志数据，转换成训练样本，训练预估模型。

每一条样本由两部分构成：一部分是特征，包含用户端的画像内容，物品端的结构化内容，可选的还有日志记录时一些上下文场景信息，如时间、地理位置、设备等等，另一部分就是用户行为，作为标注信息，包含“有反馈”和“无反馈”两类。

用这样的样本训练一个二分类器，常用模型是逻辑回归（Logistic Regression）和梯度提升树（GBDT）或者两者的结合。在推荐匹配时，预估用户行为发生的概率，按照概率排序。这样更合理更科学，而且这一条路可以一直迭代优化下去。

总结

基于内容的推荐一般是推荐系统的起步阶段，而且会持续存在，它的重要性不可取代。因为：

内容数据始终存在并且蕴含丰富的信息量，不好好利用就可惜了；

产品冷启动阶段，没有用户行为，别无选择；

新的物品要被推荐出去，首选内容推荐。

基于内容的整体框架也是很清晰的，其中对内容的分析最为重要，推荐算法这一款可以考虑先使用糙快猛的相似度计算，也可以采用机器学习思路训练预估模型，当然这必须得有大量的用户行为做保证。

好的，今天的内容就到这里，你可以在留言中谈一谈你对整个内容推荐链条各个环节的理解吗？欢迎和我一起讨论，感谢你的收听，我们下期再见。

本周知识要点

【模型融合】一网打尽协同过滤、矩阵分解和线性模型

在上一篇文章中，我讲到了使用逻辑回归和梯度提升决策树组合的模型融合办法，用于 CTR 预估，我还满怀爱意地给这对组合起了个名字，叫做辑度组合，因为这对组合的确可以在很多地方帮到我们。

这对组合中，梯度提升决策树，也就是人们常说的 GBDT，所起的作用就是对原始的特征做各种有效的组合，一棵树一个叶子节点就是一种特征组合。

这大概就是逻辑回归的宿命吧，作为一个广义线性模型，在这个由非线性组成的世界里，唯有与各种特征组合办法精诚合作，才能活下去。

从特征组合说起

对逻辑回归最朴素的特征组合就是二阶笛卡尔乘积，但是你有没有想过这样暴力组合的问题所在。

两两组合导致特征维度灾难；

组合后的特征不见得都有效，事实上大部分可能无效；

组合后的特征样本非常稀疏，意思就是组合容易，但是并不能在样本中找到对应的组合出现，也就没办法在训练时更新参数。

如果把包含了特征两两组合的逻辑回归线性部分写出来，就是：

y^=ω0+∑i=1nωixi+∑i=1n∑j=i+1nωijxixjy^=ω0+∑i=1nωixi+∑i=1n∑j=i+1nωijxixj

这和原始的逻辑回归相比，就多出来了后面那一大坨，特征两两组合那部分，也需要去学习对应的参数权重。

问题就是两两组合后非常有可能没有样本能够学习到  $w_{ij}，不但没有样本可以用来学习到参数，而且在应用时，如果遇到了这样的组合，也就只能放弃，因为没有学到权重。

针对这个问题，就有了一个新的算法模型：因子分解机模型，也叫做 FM，即 Factorization Machine。因子分解机也常常用来做模型融合，今天就和你聊聊因子分解机的来龙去脉。

FM 模型

1. 原理

因子分解机模型是在 2010 年被提出来的。因为逻辑回归在做特征组合时样本稀疏，从而无法学到很多特征组合的权重，所以因子分解机的提出者就想，能不能对上面那个公式中的 wijwij 做解耦，让每一个特征学习一个隐因子向量出来。

就好像前面讲矩阵分解时，为每一个用户和每一个物品各自都学习一个隐因子向量一样，这样一来，任何两个特征不小心在实际使用时相遇了，需要组合，那么各自掏出自己随身携带的隐因子变量做一个向量点积，就是两者组合特征的权重了。

还是针对逻辑回归的线性部分，用公式写一下更清楚：

y^=ω0+∑i=1nωixi+∑i=1n∑j=i+1n<Vi,Vj>xixjy^=ω0+∑i=1nωixi+∑i=1n∑j=i+1n<Vi,Vj>xixj

这个公式和前面特征组合的公式相比，不同之处就是原来有个 wijwij，变成了这里的两个隐因子向量的点积 <Vi,Vj><Vi,Vj>。

不要小看这个变化。它其实认为两个特征之间，即使没有共同出现在一条样本中，也是有间接联系的。比如说特征 A 和特征 B 曾在一些样本中一起出现过，特征 B 和特征 C 曾在一些样本中出现过，那么特征 A 和特征 C 无论是否在样本中一起出现过，仍然是有些联系的。

如果在实际预测 CTR 时，特征 A 和特征 C 真的在一起出现了，如果你用的是因子分解机模型，这时候你的预测程序就不慌不忙走向数据库，从中取出早已准备好的特征 A 和特征 C 的隐因子向量，拿出来做一个点积运算，就得到了两者组合的权重。

也许逻辑回归见到这一切不禁要问：居然还有这种操作？是的，因子分解机的先进之处就在于此。

现在聪明如你，一定也想到了，既然二阶特征组合可以学到隐因子向量，那么三阶特征组合也可以加进来，四阶，五阶……想一想是不是有点小激动？不要急，组合越多，计算复杂度就会陡增，所以一般在实际使用中，因子分解机就表演到二阶特征组合就 OK 了。

2 模型训练

因子分解机的参数学习并无特别之处，看目标函数，我在这里是把它当作融合模型来看的，用来做 CTR 预估，因此预测目标是一个二分类，因子分解机的输出还需要经过 sigmoid 函数变换：

σ(y^)=11+e−y^σ(y^)=11+e−y^

因此，损失目标函数也就是常用的 logistic loss：

loss(θ)=−1m∑i=1m[y(i)log(σ(y^))+(1−y(i))log(1−σ(y^))]loss(θ)=−1m∑i=1m[y(i)log(σ(y^))+(1−y(i))log(1−σ(y^))]

公式中 σ(y^)σ(y^) 是因子分解机的预测输出后经过 sigmoid 函数变换得到的预估 CTR， y(i)y(i) 是真实样本的类别标记，正样本是 1，负样本是 0，m 是样本总数。

对这个损失目标函数使用梯度下降或者随机梯度下降就可以得到模型的参数，和前面文章里的没有区别，注意损失函数实际上还需要加上正则项，这在上一篇专栏中已经总结过机器学习损失函数的两板斧，就是偏差和方差。

3 预测阶段

假如现在已经得到了因子分解机的模型参数，你忍不住跃跃欲试想端着它冲上战场，且慢，因子分解机中二阶特征组合那一坨，在实际计算时，复杂度有点高，如果隐因子向量的维度是 k，特征维度是 n，那这个复杂度就是 O(kn2)O(kn2)，其中 n 方是特征要两两组合，k 是每次组合都要对 k 维向量计算点积。需要对此稍微做一下改造，改造过程如下：

∑i=1n∑j=i+1n<Vi,Vj>xixj=12∑i=1n∑j=1n<Vi,Vj>xixj−12∑i=1n<Vi,Vj>xixi =12(∑i=1n∑j=1n∑f=1kvi,fvj,fxixj−∑i=1n∑f=1kvi,fvi,fxixi) =12∑f=1k((∑i=1nvi,fxi)(∑j=1nvj,fxj)−∑i=1nv2i,fx2i) =12∑f=1k((∑i=1nvi,fxi)2−∑i=1nv2i,fx2i)∑i=1n∑j=i+1n<Vi,Vj>xixj=12∑i=1n∑j=1n<Vi,Vj>xixj−12∑i=1n<Vi,Vj>xixi =12(∑i=1n∑j=1n∑f=1kvi,fvj,fxixj−∑i=1n∑f=1kvi,fvi,fxixi) =12∑f=1k((∑i=1nvi,fxi)(∑j=1nvj,fxj)−∑i=1nvi,f2xi2) =12∑f=1k((∑i=1nvi,fxi)2−∑i=1nvi,f2xi2)

看上去这个有点复杂，你如果不想理解也没关系，我直接告诉你最后该怎么算。

loop1 begin: 循环 k 次，k 就是隐因子向量的维度，其中，循环到第 f 次时做以下事情

 loop2 begin: 循环 n 个特征，第 i 次循环时做这样的事情

  1. 从第 i 个特征的隐因子向量中拿出第 f 维的值

  2. 计算两个值：A 是特征值和 f 维的值相乘，B 是 A 的平方

 loop2 end

 把 n 个 A 累加起来，并平方得到 C，把 n 个 B 也累加起来，得到 D

 用 C 减 D，得到 E

loop1 end

把 k 次循环得到的 k 个 E 累加起来，除以 2

这就是因子分解机中，二阶组合部分的实际计算方法，现在这样做的复杂度只是 O(kn)，原来的平方复杂度不见了。

4. 一网打尽其他模型

下面继续带你见识一些因子分解机的神奇之处。看下面这张图，这里示意了一批样本。

这张图中每一条样本都记录了用户对电影的评分，最右边的 y 是评分，也就是预测目标；左边的特征有五种，用户 ID、当前评分的电影 ID、曾经评过的其他分、评分时间、上一次评分的电影。

好，现在我们来看因子分解机如何一网打尽其他模型的，注意，这里说的一网打尽并不是打败，而是说模型可以变形成其他模型。

前面已经说了因子分解机可以实现带有特征组合的逻辑回归。

现在假设图中的样本特征只留下用户 ID 和电影 ID，因子分解机模型就变成：

y^=ω0+ωu+ωi+<Vu,Vi>y^=ω0+ωu+ωi+<Vu,Vi>

解释一下如何变成这样的。因为用户 ID 和电影 ID，在一条样本中，各自都只有一个维度是 1，其他都是 0，所以在一阶部分就没有了求和符合，直接是 wu 和 wi，二阶部分特征乘积也只剩下了一个 1，其他都为 0 了。你瞧，这不就是带有偏置信息的 SVD 吗？

现在继续，在 SVD 基础上把样本中的特征加上用户历史评过分的电影 ID，再求隐因子向量，这就是 SVD++ 呀！

再加上时间信息，就变成了 time-SVD。

所以因子分解机是把我之前讲过的矩阵分解一网打尽了，顺便还干起了逻辑回归的工作，也正因如此，因子分解机常常用来做模型融合，在推荐系统的排序阶段肩负起对召回结果做重排序的任务。

5.FFM

因子分解机的故事已经讲完，但我后来在因子分解机基础上改进了一下。改进的思路是：不但认为特征和特征之间潜藏着一些不可告人的关系，还认为特征和特征类型有着千丝万缕的关系。

这个特征类型，就是某些特征实际上是来自数据的同一个字段，比如用户 ID，占据了很多维度，变成了很多特征，但他们都属于同一个类型，都叫“用户 ID”。这个特征类型就是字段，即 Field。这种改进叫做 Field-aware Factorization Machines，简称 FFM。

再回顾一下，因子分解机模型的样子是这样：

y^=ω0+∑i=1nωixi+∑i=1n∑j=i+1n<Vi,Vj>xixjy^=ω0+∑i=1nωixi+∑i=1n∑j=i+1n<Vi,Vj>xixj

之前因子分解机认为每个特征有一个隐因子向量，FFM 改进的是二阶组合那部分，改进的模型认为每个特征有 f 个隐因子向量，这里的 f 就是特征一共来自多少个字段（Field），二阶组合部分改进后如下：

∑i=1n∑j=i+1n<Vi,fj,Vj,fi>xixj∑i=1n∑j=i+1n<Vi,fj,Vj,fi>xixj

FFM 模型也常用来做 CTR 预估。在 FM 和 FFM 事件过程中，记得要对样本和特征都做归一化。

总结

今天，我给你介绍了另一种常用来做 CTR 预估的模型，因子分解机。因子分解机最早提出在 2010 年，在一些数据挖掘比赛中都取得了很好的乘积，后来被引入工业界做模型融合，也表现不俗。严格来说，因子分解机也算是矩阵分解算法的一种，因为它的学习结果也是隐因子向量，也是用过隐因子向量的点积代替原来的单个权重参数。

最后，由于不断提到特征组合的重要性，前有 GBDT，现有 FM，都是在特征组合上花功夫，你能不能在这里分享一下，你所用过的特征组合办法有哪些呢？欢迎留言一起讨论。

【模型融合】深度和宽度兼具的融合模型 Wide and Deep

我在前面已经提到过一个事实，就是推荐系统的框架大都是多种召回策略外挂一个融合排序。召回策略的姿势繁多，前面的专栏文章已经涉及了一部分内容。今天我们继续说融合排序。

要深还是要宽

融合排序，最常见的就是 CTR 预估，你一定不要把自己真的只局限在 C 上，这里说的 CTR 预估的 C，可以是产品中的任何行为，视频是不是会看完，看完后是不是会收藏，是不是会分享到第三方平台，查看的商品是不是会购买等等，都可以看成那个可以被预估发生概率的 CTR。

CTR 预估的常见做法就是广义线性模型，如 Logistic Regression，然后再采用特征海洋战术，就是把几乎所有的精力都放在搞特征上：挖掘新特征、挖掘特征组合、寻找新的特征离散方法等等。

这种简单模型加特征工程的做法好处多多：

线性模型简单，其训练和预测计算复杂度都相对低；

工程师的精力可以集中在发掘新的有效特征上，俗称特征工程；

工程师们可以并行化工作，各自挖掘特征；

线性模型的可解释性相对非线性模型要好。

特征海洋战术让线性模型表现为一个很宽广（Wide）的模型，可以想象逻辑回归中那个特征向量在特征工程的加持下，越来越宽的样子。

最近十年，是深度学习独步天下的十年，犹如异军突起，一路摧城拔寨，战火自然也烧到了推荐系统领域，用深度神经网络来革“线性模型 + 特征工程”的命，也再自然不过。

用这种“深模型”升级以前的“宽模型”，尤其是深度学习“端到端”的诱惑，可以让每天沉迷搞特征无法自拔的工程师们主动投怀送抱。

深度学习在推荐领域的应用，其最大好处就是“洞悉本质般的精深”，优秀的泛化性能，可以给推荐很多惊喜。

硬币总有正反面，深度模型的泛化强于线性模型，也会导致推荐有时候看上去像是“找不着北”，就是大家常常自问的那句话：“不知道这是怎么推出来的？”用行话说，就是可解释性不好。

以前全面搞特征时，你叫人家“宽模型”小甜甜，现在新模型换旧模型，“深模型”一出，就叫“宽模型”牛夫人，这样不好，还是要两者合作，才能最大限度地发挥效果。

因此，Google 在 2016 年就发表了他们在 Google Play 应用商店上实践检验过的 CTR 预估方法：Wide & Deep 模型，让两者一起为用户们服务，这样就取得了良好效果。

下面，我就为你详细介绍一下这个深宽模型。

Wide & Deep 模型

一个典型的推荐系统架构，其实很类似一个搜索引擎，搜索由检索和排序构成。推荐系统也有召回和排序两部构成，不过，推荐系统的检索过程并不一定有显式的检索语句，通常是拿着用户特征和场景特征去检索召回，其中用户特征也就是在前面的专栏中提到的用户画像。

示意图如下.

简单描述一下这个示意图。

首先使用用户特征和上下文场景特征从物品库中召回候选推荐结果，比如得到 100 个物品，然后用融合模型对这 100 个物品做最终排序，输出给用户展示。

同时开始记录展示日志和用户行为日志，再把收集到的日志和用户特征、上下文场景特征、物品特征拉平成为模型的训练数据，训练新的模型，再用于后面的推荐，如此周而复始。

今天要说的深宽模型就是专门用于融合排序的，分成两部分来看。一部分是线性模型，一部分是深度非线性模型。整个示意图如下：

我来解释一下这个示意图，这个示意图有三部分。最左边是宽模型，中间是深宽模型，最右边是纯的深度模型。

首先，线性模型部分，也就是“宽模型”，形式如下：

再次强调一下，这是线性模型的标准形式，逻辑回归只是在这基础上用 sigmoid 函数变换了一下。

模型中的 X 是特征，W 是权重，b 是模型的偏置，也是线性模型的截距。线性模型中常用的特征构造手段就是特征交叉。

例如：“性别 = 女 and 语言 = 英语。”就是由两个特征组合交叉而成，只有当“性别 = 女”取值为 1，并且“语言 = 英语”也取值为 1 时，这个交叉特征才会取值为 1。线性模型的输出这里采用的 Logistic Regression。

好，现在把头转到右边，看看深度模型。深度模型其实就是一个前馈神经网络。

深度模型对原始的高维稀疏类别型特征，先进行嵌入学习，转换为稠密、低维的实值型向量，转换后的向量维度通常在 10-100 这个范围。

这里的嵌入学习，就是先随机初始化嵌入向量，再直接扔到整个前馈网络中，用目标函数来优化学习。

由于本专栏并不会专门讲深度学习的原理，后面还会继续讲到深度学习和推荐系统的结合使用，所以有必要在这里简单普及一下深度学习的基本概念，不然我自顾自地开车，你可能会觉得辣眼睛。

就以这里的“深”模型，也就是示意图中最右边的图为例，一个深度神经网络由输入层，隐藏层，输出层构成。

那这个和逻辑回归的区别在哪呢？你可以认为逻辑回归是个残缺的神经网络，只有输入层和输出层，没有隐藏层。

逻辑回归的输入层就是特征向量，原来我们熟悉的特征权重，就是神经网络的参数，就存在于这个残缺的神经网络输入层和输出层的连线上，后面都可以这么理解，深度神经网络参数都在那些连线上。

这个残缺神经网络的输出层做了两件事，这时特征值在经过连线送到输出层时已经乘以了连线上的参数，第一件事就是把这些值加起来，第二件事就是用 sigmoid 函数变换一下。

把逻辑回归当成一个残缺的神经网络理解后，再回头看真正的神经网络，这里多了一个隐藏层，这个多出来的隐藏层干的事就是刚才提到的输出层的两板斧。

只不过一个隐藏层可以有多个神经元在干这两件事，隐藏层的这多个神经元就相当于输出层的输入层。

这个增加的隐藏层有什么意义呢？意义就是给模型提供了非线性转换。

所谓深度学习，就是深度神经网络，就是有不止一层的隐藏层存在。层数越多，非线性越强，模型越复杂。还有两点需要说明：

隐藏层的激活函数不一定是 sigmoid 函数，甚至往往不用 sigmoid 函数；

输出层的函数也不一定是 sigmoid 函数，这个根据预测目标而定，回归任务就是 i 直接输出求和部分，二分类是 sigmoid 函数，多分类则是 softmax。

好，插播深度学习概念结束，回到主题来。深模型中，每一个隐藏层激活方式表示如下。

其中 l 表示第 l 个隐藏层，f 是激活函数，通常选用 ReLU，也叫整流线性单元，为什么选用 ReLU 而不是 sigmoid 函数，原因主要是 sigmoid 函数在误差反向传播时梯度容易饱和。

如果你不明白这句话可以不用管，不影响你上车。这里我用示意图说明了一下常用激活函数的形状。

紫色是 sigmoid 函数，就是逻辑回归用的那个，输入值是任意范围，输出是 0 到 1 之间；

草绿色是反正切函数，和 sigmoid 函数样子很像，输入值是任意范围，输出是 -1 到 1 之间；

红色就是 ReLU 函数，当输入小于 0 时，输出为 0，当输入大于 0 时，输出等于输入；

蓝色是 softplus 函数，是一条渐近线，输入趋向于负无穷时，输出趋于 0，输入趋于正无穷时，输出趋向于等于输入。

最后，看看两者的融合，即深宽模型。深模型和宽模型，由逻辑回归作为最终输出单元，深模型最后一个隐藏层作为特征接入逻辑回归，宽模型的原始特征与之一起接入逻辑回归，然后训练参数。

参数学习就是通常说的端到端，把深模型和宽模型以及最终融合的权重放在一个训练流程中，直接对目标函数负责，不存在分阶段训练。它与机器学习中的集成学习方法有所区别，集成学习的子模型是独立训练的，只在融合阶段才会学习权重，这里是整体。

把深宽模型的最后输出过程表示成公式就是：

其中，Y 是我们要预估的行为，二值变量，如购买，或点击，Google 的应用场景为“是否安装 APP”。σ是 sigmoid 函数，WwideTWwideT 宽模型的权重，Φ(X) 是宽模型的组合特征，WdeepTWdeepT 应用在深模型输出上的权重，a(lf)a(lf) 是深模型的最后一层输出，b 是线性模型的偏置。

几点技巧

这个深宽模型已经在 TensorFlow 中有开源实现，具体落地时整个数据流如下图所示。

整个流程分为三大块：数据生成，模型训练，模型应用。

1 数据生成

数据生成有几个要点：

每一条曝光日志就生成一条样本，标签就是 1/0，安装了 App 就是 1，否则就是 0。

将字符串形式的特征映射为 ID，需要用一个阈值过滤掉那些出现样本较少的特征。

对连续值做归一化，归一化的方法是：对累积分布函数 P(X<=x) 划分 nq 个分位，落入第 i 个分位的特征都归一化为下图所示。

2 模型训练

整个模型的示意如图所示。其要点，在深度模型侧：

每个类别特征 embedding 成一个 32 维向量；

将所有类别特征的 embedding 变量连成一个 1200 维度左右的大向量；

1200 维度向量就送进三层以 ReLU 作为激活函数的隐藏层；

最终从 Logistic Regreesion 输出。

宽模型侧就是传统的做法：特征交叉组合。

当新的样本集合到来时，先是用上一次的模型来初始化模型参数，然后在此基础上进行训练。

新模型上线前，会先跑一遍，看看会不会出事，算是一个冒烟测试。

3 模型应用

模型验证后，就发布到模型服务器。模型服务，每次网络请求输入的是来自召回模块的 App 候选列表以及用户特征，再对输入的每个 App 进行评分。评分就是用我们的“深宽模型”计算，再按照计算的 CTR 从高到低排序。

为了让每次请求响应时间在 10ms 量级，每次并不是串行地对每个候选 App 计算，而是多线程并行，将候选 App 分成若干并行批量计算。

正因为有这些小的优化点，GooglePlay 的 App 推荐服务，就是在峰值时每秒计算千万级的 App。

总结

将传统的“宽模型”和新的“深模型”结合，虽然更多的是一种工程上的创新，但是非常有实用性，模型也容易很理解。

简单画一下全文重点。

深宽模型是一个结合了传统线性模型和深度模型的工程创新。

这个模型适合高维稀疏特征的推荐场景，稀疏特征的可解释性加上深度模型的泛化性能，双剑合璧。

这个模型已经开源在 TensorFlow 中，大大减小了落地成本，感兴趣可自行取用。

为了提高模型的训练效率，每一次并不从头开始训练，而是用上一次模型参数来初始化当前模型的参数。

将类别型特征先做嵌入学习，再将嵌入稠密向量送入深度模型中。

为了提高服务的响应效率，对每次请求要计算的多个候选 App 采用并行评分计算的方式，大大降低响应时间。

嗯，这真的是一个既博学又精深的模型啊。

最后一点后话，这个模型在线上效果还是不错的，以 GooglePlay 的 App 推荐效果为例，用户安装表现良好，对照实验结果如图所示。

可以看到，线上效果直接相对于对照组（纯线性模型 + 人工特征）有 3.9% 的提升，但是线下的 AUC 值提高并不明显，这其实也给你提了个问题，AUC 值是不是最佳的线下评估方式？欢迎留言一起讨论。

本周知识图

【模型融合】经典模型融合办法：线性模型和树模型的组合拳

推荐系统在技术实现上一般划分为三个阶段：挖掘、召回、排序。

为什么要融合？

挖掘的工作就是对用户和物品做非常深入的结构化分析，庖丁解牛一样，各个角度各个层面的特征都被呈现出来，并且建好索引，供召回阶段使用，大部分挖掘工作都是离线进行的。

接下来就是召回，为什么会有召回？因为物品太多了，每次给一个用户计算推荐结果时，如果对全部物品挨个计算，那将是一场灾难，取而代之的是用一些手段从全量的物品中筛选出一部分比较靠谱的。

最后就是排序，针对筛选出的一部分靠谱的做一个统一的论资排辈，最后这个统一的排序就是今天要讲的主题：融合。

前面巴拉巴拉说了一段，画成图的话会好理解一些，示意图如下。

为什么要融合呢？这还得倒回去说一说召回是什么，以及这个阶段到底发生了什么？

在召回阶段，其实就是各种简单的、复杂的推荐算法，比如说基于内容的推荐，会产生一些推荐结果，比如基于物品的协同过滤会产生一些结果，矩阵分解会产生一些结果，等等。

总之，每种算法都会产生一批推荐结果，一般同时还附带给每个结果产生一个推荐分数，是各自算法给出来的。

于是问题就来了，这些不同算法产生的推荐分数，最后要一起排个先后，难道依据各自的分数吗？

这样是不行的，为什么？有几个原因：

有个算法可能只给出结果，不给分数，比如用决策树产生一些推荐结果；

每种算法给出结果时如果有分数，分数的范围不一定一样，所以不能互相比较，大家各自家庭背景不一样；

即使强行把所有分数都归一化，仍然不能互相比较，因为产生的机制不同，有的可能普遍偏高，有的可能普遍偏低。

既然来自各个地方的状元凑在一起，谁也不服谁，那只能再举行一次入学考试了，这个入学考试就是融合模型。也就是，不同算法只负责推举出候选结果，真正最终是否推荐给用户，由另一个统一的模型说了算，这个就叫做模型的融合。

模型融合的作用除了统一地方军阀，还有集中提升效果的作用。在机器学习中，有专门为融合而生的集成学习思想。

今天要讲的一个典型的模型融合方案是：逻辑回归和梯度提升决策树组合，我可以给它取个名字叫做“辑度组合”。

“辑度组合”原理

在推荐系统的模型融合阶段，就要以产品目标为导向。举个简单的例子，信息流推荐，如果以提高 CTR 为目标，则融合模型就要把预估 CTR 作为本职工作，这个工作谁最能胜任呢，一直以来就是逻辑回归。

下面，我就来简单介绍一些常见的逻辑回归。

逻辑回归

CTR 预估就是在推荐一个物品之前，预估一下用户点击它的概率有多大，再根据这个预估的点击率对物品排序输出。

逻辑回归常常被选来执行这个任务，它的输出值范围就是 0 到 1 之间，刚好满足点击率预估的输出，这是一个基础。因为逻辑回归是广义线性模型，相比于传统线性模型，在线性模型基础上增加了 sigmoid 函数。

下面就简单说说，逻辑回归如何做 CTR 预估？

我还是按照一直以来的套路来讲，先讲它在真正使用时怎么做的，再一步步往回看怎么得到所需要的条件。

在对召回阶段不同算法给出的候选物品计算 CTR 预估时，需要两个东西：

特征；

权重。

第一个是特征，就是用量化、向量的方式把一个用户和一个物品的成对组合表示出来。这里说的量化方式包括两种：实数和布尔。实数好理解，比如一个用户的年龄，一个用户平均在某个品类上每个月的开销，类似等等。

布尔，就是取值 0 或者 1，针对两种类别形式的，比如用户所在的省、市，当时是白天还是晚上，物品的每一个标签。

用户和每一个候选物品都组一下 CP，然后以这种特征化的方式表达出来，就可以计算了，否则类别形式的字段不能直接参与计算。

第二个是权重，每个特征都有一个权重，权重就是特征的话事权。在这场决定哪些物品最终有机会能走到前台的选秀过程中，用户和物品这对 CP 的所有特征都有投票权，只是同人不同命，每个特征的权重不一样，对最终计算 CTR 影响有大有小。

这个权重就很重要了，显然不能由愚蠢的人类来指定，需要模型自主从大量的历史数据中学习得到。

有了特征，它是一个向量，假如把它叫做 x；还有特征的权重，也是一个维度和特征一样的向量，假如叫做 w。

我们通过对 x 和 w 做点积计算，就得到了一个传统线性模型的输出，再用 sigmoid 函数对这个值做一个变换，就得到一个 0 到 1 之间的值，也就是预估的 CTR。

这里所说的 sigmoid 函数长这个样子：

σ(w×x)=11+e−w×xσ(w×x)=11+e−w×x

这个函数曲线如图所示。

那看上去其实要做的就是两件事了：搞特征、学权重。

事实上的确如此，甚至前者占据更多的时间。逻辑回归特特征的取值都要求要在 0 到 1 之间。

甚至在一些领域，比如搜索广告，特征全都是布尔取值，只有出现和不出现两种，一旦遇到实数取值的特征，就将它划分成多个区间段，也变成了布尔取值。

除此之外，由于逻辑回归是广义线性模型，所谓广义就是因为加了 sigmoid 函数，所以很多非线性关系它无能为力。

比如说，有一天你发现“ID 为 233 的用户喜欢买各种钢笔”这个事实，它可以有两个特征组合出来，一个是“ID 为 233”，是一个布尔特征，另一个是“物品为钢笔”，也是一个布尔特征，显然构造一个新特征，叫做“ID 为 233 且物品为钢笔”。

只有两个原始特征都取值为 1 时，这个构造出的特征才会取值为 1，这种组合就是非线性，逻辑回归本身对两个原始特征仅仅是线性加权，并不能很好地刻画这个组合关系，非得组合才能助它一臂之力。

类似这样的工作，行话都叫做特征工程，刚才举例所说的特征组合叫做二阶组合，还有三阶组合，只要你高兴，也没人拦着你搞四阶组合。

但是要注意，特征组合的难点在于：组合数目非常庞大，而且并不是所有组合都有效，只有少数组合有效。

需要不断去弄脏双手，脚上沾泥地从数据中发现新的、有效的特征及特征组合。

特征工程 + 线性模型，是模型融合、CTR 预估等居家旅行必备。

权重那部分就是老生常谈了，简单说就是你准备好样本，喂给优化算法，优化算法再挤出新鲜的权重。

权重的学习主要看两个方面：损失函数的最小化，就是模型的偏差是否足够小；另一个就是模型的正则化，就是看模型的方差是否足够小；都是希望模型能够有足够的生命力，在实际生产线上最好能和实验阶段表现一样好。

除了要学习出偏差和方差都较小的模型，还需要能够给工程上留出很多余地，具体来说就是两点，一个是希望越多权重为 0 越好，权重为 0 称之为稀疏，可以减小很多计算复杂度，并且模型更简单，方差那部分会可控。

另一个是希望能够在线学习这些权重，用户源源不断贡献他们的行为，后台就会源源不断地更新权重，这样才能实现生命的大和谐。

要学习逻辑回归的权重，经典的方法如梯度下降一类，尤其是随机梯度下降，这在前面讲矩阵分解时已经提到过，可以实现在实时数据流情形下，更新逻辑回归的权重，每一个样本更新一次。

但是随机梯度下降常被人诟病的是，它什么也表现不好，很难得到稀疏的模型，效果收敛得也很慢。

也就是模型预测结果在通往真正想要到达的靶心路上看上去像是喝醉了酒一样，歪歪斜斜，像是很随机，但是趋势上还是在朝损失函数下降的方向。

后来 Google 在 2013 年 KDD 上发表了新的学习算法：FTRL，一种结合了 L1 正则和 L2 正则的在线优化算法，现在各家公司都采用了这个算法。

这里也顺便提一句，这个专栏重点讲解的是推荐系统落地会用到的东西，尽量通俗易懂。如果深入到机器学习和人工智能其他分支，可以参考极客时间上洪亮劼老师的“AI 技术内参”专栏。

对于我给你讲过的原理，希望可以让你有个直观的理解，在专栏结束后的图书出版计划中，我会在书中更加细致深入地讲原理，就有更多的代码和公式。

梯度提升决策树 GBDT

前面提到，特征组合又能有效表达出数据中的非线性事实，但是发现成本却很高，需要花大量的人力和物力，那么有没有算法能够在这个阶段帮助到你呢？

答案是，有！就是用树模型。

树模型，可以理解为苏格拉底式的诘问，想象不断对一个样本提问：是男用户吗？是的话再问：是北上广的用户吗？不是的话则可以问：是月收入小于 5000 的用户吗？

这种不断提问按照层级组织起来，每次回答答案不同后再提出不同的问题，直到最后得出最终答案：用户对这个推荐会满意吗？

这就是树模型。树模型天然就可以肩负起特征组合的任务，从第一个问题开始，也就是树的根节点，到最后得到答案，也就是叶子节点，这一条路径下来就是若干个特征的组合。

树模型最原始的是决策树，简称 DT，先驱们常常发现，把“多个表现”略好于“随机乱猜”的模型以某种方式集成在一起往往出奇效，所以就有树模型的集成模型。最常见的就是随机森林，简称 RF，和梯度提升决策树，简称 GBDT。

先讲一下剃度提升决策树的原理。按照其名字，我把它分成两部分：一个是 GB，一个是 DT。GB 是得到集成模型的方案，沿着残差梯度下降的方向构建新的子模型，而 DT 就是指构建的子模型要用的决策树。

梯度提升决策树其实本意是用来做回归问题的，怎么回事呢？

举个例子好了。假如这里有以下这么几条样本。

现在有个任务是根据是否喜欢养花，喜欢打游戏，喜欢帽子来预测年龄，模型就是梯度提升决策树 GBDT。假设我们设定好每个子树只有一层，那么三个特征各自按照取值都可以构成两分支的小树枝。

树根节点为：是否喜欢养花，左分支就是不喜欢，被划分进去的样本有 13、14、15，35 这四个年龄；右边的就是样本 25、49、68、71、73。左边的样本均值是 19.25，右边的样本均值是 57.2。

树根节点为：是否喜欢打游戏，左分支是不喜欢，被划分进去就有 49，71，73；右边是喜欢，被划分进去的样本有 13、14、15、25、35、68。左边的均值是 64，右边的均值是 28.3。

树根节点为：是否喜欢帽子，左分支是不喜欢，被划分进去就有 14、15、49、71；右边是喜欢，右边是 13、25、35、68、73，左边均值是 37.25，右边是 42.8。

叶子节点上都是被划分进去的样本年龄均值，也就是预测值。这里是看哪棵树让残差减小最多，分别拿三个方案去预测每个样本，统计累积的误差平方和，三个分别是 1993.55、2602、5007.95，于是显然第一棵树的预测结果较好，所以 GBDT 中第一棵树胜出。

接下来第二棵树如何生成呢？这里就体现出 GBDT 和其他提升算法的不同之处了，比如和 Ada boost 算法不同之处，GBDT 用上一棵树去预测所有样本，得到每一个样本的残差，下一棵树不是去拟合样本的目标值，而是去拟合上一棵树的残差。这里，就是去拟合下面这个表格。

新一轮构建树的过程以最后一列残差为目标。构建过程这里不再赘述，得到第二棵树。如此不断在上一次建树的残差基础上构建新树，直到满足条件后停止。

在得到所有这些树后，真正使用时，是将它们的预测结果相加作为最终输出结果。这就是 GBDT 的简单举例。

这里有两个问题。

第一个，既然是用来做回归的，上面这个例子也是回归问题，如何把它用来做分类呢？那就是把损失函数从上面的误差平方和换成适合分类的损失函数，例如对数损失函数。

更新时按照梯度方向即可，上面的误差平方和的梯度就刚好是残差。对于 CTR 预估这样的二分类任务，可以将损失函数定义为：

−ylog§−(1−y)log(1−p)−ylog§−(1−y)log(1−p)

第二个，通常还需要考虑防止过拟合，也就是损失函数汇总需要增加正则项，正则化的方法一般是：限定总的树个数、树的深度、以及叶子节点的权重大小。

第三个，构建每一棵树时如果遇到实数值的特征，还需要将其分裂成若干区间，分裂指标有很多，可以参考 xgboost 中的计算分裂点收益，也可以参考决策树所用的信息增益。

二者结合

前面介绍了逻辑回归 LR，以及剃度提升决策树 GBDT 的原理。实际上可以将两者结合在一起，用于做模型融合阶段的 CTR 预估。这是 Facebook 在其广告系统中使用的方法，其中 GBDT 的任务就是产生高阶特征组合。

具体的做法是：GBDT 产生了 N 棵树，一条样本来了后，在每一棵树上都会从根节点走到叶子节点，到了叶子节点后，就是 1 或者 0，点或者不点。把每一棵树的输出看成是一个组合特征，取值为 0 或者 1，一共 N 棵树，每棵树 i 有 MiMi 个叶子就相当于有 M 种组合，一棵树对应一个 one-hot（独热）编码方式，一共就有 ∑Ni=1Mi∑i=1NMi 个维度的新特征，作为输入向量进入 LR 模型，输出最终的结果。

示意图如下。

每一条样本，样本内容一般是把用户、物品、场景三类特征拼接在一起，先经过 N 棵 GBDT 树各自预测一下，给出自己的 0 或者 1 的预测结果，接着，这个 N 个预测结果再作为 N 个 one-hot 编码特征拼接成一个向量送入逻辑回归中，产生最终的融合预估结果。

另外，由于两者结合后用来做推荐系统的模型融合，所以也可以考虑在输入特征中加入各个召回模型产生的分数，也许会有用。

以上就是咱们的“辑度组合”原理，虽然简单，但在实际应用中非常的有效。

总结

今天我主要讲了简单的逻辑回归和梯度提升决策树，两者都是不太复杂的模型。并且无论是逻辑回归，还是梯度提升决策树，都有非常成熟的开源实现，可以很快落地。

由于篇幅限制，在梯度提升决策树那部分有一些细节被我略过了，你能自己手算出例子中的第二棵树是什么样的吗？欢迎留言一起讨论。感谢你的收听，我们下期再见。

【深度学习】深度学习在推荐系统中的应用有哪些?

时至今日，深度学习已经不是一个新名词了，由于它的出现，计算机视觉、自然语言理解等领域的从业者都过上了好日子，错误率大幅度降低。

尤其是那些不断号称端到端的建模方式，让还在埋头于特征工程的推荐系统从业者们跃跃欲试，想赶紧引入深度学习大显身手。

经过这些年学界和业界的不断尝试，深度学习在推荐系统中已经有了很多成功的应用。

所以我在这个专栏里面理应本着实用落地的原则给你介绍一下，到底深度学习在推荐系统中有些什么应用，以及到底是怎么回事？

深度学习与推荐系统

深度学习也就是深度神经网络，并不是一个全新的概念，而是枯木逢春；所以它才能在计算力成本下降、效率提升、数据量陡增的今天得以焕发光彩，原来的浅层模型可以逐渐深入，挖掘出事物背后的更多规律和特征。

因此，深度学习的原理在这里并不做过多涉及，如果需要了解，你可以去专攻一下深度学习。

我在这里仅仅用简单的语言力图消除一些概念上的陌生感，在有了一些直观的认识后，直接进入到应用阶段，看看它可以帮助你做什么事。

你还记得矩阵分解吗？矩阵分解是把原来用户和物品之间的大矩阵，分解成了两个小矩阵相乘。这两个小矩阵小在哪？

原始的矩阵中，表示每个用户的向量是物品，表示每个物品的向量是用户，两者向量的维度都特别高不说，还特别稀疏，分解后用户向量和物品向量不但维度变得特别小，而且变稠密了。

业界还把这个稠密的向量叫做隐因子，意图直观说明它的物理意义：用户背后的偏好因子，物品背后的主题因子。

实际上，你完全可以把矩阵分解看成是一种浅层神经网络，只有一层，它的示意图如下。

这个示意图表示了一个用户 Ui，评分过的物品有 I2 和 I4，分解后的矩阵隐因子数量是 2，用户 Ui 的隐因子向量就是 [w1, w2]，物品 I2 的隐因子向量是 [w3, w5]，物品 I4 的隐因子向量是 [w4, w6]。

可以把矩阵分解看成是一个拥有一个隐藏层的神经网络，得到的隐因子向量就是神经网络的连接权重参数。

在前面的专栏中，我第一次提到深度学习时，还建议你把逻辑回归看成一个没有隐藏层的神经网络。因此，深度学习，也就是深度神经网络并不是那么神秘，只是深。这个“深”代表了事物的某些本质属性。

这种对本质属性的挖掘，有两个好处。

可以更加高效且真实地反映出事物本身的样子。对比一下，一张图片用原始的像素点表示，不但占用空间大，而且还不能反应图片更高级的特征，如线条、明暗、色彩，而后者则可以通过一系列的卷积网络学习而得。

可以更加高效真实地反映出用户和物品之间的连接。对比一下，以用户历史点击过的物品作为向量表示用户兴趣；用这些物品背后隐藏的因子表示用户兴趣，显然后者更高效更真实，因为它还考虑了物品本身的相似性，这些信息都压缩到隐因子向量中了，同时再得到物品的隐因子向量，就可以更加直接平滑地算出用户对物品的偏好程度。

这两个好处，正是深度学习可以帮助推荐系统的地方。第一个叫做 Embedding，就是嵌入，第二个叫做 Predicting，就是预测。

其实两者我在前面的内容都已经有涉及了，矩阵分解得到的隐因子向量就是一种 Embedding，Word2vec 也是一种 Embedding，Wide&Deep 则是用来预测的。关于第二种，具体来说有几个方向：深度神经网络的 CTR 预估，深度协同过滤，对时间序列的深度模型。

下面逐一带你认识。首先就是深度学习的第一种应用。

各种 2vec

你还记得在内容推荐那一章里，我跟你提到过，对内容的挖掘怎么深入都不为过，越深入越好，很多时候甚至优于对排序模型的优化。

那里提到了 Word2vec，用于学习词嵌入向量。当把一个词表示成一个稠密的向量后，就可以计算词的相似度，进而可以计算句子的相似度，也可以直接把这个稠密向量作为特征输入给高级的预测模型。

于是，这个 2vec 的思想，就被发扬光大了。首先还是在文本领域，从 Word2vec 到 Sentence2vec，再到 Doc2vec。其实思想都类似甚至会让你觉得有上当受骗的错觉。

简单介绍一下 Word2vec。你知道，Word2Vec 最终是每个词都得到一个稠密向量，十分类似矩阵分解得到的隐因子向量，得到这个向量有两个训练方法。

先说第一个方法，想象你拿着一个滑动窗口，在一篇文档中从左往右滑动，每一次都有 N 个词在这个窗口内，每移动一下，产生 N-1 条样本。

每条样本都是用窗口内一个词去预测窗口正中央那个词，明明窗口内是 N 个词，为什么只有 N-1 条样本呢？因为正中央那个词不用预测它本身啊。这 N-1 条样本的输入特征是词的嵌入向量，预测标签是窗口那个词。示意图如下所示。

图中把 N-1 个样本放在一起示意的，无法看出隐藏层，实际上，输入时每个词可以用 One-hot 方式表示成一个向量，这个向量长度是整个词表的长度，并且只有当前词位置是 1，其他都是 0。

隐藏层的神经元个数就是最终得到嵌入向量的维度数，最终得到的嵌入向量元素值，实际上就是输入层和隐藏层的连接权重。示意图如下。

至于 Word2vec 的第二种训练方法，则是把上述的 N-1 条样本颠倒顺序，用窗口中央的词预测周围的词，只是把输入和输出换个位置，一样可以训练得到嵌入向量。

这里注意，看上去 Word2vec 是构造了一个监督学习任务，但实际上并不是为了得到一个预测模型，在实际中用词预测词，是为了得到词的嵌入向量，Embedding 本身就是目的。

我们沿着 Word2vec 这种学习嵌入向量的思路想，既然词可以表示成一个稠密向量干这干那，那不如来个 Sentence2vec，把一个句子表示成一个嵌入向量，通常是把其包含的词嵌入向量加起来就完事了。

而 Doc2vec 则略微一点点不同，说明一点，多个句子构成一个段落，所以这里的 Doc 其实就是段落。Doc2vec 在窗口滑动过程中构建 N-1 条样本时，还增加一条样本，就是段落 ID 预测中央那个词，相当于窗口滑动一次得到 N 条样本。

一个段落中有多少个滑动窗口，就得到多少条关于段落 ID 的样本，相当于这个段落中，段落 ID 在共享嵌入向量。段落 ID 像个特殊的词一样，也得到属于自己的嵌入向量，也就是 Doc2vec。

理解了 Word2vec 之后，它在推荐系统中的应用就有举一反三的应用。第一个就是 Product2vec，你看这名字就知道我要干嘛了，对，就是给商品学习一个嵌入向量。

这简直就是照着词嵌入的做法来，把用户按照时间先后顺序加入到购物车的商品，看成一个一个的词，一个购物车中所有的商品就是一个文档；于是照猫画虎学出每个商品的嵌入向量，用于去做相关物品的推荐，或者作为基础特征加入到其他推荐排序模型中使用。

类似的，如果是应用商场的 App 推荐，也可以依计行事，把用户的下载序列看成文档，学习每个 App 的嵌入向量。

虽然，这个嵌入学习得到的结果，样子和矩阵分解得到隐因子向量一样，但是机制不同，可以两者都有，拼接成一个更大的稠密向量去做你喜欢做的事，比如 CTR 预估。

且慢，各种 2vec 的做法其实还不算深度学习，毕竟隐藏层才一层而已。如果要用更深的模型学习嵌入向量，就是深度学习中的 AutoEncoder。

它是一种输入和输出一模一样的神经网络，这个神经网络就一个目的，更加清楚地认识自己，在这个优化目标指导下，学到的网络连接权重都是不同的嵌入向量，所以也叫做 AutoEncoder，自动编码器。

从输入数据逐层降维，相当于是一个对原始数据的编码过程，到最低维度那一层后开始逐层增加神经元，相当于是一个解码过程，解码输出要和原始数据越接近越好，相当于在大幅度压缩原始特征空间的前提下，压缩损失越小越好。

以上是深度学习如何通过学习更好地表达事物特征，帮助推荐系统取得更好效果的做法。

YouTube 视频推荐

以 YouTube 为例，它们在自己的推荐系统大量用到了深度学习，用于推荐更好的视频给用户。我来给你仔细描述一下，具体到视频推荐场景，深度学习可以在哪些地方用到。

首先，Youtube 把推荐的预测任务看成是一个多分类，这个和之前常规的推荐系统要么预测行为要么预测评分的做法不太一样，而是把候选物品当成多个类别，预测用户下一个会观看哪个视频。

P(wt=i|U,C)=eviu∑j∈VevjuP(wt=i|U,C)=eviu∑j∈Vevju

这个公式中 U 是用户 C 是场景，输入是视频的嵌入向量和用户的嵌入向量。这里就涉及了先要使用深度神经网络，从用户历史反馈行为和场景信息中学习物品和用户的嵌入向量。整个推荐排序模型示意图如下。

看这个推荐模型示意图，就可以看到深度学习应用在了哪些地方。

根据观看历史把视频变成了嵌入向量，然后平均后作为输入特征之一，这个和前面的 Product2vec 的思路一致，把观看历史看成文档，观看的视频看成词。

搜索 Query 也变成了嵌入向量，平均之后作为输入特征之二。

人口统计学信息统统都嵌入了。

还加入视频的年龄信息，也就是在预测时，视频上传多久了。

所有这些不同的嵌入向量拼接成一个大的输入向量，经过深度神经网络，在输出层以 Softmax 作为输出函数，预测下一个观看视频。

在模型训练时，以 Softmax 作为输出层，但是在实际线上预测服务时，由于模型关心相对顺序，所以并不需要真的去计算 Softmax，而是拿着用户的特征向量做近似的近邻搜索，只生成最相近的一些推荐结果。

整个推荐系统非常好理解，也比较好落地，所有的模型都可以通过 TensorFlow 快速实现。

总结

通过观察 YouTube 的推荐系统中所用到的深度学习来看，在排序方面，深度神经网络已经崭露头角，包括前面讲融合模型时，专门讲到的 Wide&Deep 模型，也是深度学习在排序方面的贡献。

除此之外，深度学习更多发挥作用的地方是特征表达上，各种嵌入技术得以让物品、用户、关系等对象的特征化有更好的输出。

今天主要介绍了深度学习在推荐系统可以发挥哪些作用，同时以 YouTube 为例，介绍了国际大厂在这方面的落地情况。

从 YouTube 的推荐系统可以看出，深度学习主要贡献在于特征表达学习和排序模型上。当然，深度学习纵有千般好，如果你的数据量少得可怜，那么你也只能过过眼瘾，很难真正落地执行。

给你留一个思考题，我们一起交流。为什么 YouTube 排序模型训练时规规矩矩按照 Softmax 输出，以交叉熵作为目标函数，但在线上运行时，却可以按近邻搜索去近似，背后有什么考虑？欢迎留言一起讨论。

【深度学习】用RNN构建个性化音乐播单

时间是一个客观存在的物理属性，很多数据都有时间属性，只不过大多时候都把它忽略掉了。前面讲到的绝大多数推荐算法，也都没有考虑“用户在产品上作出任何行为”都是有时间先后的。

正是认识到这一点，有一些矩阵分解算法考虑了时间属性，比如 Time-SVD；但是，这种做法只是把时间作为一个独立特征加入到模型中，仍然没有给时间一个正确的名分。

时间的重要性

时间属性反应在序列的先后上，比如用户在视频网站上观看电视剧会先看第一集再看第二集，股市数据先有昨天的再有今天的，说“我订阅了《推荐系统 36 式》专栏”这句话时，词语也有先后，这种先后的关系就是时间序列。

具体到推荐系统领域，时间序列就是用户操作行为的先后。绝大数推荐算法都忽略操作的先后顺序，为什么要采取这样简化的做法呢？因为一方面的确也能取得不错的效果，另一方面是深度学习和推荐系统还迟迟没有相见。

在深度学习大火之后，对时间序列建模被提上议事日程，业界有很多尝试，今天以 Spotify 的音乐推荐为例，介绍循环神经网络在推荐系统中的应用。

循环神经网络

循环神经网络，也常被简称为 RNN，是一种特殊的神经网络。再回顾一下神经网络的结构，示意图如下：

普通神经网络有三个部分，输入层 x，隐藏层 h，输出层 o，深度神经网络的区别就是隐藏层数量有很多，具体多少算深，这个可没有定论，有几层的，也有上百层的。

把输入层和隐藏层之间的关系表示成公式后就是：

h=F(Wx)h=F(Wx)

就是输入层 x 经过连接参数线性加权后，再有激活函数 F 变换成非线性输出给输出层。

在输出层就是：

O=ϕ(Vh)O=ϕ(Vh)

隐藏层输出经过输出层的网络连接参数线性加权后，再由输出函数变换成最终输出，比如分类任务就是 Softmax 函数。

那循环神经网络和普通神经网络的区别在哪？

区别就在于：普通神经网络的隐藏层参数只有输入 x 决定，因为当神经网络在面对一条样本时，这条样本是孤立的，不考虑前一个样本是什么，循环神经网络的隐藏层不只是受输入 x 影响，还受上一个时刻的隐藏层参数影响。

我们把这个表示成示意图如下：

解释一下这个示意图。在时刻 t，输入是 xt，而隐藏层的输出不再是只有输入层 xt，还有时刻 t-1 的隐藏层输出 h(t-1)，表示成公式就是：

ht=F(Wxt+Uht−1)ht=F(Wxt+Uht−1)

对比这个公式和前面普通神经网络的隐藏层输出，就是在激活函数的输入处多了一个 Uht−1Uht−1 。别小看多这一个小东西，它背后的意义非凡。

我一直在传递一个观点，隐藏层的东西，包括矩阵分解和各种 Embedding 得到的隐因子，是对很多表面纷繁复杂的现象所做的信息抽取和信息压缩。

那么上一个时刻得到的隐藏层，就是对时间序列上一个时刻的信息压缩，让它参与到这一个时刻的隐藏层建设上来，物理意义就是认为现在这个时刻的信息不只和现在的输入有关，还和上一个时刻的状态有关。这是时间序列本来的意义，也就是循环神经网络的意义。

播单生成

了解了循环神经网络原理之后，我再和你一起来看下它如何应用在推荐系统中的。

在网络音乐推荐中，尤其是各类 FM 类 App，提倡的是一直听下去，比如是你在做家务时，你在开车时，一首歌接着一首歌地播下去，就很适合这些场景。

通常要做到这样的效果，有这么几种做法。

电台音乐 DJ 手工编排播单，然后一直播放下去，传统广播电台都是这样的。

用非时序数据离线计算出推荐集合，然后按照分数顺序逐一输出。

利用循环神经网络，把音乐播单的生成看成是歌曲时间序列的生成，每一首歌的得到不但受用户当前的特征影响，还受上一首歌影响。

Spotify 采用了第三种办法，下面我就详细讲解这个推荐算法。

1. 数据

个性化的播单生成，不再是推荐一个一个独立的音乐，而是推荐一个序列给用户。所用的数据就是已有播单，或者用户的会话信息。其中用户会话信息的意思就是，当一个用户在 App 上所做的一系列操作。

把这些数据，看成一个一个的文档，每一个音乐文件就是一个一个的词。听完什么再听什么，就像是语言中的词和词的关系。

2. 建模

你可以把播单生成看成由若干步骤组成，每一步吐出一个音乐来。这个吐出音乐的动作实际上是一个多分类问题，类别数目就是总共可以选择的音乐数目，如果有 100 万首歌可以选择，那么就是一个 100 万分类任务。

这个分类任务计算输入是当前神经网络的隐藏状态，然后每一首歌都得到一个线性加权值，再由 Softmax 函数为每一首歌计算得到一个概率。表示如下：

p(oti|ht)=evih∑j∈Mevjhp(oti|ht)=evih∑j∈Mevjh

假如隐藏层有 k 个神经元，也就是说 h 是一个 k 维向量，输出层有 m 首歌可选，所以是一个 One-hot 编码的向量，也就是说一个 m 维向量，只有真正输出那首歌 i 是 1，其他都是 0，那么输出层就有 k 乘以 m 个未知参数。

再往前，计算隐藏层神经元输出时，不但用到输入层的信息，在这里，输入层也是一首歌，也有 m 首歌可以选择，所以输入向量仍然是一个 One-hot 编码的向量。

除此之外，每一个隐藏层神经元还依赖上一个时刻自己的输出值，隐藏层神经元是 k 个，一个 k 维向量。

按照隐藏层计算公式就是下面的样子。

ht=F(Wxt+Uht−1)ht=F(Wxt+Uht−1)

W 就是一个 m 乘以 k 的参数矩阵，U 就是一个 k 乘以 k 的参数矩阵。

如此一来，循环神经网络在预测时的计算过程就是：

当用户听完一首歌，要预测下一首歌该推荐什么时，输入就是一个 One-hot 编码的 m 维度向量，用 m 乘以 k 形状的输入层参数矩阵，乘以这个 m 向量，然后用隐藏层之间的 k 乘 k 参数矩阵，去乘以上一个隐藏状态向量，两者都得到一个 k 维向量，相加后经过非线性激活函数，比如 ReLU，这样就得到当前时刻的隐藏层输出值。

再用当前时刻的隐藏层输出值，经过 k 乘以 m 形状的输出层参数矩阵，得到一个 m 维向量，再用 Softmax 把这个 m 维向量归一化成概率值，就是对下一首歌的预测，可以挑选最大概率的若干首歌作为输出，或者直接输出概率最高的那首歌直接播放。

这个计算过程示意图如下：

一个播单生成模型的参数就是这么三大块。

连接输入和隐藏之间的矩阵 Wm×kWm×k；

连接上一个隐藏状态和当前隐藏状态的矩阵： Uk×kUk×k；

连接隐藏层和输出层的矩阵 Vk×mVk×m。

得到了这些参数，就得到了播单推荐模型，怎么得到呢？这里就再简要讲一下神经网络的参数如何训练得到。

你知道一个简单的逻辑回归模型参数如何训练得到吗？大致是这样几步：

初始化参数；

用当前的参数预测样本的类别概率；

用预测的概率计算交叉熵；

用交叉熵计算参数的梯度；

用学习步长和梯度更新参数；

迭代上述过程直到满足设置的条件。

神经网络的参数学习大致也是这个过程，但略为复杂的地方就是第 4 步和第 5 步，因为逻辑回归没有隐藏层，神经网络有隐藏层。那怎么办呢？我不打算讲解具体的做法，我打算给你建立一个直观印象。

还记得下面这个函数对 x 求导是怎么计算的吗？

f(x)=g(x)2;f(x)=g(x)2;

g(x)=exg(x)=ex

函数 f(x) 是另一个函数 gx 的平方，函数 g(x) 又是一个指数函数。那么要对 f(x0 求导，就是一个链式规则，先把 g(x) 看成个一个整体求导，再乘以 g(x) 的求导结果：

f′(x)=2g(x)ex=2exex=2e2xf′(x)=2g(x)ex=2exex=2e2x

你就需要记住一点：链式规则，一路求导下去。

现在回到神经网路的训练，这个方法有个高大上的名字，叫做误差方向传播。

实际上就是链式求导法则，因为要更新参数，就需要计算参数在当前取值时的梯度，要计算梯度就要求导，要求导就要从交叉熵函数开始，先对输出层参数求导计算梯度，更新输出层参数，接着链式下去，对输入层参数求导计算梯度，更新输入层参数。

交叉熵是模型的目标函数，训练模型的目的就是要最小化它，也就是“误差反向传播”的“误差”。

相信聪明如你已经在直观上理解了一个普通神经网络是怎么训练的了，那么一个循环神经网络的参数训练有何不同呢？唯一不同就是多了一个参数矩阵，连接当前隐藏层和上一次隐藏层的参数矩阵 U，也是链式求导法则的传播路径，也就是多了一些求导计算，更新参数方式并没有什么不同。

总结

好了，今天介绍了如何使用循环神经网络推荐音乐播单，播单是一个时间序列，听完上一首歌会影响下一首歌。

循环神经网络和普通神经网络相比，就是在两个时刻的隐藏状态之间多了网络连接。看上去这个网络连接只与上一个时刻有关，事实上，上一个状态又与上上个状态有关，所以实际上任意一个时刻的状态是与此前所有的状态有关的。

今天的应用虽然是以播单推荐为例，但其实循环神经网络还可以应用在很多其他地方，你对循环神经网络的应用有任何问题都可以留言给我，我们一起讨论。

感谢你的收听，我们下期再见。

【矩阵分解】Facebook是怎么为十亿人互相推荐好友的

上一篇中，我和你专门聊到了矩阵分解，在这篇文章的开始，我再为你回顾一下矩阵分解。

回顾矩阵分解

矩阵分解要将用户物品评分矩阵分解成两个小矩阵，一个矩阵是代表用户偏好的用户隐因子向量组成，另一个矩阵是代表物品语义主题的隐因子向量组成。

这两个小矩阵相乘后得到的矩阵，维度和原来的用户物品评分矩阵一模一样。比如原来矩阵维度是 m x n，其中 m 是用户数量，n 是物品数量，再假如分解后的隐因子向量是 k 个，那么用户隐因子向量组成的矩阵就是 m x k，物品隐因子向量组成的矩阵就是 n x k。

得到的这两个矩阵有这么几个特点：

每个用户对应一个 k 维向量，每个物品也对应一个 k 维向量，就是所谓的隐因子向量，因为是无中生有变出来的，所以叫做“隐因子”；

两个矩阵相乘后，就得到了任何一个用户对任何一个物品的预测评分，具体这个评分靠不靠谱，那就是看功夫了。

所以矩阵分解，所做的事就是矩阵填充。那到底怎么填充呢，换句话也就是说两个小矩阵怎么得到呢？

按照机器学习的套路，就是使用优化算法求解下面这个损失函数：

minq∗,p∗∑(u,i)∈κ(rui−puqTi)2+λ(||qi||2+||pu||2)minq∗,p∗∑(u,i)∈κ(rui−puqiT)2+λ(||qi||2+||pu||2)

这个公式依然由两部分构成：加号左边是误差平方和，加号右边是分解后参数的平方。

这种模式可以套在几乎所有的机器学习训练中：就是一个负责衡量模型准不准，另一个负责衡量模型稳不稳定。行话是这样说的：一个衡量模型的偏差，一个衡量模型的方差。偏差大的模型欠拟合，方差大的模型过拟合。

有了这个目标函数后，就要用到优化算法找到能使它最小的参数。优化方法常用的选择有两个，一个是随机梯度下降（SGD），另一个是交替最小二乘（ALS）。

在实际应用中，交替最小二乘更常用一些，这也是社交巨头 Facebook 在他们的推荐系统中选择的主要矩阵分解方法，今天，我就专门聊一聊交替最小二乘求矩阵分解。

交替最小二乘原理 (ALS)

交替最小二乘的核心是交替，什么意思呢？你的任务是找到两个矩阵 P 和 Q，让它们相乘后约等于原矩阵 R：

Rm×n=Pm×k×QTn×kRm×n=Pm×k×Qn×kT

难就难在，P 和 Q 两个都是未知的，如果知道其中一个的话，就可以按照线性代数标准解法求得，比如如果知道了 Q，那么 P 就可以这样算：

Pm×k=Rm×n×Q−1n×kPm×k=Rm×n×Qn×k−1

也就是 R 矩阵乘以 Q 矩阵的逆矩阵就得到了结果。

反之知道了 P 再求 Q 也一样。交替最小二乘通过迭代的方式解决了这个鸡生蛋蛋生鸡的难题：

初始化随机矩阵 Q 里面的元素值；

把 Q 矩阵当做已知的，直接用线性代数的方法求得矩阵 P；

得到了矩阵 P 后，把 P 当做已知的，故技重施，回去求解矩阵 Q；

上面两个过程交替进行，一直到误差可以接受为止。

你看吧，机器就是这么单纯善良，先用一个假的结果让算法先运转起来，然后不断迭代最终得到想要的结果。这和做互联网 C2C 平台的思路也一样，告诉买家说：快来这里，我们是万能的，什么都能买到！

买家来了后又去告诉卖家们说：快来这里开店，我这里掌握了最多的剁手党。嗯，雪球就这样滚出来了。

交替最小二乘有这么几个好处：

在交替的其中一步，也就是假设已知其中一个矩阵求解另一个时，要优化的参数是很容易并行化的；

在不那么稀疏的数据集合上，交替最小二乘通常比随机梯度下降要更快地得到结果，事实上这一点就是我马上要说的，也就是关于隐式反馈的内容。

隐式反馈

矩阵分解算法，是为解决评分预测问题而生的，比如说，预测用户会给商品打几颗星，然后把用户可能打高星的商品推荐给用户，然而事实上却是，用户首先必须先去浏览商品，然后是购买，最后才可能打分。

相比“预测用户会打多少分”，“预测用户会不会去浏览”更加有意义，而且，用户浏览数据远远多于打分评价数据。也就是说，实际上推荐系统关注的是预测行为，行为也就是一再强调的隐式反馈。

那如何从解决评分预测问题转向解决预测行为上来呢？这就是另一类问题了，行话叫做 One-Class。

这是什么意思呢？如果把预测用户行为看成一个二分类问题，猜用户会不会做某件事，但实际上收集到的数据只有明确的一类：用户干了某件事，而用户明确“不干”某件事的数据却没有明确表达。所以这就是 One-Class 的由来，One-Class 数据也是隐式反馈的通常特点。

对隐式反馈的矩阵分解，需要将交替最小二乘做一些改进，改进后的算法叫做加权交替最小二乘：Weighted-ALS。

这个加权要从哪说起？用户对物品的隐式反馈，通常是可以多次的，你有心心念念的衣服或者电子产品，但是刚刚剁完手的你正在吃土买不起，只能每天去看一眼。

这样一来，后台就记录了你查看过这件商品多少次，查看次数越多，就代表你越喜欢这个。也就是说，行为的次数是对行为的置信度反应，也就是所谓的加权。

加权交替最小二乘这样对待隐式反馈：

如果用户对物品无隐式反馈则认为评分是 0；

如果用户对物品有至少一次隐式反馈则认为评分是 1，次数作为该评分的置信度。

那现在的目标函数在原来的基础上变成这样：

minq∗,p∗∑(u,i)∈κcui(rui−puqTi)2+λ(||qi||2+||pu||2)minq∗,p∗∑(u,i)∈κcui(rui−puqiT)2+λ(||qi||2+||pu||2)

多出来的 Cui 就是置信度，在计算误差时考虑反馈次数，次数越多，就越可信。置信度一般也不是直接等于反馈次数，根据一些经验，置信度 Cui 这样计算：

cui=1+αCcui=1+αC

其中阿尔法是一个超参数，需要调教，默认值取 40 可以得到差不多的效果，C 就是次数了。

这里又引出另一个问题，那些没有反馈的缺失值，就是在我们的设定下，取值为 0 的评分就非常多，有两个原因导致在实际使用时要注意这个问题：

本身隐式反馈就只有正类别是确定的，负类别是我们假设的，你要知道，One-Class 并不是随便起的名字；

这会导致正负类别样本非常不平衡，严重倾斜到 0 评分这边。

因此，不能一股脑儿使用所有的缺失值作为负类别，矩阵分解的初心就是要填充这些值，如果都假设他们为 0 了，那就忘记初心了。应对这个问题的做法就是负样本采样：挑一部分缺失值作为负类别样本即可。

怎么挑？有两个方法：

随机均匀采样和正类别一样多；

按照物品的热门程度采样。

请允许我直接说结论，第一种不是很靠谱，第二种在实践中经过了检验。

还是回到初心来，你想一想，在理想情况下，什么样的样本最适合做负样本？

就是展示给用户了，他也知道这个物品的存在了，但就是没有对其作出任何反馈。问题就是很多时候不知道到底是用户没有意识到物品的存在呢，还是知道物品的存在而不感兴趣呢？

因此按照物品热门程度采样的思想就是：一个越热门的物品，用户越可能知道它的存在。那这种情况下，用户还没对它有反馈就表明：这很可能就是真正的负样本。

按照热门程度采样来构建负样本，在实际中是一个很常用的技巧，我之前和你提到的文本算法 Word2Vec 学习过程，也用到了类似的负样本采样技巧。

推荐计算

在得到了分解后的矩阵后，相当于每个用户得到了隐因子向量，这是一个稠密向量，用于代表他的兴趣。同时每个物品也得到了一个稠密向量，代表它的语义或主题。而且可以认为这两者是一一对应的，用户的兴趣就是表现在物品的语义维度上的。

看上去，让用户和物品的隐因子向量两两相乘，计算点积就可以得到所有的推荐结果了。但是实际上复杂度还是很高，尤其对于用户数量和物品数量都巨大的应用，如 Facebook，就更不现实。于是 Facebook 提出了两个办法得到真正的推荐结果。

第一种，利用一些专门设计的数据结构存储所有物品的隐因子向量，从而实现通过一个用户向量可以返回最相似的 K 个物品。

Facebook 给出了自己的开源实现 Faiss，类似的开源实现还有 Annoy，KGraph，NMSLIB。

其中 Facebook 开源的 Faiss 和 NMSLIB（Non-Metric Space Library）都用到了 ball tree 来存储物品向量。

如果需要动态增加新的物品向量到索引中，推荐使用 Faiss，如果不是，推荐使用 NMSLIB 或者 KGraph。用户向量则可以存在内存数据中，这样可以在用户访问时，实时产生推荐结果。

第二种，就是拿着物品的隐因子向量先做聚类，海量的物品会减少为少量的聚类。然后再逐一计算用户和每个聚类中心的推荐分数，给用户推荐物品就变成了给用户推荐物品聚类。

得到给用户推荐的聚类后，再从每个聚类中挑选少许几个物品作为最终推荐结果。这样做的好处除了大大减小推荐计算量之外，还可以控制推荐结果的多样性，因为可以控制在每个类别中选择的物品数量。

总结

在真正的推荐系统的实际应用中，评分预测实际上场景很少，而且数据也很少。因此，相比预测评分，预测“用户会对物品干出什么事”，会更加有效。

然而这就需要对矩阵分解做一些改进，加权交替最小二乘就是改进后的矩阵分解算法，被 Facebook 采用在了他们的推荐系统中，这篇文章里，我也详细地解释了这一矩阵分解算法在落地时的步骤和注意事项。

其中，我和你提到了针对 One-Class 这种数据集合，一种常用的负样本构建方法是根据物品的热门程度采样，你能想到还有哪些负样本构建方法吗？欢迎留言一起讨论。感谢你的收听，我们下次再见。

【矩阵分解】如果关注排序效果，那么这个模型可以帮到你

矩阵分解在推荐系统中的地位非常崇高，恐怕本专栏介绍的其他算法模型都不能轻易地撼动它。

它既有协同过滤的血统，又有机器学习的基因，可以说是非常优秀了；但即便如此，传统的矩阵分解无论是在处理显式反馈，还是处理隐式反馈都让人颇有微词，这一点是为什么呢？

矩阵分解的不足

前面我讲过的两种矩阵分解，本质上都是在预测用户对一个物品的偏好程度，哪怕不是预测评分， 只是预测隐式反馈，也难逃这个事实，因为算法展现出来的目标函数就出卖了这一切。

得到这样的矩阵分解结果后，常常在实际使用时，又是用这个预测结果来排序。所以，从业者们口口声声宣称想要模型的预测误差最小化，结果绕了一大圈最后还是只想要一个好点的排序，让人不禁感叹：人心总是难测。

这种针对单个用户对单个物品的偏好程度进行预测，得到结果后再排序的问题，在排序学习中的行话叫做 point-wise，其中 point 意思就是：只单独考虑每个物品，每个物品像是空间中孤立的点一样。

与之相对的，还有直接预测物品两两之间相对顺序的问题，就叫做 pair-wise，pair，顾名思义就是成对成双，也许恐怕这类模型对单身的人士不是很友好。

前面讲的矩阵分解都属于 point-wise 模型。这类模型的尴尬是：只能收集到正样本，没有负样本，于是认为缺失值就是负样本，再以预测误差为评判标准去使劲逼近这些样本。逼近正样本没问题，但是同时逼近的负样本只是缺失值而已，还不知道真正呈现在用户面前，到底是不喜欢还是喜欢呢？

虽然这些模型采取了一些措施来规避这个问题，比如负样本采样，但是尴尬还是存在的，为了排序而绕路也是事实。

既然如此，能不能直面问题，采用 pair-wise 来看待矩阵分解呢？当然能，不然我也不会写出这一篇专栏文章了。

其实人在面对选择时，总是倾向矮子中选高个子，而不是真的在意身高到底是不是 180，因此，更直接的推荐模型应该是：能够较好地为用户排列出更好的物品相对顺序，而非更精确的评分。

这个问题已经有可爱的从业者们提出了方法，就是本文的主角：贝叶斯个性化排序，简称 BPR 模型。下面，我就带你一探这个模型的究竟。

贝叶斯个性化排序

在前面的专栏文章中，有一个词叫做均方根误差，被我提过多次，用于评价模型预测精准程度的。那么现在要关注的是相对排序，用什么指标比较好呢？答案是 AUC，AUC 全称是 Area Under Curve，意思是曲线下的面积，这里的曲线就是 ROC 曲线。

AUC

但是，我不打算继续解释什么是 ROC 曲线了，那是它的原始定义，而我想跟你悄悄说的是另一件事，AUC 这个值在数学上等价于：模型把关心的那一类样本排在其他样本前面的概率。最大是 1，完美结果，而 0.5 就是随机排列，0 就是完美地全部排错。

听到这个等价的 AUC 解释，你是不是眼前一亮？这个非常适合用来评价模型的排序效果，比如说，得到一个推荐模型后，按照它计算的分数，能不能把用户真正想消费的物品排在前面？这在模型上线前是可以用日志完全计算出来的。

AUC 怎么计算呢？一般步骤如下。

用模型给样本计算推荐分数，比如样本都是用户和物品这样一对一对的，同时还包含了有无反馈的标识；

得到打过分的样本，每条样本保留两个信息，第一个是分数，第二个是 0 或者 1，1 表示用户消费过，是正样本，0 表示没有，是负样本；

按照分数对样本重新排序，降序排列；

给每一个样本赋一个排序值，第一位 r1 = n，第二位 r2 = n-1，以此类推；其中要注意，如果几个样本分数一样，需要将其排序值调整为他们的平均值；

最终按照下面这个公式计算就可以得到 AUC 值。

我在文稿中放了这个公式，你可以点击查看。

AUC=∑i∈(样本)ri−M×(M+1)2M×NAUC=∑i∈(样本)ri−M×(M+1)2M×N

这个公式看上去复杂，其实很简单，由两部分构成：

第一部分： 分母是所有我们关心的那类样本，也就是正样本，有 M 个，以及其他样本有 N 个，这两类样本相对排序总共的组合可能性，是 M x N；

第二部分： 分子也不复杂，原本是这样算的：第一名的排序值是 r1，它在排序上不但比过了所有的负样本，而且比过了自己以外的正样本。

但后者是自己人，所以组合数要排除，于是就有 n - M 种组合，以此类推，排序值为 rM 的就贡献了 rM - 1，把这些加起来就是分子。

关于 AUC，越接近 1 越好是肯定的，但是并不是越接近 0 就越差，最差的是接近 0.5，如果 AUC 很接近 0 的话，只需要把模型预测的结果加个负号就能让 AUC 接近 1，具体的原因自行体会。

好了，已经介绍完排序的评价指标了，该主角出场了，BPR 模型，它提出了一个优化准则和学习框架，使得原来传统的矩阵分解放进来能够焕发第二春。

那到底 BPR 做了什么事情呢？主要有三点：

一个样本构造方法；

一个模型目标函数；

一个模型学习框架。

通过这套三板斧，便可以脱离评分预测，来做专门优化排序的矩阵分解。下面详细说说这三板斧。

构造样本

前面介绍的矩阵分解，在训练时候处理的样本是：用户、物品、反馈，这样的三元组形式。

其中反馈又包含真实反馈和缺失值，缺失值充当的是负样本职责。BPR 则不同，提出要关心的是物品之间对于用户的相对顺序，于是构造的样本是：用户、物品 1、物品 2、两个物品相对顺序，这样的四元组形式，其中，“两个物品的相对顺序”，取值是：

如果物品 1 是消费过的，而物品 2 不是，那么相对顺序取值为 1，是正样本；

如果物品 1 和物品 2 刚好相反，则是负样本；

样本中不包含其他情况：物品 1 和物品 2 都是消费过的，或者都是没消费过的。

这样一来，学习的数据是反应用户偏好的相对顺序，而在使用时，面对的是所有用户还没消费过的物品，这些物品仍然可以在这样的模型下得到相对顺序，这就比三元组 point-wise 样本要直观得多。

目标函数

现在，每条样本包含的是两个物品，样本预测目标是两个物品的相对顺序。按照机器学习的套路，就该要上目标函数了。

要看 BPR 怎么完成矩阵分解，你依然需要像交替最小二乘那样的思想。

先假装矩阵分解结果已经有了，于是就计算出用户对于每个物品的推荐分数，只不过这个推荐分数可能并不满足均方根误差最小，而是满足物品相对排序最佳。

得到了用户和物品的推荐分数后，就可以计算四元组的样本中，物品 1 和物品 2 的分数差，这个分数可能是正数，也可能是负数，也可能是 0。

你和我当然都希望的情况是：如果物品 1 和物品 2 相对顺序为 1，那么希望两者分数之差是个正数，而且越大越好；如果物品 1 和物品 2 的相对顺序是 0，则希望分数之差是负数，且越小越好。

用个符号来表示这个差：Xu12，表示的是对用户 u，物品 1 和物品 2 的矩阵分解预测分数差。然后再用 sigmoid 函数把这个分数差压缩到 0 到 1 之间。

Θ=11+e−(Xu12)Θ=11+e−(Xu12)

也其实就是用这种方式预测了物品 1 排在物品 2 前面的似然概率，所以最大化交叉熵就是目标函数了。

目标函数通常还要防止过拟合，加上正则项，正则项其实认为模型参数还有个先验概率，这是贝叶斯学派的观点，也是 BPR 这个名字中“贝叶斯”的来历。

BPR 认为模型的先验概率符合正态分布，对应到正则化方法就是 L2 正则，这些都属于机器学习的内容，这里不展开讲。

我来把目标函数写一下：

∏u,i,jp(i>uj|θ)p(θ)∏u,i,jp(i>uj|θ)p(θ)

所有样本都计算：模型参数先验概率 p theta，和似然概率的乘积，最大化这个目标函数就能够得到分解后的矩阵参数，其中 theta 就是分解后的矩阵参数。

最后说一句，把这个目标函数化简和变形后，和把 AUC 当成目标函数是非常相似的，也正因为如此，BPR 模型的作者敢于宣称该模型是为 AUC 而生的。

训练方法

有了目标函数之后，就要有请训练方法了。显然是老当益壮的梯度下降可以承担这件事，梯度下降又有批量梯度和随机梯度下降两个选择，前者收敛慢，后者训练快却不稳定。因此 BPR 的作者使用了一个介于两者之间的训练方法，结合重复抽样的梯度下降。具体来说是这样做的：

从全量样本中有放回地随机抽取一部分样本；

用这部分样本，采用随机梯度下降优化目标函数，更新模型参数；

重复步骤 1，直到满足停止条件。

这样，就得到了一个更符合推荐排序要求的矩阵分解模型了。

总结

今天是矩阵分解三篇的最后一篇，传统的矩阵分解，无论是隐式反馈还是显式反馈，都是希望更加精准地预测用户对单个物品的偏好，而实际上，如果能够预测用户对物品之间的相对偏好，则更加符合实际需求的直觉。

BPR 就是这样一整套针对排序的推荐算法，它事实上提出了一个优化准则和一个学习框架，至于其中优化的对象是不是矩阵分解并不是它的重点。

但我在这里结合矩阵分解对其做了讲解，同时还介绍了排序时最常用的评价指标 AUC 及其计算方法。

你在看了 BPR 算法针对矩阵分解的推荐计算过程之后，试着想一想，如果不是矩阵分解，而是近邻模型，那该怎么做？欢迎留言给我，一起聊聊。

【矩阵分解】那些在Netflix Prize中大放异彩的推荐算法

早在前几篇务虚的文章中，我就和你聊过了推荐系统中的经典问题，其中有一类就是评分预测。

让我摸着自己的良心说，评分预测问题只是很典型，其实并不大众，毕竟在实际的应用中，评分数据很难收集到，属于典型的精英问题；与之相对的另一类问题行为预测，才是平民级推荐问题，处处可见。

缘起

评分预测问题之所以“虽然小众却十分重要”，这一点得益于十多年前 Netflix Prize 的那一百万美元的悬赏效应。

公元 2006 年 10 月 2 号，对于很多人来说，这只是平凡了无新意的一天，但对于推荐系统从业者来说，这是不得了的一天，美国著名的光盘租赁商 Netflix 突然广发英雄帖，放下“豪”言，这个就是土豪的“豪”，凡是能在他们现有推荐系统基础上，把均方根误差降低 10% 的大侠，可以瓜分 100 万美元。消息一出，群贤毕至。

Netflix 放出的比赛数据，正是评分数据，推荐系统的问题模式也是评分预测，也就是为什么说，评价标准是均方根误差了。

这一评分预测问题在一百万美元的加持下，催生出无数推荐算法横空出世，其中最为著名的就是一系列矩阵分解模型，而最最著名的模型就是 SVD 以及其各种变体。这些模型后来也经受了时间检验，在实际应用中得到了不同程度的开枝散叶。

今天我就来和你细聊一下矩阵分解，SVD 及其最有名的变种算法。

矩阵分解

为什么要矩阵分解

聪明的你也许会问，好好的近邻模型，一会儿基于用户，一会儿基于物品，感觉也能很酷炫地解决问题呀，为什么还要来矩阵分解呢？

刨除不这么做就拿不到那一百万的不重要因素之外，矩阵分解确实可以解决一些近邻模型无法解决的问题。

我们都是读书人，从不在背后说模型的坏话，这里可以非常坦诚地说几点近邻模型的问题：

物品之间存在相关性，信息量并不随着向量维度增加而线性增加；

矩阵元素稀疏，计算结果不稳定，增减一个向量维度，导致近邻结果差异很大的情况存在。

上述两个问题，在矩阵分解中可以得到解决。矩阵分解，直观上说来简单，就是把原来的大矩阵，近似分解成两个小矩阵的乘积，在实际推荐计算时不再使用大矩阵，而是使用分解得到的两个小矩阵。

具体说来就是，假设用户物品的评分矩阵 A 是 m 乘以 n 维，即一共有 m 个用户，n 个物品。我们选一个很小的数 k，这个 k 比 m 和 n 都小很多，比如小两个数量级这样，通过一套算法得到两个矩阵 U 和 V，矩阵 U 的维度是 m 乘以 k，矩阵 V 的维度是 n 乘以 k。

这两个矩阵有什么要求呢？要求就是通过下面这个公式复原矩阵 A，你可以点击文稿查看公式。

Um×kVTn×k≈Am×nUm×kVn×kT≈Am×n

类似这样的计算过程就是矩阵分解，还有一个更常见的名字叫做 SVD；但是，SVD 和矩阵分解不能划等号，因为除了 SVD 还有一些别的矩阵分解方法。

1 基础的 SVD 算法

值得一说的是，SVD 全称奇异值分解，属于线性代数的知识 ; 然而在推荐算法中实际上使用的并不是正统的奇异值分解，而是一个伪奇异值分解（具体伪在哪不是本文的重点）。

今天我介绍的 SVD 是由 Netflix Prize 中取得骄人成绩的 Yehuda Koren 提出的矩阵分解推荐算法。

按照顺序，首先介绍基础的 SVD 算法，然后是考虑偏置信息，接着是超越评分矩阵增加多种输入，最后是增加时间因素。好，一个一个来。

前面已经从直观上大致说了矩阵分解是怎么回事，这里再从物理意义上解释一遍。矩阵分解，就是把用户和物品都映射到一个 k 维空间中，这个 k 维空间不是我们直接看得到的，也不一定具有非常好的可解释性，每一个维度也没有名字，所以常常叫做隐因子，代表藏在直观的矩阵数据下面的。

每一个物品都得到一个向量 q，每一个用户也得到一个向量 p。对于物品，与它对应的向量 q 中的元素，有正有负，代表着这个物品背后暗藏的一些用户关注的因素。

对于用户，与它对应的向量 p 中的元素，也有正有负，代表这个用户在若干因素上的偏好。物品被关注的因素，和用户偏好的因素，它们的数量和意义是一致的，就是我们在矩阵分解之处人为指定的 k。

举个例子，用户 u 的向量是 pu，物品 i 的向量是 qi，那么，要计算物品 i 推荐给用户 u 的推荐分数，直接计算点积即可：

r^ui=puqTir^ui=puqiT

看上去很简单，难在哪呢？难在如何得到每一个用户，每一个物品的 k 维向量。这是一个机器学习问题。按照机器学习框架，一般就是考虑两个核心要素：

损失函数；

优化算法。

SVD 的损失函数是这样定义的：

minq∗,p∗∑(u,i)∈κ(rui−puqTi)2+λ(||qi||2+||pu||2)minq∗,p∗∑(u,i)∈κ(rui−puqiT)2+λ(||qi||2+||pu||2)

理解 SVD 的参数学习过程并不是必须的，如果你不是算法工程师的话不必深究这个过程。

由于这个公式略复杂，如果你正在听音频，就需要自己看一下图片。这个损失函数由两部分构成，加号前一部分控制着模型的偏差，加号后一部分控制着模型的方差。

前一部分就是：用分解后的矩阵预测分数，要和实际的用户评分之间误差越小越好。

后一部分就是：得到的隐因子向量要越简单越好，以控制这个模型的方差，换句话说，让它在真正执行推荐任务时发挥要稳定。这部分的概念对应机器学习中的过拟合，有兴趣可以深入了解。

整个 SVD 的学习过程就是：

准备好用户物品的评分矩阵，每一条评分数据看做一条训练样本；

给分解后的 U 矩阵和 V 矩阵随机初始化元素值；

用 U 和 V 计算预测后的分数；

计算预测的分数和实际的分数误差；

按照梯度下降的方向更新 U 和 V 中的元素值；

重复步骤 3 到 5，直到达到停止条件。

过程中提到的梯度下降是优化算法的一种，想深入了解可以参见任何一本机器学习的专著。

得到分解后的矩阵之后，实质上就是得到了每个用户和每个物品的隐因子向量，拿着这个向量再做推荐计算就简单了，哪里不会点哪里，意思就是拿着物品和用户两个向量，计算点积就是推荐分数了。

2 增加偏置信息

到现在，你已经知道基础的 SVD 是怎么回事了。现在来多考虑一下实际情况，试想一下：有一些用户会给出偏高的评分，比如标准宽松的用户；有一些物品也会收到偏高的评分，比如一些目标观众为铁粉的电影，甚至有可能整个平台的全局评分就偏高。

所以，原装的 SVD 就有了第一个变种：把偏置信息抽出来的 SVD。

一个用户给一个物品的评分会由四部分相加：

r^ui=μ+bi+bu+puqTir^ui=μ+bi+bu+puqiT

从左至右分别代表：全局平均分、物品的评分偏置、用户评分的偏置、用户和物品之间的兴趣偏好。

针对前面三项偏置分数，我在这里举个例子，假如一个电影评分网站全局平均分是 3 分，《肖申克的救赎》的平均分比全局平均分要高 1 分。

你是一个对电影非常严格的人，你一般打分比平均分都要低 0.5，所以前三项从左到右分别就是 3，1，-0.5。如果简单的就靠这三项，也可以给计算出一个你会给《肖申克的救赎》打的分数，就是 3.5。

增加了偏置信息的 SVD 模型目标函数稍有改变：

和基本的 SVD 相比，要想学习两个参数：用户偏置和物品偏置。学习的算法还是一样的。

3 增加历史行为

探讨完增加偏执信息的 SVD 后，接着你再思考一个问题：有的用户评分比较少。事实上这很常见，相比沉默的大多数，主动点评电影或者美食的用户是少数。

换句话说，显式反馈比隐式反馈少，那么能不能利用隐式反馈来弥补这一点呢？另外，再考虑多一点，对于用户的个人属性，比如性别等，是不是也可以加入到模型中来弥补冷启动的不足呢？

是的，都是可以的，在 SVD 中结合用户的隐式反馈行为和属性，这套模型叫做 SVD++。

先说隐式反馈怎么加入，方法是：除了假设评分矩阵中的物品有一个隐因子向量外，用户有过行为的物品集合也都有一个隐因子向量，维度是一样的。把用户操作过的物品隐因子向量加起来，用来表达用户的兴趣偏好。

类似的，用户属性，全都转换成 0-1 型的特征后，对每一个特征也假设都存在一个同样维度的隐因子向量，一个用户的所有属性对应的隐因子向量相加，也代表了他的一些偏好。

综合两者，SVD++ 的目标函数中，只需要把推荐分数预测部分稍作修改，原来的用户向量那部分增加了隐式反馈向量和用户属性向量：

r^ui=μ+bi+bu+ (pu+|N(u)|−0.5∑i∈N(u)xi+∑a∈Auya)qTir^ui=μ+bi+bu+ (pu+|N(u)|−0.5∑i∈N(u)xi+∑a∈Auya)qiT

（滑动查看完整公式）

学习算法依然不变，只是要学习的参数多了两个向量：x 和 y。一个是隐式反馈的物品向量，另一个用户属性的向量。

这样一来，在用户没有评分时，也可以用他的隐式反馈和属性做出一定的预测。

4 考虑时间因素

截止到目前，我们还没有正视过一个人性：人是善变的。这个是一个广义的评价，我们在进步也是在变化，今天的我们和十年前的我们很可能不一样了。这是常态，因此，在 SVD 中考虑时间因素也变得顺理成章。

在 SVD 中考虑时间因素，有几种做法：

对评分按照时间加权，让久远的评分更趋近平均值；

对评分时间划分区间，不同的时间区间内分别学习出隐因子向量，使用时按照区间使用对应的隐因子向量来计算；

对特殊的期间，如节日、周末等训练对应的隐因子向量。

总结

至此，我们介绍了在 Netflix Prize 比赛中最为出众的模型：SVD 及其一些典型的改进。改进方案分别是：

考虑偏置信息；

考虑隐式反馈和用户属性；

考虑时间因素。
其实 Netflix Prize 比赛上诞生了很多其他优秀的算法，或者把一些已有的算法应用得到很好的效果，比如受限玻尔兹曼机用来融合多个模型，这个我会在后面的专栏文章中专门再讲。

好了，最后我要给你留一个思考题，假如矩阵分解面对的数据不是评分数据，而是行为数据，那么今天讲到的损失函数是否依然有效呢？欢迎留言一起讨论。感谢你的收听，我们下次再见。

【近邻推荐】人以群分，你是什么人就看到什么世界

要说提到推荐系统中，什么算法最名满天下，我想一定是协同过滤。在很多场合，甚至有人把协同过滤和推荐系统划等号，可见二者的关系多么紧密。

协同过滤的重点在于“协同”，所谓协同，也就是群体互帮互助，互相支持是集体智慧的体现，协同过滤也是这般简单直接，历久弥新。

协同过滤

当你的推荐系统度过了只能使用基于内容的推荐阶段后，就有了可观的用户行为了。这时候的用户行为通常是正向的，也就是用户或明或暗地表达着喜欢的行为。这些行为可以表达成一个用户和物品的关系矩阵，或者说网络、或者说是图，都是一个东西。

这个用户物品的关系矩阵中填充的就是用户对物品的态度，但并不是每个位置都有，需要的就是把那些还没有的地方填起来。这个关系矩阵是协同过滤的命根子，一切都围绕它来进行。

协同过滤是一个比较大的算法范畴。通常划分为两类：

基于记忆的协同过滤（Memory-Based）；

基于模型的协同过滤（Model-Based）。

基于记忆的协同过滤，现在看上去极其简单，就是记住每个人消费过什么东西，然后给他推荐相似的东西，或者推荐相似的人消费的东西。基于模型的协同过滤则是从用户物品关系矩阵中去学习一个模型，从而把那些矩阵空白处填满。

接下来一段时间，我们就围绕这两个类别的协同过滤与你好好聊聊。今天我先来说的是基于记忆的协同过滤的一种——基于用户，或者叫做 User-Based， User to User。

基于用户的协同过滤

背后的思想

你有没有过这种感觉，你遇到一个人，你发现他喜欢的书、喜欢的电影也基本上都是你喜欢的，从此以后，你就想老是想问他：还有什么好推荐的，最近又看了什么书，最近又看了什么电影？甚至不惜和他撞衫，和他穿一个风格的衣服。

对喽，这个感觉非常地自然直接，它就是基于用户的协同过滤背后思想。详细来说就是：先根据历史消费行为帮你找到一群和你口味很相似的用户；然后根据这些和你很相似的用户再消费了什么新的、你没有见过的物品，都可以推荐给你。

这就是我们常说的人以群分，你是什么人，你就会遇到什么人，所以说，要谨慎交友啊。

这其实也是一个给用户聚类的过程，把用户按照兴趣口味聚类成不同的群体，给用户产生的推荐就来自这个群体的平均值；所以要做好这个推荐，关键是如何量化“口味相似”这个看起来很直接简单的事情。这关系到一个用户会跟哪些人在同一个房间内，万一进错了房间，影响就会不好。

原理

书归正传，我们来说一说基于用户的协同过滤具体是怎么做的。前面说过，核心是那个用户物品的关系矩阵，这个矩阵是最原始的材料。

第一步，准备用户向量，从这个矩阵中，理论上可以给每一个用户得到一个向量。

为什么要说是“理论上”呢？因为得到向量的前提是：用户爸爸需要在我们的产品里有行为数据啊，否则就得不到这个向量。

这个向量有这么三个特点：

向量的维度就是物品的个数；

向量是稀疏的，也就是说并不是每个维度上都有数值，原因当然很简单，这个用户并不是消费过所有物品，废话嘛，连我们压箱底的都给用户推荐了，那当然不用再推荐什么了；

向量维度上的取值可以是简单的 0 或者 1，也就是布尔值，1 表示喜欢过，0 表示没有，当然因为是稀疏向量，所以取值为 0 的就忽略了。

第二步，用每一个用户的向量，两两计算用户之间的相似度，设定一个相似度阈值或者设定一个最大数量，为每个用户保留与其最相似的用户。

这里两两计算相似度如何计算，市面上有很多相似度计算方法，你也可以自己设计，我们在后面的文章里会逐一介绍，这里先略过不提。

第三步，为每一个用户产生推荐结果。

把和他“臭味相投”的用户们喜欢过的物品汇总起来，去掉用户自己已经消费过的物品，剩下的排序输出就是推荐结果，是不是很简单。具体的汇总方式我们用一个公式来表示。

这个公式也是很简单的。等号左边就是计算一个物品 i 和一个用户 u 的匹配分数，等号右边是这个分数的计算过程，分母是把和用户 u 相似的 n 个用户的相似度加起来，分子是把这 n 个用户各自对物品 i 的态度，按照相似度加权求和。

这里的态度最简单就是 0 或者 1，1 表示喜欢过，0 表示没有，如果是评分，则可以是 0 到 5 的取值。整个公式就是相似用户们的态度加权平均值。

实践

看上去简单得不值一提，但是在实现上却有一些坑，需要小心小心再小心。你想过以下这几个问题吗？

只有原始用户行为日志，需要从中构造出矩阵，怎么做？

如果用户的向量很长，计算一个相似度则耗时很久，怎么办？

如果用户量很大，而且通常如此，两两计算用户相似度也是一个大坑，怎么办？

在计算推荐时，看上去要为每一个用户计算他和每一个物品的分数，又是一个大坑，怎么办？

嗯……不要气馁，下面我会逐一说下如何化解这些问题。

1 构造矩阵

我们在做协同过滤计算时，所用的矩阵是稀疏的，说人话就是：很多矩阵元素不用存，因为是 0。这里介绍典型的稀疏矩阵存储格式。

CSR：这个存储稍微复杂点，是一个整体编码方式。它有三个组成：数值、列号和行偏移共同编码。

COO：这个存储方式很简单，每个元素用一个三元组表示（行号，列号，数值），只存储有值的元素，缺失值不存储。

这些存储格式，在常见的计算框架里面都是标准的，如 Spark 中，Python 的 NumPy 包中。一些著名的算法比赛也通常都是以这种格式提供数据。这里不再赘述了。

把你的原始行为日志转换成上面的格式，就可以使用常用计算框架的标准输入了。

2 相似度计算

相似度计算是个问题。

首先是单个相似度计算问题，如果碰上向量很长，无论什么相似度计算方法，都要遍历向量，如果用循环实现就更可观了，所以通常降低相似度计算复杂度的办法有两种。

对向量采样计算。道理很简单，两个一百维的向量计算出的相似度是 0.7，我现在忍受一些精度的损失，不用 100 维计算，随机从中取出 10 维计算，得到相似度是 0.72，显然用 100 维计算出的 0.7 更可信一些，但是在计算复杂度降低十倍的情形下，0.72 和它误差也不大，后者更经济。这个算法由 Twitter 提出，叫做 DIMSUM 算法，已经在 Spark 中实现了。

向量化计算。与其说这是一个小技巧，不如说这是一种思维方式。在机器学习领域，向量之间的计算是家常便饭，难道向量计算都要用循环实现吗？并不是，现代的线性代数库都支持直接的向量运算，比循环快很多。也就是我们在任何地方，都要想办法把循环转换成向量来直接计算，一般像常用的向量库都天然支持的，比如 Python 的 NumPy 。

其次的问题就是，如果用户量很大，两两之间计算代价就很大。

有两个办法来缓解这个问题：

第一个办法是：将相似度计算拆成 Map Reduce 任务，将原始矩阵 Map 成键为用户对，值为两个用户对同一个物品的评分之积，Reduce 阶段对这些乘积再求和，Map Reduce 任务结束后再对这些值归一化；

第二个办法是：不用基于用户的协同过滤。

另外，这种计算对象两两之间的相似度的任务，如果数据量不大，一般来说不超过百万个，然后矩阵又是稀疏的，那么有很多单机版本的工具其实更快，比如 KGraph、 GraphCHI 等。

3 推荐计算

得到了用户之间的相似度之后。接下来还有一个硬骨头，计算推荐分数。显然，为每一个用户计算每一个物品的推荐分数，计算次数是矩阵的所有元素个数，这个代价，你当然不能接受啊。这时候，你注意回想一下前面那个汇总公式，有这么几个特点我们可以来利用一下：

只有相似用户喜欢过的物品需要计算，这个大大的赞，这个数量相比全部物品少了很多；

把计算过程拆成 Map Reduce 任务。

拆 Map Reduce 任务的做法是：

遍历每个用户喜欢的物品列表；

获取该用户的相似用户列表；

把每一个喜欢的物品 Map 成两个记录发射出去，一个是键为 < 相似用户 ID，物品 ID，1> 三元组，可以拼成一个字符串，值为 < 相似度 >，另一个是键为 < 相似用户 ID，物品 ID，0> 三元组，值为 < 喜欢程度 * 相似度 >，其中的 1 和 0 为了区分两者，在最后一步中会用到；

Reduce 阶段，求和后输出；

< 相似用户 ID，物品 ID, 0> 的值除以 < 相似用户 ID，物品 ID, 1> 的值

一般来说，中小型公司如果没有特别必要的话，不要用分布式计算，看上去高大上、和大数据沾上边了，实际上得不偿失。

拆分 Map Reduce 任务也不一定非要用 Hadoop 或者 Spark 实现。也可以用单机实现这个过程。

因为一个 Map 过程，其实就是将原来耦合的计算过程解耦合了、拍扁了，这样的话我们可以利用多线程技术实现 Map 效果。例如 C++ 里面 OpenMP 库可以让我们无痛使用多线程，充分剥削计算机所有的核。

4 一些改进

对于基于用户的协同过滤有一些常见的改进办法，改进主要集中在用户对物品的喜欢程度上：

惩罚对热门物品的喜欢程度，这是因为，热门的东西很难反应出用户的真实兴趣，更可能是被煽动，或者无聊随便点击的情形，这是群体行为常见特点；

增加喜欢程度的时间衰减，一般使用一个指数函数，指数就是一个负数，值和喜欢行为发生时间间隔正相关即可，这很好理解，小时候喜欢的东西不代表我现在的口味，人都是会变的，这是人性。

应用场景

最后，说一说基于用户的协同过滤有哪些应用场景。基于用户的协同过滤有两个产出：

相似用户列表；

基于用户的推荐结果。

所以我们不但可以推荐物品，还可以推荐用户！比如我们在一些社交平台上看到：“相似粉丝”“和你口味类似的人”等等都可以这样计算。

对于这个方法计算出来的推荐结果本身，由于是基于口味计算得出，所以在更强调个人隐私场景中应用更佳，在这样的场景下，不受大 V 影响，更能反应真实的兴趣群体，而非被煽动的乌合之众。

总结

今天，我与你聊了基于用户的协同过滤方法，也顺带普及了一下协同过滤这个大框架的思想。基于用户的协同过滤算法简单直接，但是非常有效。只是，在实现这个方法时，有很多需要注意的地方，比如：

相似度计算本身如果遇到超大维度向量怎么办；

两两计算用户相似度遇到用户量很大怎么办？

同时，我也聊到了如何改进这个推荐算法，希望能够帮到你，针对你自己的产品，你可以再多想几种改进办法吗？欢迎留言一起讨论。感谢你的收听，我们下次再见。

【近邻推荐】协同过滤中的相似度计算方法有哪些

今天，我们来聊聊协同过滤中的相似度计算方法有哪些。

相似度的本质

推荐系统中，推荐算法分为两个门派，一个是机器学习派，另一个就是相似度门派。机器学习派是后起之秀，而相似度派则是泰山北斗，以致撑起来推荐系统的半壁江山。

近邻推荐顾名思义就是在地理位置上住得近。如果用户有个邻居，那么社交软件上把邻居推荐给他在直观上就很合理，当然，如果邻居姓王的话，就不要推荐了。

这里说的近邻，并不一定只是在三维空间下的地理位置的近邻，在任意高维空间都可以找到近邻，尤其是当用户和物品的特征维度都很高时，要找到用户隔壁的邻居，就不是那么直观，需要选择好用适合的相似度度量办法。

近邻推荐的核心就是相似度计算方法的选择，由于近邻推荐并没有采用最优化思路，所以效果通常取决于矩阵的量化方式和相似度的选择。

相似度，与之配套的还有另一个概念就是距离，两者都是用来量化两个物体在高维空间中的亲疏程度的，它们是硬币的两面。

推荐算法中的相似度门派，实际上有这么一个潜在假设：如果两个物体很相似，也就是距离很近，那么这两个物体就很容易产生一样的动作。

如果两篇新闻很相似，那么他们很容易被同一个人先后点击阅读，如果两个用户很相似，那么他们就很容易点击同一个新闻。这种符合直觉的假设，大部分时候很奏效。

其实属于另一门派的推荐算法——机器学习中，也有很多算法在某种角度看做是相似度度量。

例如，逻辑回归或者线性回归中，一边是特征向量，另一边是模型参数向量，两者的点积运算，就可以看做是相似度计算，只不过其中的模型参数向量值并不是人肉指定的，而是从数据中由优化算法自动总结出来的。

在近邻推荐中，最常用的相似度是余弦相似度。然而可以选用的相似度并不只是余弦相似度，还有欧氏距离、皮尔逊相关度、自适应的余弦相似度、局部敏感哈希等。使用场景各不相同，今天，我会分别一一介绍如下。

相似度的计算方法

数据分类

在真正开始巡视相似度计算方法前，我先给你把度量对象做个简单分类。相似度计算对象是向量，或者叫做高维空间下的坐标，一个意思。那表示这个向量的数值就有两种：

实数值；

布尔值，也就是 0 或者 1。

下面介绍的不同计算方法适用于不同的数据种类。

1 欧氏距离

欧氏距离，如名字所料，是一个欧式空间下度量距离的方法。两个物体，都在同一个空间下表示为两个点，假如叫做 p 和 q，分别都是 n 个坐标。那么欧式距离就是衡量这两个点之间的距离，从 p 到 q 移动要经过的距离。欧式距离不适合布尔向量之间。

计算方式可以表示如下，我在文稿中放了一个公式，你可以点击查看。

E(p,q)=∑i=1n(pi−qi)2−−−−−−−−−−−√E(p,q)=∑i=1n(pi−qi)2

这个公式就是，每一个坐标上的取值相减，求平方和，最后输出方根。

显然，欧式距离得到的值是一个非负数，最大值是正无穷。通常相似度计算度量结果希望是 [-1，1] 或者 [0，1] 之间，所以欧式距离要么无法直接使用到这个场景中，要么需要经过二次转化得到，我在文稿中放了一个最常用的转化公式，你可以点击查看。

11+E(p,q)11+E(p,q)

距离加一后取倒数。这个公式能够把范围为 0 到正无穷的欧式距离转换为 0 到 1 的相似度。

欧式距离度量的是空间中两个点的绝对差异，适用于分析用户能力模型之间的差异，比如消费能力、贡献内容的能力等。

当然，虽然欧式距离计算两个点的距离，实际上，点的坐标表示和我们常说的向量表示是同一回事，希望这句话是废话，你早已懂得。

2 余弦相似度

大名鼎鼎的余弦相似度，度量的是两个向量之间的夹角，其实就是用夹角的余弦值来度量，所以名字叫余弦相似度。当两个向量的夹角为 0 度时，余弦值为 1，当夹角为 90 度时，余弦值为 0，为 180 度时，余弦值则为 -1。

余弦相似度在度量文本相似度、用户相似度、物品相似度的时候都较为常用；但是在这里需要提醒你一点，余弦相似度的特点：它与向量的长度无关。因为余弦相似度计算需要对向量长度做归一化：

cos(p,q)=∑ipiqi∑iq2i−−−−−√∑ip2i−−−−−√cos(p,q)=∑ipiqi∑iqi2∑ipi2

经过向量长度归一化后的相似度量方式，背后潜藏着这样一种思想：两个向量，只要方向一致，无论程度强弱，都可以视为“相似”。

这简直就是：招聘人才时只看价值观，不考核代码能力，只要肯干，搬砖嘛，谁搬不是搬。这样做错不错呢？很显然，有非常大的合理性。

比如，我用 140 字的微博摘要了一篇 5000 字的博客内容，两者得到的文本向量可以认为方向一致，词频等程度不同，但是余弦相似度仍然认为他们是相似的。

在协同过滤中，如果选择余弦相似度，某种程度上更加依赖两个物品的共同评价用户数，而不是用户给予的评分多少。这就是由于余弦相似度被向量长度归一化后的结果。

余弦相似度对绝对值大小不敏感这件事，在某些应用上仍然有些问题。

举个小例子，用户 A 对两部电影评分分别是 1 分和 2 分，用户 B 对同样这两部电影评分是 4 分和 5 分。用余弦相似度计算出来，两个用户的相似度达到 0.98。这和实际直觉不符，用户 A 明显不喜欢这两部电影。

针对这个问题，对余弦相似度有个改进，改进的算法叫做调整的余弦相似度（Adjusted Cosine Similarity）。调整的方法很简单，就是先计算向量每个维度上的均值，然后每个向量在各个维度上都减去均值后，再计算余弦相似度。

前面这个小例子，用调整的余弦相似度计算得到的相似度是 -0.1，呈现出两个用户口味相反，和直觉相符。

3 皮尔逊相关度

皮尔逊相关度，实际上也是一种余弦相似度，不过先对向量做了中心化，向量 p 和 q 各自减去向量的均值后，再计算余弦相似度。

R(p,q)=∑ni=1(pi−p¯)(qi−q¯)∑ni=1(pi−p¯)2−−−−−−−−−−−−√∑ni=1(qi−q¯)2−−−−−−−−−−−−√R(p,q)=∑i=1n(pi−p¯)(qi−q¯)∑i=1n(pi−p¯)2∑i=1n(qi−q¯)2

皮尔逊相关度计算结果范围在 -1 到 1。-1 表示负相关，1 比表示正相关。皮尔逊相关度其实度量的是两个随机变量是不是在同增同减。

如果同时对两个随机变量采样，当其中一个得到较大的值另一也较大，其中一个较小时另一个也较小时，这就是正相关，计算出来的相关度就接近 1，这种情况属于沆瀣一气，反之就接近 -1。

由于皮尔逊相关度度量的时两个变量的变化趋势是否一致，所以不适合用作计算布尔值向量之间相关度，因为两个布尔向量也就是对应两个 0-1 分布的随机变量，这样的随机变量变化只有有限的两个取值，根本没有“变化趋势，高低起伏”这一说。

4 杰卡德（Jaccard）相似度

杰卡德相似度，是两个集合的交集元素个数在并集中所占的比例。由于集合非常适用于布尔向量表示，所以杰卡德相似度简直就是为布尔值向量私人定做的。对应的计算方式是：

分子是两个布尔向量做点积计算，得到的就是交集元素个数；

分母是两个布尔向量做或运算，再求元素和。

余弦相似度适用于评分数据，杰卡德相似度适合用于隐式反馈数据。例如，使用用户的收藏行为，计算用户之间的相似度，杰卡德相似度就适合来承担这个任务。

总结

今天，我介绍了常用的几种相似度计算方法，以及其各自的使用场景。

这里的场景是按照数据形式划分的，按照向量维度取值是否是布尔值来看，杰卡德相似度就只适合布尔值向量，余弦相似度弹性略大，适合两种向量。欧式距离度量的是绝对差异，余弦相似度度量的是方向差异，但是调整的余弦相似度则可以避免这个弱点。

现在留给你一个问题：如果在一个社交网络中，要计算好友的相似度，你会选择哪种相似度来做？欢迎留言讨论。

感谢收听，我们下期再见。

【近邻推荐】解密“看了又看”和“买了又买”

不管你有没有剁过手，你对“看了这个商品的还看了”这样的推荐形式一定不陌生。无论是猫还是狗，或者是其他电商网站，这样的推荐产品可以说是推荐系统的标配了。

类似的还有，如点评标记类网站的“喜欢了这部电影的还喜欢了”，社交媒体网站的“关注了这个人还关注了”，这些都只是文案类似，动词不同而已。

这样的推荐形式背后都是来自一个古老的推荐算法，叫做基于物品的协同过滤，通常也被叫作 Item-Based，因为后者更容易搜索到相关的文章，所以被更多地提及。

如果做推荐系统不知道“基于物品的协同过滤”，那等同于做程序员不懂得冒泡排序。这个朴素的算法，就像是乔峰大战聚贤庄所用的“太祖长拳”一样，简单直接有效，读过高中就懂，用得好也能够战倒绝大多数的武林豪杰。今天，我就来和你聊聊这个朴素的算法。

基于物品（Item-Based）的八卦

基于物品的协同过滤算法诞生于 1998 年，是由亚马逊首先提出的，并在 2001 年由其发明者发表了相应的论文（ Item-Based Collaborative Filtering Recommendation Algorithms ）。

这篇论文在 Google 学术上引用数已近 7000，并且在 WWW2016 大会上被授予了“时间检验奖”，颁奖词是：“这篇杰出的论文深深地影响了实际应用”。历经了 15 年后仍然在发光发热，这个奖它显然受之无愧。

虽然今天各家公司都在使用这个算法，好像它是一个公共资源一样，然而并不是这样，亚马逊早在 1998 年，也就是论文发表的三年前就申请了专利。

讲完了算法的八卦，开始说正事了。

基于物品（Item-Based）原理

在基于物品的协同过滤出现之前，信息过滤系统最常使用的是基于用户的协同过滤。基于用户的协同过滤首先计算相似用户，然后再根据相似用户的喜好推荐物品，这个算法有这么几个问题：

用户数量往往比较大，计算起来非常吃力，成为瓶颈；

用户的口味其实变化还是很快的，不是静态的，所以兴趣迁移问题很难反应出来；

数据稀疏，用户和用户之间有共同的消费行为实际上是比较少的，而且一般都是一些热门物品，对发现用户兴趣帮助也不大。

和基于用户的不同，基于物品的协同过滤首先计算相似物品，然后再根据用户消费过、或者正在消费的物品为其推荐相似的，基于物品的算法怎么就解决了上面这些问题呢？

首先，物品的数量，或者严格的说，可以推荐的物品数量往往少于用户数量；所以一般计算物品之间的相似度就不会成为瓶颈。

其次，物品之间的相似度比较静态，它们变化的速度没有用户的口味变化快；所以完全解耦了用户兴趣迁移这个问题。

最后，物品对应的消费者数量较大，对于计算物品之间的相似度稀疏度是好过计算用户之间相似度的。

根据我在上一篇文章中所说，协同过滤最最依赖的是用户物品的关系矩阵，基于物品的协同过滤算法也不能例外，它的基本步骤是这样的：

构建用户物品的关系矩阵，矩阵元素可以是用户的消费行为，也可以是消费后的评价，还可以是对消费行为的某种量化如时间、次数、费用等；

假如矩阵的行表示物品，列表示用户的话，那么就两两计算行向量之间的相似度，得到物品相似度矩阵，行和列都是物品；

产生推荐结果，根据推荐场景不同，有两种产生结果的形式。一种是为某一个物品推荐相关物品，另一种是在个人首页产生类似“猜你喜欢”的推荐结果。不要急，稍后我会分别说。

计算物品相似度

前面较为笼统地说要计算物品之间的相似度，现在详细说说这块。从用户物品关系矩阵中得到的物品向量长什么样子呢？我来给你描述一下：

它是一个稀疏向量；

向量的维度是用户，一个用户代表向量的一维，这个向量的总共维度是总用户数量；

向量各个维度的取值是用户对这个物品的消费结果，可以是行为本身的布尔值，也可以是消费行为量化如时间长短、次数多少、费用大小等，还可以是消费的评价分数；

没有消费过的就不再表示出来，所以说是一个稀疏向量。

接下来就是如何两两计算物品的相似度了，一般选择余弦相似度，当然还有其他的相似度计算法方法也可以。计算公式如下：

sim(i,j)=∑nk=1Rik∗Rjk∑nk=1R2ik−−−−−−−−√∑nk=1R2jk−−−−−−−−√sim(i,j)=∑k=1nRik∗Rjk∑k=1nRik2∑k=1nRjk2

用文字解释一下这个公式：

分母是计算两个物品向量的长度，求元素值的平方和再开方。分子是两个向量的点积，相同位置的元素值相乘再求和。

很简单，因为这个公式出自中学数学课本，所以我刚才说读过高中就懂。

这个公式的物理意义就是计算两个向量的夹角余弦值，相似度为 1 时，对应角度是 0，好比时如胶似漆，相似度为 0 时，对应角度为 90 度，毫不相干，互为路人甲。

看上去计算量很大，貌似每一个求和的复杂度都是和向量维度、也就是用户数量一样的。但是别忘了，前面我说过他们都是稀疏向量，也就是向量中绝大多数值都是 0，求和时不用算，点积时更不用算，甚至求点积时只用管两个物品的公共用户，只是少许几个乘积而已。

物品之间的相似度计算是这个算法最可以改进的地方。通常的改进方向有下面两种。

1. 物品中心化。把矩阵中的分数，减去的是物品分数的均值；先计算每一个物品收到评分的均值，然后再把物品向量中的分数减去对应物品的均值。这样做的目的是什么呢？去掉物品中铁杆粉丝群体的非理性因素，例如一个流量明星的电影，其脑残粉可能会集体去打高分，那么用物品的均值来中心化就有一定的抑制作用。

2. 用户中心化。把矩阵中的分数，减去对应用户分数的均值；先计算每一个用户的评分均值，然后把他打过的所有分数都减去这个均值。

这样做的目的又是什么呢？每个人标准不一样，有的标准严苛，有的宽松，所以减去用户的均值可以在一定程度上仅仅保留了偏好，去掉了主观成分。

上面提到的相似度计算方法，不只是适用于评分类矩阵，也适用于行为矩阵。所谓行为矩阵，即矩阵元素为 0 或者 1 的布尔值，也就是在前面的专栏中讲过的隐式反馈。隐式反馈取值特殊，有一些基于物品的改进推荐算法无法应用，比如著名的 Slope One 算法。

计算推荐结果

在得到物品相似度之后，接下来就是为用户推荐他可能会感兴趣的物品了，基于物品的协同过滤，有两种应用场景。

第一种属于 TopK 推荐，形式上也常常属于类似“猜你喜欢”这样的。

出发方式是当用户访问首页时，汇总和“用户已经消费过的物品相似”的物品，按照汇总后分数从高到低推出。汇总的公式是这样的：

Rui^=∑mj=1sim(i,j)∗Ruj∑mj=1sim(i,j)Rui^=∑j=1msim(i,j)∗Ruj∑j=1msim(i,j)

这个公式描述一下，核心思想就和基于用户的推荐算法一样，用相似度加权汇总。

要预测一个用户 u 对一个物品 i 的分数，遍历用户 u 评分过的所有物品，假如一共有 m 个，每一个物品和待计算物品 i 的相似度乘以用户的评分，这样加权求和后，除以所有这些相似度总和，就得到了一个加权平均评分，作为用户 u 对物品 i 的分数预测。

和基于物品的推荐一样，我们在计算时不必对所有物品都计算一边，只需要按照用户评分过的物品，逐一取出和它们相似的物品出来就可以了。

这个过程都是离线完成后，去掉那些用户已经消费过的，保留分数最高的 k 个结果存储。当用户访问首页时，直接查询出来即可。

第二种属于相关推荐，也就是我们今天专栏题目所指的场景。

这类推荐不需要提前合并计算，当用户访问一个物品的详情页面时，或者完成一个物品消费的结果面，直接获取这个物品的相似物品推荐，就是“看了又看”或者“买了又买”的推荐结果了。

Slope One 算法

经典的基于物品推荐，相似度矩阵计算无法实时更新，整个过程都是离线计算的，而且还有另一个问题，相似度计算时没有考虑相似度的置信问题。例如，两个物品，他们都被同一个用户喜欢了，且只被这一个用户喜欢了，那么余弦相似度计算的结果是 1，这个 1 在最后汇总计算推荐分数时，对结果的影响却最大。

Slope One 算法针对这些问题有很好的改进。在 2005 年首次问世，Slope One 算法专门针对评分矩阵，不适用于行为矩阵。Slope One 算法计算的不是物品之间的相似度，而是计算的物品之间的距离，相似度的反面。举个例子就一目了然，下面是一个简单的评分矩阵：

这个矩阵反应了这些事实：用户 1 给物品 A、B、C 都评分了，分别是 5，3，2；用户 2 给物品 A、B 评分了，分别是 3、4；用户 3 给物品 B、C 评分了，分别是 2、5。现在首先来两两计算物品之间的差距：

括号里表示两个物品的共同用户数量，代表两个物品差距的置信程度。比如物品 A 和物品 B 之间的差距是 0.5，共同用户数是 2，反之，物品 B 和物品 A 的差距是 -0.5，共同用户数还是 2。知道这个差距后，就可以用一个物品去预测另一个物品的评分。

如果只知道用户 3 给物品 B 的评分是 2，那么预测用户 3 给物品 A 的评分呢就是 2.5，因为从物品 B 到物品 A 的差距是 0.5。

在此基础上继续推进，如果知道用户给多个物品评分了，怎么汇总这些分数呢？

方法是把单个预测的分数按照共同用户数加权求平均。比如现在知道用户 3 不但给物品 B 评分为 2，还给物品 C 评分为 5，物品 B 对物品 A 的预测是 2.5 分，刚才计算过了，物品 C 给物品 A 的预测是 8 分，再加权平均。

8∗1+2.5∗2(1+2)=4.338∗1+2.5∗2(1+2)=4.33

就得到了推荐分数为 4.33 分。是不是很简单？

总结

今天我们在基于用户的协同过滤基础上介绍了比较常见的一个算法：基于物品的协同过滤。这个方法常常在电商网站上见到，“买了又买”“看了又看”这样的相关推荐，都是由这个推荐算法产生。

最后我们介绍了一个改良版的基于物品推荐算法 Slope One。这里也留下了一个问题给你：为什么说 Slope One 可以做到在线更新呢？欢迎留言讨论。

【关键模块】 推荐系统服务化、存储选型及API设计

在过往的文章中，我讲到了推荐系统方方面面的相关概念。那么说，对于认识一个推荐系统来说，还差最后一个问题需要解决，那就是：万事俱备，如何给用户提供一个真正的在线推荐服务呢？

服务化是最后一步

其实一个推荐系统的在线服务，和任何别的在线服务相比，也没有什么本质区别，只是仍然还有一些特殊性。

提供一个在线服务，需要两个关键元素：数据库和 API。今天我就来专门说一说推荐系统中大家常常用到的数据库，并会谈谈推荐系统的 API 应该如何设计。

存储

这里注意一下，今天这里讲到的存储，专指近线或者在线部分所用的数据库，并不包括离线分析时所涉及的业务数据库或者日志数据库。

近线和在线的概念我在前面已经讲到过。推荐系统在离线阶段会得到一些关键结果，这些关键结果需要存进数据库，供近线阶段做实时和准实时的更新，最终会在在线阶段直接使用。

首先来看一下，离线阶段会产生哪些数据。按照用途来分，归纳起来一共就有三类。

特征。特征数据会是最多的，所谓用户画像，物品画像，这些都是特征数据，更新并不频繁。

模型。尤其是机器学习模型，这类数据的特点是它们大都是键值对，更新比较频繁。

结果。就是一些推荐方法在离线阶段批量计算出推荐结果后，供最后融合时召回使用。任何一个数据都可以直接做推荐结果，如协同过滤结果。

如果把整个推荐系统笼统地看成一个大模型的话，它依赖的特征是由各种特征工程得到的，这些线下的特征工程和样本数据共同得到模型数据，这些模型在线上使用时，需要让线上的特征和线下的特征一致，因此需要把线下挖掘的特征放到线上去。

特征数据有两种，一种是稀疏的，一种是稠密的，稀疏的特征常见就是文本类特征，用户标签之类的，稠密的特征则是各种隐因子模型的产出参数。

特征数据又常常要以两种形态存在：一种是正排，一种是倒排。

正排就是以用户 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 的数据模型组织形式如下图所示：

从这个图可以看出来，可以通过行主键及列名就可以访问到数据矩阵的单元格值。

前面也说过，用户和物品的画像数据适合存储在 Cassandra 中。也适合存储模型数据，如相似度矩阵，还可以存储离线计算的推荐结果。

2. 键值数据库

除了列式数据库外，还有一种存储模式，就是键值对内存数据库，这当然首推 Redis。Redis 你可以简单理解成是一个网络版的 HashMap，但是它存储的值类型比较丰富，有字符串、列表、有序列表、集合、二进制位。

并且，Redis 的数据放在了内存中，所以都是闪电般的速度来读取。

在推荐系统的以下场景中常常见到 Redis 的身影：

消息队列，List 类型的存储可以满足这一需求；

优先队列，比如兴趣排序后的信息流，或者相关物品，对此 sorted set 类型的存储可以满足这一需求；

模型参数，这是典型的键值对来满足。

另外，Redis 被人诟病的就是不太高可用，对此已经有一些集群方案，有官方的和非官方的，可以试着加强下 Redis 的高可用。

3. 非数据库

除了数据库外，在推荐系统中还会用到一些非主流但常用的存储方式。第一个就是虚拟内存映射，称为 MMAP，这可以看成是一个简陋版的数据库，其原理就是把磁盘上的文件映射到内存中，以解决数据太大不能读入内存，但又想随机读取的矛盾需求。

哪些地方可以用到呢？比如你训练的词嵌入向量，或者隐因子模型，当特别大时，可以二进制存在文件中，然后采用虚拟内存映射方式读取。

另外一个就是 PMML 文件，专门用于保存数据挖掘和部分机器学习模型参数及决策函数的。当模型参数还不足以称之为海量时，PMML 是一个很好的部署方法，可以让线上服务在做预测时并不依赖离线时的编程语言，以 PMML 协议保存离线训练结果就好。

API

除了存储，推荐系统作为一个服务，应该以良好的接口和上有服务之间交互，因此要设计良好的 API。

API 有两大类，一类数据录入，另一类是推荐服务。数据录入 API，可以用于数据采集的埋点，或者其他数据录入。

推荐服务的 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 该如何设计呢？欢迎留言一起讨论。感谢你的收听，我们下期再见。

【关键模块】巧妇难为无米之炊：数据采集关键要素

推荐系统离不开数据，数据就是推荐系统的粮食，要有数据就得收集数据。在自己产品中收集数据，主要还是来自日志。

日志和数据

数据驱动这个概念也是最近几年才开始流行起来的，在古典互联网时代，设计和开发产品完全侧重于功能易用和设计精巧上，并且整体驱动力受限于产品负责人的个人眼光，这属于是一种感性的把握，也因此对积累数据这件事就不是很重视。

在我经手的产品中，就有产品上线很久，需要搭建推荐系统时，却发现并没有收集相应的数据，或者收集得非常杂乱无章。

关于数据采集，按照用途分类又有三种：

报表统计；

数据分析；

机器学习。

当然，这三种的用途并不冲突，而且反而有层层递进的关系。最基本的数据收集，是为了统计一些核心的产品指标，例如次日留存，七日留存等，一方面是为了监控产品的健康状况，另一方面是为了对外秀肌肉，这一类数据使用非常浅层，对数据的采集要求也不高。

第二种就是比较常见的数据采集需求所在了。在前面第一种用途基础上，不但需要知道产品是否健康，还需要知道为什么健康、为什么不健康，做对了什么事、做错了什么事，要从数据中去找到根本的原因。

这种数据采集的用途，驱动了很多多维分析软件应运而生，也驱动了多家大数据创业公司应运而生。

数据分析工作，最后要产出的是比较简明清晰直观的结论，这是数据分析师综合自己的智慧加工出来的，是有人产出的。

它主要用于指导产品设计、指导商业推广、指导开发方式。走到这一步的数据采集，已经是实打实的数据驱动产品了。

第三种，就是收集数据为了机器学习应用，或者更广泛地说人工智能应用。那么机器学习应用，主要在消化数据的角色是算法、是计算机，而不是人。

这个观点是我在专栏写作之初，讲解用户画像相关内容时就提到的，再强调一遍就是，所有的数据，不论原始数据还是加工后的数据都是给机器“看”的，而不是给人“看”的。

所以在数据采集上，可以说多多益善，样本是多多益善，数据采集的维度，也就是字段数多多益善，但另一方面，数据是否适合分析，数据是否易于可视化地操作并不是核心的内容。

当然，实际上在任何一款需要有推荐系统的产品中，数据采集的需求很可能要同时满足上述三种要求。

本文为了讨论方便，不会重点讨论多维数据分析的用途，而是专门看看为了满足推荐系统，你需要怎么收集日志、采集数据。

因为推荐系统就是一个典型的人工智能应用，数据是要喂给机器“吃”的。

下面我就开始给你详细剖析一下为推荐系统收集日志这件事。

数据采集

给推荐系统收集日志这件事，依次要讨论的是：日志的数据模型，收集哪些日志，用什么工具收集，收集的日志怎么存储。

1. 数据模型

数据模型是什么？所谓数据模型，其实就是把数据归类。产品越复杂，业务线越多，产生的日志就越复杂。

如果看山是山，一个数据来源一个数据来源地去对待的话，那将效率非常低下，因此需要首先把要收集的日志数据归入几个模型。不同的数据应用，数据模型略有不同。

就推荐系统而言，推荐系统要做的事情就是预测那些最终会建立的人和物之间的连接，依赖的是已有的连接，以及人和物的属性，而且，其中最主要的是已有的连接，人和物的属性只不过是更加详细描述这些连接而已。

数据模型帮助梳理日志、归类存储，以方便在使用时获取。你可以回顾一下在前面讲过的推荐算法，这些推荐算法形形色色，但是他们所需要的数据可以概括为两个字：矩阵。

再细分一下，这些矩阵就分成了四种。

基于这个分析，可以给要收集的数据归纳成下面几种。

有了数据模型，就可以很好地去梳理现有的日志，看看每一种日志属于哪一种。并且，在一个新产品上线之初，该如何为将来的推荐系统记录日志也比较清楚了。这个数据模型当然不能概括全部数据，但是用来构建一个推荐系统就绰绰有余了。

接下来就是去收集数据了。收集数据，就是把散布在各个地方的数据聚拢，也包括那些还根本没有记录的数据的地方要开始记录。

2. 数据在哪？

按照前面的数据建模，我们一起来看一下要收集的数据会怎么产生。主要来自两种，一种是业务运转必须要存储的记录，例如用户注册资料，如果不在数据库中记录，产品就无法正常运转。

另一种就是在用户使用产品时顺便记录下来的，这叫做埋点。第一种数据源来自业务数据库，通常都是结构化存储，MySQL。第二种数据需要埋点，埋点又有几种不同方法。

第一种，SDK 埋点。这个是最经典古老的埋点方法，就是在开发自己的 App 或者网站时，嵌入第三方统计的 SDK，App 如友盟等，网站如 Google Analytics 等。

SDK 在要收集的数据发生点被调用，将数据发送到第三方统计，第三方统计得到数据后再进一步分析展示。

这种数据收集方式对推荐系统的意义不大，因为得不到原始的数据而只是得到统计结果，我们可以将其做一些改动，或者自己仿造做一些开发内部数据采集 SDK，从而能够收集到鲜活的数据。

第二种，可视化埋点。可视化埋点在 SDK 埋点基础上做了进一步工作，埋点工作通过可视化配置的方式完成，一般是在 App 端或者网站端嵌入可视化埋点套件的 SDK，然后再管理端接收前端传回的应用控件树，通过点选和配置，指令前端收集那些事件数据。业界有开源方案实现可参考，如 Mixpanel。

第三种，无埋点。所谓无埋点不是不埋点收集数据，而是尽可能多自动收集所有数据，但是使用方按照自己的需求去使用部分数据。

SDK 埋点就是复杂度高，一旦埋点有错，需要更新客户端版本，可视化埋点的不足就是：收集数据不能收集到非界面数据，例如收集了点击事件，也仅仅能收集一个点击事件，却不能把更详细的数据一并返回。

上面是按照技术手段分，如果按照收集数据的位置分，又分为前端埋点和后端埋点。

这两个区别是这样的，举个例子，要收集用户的点击事件，前端埋点就是在用户点击时，除了响应他的点击请求，还同时发送一条数据给数据采集方。

后端埋点就不一样了，由于用户的点击需要和后端交互，后端收到这个点击请求时就会在服务端打印一条业务日志，所以数据采集就采集这条业务日志即可。

埋点是一项非常复杂繁琐的事情，需要前端工程师或者客户端工程师细心处理，不在本文讨论范围内。但是幸好，国内如神策数据等公司，将这些工作已经做得很傻瓜化了，大大减轻了埋点数据采集的困扰。

对于推荐系统来说，所需要的数据基本上都可以从后端收集，采集成本较低，但是有两个要求：要求所有的事件都需要和后端交互，要求所有业务响应都要有日志记录。这样才能做到在后端收集日志。

后端收集业务日志好处很多，比如下面几种。

实时性。由于业务响应是实时的，所以日志打印也是实时的，因此可以做到实时收集。

可及时更新。由于日志记录都发生在后端，所以需要更新时可以及时更新，而不用重新发布客户端版本。

开发简单。不需要单独维护一套 SDK。

归纳一下，Event 类别的数据从后端各个业务服务器产生的日志来，Item 和 User 类型数据，从业务数据库来，还有一类特殊的数据就是 Relation 类别，也从业务数据库来。

3. 元素有哪些？

后端收集事件数据需要业务服务器打印日志。需要打印哪些信息才算是一条完整的时间数据呢？大致需要包含下面的几类元素。

用户 ID，唯一标识用户身份。

物品 ID，唯一标识物品。这个粒度在某些场景中需要注意，例如电商，物品的 ID 就不是真正要去区别物和物之间的不同，而是指同一类试题，例如一本书《岛上书店》，库存有很多本，并不需要区别库存很多本之间的不同，而是区别《岛上书店》和《白夜行》之间的不同。

事件名称，每一个行为一个名字。

事件发生时间，时间非常重要。

以上是基本的内容，下面再来说说加分项。

事件发生时的设备信息和地理位置信息等等；

从什么事件而来；

从什么页面而来；

事件发生时用户的相关属性；

事件发生时物品的相关属性。

把日志记录想象成一个 Live 快照，内容越丰富就越能还原当时的场景。

4. 怎么收集？

一个典型的数据采集架构如下图所示。

下面描述一下这个图。最左边就是数据源，有两部分，一个是来自非常稳定的网络服务器日志，nginx 或者 Apache 产生的日志。这类日志对推荐系统的作用是什么呢？

因为有一类埋点，在 PC 互联网时代，有一种事件数据收集方式是，放一个一像素的图片在某个要采集数据的位置。

这个图片被点击时，向服务端发送一个不做什么事情的请求，只是为了在服务端的网络服务器那里产生一条系统日志。 这类日志用 logstash 收集。

左边另外的数据源就是业务服务器，这类服务器会处理具体场景的具体业务，甚至推荐系统本身也是一个业务服务器。

这类服务器有各自不同的日志记录方式，例如 Java 是 Log4j，Python 是 Logging 等等，还有 RPC 服务。这些业务服务器通常会分布在多台机器上，产生的日志需要用 Flume 汇总。

Kafka 是一个分布式消息队列，按照 Topic 组织队列，订阅消费模式，可以横向水平扩展，非常适合作为日志清洗计算层和日志收集之间的缓冲区。

所以一般日志收集后，不论是 Logstash 还是 Flume，都会发送到 Kafka 中指定的 Topic 中。

在 Kafka 后端一般是一个流计算框架，上面有不同的计算任务去消费 Kafka 的数据 Topic，流计算框架实时地处理完采集到的数据，会送往分布式的文件系统中永久存储，一般是 HDFS。

日志的时间属性非常重要。因为在 HDFS 中存储日志时，为了后续抽取方便快速，一般要把日志按照日期分区。当然，在存储时，按照前面介绍的数据模型分不同的库表存储也能够方便在后续构建推荐模型时准备数据。

5. 质量检验

数据采集，日志收集还需要对采集到的数据质量做监控。数据质量通常需要数据中心的同学重点关注。推荐系统作为数据的使用方，虽然不用重点关注如何保证数据质量，但是需要能够发现数据质量问题，不然在错误的数据上无法训练出聪明的推荐模型的。

关注数据质量，大致需要关注下面几个内容。

是否完整？事件数据至少要有用户 ID、物品 ID、事件名称三元素才算完整，才有意义。

是否一致？一致是一个广泛的概念。数据就是事实，同一个事实的不同方面会表现成不同数据，这些数据需要互相佐证，逻辑自洽。

是否正确？该记录的数据一定是取自对应的数据源，这个标准不能满足则应该属于 Bug 级别，记录了错误的数据。

是否及时？虽然一些客户端埋点数据，为了降低网络消耗，会积攒一定时间打包上传数据，但是数据的及时性直接关系到数据质量。由于推荐系统所需的数据通常会都来自后端埋点，所以及时性还可以保证。

总结

今天和你聊了数据采集的若干要点。数据是推荐系统做饭的米，没有数据就没有任何推荐策略的落地，因此采集数据是一个非常重要的工作。

采集数据需要首先梳理好自己的数据有哪些，本文不是帮你梳理你的自己的产品中有哪些数据，而是告诉你看推荐系统需要哪些数据。

另外有一点，数据采集的需求方有很多，推荐系统只是其中一个，通常数据分析对数据的需求，集中在多维数据分析，当然推荐系统也需要多维数据，只是推荐系统更关注事件。

我把这些数据全都看成矩阵，有了矩阵，无论是内容推荐还是协同过滤，矩阵分解，还是机器学习深度学习，就都有了输入。

我总结了推荐系统需要四种矩阵，对应四种数据，列表如下：

为了构建推荐系统，上面四类数据足够了，其中除了 Relation 数据之外，另外三种是必须的。所以，你照着这个药方子去抓药就好了。

另外，我还介绍了日志收集系统的架构图，以及一些埋点技术的简要介绍，可以帮助理解埋点收集数据这件事。

最后，数据质量要过硬，好质量的数据胜似黄金，低质量的数据价值也就不高，收集到错误的数据除了带来存储和传输成本，还无法创造价值，所以检测数据质量也很重要。

今天就讲到这里，最后留一个思考问题，一个信息流产品，需要采集的数据具体有哪些？欢迎留言一起讨论。

感谢你的收听，我们下次再见。

【关键模块】让你的推荐系统反应更快：实时推荐

更快，更高，更强，不只是奥林匹克运动所追求的，也是推荐系统从业者所追求的三个要素：捕捉兴趣要更快，指标要更高，系统要更健壮。

我今天就要说的就是这个“更快”。推荐系统是为了在用户和物品之间建立连接，手段是利用已有的用户物品之间的连接，然而任何事物都是有生命周期的，包括这里说的这个虚无的“连接”也是有的。

为什么要实时

一个连接从建立开始，其连接的强度就开始衰减，直到最后，可能用户不记得自己和那个物品曾经交汇过眼神。因此，推荐系统既然使用已有的连接去预测未来的连接，那么追求“更快”就成了理所当然的事情。

用户和物品之间产生的连接，不论轻如点击，还是重如购买，都有推荐的黄金时间。在这个黄金时间，捕捉到用户的兴趣并且给与响应，可能就更容易留住用户。

在业界，大家为了高大上，不会说“更快”的推荐系统，而是会说“实时”推荐系统。实际上，绝对的实时是不存在的，哪怕延迟级别在微秒的推荐，也是会有延迟的。但是为了顺应时代潮流，我还是会在后面的内容中说这是实时推荐，你就那么一听，知道就好。

关于到底什么是实时推荐，实际上有三个层次。

第一层，“给得及时”，也就是服务的实时响应。这个是最基本的要求，一旦一个推荐系统上线后，在互联网的场景下，没有让用户等个一天一夜的情况，基本上最慢的服务接口整个下来响应时间也超过秒级。达到第一层不能成为实时推荐，但是没达到就是不合格。

第二层，“用得及时”，就是特征的实时更新。例如用户刚刚购买了一个新的商品，这个行为事件，立即更新到用户历史行为中，参与到下一次协同过滤推荐结果的召回中。做到这个层次，已经有实时推荐的意思了，常见的效果就是在经过几轮交互之后，用户的首页推荐会有所变化。这一层次的操作影响范围只是当前用户。

第三层，“改得及时”，就是模型的实时更新。还是刚才这个例子，用户刚刚购买了一个新的商品，那需要实时地去更新这个商品和所有该用户购买的其他商品之间的相似度，因为这些商品对应的共同购买用户数增加了，商品相似度就是一种推荐模型，所以它的改变影响的是全局推荐。

实时推荐

好，下面就讲一下如何构建一个处在第三层次的实时推荐系统。

1. 架构概览

按照前面的分析，一个处在第三层次的实时推荐，需要满足三个条件：

数据实时进来

数据实时计算

结果实时更新

为此，下面给出一个基本的实时推荐框图。

整体介绍一下这个图，前端服务负责和用户之间直接交互，不论是采集用户行为数据，还是给出推荐服务返回结果。

用户行为数据经过实时的消息队列发布，然后由一个流计算平台消费这些实时数据，一方面清洗后直接入库，另一方面就是参与到实时推荐中，并将实时计算的结果更新到推荐数据库，供推荐服务实时使用。

2. 实时数据

实时流数据的接入，在上一篇专栏中已经讲到过，需要一个实时的消息队列，开源解决方案 Kafka 已经是非常成熟的选项。

Kafka 以生产者消费者的模式吞吐数据，这些数据以主题的方式组织在一起，每一个主题的数据会被分为多块，消费者各自去消费，互不影响，Kafka 也不会因为某个消费者消费了而删除数据。

每一个消费者各自保存状态信息：所消费数据在 Kafka 某个主题某个分块下的偏移位置。也因此任意时刻、任意消费者，只要自己愿意，可以从 Kafka 任意位置开始消费数据，一遍消费，对应的偏移量顺序往前移动。示意图如下。

一个生产者可以看做一个数据源，生产者决定数据源放进哪个主题中，甚至通过一些算法决定数据如何落进哪个分块里。示意图如下：

因此，Kafka 的生产者和消费者在自己的项目中实现时都非常简单，就是往某个主题写数据，以及从某个主题读数据。

3. 流计算

整个实时推荐建立在流计算平台上。常见的流计算平台有 Twitter 开源的 Storm，“Yahoo！”开源的 S4，还有 Spark 中的 Streaming。

不过随着 Storm 使用者越来越多，社区越来越繁荣，并且相比 Streaming 的 MiniBatch 模式，Storm 才是真正的流计算。因此，在你构建自己的实时推荐时，流计算平台不妨就选用 Storm，不过最新的流计算框架 FLink 表现强劲，高吞吐低延迟，如果你所在团队有人愿意尝试一下也很不错。

Storm 是一个流计算框架，它有以下几个元素。

Spout，意思是喷嘴，水龙头，接入一个数据流，然后以喷嘴的形式把数据喷洒出去。

Bolt，意思是螺栓，像是两段水管的连接处，两端可以接入喷嘴，也可以接入另一个螺栓，数据流就进入了下一个处理环节。

Tuple，意思是元组，就是流在水管中的水。

Topology，意思是拓扑结构，螺栓和喷嘴，以及之间的数据水管，一起组成了一个有向无环图，这就是一个拓扑结构。

注意，Storm 规定了这些基本的元素，也是你在 Storm 平台上编程时需要实现的，但不用关心水管在哪，水管由 Storm 提供，你只用实现自己需要的水龙头和水管连接的螺栓即可。

因此，其编程模型也非常简单。举一个简单的例子，看看如何用 Storm 实现流计算？假如有一个字符串构成的数据流，这个数据流恰好也是 Kafka 中的一个主题，正在源源不断地在接入。

要用 Storm 实现一个流计算统计每一个字符的频率。你首先需要实现一个 Spout，也就是给数据流加装一个水龙头，这个水龙头那一端就是一个 Kafka 的消费者，从 Kafka 中不断取出字符串数据，这头就喷出来，然后再实现 Bolt，也就是螺栓。

当有字符串数据流进来时，把他们拆成不同的字符，并以（字符，1）这样的方式变成新的数据流发射出去，最后就是去把相同字符的数据流聚合起来，相加就得到了字符的频率。

实际上，如果你知道 MapReduce 过程的话，你会发现虽然 Storm 重新取了名字，仍然可以按照 MapReduce 来理解。

Storm 的模型示意如下：

Storm 中要运行实时推荐系统的所有计算和统计任务，比如有下面几种：

清洗数据；

合并用户的历史行为；

重新更新物品相似度；

在线更新机器学习模型；

更新推荐结果。

4. 算法实时化

我在前面的文章里面，已经介绍过基于物品的协同过滤原理。下面我以基于物品的协同过滤算法为主线，来讲解一下如何实现实时推荐，其他算法你可以举一反三改造。

主要是两个计算，第一个是计算物品之间的相似度。

sim(i,j)=cousers(itemi,itemj)countusers(itemi)−−−−−−−−−−−−−−−√countusers(itemj)−−−−−−−−−−−−−−−√sim(i,j)=cousers(itemi,itemj)countusers(itemi)countusers(itemj)

计算了物品和物品之间的相似度之后，用另一个公式来计算推荐分数：

rec(u,i)=∑j∈Nisim(i,j)ruj∑j∈Nisim(i,j)rec(u,i)=∑j∈Nisim(i,j)ruj∑j∈Nisim(i,j)

要做到前面说的第三层次实时推荐，首先就是要做到增量更新物品之间的相似度。相似度计算分成三部分：

分子上的“物品对”，共同评分用户数；

分母上左边是物品 i 的评价用户数；

分母上右边是物品 j 的评价用户数。

所以更新计算相似度就要更新三部分，实际上一种相似度增量更新策略是在收到一条用户评分事件数据时，然后取出这个用户的历史评分物品列表，因为所有的历史评分物品现在和这个新评分物品之间，就要增加一个共同评分了。

并且，这个新物品本身，也要给自己一个评分用户数。更新完三个后，就实时更新所有这些“物品对”的相似度了。

转换成 Storm 的编程模型，你需要实现：

Spout：消费实时消息队列中的用户评分事件数据，并发射成（UserID , ItemID_i）这样的 Tuple

Bolt1：接的是源头 Spout，输入了 UserID 和 ItemID_i，读出用户历史评分 Item 列表，遍历这些 ItemID_j，逐一发射成 ((Item_i, Item_j), 1) 和 ((Item_j, Item_i), 1)，并将 Item_i 加进历史评分列表中；

Bolt2：接的是源头 Spout，输入了 UserID 和 ItemID_i，发射成 (ItemID_i, 1)；

Bolt3：接 Bolt1，更新相似度所需的分子

Bolt4：接 Bolt2，更新物品自己的评分用户数

把这个过程表示成公式就是：

sim(i,j)=cousers(itemi,itemj)+Δcouserscountusers(itemi)+Δcountusers(i)−−−−−−−−−−−−−−−−−−−−−−−−−−−−−√countusers(itemj))+Δcountusers(j)−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−√sim(i,j)=cousers(itemi,itemj)+Δcouserscountusers(itemi)+Δcountusers(i)countusers(itemj))+Δcountusers(j)

另外，还有实时更新推荐结果，也是作为 Storm 的一个 Bolt 存在，接到用户行为数据，重新更新推荐结果，写回推荐结果数据中。

5. 效率提升

上面展示了一个基于物品的协同过滤算法在实时推荐中的计算过程，那么随之而来的一些问题也需要解决。比如当用户历史行为数据有很多时，或者物品对是热门物品时，相似度实时更新就有些挑战了。对此可以有如下应对办法：剪枝，加窗，采样，缓存。

所谓剪枝就是，并不是需要对每一个“物品对”都做增量计算，为什么呢？

你想一想，两个物品之间的相似度，每更新一次得到的新相似度，可以看成一个随机变量，那么这个随机变量就有一个期望值，一旦物品之间的相似度可以以较高的置信度确认，它已经在期望值附近小幅度波动了，也就没必要再去更新了。

甚至如果进一步确定是一个比较小的相似度，或者可以直接干掉这个物品对，不被更新，也不参与计算。

那么问题就来了，怎么确定什么时候可以不再更新这个物品对的相似度了呢？这时候要用到一个不等式：Hoeffding 不等式。

Hoeffding 不等式适用于有界的随机变量。相似度明显是有界的，最大值是 1，最小值是 0。所以可以用这个不等式，Hoeffding 不等式是这样一个统计法则：随机变量的真实期望值不会超过 x^+ϵx^+ϵ 的概率是概率 1- δδ，其中 ϵϵ 的值是这样算的：

ϵ=ln(1δ)2n−−−−−√ϵ=ln(1δ)2n

公式中：x^x^ 是历次更新得到的相似度平均值，n 是更新过的次数。这样一来，你选定 δδ 和 ϵϵ 之后就知道更新多少次之后就可以放心大胆地使用了。

例如，下面这个表格是举的几个例子，这里设置 δ=0.05δ=0.05。

也就是在前面讲到的更新相似度的 Bolt 中，如果发现一个物品对的更新次数已经达到最少更新次数，则可以不再更新，并且，如果此时相似度小于设定阈值，就可以斩钉截铁地说：这两个物品不相似，以后不用再参与推荐计算了。

这就是一项基于统计的剪枝方法，除此之外还有加窗、采样、合并三种常规办法。

首先，关于加窗。当我说，用户的兴趣会衰减，请你不要怀疑这一点，因为这是这篇文章的基本假设和出发点。

用户兴趣衰减，那么一个直接的推论就是，比较久远的用户历史行为数据所起的作用应该小一些。

所以，另一个剪枝技术就是：滑窗。设定一个时间窗口，时间窗口内的历史行为数据参与实时计算，窗口外的不再参与实时计算。这个窗口有两种办法：

最近 K 次会话。用户如果反复来访问产品，每次访问是一次会话，那么实时计算时只保留最近 K 次会话信息。

最近 K 条行为记录。不管访问多少次，只保留最近 K 条历史行为事件，参与到实时推荐中。

两种滑窗方法都可以有效保证实时计算的效率，同时不会明显降低推荐效果。

关于采样。当你的推荐系统遇到热门的物品或者异常活跃的用户，或者有时候就只是突然一个热点爆发了。

它们会在短时间产生大量的数据，除了前面的剪枝方法，还可以对这种短时间大量出现的数据采样，采样手段有很多，可以均匀采样，也可以加权采样，这在前面的专栏里已经详细介绍过方法。

关于合并计算。在前面介绍的增量计算中，是假设收到每一个用户行为事件时都要去更新相似度和推荐结果，如果在突然大量涌入行为数据时，可以不必每一条来了都去更新，而是可以在数据流的上游做一定的合并。

相似度计算公式的分子分母两部分都可以这样做，等合并若干事件数据之后，再送入下游去更新相似度和推荐结果。

最后，提高实时推荐的效率，甚至不只是推荐系统，在任何互联网应用的后端，缓存都是提高效率必不可少的部分。可以根据实际情况，对于高频访问的物品或者用户增加缓存，这可能包括：

活跃用户的历史行为数据；

热门物品的特征数据；

热门物品的相似物品列表。

缓存系统一般采用 Memcached 或者 Redis 集群。缓存有个问题就是，数据的一致性可能比较难保证，毕竟它和真正的业务数据库之间要保持时时刻刻同步也是一项挑战。

好了，上面讲到的这些实时推荐有关的优化技巧，其实都是为了满足第三层次的实时推荐要求。

实时更新的推荐结果同步到推荐服务所依赖的线上数据库，这个线上数据库还要定期被线下离线批量的推荐结果所替代。这样一来，实时推荐和离线批量之间就形成了互为补充的作用，这个模式也就是大数据架构最常见的 Lambda 架构。

总结

今天以协同过滤为例讲到了如何构建一个实时推荐，实际上，并不是每一种推荐算法都适合做实时推荐的，或者没必要。

幸运的是，很多机器学习算法，都可以使用一些在线学习方法更新模型，这些在线学习算法非常适合作为流计算的任务运行，属于我说的第三层次实时推荐。

另外还有一种算法天然就适合在线实时进行，那就是前面讲到的 Bandit 算法，通过和用户之间反复互动更新推荐。

实时推荐有三个层次，很多非工程师的朋友们常常脑海里想象的实时推荐实际上只是第二层次，也就是实时更新特征，并没实时更新模型，虽然两者的结果看上去都是推荐结果实时更新了，但是意义不同，难度不同，效果也不同。

今天的话题就聊到这里，留给你一个小小的问题，你能说一说你觉得哪些推荐策略不适合放在实时推荐中吗？欢迎留言一起讨论。

【关键模块】让数据驱动落地，你需要一个实验平台

数据驱动这个口号喊了很多年了，这个口号也几乎成为了行业共识，但是数据驱动又像鬼一样，人人都在说，但几乎没人见过它长什么样子。

数据驱动和实验平台

要做到数据驱动，就要做到两点：第一点是数据，第二点是驱动。这听上去似乎像是废话，实际上不是。

这第一点的意思是，要采集数据，全方位，数据像是石油一样，没有它就谈不上驱动；第二点的意思是要让大家看数据，光采集了没有用，还需要让所有人盯着数据看。

而要做到驱动，需要一个 AB 实验平台。数据驱动的重点是做对比实验，通过对比，让模型、策略、设计等不同创意和智慧结晶新陈代谢，不断迭代更新。

对比实验也常常被大家叫做 ABTest，这个意思就是一个 A 实验，一个 B 实验，这样说可能有些模糊，所以我需要先和你说说什么叫做对比实验，然后再说说一个对比实验平台应该长什么样子。

你都可以把任何一家个性化推荐产品想象成一个函数，这个函数有很多参数影响它工作，函数的输出就是推荐物品列表。这些函数参数可以有各种组合，通过其中一种参数组合去面对一小股用户的考验，这就是一个实验。

要做实验，要做很多实验，要很快做很多实验，要很多人同时很快做很多实验，就需要实验平台。

要讨论实验平台，先要认识实验本身。互联网实验，需要三个要素。

流量：流量就是用户的访问，也是实验的样本来源。

参数：参数就是各种组合，也是用户访问后，从触发互联网产品这个大函数，到最后返回结果给用户，中间所走的路径。

结果：实验的全过程都有日志记录，通过这些日志才能分析出实验结果，是否成功，是否显著。

把互联网产品想象一个有向无环图，每个节点是一个参数，不同的分支是参数的不同取值，直到走到终点，这一条路径上所有经过的参数取值，构成了服务的调用路径。

具体在推荐系统中，可能这些参数就是不同的模型与策略名称。每当一个用户经过这一系列的调用路径后，就为每一个分支产生了一条实验样本。

于是问题来了，每一个用户到来时，如何为他们决定要走哪条路径呢？这就要先经过实验对照来看。

实验要观察的结果就是一个随机变量，这个变量有一个期望值，要积累很多样本才能说观察到的实验结果比较接近期望值了，或者要观察一定时期才能说对照实验之间有区别或者没区别。

因为只有明显有区别并且区别项好，才能被进一步推上全线。

在设计一个实验之初，实验设计人员总是需要考虑下面这些问题。

实验的起止时间。这涉及到样本的数量，关系到统计效果的显著性，也涉及能否取出时间因素的影响。

实验的流量大小。这也涉及了样本的数量，关系到统计效果的显著性。

流量的分配方式。每一个流量在为其选择参数分支时，希望是不带任何偏见的，也就是均匀采样，通常按照 UUID 或者 Cookie 随机取样。

流量的分配条件。还有一些实验需要针对某个流量的子集，例如只对重庆地区的用户测试，推荐时要不要把火锅做额外的提升加权。

流量如何无偏置。这是流量分配最大的问题，也是最难的问题。同时只做一个实验时，这个问题不明显，但是要同时做多个实验，那么如何避免前面的实验给后面的实验带来影响，这个影响就是流量偏置，意思是在前面实验的流量分配中，有一种潜在的因素在影响流量分配，这个潜在的因素不易被人察觉，潜在的因素如果会影响实验结果，那么处在这个实验后面获得流量的实验，就很难得到客观的结论。这个无偏置要求，也叫做“正交”。

这些问题也是实验平台在设计之初要考虑的。试想一下，推荐系统中，算法工程师总是在尝试很多模型，或者在线下给出很多的模型调参，线下评测时，各种指标都是一片锣鼓喧天、红旗招展，恨不得立即上线去验验真实效果。

每一个算法工程师都这么想，但是线上流量有限，因此需要重叠实验，废水循环，最好能够做到洗脸的水冲马桶，这样灵活的实验平台长什么样子。

Google 公司的实验平台已经成为行业争相学习的对象，所以今天我会以 Google 的实验平台为主要对象，深入浅出地介绍一个重叠实验平台的方方面面。

重叠实验架构

所谓重叠实验，就是一个流量从进入产品服务，到最后返回结果呈现给用户，中间设置了好几个检查站，每个检查站都在测试某些东西，这样同时做多组实验就是重叠实验。

前面说了，重叠实验最大的问题是怎么避免流量偏置。为此，需要引入三个概念。

域：是流量的一个大的划分，最上层的流量进来时首先是划分域。

层：是系统参数的一个子集，一层实验是对一个参数子集的测试。

桶：实验组和对照组就在这些桶中。

层和域可以互相嵌套。意思是对流量划分，例如划分出 50%，这 50% 的流量是一个域，这个域里面有多个实验层，每一个实验层里面还可以继续嵌套域，也就是可以进步划分这 50% 的流量。下面这两个图示意了有域划分和没有域划分的两种情况。

图中左边是一个三层实验，但是并没有没有划分域。第一层实验要测试 UI 相关，第二层要测试推荐结果，第三层要测试在推荐结果插入广告的结果。

三层互不影响。图中的右边则添加了域划分，也就是不再是全部流量都参与实验中，而是被分走了一部分到左边域中。剩下的流量和左边的实验一样。

这里要理解一点，为什么多层实验能做到重叠而不带来流量偏置呢？

这就需要说桶的概念。还是上面示意图中的左图，假如这个实验平台每一层都是均匀随机分成 5 个桶，在实际的实验平台上，可能是上千个桶，这里只是为了举例。

示意图如下：

这是一个划分域的三层实验。每一层分成 5 个桶，一个流量来了，在第一层，有统一的随机分流算法，将 Cookie 或者 UUID 加上第一层 ID，均匀散列成一个整数，再把这个整数对 5 取模，于是一个流量就随机地进入了 5 个桶之一。

每一个桶均匀得到 20% 的流量。每一个桶里面已经决定好了为你展示什么样的 UI，流量继续往下走。每一个桶的流量接着依然面对随机进入下一层实验的 5 个桶之一，原来每个桶的 20% 流量都被均分成 5 份，每个桶都有 4% 的流量进入到第二层的每个桶。

这样一来，第二层每个桶实际上得到的依然是总流量的 20%，而且上一层实验带来的影响被均匀地分散在了这一层的每一个桶中，也就是可以认为上一层实验对这一层没有影响。同样的，第三层实验也是这样。

这就是分层实验最最基本的原理。在这个基础上，增加了域的概念，只是为了更加灵活地配置更多实验。

关于分层实验，有几点需要注意：

每一层分桶时，不是只对 Cookie 或者 UUID 散列取模，而是加上了层 ID，是为了让层和层之间分桶相互独立；

Cookie 或者 UUID 散列成整数时，考虑用均匀的散列算法，如 MD5。

取模要一致，为了用户体验，虽然是分桶实验，但是同一个用户在同一个位置每次感受不一致，会有损用户体验。

Google 的重叠实验架构还有一个特殊的实验层，叫做发布层，优先于所有其他的实验层，它拥有全部流量。这个层中的实验，通常是已经通过了 ABtest 准备全量发布了。示意图如下：

前面举例所说的对用户身份 ID 做散列的流量分配方式，只是其中一种，还有三种流量分配方式，一共四种：

Cookie+ 层 ID 取模；

完全随机；

用户 ID+ 层 ID 取模；

Cookie+ 日期取模。

在实验中，得到流量后还可以增加流量条件，比如按照流量地域，决定要不要对其实验，如果不符合条件，则这个流量不会再参与后面的实验，这样避免引入偏置，那么这个流量会被回收，也就是使用默认参数返回结果。

在 Google 的架构中，由于层和域还可以嵌套，所以在进入某个层时，可能会遇到一个嵌套域，这时候需要按照域划分方式继续下沉，直到遇到实验或者被作为回收流量返回。整个实验平台，工作的示意图如下所示：

说明如下：

图中涉及了判断的地方，虚线表示判断为假，实线表示判断为真。

从最顶端开始，不断遍历域、层、桶，最终输出一个队列 Re，其中记录对每一个系统参数子集如何处理，取实验配置的参数还是使用默认参数，其中无偏流量表示使用默认参数，也就是在那一层不参与实验，流量被回收。

拿到 Re 就得到了全部的实验，在去调用对应的服务。

统计效果

除了分层实验平台之外，还存在另一个问题，每一个实验需要累计获得多少流量才能得到实验结论呢？

这涉及了一点统计学知识。实验得到的流量不够，可以说实验的结论没有统计意义，也就浪费了这些流量，而实验在已经具有统计意义之后，如果还占用流量做测试，则也是在浪费流量。

如何确定实验规模呢？Google 给出了如下公式：

N>=10.5(sθ)2N>=10.5(sθ)2

公式中：

ss 是实验指标的标准差。

θθ 是希望检测的敏感度，比如想检测到 2% 的 CTR 变化。

上面这个公式计算出来的实验规模，表示以 90% 的概率相信结果的显著性，也就是有 90% 的统计功效。

对比实验的弊端

AB 测试实验平台，是产品要做到数据驱动必不可少的东西，但是这种流量划分的实验方式也有自己的弊端，列举如下：

落入实验组的流量，在实验期间，可能要冒着一定的风险得到不好的用户体验，在实验结束之前，这部分流量以 100% 的概率面对这不确定性；

要得得到较高统计功效的话，就需要较长时间的测试，如果急于看到结果全面上线来说有点不能接收；

下线的实验组如果不被人想起，就不再有机会得到测试。

诸如此类弊端，也可以考虑在实验平台中用 Bandit 算法替代流量划分的方式，通过 Bandit 算法选择不同的参数组合、策略，动态实时地根据用户表现给出选择策略，一定程度上可以避免上面列举的弊端。

总结

实验平台是推荐系统要做到数据驱动必不可少的东西，但是如何做到科学高效快速地做实验呢？

常见的做实验，只是简单地选择一个尾号的用户 ID 作为实验组，再选择另一个尾号作为对照组，甚至选择剩下所有的用户 ID 作为对照组。

这样做出来的实验，显然是有问题，因为并不知道通过用户尾号来分组是不是能做到无偏？另一个问题是，这样就只能在一个时期只能做一个实验，非常低效。

本文以 Google 开放的实验平台架构作为原型，对其核心技术做了详细介绍。这个实验平台做到了同时无偏地做多组对照实验。因为它巧妙地引入了三个概念的嵌套结合：

域；

层；

桶。

三个概念层层相扣，流量划分得到了一个可行的方案。这个实验平台方案已经应用在很多公司中，你不妨在自己的公司尝试做一下。

最后留给你一个问题，关于分层实验的原理，你是否已经理解了为什么层和层之间可以做到毫不影响，欢迎给我留言讨论。

【常见架构】Netflix个性化推荐架构

你是否常常被乱花渐欲迷人眼的推荐算法绕得如坠云中，觉得好像算法就是推荐系统的全部，哪怕就算不是全部，也肯定至少是个嫡生的长子。

然而，实际上工程实现才是推荐系统的骨架，如果没有很好的软件实现，算法不能落地产生效果，产品不能顺畅地服务用户，不能顺利地收集到用户的反馈，更不能让推荐系统往更好的方向进化。

一个好的推荐系统不仅仅是在线下模型评测指标多么好，也不仅仅是在某个时刻像是灵光乍现一样击中了用户某个口味，而是随着用户的不断使用，产品和用户一起变好，产品背后的人得到进步，用户也越来越喜欢产品。

虽然影响是否用户产品的因素有很多很多，但是能否流畅地给用户提供服务是一个最基本的标准。

架构的重要性

推荐系统向来是一个锦上添花的东西，因此传统的观点是推荐系统更加注重线下的模型效果，而非线上的服务质量。但是你也知道，时至今日，推荐系统不再只是锦上添花，而是承担了产品的核心功能。因此，对推荐系统架构的要求也高了很多。

一个好的推荐系统架构应该具有这些特质：

实时响应请求；

及时、准确、全面记录用户反馈；

可以优雅降级；

快速实验多种策略。

上一篇专栏文章介绍的是当下最热门的推荐系统产品形式——信息流的架构，信息流并不是传统意义上的推荐系统，今天我要介绍一种更符合经典推荐系统的架构，这就是著名的流媒体 Netflix 的推荐系统架构。

通过这篇文章，我会为你介绍，实现一个简化版的推荐系统架构应该至少包含哪些元素，同时，我会带你一起总结出，一个经典推荐系统架构应该有的样子。

经典架构

好了，废话少说，我先上图。下面这张图就是 Netflix 的推荐系统架构图。

我先整体看一下这个架构，一共分成三层：在线、近线、离线。

你是不是也发现似乎有一个不太熟识的词出现：近线。对，这个近线是通常不太提的一个概念，或者通常就把它归入了在线的范畴。

实际上，可以这样定义这三个层级：

离线：不用实时数据，不提供实时服务；

近线：使用实时数据，不保证实时服务；

在线：使用实时数据，要保证实时服务。

在具体介绍这些内容之前，我先来说说数据流的情况。

1. 数据流

用户在产品 UI 上使用产品，消费展示的内容，产生行为事件数据，实时地被收集走，一边进入分布式的文件系统中存储，供离线阶段使用，另一边流向近线层的消息队列，供近线阶段的流计算使用。

离线存储的全量数据被抽取出来，组成离线计算所需的训练数据，这些训练数据被一个管理数据生成和发布的组件统一管理，要使用数据的下游，比如模型训练会在离线数据生成时得到这个组件的通知，从而开始训练，训练得到的模型用于进一步为用户计算推荐结果。

离线阶段的推荐结果或者模型在近线阶段被更新，进一步在在线阶段被直接使用，产生最终的推荐结果，呈现给用户。

这是整个数据流情况。下面我一一详细介绍每个部分。

2. 在线层

在线层的触发时机是当用户发出请求，也就是用户进入一个推荐场景，推荐位等着展示推荐结果时，这个时候需要承担责任就是在线层。在线层就是实时响应用户请求。简单说，在线层的特点就是“使用实时数据，要保证实时服务”。

在线层的优势有：

直接首次接触到大多数最新数据；

对用户请求时的上下文了如指掌；

只需计算必须的信息，不需要考虑所有的信息。

在线层也有严格的制约：

严格的服务响应时间，不能超时，或者让用户等太久；

服务要保证可用性，稳定性；

传输的数据有限。

在线层常常展现出的形式就是 Rest API 形式，后端则通常是 RPC 服务内部互相调用，以用户 ID、场景信息去请求，通常就在 ms 响应时间内返回 Json 形式的推荐结果。那么哪些计算逻辑适合放在在线层呢？

简单的算法逻辑；

模型的预测阶段；

商业目标相关的过滤或者调权逻辑；

场景有关的一些逻辑；

互动性强的一些算法。

在线阶段要处理的对象一般是已经预处理后的推荐结果，是少量物品集合。

比如说当用户访问一个物品详情页，需要做相关推荐，那么在线阶段给在线服务的 Rest API 传入用户身份以及当前的物品 ID，实时地取出物品 ID 对应的相关物品 ID，再根据用户信息对这些物品 ID 做一些重排和过滤，就可以输出了，整个过程都是在 ms 级别完成。

这个实时响应的过程中，如果发生意外，比如说这个物品 ID 就没有相关的物品，那么这时候服务就需要降级，所谓的降级就是不能达到最好的效果了，但是不能低于最低要求，这里的最低要求就是必须要返回东西，不能开天窗。

于是，这就降级为取出热门排行榜返回。虽然不是个性化的相关结果，但是总比开天窗要好。这就是服务的可用性。

在线阶段还要实时地分发用户事件数据，就是当用户不断使用产品过程产生的行为数据，需要实时地上报给有关模块。这一部分也是需要实时的，比如用于防重复推荐的过滤。

3. 离线层

讲完在线层，再来看看离线层。离线层就是躲在推荐系统的大后方，批量、周期性地执行一些计算任务。其特点是“不用实时数据，不提供实时服务”。

离线层的示意图如下：

离线阶段主要面对的数据源就是 Hadoop，实质上是 HDFS。收集到的所有日志都存在这里面，是一个全量的数据中心。

通过 Pig 或者 Hive 等工具，从全量日志中按照算法要求抽取出不同的数据，再加上其他数据变成了不同算法所需的数据源。

如果这种数据源比较多时，就需要有专门的工具统一管理起来，这个管理上要求：

数据准备好之后及时通知相关方，也就是要有订阅发布的模式；

能够满足下游不同的存储系统；

完整的监控体系，并且监控过程对于数据使用方是透明的。

在 Netflix 内部，承担这个管理任务的工具叫做 Hermes，类似 Kafka，但是又有不同的内部工具。

离线阶段的任务主要是两类：模型训练和推荐结果计算。

通常机器学习类模型，尤其是监督学习和非监督学习，都需要大量的数据和多次迭代，这类型的模型训练任务最适合放在离线阶段。

举个例子，你已经知道推荐系统中会有召回和融合排序这两个阶段。通常一些推荐算法，例如协同过滤就是在离线阶段计算出每个人的推荐结果，作为线上融合排序的候选集之一，也就是示意图中的“推荐结果”。

另一方面，假如融合排序模型时逻辑回归，那么逻辑回归模型的参数也通常在离线阶段就训练完成的，在线阶段也只是取出来参数用于计算而已。

离线阶段有以下这么几个好处：

可以处理最大的数据量；

可进行批量处理和计算；

不用有响应时间等要求。

当然坏处也是明显的：

无法及时响应前端需求；

面对的数据较静态，无法及时反应用户的兴趣变化。

大多数推荐算法，实际上都是在离线阶段产生推荐结果的。离线阶段的推荐计算和模型训练，如果要用分布式框架，通常可以选择 Spark 等。

4. 近线层

最后，我来讲讲近线层。近线层的特点是“使用实时数据，不保证实时服务”，这实在是一个很不讲道理的计算层，因为把它的特点翻译得直白点就是：喂给我最新鲜的牧草，但是我不保证能马上给你挤奶。

虽然这看上去蛮不讲理，但实际上这是一个非常重要的一层，它结合了离线层和在线层的好处，摒弃了两者的不足。

近线层，也叫做准实时层，所谓“准实时”，就是接近实时，但不是真的实时。

从前面的架构图中也可以看出，这一层的数据来源是实时的行为事件队列，但是计算的结果并不是沿着输入数据的方向原路返回，而是进入了在线数据库中，得到用户真正发起请求时，再提供服务。

一个典型的近线计算任务是这样的：从事件队列中获取最新的一个或少许几个用户反馈行为，首先将这些用户已经反馈过的物品从离线推荐结果中剔除，进一步，用这几个反馈行为作为样本，以小批量梯度下降的优化方法去更新融合模型的参数。

这两个计算任务都不会也不需要立即对用户做出响应，也不必须在下一次用户请求时就产生效果，就是说当用户实时请求时，不需要去等待近线任务的最新结果，因为两者是异步的。

近线计算任务一个核心的组件就是流计算，因为它要处理的实时数据流。常用的流计算框架有 Storm，Spark Streaming，FLink 等，Netflix 采用的内部流计算框架 Manhattan，这和 Storm 类似。

略有区别的是 Spark Streaming，实际上并不是实时流计算，而是小批量计算。

简化架构

Netflix 是为全球多个国家同时提供在线服务的，因此推荐系统的架构略微复杂。倘若你现在刚刚接手一个新产品，要从 0 开始搭建一个推荐系统，那么可以以 Netflix 的架构作为蓝本，做一定的简化。

关键简化有两点：

完全舍弃掉近线层；

避免使用分布式系统。

其中第二点，在一个新产品的场景下， 当数据量还没有那么大时，使用分布式存储或者计算框架，非常不划算。

如果性能不足，请升级单机配置。根据经验，一个几千万用户，几十万到百万的物品的协同过滤或者矩阵分解，如果充分发挥单机的性能，综合效率会远远优于在 Spark 上运行。

另外在一个推荐系统刚从 0 开始的阶段，离线阶段的算法也没有那么多，很多情况甚至都只有协同过滤结果，这时候线上融合模型也不必那么复杂，一个简单的加权融合就可以了，因此在线层也不必复杂。

总结

今天我以 Netflix 架构为原型，向你介绍了一个经典的推荐系统架构长什么样子。关于这个架构你只需要记住一点：它有三层，三层分别是离线，近线，在线。

我用如下的表格将这三层综合对比，并且简单举例，我们看看每一层分别放哪些任务。

以上就是对这个架构的宏观总结对比。如前所说，其实架构都是进化出来的，你千万不必在一开始就追求完美的架构，满足最低要求就好。

针对这个架构提一个问题，前面讲到的 Bandit 算法，你觉得应该在哪一层比较好呢？欢迎留言讨论。

【常见架构】典型的信息流架构是什么样的

从今天起，我们不再单独介绍推荐算法的原理，而是开始进入一个新的模块——工程篇。

在工程实践的部分中，我首先介绍的内容是当今最热门的信息流架构。

信息流是推荐系统应用中的当红炸子鸡，它表现形式有很多：社交网络的动态信息流、新闻阅读的图文信息流、短视频信息流等等。

如果要搭建一个自己的信息流系统，它应该是怎么样的呢？今天，我就来带你一探信息流架构的究竟。

整体框架

信息流，通常也叫作 feed，这个英文词也很有意思，就是“喂”给用户的意思。

传统的信息流产品知识简单按照时间排序，而被推荐系统接管后的信息流逐渐成为主流，按照兴趣排序，也叫作“兴趣 feed”。

所以我们通常提到信息流，或者兴趣 feed，其实都是在说同一个话题。

这里温馨提示一下：如果要搜索 feed 相关的技术文章，你应该用“Activity Stream”作为关键词去搜，而不应该只用“feed”搜索，Activity Stream 之于 feed，就好比多潘立酮之于吗丁啉，前者是行话，后者是通俗说法。

要实现一个信息流，整体逻辑上是比较清楚的。可以划分为两个子问题。

如何实现一个按照时间顺序排序的信息流系统？

如何给信息流内容按照兴趣重排序？

我这里先给出一个整体的框架，然后再分别详谈。

这张架构图划分成几个大的模块：日志收集、内容发布、机器学习、信息流服务、监控。这里分别介绍一下：

日志收集，是所有排序训练的数据来源，要收集的最核心数据就是用户在信息流上产生的行为，用于机器学习更新排序模型；

内容发布，就是用推或者拉的模式把信息流的内容从源头发布到受众端；

机器学习，从收集的用户行为日志中训练模型，然后为每一个用户即将收到的信息流内容提供打分服务；

信息流服务，为信息流的展示前端提供 Rest API；

监控，这是系统的运维标配，保证系统的安全和稳定等。

数据模型

信息流的基本数据有三个：用户（User）、内容（Activity）和关系（Connection）。
用户不用说，就是区别不同用户的身份 ID，我来说一说其他的两种。

1. 内容即 Activity。

用于表达 Activity 的元素有相应的规范，叫作 Atom，你可以参考它并结合产品需求，定义出自己的信息流数据模型来。

根据 Atom 规范的定义，一条 Activity 包含的元素有：Time、Actor、Verb、Object、Target、Title、Summary。下面详细解释一下这些元素。

Time：即“Activity 发生的时间”。

Actor：即“Activity 由谁发出的”。通常 Actor 就是用户 ID，但是我们也可以扩展到其他拟人化物体上，如关注的一个“店铺”，收藏的一部“电影”，或者用户喜欢的一个标签或者分类。也就是和用户建立连接的另一端。

Verb：动词，就是连接的名字，比如“Follow”“Like”等，也可以是隐含的连接，如挖掘出的用户兴趣词和用户之间这种潜规则。

Object：即动作作用到最主要的对象，只能有一个，比如一个人赞过的一张照片，店铺上新的一件商品，一个分类下一篇新的文章。

Target：动作的最终目标，与 verb 有关，可以没有。它对应英语中介词 to 后接的事物，比如“John saved a movie to his wishlist”（John 保存了一部电影到清单里），这里电影就是 Object，而清单就是 Target。

Title：这个是 Activity 的标题，用自然语言描述，用于展示给用户。

Summary：通常是一小段 HTML 代码，是对这个 Activity 的描述，还可能包含类似缩略图这样的可视化元素，可以理解为 Activity 的视图，不是必须的。

举个例子： 2016 年 5 月 6 日 23:51:01（Time）@刑无刀（Actor） 分享了（Verb） 一条微博（Object） 给 @极客时间 （Target）。把前面这句话去掉括号后的内容就是它的 Title，Summary 暂略。

除了上面的字段外，还有一个隐藏的 ID，用于唯一标识一个 Activity。社交电商 Etsy 在介绍他们的信息流系统时，还创造性地给 Activity 增加了 Owner 属性，同一个 Activity 可以属于不同的用户，相当于考虑了方向。

2. 关系即连接。

互联网产品里处处皆连接，有强有弱，好友关系、关注关系等社交是较强的连接，还有点赞、收藏、评论、浏览，这些动作都可以认为是用户和另一个对象之间建立了连接。有了连接，就有信息流的传递和发布。

定义一个连接的元素有下面几种。

From：连接的发起方。

To：被连接方。

Type/Name：就是 Atom 模型中的 Verb，即连接的类型：关注、加好友、点赞、浏览、评论，等等。

Affinity：连接的强弱。

如果把建立一个连接视为一个 Activity 模型的话，From 就对应 Activity 中的 Actor，To 就对应 Activity 中的 Object。

连接的发起从 From 到 To，内容的流动从 To 到 From。Connection 和 Activity 是相互加强的，这是蛋和鸡的关系：有了 Activity，就会产生 Connection，有了 Connection，就可以“喂”（feed）给你更多的 Activity。

在数据存储上可以选择的工具有下面的几种：

Activity 存储可以采用 MySQL、Redis、Cassandra 等；
Connection 存储可以采用 MySQL；
User 存储可以采用 MySQL。

动态发布

用户登录或者刷新后，信息流是怎么产生的呢？我们把动态内容出现在受众的信息流中这个过程称为 Fan-out，直觉上是这样实现的：

获取用户所有连接的终点（如好友、关注对象、兴趣标签）；

获取这些连接终点（关注对象）产生的新内容（Activity）；

按照某个指标排序后输出。

上面这个步骤别看简单，在一个小型的社交网络上，通常很有效，而且 Twitter 早期也是这么做的。这就是江湖行话说的“拉”模式（Fan-out-on-load），即：信息流是在用户登录或者刷新后实时产生的。这里有一个示意图，你可以点击查看。

拉模式就是当用户访问时，信息流服务才会去相应的发布源拉取内容到自己的 feed 区来，这是一个阻塞同步的过程。“拉”模式的好处也显而易见，主要有下面两种。

实现简单直接：一行 SQL 语句就搞定了。

实时：内容产生了，受众只要刷新就看得见。

但是也有很大的不足：

随着连接数的增加，这个操作的复杂度指数级增加；

内存中要保留每个人产生的内容；

服务很难做到高可用。

与“拉”模式对应，还有一个“推”模式（Fan-out-on-write）。我在文稿里放了一张图，你可以点击查看。

当一个 Actor 产生了一条 Activity 后，不管受众在不在线，刷没刷新，都会立即将这条内容推送给相应的用户（即和这个 Actor 建立了连接的人），系统为每一个用户单独开辟一个信息流存储区域，用于接收推送的内容。如此一来，当用户登录后，系统只需要读取他自己的信息流即可。

“推”模式的好处显而易见：在用户访问自己的信息流时，几乎没有任何复杂的查询操作，所以服务可用性较高。

“推”模式也有一些不足。

大量的写操作：每一个粉丝都要写一次。

大量的冗余存储：每一条内容都要存储 N 份（受众数量）。

非实时：一条内容产生后，有一定的延迟才会到达受众信息流中。

无法解决新用户的信息流产生问题。

既然两者各有优劣，那么实际上就应该将两者结合起来，一种简单的结合方案是全局的：

对于活跃度高的用户，使用推模式，每次他们刷新时不用等待太久，而且内容页相对多一些；

对于活跃度没有那么高的用户，使用拉模式，当他们登录时才拉取最新的内容；

对于热门的内容生产者，缓存其最新的 N 条内容，用于不同场景下的拉取。

还有一种结合方案是分用户的，这是 Etsy 的设计方案：

如果受众用户与内容产生用户之间的亲密度高，则优先推送，因为更可能被这个受众所感兴趣；

如果受众用户与内容产生用户之间的亲密度低，则推迟推送或者不推送；

也不是完全遵循亲密度顺序，而是采用与之相关的概率。

在中小型的社交网络上，采用纯推模式就够用了，结合的方案可以等业务发展到一定规模后再考虑。

对于信息流的产生和存储可以选择的工具有：

用户信息流的存储可以采用 Redis 等 KV 数据库, 使用 uid 作为 key。

信息流推送的任务队列可以采用 Celery 等成熟框架。

信息流排序

信息流的排序，要避免陷入两个误区：

没有目标；

人工量化。

第一个误区“没有目标”意思就是说，设计排序算法之前，一定要先弄清楚为什么要对时间序重排？希望达到什么目标？只有先确定目标，才能检验和优化算法。

第二个误区是“人工量化”，也就是我们通常见到的产品同学或者运营同学要求对某个因素加权、降权。这样做很不明智，主要是不能很好地持续优化。

目前信息流采用机器学习排序，以提升类似互动率，停留时长等指标，这已经成为共识。比如说提高互动率则需要下面几个内容。

首先，定义好互动行为包括哪些，比如点赞、转发、评论、查看详情等；

其次，区分好正向互动和负向互动，比如隐藏某条内容、点击不感兴趣等是负向的互动。

基本上到这里就可以设计成一个典型的二分类监督学习问题了，对一条信息流的内容，在展示给用户之前，预测其获得用户正向互动的概率，概率就可以作为兴趣排序分数输出。

能产生概率输出的二分类算法都可以用在这里，比如贝叶斯、最大熵、逻辑回归等。

互联网常用的是逻辑回归（Logistic Regression），谁用谁知道，用过的都说好；也有 Facebook 等大厂采用了逻辑回归加梯度提升树模型（又称 GBDT）来对信息流排序，效果显著。

如今大厂都已经转向深度学习了，但我还是建议小厂或者刚起步的信息流先采用线性模型。

对于线性模型，一个重要的工作就是特征工程。信息流的特征有三类：

用户特征，包括用户人口统计学属性、用户兴趣标签、活跃程度等；

内容特征，一条内容本身可以根据其属性提取文本、图像、音频等特征，并且可以利用主题模型提取更抽象的特征。

其他特征，比如刷新时间、所处页面等。

排序模型在实际使用时，通常做成 RPC 服务，以供发布信息流时调用。

数据管道

信息流是一个数据驱动的系统，既要通过历史数据来寻找算法的最优参数，又要通过新的数据验证排序效果，所以搭建一个数据流管道就是大家翘首期盼的。

这个管道中要使用的相关数据可能有：

互动行为数据，用于记录每一个用户在信息流上的反馈行为；

曝光内容，每一条曝光要有唯一的 ID，曝光的内容仅记录 ID 即可；

互动行为与曝光的映射关系，每条互动数据要对应到一条曝光数据；

用户画像内容，即用户画像，提供用户特征，具体请见我在第 4、5、6 三篇中的内容；

信息流的内容分析数据，提供内容特征，即物品画像。

对于一个从零开始的信息流，没必要做到在线实时更新排序算法的参数，所以数据的管道可以分成三块：

生成训练样本，可离线；

排序模型训练，可离线；

模型服务化，实时服务；

像 Pinterest 早期的管道也差不多就是这样。

在离线训练优化模型时，关注模型的 AUC 是否有提升，线上 AB 测试时关注具体的产品目标是否有提升，比如互动率等，同时还要根据产品具体形态关注一些辅助指标。

另外，互动数据相比全部曝光数据，数量会小得多，所以在生成训练数据时需要对负样本（展示了却没有产生互动的样本）进行采样，采样比例也是一个可以优化的参数。

固定算法和特征后，在 0.1~0.9 之间遍历对比实验，选择最佳的正负比例即可。经验比例在 2:3 左右，即负样本略大于正样本，你可以用这个比例做启发式搜索。

总结

今天我逐一梳理了实现一个通用信息流的关键模块，及其已有的轮子，从而能最大限度地降低开发成本。

这些对于一个中小型的社交网络来说已经足够，当你面临更大的社交网络，会有更多复杂的情况出现，尤其是系统上的。

所以，壮士，请好自为之，时刻观察系统的监控、日志的规模。

你在了解了典型的信息流架构之后，可以说一说 Facebook 这样的社交网络 feed，和头条这样的资讯信息流之间的差别和共同点吗？欢迎给我留言。

【常见架构】总览推荐架构和搜索、广告的关系

在数字世界中，信息过载是必然的，应对信息过载重担先后交给了两种主要形式，包括以搜索为代表的各种主动寻找信息的形式，以及以推荐为代表的各种被动接受信息的形式。

三种信息获取方式

当用户想要从浩如烟海的网页中，找到对自己有用的信息，首选当然是搜索引擎，这是属于“已知的未知”需求，剩下的“未知的已知”和“未知的未知”则需要推荐系统去满足，只是推荐系统常常会出现画蛇添足去满足“已知的已知”这样的伪需求。

另外介于两者之间，还有一种商业化解决信息触达问题，就是广告系统。在线广告从条幅广告，到搜索广告再到社交精准广告，也逐渐形成了一个理念就是：把广告当成一种有用的信息去找到最需要它的人。

三者都是解决信息过滤问题的，那么它们有没有一些共同之处呢？答案是肯定的，这三者的确有共同之处，但是也有不同之处。

三者对比

搜索，推荐和广告本质上都在解决信息过载的问题，各自解决的手段、目标不相同，各自诞生在产品生命周期不同阶段，以至于系统实现也不尽相同。

我们从几个维度对比一下，看看它们不同和相同在哪。

搜索更关注内容消费者，搜索要解决的是精确快速找到想要的结果，最重要的目标是降低延迟和提高相关性。搜索引擎是一个效率工具，希望用户找到信息越快越好，而不是希望用户沉迷在搜索引擎中。

所以搜索一般不会像信息流产品那样变成时间杀手，不过现在有些搜索引擎会主推信息流，这个另当别论。

人们需要依赖搜索而不沉迷搜索，这就与搜索引擎的目标有关。在搜索解决用户的信息获取需求时，惊喜并不在考虑范围内，因此就不会随随便便地利用集体智慧去扩充一些不那么直接相关的结果。

推荐系统则不同，首先传统的推荐系统大都是起一个“锦上添花”的作用，一般很少会将其作为核心功能来承载产品。

由于推荐系统通常的目标不是帮用户找到相关内容，而是希望用户消费内容，消费越多越好，于是业界逐渐演变出一个比较畸形的认识，“好的推荐系统应该变成一个时间杀手，让用户走进去就不想出来”才是最好的。

推荐系统不同于搜索引擎，用户使用搜索时目标明确，而使用推荐系统往往总会漫无目的，这样一来，在推荐结果中就有很多发挥的余地，可以给用户制造一些惊喜，或者让推荐结果呈现多样性，这一点和搜索很不一样。

另外，搜索是针对个人用户的，一个用户发起一个请求，而推荐系统既可能针对单个用户进行推荐，也可能针对用户群进行推荐。

推荐和搜索除了这些不同，还有更多的是相同点。基于内容的推荐，本质上就是一个小的搜索引擎。

实际上很多推荐引擎底层的技术实现，尤其是数据存储上大量借鉴了搜索相关技术，比如使用用户的兴趣标签召回推荐结果，就需要先对推荐候选池按照兴趣标签建立倒排索引，从而检索出候选集。

最后，广告是一个很特殊的存在，前面也说了，搜索和推荐都是为人找信息，而广告是为信息找人。但它在形式上又像推荐，总是“不请自来”，在技术实现上又兼有推荐和搜索两者特点。

而且它背后包含的是纯粹的商业目标，说白了就是和广告的计费方式有关，如果按照展现次数计费，系统就是要多做展现。

如果按照点击付费，那么就是想办法消耗光广告主的充值，所以广告系统关注的是商业利益最大化，精准和相关都不是终极目的，只是其中的一个手段。

架构抽象

我们抽象一下三者的需求共性：本质上都是在匹配，匹配用户的兴趣和需求（看成 context），但匹配的目标，条件和策略不尽相同。示意图如下：

我们再进一步抽象下去，又可以分为三步：过滤候选、排序候选、个性化输出。

1. 过滤候选

过滤候选这一步在搜索里天经地义，从查询关键字中解析得到查询意图，以及结构化的搜索条件，再用结构化的查询条件从倒排索引中检索出排序候选。

与之相似的是广告系统，搜索广告也是查询关键字去检索候选广告，而联盟广告则是拿着用户标签去需求方获取广告候选。

在推荐系统中，我之前一再强调有挖掘、召回和排序三个阶段，其中的召回阶段就是过滤候选阶段，基于内容的就和搜索一样，用标签检索候选，协同过滤则检索出相似物品来，等等。

一种离线阶段的推荐算法对应一种召回策略，为了保证高效地召回，都要建立相应的索引，这样一来，是不是搜索、广告和推荐都离不开过滤候选这一步，而过滤候选就离不开建立索引。

事实上，如果你的产品已有搜索，要增加推荐功能，最快的实现方式的确就是在已有搜索的基础上开发。

比如，你已经有了 ElasticSearch 搭建的搜索引擎，那么在后面开发推荐系统时，完全可以把 ElasticSearch 作为管理各种推荐结果的数据库来使用。在 ElasticSearch 中按照不同方式建立索引，再于在线阶段根据条件检索出来，重新融合排序输出。

2. 排序候选

候选排序这一步，对于三者来说，主要区别在于排序的目标和约束。搜索的排序目标是高相关性，无论 BM25 为代表的传统排序模型，还是以 Learn to Rank 为代表的机器学习排序皆是如此，把用户每次在搜索上花费的时间是不是更少（而不是更多）来衡量搜索的效果。

推荐系统的排序则比较复杂，根据推荐系统不同的产品形式、产品目标，排序策略也不同。

前面讲过，通常推荐系统用 CTR 预估来融合召回策略得到的候选集，如果做得深入，还需要考虑探索利用问题。附加的约束则是千变万化。电商中，当天买过的当天就不能再推了，新闻推荐里，重复的新闻不能再推了。

某些场景需要推荐搭配，某些场景需要推荐相似，TopN 推荐还需要考虑多样性，序列推荐要考虑前序和后续等等。

广告系统的排序更多是从经济学角度去看，CPC 广告的排序方式是结合预估 CTR、出价、广告质量三者一起考虑。同时还要考虑很多别的因素，尤其是商业因素，平台方的要求，广告主的要求等等，是一个纯动态的博弈。

3. 个性化输出

个性化最被推荐系统所看重，而且在某些场合，个性化一度成为推荐系统的代名词，然而个性化只是推荐系统的衡量指标之一而已，个性化的前提也一定是信息够丰富够垂直才行。

搜索的个性化需求相对来说松弛一些，常见的是利用地域等人口统计学体现个性化，而且对于歧义较少的查询关键字，搜索如果太个性化既没意义又有风险。

4. 三者的协同

搜索、推荐、广告三者业务和技术上已经有很多重叠，也能够产生很多协同作用。

有一部分搜索需求是无法用搜索相关性满足的，比如“一个人的夜晚听什么歌”这样的 query，这就需要推荐系统去满足，交互形式可能是眼下大热的聊天机器人，也可能是流推荐等等。

如果能够识别出这样的搜索请求，其实更应该交给推荐系统来响应，这类是看似搜索请求，实际上则是漫无目的。

推荐系统总体上滞后于用户的即时需求，再强大的推荐系统，也要有搜索引擎来与之配合。

一方面，搜索因为能够满足用户的主动寻找需求，所以能够化解一些推荐不力不及时的尴尬。

另一方面，搜索可以积累用户兴趣数据；当二者结合起来考虑时，可以避免“搜什么推什么”的窘境，整个系统能够综合考虑哪些是即时快速需求，哪些是长期兴趣。

广告系统，在技术上和搜索跟推荐并无本质差异，差异在意图不同，功能不同。对用户的信息需求满足，搜索和推荐离真正得到满足之间总是有一定的鸿沟，要么是信息不足，要么是信息过载，这些鸿沟可以利用经济手段进行调配，这就是广告系统。

总结

总结一下，今天我们提到了推荐架构和搜索、广告的关系，并将三者分别作了对比。最后，我还分别抽象了三者的架构。

以上分析只是基于纯粹技术和业务角度作的简单分析，不过几乎所有人都觉得这个提法是意料之中，大家承认三者有统一的概念基础，对此亦有共识，但是我们在实现时还需要考虑一些困难，毕竟仅有少数公司有统一架构的成功案例。

实际上，这三者有统一的可能性，而且不低。如果要统一，从 0 就应该开始，这其实更适合创业公司或中小公司。

由于人的因素很重，所以从一开始就应该把三者划归一个团队来统一规划，在人员配置上做到技术上统一，业务上分开。并且还要知道必须用数据去证明：统一之后比统一之前好，而不是工程师自己“感觉不错”，这个“好”可以体现在实际上的业务指标提升，也可以体现在开发效率提升。

最后，说一个很有意思的现象，搜索和推荐的信息对象理论上可以共用的，也就是说可以允许用户设置条件检索一堆候选对象，也可以把这些候选对象主动推荐到可能感兴趣的用户面前。

但是广告的信息对象却是另一个隔离的存在，为什么不能让用户直接设置条件检索我们的广告库存呢，就像是一个通常的搜索引擎一样，也许是可能的。这个问题你怎么看？你可以给我留言，我们一起讨论。

感谢你的收听，我们下期再见。

【开源工具】和推荐系统有关的开源工具及框架介绍

我们懂得了原理，知道了实际推荐系统需要考虑哪些元素之后。正当你摩拳擦掌之际，如果发现要先从挖地基开始，你整个人可能是崩溃的。

轮子不要重复造

但是事实上你没必要这样做也不应该这样做。大厂研发力量雄厚，业务场景复杂，数据量大，自己从挖地基开始研发自己的推荐系统则是非常常见的，然而中小厂职工们则要避免重复造轮子。这是因为下面的原因。

中小企业，或者刚刚起步的推荐系统，要达成的效果往往是基准线，通用的和开源的已经能够满足；

开源的轮子有社区贡献，经过若干年的检验后，大概率上已经好于你自己从零开始写一个同样功能的轮子；

对于没有那么多研发力量的厂来说，时间还是第一位的，先做出来，这是第一要义。

既然要避免重复造轮子，就要知道有哪些轮子。

有别于介绍一个笼统而大全的“推荐系统”轮子，我更倾向于把粒度和焦点再缩小一下，介于最底层的编程语言 API 和大而全的”推荐系统”之间，本文按照本专栏的目录给你梳理一遍各个模块可以用到的开源工具。

这里顺带提一下，选择开源项目时要优先选择自己熟悉的编程语言、还要选有大公司背书的，毕竟基础技术过硬且容易形成社区、除此之外要考虑在实际项目中成功实施过的公司、最后还要有活跃的社区氛围。

内容分析

基于内容的推荐，主要工作集中在处理文本，或者把数据视为文本去处理。文本分析相关的工作就是将非结构化的文本转换为结构化。主要的工作就是三类。

主题模型；

词嵌入；

文本分类。

可以做这三类工作的开源工具有下面的几种。

由于通常我们遇到的数据量还没有那么大，并且分布式维护本身需要专业的人和精力，所以请慎重选择分布式的，将单机发挥到极致后，遇到瓶颈再考虑分布式。

这其中 FastText 的词嵌入和 Word2vec 的词嵌入是一样的，但 FastText 还提供分类功能，这个分类非常有优势，效果几乎等同于 CNN，但效率却和线性模型一样，在实际项目中久经考验。LightLDA 和 DMWE 都是微软开源的机器学习工具包。

协同过滤和矩阵分解

基于用户、基于物品的协同过滤，矩阵分解，都依赖对用户物品关系矩阵的利用，这里面常常要涉及的工作有下面几种。

KNN 相似度计算；

SVD 矩阵分解；

SVD++ 矩阵分解；

ALS 矩阵分解；

BPR 矩阵分解；

低维稠密向量近邻搜索。

可以做这些工作的开源工具有下面几种。

这里面的工作通常是这样：基础协同过滤算法，通过计算矩阵的行相似和列相似得到推荐结果。

矩阵分解，得到用户和物品的隐因子向量，是低维稠密向量，进一步以用户的低维稠密向量在物品的向量中搜索得到近邻结果，作为推荐结果，因此需要专门针对低维稠密向量的近邻搜索。

同样，除非数据量达到一定程度，比如过亿用户以上，否则你要慎重选择分布式版本，非常不划算。

模型融合

模型融合这部分，有线性模型、梯度提升树模型。

线性模型复杂在模型训练部分，这部分可以离线批量进行，而线上预测部分则比较简单，可以用开源的接口，也可以自己实现。

其他工具

Bandit 算法比较简单，自己实现不难，这里不再单独列举。至于深度学习部分，则主要基于 TensorFlow 完成。

存储、接口相关开源项目和其他互联网服务开发一样，也在对应章节文章列出，这里不再单独列出了。

完整推荐系统

这里也梳理一下有哪些完整的推荐系统开源项目，可以作为学习和借鉴。 所谓完整的推荐系统是指：包含推荐算法实现、存储、接口。

总结

你可能注意到了，这里的推荐系统算法部分以 Python 和 C++ 为主，甚至一些 Python 项目，底层也都是用 C++ 开发而成。

因此在算法领域，以 Python 和 C++ 作为开发语言会有比较宽泛的选择范围。

至于完整的推荐系统开源项目，由于其封装过于严密，比自己将大模块组合在一起要黑盒很多，因此在优化效果时，不是很理想，需要一定的额外学习成本，学习这个系统本身的开发细节，这个学习成本是额外的，不是很值得投入。

因此，我倾向于选择各个模块的开源项目，再将其组合集成为自己的推荐系统。这样做的好处是有下面几种。

单个模块开源项目容易入手，学习成本低，性能好；

自己组合后更容易诊断问题，不需要的不用开发；

单个模块的性能和效果更有保证。

当然，还是那句话，实际问题实际分析，也许你在你的情境下有其他考虑和选择。如果还有哪些开源项目，你觉得值得推荐，也欢迎留言分享。

【效果保证】推荐系统的测试方法及常用指标介绍

当我们刚开始学习推荐系统的时候，我就希望你想清楚为什么要做推荐系统。在逐渐深入的过程中，我开始唠叨推荐系统的林林总总。

到了今天，假如你已经有了自己的推荐系统，这个系统已经上线，代替了以前绝大多数人工的工作，夜以继日地工作，为电商网站创造销售额，为信息流创造阅读时间和互动，为社交网站创造社交关系。

为什么要关注指标

然而，这样你就可以安心睡大觉了吗？显然你想错了，它成功上线时，也是你失业的时候，我们暂且不说是否真的有这一天。就算是一切正常运作，你还是需要每天把这个系统捧在手心，教它在刁钻的用户面前如何长大，既要小心它学坏，也要小心它偷懒不学无术。

总之，养过孩子的人会懂的。面对推荐系统这样一个有诸多复杂因素联动起作用的系统，要时时刻刻知道它好不好，健不健康，你同样需要掌握一些测试方法和检测指标。

推荐系统的测试方法

在最开始几篇中，我说过你需要有不确定性思维，但是这绝不是帮你在老板那里开脱的说辞。

推荐系统也需要测试，只是它不同于传统的功能测试。传统软件的功能测试，功能的响应是有预期的，点击一个加关注按钮，应该有什么响应，是被产品文档明确规定的；也因此在开发功能的时候，可以同步写出测试用例来。

这非常明白，在功能开发时，你做了任何改动，只要跑一下测试用例，逻辑对不对就一目了然了。反观推荐系统就没那么容易了，你什么都没动，可能两次推荐的结果都有可能不一样，而且很可能这个不一样也是你自己或者你老板要求的。

那么推荐系统要怎么测试呢？与其说推荐系统没有确定性的预期响应，不如说推荐系统的响应维度更高。

因为确定性的功能响应像是一个点，而推荐系统的响应则是高维空间中的一个区域，而不是一个点。那么是不是推荐系统不需要单元测试了呢？显然也不是。

归纳起来，推荐系统的测试方法有四种： 业务规则扫描、离线模拟测试、在线对比测试、用户访谈。

1. 业务规则扫描

首先，业务规则扫描本质上就是传统软件的功能测试。确定的业务规则会对应有确定的规则，这些规则就可以翻译成单元测试，像是运行单元测试那样，对推荐系统逐一扫描业务规则。

通常这些业务规则对测试的要求也有“软的”和“硬的”两种。前者会对业务规则违反情况做一个基线规定，比如触发几率小于万分之一，在扫描测试时统计触发次数，只要统计触发几率不超过基线，就算是合格。

而硬的规则，就是一票否决，例如一些业务黑名单，简直就是高压线，测试时碰不得，碰了就是 Bug，就要想办法修正。

除了业务规则，还有一些容易被人忽视的地方，比如绝大多数推荐模型都涉及了数学计算，而数学计算中也有一些潜在的规则不能违反。

比如除数不能为 0，比如计算机的浮点数精度有限，经过一些指数运算后可能就出现预期之外的结果，还可能一些连续相乘的计算要防止出现 0 的乘数，类似这些在计算中的潜在业务规则，也需要扫描测试。

2. 离线模拟测试

其次，就是在离线模拟测试。这是一种军事演习式的测试。模拟测试当然无法代替真实数据，但是也能暴露一些问题。通常做法是先收集业务数据，也就是根据业务场景特点，构造用户访问推荐接口的参数。

这些参数要尽量还原当时场景，然后拿这些参数数据去实时访问推荐推荐，产生推荐结果日志，收集这些结果日志并计算评测指标，就是离线模拟测试。

显然，离线模拟测试是失真的测试，并且评测指标也有限，因为并不能得到用户真实及时的反馈。但是仍然有参考意义。

这些模拟得到的日志可以统称为曝光日志，它可以评测一些非效果类指标，例如推荐覆盖率，推荐失效率，推荐多样性等。关于这些指标具体含义，稍后再讲。那是不是离线模拟测试就对效果一无所知、无法模拟呢？

也并不是，有一种办法是，利用历史真实日志构造用户访问参数，得到带评测接口的结果日志后，结合对应的真实反馈，可以定性评测效果对比。

比如，可以评测推荐结果的 TopK 的准确率，或者排序效果 AUC。这些模型效果类指标，虽然不能代表最终关注的商业指标，但是两者之间一般存在一定的相关性。

通常来说 TopK 准确率高，或者 AUC 高于 0.5 越多，对应的商业指标就会越好，这是一个基本假设。通过离线模拟评测每一天的模型效果指标，同时计算当天真实的商业指标，可以绘制出两者之间的散点图，从而回归出一个简单的模型，用离线模型效果预估上线后真实商业指标。

3. 在线对比测试

第三种测试方法就是真正的实战了，那就是 ABTest，即在线对比测试，分流量做真实的评测。这需要一个支持流量正交切分的 ABTest 框架，在前面的文中已经讲到过。ABTest 在样本充分的前提下，基本上可以定性新的推荐系统是否比老的推荐系统更加优秀。

4. 用户访谈

最后一种测试方法就是用户访谈，或者说用户调查。前面三种测试方法，背后的思想是数据驱动。

然而正如我在本文开篇时所说，数据测量的是系统外在表现，并不反映系统原理，而且数据指标是人设计的，是存在主观性和片面性的，人的认知广度和深度各有不同。

因此，除了要紧紧团结在“数据驱动”这个核心思想周围，还需要深入用户，对用户做最直接的交流，对用户访谈，更重要的意义不是评测推荐系统，而是评测推荐系统的指标，设计是否合理，是否其高低反映了你预先的设定。

除此之外，通过前面三种测试方法如果得知系统表现不好，那么结合直接真实的用户调查和访谈，可以为系统优化找到真实原因。这种方法类比一下就是：维修下水道时，你需要钻进下水道。

常用指标

推荐系统有很多指标。你之前如果阅读过一些介绍推荐系统指标的文献或书籍，想必会对繁多的指标望而却步，总之就是各种率。实际上所有指标就是在回答两个问题：系统有多好，还能好多久？

这两个问题恰恰就是推荐系统里面一个老大难问题的反映：探索利用问题。

系统有多好？这就是想问问：对数据利用得彻底吗？还能好多久？这个问题就是想问问：能探索出用户新的兴趣吗？这样就能继续开采利用了。也好比在职场中看一个人，除了看他现在的经验和解决问题能力有多强，还要看他学习能力有多强，毕竟世界是变化的，朝阳也会变成夕阳。

下面我分别说说这两类指标有哪些。

1. 系统有多好？

检测系统到底有多好，其实，也有两类，一类是深度类，一类是广度类。

把数据看做是一座矿山，推荐系统是一个开采这座矿山的器械，“系统有多好”这个问题就是在关心开采得好不好，所以其实就看现有矿山上开采得深不深，开采得到不到位。广度类指标就是指在矿山上打满了钻井，而不仅仅盯着一处打钻井。

深度类指标，就是看推荐系统在它的本职工作上做得如何。还记得推荐系统的本职工作是什么吗？就是预测用户和物品之间的连接，预测的方法又有评分预测和行为预测。

因此深度类指标就指在检测系统在这两个工作上是否做得到位，有针对离线模型的指标，也有在线的指标，下面我分别说一说。

1. 评分准确度。通常就是均方根误差 RMSE，或者其他误差类指标，反映预测评分效果的好坏。在讲协同过滤时已经详细说过这个指标。这里不再赘述。

2. 排序。检测推荐系统排序能力非常重要，因为把用户偏爱的物品放在前面是推荐系统的天职。

由于推荐系统输出结果是非常个人化的，除了用户本人，其他人都很难替他回答哪个好哪个不好，所以通常评价推荐系统排序效果很少采用搜索引擎排序指标，例如 MAP，MRR，NDCG。

搜索引擎评价搜索结果和查询相关性，具有很强的客观属性，可以他人代替评价。推荐系统评价排序通常采用 AUC。也在前面介绍 BPR 模型时，专门讲到过。

3. 分类准确率。这个指标也是针对行为预测的，而行为预测就是分类问题，所以评价准确度就很自然。

在推荐系统中，评价准确度略微特殊，一般评价 TopK 准确率，与之对应还有 TopK 召回率，这里的 K 和实际推荐系统场景有关，就是实际每次推荐系统需要输出几个结果。

TopK 准确度计算方式如下：

如果日志中用户有 A、B 两个物品有正反馈行为，推荐系统推出一个物品列表，长度为 K，这个列表中就有可能包含 A、B 两个物品中的一个或多个，下面这个表格就说明了 TopK 准确率和 TopK 召回率的含义。

这三个指标，比较直观地反映了推荐系统在“预测”这件事上对数据开采的深度，实际上由于模型不同，还可以有不同的指标，也可以自己设计指标，这里不再赘述。但这三个指标也属于比较初期的指标，距离最终商业指标还有一定的距离。

通常检测推荐系统的商业指标有：点击率，转化率。其实把用户从打开你的应用或者网站开始，到最终完成一个消费，中间要经历数个步骤，也是大家常说的漏斗转化过程。

推荐系统如果在其中某个环节起作用，那么就要衡量那个环节的转化率，这个相比前面三个指标，更加接近真实效果。

除了比例类的商业指标，还要关注绝对量的商业指标，常见的有：社交关系数量，用户停留时长，GMV（成交金额），关注绝对数量，除了因为它才是真正商业目标，还有一个原因，是要看推荐系统是否和别的系统之间存在零和博弈情况。

假如推荐系统导流效果提升，搜索引擎导流下降，从整个平台来看，因为整个平台的商业目标并没有那么成绩喜人，也需要警惕。

讲完深度类指标，下面进入广度类指标。

4. 覆盖率。这项指标就是看推荐系统在多少用户身上开采成功了，覆盖率又细分为 UV 覆盖率和 PV 覆盖率。UV 覆盖率计算方法是。

COVuv=Nl>cNuvCOVuv=Nl>cNuv

解释一下，首先要定义有效推荐，就是推荐结果列表长度保证在 C 个之上，独立访问的用户去重就是 UV，有效推荐覆盖的独立去重用户数除以独立用户数就是 UV 覆盖率。PV 覆盖率计算方法类似，唯一区别就是计算时分子分母不去重。

COVpv=N∗l>cN∗pvCOVpv=Nl>c∗Npv∗

5. 失效率。失效率指标衡量推荐不出结果的情况。也分为 UV 失效率和 PV 失效率。UV 失效率计算方法是。

LOSTuv=Nl=0NuvLOSTuv=Nl=0Nuv

分子是推荐结果列表长度为 0 覆盖的独立用户数，分母依然是去重后的独立访问用户数。PV 失效率也一样，区别是不去重。

LOSTpv=N∗l=0N∗pvLOSTpv=Nl=0∗Npv∗

6. 新颖性。对于用户来说，“总是看到你这张老脸”会让他们审美疲劳，所以对用户来说，推荐的物品要有一定的新颖性。直观理解就是用户没见过。

所以新颖性需要讲粒度，物品粒度、标签粒度、主题粒度、分类粒度等等。每个粒度上评价用户没见过的物品比例。对于物品级别的新颖性，更多是靠直接过滤保证，这在前面章节已经专门讲到了对应的过滤算法。

7. 更新率。检测推荐结果更新程度。如果推荐列表每天几乎一样，显然不可取，尤其是新闻资讯类，要求每次刷新都不一样，对更新率要求更高。更新率可以有很多衡量方式，有一种是衡量每个推荐周期和上个周期相比，推荐列表中不同物品的比例。这个周期，可以是每次刷新，也可以是每天。

UPDATE=ΔNdiffNlastUPDATE=ΔNdiffNlast

2. 还能好多久？

除了关注系统表现有多好外，你还需要忧虑另一件事，你的系统还能好多久？也就是系统是否健康。

在推荐系统中，需要数据不断更新，这样系统才是一个活系统，用户兴趣客观上会变迁，数据源客观上也是会用光的一天，所以推荐系统如果不能应对这两个变化，就好不了太久。

衡量推荐系统是否健康的指标常用的有三个。

1. 个性化。虽然说到推荐系统时，言必称个性化，但实际上能做到真正个性化很难，那要求用户每个人都独立思考、爱好明确、不受群体影响。但是个性化程度的确能够反映推荐系统的健康程度，按照我们在专栏第一篇“是否需要推荐系统”中提出的那个公式来看：

ΔconnectionΔuser×ΔitemΔconnectionΔuser×Δitem

如果没有个性化，那么分子上增加的连接数，其实是不受分母上增加的物品数影响的，因为所有人都只消费那少数几个物品，那么你其实不需要推荐系统。

个性化如何检测呢？有一个直观的方法，取一天的日志，计算用户推荐列表的平均相似度，如果用户量较大，对用户抽样。

2. 基尼系数。基尼系数衡量推荐系统的马太效应，反向衡量推荐的个性化程度。把物品按照累计推荐次数排序，排在位置 i 的物品，其推荐次数占总次数为 pipi。那么基尼系数为：

Gini=1n∑i=1npi∗(2i−n−i)Gini=1n∑i=1npi∗(2i−n−i)

看这个公式可以知道，如果推荐次数越不平均，那么基尼系数就越趋近于 1。

3. 多样性。多样性不但要在推荐服务吐出结果时需要做一定的保证，也要通过日志做监测。

多样性可能会损失一些效果指标，但是从长远上来看，对推荐系统所在平台是有利的。多样的推荐结果也会让产品显得生机勃勃，提升品牌形象。多样性衡量方式通常要依赖维度体系选择，例如常见的是在类别维度上衡量推荐结果的多样性。方法是下面这样的。

Div=∑ni=1−pi∗log(pi)n∗log(n)Div=∑i=1n−pi∗log(pi)n∗log(n)

多样性衡量实际上在衡量各个类别在推荐时的熵，一共有 n 个类别，分母是各个类别最均匀，都得到一样的推荐次数情况下对应的熵。

分子则是实际各个类别得到的推荐次数，pipi 是类别 i 被推荐次数占总推荐次数的比例，计算它的熵。两者求比值是为了在类别数增加和减少时，可以互相比较。

这种计算多样性是一个整体的评价，还可以具体评价每次推荐的多样性，每个用户的多样性，也就是 PV 多样性和 UV 多样性。

总结

推荐系统作为一种 AI 系统，其测试方法不完全相同于传统软件功能测试。对于推荐系统，也有一定的单元测试，扫描业务规则，对系统做一票否决制，因为这些业务规则定义明确。

除此之外，还要先经过离线模拟，再线上小范围实测，这部分测试就是在践行数据驱动。这部分指标主要在回答系统的两个问题。

系统表现有多好？

系统还能好多久？

只要系统现在表现好，并且系统生命力强，那么你的推荐系统就是好的推荐系统。这些指标就是在忠实反映这两个侧面的。

但是，光靠数据驱动，又容易走入歧途，还需要常常审视这些指标到底是否真实反映系统状态，所以还需要对用户做调查访谈，深入群众，听取最真实的感受，回来重新看看自己的指标是否合理，是否需要重新设计指标。

这里限于篇幅，没有完全列出推荐系统所用的指标，欢迎你留言，把你用过的指标分享出来，我们一起讨论。

感谢你的收听，我们下次再见。

【效果保证】道高一尺魔高一丈：推荐系统的攻防

毫无疑问，推荐系统是一种流量操控手段，所以其运转需要满足平台方的利益。

为了这个目的，推荐系统通过科学的手段建立起一套运转规则和逻辑，希望平台内的各方能够皆大欢喜，物品生产方能源源不断地生产物品，消费方能孜孜不倦地消费。

当然这里的物品和消费都是泛指，物品可以是内容、商品、娱乐方式、甚至是人等等，消费也不定是直接掏钱，花时间也是一种消费。

既然推荐系统是某一方流量诸侯的运转规则，那么就不能不考虑到在其诸侯封地之内会有刁民闹事、钻营规则的漏洞，从而达到自己的目的。

攻和防

用行话说，就是推荐系统也会受到攻击，推荐系统也是一种软件，只要是软件，就一定有安全问题，推荐系统也不能免俗。

如果推荐系统非常脆弱，容易受到攻击，那么推荐系统就不是为平台利益而运转，而是为攻击者利益而运转，推荐系统不过是个傀儡，前面讲到的那么多酷炫的算法也就成了摆设，想必正在听课的你会瑟瑟发抖吧？

让前面讲到的所有算法、架构起到它该起的作用；让那些指标数据反映真实的效果，这两件事都很重要。推荐系统如果被攻击也就需要被防护，因此，我今天就和你讨论一下推荐系统的攻防这个略带黑色的话题。

攻击

知己知彼，百战不殆。要更好地守护你的推荐系统，就需要先了解别人会怎么攻击你的推荐系统。在推荐系统攻防研究领域，被研究得最为彻底的就是针对协同过滤的攻防。

为什么呢？一方面是协同过滤本身就应用广泛，另一方面是针对协同过滤的攻击容易生效。

我们先概略认识一下推荐系统的攻击是怎么回事，然后再认识一下攻击怎么做。

有人对身为流量控制器的推荐系统攻击，并不是他吃饱了没事做，来帮你测试系统，根据“无利不起早”这条社会公理，攻击方一定是想扶持或者打压某些物品，从而获得他想要的个人利益。

攻击方要扶持一个物品，就想要推荐算法在计算他的评分时给出高分，想要打压一个物品，就要反之行事。

不论目的是扶持还是打压，都需要先达到操纵选民的目的，你知道的，协同过滤，无论是基于物品还是基于用户，都是群体智慧，也就是说需要有投票过程。

所以攻击协同过滤，核心问题在于如何操纵选民。选民有两种，一种是用户，一种是物品，前者是基于用户的协同过滤所需要的，后者是基于物品的协同过滤所需要的。

现在，从一个简单例子开始，你和我一起来思考，如何攻击基于用户的协同过滤算法。

我们先回顾一下它的原理，首先计算出用户之间的相似度，在给一个用户计算推荐结果时，让相似的用户集体决策，其背后的思想也很直接：人以群分，与你口味相似的人给你推荐的结果你会喜欢。

那么攻击任务就是，要让自己扶持的物品在推荐算法决定是否要推荐给一个用户时，得到高分。

方法就是操纵选民，这里的选民就是和被欺骗用户相似的用户，被欺骗用户肯定是吃瓜群众，也是攻击方的利益攫取，所以不会成为被操纵的选民。

通常的手段就是，批量制造假用户资料，并装作是与被欺骗用户兴趣相投的人。这叫做托攻击或者 Shilling Attacks，托也就是水军，名字很形象有没有？

具体怎么制造这批选民呢？首先，攻击者会注册一批用户，这部分用户就是攻击者可以操纵的选民，然后让这批用户去做出和被欺骗用户一样的历史评分行为。

被欺骗的用户打高分的物品，这批水军也打高分，这样一来就可以在计算用户相似度时，这一批新注册的用户都和那个用户有较高的相似度，从而就变成了参与推荐算法计算时的选民，也就可以给扶持的物品打高分或者给打压的物品打低分。

只不过，针对一个吃瓜群众做这些事情显然是一个不划算的事情，所以攻击者会先找到目标用户群体，针对目标用户群体来做这些事，这样一来就可以把扶持的物品推荐给这个群体，让打压的物品从这个群体面前消失。

攻击者在伪造用户兴趣时，除了要做出和被欺骗用户相似的历史行为之外，还要做出掩人耳目的行为，以防止被平台发现，所以还会给一些无关的物品打分。至此，一个简单的攻击过程完成了。

总结一下，攻击手段包含这些元素。

目标物品，就是攻击方要扶持或者打压的那个物品。

助攻物品，就是用来构造假的相似用户所需要的物品。

陪跑物品，就是用来掩饰造假的物品。

三类物品构成一个靶子，靶心是攻击者要拿下的，层层包围，示意如下。

其中，根据对最外环物品的评分构造方法不同，可以把攻击分为两种。

随机攻击。随机攻击就是在上面示意图中，构造最外环“陪跑物品”评分时，采用随机打分方式生成。随机打分就是用全局平均分构造一个正态分布，给无关物品打分时，用这个正态分布产生一个随机分值。

平均分攻击。平均分攻击也是用在最外环物品中，给他们打每个物品的平均分。需要先统计出被打分物品的平均分，然后攻击方给这个物品也打上平均分。

前面举例的这种攻击手段，需要先找到一批被欺骗用户，然后逐一为它们构造相似用户，最后才能如愿地实现扶持或打压目标物品。于是就有更为狡猾的攻击办法，这里举两种，一种是热门攻击，还有一种是分段攻击。

热门攻击就是攻击者会想办法让目标物品和热门物品扯上关系。这样做有事半功倍的效果，热门物品有个特点是：评分用户多。如果和它扯上关系，那就找到了一个数量较大的群体，攻击的影响也会巨大。

和热门物品扯上关系最常用的就是，使用假用户同时给热门物品和目标物品评上高分，这是针对扶持目标物品的做法，如果要打压，则给热门评高分，给目标物品最低分，陪跑物品就采用随机评分的方式。

热门攻击，若干年前在某电商网站真实发生过，攻击者想让自己的图书得到更多推荐，于是大量同时购买畅销书以及那本想得到推荐的图书，最后在畅销书页面的相关推荐中就推出了那本书，攻击者目的达成。

热门攻击有两个“优势”。

如果是扶持目标物品，则经过热门攻击后，基于物品的协同过滤算法会把目标物品计算为热门物品的相似物品，上述实际案例就是如此；

基于用户的协同过滤算法，也会把消费过多个热门物品的用户计算为假用户的相似用户，从而为这些用户推荐出目标物品。

热门攻击有时候并不是攻击者有意发起的，而是一种群体现象，例如粉丝出征，消费者集体维权，都可能产生出热门攻击的效果。

分段攻击就是想办法把目标物品引入到某个群体中，做法就是攻击者先圈定好用户群体，再列出这个群体肯定喜欢的物品集合，然后同时用假用户给目标物品和这批物品集合评分，做法类似热门攻击。

最后的攻击效果就是：如果扶持目标物品，那么这个被圈定的用户群体会看见，如果打压，那么目标物品就会在这群人面前消失。

防护

好了，讲完攻击手段之后，也该讲讲如何防护了，毕竟这才是你的本职。

前面已经说过，上述这些攻击手段，核心都是操纵选民，手段是构造假用户兴趣，因此你自然会想到，防护的手段核心就是识别出被操纵的选民，这是当然的，但是并不仅仅如此，防护手段按照层级，可以分为下面几种。

平台级。这一层属于在推荐系统之外的防护手段，一面是提高批量注册用户的成本，从攻击者的第一步遏制，比如弹验证码，另一方面是产品教育用户积极参与，并提供真实的反馈，让推荐系统所用的数据真实性比例越高，越不容易被攻击，这是最根本的。

数据级。数据级别防护重点是从数据中识别出哪些数据是假的，哪些用户是被操纵的选民，一旦识别出来就将这些数据删除。做法通常是采用机器学习思路，标注一批假用户或假反馈数据，训练分类器，在线上识别出反馈，将其延后或者排除在推荐计算之外，通常要和反垃圾系统紧密结合。或者对用户数据聚类，假用户产生的数据一定有着和正常用户不一样的分布，因为它目标明确，所以无监督的办法可以找出假用户群体来，一旦确认可以删除整个群体，可以采用的有主成分分析等做法。

算法级。算法级别就是在推荐算法设计时，要根据情况做一些改进和选择。一般来说基于用户的协同过滤更容易受到攻击，因此需要对基于用户的协同过滤做改进。

改进方向包括下面几种。

引入用户质量，限制对于低质量的用户参与计算，或者限制新用户参与计算；

限制每个用户的投票权重，即在计算用户相似度时引入较重的平滑因子，使得用户之间的相似度不容易出现过高的值，也就是变相使得投票时参与用户更多一些，提高攻击者的成本。

除此之外，采用多种推荐算法最后再走模型融合之路也是一种提高推荐系统健壮性的有效做法。

将上述三个层级的防护表示如下图更清楚些，核心要保护推荐结果符合平台方的利益。

总结

让效果指标真的在反映效果，也是要追求的效果。这句话虽然有点绕口，但是作为推荐系统从业者，应该牢记心间。这句话背后反映的就是推荐系统的健壮性。

外部对推荐系统的攻击常常发生，而且又常常发生在协同过滤算法上。协同过滤相比训练出模型的推荐算法来说，的确更加脆弱些。

基于用户的协同过滤又比基于物品的协同过滤要更常被攻击，究其原因，因为基于用户的协同过滤被攻击不容易在直观上发现，毕竟人在现实中也容易盲从，更何况在数字世界中被人伪造了几个知音一样的用户帮他们推荐呢？

基于物品的协同过滤如果被攻击，直观上可能就不符合人们的理解，所以容易被发现，例如攻击者通过伪造数据，导致《小时代》和《肖申克的救赎》非常相似，这在计算出结果还没被上线使用时就被发现了。

任何协同的攻防都需要实际问题实际分析，因为攻防是一个永无止境的过程，如果已经有非常明确的规律，那么想必攻击方也就不会采用了。如果你也遇到过推荐系统被攻击的例子，欢迎留言给我，我们一起研究一下。

感谢你的收听，我们下期再见。

【产品篇】推荐系统在互联网产品商业链条中的地位

今天我要和你探讨的是推荐系统在商业链条中的地位。

一个完整的推荐系统一定是置身商业世界中的，而不是只在学术界撒野，毕竟学术界大部分时间里只关注算法和模型的效果，并且是建立在强假设和非常有限的数据基础上的，显然这不并算是推荐系统的全貌。

在商业世界里，就应该带一点“功利”的眼光看待推荐系统，但功利地看待推荐系统之前，要认识到推荐系统在商业链条中到底是个什么样的角色和作用？

推荐系统的作用

商业社会中亘古不变的关系是供求关系，供求关系的背后是交换。无论是实体经济还是虚拟经济，都是基于这个原理。供求关系动态变化，当供给小于需求时，就产生了稀缺，有了稀缺，就有了商业。

我不是商人，所以我就只懂得基本原理，下面我就根据这个基本原理来说说推荐系统的角色和地位。

推荐系统处理的是信息，它的主要作用是在信息生产方和信息消费方搭建起桥梁。所以推荐系统是信息经济中的一个装置。那么在信息经济中供求到底又是什么呢？

信息经济中，看上去供求方是信息生产者，需求方是注意力提供者。这里似乎猝不及防地就引出了“注意力”这个词。

所以，无论推荐系统服务的是什么样的产品，这些产品属于资讯，社交，电商，游戏等不同的形式，它们最终得到真金白银的手段不一样，也就是所谓的商业模式各有不同，但是它们都有一个关键步骤就是：获得用户的注意力。

用户产生行为就是付出注意力的表现，也因此信息流产品都在看谁家的阅读时间长，那都是白花花的注意力啊。信息经济其实就是注意力经济，而推荐系统就是留住注意力的重要手段。

那么说注意力这种东西到底是什么？

它不是真的物理“力”，不属于四种宇宙基本力，而是一种决策可能性。比如用户为一个广告倾注了注意力，那么就有可能去点击广告，如果这个注意力非常强烈，还会继续消费广告中的商品，如果注意力足够持久，那么就会参与到整个链条上的各个经济活动。

注意力有个特点：总量有限。随着信息越来越丰富，注意力越来越稀缺。

首先，在门户时代，信息稀缺，注意力丰富，用户主动找信息。

其次，在搜索时代，信息已经丰富，但搜索的工具属性和使用场景单一，导致它并不会侵蚀用户的注意力，所以依然是用户主动找信息。

最后，在移动互联网普及之后，信息已经泛滥到很大程度，智能手机变成身体的一个器官，丰富的注意力被信息源以推荐的方式逐渐侵蚀，注意力从丰富变成稀缺。

但是，注意力本身有价值高低之分。资讯阅读类注意力量大，但是便宜，电商、游戏类注意力贵重，但数量上不如资讯阅读类。这个注意力价值，一般在行业里被粗略称呼为用户价值，实际上这应该是注意力价值。

综合看，三个时代的信息和注意力关系如下图所示。

在推荐系统的帮助下，注意力变成了稀缺方，信息源在打着灯笼到处寻找注意力，其实商品不再是信息，商品就是注意力，信息源变成了这些注意力的消费方。

有限的注意力在推荐系统的帮助下，聚到了平台上，平台方需要像电力一样把这些注意力储存起来，储存起来的注意力就是平台方最有价值的资产。

储存这些注意力的并不是电池板，而是产品，而推荐系统是一种注意力储存设备型号，这就是推荐系统在商业链条中的角色和地位。

如何定量地定义注意力？直观地看，注意力的存在会导致平台上内容被消耗。因此，我个人把注意力定义为：内容被消耗的加速度与平台内容复杂度的乘积。

写成公式就是：

attention=A×Cattention=A×C

是的， 你没看错，我就是类比了牛顿第二定律定义了注意力。我来解释一下这个公式。

C 是内容复杂程度，因为不好量化，可以理解为内容被消耗光所需的时间，比如论文网站和鸡汤文网站，要读完两者，难度显然不同，表现为消耗时间不同，再比如，卖奢侈品电商网站和卖地摊货的电商网站，要买光所有商品，花费的钱需要时间去累积，也表现在消耗时间不同。

A 是内容消耗的加速度，为什么是加速度，而不是速度呢？因为这里衡量的注意力并不只是内容消费者的注意力，还有内容创作者的注意力，是两者合并后的结果。如果用户的注意力和内容创作者倾注的注意力相同，就表现为每天消耗的内容数量一样，加速度为 0，整个平台上没有多余的注意力剩下，没有多余的注意力剩下，就无法销售注意力。

内容消耗的加速度，还与参加消耗的用户数量有关，用户数量越多，每天消耗越多，用户数量指数增加，则消耗的加速度就不为 0，平台方就有了多余的注意力。

上述是一个注意力定义框架，限于篇幅，这里不展开详细定义。但是可以针对这个注意力定义框架制定一些提升平台剩余注意力的策略，及负面影响。

内容创作适当少倾注注意力，这样的结果是，用户消耗会快，但难度也会减少，总注意力会受到制衡；

提升内容难度，这意味着创作者也要倾注更多注意力，有可能用户方消耗不了，加速度变成负数；

提高单用户消耗加速度，这就是推荐系统的作用，给用户推荐他更愿意消耗的内容；

提高用户数，或者说提高活跃用户数。

定义了注意力之后，就能看清楚推荐系统在提升平台注意力的作用，也就能看清楚推荐系统的价值。

推荐系统的成本

既然是商业，那么就会考虑成本，虽然只考虑成本是非常浅薄的商业思维；但是这不重要，如果你是公司或者团队负责人，想清楚你的成本，你就会掂量一下是不是要去把“个性化”或者“算法”的标签贴给自己的产品。

如果你是从业者，清楚成本你就会有危机感，你不会觉得老板不懂，所以就不把成本放在眼里，而是会时刻提醒自己，一切成本，他都是知道的，包括你本身。

这并非危言耸听，时代赐予的红利会消失，创造的价值覆盖了成本才能挺过来。

大致来说，打造一个推荐系统的成本分布在这几个地方：

团队成本；

硬件成本；

机会成本。

1. 团队成本

团队成本包含团队组建的成本和团队维护的成本。一个推荐系统的团队至少要包含以下几类全职的人。

算法工程师，承担了数据科学家和程序员的双重工作，以数据科学为主，并兼具工程能力，在国外一般叫做机器学习工程师。团队里的这类人，由于市场长期供不应求，所以招募成本很高。比如要在各大招聘网站去投放广告，不断和人 social 混脸熟，高昂的猎头费用，转化率极低。招募这部分人，如果只是靠在朋友圈发个招聘文案，可以说是 0 可能会招到人。招募成本高，人员本身的成本也高，由于时代红利存在，整体薪资水平水涨船高，在无法真实分辨出每个人实际价值前，也只能付出这部分人才试错成本。

软件工程师，如果把推荐系统分为引擎和算法的话，那么软件工程师承担的责任比算法工程师更大，因为算法可以用一些开箱即用的开源工具暂时顶上去；而没有引擎，算法则就没有了用武之地。软件工程师由于市场存量高于算法工程师，所以招募时稍微好一点，但是请注意是好一点，实际上，要找到好的软件工程师，该付出的成本一个都不少。

团队成本占据了推荐系统成本的大头，老板们也容易在这一部分产生焦虑，不要这样的团队，生怕自己的产品被市场抛弃，维护这样一个团队呢，那真是“玩儿得特大”。

其实不只是推荐系统，对于技术团队，有一个错误的认识被无数前辈警醒过，那就是：短期高估，长期低估。

团队维护的成本除了实打实的薪资支出，还有文化建设成本。工程师们都号称需要宽容自由的环境，形式上看就像是花钱请了一群野马，这也是成本，或者说风险。

因为真正优秀的工程师才会在宽松环境下创造出远大于成本的价值，而普通工程师有可能在宽松自由的环境下逐渐废掉。

给团队维护一个宽松自由的环境，就需要有一些非常明确地验收工程师成果的机制，这种技术文化建设也不是一朝一夕的事情，需要付出很大的精力。

2. 硬件成本

推荐系统是数据贪婪型。为了获得更多的数据，需要非常高配置的硬件支持，这是由于：

要存储更多的数据；

要更安全保存数据；

要更快响应用户，才能留住用户；

要更好的开发环境，才能提高工程师开发效率，要知道工程师的时间成本最高。

等等这些理由都告诉我们：推荐系统是数据贪婪型，而推荐系统工程师是硬件贪婪型。当然，幸运的是有摩尔定律，硬件成本在逐年下降，配置却在逐年提高，所以硬件成本比起团队成本，只是毛毛雨啦。

有了团队，不要在硬件上节省，节省的是非常有限的硬件成本，浪费的是非常昂贵的团队成本。

3. 机会成本

这个就是非常玄学了，并且也不好评估，如果有平行时空存在，倒是可以给这个做个 ABTest。

所谓机会成本就是：可能推荐系统并没有帮助产品创造什么价值，反而把很多资源投入在这上面，白白浪费了市场窗口期。

在信息流大火的今天，大家觉得个性化咨询阅读天然成立，然而仅仅在十年前，许多做个性化阅读的产品投入巨大，到今天可以说尸骨无存。如果当年他们不用推荐系统做，而是老老实实用人工编辑的方式做，也许有不一样的结果。这个就是机会成本。

直白地讲，机会成本就是那句毒鸡汤的正经说法：选择大于努力。

总结

今天，我带你换了一个角度去看待推荐系统的角色和作用，它是一台注意力存储机器，在注意力稀缺的今天，用投其所好的方式存储注意力，并将注意力作为商品与人交换产生价值，也就是广告、电商销售、其他增值服务。这些都是存储了注意力，并对存储的注意力进行了变现。

但是，你要认识到，存储注意力并非只有推荐系统一种机器，所以你们家要不要采购这台注意力存储机器呢？

那就需要再考虑一下成本了，如果存储的注意力价值远不及成本，想必你会采购这台注意力存储机器。这样就和专栏的开篇讲到，要不要上推荐系统的分析首尾呼应。

你能估算一下你熟悉的产品的注意力大小吗？你可以给我留言，我们一起讨论。

【产品篇】说说信息流的前世今生

信息流，就是 Feed，包括社交动态信息流，也有图文资讯信息流，短视频信息流。

在前面说过，推荐系统是一种注意力存储器，注意力是信息经济时代的稀缺商品，广告商向平台方购买注意力，平台方把存储的注意力分一点给广告商，然后通过推荐系统收集更多注意力补充回来。

在今天，最厉害的注意力存储器就是信息流，尤其是个性化信息流，也叫做兴趣 Feed，这也是推荐系统的一种。

前世今生

说信息流，就不得不提到 NewsFeed。2004 年，Facebook 问世，2006 年，信息流鼻祖 NewsFeed 横空出世，经过十多年，NewsFeed 已经是日收入几千万美金的现金大牛。

在 NewsFeed 上线前，经历过两个抗议阶段，第一个是把新鲜事公布出来，原先的新鲜事被大家认为是隐私，在时间线中呈现出来被好友看见不妥，而事实是，每个人在意的除了自己的隐私被公布，更在意的是朋友的八卦，数据表明新鲜事被公布后，用户活跃度大幅上涨。

第二个就是 NewsFeed 上线，用户广泛抗议，原来按照时间先后顺序阅读新鲜事，现在却按照重要程度阅读，非常不习惯，然而数据表明，用户互动行为再一次大幅度提高。

这些年来，NewsFeed 有数不清的改进，甚至每天线上会同时部署很多算法版本进行 AB 测试。后来的故事大家都知道了，Facebook 上市，股价逐年上涨。

NewsFeed 的成功，验证了几个常识：

数据驱动比舆论驱动靠谱，别听人们嘴上是怎么说的，只看人们是如何行动的；

窥探隐私，向群体靠拢，害怕孤单是普遍人性，把新鲜事公开这件事验证了这一点；

注意力非常有限，用推荐系统的方法更好地储存注意力，基于兴趣的信息流验证了这一点。

后来，Twitter，微博，Instagram，老牌的时间线信息流方式如今都换成了按照兴趣筛选内容，原因都是信息泛滥，用户错过的信息量越来越多，注意力耗散很多，无法将耗散的注意力变现成了这些平台最大的痛。

今天，搜索公司 Google、百度，都已经押注了信息流，更不说那辆行驶在注意力收割航道里的短视频新兴巨轮。

这些公司，尤其是其中的上市公司，在财报里也会提及信息流，可以说，信息流在今天已经是红透了半边天。

配套设施

信息流是一个低衰减的注意力存储器，但是光有信息流是不完整的，最大的问题可能有两个：

内容源不足，无法形成信息过载，注意力就不会稀缺，注意力是无法待价而沽的商品；

在注意力变成稀缺的事物后，存储的注意力无法变现，反哺平台自身。

针对这两个问题，完整的信息流产品还需要配套设施。以 NewsFeed 为例，讲讲信息流的配套设施。

1. 内容源

内容源是注意力的重要间接影响因素。“内容哪里来”是信息流要不断思考的问题，对于 NewsFeed 来说，就是社交关系上的人发布新鲜事。

NewsFeed 存在的前提是要依赖用户建立大量的社交联系，这样才会出现信息过载，因此 NewsFeed 的一个重要的配套设施就是“你可能感兴趣的人”推荐系统。

这是一个我们在产品形式上比较熟悉的推荐系统，它是一套大规模矩阵分解算法，在前面的专栏已经专门讲过，这套推荐系统希望用户和用户，用户和 App、公共主页等都建立起大量的连接。

建立起连接，相当于变相地增加了内容源，这些用户发布的新鲜事，App 产生的内容，公共主页发布的帖子，都会通过这些连接流进用户的个人信息流。

社交信息流中，内容源依赖于社交关系的数量。而图文资讯信息流，则更多依赖爬虫技术，“不生产内容，只是内容的搬运工”。

依赖爬虫的信息流内容源，质量非常不可控，会有涉黄、涉政、涉暴力等敏感内容，甄别工作量非常巨大，而且一旦控制不好就是社会事件，代价惨重，这一点在 2018 年，你一定感受很深。

内容源是信息流的一种重要基础设施，要想尽办法建设好。内容源应该考虑下面几种。

质量：虽然群体喜欢消费低质量的内容，便宜商品，但是一旦出现敏感内容， 不合格的商品等，代价还是很高昂。

多样性：信息只有多样了才有信息量，有了多样性才能满足更多的用户，才能在存储海量注意力时不衰减。

数量：数量自不必说，推荐系统解决信息过载问题，没有信息过载问题怎么办呢？就是先制造信息过载问题，要制造信息过载，信息的数量就要有保障。

2. 广告系统

NewsFeed 还有另一个配套设施，也是它为什么每天能吸金几千万刀的原因：那就是广告系统。

Facebook 的广告形态多样。

Suggested Page (你可能喜欢的公众页)

Page Post (公众号帖子推广)

Suggested App (你可能喜欢的应用)

Video Ads (视频广告)

广告主花钱购买信息流存储的注意力，俗称信息流变现。实际上就是信息流产品供应注意力，广告主消费注意力。注意这枚硬币的另一面：广告主供应的什么，用户是否消费了，则是另外一套看待角度。

以前，Facebook 鼓励商业机构花钱投广告增加粉丝，彼时的 NewsFeed 算法允许随意发广告，看上去就是公共主页发布了新鲜事。

这一阶段对应着增加用户和内容源之间的连接阶段，是一个非常必要的步骤。看上去广告主增加了自己的粉丝，用户增加了内容源，而本质上则是让注意力买主先看到他种草的商品，这个阶段只让广告主每天摸摸自己种草的商品，并不是真的给他。

直到后来，平台方开始严格限制商业广告与普通用户触达，不只是 Facebook，任何的信息流平台，在广告主吸引了足够粉丝之后，都会果断限制广告无节制地触达用户。种草的商品突然提价，广告主就只能剁手买买买，这就是广告系统了。

跟据某个专门做 NewsFeed 推广的公司追踪结果，1000 个公共主页的 50000 条内容以原生方式触达用户的比例，从 2012 年 16% 降低到了 2014 年的 6.51%，降了一倍还多。

当然也可能是因为用户平均关注的公共主页增多了，而本质上的原因就是，注意力市场开市了。

在注意力这边，存储注意力要做的事就是基于兴趣筛选信息流，重新排序展示，这样的好处就是用户不会错过自己感兴趣的，而本质上就是留住注意力，不要衰减。在注意力购买方这边，通过广告系统，大家去购买自己看中的注意力。

信息流，看上去就是这么一个简单的商业逻辑。广告主这边一开始和信息流平台方有非常甜蜜的日子，直到要花钱购买自己帮忙存储的注意力时，就会有怨言了。

曾经就有广告主对 Facebook 抗议道：那你干脆不要干涉 NewsFeed 排序啊，按时间线自然展示，用户错过就错过，大家都公平。对此 Facebook 的解释则是：数据显示，重排序后的 NewsFeed 可以让用户阅读积极性提高很多。这句话的意思是：这样做才能存储注意力啊。

关于到底要不要重排序的争吵，我们要看清楚，双方都是商业机构：一边是要消费注意力，一边在销售注意力。

这本身就是买卖嘛，不要谈什么情怀，商业社会永远是逐利的，逐利的手段就是制造信息不对称，并且在制造过程中不断提高效率和降低成本。

显然，“大家一起穷，完全拼人品”的时间线，不符合基本商业逻辑，信息流才符合商业逻辑。

世界上最遥远的距离就是：手握大把粉丝，却不能随心所欲地曝光自己的产品。

总结和展望

具体信息流会怎么发展，我们无法预测，但是可以肯定的有三点：

信息流是推荐系统在商业上最成功的应用；

完全依赖数据驱动的信息流会面临黑天鹅事件，所以人和算法协同进化的信息流会是最有生命力的；

数量上，注意力已被大厂囤在自己了手中，那么下一步要关注的是注意力的质量，这是信息流平台方的商品，毕竟广告主购买了注意力后，发现是地摊货，生意也不会长久的。

今天和你聊了信息流前世今生和未来，其中有很多我的个人观点，观点都可以交锋，欢迎你留言和我交锋。感谢你的收听，我们下期再见。

【团队篇】组建推荐团队及工程师的学习路径

如果你是老板，或者是公司里的推荐系统包工头，那么你一定会关心：要凑齐多少人才能开始搬砖？

一个推荐系统复杂度没有上限，但是有最低标准，所以下面在估算推荐系统团队规模时，按照下限来估计，照这个方式建立的团队就叫做“有下限的团队”。

团队组建

我们先定义团队的角色，这里既然是组建“有下限团队”，当然按照能省则省的原则。

算法工程师，承担的是数据科学家和机器学习工程师的双重职责，主要职责是清洗数据，训练离线推荐模型，开发算法接口，评估指标。

软件开发工程师，承担算法之外的开发任务，例如数据库的搭建维护，API 接口的开发，日志的收集，在线系统的高可用等，当然“有下限的团队”可以适当简陋些，不用考虑高性能。

其他非技术角色，如果是“有下限团队”的话，这一项也可以省略，如果涉及了跨部门合作，或者你不幸被老板提出各种“推得不准”的伪 Bug，那么你就需要一个这样的角色去充当工程师港湾，来阻挡外界的风雨。

接下来，估计一下每个角色的数量。这里主要是估计工程师的数量。

关于算法工程师，最低配需要 2 位，一位三年左右经验的算法工程师负责数据分析，数据清洗，推荐模型训练，评估和上线，外带一位三年以下经验的初级工程师，从中辅助分担琐碎工作。

为什么说有经验的算法工程师一位就够了，假如你使用矩阵分解作为推荐系统第一版核心算法，那么推荐使用 Quora 开源的 QMF 工具。它能在一台 32 核、244G、640G 固态硬盘的服务器上用 20 分钟完成 10 亿非零元素，千万用户和物品的矩阵分解。工具简单易用，一个有经验的工程师足够让其运转起来。

那么核心问题就是，一台机器是不是撑得起你老板的野心？我认为，撑得起，具体的估算如下。

根据我前文中对注意力的定义：内容消耗的加速度乘以内容的消耗难度。当注意力为正数时，是上马推荐系统的好时机。

因为这说明平台方已经有了注意力的原始积累，只需要加上推荐系统将它保存下来并加以扩大即可。那么组建的这个“有下限团队”最低要求就是能留住当前的注意力。

注意力为正时，每天的用户消耗内容数量应该是指数级别，比如 f(a,t)=taf(a,t)=ta (a >= 2)。其中 t 是时间，a 大于 2 时才会有正的注意力。因为它的二阶导数为：a(a−1)t(a−2)a(a−1)t(a−2)。当然，这个数学推导不重要，只是举个例子。

每天的内容消耗量，其实就是用户产生的行为数据条数，至少是正比关系，这里从简考虑，认为二者等同。假如 a=2，t 的单位是天。那么在 t 天后，累计产生的日志数量是：

∑i=1Ti2=T(T+1)(2T+1)6∑i=1Ti2=T(T+1)(2T+1)6

现在看看，如果你公司使用的服务器和 Quora 评测 QMF 时所用服务器一样的配置，用单机运行 QMF，极限是撑多久？我简单列个方程。

T(T+1)(2T+1)6=6000000000T(T+1)(2T+1)6=6000000000

方程右边就是 QMF 评测时处理的 60 亿非零元素。解这个一元三次方程，得到唯一的实数解是 1441.75 天，也就是 3.9 年。

所以告诉你的老板，你一个人可以撑四年，只管定期加工资就可以了，不用加人。

那么为什么明明一位算法工程师就可以，还要外带一位，这主要是考虑，团队人才应该有梯度和备份。

关于软件开发工程师，至少需要四位，是的，你猜到了，我要证明的是需要两位，还有两位是为了人才梯度和冗余备份。分工是这样的。

推荐服务输出，一位三年及以上经验的后端开发工程师，外带一位三年以下的初级工程师。负责调用推荐 RPC 服务，开发必要的过滤逻辑，填充详细字段等；

反馈数据收集和管理，一位三年以上经验的运维工程师，外带一位三年以下的初级工程师，负责回收用户反馈数据，统一存储日志数据。

以上就是一个最低配推荐系统团队的配置。当然，如果能复用现有团队的部门工程师，则灵活处理。上面的估算也只是一个示例。

个人成长

下面来说说，工程师个人该如何学习和成长的问题。

推荐系统工程师和一般意义的软件工程师相比，看上去无需像 IOS 或者 Android 工程师写大量的代码；也无需像研究院的研究员那样，非得憋出漂亮的数学模型才能工作；更无需像数据分析师绘制出漂亮的图表。那推荐系统工程师的定位是什么呢？

实际上，这里说的几个“看上去无需”，并不是降低了推荐系统工程师的要求，而是提高了要求。因为你得具备三个核心素质：

有较强的工程能力，能快速交付高效率少 Bug 的算法实现，虽然项目中不一定要写非常大量的代码；

有较强的理论基础，能看懂最新的论文，虽然不一定要原创出漂亮的数学模型；

有很好的可视化思维，能将不直观的数据规律直观地呈现出来，向非工程师解释清楚问题所在，原理所在。

首先，虽然世人目光都聚焦在高大上的推荐算法上，然而算法模块确实是容易标准化的，开源算法实现一般也能满足中小厂的第一版所需，而实现整个推荐系统的路径却不可复制，这个实现路径就是工程。

可以说，是工程能力决定了推荐系统的上限。如何提高工程能力，无他，就是反复刻意练习。但是对于入行不同年限的人来说，提高的办法则各不相同。

对于在校生，一个比较好的办法是，将看到的任何算法知识、论文或者图书，都亲手转换成代码，一个简单的算法，从你看懂到你无 Bug 地实现出来，其实还有很远的距离，在实现完成后，去阅读对应的热门开源应用，阅读它的实现方法，对照自己的，总结差距。

对于刚工作的新人，这时候你已经有一定的工程基础，并且没有太多的整块时间，那么就要好好把握工作中的项目实战。

避免重复造轮子的前提是知道有轮子，并且知道轮子好在哪，这要求你熟读现有轮子的各种，对它性能、实现方法了如指掌，如此才能在不重复造轮子的基础上安心实施拿来主义，并且可以进一步将轮子按照实际使用的所需问题进行改良。

对于工作一定年限的人，这时候你已经熟知各种轮子极其弊端，也能改进了，那么在业务逐渐增长后，需要考虑将系统中部分模块中所使用的开源加上补丁，整体升级为自研系统。这个开发可以从一些风险不高的模块着手，逐渐锻炼。

上述三个大的阶段，比较粗略。但是核心思想就是：爱动手，爱思考，爱阅读，爱总结。

第二，是理论基础。对于一个从事推荐系统的工程师来说，一定需要有数理基础。高等数学、概率统计、线性代数这些大学基础课一定还在自己心中，没有还给老师。

如果不幸还给老师，你需要重新捡起来，因为整个机器学习都是建立在高等数学基础上的。另外，有一个学科我个人认为很重要，甚至成为人生的指南，那就是信息论。信息论用量化方式确定了什么是信息，很多算法问题也因此可以从通信角度考虑。

第三，是数据可视化思维，在做数据分析和清洗工作时，需要想办法直观地呈现出来，在工具层面，掌握一些常用的绘图工具就很有必要了。Python 中的 Matplotlib，R 语言中的 ggplot2，Linux 命令里面的 Gnuplot，Windows 里的 Excel 等等都是非常常用的绘图工具。

掌握工具并不难，还需要有 show 的冲动，直观方式呈现出数据规律不但对自己优化算法和系统有非常大的作用，还可以与合作伙伴快速达成任务共识，节省沟通成本。

这三个能力，建立起来的难度逐渐增加，需要持之以恒，与《卖油翁》那句著名的“无他，但手熟尔”，规律相同。

除此之外，还有一些非典型工程师的加分项。

学习能力：虽然缓慢，但是科学一直在突破边界，技术更是日新月异地升级了一代又一代，而文化的进化则远远快于人类基因的进化，这些变化，都要求你我要有不断学习的意识，还要有会学习的能力。

沟通能力：在一些中大型厂，一些数据资源分散在不同部门，在技术之外需要去整合这些资源，这需要沟通能力。

表达能力：能把一件事情讲清楚，最直接的好处是在团队内部减少无效的沟通，提高工作效率。

总结

今天我主要谈的是推荐系统中人的因素，包括了团队和个人，这部分内容本来和技术干货内容相比，就有点形而上，但是事实上却又绕不开这部分内容。

因此，我先用一个例子呈现了一个“有下限团队”应该有多少人。这里没有考虑人的个人能力差别，这里就假设大家智商都有一样，没有天才和白痴。

很多时候，其实单机就能搞定很多看上去很复杂的事情，这是我不太推崇分布式的原因，因为多数时候没必要。

最后，我谈了我对推荐系统工程师的能力看法，一共有三个层次，建设起来由易到难，需要不断刻意练习，才可能有较大的能力进步，这一点我和你共勉。

你对工程师的学习路径又有哪些体会呢，可以跟我留言，我们一起分享。

感谢你的收听，我们下次再见。

【尾声】遇“荐”之后，江湖再见

好了，专栏终于写完了，所以我可以承认了：写专栏的过程还是很痛苦的。

如果要说整个过程中的一些感悟和心路，那就概括为三个“如”字吧。这三个“如”字，是三种痛苦，同时，也是三种收获。

如相问，写专栏

开始写专栏的时候也刚好是我入职贝壳找房（原链家网）的前后。这几年在小公司自由折腾惯了的我，需要突然适应成熟公司的一板一眼和部门合作，还有接踵而来的各种工作计划，都足以让人有点力不从心。

结果，专栏上线后的第一周，编辑就告诉我每周需要更新三篇，无论她当时是以多么轻松的口吻描述这个事实，我内心都是崩溃的，这不是鸡汤文，不是情感专栏，是硬朗的技术干货。

那还能怎么办？当然是选择原谅他们啊。所以，从写专栏的第一篇开始，我几乎无休地写了三个月，当然，注意这里我很有心机地写的是“几乎”。

这是身体消耗的辛苦，之后呢？却也让我遇见了另一个自己，持续的高强度写作，让我更加注意精力分配，也比以前更加地自律。这主要得益于 Lizzi 定期问我“这周作业写完没有”，让我仿佛回到小学，还有编辑也经常时不时地问候我“什么时候交稿”，令我不敢怠慢。

回望这三个月，真的是：洛阳亲友如相问，就说我在写专栏。

如来故，不负卿

写专栏以来，就有两个声音在内心互相叫骂。

这一边是要警惕那些博客写得好的工程师啊，不务正业。这个声音源自微信之父，感兴趣的人可以去搜一下原话。

那一边是一些早已是业界常识的算法或技术，很多人竟然第一次听说，或者还从没弄懂过，这就真的很需要有人助一臂之力了。

前面那个声音让我一度怀疑自己是否真的不务正业，后面那个声音时常鞭策我笔耕不辍。

两个声音每天都在对话，结果谁赢了呢，你猜？当然是后者，不然也不会有这个专栏了，这也是开设这个专栏的初心：填平知识的鸿沟，消除知识的信息不对称。

这是内心挣扎的痛苦，无法两全。任何事情，总是应该看待它的价值和闪光点，而不是盯着它的不足和负面。

对于写作这件事亦是如此，有价值，我喜欢，就可以开始，虽然曾经也有身边人对我投来不屑和嗤之以鼻的表情，令我一度深深地怀疑过自己的选择。

今天，写完这个专栏之际，我感觉到前所未有的成就感，因为过程中不断收到订阅者的反馈，大家的反馈让我坚信自己初心的正确，也让我庆幸自己没有因为别人的看法而改变自己的内心所向。

“世间安得两全法，不负如来不负卿。”选择有价值的方向，并沿着它勇往直前。

如临渊，如履冰

工作以来，我也持续在一些渠道零碎地分享东西，这一次写专栏很不同，因为大家都是付费阅读。这让我时常处于如履薄冰的心态中，非常害怕自己输出的东西不好，质量对不起别人付出的软妹币和阅读时间，如果订阅者觉得不爽还要付出一定的负面情绪来 diss 我，那我的罪过就十分大了。

这种不放心，令我时常表现为拖延症：每一篇迟迟不能开始写，总觉得准备得不够，直到 Deadline 施施然地走来，我才硬着头皮开始落笔。总觉得自己没有写太好，就这样念叨着，写到了最后一篇。

写完最后一篇之后，我写了个 Python 程序统计全部字数，将近十五万字，着实把我自己吓了一跳，万万没想到，我原来是这样的能说。

这是一种战战兢兢的痛苦。但也是一种心怀敬畏的力量，这种力量，让我能在深渊旁边安全前行、在薄冰上也能站稳。

深渊凝视着我，我也凝视着深渊，深渊就在那，我也就在那，和谐地共处天地间。

敬畏会带来质量，敬畏会带来价值。敬畏你的行业，敬畏你的服务对象，敬畏你所要解决的问题，这个世界也许会变得更好一点。

如临深渊，如履薄冰，心若是存有敬畏，冰只会被春天融化。

好了，专栏结束了，但交流并不会就此终止，我们仍然可以一起交流与分享，山高水远，我们江湖再见。

推荐系统的参考阅读

专栏主体内容已经结束了，在专栏写作的过程中，我阅读了很多业界公开的资料，我觉得有必要整理出来，供想深入阅读的人继续去找虐。

整体来说，在选择参考文献时，我偏爱那些由公司发表的。因为推荐系统本质上还是一种非常依赖实践的算法应用方向，并且，这些商业公司论文中的技术内容也在他们实际的场景中经过了检验。

另外，更多的内容是来自我自己的大脑中，所以我在下面列出来的只是一部分，在经过反复删减之后，保留了这些，有中文有英文，一般来说英文居多。有较理论化的，如优化理论，更多的是较实践派，可以学完即用。这些资料分成这么几个类型。

论文：以论文形式发表的，期刊数据库中可以下载到。

网络文章：就是在网上自由流传的内容或者博客，为了方便阅读，我将它们保存为 PDF 格式。

演示文稿：就是作者曾公开演讲过的内容，相对来说不是那么严谨，但是更容易理解。

书：推荐系统相关的书较少，我在专栏中参考过的书只有一本（附件中不提供书的电子文档）。

以上的参考文献我按照章节顺序列在了下面，我还在后面附上一个推荐书单。你可以点击查看。

原理篇

1. 内容推荐

题目：Bag of Tricks for Efficient Text Classification

类型：论文

作者：Facebook

说明：

Facebook 开源的文本处理工具 fastText 背后原理。可以训练词嵌入向量，文本多分类，效率和线性模型一样，效果和深度学习一样，值得拥有。

题目：The Learning Behind Gmail Priority Inbox

类型：论文

作者：Google

说明：

介绍了一种基于文本和行为给用户建模的思路，是信息流推荐的早期探索，Gmail 智能邮箱背后的原理。

题目：Recommender Systems Handbook(第三章，第九章)

类型：书

作者：Francesco Ricci 等

说明：

这本书收录了推荐系统很多经典论文，话题涵盖非常广，第三章专门讲内容推荐的基本原理，第九章是一个具体的基于内容推荐系统的案例。

题目：文本上的算法

类型：网络文章 (网络免费版，已有成书《文本上的算法: 深入浅出自然语言处理》，内容更丰富)

作者：路彦雄

说明：

介绍了文本挖掘中常用的算法，及基础概念。内容涉及概率论，信息论，文本分类，聚类，深度学习，推荐系统等。

题目：LDA 数学八卦

类型：网络文章

作者：Rickjin(@靳志辉)

说明：

由浅入深地讲解 LDA 原理，对于实际 LDA 工具的使用有非常大的帮助。

2. 近邻推荐

题目：Amazon.com recommendations: item-to-item collaborative filtering

类型：论文

作者：Amazon

说明：

介绍 Amazon 的推荐系统原理，主要是介绍 Item-Based 协同过滤算法。

题目：Slope One Predictors for Online Rating-Based Collaborative Filtering

类型：论文

作者：Daniel Lemire 等

说明：

Slope One 算法。

题目：Item-Based Collaborative Filtering Recommendation Algorithms

类型：论文

作者：Badrul Sarwar 等

说明：

GroupLens 的研究团队对比了不同的 Item-to-Item 的推荐算法。

题目：Collaborative Recommendations Using Item-to-Item Similarity Mappings

类型：专利

作者：Amazon

说明：

是的，Amazon 申请了 Item-Based 算法的专利，所以如果在美上市企业，小心用这个算法。

题目：Recommender Systems Handbook（第 4 章）

类型：书

作者：Francesco Ricci 等

说明：

第四章综述性地讲了近邻推荐，也就是基础协同过滤算法。

3. 矩阵分解

题目：Matrix Factorization and Collaborative Filtering

类型：演示文稿

作者：Daryl Lim

说明：

从 PCA 这种传统的数据降维方法讲起，综述了矩阵分解和协同过滤算法。矩阵分解也是一种降维方法。

题目：Factorization Meets the Neighborhood: a Multifaceted Collaborative Filtering Model

类型：论文

作者：Yehuda Koren

说明：

把矩阵分解和近邻模型融合在一起。

题目：BPR- Bayesian Personalized Ranking from Implicit Feedback

类型：论文

作者：Steffen Rendle 等

说明：

更关注推荐结果的排序好坏，而不是评分预测精度，那么 BPR 模型可能是首选，本篇是出处。

题目：Collaborative Filtering for Implicit Feedback Datasets

类型：论文

作者：Yifan Hu 等

说明：

不同于通常矩阵分解处理的都是评分数据这样的显式反馈，本文介绍一种处理点击等隐式反馈数据的矩阵分解模型。

题目：Matrix Factorization Techniques For Recommender Systems

类型：论文

作者：Yehuda Koren 等

说明：

本文是大神 Yehuda Koren 对矩阵分解在推荐系统中的应用做的一个普及性介绍，值得一读。

题目：The BellKor Solution to the Netflix Grand Prize

类型：论文

作者：Yehuda Koren

说明：

也是一篇综述，或者说教程，针对 Netflix Prize 的。

4. 模型融合

题目：Adaptive Bound Optimization for Online Convex Optimization

类型：论文

作者：Google

说明：

FTRL 是 CTR 预估常用的优化算法，本文介绍 FTRL 算法原理。

题目：在线最优化求解

类型：网络文章

作者：冯扬

说明：

是对 FTRL 的通俗版解说。

题目：Ad Click Prediction: a View from the Trenches

类型：论文

作者：Google

说明：

FTRL 工程实现解读。

题目：Factorization Machines

类型：论文

作者：Steffen Rendle

说明：

提出 FM 模型的论文，FM 用于 CTR 预估。

题目：Field-aware Factorization Machines for CTR Prediction

类型：论文

作者：Yuchin Juan

说明：

FFM 模型，用于 CTR 预估。

题目：Practical Lessons from Predicting Clicks on Ads at Facebook

类型：论文

说明：

提出了 LR + GBDT 的 CTR 预估模型。

题目：Wide & Deep Learning for Recommender Systems

类型：论文

作者：Google

说明：

提出融合深度和宽度模型的Wide&Deep 模型，用于 CTR 预估。

5.Bandit 算法

题目：Introduction to Bandits- Algorithms and Theory Part 1- Bandits with small sets of actions

类型：演示文稿

作者：Jean-Yves Audibert 等

说明：

介绍 bandit 算法概念，理论和算法，这部分主要针对小的选项候选集。

题目：Introduction to Bandits- Algorithms and Theory Part 2- Bandits with large sets of actions

类型：演示文稿

作者：Jean-Yves Audibert 等

说明：

介绍 Bandit 算法概念，理论和算法，这部分主要针对较大的选项候选集。

题目：A Contextual-Bandit Approach to Personalized News Article Recommendation

类型：论文

作者：Yahoo

说明：

Linucb 的原始论文，考虑上下文的 Bandit 算法。

题目：Collaborative Filtering Bandits

类型：论文

作者：Shuai Li 等

说明：

Bandit 算法与协同过滤结合，提出 COFIBA 算法。

6. 深度学习

题目：Deep Neural Networks for YouTube Recommendations

类型：论文

作者：Google

说明：

介绍 YouTube 视频推荐系统在深度神经网络上的尝试。能从中看到 wide&deep 模型的影子。

题目：Efficient Estimation of Word Representations in Vector Space

类型：论文

作者：Google

说明：

Word2Vec 的作者在这篇文章中提出了一种词嵌入向量学习方法，也就是把开源工具包 Word2Vec 背后的模型详细介绍了一次。理论上很简单，更多是一些工程技巧的分享。Word2Vec 给推荐系统带来了一种新的隐因子向量学习方法，深陷评分预测泥潭的矩阵分解被开拓了思路。

题目：Item2Vec: Neural Item Embedding for Collaborative Filtering

类型：论文

作者：Microsoft

说明：

这篇就是借鉴了 word2vec 在语言建模中的思路，为推荐系统的行为建模，从中为物品学习嵌入向量。

题目：Learning Representations of Text using Neural Networks

类型：演示文稿

作者：Google

说明：

理解为 word2vec 作者写一个教程。

题目：Long Short-Term Memory

类型：论文

作者：Sepp Hochreiter 等

说明：

可以用来为序列建模的 LSTM，实际上在 1997 年就发表论文了，只是在十几年后才大火。

题目：An Empirical Exploration of Recurrent Network Architectures

类型：论文

作者：Google

说明：

Google 在 RNN 模型使用上的经验分享。

题目：Recurrent Neural Networks for Collaborative Filtering

类型：网络文章

作者：Erik Bernhardsson

说明：

这是 Erik Bernhardsson 在 Spotify 期间所做的尝试，用 RNN 自动构建音乐播单。Erik Bernhardsson 还有一项开源项目 Annoy，用于稠密向量的近邻搜索，在推荐系统中也用得较多。

7. 其他实用算法

题目：Detecting Near-Duplicates for Web Crawling

类型：论文

作者：Google

说明：

在这篇论文中提出了 simhash 算法，用于大规模网页去重。

题目：Weighted Random Sampling over Data Streams

类型：论文

作者：Pavlos S. Efraimidis

说明：

对流式数据的加权采样。

题目：Weighted Sampling Without Replacement from Data Streams

类型：论文：

作者：Vladimir Braverman 等

说明：

介绍了两种对流式数据的加权采样。

工程篇

1. 常见架构

题目：Activity Feeds Architecture

类型：演示文稿

作者：Etsy

说明：

本文非常详细地介绍了社交动态信息流的架构设计细节。

题目：Atom Activity Streams 1.0

类型：规范文档

作者：Activity Streams Working Group

说明：

这是一份动态信息流数据模型的协议规范文档，由 Activity Streams Working Group 共同发出，这个组织包含 Google 和 Microsoft。

题目：Beyond the 5 stars（Netflix Recommendations）

类型：网络文章

作者：Netflix

说明：

Netflix 详细宏观上介绍了自家推荐系统的产品形态，不只是比赛中的评分预测那么简单的。

题目：System Architectures for Personalization and Recommendation

类型：网络文章

作者：Netflix

说明：

Netflix 推荐系统的架构介绍。

题目：Information Seeking-Convergence of Search, Recommendations and Advertising

类型：论文

作者：H Garcia-Molina 等

说明：

探讨搜索、推荐、广告三者架构统一。

2. 关键模块

题目：Overlapping Experiment Infrastructure- More, Better, Faster Experimentation

类型：论文

作者：Google

说明：

ABTest 实验平台的扛鼎之作，Google 出品，值得拥有。

题目：TencentRec：Real-time Stream Recommendation in Practice

类型：论文

作者：腾讯

说明：

介绍了腾讯内部的实时推荐系统架构。

题目：Personalization at Spotify using Cassandra

类型：网络文章

作者：Spotify

说明：

介绍了 Spotify 在推荐系统所用到的数据存储中间件。

3. 效果保证

题目：Tutorial on Robustness of Recommender Systems

类型：演示文稿

作者：Neil Hurley

说明：

本文非常详细讨论了对推荐系统的攻击和防护，并有实验模拟。

题目：Recommender Systems Handbook(第八章)

类型：书

作者：Francesco Ricci 等

说明：

该书第八章介绍了能见到的几乎所有推荐系统评价指标，只是实际上用不到这么多指标。

其他书目

Pattern Recognization and Machine Learning（机器学习基础，有此一本足够了）。

推荐系统实践（国内唯一一本非翻译的推荐系统书籍，入门必选）。

信号与噪声（介绍贝叶斯统计的一本科普书）。

复杂（推荐系统面对的是复杂网络，了解复杂系统和复杂网络的特点，有助于开脑洞）。

信息简史（既然是信息经济，当然要读一本关于信息的历史）。

知道你们不会读的，所以就不推荐太多了。但愿我这个激将法有助于你学习进步。

打包资料下载地址：

https://pan.baidu.com/s/1pbjQ94QBcRerv6ZW3-sopg

密码:6mds