大规模的协作过滤,预测Okcupid谁将喜欢您*,带JAX **188bet金宝搏官网

佛罗里达州杰克逊维尔的照片。没有关系。

*您的可爱不保证,但我们会尝试

** JAX是实验性的,不是官方支持的谷歌产品。版本锁定您的代码与容器化在可能的地方。

术语简介

在Ok188bet金宝搏官网Cupid上,当你告诉我们你喜欢谁,不喜欢谁时,我们会在后台把它记录为“投票”。接下来,我们将分别谈论投票人和你不喜欢(不喜欢)的人。每一个“喜欢”或“通过”都是一个“投票”。

我们录制了很多选票!

动机

(如果您已经了解协作过滤和SVD近似,请随意跳到实现部分)

我们想要知道,根据你过去的经历和喜好,谁是你还没见过的你喜欢的人?如果我们有了这个,我们可以利用它向你展示你更可能喜欢的人,这意味着你喜欢他们,他们得到一个赞,也许他们喜欢你,希望从那里一切都很好!

理解我们将如何处理这一问题的第一步是将所有这些投票放入一个“交互矩阵”。本质上,这个矩阵的行是投票者,列是投票者。当一个投票者喜欢一个votee时,我们在对应于(voter, votee)对的坐标上放一个1,如果投票者通过了一个votee,我们就放一个0。

假设我们有投票人A B和C,我们记录了A对B和C的投票,B对A的投票,C对B的投票。

我们的互动矩阵现在看起来像这样,选民作为列和投票是列的行:

忽略奇怪的格式

简单,现在我们的任务是尝试弄清楚如何在这里“填写”问号,例如如何“填写”问号。C票票吗?

那么,我们如何这样做?好吧,我们可以尝试根据他们喜欢和通过的人来寻找类似的选民。一旦我们这样做了,我们就可以将这些类似的选民的投票带到他们的选民失踪的投票中。

这就是所谓的基于内存的协同过滤,虽然理论上很好,但在计算上并不是很可扩展。当我们有数百万的投票人,并且每个星期有上亿的投票人投票时,当用户需要一个快速的推荐列表时,这是很难做到的!

学习陈述代替

那么,让我们用另一种方法,来近似这些相似点。

为此,我们将用一个向量表示每个投票者和被投票者。就我们的目的而言,vector只是一个固定长度的数字数组。我们想从这些向量中得到的是,当我们取它们的点积(即,将每个数字与另一个向量中相应的数字相乘,取总和)时,它对应于我们观察到的(或将观察到的)结果,当投票人与被投票人相互作用时。

所以我们想要。.这里有个重要的提示,你会注意到,我们想.这实际上并没有工作(Dot产品是换向的),因此我们将为每个用户录制两个向量 - 选票向量()和选民矢量().

有了这个,我们就

抱歉,B, A没那么喜欢你。

好的,所以现在我们建立了我们想要的向量。vectors有呃,他们的数字。但是,我们如何弄清楚哪个数字?我们可以填补这么多数字!

好吧,我们可以从我们观察到的选票中了解数字!我们将使用梯度下降,它可以描述如下:

  • 随机初始化每个人的voter和votee向量
  • 对于一些“epoch”(通过观察到的投票),我们将通过我们观察到的每个投票
  • *计算选民的选民矢量和选票的选票矢量之间的点产品
  • *计算点积和实际结果之间的差值(误差)
  • *取坡度的错误
  • 这告诉我们矢量中每个数字对误差的贡献有多大
  • **我们可以使用这个作为一个“方向”来移动矢量
  • *反向移动,从矢量减去梯度
  • **这将减少(下降)错误
  • **值得注意的是,我们不需要逐个投票,而是可以一次做一堆批处理这些计算。
  • 返回我们为每个选民和投票人学习的向量。

(作者注:Medium没有嵌套列表,而最初的平台有,抱歉)

一旦完成,我们能对这些向量做什么?现在,当我们想知道一个投票人是否会喜欢一个投票人,我们可以简单地取他们各自向量的点积,然后找出模型认为会发生什么!

这是我们可以卸载的非常快速的操作118金宝搏app下载 提供建议,它不会变得更慢,我们有更多的用户!

但是我们为什么要做这个过程呢?我跳过了很多过程,但本质上,整个过程是从近似奇异值分解(SVD)得到的,以重构投票人-投票人交互矩阵。如果你有兴趣读更多这部分,我建议你读开创性的Netflix大奖后这就是很多研究的起源。

为了简单起见,我们省略了偏差,正则化,变换等我们在实践中经常用到的东西。

规模问题

虽然当我们想要确定一个用户是否会喜欢另一个用户时,这要快得多,但训练过程仍然需要我们对每个投票进行循环和梯度更新。虽然这些梯度更新相对便宜,他们加起来与数以百万计的投票训练!

实现

