GraphGists

最近,我被要求提出一种提供推荐的方法。幸运的是,凭借在最近的 Neo4j 伦敦聚会上从 Max De Marzi 和 Mark Needham 的演讲中获得的知识,我知道这可以用 Neo4j 轻松实现。

推荐引擎的关键问题来自数据。幸运的是,Neo4j 附带了 Northwind 图示例。Northwind 数据库是一个臭名昭著的数据集,包含多年来用于教授关系数据库的购买历史记录,并且是一个很好的起点。

您可以按照 Neo4j 上的 “将数据导入 Neo4j” 文章将 Northwind 数据库导入图中,或者在 Neo4j 的浏览器中输入以下内容,例如 Neo4j 桌面版中的空数据库或 空白沙箱

:play northwind graph

以下是如何手动加载数据

现在我们有一些数据了,让我们开始探索数据集。

数据集

7a4346ed 0bdb 4f6b baeb 231c6b0b0bd2 HZuG Jml9

Northwind 图为我们提供了丰富的数据集,但我们主要关注的是客户及其订单。在图中,数据建模如下

4c559e14 baaa 4d77 ad62 f3f52deab74a q6scT bsop

要查找数据集中最热门的产品,我们可以从 :Customer:Product 沿着路径查找。

match (c:Customer)-[:PURCHASED]->(o:Order)-[:PRODUCT]->(p:Product)
return c.companyName, p.productName, count(o) as orders
order by orders desc
limit 5

基于内容的推荐

我们可以为客户提供的最简单的推荐是基于内容的推荐。根据他们之前的购买记录,我们能否推荐一些他们还没有购买过的商品?对于我们的客户购买的每种产品,让我们看看其他客户也购买了哪些产品。每个 :Product 都与一个 :Category 相关联,因此我们可以使用它进一步缩小要推荐的产品列表。

match (c:Customer)-[:PURCHASED]->(o:Order)-[:PRODUCT]->(p:Product)
<-[:PRODUCT]-(o2:Order)-[:PRODUCT]->(p2:Product)-[:PART_OF]->(:Category)<-[:PART_OF]-(p)
WHERE c.customerID = 'ANTON' and NOT( (c)-[:PURCHASED]->(:Order)-[:PRODUCT]->(p2) )
return c.companyName, p.productName as has_purchased, p2.productName as has_also_purchased, count(DISTINCT o2) as occurrences
order by occurrences desc
limit 5

到目前为止,这很标准。

协同过滤

协同过滤是一种由推荐引擎使用的方法,它根据其他客户的反馈来推荐内容。为此,我们可以使用 k-NN (k-最近邻) 算法。k-N 通过根据项目彼此之间的相似性将项目分组到分类中来工作。在我们的例子中,这可能是两个客户对产品的评分。举个现实世界的例子,Netflix 就是根据你已经观看的节目的评分来推荐节目的。

使此模型工作的第一件事是创建一些“评分关系”。现在,让我们根据客户购买产品的次数为每种产品创建一个介于 0 和 1 之间的分数。

MATCH (c:Customer)-[:PURCHASED]->(o:Order)-[:PRODUCT]->(p:Product)
WITH c, count(p) as total
MATCH (c)-[:PURCHASED]->(o:Order)-[:PRODUCT]->(p:Product)
WITH c, total,p, count(o)*1.0 as orders
MERGE (c)-[rated:RATED]->(p)
ON CREATE SET rated.rating = orders/total
ON MATCH SET rated.rating = orders/total
WITH c.companyName as company, p.productName as product, orders, total, rated.rating as rating
ORDER BY rating DESC
RETURN company, product, orders, total, rating LIMIT 10

现在我们的模型应该如下所示

33d26c02 b28c 4d82 9be4 ffb024550153 1NcVSJLAC
MATCH (me:Customer)-[r:RATED]->(p:Product)
WHERE me.customerID = 'ANTON'
RETURN p.productName, r.rating limit 10

现在我们可以使用这些评分来比较两个客户的偏好。

// See Customer's Similar Ratings to Others
MATCH (c1:Customer {customerID:'ANTON'})-[r1:RATED]->(p:Product)<-[r2:RATED]-(c2:Customer)
RETURN c1.customerID, c2.customerID, p.productName, r1.rating, r2.rating,
CASE WHEN r1.rating-r2.rating < 0 THEN -(r1.rating-r2.rating) ELSE r1.rating-r2.rating END as difference
ORDER BY difference ASC
LIMIT 15

现在,我们可以使用余弦相似度计算两个客户之间的相似度分数(感谢 Nicole White 提供了最初的 Cypher 查询……)

MATCH (c1:Customer)-[r1:RATED]->(p:Product)<-[r2:RATED]-(c2:Customer)
WITH
	SUM(r1.rating*r2.rating) as dot_product,
	SQRT( REDUCE(x=0.0, a IN COLLECT(r1.rating) | x + a^2) ) as r1_length,
	SQRT( REDUCE(y=0.0, b IN COLLECT(r2.rating) | y + b^2) ) as r2_length,
	c1,c2
MERGE (c1)-[s:SIMILARITY]-(c2)
SET s.similarity = dot_product / (r1_length * r2_length)
MATCH (me:Customer)-[r:SIMILARITY]->(them)
WHERE me.customerID='ANTON'
RETURN me.companyName, them.companyName, r.similarity
ORDER BY r.similarity DESC limit 10

很好,现在让我们根据这些相似度分数做出推荐。

WITH 1 as neighbours
MATCH (me:Customer)-[:SIMILARITY]->(c:Customer)-[r:RATED]->(p:Product)
WHERE me.customerID = 'ANTON' and NOT ( (me)-[:RATED|PRODUCT|ORDER*1..2]->(p:Product) )
WITH p, COLLECT(r.rating)[0..neighbours] as ratings, collect(c.companyName)[0..neighbours] as customers
WITH p, customers, REDUCE(s=0,i in ratings | s+i) / LENGTH(ratings)  as recommendation
ORDER BY recommendation DESC
RETURN p.productName, customers, recommendation LIMIT 10

就是这样!使用 Neo4j 快速简单地进行推荐。