剑客
关注科技互联网

使用Elasticsearch实现推荐系统

聚合:找出喜欢这部电影的人们同时还喜欢哪些电影

假设你运营了一个电影网站,你有很多用户,并且想知道如何推荐给这些用户他们喜欢的电影。一个办法是,把每个用户作为一个文档创建索引,如下所示(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"]}

我们想给喜欢《终结者》的用户进行推荐。也就说,我们需要知道喜欢《终结者》这部电影的用户还喜欢哪些电影。这是非常常见的“购物篮分析” – 推荐系统的基础组件。“购物篮分析”来源于分析用户的购物篮并且统计出用户感兴趣物品间的关系。研究案例表明,通常在杂货铺购买尿片的人同时会选择购买啤酒。深入洞察此类行为将有助于提升用户和商家的价值。本文我们聚焦在《终结者》并找出对这部电影可能感兴趣的潜在用户。

使用Elasticsearch实现推荐系统

利用那些存储了用户观影历史的ElasticSearch文档,我们如何找出看过《终结者》的观众喜欢什么呢?立刻能想到的办法是进行“项”的聚合。项聚合可以得出当前搜索结果中特定字段所包含的项的数量。在某些应用中项可能会是电影的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
    }
}

通过上述的用户记录,你可以了解到喜欢《终结者》的用户中有两个人同时喜欢《阿甘正传》,两个人同时喜欢《洛奇》。此外,如果我们对你最喜欢的电影重复这样的操作(搜索《终结者》或者《铁血战士》或者《情到深处》等你喜欢的电影),然后你会得到一些协同过滤:发起一个搜索,找到那些和你喜欢同样电影的用户。然后你可以比较下还未看过的电影和你喜欢的电影同时出现的次数。

但是这种简单计数的方法是否足够好呢?我们在其它文章中进行了讨论,认为用简单统计同时出现的次数的方法实现推荐系统是一种很差的方法。简单计数法优先考虑的是全局的普遍关系,而不是有意义的关系。例如在这个例子中,每个人都喜欢《阿甘正传》。如果我们用这种方法做推荐,那么每个用户都被推荐了《阿甘正传》。我们把这种问题称为“奥普拉读书俱乐部”问题,这些关系普遍存在但不是特别有用。

这里更有趣的是关于电影《终结者》的相关计数结果。以《洛奇》为例。每个喜欢《洛奇》的用户都喜欢《终结者》,竟然有100%的重合率!换句话说,《洛奇》在有条件时(喜欢看《终结者》)出现的的比率为100%,在全局中(无条件时)出现的比率仅为50%(有2人喜欢《洛奇》,共有4人)。从这个结果来看,《洛奇》非常适合推荐给喜欢《终结者》的用户的。

使用显著项度量人和物之间的关系

