内容


图形数据库介绍,第 2 部分

使用图形数据库构建推荐引擎

Comments

系列内容:

此内容是该系列 2 部分中的第 # 部分: 图形数据库介绍,第 2 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:图形数据库介绍,第 2 部分

敬请期待该系列的后续内容。

图形数据库介绍:构建推荐引擎

构建推荐引擎
构建推荐引擎

图形数据库的最大优势之一是,能够轻松快速地为用户生成推荐。推荐能让用户更容易找到他们想要的产品,从而提高您的销量。这是一个双赢策略!

Lauren's Lovely Landscapes 应用程序目前有一个在主页上显示个性化推荐的推荐引擎。在本教程中,将探索这个现有引擎背后的代码。然后实现一个功能来在产品页上显示推荐。

应用程序简介

您将使用一个名为 Lauren's Lovely Landscapes 的示例在线商店。该商店允许用户浏览并购买印刷品。<for developers> 页面包含有关如何构建该应用程序的信息,以及用来插入和删除示例数据的链接。

备注:本教程使用了 Lauren's Lovely Landscapes 的第 2 版。如果您拥有第 1 版(在主页上没有推荐该版本),您需要获取该代码的新副本。

laurens lovelely landscapes 主页
laurens lovelely landscapes 主页

运行应用程序浏览代码部署到 IBM Bluemix

您需要做的准备工作

开始之前,您需要在 IBM Bluemix® 上进行注册。还需要以下这些浏览器之一的最新版本:

  • Chrome
  • Firefox
  • Internet Explorer
  • Safari

不要求您必须阅读完本教程的第 1 部分。但是,您需要已经部署 Lauren's Lovely Landscapes 第 2 版的副本(参见第 1 部分中的 “部署应用程序” 了解操作说明),并知道如何访问您的项目的 Web IDE(参见 第 1 部分 中的 “打开代码” 了解操作说明)。

探索现有推荐引擎

推荐引擎允许您向用户提供个性化的推荐。推荐可以很简单(例如,可以根据上周买得最多的商品进行推荐),也可以很复杂(例如,可以根据用户的人口统计特征、购买历史和社交媒体联系进行推荐)。

在本节中,将探索 Lauren's Lovely Landscapes 应用程序用来在其主页上显示个性化推荐的现有推荐引擎。

探索推荐引擎背后的理论

可以通过许多方法为用户生成推荐。此应用程序根据 Apache TinkerPop 推荐 recipe 进行推荐。您会在下面看到,Lauren's Lovely Landscapes 中的 Gremlin 查询不同于该 recipe 中包含的查询,但该查询背后的概念是一样的。

有关该推荐引擎的工作原理的详细解释,请观看下面的视频:

使用 IBM Graph 进行推荐
使用 IBM Graph 进行推荐

