neo4j recommendation sandbox推荐系统

作者: redcohen | 来源:发表于2019-03-12 19:59 被阅读68次

    接着上文(1) 开始。

    4. 深入:协同过滤(Collaborative Filtering )

    回顾前文,一句话描述协同过滤:根据相似的(‘趣味相投’)人来推荐商品。

    稍做扩展就是,要给A推荐商品,那么找到和A趣味相投的B,然后把B喜欢的推荐给A,that's it!。

    在本沙盒里面,有用户(User)对电影(Movie)的评分(Rating, 作为属性包含在RATED这个关系里面)。


    Rating

    先看下Misty Williams这个User都做出哪些评分信息。

    // Show all ratings by Misty Williams
    MATCH (u:User {name: "Misty Williams"})
    MATCH (u)-[r:RATED]->(m:Movie)
    RETURN *;
    

    沙盒在一共召回423条信息(仅显示15条)。


    Rating demo

    再看下他评分的均值(每个人的严格程度不一样)

    // Show all ratings by Misty Williams
    MATCH (u:User {name: "Misty Williams"})
    MATCH (u)-[r:RATED]->(m:Movie)
    RETURN avg(r.rating) AS average;
    

    返回:

    average
    3.534278959810876

    接着,我们再看下高于均值的评分都是哪些电影。

    MATCH (u:User {name: "Misty Williams"})
    MATCH (u)-[r:RATED]->(m:Movie)
    WITH u, avg(r.rating) AS average
    MATCH (u)-[r:RATED]->(m:Movie)
    WHERE r.rating > average
    RETURN *;
    

    返回了199个电影。


    Misty Williams喜欢的电影

    那么,这199个高于评分均值的电影,从某种程度上就可以刻画'Misty Williams'这个人的喜好。
    扩展一下,借此,就可以衡量两个人的喜好类似是否类似。

    4.1 最简单的协同过滤(Collaborative Filtering )

    ok,让我们从一个最简单粗暴的协同过滤开始。

    假设: 如果只要A跟B看过/评价过同一个电影,就认为A跟B趣味相投。
    推导:B看过/评价过的,而A没有的,就推荐给A。

    代码实现:

    MATCH (u:User {name: "Cynthia Freeman"})-[:RATED]->(:Movie)<-[:RATED]-(o:User)
    MATCH (o)-[:RATED]->(rec:Movie)
    WHERE NOT EXISTS( (u)-[:RATED]->(rec) )
    RETURN rec.title, rec.year, rec.plot
    LIMIT 25
    

    这个假设-推导可谓是粗陋至极,看下效果。


    粗暴的协同过滤

    后面会用KNN(k近邻算法)来改进。

    再来看,如果只考虑用户喜欢的genre,来做协同过滤。如下代码:

    MATCH (u:User {name: "Andrew Freeman"})-[r:RATED]->(m:Movie)
    WITH u, avg(r.rating) AS mean
    
    MATCH (u)-[r:RATED]->(m:Movie)-[:IN_GENRE]->(g:Genre)
    WHERE r.rating > mean
    
    WITH u, g, COUNT(*) AS score
    
    MATCH (g)<-[:IN_GENRE]-(rec:Movie)
    WHERE NOT EXISTS((u)-[:RATED]->(rec))
    
    RETURN rec.title AS recommendation, rec.year AS year, COLLECT(DISTINCT g.name) AS genres, SUM(score) AS sscore
    ORDER BY sscore DESC LIMIT 10
    
    

    解释下代码(原理)
    1.首先取回Andrew看过的所有电影u,把评分算个均值记为mean。
    2.选出高于mean的那些电影以及对应的genres/g,并且路径个数总计为score。
    3.把含g中genre的电影全作为候选rec,但是排除掉Andrew已经看过/评价过的。
    4.最后把每个电影,对应genres每项对应的score相加成为sscore值。返回前10项。

    可以看出,还是很粗糙。


    只考虑genre的协同过滤

    4.2 基于余弦相似度的‘趣味相投’度衡量

    协同过滤,第一个要解决的问题是如何衡量两个人是‘趣味相投’的,英文叫'with the same preferences'.

    现在用一个很直截了当的方法:把一个User A所有评价过的电影当做一个向量V_a,每维的值就是评分(rating)。然后两个User A&B的相似度,就可以用V_a, V_b的余弦相似度衡量。

    cosine similarity

    示例代码:

    // Most similar users using Cosine similarity
    MATCH (p1:User {name: "Cynthia Freeman"})-[x:RATED]->(m:Movie)<-[y:RATED]-(p2:User)
    WITH COUNT(m) AS numbermovies, SUM(x.rating * y.rating) AS xyDotProduct,
    SQRT(REDUCE(xDot = 0.0, a IN COLLECT(x.rating) | xDot + a^2)) AS xLength,
    SQRT(REDUCE(yDot = 0.0, b IN COLLECT(y.rating) | yDot + b^2)) AS yLength,
    p1, p2 WHERE numbermovies > 10
    RETURN p1.name, p2.name, xyDotProduct / (xLength * yLength) AS sim
    ORDER BY sim DESC LIMIT 100;
    

    其中,REDUCE是Cypher里面定义一个func。


    REDUCE in Cypher

    在这里是得到向量内积。SQRT是开平方。整体就是求2范数。

    沙盒效果如下,还不错,召回很多相似度极高的。


    Most similar users using Cosine similarity

    4.3 基于Pearson相似度的‘趣味相投’度衡量

    问题:每个人评分时尺度不一,有的倾向于给较高分,有的则倾向于给较低分。cosine相似度不能很好解决这个。而Pearson相关系数就可以这一问题加以考虑。

    先给出公式,如下。


    Pearson相似度

    实现代码示例:

    1. MATCH (u1:User {name:"Cynthia Freeman"})-[r:RATED]->(m:Movie)
    2. WITH u1, avg(r.rating) AS u1_mean
    
    3. MATCH (u1)-[r1:RATED]->(m:Movie)<-[r2:RATED]-(u2)
    4. WITH u1, u1_mean, u2, COLLECT({r1: r1, r2: r2}) AS ratings WHERE size(ratings) > 10
    
    5. MATCH (u2)-[r:RATED]->(m:Movie)
    6. WITH u1, u1_mean, u2, avg(r.rating) AS u2_mean, ratings
    
    7. UNWIND ratings AS r
    
    8. WITH sum( (r.r1.rating-u1_mean) * (r.r2.rating-u2_mean) ) AS nom,
         sqrt( sum( (r.r1.rating - u1_mean)^2) * sum( (r.r2.rating - u2_mean) ^2)) AS denom,
         u1, u2 WHERE denom <> 0
    
    9. RETURN u1.name, u2.name, nom/denom AS pearson
    10. ORDER BY pearson DESC LIMIT 100
    

    对代码解释下。
    1~2: 找到Cynthia以及他u1评价过的所有电影,评分均为记为u1_mean。把u1, u1_mean传下去。

    3~4: 匹配到一个u2,如果他和u1一起评价过的电影数超过10个以上。并把u1,u1_mean,u2以及u1,u2两者针对每个电影的评分对形成的list记为ratings也传递下去。

    5~6: 找出u2所有评价过的电影,以计算评分均值u2_mean。把u1,u1_mean,u2,u2_mean,ratings继续往下传。

    7-8: 把ratings这个list展开,每项是r。 针对r进行sum。按Pearson公式,计算分子记为norm,分母记为denom,并且保证denom不为0。 把nom, denom, u1,u2往下传。// 体会unwind的作用。

    9-10:按pearson降序返回前100项:u1.name, u2.name, pearson。

    沙盒效果如下,仔细对比上面的cosine sim和这个的结果,ranking不一样了。


    Most similar users using Pearson similarity

    4.4 把协同过滤推荐进行到底!

    前文终于把如何寻找好'趣味相投'的人研究了个差不离(当然还是很粗浅,实践中要复杂很多)。接下来就要接着4.1小节完整地把推荐进行到底。

    思路:
    (1)要给A推荐商品,先找到和A趣味相投的k个人(k个最近邻B1...Bk)
    (2)然后把[B1..Bk]喜欢的,但是A还不知道的商品,优选出一部分推荐给A。

    衡量两个人趣味相投度用4.3小节的方法,然后选出10个。再从这10个人评价过的电影中,找出评价度高的。那么,就有两个数量:人和人的相关度(Pearson系数),人对电影的评分(rating)需要综合考虑。

    代码如下:

    MATCH (u1:User {name:"Cynthia Freeman"})-[r:RATED]->(m:Movie)
    WITH u1, avg(r.rating) AS u1_mean
    
    MATCH (u1)-[r1:RATED]->(m:Movie)<-[r2:RATED]-(u2)
    WITH u1, u1_mean, u2, COLLECT({r1: r1, r2: r2}) AS ratings WHERE size(ratings) > 10
    
    MATCH (u2)-[r:RATED]->(m:Movie)
    WITH u1, u1_mean, u2, avg(r.rating) AS u2_mean, ratings
    
    UNWIND ratings AS r
    
    WITH sum( (r.r1.rating-u1_mean) * (r.r2.rating-u2_mean) ) AS nom,
         sqrt( sum( (r.r1.rating - u1_mean)^2) * sum( (r.r2.rating - u2_mean) ^2)) AS denom,
         u1, u2 WHERE denom <> 0
    
    WITH u1, u2, nom/denom AS pearson
    ORDER BY pearson DESC LIMIT 10
    
    MATCH (u2)-[r:RATED]->(m:Movie) WHERE NOT EXISTS( (u1)-[:RATED]->(m) )
    
    RETURN m.title, SUM( pearson * r.rating) AS score
    ORDER BY score DESC LIMIT 25
    

    以上代码,就是最后三行是相对于4.3新加的,意思也很明确。就是排除已评价的,综合考虑Pearson和rating为电影的排序用分数,然后选top25。enjoy!

    沙盒验证效果如下:


    协同过滤推荐结果

    至此,本系列学习记录结束,请大家指正。

    上文链接neo4j recommendation sandbox推荐系统沙盒学习(1)

    相关文章

      网友评论

        本文标题:neo4j recommendation sandbox推荐系统

        本文链接:https://www.haomeiwen.com/subject/ocpnpqtx.html