2

Neo4j と Reco4PHP を使用して、単純なレコメンデーション エンジンを作成しようとしています。

データ モデルは、次のノードと関係で構成されます。

(ユーザー)-[:HAS_BOUGHT]->(製品 {category_id: int})-[:DESIGNED_BY]->(デザイナー)

このシステムでは、ユーザーが購入したのと同じデザイナーの商品をレコメンドし、ブーストしたいと考えています。レコメンデーションを作成するために、1 つの Discovery クラスと 1 つの Post-Processor クラスを使用して製品をブーストします。下記参照。これは機能しますが、非常に遅いです。データモデルには最大 1000 の製品と最大 100 のデザイナーが含まれているため、完了するまでに 5 秒以上かかります。

// 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 秒以上かかる場合は、運用環境では使用できません。

私が持っている速度を改善するには:

  • Product:id と Designer:id にインデックスを作成しました
  • node_auto_indexing=trueを neo4j.propertiesに追加します。
  • -Xmx4096mを .neo4j-community.vmoptions に追加しますが、実際には違いはありません。

これらの Cypher クエリに 5 秒以上かかるのは普通のことですか、それとも改善の余地はありますか? :)

4

2 に答える 2

2

主な問題は、ポスト プロセッサ クエリにあります。目標は次のとおりです。

おすすめアイテムをデザインしたデザイナーから購入した商品の数に基づいて、おすすめをブーストします。

したがって、クエリを少し変更して、デザイナーに直接一致させて集計することもできます。またUNWIND、製品 ID のすべての反復でユーザーと一致するため、最初にユーザーを検索してから検索することをお勧めします。

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

完全なポスト プロセッサは次のようになります。

    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')));
    }

私はあなたのドメインに従って完全に機能する実装を持つリポジトリを作成しました:

https://github.com/ikwattro/reco4php-example-so

データ受信後更新

ここに画像の説明を入力

製品とユーザーの間に同じタイプの関係が複数あるという事実は、見つかったパターンの数に指数関数を追加しています。

2 つの解決策があります。

それらを区別し、パターンの最後に WHERE 句を追加します。

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

Neo4j 3.0+ では、USING JOIN使用法を活用して、以前と同じクエリを保持できます。

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+の時間が 190 ミリ秒に短縮されました。post processing

于 2016-05-06T19:59:08.633 に答える
0

関数 GetItems() またはデータ (cypher ダンプ) が含まれていないため、Cypher についてのみコメントできます。しかし、目立ったものはほとんどありません

  1. (reco) のラベルを使用する方が高速です。製品だと思いますか?
  2. また、これは [:DESIGNED_BY]->()<-[:DESIGNED_BY]?
  3. 万が一 GetItems() がアイテムを 1 つずつ取得する場合、それが問題であり、インデックスが必要な場所でもある可能性があります。ところで、その条件をメイン クエリに入れてみませんか?

IDのインデックスもわかりませんか?それらが Neo4j id である場合、それらは物理的な場所であり、インデックスを作成する必要はありません。そうでない場合は、なぜ id() 関数を使用しますか?

結論として、ラベルは役立つかもしれませんが、データセットが大きい場合は奇跡を期待しないでください。Neo4j での集計は超高速ではありません。フィルターなしで 1,000 万件のレコードをカウントするのに 12 秒かかりました。

于 2016-05-06T14:09:41.747 に答える