在本节中,将使用 Graph Query Editor 逐步构建用于生成推荐的 Gremlin 查询。

  1. 为了确保您生成的推荐与本教程匹配,请删除您的图形中的数据并插入示例数据:
    1. 导航到已部署的应用程序的 <for developers> 页面。
    2. The Data 部分,单击删除数据的链接。
    3. The Data 部分,单击插入示例数据的链接。
      备注:插入数据可能会花一两分钟的时间。
  2. 在新浏览器选项卡或窗口中为您的图形实例打开 Graph Query Editor:
    1. 在新浏览器选项卡或窗口中,导航到 Bluemix.net。
    2. 在仪表板上,向下滚动到 All Services 部分并单击 LaurensLovelyLandscapesSample-Graph
    3. 在 Manage 选项卡上(默认已打开),单击 Open。这将为您的图形实例打开 Graph Query Editor。
  3. 默认情况下,已选择 g 图形。通过单击顶部导航菜单中的 g 旁边的向下箭头,然后单击 landscapes_graph,切换到存储您的数据的 landscapes_graph
  4. 让我们逐步为我们的推荐构建查询,以便了解每一步。首先创建一个新遍历。然后搜索用户 “dale”,并将得到的顶点命名为 “buyer”,以便以后可在查询中引用它。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("user").has("username","dale").as("buyer");
  5. 单击 Submit Query 按钮 (白色背景上的箭头)。
  6. 查询结果会显示在一个新窗口中。该结果显示了一个针对 dale 的用户顶点。
  7. 添加到现有查询。从您命名为 buyer 的用户顶点 dale,遍历所有 “buys” 边,以便查找 dale 购买的所有印刷品。将这些印刷品汇集在一起并将它们命名为 “bought”,以便以后可在查询中引用它们。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("user").has("username","dale").as("buyer")
    .out("buys").aggregate("bought");
  8. 单击 Submit Query 按钮 (白色背景上的箭头)。
  9. 查询结果会显示在下面的新窗口中。您可以看到,dale 购买了 3 件印刷品:Las Vegas、Australia 和 Japan。
  10. 通过将 dale 已购买的印刷品集合添加到现有查询中,遍历 buys 边来找到已购买该用户购买的任何印刷品的所有用户顶点(除购买者 dale 外)。使用 dedup() 删除重复数据,因为您只需找到每个用户一次。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("user").has("username", "dale").as("buyer")
    .out("buys").aggregate("bought")
    .in("buys").where(neq("buyer")).dedup();
  11. 单击 Submit Query 按钮 (白色背景上的箭头)。
  12. 查询结果会显示在下面的窗口中。可以看到 3 个用户(Jason、Deanna 和 Joy)购买了至少一本与 dale 相同的印刷品。
  13. 继续将已购买至少一本与 dale 相同的印刷品的用户添加到现有查询中,遍历 buys 边以查找这些用户购买的印刷品。排除 dale 已购买的印刷品,因为您不需要向他推荐他已购买的印刷品。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("user").has("username", "dale").as("buyer")
    .out("buys").aggregate("bought")
    .in("buys").where(neq("buyer")).dedup()
    .out("buys").where(without("bought"));
  14. 单击 Submit Query 按钮 (白色背景上的箭头)。
  15. 查询结果会显示在下面的新窗口中。可以看到该用户集合购买了两种印刷品:Antarctica 和 Alaska。请注意,左侧的 JSON 结果多次列出了这些印刷品,每列出该印刷品一次表示一次购买。右侧的可视摘要仅显示每种印刷品一次。
  16. 添加到现有查询,现在您已经知道了推荐的印刷品,您需要按名称将它们进行分组,对它们排序,并仅列出最畅销的 3 种印刷品。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("user").has("username", "dale").as("buyer")
    .out("buys").aggregate("bought")
    .in("buys").where(neq("buyer")).dedup()
    .out("buys").where(without("bought"))
    .groupCount().by('name').order(local).by(valueDecr).limit(local, 3);
  17. 单击 Submit Query 按钮 (白色背景上的箭头)。
  18. 查询结果会显示在一个新窗口中。您会看到,Alaska 印刷品被购买了 3 次(因此它是我们的首要推荐),Antarctica 印刷品被购买了两次(因此它是我们的次要推荐)。现在您有了查询,来看一看代码。
  19. 此时,您的工作可能已经完成了,因为您已经生成了一个有序的推荐列表。但是,要让应用程序显示与每项推荐有关联的图像,除了需要 name 属性之外,还需要 imgPath 属性。更新查询以添加一个名为 byNameImgPath 的新函数,该函数负责将图像名称和图像路径存储在查询结果中。将 by('name') 替换为 by(byNameImgPath),以调用这个新函数。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    java.util.function.Function byNameImgPath = { Vertex v -> "" + v.value("name") + ":"+ v.value("imgPath") };
    gt.V().hasLabel("user").has("username", "dale").as("buyer")
    .out("buys").aggregate("bought")
    .in("buys").where(neq("buyer")).dedup()
    .out("buys").where(without("bought"))
    .groupCount().by(byNameImgPath).order(local).by(valueDecr).limit(local, 3);
  20. 单击 Submit Query 按钮 (白色背景上的箭头)。
  21. 查询结果会显示在下面的新窗口中。您可以看到,除了名称之外,结果中现在还显示了 imgPath。查询已准备就绪!

探索推荐引擎背后的代码