所以,我们得尽快完成。为了让每个人的建议保持新鲜,我们希望每天都计算(并重新计算)这些向量。此外,我们不想仅仅被这个简单的操作所困——我们希望能够尝试和研究使用不同的错误函数、训练方法、优化器等来更新向量的不同方法。

我们已经遇到了一些实现这种基本算法的库。下面,我们将介绍我们最喜欢的方法,然后讨论我们为什么使用JAX构建自己的方法。

基线-惊喜

当我们开始这个项目时,我们使用了流行的惊喜尼古拉斯拥抱图书馆。虽然惊喜提供了许多推荐算法,但我们专门对其实施感兴趣SVD..尽管它是一个Python库,但它使用奇特的c级Python扩展实现了所有关键部分。这使得它比纯Python快得多。

然而,如果我们提出一些复杂的新误差函数,我们就必须手动计算相应的梯度函数。除了纯粹的懒惰之外,我们希望避免这种情况,因为这是一个可能引入许多难以诊断bug和难以维护代码的过程。

有很多框架是用来自动计算渐变的(例如TensorFlow, PyTorch),但我们能在保持Surprise效率的同时获得这种灵活性吗?

什么是jax?不是在佛罗里达州?

jax.提供了一个简单的Python接口来表示(使用类似numpy的API)我们想要的操作,同时还允许我们将这些函数转换为它们的渐变返回版本!

不仅如此,它还提供了许多使用即时编译和向量化(just - in - time)优化代码的方法,以及其他一些整洁的特性。如果你有兴趣,我建议你读读他们的文件

我们来定义做点积的函数。很简单。

现在我们来定义误差函数。现在,我们将使用平方欧几里德距离在真实的结果和预测的结果之间。这通常被称为L2损耗。

现在,我们将在我们的这批选票上平均损失,我们正在训练使用函数变换来得到这批选票的最终损失函数。用于允许我们在输入的每一行上有效地运行函数。

这允许我们给出两个矩阵,每个形状(对于投票人和被投票人向量),以及向量的形状(为了真正的结果)函数。然后,这个函数将返回一个标量值,表示在预测结果时向量当前的平均“错误”程度。

那么,现在我们如何获得渐变?我们将使用它函数为:

这将产生一个函数,当给出与函数,返回对应于的两个梯度矩阵0)和此外,我们正在运行所有这些函数因此,一旦编译,我们就可以利用更快的执行!

那么,现在我们有了所有这些,让我们把我们的训练循环放在一起。

对于我们的数据,我们将以元组的形式进行投票:

这些索引(事先)被映射为将投票者和投票者与关联的行相对应

我们的训练循环是这样的:

我们称之为这种方法.那么,这是怎么做到的呢?

别理洗牌的事,那没能晋级

哎呀!即使是少量的投票,我们也花了更多的时间而不是惊喜!我们可以做得更好吗?

要走了快(er)

好吧,那显然不太顺利。如果我们回想一下,我们是否可以尝试使用JAX的numpy-like API来jit编译大量的索引和更新部分?那样会更快吗?

抱歉,太多了

好的,很酷。那么,几个音符,是怎么回事?为什么我们为此分配到的结果

jit编译JAX函数的一个关键限制是我们不能有任何就地操作.这意味着任何修改其输入的函数都不能工作。想了解更多原因,请查看他们的文件

这个版本,我们称之为,测量了?

我们还是走得太慢了。惊喜轻而易举地打败了我们!

我们还可以在jit编译的函数中填充什么?

我应该注意我们正在使用对于这些比较。这并不是因为这一定是一个好的批处理大小,而是因为这是Surprise允许的-所以我们想要确保我们的比较是公平的。

要走了快(美国东部时间)

现在我们正在复制(记住,没有就地改变)整个选民矩阵和选票矩阵进入JIT编译的函数,即使我们只是更新(最多)行!当我们有数百万的选民和选民时,这可能会变得昂贵。

但如果我们可以填充整个进入一个jit编译的功能?

哇,所以,这看起来很不同,但同时相似!这是谢谢的实验jax.API,它允许我们编写一个看起来有状态的循环,并通过JAX将其转换为一个就地(或纯)函数。循环中的所有可变状态都必须放在对象。

但一旦完成,我们可以使用

然而,一些事情变得复杂,问题的根源是在不断变化发展的。这意味着我们不能使用典型的切片操作符,比如-我们必须使用代替。另外,我们必须指定它是静态值,这意味着JAX将重新编译如果他们改变。所以,当调用我们现在必须计算我们的训练运行中的批次。简单渐变血统不是一个大的事情!

好吧,这是非常复杂的,我花了大概一个月的时间在键盘上摸索,但是这个方法(我们称之为) 比较?

其他的现在都不值得看了

这就是我所说的速度!事实上,我们看到,虽然我们已经获得了数千万的选票,但我们并没有像《惊喜》那样放慢脚步!我们甚至做梦也想不到能和早期版本的模型进行如此高度的比较!

