GraphGists

简介

我最近受到启发,重新构思了 BeachBody 健身计划和营养补充剂,将其作为图来表示,目标是根据我的用户角色提供个性化的健身计划、营养补充剂和其他用户的推荐。对于那些不知道的人,BeachBody 负责 P90X、INSANITY、T25 以及许多其他产品,这些产品多年来一直通过 DVD 形式分发。

项目

在这个推荐演示中,我利用了其现有网站上所有公开可用的数据,并创建了几个用户角色来演示将现有数据转换为 Neo4j 图数据库带来的个性化推荐和互联数据优势。我专注于提供一些关键推荐的建议

  • 1. 哪些健身计划最能帮助我实现我的锻炼目标?

  • 2. 哪些营养补充剂可以帮助我实现我的饮食和锻炼目标?

  • 3. 社区中是否有其他用户可以和我一起锻炼,以及我们一起进行哪种锻炼会比较好?

FitnessAndNutritionRecommendations

概念

今天网站上存在的主要概念是健身计划、营养补充剂、装备和社区。在最初的版本中,我选择忽略了装备。

我认为这些既定概念之间可以很好地契合的一些主要想法,这些想法可以让我作为其产品的消费者和社区参与者有兴趣获得我想要收到的推荐:

  • 锻炼目标

  • 饮食目标

  • 肌肉群

  • 身体部位

  • 锻炼类型

  • 锻炼等级

  • 补充类型

…还有另一个关键因素需要考虑健身计划,那就是我作为消费者可能存在的任何身体限制或饮食限制。

设置

加权边

最初,我们将从探索用户角色对各种锻炼目标、饮食目标、肌肉群和身体部位的“重要性”开始,这将用作评分算法的一部分。

// Order User's Values by Importance
MATCH (u:User {username: "ben"})-[r:VALUES]->(x) RETURN x.name, r.importance ORDER BY r.importance DESC;
// Order User's Desires by Importance
MATCH (u:User {username: "ben"})-[r:DESIRES]->(x) RETURN x.name, r.importance ORDER BY r.importance DESC;

个性化

用户拥有的偏好对于充分了解他们以进行明智的推荐至关重要。在这里,我们从用户“ben”开始,并遍历一层到我们通过与该用户互动收集的所有直接信息。

// Show User's Preferences
MATCH (u:User {username: "ben"}) WITH u MATCH (u)-[:HAS]->(pl:PhysicalLimitation), (u)-[:IS_AT]->(wl:WorkoutLevel), (u)-[:PREFERS]->(wt:WorkoutType), (u)-[:VALUES]->(ba:BodyArea), (u)-[:DESIRES]->(mg:MuscleGroup) RETURN u, pl, wl, wt, ba, mg;

然后,我们查看所有具有分类节点的用户,这些节点之间由'x'表示。从这些'x'节点,我们将找到也连接到这些x的所有健身计划,这为我们提供了潜在推荐的集合。

// Show All Users, Preferences/Classifcations and Fitness Programs
MATCH (u:User)-[]-(x)-[]-(u2:User)
OPTIONAL MATCH (x)-[]-(fp:FitnessProgram)
RETURN u, x, u2, fp;

类似地,我们也可以从饮食目标开始,看看哪些营养补充剂和用户具有相同的目标。

// Explore Nutritional Supplements, Eating Goals and Users
MATCH (eg:EatingGoal) WITH eg OPTIONAL MATCH (eg)-[]-(x) RETURN eg, x;

产品交叉销售

在这里,我们查看哪些饮食目标和锻炼目标会被认为是平行的,并由此检查用户模式,而这些模式之间没有直接联系。

// Explore Parallel Eating and Workout Goals - Performance/Fat Burning
MATCH (eg:EatingGoal {name: "Performance"})-[]-(x)-[]-(wg:WorkoutGoal {name: "Fat Burning"}) RETURN eg, x, wg;
// Explore Parallel Eating and Workout Goals - Weight Loss
MATCH (eg:EatingGoal {name: "Weight Loss"})-[]-(x)-[]-(wg:WorkoutGoal {name: "Weight Loss"}) RETURN eg, x, wg;
// Explore Parallel Eating and Workout Goals - Weight Loss/Get Healthy/Wellness
MATCH (eg:EatingGoal)-[]-(x)-[]-(wg:WorkoutGoal {name: "Weight Loss"}) WHERE eg.name = "Get Healthy" OR eg.name = "Wellness" RETURN eg, x, wg;

推荐:向用户推荐健身计划

我们从向用户推荐其前五名健身计划开始。

  1. 用户是我们的起始节点,从那里我们可以选择引入任何身体限制,将其用作评分中的排除项。

  2. 然后,我们找到所有具有重要性权重的偏好。

  3. 接下来,我们需要获取连接到用户偏好的所有健身计划,并且不排除用户拥有的任何身体限制。

  4. 然后,我们构建具有其权重的特征,并评估用户与可能推荐的健身计划之间连接的重要性。

  5. 然后,我们按该分数排序并将返回集限制为 5 个。