要解决简单计数法带来的问题,利用显著项来聚合是一个更好的方法。这种聚合方法度量了我们所需要的这种在统计学意义上更重要、更有意义的推荐的关系。它的作用不是计算简单的项的计数,而是计算项在当前结果中相比于在背景语料库中的统计显著性。在《购物篮筐分析》一文中,我们讨论了正反两种评价显著性的不同方法。现在,我们来探讨一下显著项能干什么。 在我们深入之前,我们先把这种方法拿出来看看:

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(项关联的文档数大于等于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显著评分

到此为止了吗?我们还需继续研究:需要理解评分方法在你的数据上是如何工作的。你需要能够深入了解所使用的评分方法才能建立起真正优秀的推荐系统。如何根据显著项得到上面的排名?正如我们在《购物篮分析》一文中所见,不同形式的计分方法有它们自己的优缺点。选择错误的方法可能在推荐的质量方面造成严重的后果。

我不打算剖析文中出现的所有显著项(有很多项),但我们还是要深入了解这种方法来教自己如何思考这些问题。这种方法被称为JLH。评分公式是:

(foregroundPercentage / backgroundPercentage) * (foregroundPercentage - backgroundPercentage)

“Foreground”的意思是该项在当前搜索结果中出现的百分比(检索结果中用户喜欢我们的电影名称百分比)。 例如,《第一滴血》的“Foreground”在《终结者》搜索结果中占比是100%。 “Background” 是整个集合的全局百分比。例如,《第一滴血》的是50%。

JLH对普通物品的评分

你将如何评价某一个评分体系是否适合你的用例?让我们想象几个场景,考虑不同场景下的得分。然后,可以批判地思考他们在现实的数据中如何展示。我们继续使用电影为例,这些数据都是假设的:分析JLH评分所用的并不是Netflix或Movielens网站上的真正数据。

第一个场景,我们已经讨论过的是每个人都喜欢的电影。在我们的数据集中,这是《阿甘正传》。99.999%的用户喜欢《阿甘正传》。

事实上,这种非常受欢迎的电影对JLH评分不太好。使用(Foreground / Background),《阿甘正传》的评分是100 / 99.999,几乎为1。同样,(Foreground – Background)=(100 – 99.999),几乎为0。 正如你将看到的,这是一个相当低的JLH评分。

这样公平吗?我认为,在大多数领域里,一个物品被接近100%的用户喜欢是少见的。更有可能的是,很少有人对电影具有可衡量的偏好。 例如,或许每个人都喜欢《阿甘正传》,但大多数的“喜欢”是评级或点击(并不实际观看),不是100%的用户与《阿甘正传》有交集。 当然,我喜欢《阿甘正传》,但上次我看到它大概是10年前。 这是一个被动的偏好。 当我看到它出现在Netflix上时,我不太可能会兴奋。

现实中更常见,最流行的电影或节目是有20%的用户喜欢的东西。那么JLH的得分如何呢?比如《心灵捕手》,如果这组用户中的100%都喜欢,那么最高得分将是(100/20)*(100-20)= 5 * 80 = 400。比《阿甘正传》要高很多。

平均声望的物品

大多数影片会显示喜欢此影片用户的百分比。让我们来看一个平均电影:《洛奇IV》,有4%的用户喜欢。

《洛奇IV》使用JLH评分公平吗?如果“Foreground”的用户中100%喜欢《洛奇IV》,我们得到(100/4)*(100 – 4)或25 * 96 = 2400。明显超过被推荐的《心灵捕手》!

更多“正常”偏差

《心灵捕手》非常受欢迎得分400,《洛奇IV》受欢迎程度一般得分2400。似乎JLH评分方式对于推荐系统来说有些糟糕?让我们考虑这些“最佳情况”场景的可能性。 100%的用户喜欢一个电影的同时又喜欢另一个电影合理吗?喜欢电影的”Foreground”概率偏离“Background”的可能性有多大?

这很大程度上取决于数据中物品偏好的相对分布。例如,人们可以说,如果你喜欢《洛奇III》,你很可能会喜欢《洛奇IV》,也许接近100%重叠。

如果你的数据集“Background”和“Foreground”的差距不大,也许这不是一个大事。 例如,让我们考虑“Foreground”的值基于以“Background”百分比为中心的正态分布的情况。让我们考虑“Background”和“Foreground”之间的恒定变化是大约75%的变化(《洛奇IV》大概是7 +/- 3%; 《心灵捕手》大概20 +/- 15%)。 在这些也许更现实的情况下,我们将期望得分:

《洛奇IV》最佳得分:(7/4)*(7-4)= 5.25

《心灵捕手》最佳分数:(35/20)*(35 – 20)= 26.25

第一项(除法)中两部电影评分接近。另一项(减法)拉开了两部电影的差距。

为什么会这样?作为JLH的创建者,Mark Harwood指出,这正是JLH设计的目的。JLH反映了许多推荐系统数据集中看到的模式:受欢迎的物品受欢迎程度不会发生太大变化。如果几乎每个人都买鸡蛋,那么分析煎蛋商品对鸡蛋销量影响就会非常小,即便如此,我们仍然想要这些煎蛋厨师一些建议:即使只能轻微增加鸡蛋的销量也很重要。

作者信息

本文系LeapCloud团队_云服务研发成员:秦鹏【原创】

现任LeapCloud服务与架构部负责人,负责公司云平台、云应用的后端研发和维护工作。有多年分布式、高并发场景的实战经验;目前在分布式存储、缓存、中间件、容器技术、微服务、公有云等领域均有涉猎。毕业于上海交通大学,曾供职于SAP,后投身MaxLeap致力于为开发者提供快稳定、可靠的云服务。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址