现在您理解了推荐引擎使用的查询,来看一看代码:

  1. 打开您的项目的 Web IDE(参见第 1 部分中的 “打开代码” 了解说明)。
  2. 在左侧导航窗格中,单击 graph.py 打开它。
  3. graph.py 中,找到第 43 行上的 getRecommendedPrints() 函数。
  4. 该代码首先确保 username 变量存在,以便查询可以运行。
  5. 接下来,该函数将创建一个包含 Gremlin 查询的新字典。该查询基于您在上一节中编写的查询,但有一个不同之处:没有查询用户名 dale,该代码使用了基于传入该函数的 username 参数的动态输入。
  6. 创建包含查询的字典后,该函数就可以调用 Graph API 了。在第 77 行上,该函数向 /gremlin 发出一个新 POST 请求,并在请求中发送包含该 Gremlin 查询的字典。
  7. 在第 79 行上,该函数将检查请求是否被成功处理(响应代码为 200)。如果请求被成功处理,该函数将处理结果(推荐)。该函数对收到的结果进行了排序;但是,当使用 json.loads() 处理这些结果时,排序会丢失,所以该函数将对结果进行重新排序。该函数将已排序结果中的重要信息存储在 prints 中。
  8. Lauren's Lovely Landscapes 的主页旨在显示 3 种个性化推荐。您刚才看到的查询可能没有生成 3 种推荐。原因如下:(1) 如果用户未经过身份验证 (2) 如果用户已购买普通用户购买的所有或几乎所有印刷品,则没有 3 种印刷品可供推荐。在第 94 行上,该函数将检查是否有 3 种推荐。如果有,那么它会返回推荐的印刷品。如果没有,它会继续尝试生成更多推荐。
  9. 在第 100 行上,该函数将更新 gremlin 字典,以便拥有一个新查询来搜索所有用户购买得最多的印刷品。
  10. 在第 116 行上,该函数将检查请求是否被成功处理(响应代码为 200)。如果请求被成功处理,该函数将处理结果(推荐)。像该函数在上面所做的一样,它会对结果进行重新排序。然后将结果存储在 prints 中,并检查结果以确保它没有存储来自第一批查询结果的重复推荐。在该函数完成对结果的处理时,会返回 prints

实现新推荐

掌握构建推荐引擎的基础知识后,是时候编写代码了!在本节中,将更新一个产品页面,以显示基于已购买该印刷品的其他用户购买的其他印刷品的推荐。

为了更好地了解这个新推荐引擎背后的概念,请观看本教程顶部的视频。

为新推荐引擎构建查询

  1. 打开包含 Graph Query Editor 的浏览器选项卡或窗口(操作说明请参见 “构建” 部分的第 2 步。)确保已选择 landscapes_graph(操作说明请参见 “构建” 部分的第 3 步。)
  2. 让我们逐步为我们的推荐构建查询,以便讨论每一步。首先创建一个新遍历。然后搜索印刷品 “Alaska”,并将得到的顶点命名为 “currentPrint”,以便以后可以引用它。在 Query Execution Box 中,输入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("print").has("name", "Alaska").as("currentPrint");
  3. 单击 Submit Query 按钮 (白色背景上的箭头)。
  4. 查询结果会显示在下面的新窗口中。可以看到一个针对 Alaska 的 print 顶点。
  5. 添加到现有查询。从 print 顶点 Alaska,遍历 buys 边以找到所有购买了该印刷品的用户。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("print").has("name", "Alaska").as("currentPrint")
    .in("buys");
  6. 单击 Submit Query 按钮 (白色背景上的箭头)。
  7. 查询结果会显示在下面的新窗口中。可以看到 3 个用户购买了 Alaska 印刷品:Jason、Deanna 和 Joy。
  8. 继续添加到现有查询。从购买了 Alaska 的用户集合,遍历 buys 边以找到这些用户购买的除 Alaska ('currentPrint') 外的所有印刷品。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("print").has("name", "Alaska").as("currentPrint")
    .in("buys").out("buys").where(neq("currentPrint"));
  9. 单击 Submit Query 按钮 (白色背景上的箭头)。查询结果会显示在下面的新窗口中。可以看到这些用户购买了 4 种印刷品:Antarctica、Australia、Las Vegas 和 Japan。请注意,左侧的 JSON 结果多次列出了这些印刷品:每列出该印刷品一次表示一次购买。右侧的可视摘要仅显示每种印刷品一次。
  10. 添加到现有查询。现在您已经知道了推荐的印刷品,需要按名称将它们进行分组,对它们排序,并列出最畅销的 3 本印刷品。在 Query Execution Box 中,输入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    gt.V().hasLabel("print").has("name", "Alaska").as("currentPrint")
    .in("buys")
    .out("buys").where(neq("currentPrint"))
    .groupCount().by('name').order(local).by(valueDecr).limit(local, 3);
  11. 单击 Submit Query 按钮 (白色背景上的箭头)。
  12. 查询结果会显示在下面的新窗口中。Las Vegas 被购买了 3 次,Antarctica 被购买了 2 次,Japan 被购买了 1 次。Australia 也被购买了 1 次,所以它是与 Japan 同样有效的推荐。或者,您可以更新查询,指定在印刷品被购买相同次数时应采用何种排序顺序,但我们将暂时跳过这一步。
  13. 此时,您的工作可能已经完成了,因为您已经生成了一个有序的推荐列表。但是,要让应用程序显示与每项推荐有关联的图像,除了需要 name 属性之外,还需要 imgPath 属性。更新查询以添加一个名为 byNameImgPath 的新函数,该函数负责将图像名称和图像路径存储在查询结果中。将 by('name') 替换为 by(byNameImgPath),以调用这个新函数。在 Query Execution Box 中,键入下面这个 Gremlin 查询:
    def gt = graph.traversal();
    java.util.function.Function byNameImgPath = { Vertex v -> "" + v.value("name") + ":"+ v.value("imgPath") };
    gt.V().hasLabel("print").has("name", "Alaska").as("currentPrint")
    .in("buys")
    .out("buys").where(neq("currentPrint"))
    .groupCount().by(byNameImgPath).order(local).by(valueDecr).limit(local, 3);
  14. 单击 Submit Query 按钮 (白色背景上的箭头)。
  15. 查询结果会显示在下面的新窗口中。除了名称之外,结果中现在还显示了 imgPath。查询已准备就绪!

