今天想和大家分享,如何使用大规模机器学习解决真实的业务问题。我们今天会以机器学习中的一个典型场景为例来讲解,即基于大规模机器学习模型的推荐系统。
推荐系统的本质是什么
比如说我们看到手机淘宝首页,往下一拉,就能看到各种各样推荐的商品;比如说百度,它会给我们推荐广告,在某种程度上他的工作方式也很像推荐系统;再比如说今日头条,今日头条从数十万的新闻中选出会被我们看到的数十个新闻,这也是推荐系统。
尽管我们在生活中会已经见过非常多的推荐系统,但是在用机器学习搭建推荐系统之前,我们还是应当先思考一下,推荐系统要解决的到底是个什么样的问题
推荐系统在本质上是一个信息检索的系统。它和搜索最大的区别是,搜索是主动式的,根据关键词和引擎参数、搜索引擎召回、机器学习排序,决定给你看到的是哪些内容。而我们看到的推荐系统,在大多数情况下是没有主动输入的(有时会有一些简单的反馈动作),是被动出现的。
推荐系统是利用上下文,根据当前用户所处的环境,根据信息的特点来决定给你推荐什么内容和商品。而在我们进一步去想之前,我们要问自己一个问题,就像上节课田老师讲的一样,推荐系统的目标是什么,什么才是一个好的推荐系统,要优化的指标是什么。
推荐系统的指标是什么
推荐系统是个产品,产品当然是想方设法让用户去喜欢的,或者至少是不讨厌的。因而,我们需要把喜欢和讨厌这两件事情定义出来。同时我们毕竟不是用户肚子里的蛔虫,我们只能用我们可以测量到的数据来描述喜欢和讨厌两件事情。并用这些数据来决定我们做什么和不做什么。
比如说:我是个电商,用户表达对一个推荐商品喜欢的方式是:点击、收藏、加购物车、甚至购买下单、分享到社交平台上等等。用户讨厌一个推荐商品的方式,就是会投诉、会提意见。因而我们要预防一些很可能会让用户讨厌的推荐结果:比如说推荐成人用品和内衣,尤其是在上班时间;比如推荐用户刚刚买过的商品,等等。
我们一定能为一个推荐系统去定义指标,我们可以给这些指标分轻重缓急,看能用什么顺序实现。现在我用的指标可能有点投机取巧,我用的是点击率。而真实的指标考虑的是很多的,仅仅考虑点击率的模型,可能会出现标题党,如果是电商就可能会出现一堆 9 块 9 包邮,这可能不是我们业务想要的。
另外即使只考虑点击率,我们也知道其实我们推荐的是一个列表,列表的质量不完全是由单一的商品决定的,而是整个列表的组合、顺序、多样性所决定的。所以真实的业务中,我们会考虑用更复杂的目标,比如 MAP 来评价一个推荐列表的质量。
但没关系,今天我们就用点击率作为试点,介绍如何用机器学习来搭建推荐系统的完整过程。
推荐系统的 y 和 x
第一步, 我们已经知道机器学习模型需要预测的就是优化目标,点击率;那我们把用户的点击行为需要记录下来。这样一来,对于机器学习来说,我们已经有 y 了
第二步,我们需要定义好 x,也就是特征。
一般来说推荐系统的特征体系由 3 个部分组成:用户特征、内容特征、上下文特征。
用户特征:包括但不限于用户姓名、性别、年龄、注册时间、收货地址、常用区域等用户特征
内容特征:包括但不限于以及商品、内容的标题分词、内容的 TF-IDF、内容来源、内容渠道、内容生产者等等
那么上下文特征, 是代表用户当前时空状态、最近一段时间的行为抽象的特征。比如说用户当前的 GPS 坐标,大家可能觉得奇怪, GPS 坐标怎么用来推荐呢 其实很简单,地球一圈是 4 万公里,GPS 一圈是 360°,一度大概是 100 公开。如果我们把 GPS 坐标保存到小数点后一位,组合起来,这样的特征就是 10*10 公里的格子,这就代表了一个有泛化能力的用户的位置。
位置是一个非常强的特征,如果我们更进一步,做到了 1 公里,显然我们可以相信,在中关村地区,大家的偏好是有共性的,而在金融街,大家的喜好也是有共性的。当大家的数据足够多的时候,落在同一个格子里的人会非常多,GPS 就会成为非常重要的特征。
另外是 IP 地址,比如最近浏览的内容、最近购买的商品,这些都会构成上下文特征。所以我们就是在用户特征、内容特征和上下文特征的基础上,预测用户对当前内容的点击率。
推荐系统的样本构造和数据拼接
一个成熟的推荐系统,它可能有非常复杂的样本构造方法,今天用了比较简单的方法,让问题变得简单。另外,一个成熟的推荐系统,它可能会有多个指标和业务边界条件。
那么接下来:基于已知的 x 和 y,我们要为机器学习构造样本。什么是一条样本 一个样本代表机器学习预测的一个最小粒度的事件。当你把一条内容展现给用户,用户点击或不点击,这就代表了一个最小粒度的事件,就是一条样本。再比如说我们给用户展示了 10 条新闻,用户对应每个新闻点击或者不点击,就是 10 条样本。
在样本采集后,就要考虑数据怎么收集和拼接了。在拼接的时候要注意的是,假如是为了优化点击率,我不光要把用户特征、上下文特征收集起来,我还要把点击率拼回到当时那一条样本请求上去。所以系统一定要有这样的考虑,记录下时间和拼接的 ID ,同时还要考虑刚才说的三类特征是处于实时变化当中的,日志也是实时产生的,而不是后面去拿的,因为这样做很可能会出现问题。 数据一旦出问题,是非常难以 debug 的。给大家举个栗子,有一家公司,BAT 之一,他的推荐系统过去几年 85% 的效果提升来源是把之前有问题的数据给修复了,做对了。
推荐系统的场景思考
样本构造还需要考虑场景的问题,比如说我们会遇到一个问题,屏幕的大小是不一样的,同样展示 10 条新闻,我怎么知道用户有没有看到它。如果没有看到就不应该作为一条样本。这时候就有两种解决方案,第一种解决方案是把用户真正看到的纳入进来,因为前端是你设计的,所以你会知道哪些内容是用户的可见范围内。 当然这会让客户端变得更重一些。
第二种是一个比较简单的方法,把内容的位置作为一个特征。因为我们知道,同样是一屏幕展现 10 行内容,即便是一样的内容,用户也会选择一个他舒服的位置去点,这个可能是偏中上的位置。所以当新闻在第三个位置被点击的时候,这可能是一个容易被点击的位置,但不一定代表这个新闻比其他新闻要好。那我们怎么办 我们就要通过某些手段,把这些偏置吸收掉,所以我们会把位置、屏幕大小等作为特征,通过特征工程的方法来吸收这个偏差,变成无偏的模型。
这时候有些人可能会问,这不是穿越吗 因为在给出预测的时候,是不知道内容最终的位置信息的。但这相当于把偏差的锅由位置来背了,这是机器学习推荐系统中的一个策略。
刚才我们已经构造好特征了,现在给大家讲怎么建模。大家可能会认为,前面的部分是快的,真正做机器学习,做特征工程、模型调参等,这些是慢的。但是今天我们会看到,在成熟的工业界里面,其实前面要花的时间会多很多,后面的内容在成熟的工具下会变得简单。
在先知上完成推荐系统的建模流程
我们会从一个真实的案例出发,虽然我们做了很多的推荐系统的案例,但毕竟不能把客户的数据给大家看,所以我们用了一份公开的数据,这份数据和我们之前讲的场景是相似的。这份数据来自于 Kaggle,叫做 Criteo 点击率预估比赛。
数据预览
首先我们看数据的样子,第 1 列 col_1 代表的是广告有没有被点击,1 代表被点击,0 代表没有被点击。然后我们看第 2 列到第 14 列,都是数值型的特征,因为这份数据已经被匿名化了,所以我们可能也不知道这些数值代表什么意思,也许是这个用户的 PV,或者标签的权重,不过我们也不需要知道。然后看第 15 列到 40 多列,这些都是离散的特征,这些特征都做了哈希化,都做了匿名处理。
这个数据有 3000 万行 40 多列,按照我们传统的做法,进行特征工程以及 one-hot 编码后,会有 4000 多万个特征。真实的业务数据中,训练数据体积会更大,往往达到上亿,同时原始特征数量会达到上百,因为为了好的个性化效果,我们会使用诸如 GPS 坐标、手机型号、ip 地址、最近浏览内容等等精细化的特征,并进行非常极致的特征工程,这样的模型在特征工程之后的特征数会达到数亿甚至几百亿。这样规模的机器学习训练,挑战的不仅是算法,更是如何在成本可承受的计算资源上进行训练和实时预估。
开发这样一个规模的可以并行运行的系统的挑战更加大,即使 BAT 这样的大公司也会养一个百人的团队,只为了做好机器学习模型训练和预估的工程实现。下面我们会看到利用第四范式的先知平台去做这件事情,会大大降低我们开发和运维一个在线机器学习系统的成本,让我们更加聚焦在业务本身。下面我们会看到在先知平台上对这份数据的建模会非常简单。
数据拆分
首先我们把这份数据拆成了训练集和测试集,以 9:1 的方式。当然这可能是不太合适的拆分方法,因为真正训练一个机器学习模型,它的拆分是按照时间排序后再拆的,就是用前面的时间来训练模型,然后用这个模型来预测点击。这样训练和测试在时间上是正交的,那么模型如果在这种实验设计下有好的效果,这个效果就会有时间平移性,上线后就会有好的效果。当然,为什么我们在这里没有使用按照时间排序拆分的方式,是因为参考了一篇论文的做法(https://arxiv.org/abs/1703.04247),这样同样的做法结果可比。在真实的业务中,不建议大家按照这种方式来处理数据。
特征工程、模型选择和评估
然后就是做特征工程了,一般来说大家可能觉得很难,但只需要告诉电脑哪个是 Label,其他的直接默认配置就好了。但天下没有免费的午餐,特征简单处理,我们要有更强的模型,所以我们用了线性分型分类器。
一般来说,推荐系统中会有离散特征和连续特征,当我们用逻辑回归这种宽的离散线性模型的时候,我就会遇到一个很大的问题,就是我需要对特征进行分段,这样才能学到连续特征的非线性结构。比如说用户年龄对点击率的影响,它并不是线性上升的,它可能会上升到 30 岁,然后就下降了,甚至有更复杂的模式,这些是非线性的特性。所以我们可以用机器对连续的特征做自动搜索分段,这样的预处理效率就大大提高了。线性分形分类器就是做的这样的事情,基本上我花了不到 5 分钟,就跑出了 0.796 的 AUC。
AUC 代表了一个模型对样本的排序能力。如果这是一个完美的排序,点击率高的 100% 排到了点击率低的前面,那 AUC 就是 1。如果是随机瞎排,那 AUC 就是 0.5。所以 AUC 越接近于 1,就代表了整体的排序能力越强。现在的 AUC 是 0.796,比论文的 0.801 稍微差一点,我只做了这么点就到了这个成绩,我觉得还是可以的。
当然这里进一步说一下,评估上线的时候,不能只是看 AUC。例如说,我要看的是 User AUC,或者每一刷的 AUC,因为模型的排序能力,一部分来自于对每个用户个性化的识别能力,另一部分来自于对用户本身的识别能力。比如说有些用户就是什么都爱点,什么都点,而有些用户基本只看标题不点开。我们把这样的人分开,对个性化推荐是有帮助的。所以我最终应该看的是,在每一个用户身上的 AUC,甚至是每一次展现的 AUC,当然先知也提供了工具,就不多赘述了。特征组合
但我还是想让这个模型更好,怎么办呢 在做推荐系统的时候,特征工程最重要的处理方法是特征组合。比如说有两个特征,一个是性别,另一个是新闻的色情等级。我们可能会注意到,男同志比较喜欢火爆的新闻,女同志可能正好相反。我把色情等级作为一个特征,可能两边的喜好不同导致最终这个特征整体对点击率的影响和一个随机数一样,它就不是好的特征,没有预测能力。
但如果我把这些特征组合起来,我就对这些空间有更细致的分割能力,我就会做出很好的效果。性别、用户 ID 和新闻色情等级组合起来,是新闻推荐非常有用的特征。进一步说,假如我们有 100 个特征,那么特征两两组合的空间,就是 一万个,这是个很大的工作量。
传统的手段是,通过业务经验和实验去筛选好的组合。但是工具可以帮我们做得更快更好,先知的 FeatureGo 功能,可以通过一系列的配置和优化目标设定,自动搜索出好的特征组合,大大节省我们的工作时间,让人的工作由机器来完成。然后我就跑了 FeatureGo,找到了 18 和 28,33 和 34 这些组合特征就不错。而如果计算资源足够,那你还可以搜索 3 阶的特征,这是由人来做非常难的事情。
在用机器完成自动特征组合后,调参其实也可以自动化的。我们知道机器学习就像炼丹,炼丹就是调参,调参就是在参数空间里,根据我们的经验去搜索一下,看什么参数是好的。我们也有好的工具,可以自动搜索到好的参数。当把这些设定好之后,我就去睡觉了。第二天醒来后发现,这个模型的 AUC 达到了 0.802,已经超过了论文的效果。由于这篇论文还是今年比较有名的论文,我还是很满意的。
模型上线
设计好模型后,一般是用 Restful API 的模式服务的。比方说根据这个 API,就可以给不同的内容进行打分,打分后根据倒排后的结果,响应推荐的内容。
推荐系统相关组件
当然,一个完整的推荐系统,不仅仅需要机器学习排序,还需要相应的组件。比方说我要有物料库,把商品的特征维护起来,这需要一个高性能的缓存和数据库,能够增删查改,能够进行特征的初步生成。我还需要一个日志系统,日志系统通过唯一性的标识,把实时的请求和后续的反馈 Label 记录并拼接起来。我还需要一个自学习的系统,机器学习的模型每天都是要更新的,如果拿 7 天前学到的模型去推今天的商品,那岂不是用前朝的剑斩本朝的官,效果一定不好,所以我们还需要自学习,或者是增量的在线学习,来保证模型捕捉到最新的用户偏好和市场情况。
然后我们还需要后续的预测,比方说我的内容有好几十万,我并不是拿好几十万给机器学习去预估,我会用启发式的方法,比如说 CF、热度、Tag 匹配等召回策略先生成候选集,然后才进入到机器学习排序。召回策略同样使得我们有更大的能力去影响机器学习排序的结果,比如我们可以过滤掉一些我们明知道不好的内容或者增加我们认为好的内容的权重或出现比例。在机器学习排序后,我们也需要对结果进行去重、多样化和随机化,最后才做成一个好的推荐系统。
今天和大家介绍的是,我们如何利用机器学习去搭建一个推荐系统的排序环节。总结起来是这么几个点:第一个是如何使用机器学习来剖析一个问题,我们用了推荐系统的例子。第二个是我们如何构造一个推荐系统的样本、数据并进行建模,当我们有一个非常好的机器学习工具的时候,我们可以把精力聚焦在业务上,在怎么找到好的数据上,以及在怎么定义好的目标和规划上。第三我们描述了机器学习系统是如何和其他系统发挥作用的,机器学习就像发动机,汽车当然需要发动机,但只有发动机车是跑不起来的,你还需要周边的配件,这是系统化的工程。