我正在尝试使用Neo4j和Reco4PHP创建一个简单的推荐引擎。
数据模型由以下节点和关系组成:
(用户) - [:HAS_BOUGHT] - >(产品{category_id:int} ) - [:DESIGNED_BY] - GT;(设计器)
在这个系统中,我想推荐使用与用户已经购买的设计器相同的产品和产品。为了创建推荐,我使用了一个Discovery类和一个Post-Processor类来增强产品。见下文。这有效,但速度很慢。完成需要5秒以上,而数据模型可容纳约1000种产品和约100名设计师。
// Disovery class
<?php
namespace App\Reco4PHP\Discovery;
use GraphAware\Common\Cypher\Statement;
use GraphAware\Common\Type\NodeInterface;
use GraphAware\Reco4PHP\Engine\SingleDiscoveryEngine;
class InCategory extends SingleDiscoveryEngine {
protected $categoryId;
public function __construct($categoryId) {
$this->categoryId = $categoryId;
}
/**
* @return string The name of the discovery engine
*/
public function name() {
return 'in_category';
}
/**
* The statement to be executed for finding items to be recommended
*
* @param \GraphAware\Common\Type\NodeInterface $input
* @return \GraphAware\Common\Cypher\Statement
*/
public function discoveryQuery(NodeInterface $input) {
$query = "
MATCH (reco:Card)
WHERE reco.category_id = {category_id}
RETURN reco, 1 as score
";
return Statement::create($query, ['category_id' => $this->categoryId]);
}
}
// Boost shared designers
class RewardSharedDesigners extends RecommendationSetPostProcessor {
public function buildQuery(NodeInterface $input, Recommendations $recommendations)
{
$ids = [];
foreach ($recommendations->getItems() as $recommendation) {
$ids[] = $recommendation->item()->identity();
}
$query = 'UNWIND {ids} as id
MATCH (reco) WHERE id(reco) = id
MATCH (user:User) WHERE id(user) = {userId}
MATCH (user)-[:HAS_BOUGHT]->(product:Product)-[:DESIGNED_BY]->()<-[:DESIGNED_BY]-(reco)
RETURN id, count(product) as sharedDesignedBy';
return Statement::create($query, ['ids' => $ids, 'userId' => $input->identity()]);
}
public function postProcess(Node $input, Recommendation $recommendation, Record $record) {
$recommendation->addScore($this->name(), new SingleScore((int)$record->get('sharedDesignedBy')));
}
public function name() {
return 'reward_shared_designers';
}
}
我很高兴它有效,但如果计算时间超过5秒,则无法在生产环境中使用。
为了提高我的速度:
这些Cypher查询通常需要5秒以上或者是否可以进行一些改进? :)
答案 0 :(得分:2)
主要问题在于您的后期处理器查询。目标是:
根据我购买的产品数量提出建议 设计师设计推荐的项目。
因此,您可以修改您的查询以直接匹配设计器并在其上进行聚合,最好先在MATCH (user) WHERE id(user) = {userId}
UNWIND {ids} as productId
MATCH (product:Product)-[:DESIGNED_BY]->(designer)
WHERE id(product) = productId
WITH productId, designer, user
MATCH (user)-[:BOUGHT]->(p)-[:DESIGNED_BY]->(designer)
RETURN productId as id, count(*) as score
之前找到用户,否则它将在产品的每次迭代中与用户匹配ids:
public function buildQuery(NodeInterface $input, Recommendations $recommendations)
{
$ids = [];
foreach ($recommendations->getItems() as $recommendation) {
$ids[] = $recommendation->item()->identity();
}
$query = 'MATCH (user) WHERE id(user) = {userId}
UNWIND {ids} as productId
MATCH (product:Product)-[:DESIGNED_BY]->(designer)
WHERE id(product) = productId
WITH productId, designer, user
MATCH (user)-[:BOUGHT]->(p)-[:DESIGNED_BY]->(designer)
RETURN productId as id, count(*) as score';
return Statement::create($query, ['userId' => $input->identity(), 'ids' => $ids]);
}
public function postProcess(Node $input, Recommendation $recommendation, Record $record)
{
$recommendation->addScore($this->name(), new SingleScore($record->get('score')));
}
完整的后处理器如下所示:
MATCH (user) WHERE id(user) = {userId}
UNWIND {ids} as cardId
MATCH (reco:Card)-[:DESIGNED_BY]->(designer) WHERE id(reco) = cardId
MATCH (user)-[:HAS_BOUGHT]->(x)
WHERE (x)-[:DESIGNED_BY]->(designer)
RETURN cardId as id, count(*) as sharedDesignedBy
我创建了一个存储库,我的域名后面有一个功能齐全的实现:
https://github.com/ikwattro/reco4php-example-so
收到数据后更新
在产品和用户之间存在多个相同类型的关系这一事实是为找到的模式数添加了指数。
有两种解决方案:
区分它们并为模式的结尾添加WHERE子句:
USING JOIN
在Neo4j 3.0+中,您可以从MATCH (user) WHERE user.id = 245
UNWIND ids as id
MATCH (reco:Card) WHERE id(reco) = id
MATCH (user:User)-[:HAS_BOUGHT]->(card:Card)-[:DESIGNED_BY]->(designer:Designer)<-[:DESIGNED_BY]-(reco:Card)
USING JOIN ON card
RETURN id, count(card) as sharedDesignedBy
用法中受益,并保持与您相同的查询:
discovery
运行这些查询后,我使用您当前的数据集将post processing
+ <?xml version="1.0" encoding="utf-8"?>
<resources>
one
two
three
four
five
six
seven
eight
nine
ten
</resources>
的时间缩短到了190毫秒。
答案 1 :(得分:0)
我只能对Cypher发表评论,因为你没有包含函数GetItems()或数据(cypher dump)。 但很少有事情突出
我也不懂id上的索引?如果它们是Neo4j id,它们是物理位置,不需要编入索引,如果它们不是你使用id()函数的原因?
总之,标签可能有所帮助,但如果您的数据集很大,则不要指望奇迹,Neo4j上的聚合速度不是很快。计算没有过滤器的10M记录花了我12秒。