编写新推荐引擎的代码

确认该查询成功生成了推荐后,是时候编写代码了!

  1. 在另一个选项卡或窗口中打开的 Web IDE 的文件导航窗格中,单击 graph.py 打开它。
  2. 在第 42 行上的 getUser() 函数上方,粘贴以下代码:
def getCommonlyPurchasedPrints(printName):
  
  # Generate a list of commonly purchased prints by searching for what
  # the people who have bought this print also purchased
  gremlin = {
    # create a new traversal
    "gremlin": "def gt = graph.traversal();" + 
      # create a function that handles storing both the image name and image path in the results
      "java.util.function.Function byNameImgPath = { Vertex v -> \"\" + v.value(\"name\") + \":\" + v.value(\"imgPath\") };" +
      # search for the node of the designated print and name it "currentPrint"
      "gt.V().hasLabel(\"print\").has(\"name\", \"" + printName + "\").as(\"currentPrint\")" +
      # go in to find all of the users who bought the designated print
      ".in(\"buys\")" + 
      # go out to find all prints (excluding the designated print) that these users purchased
      ".out(\"buys\").where(neq(\"currentPrint\"))" +  
      # group and sort to find the top 3 most commonly purchased prints
      ".groupCount().by(byNameImgPath).order(local).by(valueDecr).limit(local, 3);"
    }  
  
  response = post(constants.API_URL + '/' + constants.GRAPH_ID + '/gremlin', json.dumps(gremlin))

  if (response.status_code == 200): 
    results = json.loads(response.content)['result']['data']
    if len(results) > 0:
      results = results[0]
      # We lose the sorting from the query results when we do json.loads.
      # Sort the results in descending order by value.
      results = sorted(results.items(), key=itemgetter(1), reverse=True)
      prints = []      
      for p in results: 
        newPrint = {}
        newPrint['name'] = p[0].split(':', 1)[0]
        newPrint['imgPath'] = p[0].split(':', 1)[1]
        prints.append(newPrint)
        print 'Found print commonly purchased with %s: %s' % (printName, newPrint['name'])
      return prints
        
  raise ValueError('An error occurred while getting a list of commonly purchased prints for print %s: %s %s.' % (printName, response.status_code, response.content))

备注:在 Python 中编程时,空格很重要。确保您使用了空格来适当缩进代码。