利用JIT编译?

即使是在一个时代,这也是一个令人印象深刻的速度,当我们开始为多个时代训练时,我们应该看到一个更显著的差异编译,第一个后的后续调用应使用已编译的版本。

所以,与Surprise相比,我们应该看到JAX在每个epoch中花费的额外时间明显更少,对吧?让我们来看看在几个时代之后,每个时代比一个时代慢了多少倍。

好吧,不太。事实证明,相对于一个训练时期,JAX版本在这里以类似的速度速度向下速度令人惊讶,但是做得更好。然而,在绝对条件下,我们每欧时都会增加速度的数量级!

JAX Land Vs. Numpy Land

有一件事在整个过程中还没有说清楚不返回对象,而是对象。虽然这些在很大程度上是类似的API,但在分析JAX模型及其JIT编译的方法时,人们必须小心。

从根本上说,虽然它可能看起来像计算完成的时候(内部部分)被调用,你回来的结果实际上是期货。所以,如果我们想在Numpy Land中与它们一起工作或打印它们或任何有用的东西,我们必须“提取”它们。我们可以看到,如果我们衡量运行所需的时间几次在相同的矩阵上,看起来花费的时间并不会随着epoch的数量而增加!

然而,一旦我们提取这些,我们可以看到所花费的时间跳跃。因此,在分析JAX代码时要小心!你可以阅读更多关于异步调度的内容在这里

为什么不使用tensorflow或pytorch?

虽然TensorFlow和PyTorch对于很多梯度下降驱动的问题都很好,但在这个问题上,有一些关键部分他们处理得不好。

  • 因为我们需要为每一个投票人和每一个投票人训练一个向量,所以我们不能对我们的训练集进行采样,因此需要一种快速的方法来迭代它
  • TensorFlow和PyTorch都没有为此提供优化
  • 我们也对优化大量不同参数感兴趣
  • 神经网络每次都会优化相同的参数
  • 我们在这里处理每一步更新的少数参数,但从大矩阵

经过大量的研究和我们的尝试失败,我们确定了我们在Tensorflow或Pytorch中实现这种表现。然而,自从我们看那是一年以来,如果有人知道其他人可以自由地让我知道我有多错了!

不要在家里尝试

回到2019年12月,当Covid-19是'异常肺炎'而JAX版本为1.55时,我建立了这种方法,并得到了这些结果。

上周,当我在写这篇文章时,我意识到我有一些想要重新运行并进一步探索的结果,我发现了一个糟糕的结果。我的计时结果不能再重现了!事实证明,JAX库中发生了一些变化(我认为是这样),大大减慢了这种方法的速度。

看起来不太好

我们已经申请一张票但同时,如果您想再现这些结果,您可以使用以下版本

jax = = 0.1.55
jaxlib = = 0.1.37
numpy = = 1.17.5

结论和进一步的工作

虽然这绝对是一个改善我们的基线,这仍然是一个相当基本形式的协同过滤推荐——但这是迄今为止对我们都挺好的,让我们这些向量训练每一个选民,而在过去一周在整个OkCupid网站平均在3小时左右!188bet金宝搏官网

今年,我们利用这一点为所有用户提供了显著的改进建议。

但是,我们将何去何从?

替代训练循环和损失函数

有各种各样有趣的方法来排列训练集而不是一个接一个或随机排列。通常,像BPR和WARP这样的方法使用模型的当前性能来选择它没有得到正确的“硬”例子。这些方法可能是有用的,也是我们今后工作的方向。

神经协同过滤

NCF也是一个有希望的方法,允许我们利用哦那么赞扬深度学习来做一个非线性的版本。我们在等候主流pytrees支持然而,这是最近才增加的。

如果没有这种支持,管理NCF需要的所有不同状态将是一场噩梦。

更好的优化方法

我们将梯度转换成新向量的方法相当粗糙,而且有很多有前途的替代方法——但同样,最终依赖于或通过前面提到的pytree支持而变得非常容易。

最初出版118博宝娱乐官方网站 2021年2月2日。

188bet金宝搏官网Okcupid Tech Blog.

阅读来自工程团队的故事,每天连接数百万人

媒介是一个开放的平台,1.7亿读者可以在这里找到深刻和动态的思考。在这里,专家和未被发现的声音同样会深入任何话题的核心,并带来新的想法。学到更多

关注与你有关的作家、出版物和主题,你会在你的主页和收件箱中看到它们。探索

如果你有故事要讲,有知识要分享,有观点要提供,欢迎回家。发布你对任何话题的想法都是很容易和免费的。开一个博客

获取媒体应用程序

有一个“在App Store上下载”的按钮,点击它就会引导你进入iOS App Store
一个按钮,上面写着“Get it on,谷歌Play”,如果点击它,你就会进入谷歌Play商店