使用 ElasticSearch 实现高质量的推荐系统(第一部分)
- 发表于
- 周边
你是否知道技术书籍可以有剧透吗?剧透提醒:合上《相关搜索》(《Relevant Search》)这本书,我们可以得出这样的结论:搜索引擎技术是实现推荐的重要平台。我们认为推荐和搜索是一体两面的。两者都以“相关度”为基础为用户提供了内容的排序。唯一的不通电在于是否提供了对关键词的查询。
我们认为搜索引擎提供了一个实现易于调整的推荐的理想平台。事实上,我将在不久后的 Lucene演化史中讲述这一观点(参考我的共同执笔者在Elastic{On}上的演说)。
聚合: 找出喜欢这篇电影的人还喜欢别的什么电影
假设你正在开发一个电影网站,而且已经有一批用户了,你需要知道推荐些什么电影给他们。一个常见的做法就是把每个用户作为一个文档进行索引,例如下列代码所示(movies_liked
这里是被忽略的关键字分析字段)
PUT recs/user/1 {"movies_liked": ["Forrest Gump", "Terminator", "Rambo", "Rocky", "Good Will Hunting"]} PUT recs/user/2 {"movies_liked": ["Forrest Gump", "Terminator", "Rocky IV", "Rocky", "Rocky II", "Predator"]} PUT recs/user/3 {"movies_liked": ["Forrest Gump", "The Thin Red Line", "Good Will Hunting", "Rocky II", "Predator", "Batman"]} PUT recs/user/4 {"movies_liked": ["Forrest Gump", "Something about Mary", "Sixteen Candles"]}
我们想为喜欢电影《终结者》的用户提供推荐。换句话说,我们想知道喜欢《终结者》的用户也喜欢什么电影。这是一个十分基础的“购物篮分析”——这是推荐系统的基本组成构件。这个名称是来自检查用户的购物篮,并发现有意义的统计关系这一想法。广为人知的是,连锁杂货商们知道购买尿布的客户经常也会购买啤酒。类似的结论对于用户和商家可能会十分有价值。本文中,我们着重讨论根据《终结者》找出和《终结者》的观影者们在统计意义上有兴趣的电影。
我们应该如何根据保存了用户历史数据的文档来得出《终结者》的观影者可能喜爱的电影?你的第一感觉可能是做一个项上的聚合操作。项聚合是一个传统的方面。该操作简单统计了当前的查询结果中字段包含的项。在典型的应用中,项可能是电影的ID,但我们在本文中使用的是标题。我们搜索“终结者”并在“movies_liked”字段上做聚合,获得经常与“终结者”同时出现的电影的数量,像这样:
仅关注于聚合操作的结果最终的结果是如下的分类细目:
"movies_like_terminator": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [{ "key": "Forrest Gump", "doc_count": 2 }, { "key": "Rocky", "doc_count": 2 }, { "key": "Terminator", "doc_count": 2 }, { "key": "Good Will Hunting", "doc_count": 1 } }
如果扫描过上述的用户记录,你可以了解到喜欢《终结者》的用户中有两个喜欢《阿甘正传》的用户,两个喜欢《洛奇》的用户等等。此外,如果我们对你最喜欢的电影重复这样的操作(对《终结者》或《铁血战士》或《情到深处》和所有你喜欢的电影做搜索),然后你会得到一个协同过滤过程:发起一个搜索,找到那些和你喜欢同样电影的用户。然后你可以检查你还未看过的电影与你喜欢的电影频繁地同时出现的次数。
但是这种简单计数方法是否是一个好的方法呢?我们在《相关搜索》和购物车分析的博客文章中进行了讨论,认为用简单统计同时出现的次数的方法实现推荐是一种很差的方法。简单计数法优先考虑的是全局普遍的关系,而不是有意义的关系。例如在这个例子中,每个人都喜欢《阿甘正传》。如果我们用这种方法做推荐,那么每个用户都被推荐了《阿甘正传》。我们把这种问题称为“奥普拉读书俱乐部”问题(“Oprah Book Club” problem)——倾向于普遍但不是特别有用的同现关系。
这里更有趣的是关于电影《终结者》的相关计数结果。以《洛基》为例。每个喜欢《洛基》的用户都喜欢《终结者》——他们恰好有着100%的重合率!换句话说,《洛基》在有条件时出现的的比率为100%,在全局中(无条件时)出现的比率仅为50%。这看起来是对喜欢《终结者》的用户的一个绝好的推荐。
用显著项度量有意义的用户-项关联
要解决简单计数法带来的问题,显著项聚合是一个合适的方法。这种聚合方法度量了我们所需要的这种在统计学意义上重要,并且表示有意义的推荐的关系。它的作用不是计算简单的计数,而是计算项在当前结果中相比于在背景语料库中(译注:原文为background corups,疑是background corpus的笔误)的统计显著性。现在,我们来探讨一下显著项能干什么。
在我们深入钻研之前,我们先把这种方法拿出来溜溜:
POST recs / user / _search { "query": { "match": { "movies_liked": "Terminator" } }, "aggregations": { "movies_like_terminator": { "significant_terms": { "field": "movies_liked", "min_doc_count": 1 } } } }
上面(与简单计数法)唯一的的不同是我们用到了significant_term
运算。为了让这个操作在我们这个极小规模的数据集上能获得较好的结果,我们将min_doc_count
设为1。
事实上,这一查询解决了以上的问题,我们可以发现《阿甘正传》在推荐中没有出现,得到的推荐显得更合适:
"buckets": [{ "key": "Rocky", "doc_count": 2, "score": 1, "bg_count": 2 }, { "key": "Terminator", "doc_count": 2, "score": 1, "bg_count": 2 }, { "key": "Rambo", "doc_count": 1, "score": 0.5, "bg_count": 1 }, { "key": "Rocky IV", "doc_count": 1, "score": 0.5, "bg_count": 1 }]
深入研讨JLH显著评估
就是这样了,是吗?没有必要继续深入?并不完全是这样:你需要理解评估方法在你的数据上是如何工作的。你需要能够深入了解所使用的评估方法才能建立起真正优秀的推荐系统。如何根据显著项得到上面的排名?正如我们在《购物篮分析》一文中所见,不同形式的评估方法有它们自己的优缺点。选择错误的方法可能在推荐的质量方面造成灾难性的后果。
我不准备在这篇文章中分解每一种形式的重要项评分(significant terms scoring)(能选的其他聚合还有很多),但是让我们深入探讨一下教我们如何思考这些问题的默认方法。这个默认方法叫做JLH。它将两个值相乘
(foregroundPercentage / backgroundPercentage) * (foregroundPercentage - backgroundPercentage)
这里的“前景(foreground)”意味着这个项出现在当前搜索结果中的百分比(就像我们上面检索的项(终结者)那样,用户检索某个项)。例如,上面的《第一滴血( Rambo)》在《终结者》电影的作为前景下出现的百分比是100%。背景(background)百分比是在整个集合中的全局百分比。《第一滴血( Rambo)》的这个百分比是50%。
JLH对于极普遍项的评分
你怎样根据你的使用案例来评估这个评分的适当性呢?让我们假设几个场景。这些虚构的场景可以让我们围绕它们对评分机制做分解。然后你可以认真思考对于你的真实世界中的数据,这些场景是否会发生。当我们持续去使用电影时,我其实只是在应付虚构的场景:不要把这误解为是对于JLH针对像Netflix或者Movielens这样的真实数据集下的评分适当性的一个分析。
在第一个场景,我们已经讨论过,某部电影是每个人都喜欢的。在我们的数据集里,这部电影就是《阿甘正传》。99.999%的用户喜欢《阿甘正传》。
事实上,这样一部极受欢迎的电影的JLH评分并不是太高。《阿甘正传》的(foreground/Background)项评分是
100 / 99.999,几乎不 > 1。同样, (foreground - background)项评分是(100 - 99.999),这个分数几乎不小于1。你在下面就会看到,这对于JLH评分来说是一个相当低的数字。
但这是一个公平的场景吗?我认为在大多数领域,一个项在用户记录中出现的比例如此接近于100%是很少见的。更可能的情况是,一个小得多的的数字,但是这个数字可以衡量出对某部电影的偏爱。例如,当然可能每个人都喜欢《阿甘正传》,但是大多数检测“喜欢”的方法,如明确的收视率或观看/点击数量,不会展现出每个人都与《阿甘正传》有相互作用的结果。当然,我喜欢《阿甘正传》,但我上一次观看它可能已经是10年前的事情了。这有点像一个被动的偏好。我已经看过它,当我在Netflix上再看到它的时候,我不太可能会感到兴奋。
一个更可能的场景是,最热门的电影或者秀是某些用户,比方说,20%的用户喜欢的。JLH评分在这种场景下如何呢?如果这部电影,比如说《心灵捕手》,被这个集合里(译者注:20%的用户喜欢的某部电影的用户集合)100%的用户喜欢,那么这个最高分会是(100 / 20) * (100 - 20),也就是 5 * 80 即 400。比起不切实际的《阿甘正传》场景的得分要高得多。
受欢迎程度平均的项
大多数的电影,喜欢的用户的百分比在个位数。让我们看看一个受欢迎程度平均的电影:《洛奇4》,这部电影被4%的用户喜欢。
《洛奇4》是JLH评分如何呢?好吧,对电影重复JLH计算,如果100%的前景用户喜欢《洛奇》,我们就得到一个分数为(100 / 4) * (100 - 4) 即 25 * 96 = 2400。哇,这明显比被推荐的《心灵捕手》更值得推荐的可能性大多了!
更加“正常的”偏差
热门的《心灵捕手》得到的最好分数是400,和受欢迎程度平均的《洛奇4》得到2400的分数,看起来,LJH对于推荐来说很糟糕,是吗?不完全是。让我们考虑一下这些“最佳案例”场景的可能性。对于喜欢某部电影的用户来说,100%会喜欢另一部电影的合理性有多大?对某项的前景偏好与背景偏好出现如此巨大的偏差的可能性有多大?
好吧,这很大程度上取决于你的数据中的项偏好的相对分布。某人可以这样反驳,例如,如果你喜欢《洛奇3》,你就很可能会喜欢《洛奇4》。也许这个偏好会100%重叠。
然而,如果你的数据倾向于在背景和前景数据集之间的变化不大,也许这没什么大不了的。例如,让我们考虑一下前景的期望值是以背景百分比为中心正态分布的这样的情况。假设背景和前景之间的均匀变动约为75%(《洛奇4》可能是7+/-3%;《心灵捕手》是20 +/- 15%)。在这种可能更加具有现实意义的场景下,我们可能会得到这样的得分:
- 《洛奇4》的最高得分:(7/4) * (7-4) = 5.25
- 《心灵捕手》的最高得分:(35 / 20)*(35 - 20) = 26.25
换句话说,一个平稳的比例转移使受欢迎的电影比一个中等受欢迎的电影得到更高的分数。第一个项(除法运算)在两部电影之间是等价的。另一个项(减法运算)却将基于比例转移潜在地为受欢迎的电影提供一个更高的倍数。
什么会这样呢? 正如JLH的创造者,Mark Harwood,指出的一样, 这正式是JLH设计的目的。JLH反映了我们在许多推荐数据集上看到过的模式:热门的项不会因为受欢迎范围的变化而急剧变化。即使是在其他看似相关的项的范围内,它们也不会偏离背景的受欢迎程度太远。我们在《购物篮分析》一文中提到过每个人都在购物篮中加入鸡蛋。如果几乎每个人都买鸡蛋, 购买煎蛋配料范围下的鸡蛋购买也只会比整体的鸡蛋购买比例稍微上升一点点。但我们仍然喜欢给煎蛋厨师做出合理的推荐:所以即使鸡蛋的受欢迎程度的只是轻微上涨,也应该被重视。
考虑到这一点,也许《心灵捕手》只从背景偏离+/- 25%是更典型的。因此,就是这样,《心灵捕手》的最高得分会和没那么受欢迎的《洛奇4》差不多:
(25 / 20) * (25 - 20) = 6.25
换句话说,如果我们调查,例如《理智与情感》,《心灵捕手》在热门度得分上提高了25%,我们假设的《洛奇4》提高了75%,两者分数提高的程度是相同的。
对使用JLH重要性评分做推荐的最后的思考
基于我们看到的东西,JLH假设没那么受欢迎的项的前景概率相比于背景百分比更有可能提高。
- 典型的前景百分比趋向于和背景百分比相当进阶,因为你不会突然看到两个项的喜欢的用户100%重叠。
- 受欢迎项的前景百分比比起没那么受欢迎的前景百分比偏离背景百分比更少。
最重要的结论是,这是由你的数据模式决定的。你需要评估出你的数据的典型模式。当你着眼于典型的偏好,前景和背景百分比之间的关系是什么呢?这才是你为了构建一个优秀的推荐系统需要致力于的最困难,最领域相关的工作。
但这里还有一个我们还没考虑到的极端糟糕的例子。如果极少见的电影重叠了,该怎么办呢?我们在《购物篮分析》一文中看到过, 这是Jaccard相似度最大的弱点。所以让我们考虑一个这样的场景,它涉及到两个电影,这两个电影很都很少人喜欢,而喜欢它们的是同一个用户:
这两个电影,《蠢电影》和《怪诞艺术电影》都出现了两次。《蠢电影》和《怪诞艺术电影》碰巧都被同一个用户喜欢,这个用户看起来喜欢晦涩难懂的和/或糟糕的电影:
PUT recs / user / 2124 { "movies_liked": ["Weird Art Film", "Forrest Gump"] } PUT recs / user / 2125 { "movies_liked": ["Weird Art Film", "Dumb Movie"] } PUT recs / user / 2126 { "movies_liked": ["Dumb Movie", "Rambo"] }
这两个电影的背景百分比都非常低。也许是0.001%。当给喜欢《怪诞艺术电影》的人推荐电影时,《蠢电影》的前景百分比变成了50%。它突然变得很常见!这个分数如何?
(50 / 0.001) * (50 * 0.001) = 2499950.0
天啊!这是一个很高的分数。
简单地说,我们只有极少数的一些用户喜欢这样的电影——可能不足以对重要性做出统计学上的可靠的结论。如果你考虑了前景和背景百分比之间的距离,你就可以理解到这一点。 如果数据显示+/- 25%,我们可以预见的是这部稀有的电影的前景百分比在0.001以下,到刚好超过0.001。绝对不是50。这个值可能会颠覆我们在我们的数据中看到的典型模式。
所以除了上面的建议,这里还有另一个评估任何形式的意义评分时的重要的标准:
找一个合理的“喜欢”的最低数值边界,这个数值以上的电影才能够反映出前景和背景关系之间的典型统计学意义。如果前景百分是以背景百分比为中心+/- 3%是更典型的,然而一个罕见的电影却展现了疯狂的偏差的时候,考虑这是一个离群值,并且设置一个合理的受欢迎最小值。
(值得注意的是,一个较高的最低文档频率边界对于性能来说总是一个很好的想法,能够避免你在出现的每个虚假的(译者注:无意义的)项上做消耗资源的运算。)
最大的结论是,任何衡量统计学意义的方法取决于你的数据。典型的关系是什么?是什么看起来疯狂地颠覆了预期结果?
原文连接
的情况下转载,若非则不得使用我方内容。