让我们分析一下此函数的作用。首先,该代码创建了一个包含该 Gremlin 查询的字典。此查询非常类似于我们在上一节中生成的查询;但没有查询名为 Alaska 的印刷品,该代码使用了基于传入函数中的 printName 参数的动态输入。其次,该代码将查询包含在发往 Gremlin API 的 POST 请求中。第三,该代码将处理结果。如果响应为 200,则表明该查询已成功执行。因为 json.loads() 失失去了查询结果的排序,所以该代码将对结果进行重新排序。该代码创建了一个名为 prints 的新列表,其中存储了所推荐印刷品的信息。然后该代码循环处理每个 result,对每种推荐印刷品的 nameimgPath 进行排序。最后,如果所有操作均已顺利完成,该代码将返回 prints。如果出现错误,该代码会抛出一个 ValueError

  1. 完成后端代码的执行后,是时候将推荐的印刷品传输到前端了。在 Web IDE 中的左侧导航窗格中,单击 wsgi.py 打开它。
  2. 找到第 73 行上的 getPrint() 函数。只要用户访问某个印刷品的细节信息页面,就会调用此函数。该函数中的 try 语句的最后一行返回了印刷品页的模板。更新发送到 try 语句中的 bottle.template() 的参数,以包含推荐的印刷品:
    return bottle.template('print', username = 
    request.get_cookie("account", secret=constants.COOKIE_KEY),
    	printInfo = printInfo, commonlyPurchasedPrints = 
    graph.getCommonlyPurchasedPrints(printName))
    提示:确保 return 之前的空格保持相同。
  3. 印刷品模板能访问推荐的印刷品后,您需要更新它来显示这些印刷品。在 Web IDE 中的左侧导航窗格中,展开 views 目录并单击 print.tpl 打开它。
  4. 在表单的结束标记之后,但在包含第 28 行的页脚的行之前,粘贴以下代码:
% if len(commonlyPurchasedPrints) > 0:
			<h3>Users who ordered this print also ordered...</h3>
			<div class='container'>
				<div class='row'>		
						% for p in commonlyPurchasedPrints:			
							<div class="preview span3">
								<a href="{{p['name']}}">
									{{p['name']}}<br>
									<img src="/static/images/{{p['imgPath']}}" class="thumb">
								</a>
							</div>
						% end
				</div>
			</div> 
		% end

让我们分析一下此代码的作用。该代码首先检查是否至少有一种常购买印刷品可供推荐。如果有,该代码会创建一个包含文本 “Users who ordered this print also ordered ...” 的 3 级标题。然后该代码会循环处理常购买印刷品,显示每种印刷品的名称和图像。

部署新推荐引擎的代码

编写新推荐引擎的代码后,让我们测试一下:

  1. 在 Web IDE 顶部的工具栏中,单击 Deploy the App from the Workspace 按钮 (黑色背景上指向右侧的白色三角形)。部署可能会花一两分钟的时间。在完成应用程序部署后,工具栏中的应用程序名称旁边会出现表示状态的绿点。表示状态的绿点
    表示状态的绿点
  2. 在部署应用程序后,单击 Web IDE 顶部工具栏中的 Open the Deployed App 按钮 (部署应用程序)。部署的 Lauren's Lovely Landscapes 版本将会打开。
  3. 在部署的应用程序中,单击 Alaska 打开 Alaska 印刷品的详细信息页面。
  4. 向下滚动来查看刚刚实现的推荐功能!大功告成!

结束语

花点时间回想一下您在本教程中完成的所有工作。您首先学习了现有推荐引擎背后的理论,逐行分析了该推荐引擎背后的查询,并查看了显示推荐产品的代码。然后您实现了自己的推荐引擎来显示通常购买的产品,并看到了该功能在 Bluemix 上的实际运行。

掌握基础知识后,可以继续创建更好的推荐结果。或许您想将推荐限制到最近购买的产品。或者您想根据比通常购买的印刷品更多的因素对用户进行分组 - 或许您想考虑用户的人口统计特征或社会联系。或许您想使用 Watson 的 Visual Recognition 揭示印刷品本身的相似性,并根据这些相似性进行推荐。选择数不胜数。借助 Gremlin 图形遍历语言的强大功能和 IBM Graph 的易用性,您能够创建难以置信的自定义推荐引擎。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Cloud computing, Open source, SOA and web services
ArticleID=1044239
ArticleTitle=图形数据库介绍,第 2 部分: 使用图形数据库构建推荐引擎
publish-date=03212017