以下内容大多都翻译自neo4j官方沙盒文档,仅做了翻译及必要说明。
-redcohen
1 . 注册&登录
首先,注册一个沙盒账号。
沙盒登录页
然后,按照提示登录类似如下网址(浏览器也可以自动跳转):
https://10-0-1-148-?????.neo4jsandbox.com/browser/
沙盒浏览器demo
如果第二天以后不能访问了,记得在沙盒页面的detail选项卡页中找到密码(账号一般是neo4j),登录认证下即可。如下图
账号密码demo
2. 推荐系统简介
推荐里面分为两种:基于内容的过滤和协同过滤。
2.1 基于内容的过滤
一句话表达:根据商品推荐商品
content-based filtering在上面这个电影的例子里面,假设'Casino'是一个你已看的电影,那么就可能给你推荐'Goodfellas'。因为它们从风格/导演/主演等几个角度都有相同之处。
代码类似如下:
MATCH p=(m:Movie {title: "Casino"})-[:ACTED_IN|:IN_GENRE|:DIRECTED*2]-()
RETURN p LIMIT 25
2.2 协同过滤
一句话表达:根据人推荐商品
collaborative filtering
从上图例子,Guy Davis和Misty Williams看电影的趣味很相似,那么前者还看了'Net, The', 'Basketball Diaries, The'这两部电影,那么就可以推荐给后者。
官网给的两个代码段都不是很贴意,所以省去!
3. 深入: 基于内容的过滤
基于内容的过滤是基于商品(item)的属性(attributes)、特性(traits)来寻找相类似的商品。
3.1 基于共同属性推荐
对于电影,最简单的相似度是基于genres这个属性。
内容推荐之功夫熊猫比如上图,根据'功夫熊猫3'的genre属于动作、冒险类,可以推荐出'碟中谍5:神秘国度'
实操下,执行如下代码,在沙盒中召回和'Inception'类似的影片。
// Find similar movies by common genres
MATCH (m:Movie)-[:IN_GENRE]->(g:Genre)<-[:IN_GENRE]-(rec:Movie)
WHERE m.title = "Inception"
WITH rec, COLLECT(g.name) AS genres, COUNT(*) AS commonGenres
RETURN rec.title, genres, commonGenres
ORDER BY commonGenres DESC LIMIT 10;
基于内容推荐召回Inception类似影片
3.2 来点个性化
以上方法完全忽略用户的个性化,因此无法达到千人千面,现在考虑个性化。
推荐用户A没看过的但和已看过的风格类似的影片。
实操代码类似如下:
// Content recommendation by overlapping genres
MATCH (u:User {name: "Angelica Rodriguez"})-[r:RATED]->(m:Movie),
(m)-[:IN_GENRE]->(g:Genre)<-[:IN_GENRE]-(rec:Movie)
WHERE NOT EXISTS( (u)-[:RATED]->(rec) )
WITH rec, [g.name, COUNT(*)] AS scores
RETURN rec.title AS recommendation, rec.year AS year,
COLLECT(scores) AS scoreComponents,
REDUCE (s=0,x in COLLECT(scores) | s+x[1]) AS score
ORDER BY score DESC LIMIT 10
沙盒结果如下:
来点个性化之内容过滤
来看下代码。
第一行表达匹配A这个人看的电影涉及到的genre,以此去找rec候选。
第二行WHERE这句,表示A没RATE过,也就是没看过。
第三行WITH这句,表示看下每个推荐候选rec,它对应的genre都出现了几次(在第一行的匹配中),也就是这种类似的电影A看过多少次。做下聚集,作为scores。因为每个电影rec都可能属于多个genre,因此后半部分要用一个list[]表达。
第四行RETURN这句,表示要返回的数据项,第一个是电影title,第二个year,第三个把scorers归集为scoreComponent,第四个是对第三个中每项的第二个元素求和为score。
第五行ORDER这句,表示按score降序排列,取前10个。
3.3 多种属性,加权式计算相似度
上文中只是用了genre一个属性,而且是很粗糙的累加计分策略。本节展示下结合多种属性/特性,并且采用一些简单的加权(weighting)策略。
比如,除了gener,我们还可以根据演员/导演等等的相似度来判断两个电影的相似度。
示例代码:
// Find similar movies by common genres
MATCH (m:Movie) WHERE m.title = "Wizard of Oz, The"
MATCH (m)-[:IN_GENRE]->(g:Genre)<-[:IN_GENRE]-(rec:Movie)
WITH m, rec, COUNT(*) AS gs
OPTIONAL MATCH (m)<-[:ACTED_IN]-(a:Actor)-[:ACTED_IN]->(rec)
WITH m, rec, gs, COUNT(a) AS as
OPTIONAL MATCH (m)<-[:DIRECTED]-(d:Director)-[:DIRECTED]->(rec)
WITH m, rec, gs, as, COUNT(d) AS ds
RETURN rec.title AS recommendation, (5*gs)+(3*as)+(4*ds) AS score ORDER BY score DESC LIMIT 100
沙盒效果如下:
多属性加权推荐
来解读下代码。
第一行MATCH这句,表示锚点是绿野仙踪这个电影,用m代表。
第二行MATCH这句,表示召回和m拥有共同genre的电影候选用rec代表,同时也召回genre用g代表。
第三行WITH这句,往下传递(类似管道)m,rec, 并且把g统计总数记为gs。
第四行OPTIONAL MATCH这句,考虑在m,rec的限定下,有交集的演员a。
第五行WITH这句,往下传递m, rec, gs, 并把a统计总数记为as。
第六行~第七行,类似处理导演。
第八行RETURN这句,最主要看score这块,把genre, actor, director这三个属性的计数分别乘以5,3,4的权重,然后相加成为最后的score。
3.4 一个较为鲁棒的相似度: Jaccard距离
前文所谓‘相似度’,说法不准确。只能算一个相关度的计算公式。而要成为一个严格意义上的相似度度量,则需要满足很多条件。比如:归一、稳定、语义清晰等等。
本节介绍一个最最简单的相似度度量:Jaccard相似度。主要用来衡量两个集合的相似度。
jaccard similarity
具体cypher实现,先看只用在genre上的: 用Jaccard相似度看和'Inception'的genre相似的。
MATCH (m:Movie {title: "Inception"})-[:IN_GENRE]->(g:Genre)<-[:IN_GENRE]-(other:Movie)
WITH m, other, COUNT(g) AS intersection, COLLECT(g.name) AS i
MATCH (m)-[:IN_GENRE]->(mg:Genre)
WITH m,other, intersection,i, COLLECT(mg.name) AS s1
MATCH (other)-[:IN_GENRE]->(og:Genre)
WITH m,other,intersection,i, s1, COLLECT(og.name) AS s2
WITH m,other,intersection,s1,s2
WITH m,other,intersection,s1+filter(x IN s2 WHERE NOT x IN s1) AS union, s1, s2
RETURN m.title, other.title, s1,s2,((1.0*intersection)/SIZE(union)) AS jaccard ORDER BY jaccard DESC LIMIT 100
对代码稍作解读:
取交集特别简单,就是第一行MATCH中的g
取并集需要几步:
- 先把m的全集取到:第四行s1
- 再把other的全集取到:第六行s2
- 在第八行,计算出并集union
最后一行RETURN计算出jaccard值。
这段代码可以帮大家体会类似foreach这种操作。其中的other实际是在
foreach式遍历。
沙盒测试结果如下:
jaccard by genre
把genre, actor, director这些都结合起来,也类似。代码如下:
MATCH (m:Movie {title: "Inception"})-[:IN_GENRE|:ACTED_IN|:DIRECTED]-(t)<-[:IN_GENRE|:ACTED_IN|:DIRECTED]-(other:Movie)
WITH m, other, COUNT(t) AS intersection, COLLECT(t.name) AS i
MATCH (m)-[:IN_GENRE|:ACTED_IN|:DIRECTED]-(mt)
WITH m,other, intersection,i, COLLECT(mt.name) AS s1
MATCH (other)-[:IN_GENRE|:ACTED_IN|:DIRECTED]-(ot)
WITH m,other,intersection,i, s1, COLLECT(ot.name) AS s2
WITH m,other,intersection,s1,s2
WITH m,other,intersection,s1+filter(x IN s2 WHERE NOT x IN s1) AS union, s1, s2
RETURN m.title, other.title, s1,s2,((1.0*intersection)/SIZE(union)) AS jaccard ORDER BY jaccard DESC LIMIT 100
从中看出,cypher代码一样简洁。
沙盒验证效果如下:
jaccard by multiple attributes
(未完待续)
网友评论