// Recommend User a Fitness Program
MATCH (u:User) WHERE u.username = "ben" OPTIONAL MATCH (u)-[:HAS]->(pl)
WITH u, pl MATCH (u)-[r:IS_AT|PREFERS|DESIRES|VALUES]->(x)
WITH u, pl, x, coalesce(r.importance, 0.5) AS importance
MATCH (x)<-[]-(x2:FitnessProgram) WHERE NOT (x2)-[:LIMITED_BY]->(pl)
WITH u, x2, collect({name: x.name, weight: importance}) AS traits
WITH u, x2, reduce(s = 0, t IN traits | s + t.weight) AS score
WITH u, x2, score OPTIONAL MATCH (x2)-[]->(x)<-[]-(u)
RETURN x2, collect(x) AS x, u, score ORDER BY score DESC LIMIT 5;

推荐:向用户推荐营养补充剂

此处的过程与健身计划相同,但只是使用营养补充剂作为推荐对象。这是有效的,因为关系模式在这些类型之间是相同的。

// Recommend User a Nutritional Supplement
MATCH (u:User) WHERE u.username = "ben" OPTIONAL MATCH (u)-[:HAS]->(pl)
WITH u, pl MATCH (u)-[r:IS_AT|PREFERS|DESIRES|VALUES]->(x)
WITH u, pl, x, coalesce(r.importance, 0.5) AS importance
MATCH (x)<-[]-(x2:NutritionalSupplement) WHERE NOT (x2)-[:LIMITED_BY]->(pl)
WITH u, x2, collect({name: x.name, weight: importance}) AS traits
WITH u, x2, reduce(s = 0, t IN traits | s + t.weight) AS score
WITH u, x2, score OPTIONAL MATCH (x2)-[]->(x)<-[]-(u)
RETURN x2, collect(x) AS x, u, score ORDER BY score DESC LIMIT 5;

推荐:向用户推荐健身计划和营养补充剂的组合

这与第一个相同,只是简单地允许健身计划和营养补充剂作为可能的推荐。

// Recommend User a blend of Fitness Programs and Nutrional Supplements
MATCH (u:User) WHERE u.username = "ben" OPTIONAL MATCH (u)-[:HAS]->(pl)
WITH u, pl MATCH (u)-[r:IS_AT|PREFERS|DESIRES|VALUES]->(x)
WITH u, pl, x, coalesce(r.importance, 0.5) AS importance
MATCH (x)<-[]-(x2) WHERE (x2:FitnessProgram OR x2:NutritionalSupplement) AND NOT (x2)-[:LIMITED_BY]->(pl)
WITH u, x2, collect({name: x.name, weight: importance}) AS traits
WITH u, x2, reduce(s = 0, t IN traits | s + t.weight) AS score
WITH u, x2, score OPTIONAL MATCH (x2)-[]->(x)<-[]-(u)
RETURN x2, collect(x) AS x, u, score ORDER BY score DESC LIMIT 5;

推荐:推荐两个用户一起进行健身计划

在这里,我们进行了一些调整,以便向应该一起体验健身计划的一对用户推荐健身计划。此处的主要调整是

  1. 从起始用户的偏好遍历,以获取也连接到他们的其他用户,这使得可以推荐的用户集合成为可能。

  2. 现在,我们对用户进行评分、排序和限制,以获得最佳用户,然后继续为这两个用户找到最佳的健身计划。

  3. 在我们找到可以推荐给双方的健身计划之后,评分与之前相同。

// Recommend Two Users do a Fitness Program Together
MATCH (u:User) WHERE u.username = "ben" OPTIONAL MATCH (u)-[:HAS]->(pl)
WITH u, pl MATCH (u)-[r:IS_AT|PREFERS|DESIRES|VALUES]->(x)
WITH u, pl, x, coalesce(r.importance, 0.5) AS importance
MATCH (x)<-[]-(x2) WHERE (x2:User) AND NOT (x2)-[:LIMITED_BY]->(pl) AND u <> x2
WITH u, pl, x2, collect({name: x.name, weight: importance}) AS traits
WITH u, pl, x2, reduce(s = 0, t IN traits | s + t.weight) AS score
WITH u, pl, x2, score ORDER BY score DESC LIMIT 1
WITH u, pl, x2, score OPTIONAL MATCH (x2)-[]->(x)<-[]-(u), (x)<-[r]-(fp:FitnessProgram) WHERE NOT (fp)-[:LIMITED_BY]->(pl)
WITH u, x2, score, collect(x) AS common, fp, collect({name: x.name, weight: coalesce(r.importance, 0.5)}) AS traits
WITH u, x2, score, common, fp, reduce(s = 0, t IN traits | s + t.weight) AS score2
RETURN u, x2, score, common, fp, score2 ORDER BY score2 DESC LIMIT 1;