グラフ走査
ストリーミング式を使用したグラフ走査では、`nodes` 関数を使用して幅優先グラフ走査を実行します。
`nodes` 関数は、`scoreNodes` 関数と組み合わせて推奨事項を提供できます。 `nodes` は、より広範なストリーミング式ライブラリと組み合わせて、収集されたノードセットに対して複雑な操作を実行することもできます。
`nodes` 走査は SolrCloud コレクション内で分散され、コレクションをまたぐことができます。
`nodes` は、グラフ内の近傍にズームインし、正確な走査を実行してノードセットと集計を収集するユースケース向けに設計されています。 これらのタイプのユースケースでは、`nodes` は多くの場合 1 秒未満のパフォーマンスを提供します。いくつかのサンプルユースケースは、ドキュメントの後半で提供されています。.
このドキュメントでは、グラフの用語とストリーミング式に関する基本的な理解があることを前提としています。 グラフ走査の概念については、この Wikipedia の記事 から始めることができます。ストリーミング式の詳細については、このガイドの ストリーミング式 セクションを参照してください。 |
基本構文
最も基本的な構文から始めて、徐々に複雑さを増していきます。 `nodes` の最も基本的な構文は次のとおりです。
nodes(emails,
walk="johndoe@apache.org->from",
gather="to")
この簡単な式を分解してみましょう。
最初のパラメータ `emails` は、走査されるコレクションです。 2 番目のパラメータ `walk` は、ハードコードされたノード ID ("johndoe@apache.org") をインデックス内のフィールド (`from`) にマップします。 これにより、`from` フィールドに `johndoe@apache.org` が含まれるインデックス内のすべての **エッジ** が返されます。
`gather` パラメータは、関数に `to` フィールドの値を収集するように指示します。収集される値は、関数によって出力されるノード ID です。
上記の例では、出力されるノードは、「johndoe@apache.org」がメールを送信したすべての人になります。
walk パラメータは、ルートノード ID のリストも受け入れます。
nodes(emails,
walk="johndoe@apache.org, janesmith@apache.org->from",
gather="to")
上記の `nodes` 関数は、`from` フィールドに「johndoe@apache.org」または「janesmith@apache.org」を含むすべてのエッジを見つけ、`to` フィールドを収集します。
すべての ストリーミング式 と同様に、`nodes` 式は `/stream` ハンドラに送信することで実行できます。 例えば
curl --data-urlencode 'expr=nodes(emails,
walk="johndoe@apache.org, janesmith@apache.org->from",
gather="to")' https://127.0.0.1:8983/solr/emails/stream
この式の出力は次のようになります。
{
"result-set": {
"docs": [
{
"node": "slist@campbell.com",
"collection": "emails",
"field": "to",
"level": 1
},
{
"node": "catherine.pernot@enron.com",
"collection": "emails",
"field": "to",
"level": 1
},
{
"node": "airam.arteaga@enron.com",
"collection": "emails",
"field": "to",
"level": 1
},
{
"EOF": true,
"RESPONSE_TIME": 44
}
]
}
}
返されるすべてのタプルには、`node` フィールドがあります。 `node` フィールドには、関数によって収集されたノード ID が含まれています。 走査の `collection`、`field`、`level` も出力に含まれます。
例の各タプルのレベルが「1」であることに注意してください。 ルートノードはレベル 0 です (上記の例では、ルートノードは「johndoe@apache.org」、「janesmith@apache.org」です)。 デフォルトでは、`nodes` 関数は走査の **リーフノード** (最外部ノードセット) のみを出力します。 ルートノードを出力するには、`scatter` パラメータを指定します。
nodes(emails,
walk="johndoe@apache.org->from",
gather="to",
scatter="branches, leaves")
`scatter` パラメータは、**ブランチ** を **リーフ** とともに出力するかどうかを制御します。 ルートノードは、走査の最外部レベルではないため、「ブランチ」と見なされます。
ブランチとリーフの両方を出力する場合、出力は次のようになります。
{
"result-set": {
"docs": [
{
"node": "johndoe@apache.org",
"collection": "emails",
"field": "node",
"level": 0
},
{
"node": "slist@campbell.com",
"collection": "emails",
"field": "to",
"level": 1
},
{
"node": "catherine.pernot@enron.com",
"collection": "emails",
"field": "to",
"level": 1
},
{
"node": "airam.arteaga@enron.com",
"collection": "emails",
"field": "to",
"level": 1
},
{
"EOF": true,
"RESPONSE_TIME": 44
}
]
}
}
これで、レベル 0 のルートノードが出力に含まれるようになりました。
集計
nodes
は集計もサポートしています。例えば
nodes(emails,
walk="johndoe@apache.org, janesmith@apache.org->from",
gather="to",
count(*))
上記の式は、from
フィールドに "johndoe@apache.org" または "janesmith@apache.org" が含まれるエッジを見つけ、to
フィールドから値を収集します。また、収集された各ノードIDのカウントを集計します。
"johndoe@apache.org" と "janesmith@apache.org" の両方が同じ人物にメールを送信した場合、収集されたノードのカウントは 2 になる可能性があります。ノードセットには一意のノードセットが含まれているため、同じ人物がノードセットに2回表示されることはありませんが、カウントにはトラバーサル中に2回出現したことが反映されます。
エッジはトラバーサルの一部として一意化されるため、カウントは "johndoe@apache.org" が同じ人物にメールを送信した回数には**反映されません**。たとえば、personA が personB に 100 回メールを送信したとします。これらのエッジは一意化され、1回だけカウントされます。しかし、personC も personB にメールを送信した場合、personB のカウントは増加します。
サポートされている集計関数は、count(*)
、sum(field)
、min(field)
、max(field)
、および avg(field)
です。集計されるフィールドは、トラバーサル中に収集されたエッジに存在する必要があります。後の例(下記)では、集計が推奨事項を提供し、トラバーサルの範囲を制限するための強力なツールになることを示します。
nodes 関数のネスト
nodes
関数は、グラフをより深くトラバースするためにネストすることができます。例えば
nodes(emails,
nodes(emails,
walk="johndoe@apache.org->from",
gather="to"),
walk="node->from",
gather="to")
上記の例では、外側の nodes
関数は、内側の nodes
関数から収集されたノードセットに対して動作します。
内側の nodes
関数は、既に説明した例とまったく同じように動作することに注意してください。しかし、外側の nodes
関数の walk
パラメータは異なる動作をします。
外側の nodes
関数では、walk
パラメータは内部ストリーミング式からのタプルで動作します。このシナリオでは、walk
パラメータは node
フィールドを from
フィールドにマッピングします。内部の nodes
式から収集されたノードIDは node
フィールドに配置されることを覚えておいてください。
より簡単に言えば、内部式は "johndoe@apache.org" がメールを送信したすべての人物を収集します。このグループを "johndoe@apache.org の友人" と呼ぶことができます。外部式は、"johndoe@apache.org の友人" がメールを送信したすべての人物を収集します。これは、基本的な友人の友人のトラバーサルです。
nodes
関数をネストするこの構成は、グラフを制御しながらトラバースするための基本的な手法です。
サイクル検出
nodes
関数は、トラバーサル全体でサイクル検出を実行します。これにより、既に訪問されたノードが再びトラバースされないことが保証されます。サイクル検出は、トラバーサルのサイズを制限し、正確な集計を収集するために重要です。サイクル検出がない場合、トラバーサルのサイズはトラバーサル内の各ホップで指数関数的に増加する可能性があります。サイクル検出では、新たに出会ったノードのみがトラバースされます。
サイクル検出はコレクションの境界を**越えません**。これは、内部的にはコレクション名がノードIDの一部であるためです。たとえば、ノードID "johndoe@apache.org" は実際には emails/johndoe@apache.org
です。別のコレクションにトラバースする場合、"johndoe@apache.org" はトラバースされます。
ルートストリーム
任意のストリーミング式を使用して、トラバーサルのルートノードを提供できます。例えば
nodes(emails,
search(emails, q="body:(solr rocks)", fl="to", sort="score desc", rows="20")
walk="to->from",
gather="to")
上記の例では、検索式を介してルートノードを提供しています。また、結合などを含む任意の複雑なネストされたストリーミング式を提供して、ルートノードを指定することもできます。
walk
パラメータは、内部ストリームによって生成されたタプルからのフィールドをマッピングすることに注意してください。この場合、内部ストリームの to
フィールドを from
フィールドにマッピングします。
高頻度ノードのスキップ
グラフ内の高頻度ノードのトラバースをスキップすることが望ましいことがよくあります。これは、検索語のストップリストと性質が似ています。これを説明する最良の方法は、ユースケースの例を示すことです。
協調フィルタに基づいてユーザーにコンテンツを推奨したいとしましょう。以下は、単純な協調フィルタのアプローチです。
-
userA が読んだすべてのコンテンツを見つけます。
-
読書リストが userA に最も近いユーザーを見つけます。これらは userA と同様の嗜好を持つユーザーです。
-
手順2のユーザーが読んでいて、userA がまだ読んでいないコンテンツを推奨します。
手順2をよく見てください。大規模なグラフでは、手順2は非常に大規模なトラバーサルにつながる可能性があります。これは、userA が何百万人もの他の人々が閲覧したコンテンツを閲覧している可能性があるためです。これらの高頻度ノードをスキップするには、2つの理由があります。
-
数百万個の一意のノードを訪問する大規模なトラバーサルは、サイクル検出がメモリ内で追跡されるため、低速で多くのメモリを消費します。
-
高頻度ノードは、同様の嗜好を持つユーザーを判別するのにも役立ちません。より少ない人が閲覧したコンテンツは、より正確な推奨事項を提供します。
nodes
関数には、高頻度ノードをフィルタリングするための maxDocFreq
パラメータがあります。以下のサンプルコードは、推奨事項の手順1と2を示しています。
nodes(logs,
search(logs, q="userID:user1", fl="articleID", sort="articleID asc", fq="action:view", qt="/export"),
walk="articleID->articleID",
gather="userID",
fq="action:view",
maxDocFreq="10000",
count(*)))
上記の例では、内部検索式は logs
コレクションを検索し、"user1" が閲覧したすべての記事を返します。外側の nodes
式は、内部検索式から出力されたすべての記事を取得し、それらの記事の logs コレクション内のすべてのレコードを見つけます。次に、記事を読んだユーザーを収集して集計します。maxDocFreq
パラメータは、返される記事を(シャードごとに)10,000件以下のログレコードに表示される記事に制限します。これは、何百万人ものユーザーが閲覧した記事が返されるのを防ぎます。
トラバーサルの追跡
デフォルトでは、nodes
関数はサイクル検出を行うのに十分な情報のみを追跡します。これは、グラフ内のノードと集計を出力するのに十分な情報を提供します。
グラフの視覚化など、一部のユースケースでは、エッジを出力する必要もあります。trackTraversal="true"
を設定すると、nodes
はノード間の接続を追跡するため、エッジを構築できます。trackTraversal
が有効になっている場合、新しい ancestors
プロパティが各ノードに表示されます。ancestors
プロパティには、ノードを指していたノードIDのリストが含まれています。
以下は、trackTraversal
が true に設定された nodes
式の例です。
nodes(emails,
nodes(emails,
walk="johndoe@apache.org->from",
gather="to",
trackTraversal="true"),
walk="node->from",
trackTraversal="true",
gather="to")
コレクション間のトラバーサル
ネストされた nodes
関数は、異なる SolrCloud コレクションで動作できます。これにより、トラバーサルは1つのコレクションから別の koleksiyon に「移動」してノードを収集できます。サイクル検出はコレクションの境界を越えないため、1つのコレクションで収集されたノードは別のコレクションでトラバースされます。これは、コレクション間のトラバーサルをサポートするために意図的に行われました。コレクション間のトラバーサルの出力には、異なるコレクション属性を持つ重複ノードが含まれる可能性があることに注意してください。
以下は、"emails" コレクションから "logs" コレクションにトラバースする nodes
式の例です。
nodes(logs,
nodes(emails,
search(emails, q="body:(solr rocks)", fl="from", sort="score desc", rows="20")
walk="from->from",
gather="to",
scatter="leaves, branches"),
walk="node->user",
fq="action:edit",
gather="contentID")
上記の例では、本文に「solr rocks」が含まれるメールを送信したすべての人を見つけます。次に、これらの人々がメールを送信したすべての人を見つけます。次に、logs コレクションにトラバースし、これらの人々が編集したすべてのコンテンツIDを収集します。
nodes と他のストリーミング式の組み合わせ
nodes
関数は、ストリームソースとストリームデコレータの両方として機能できます。より広範なストリーム式ライブラリとの接続により、グラフトラバーサルを実行する際に非常に強力で柔軟性があります。2つのフレンドネットワークを交差させるためにストリーミング式ライブラリを使用する例を次に示します。
intersect(on="node",
sort(by="node asc",
nodes(emails,
nodes(emails,
walk="johndoe@apache.org->from",
gather="to"),
walk="node->from",
gather="to",
scatter="branches,leaves")),
sort(by="node asc",
nodes(emails,
nodes(emails,
walk="janedoe@apache.org->from",
gather="to"),
walk="node->from",
gather="to",
scatter="branches,leaves")))
上記の例では、"johndoe@apache.org" をルートとするフレンドネットワークと "janedoe@apache.org" をルートとするフレンドネットワークの2つの別々のフレンドネットワークを収集します。次に、フレンドネットワークは node
フィールドでソートされ、交差されます。結果のノードセットは、2つのフレンドネットワークの交差部分になります。
グラフトラバーサルのユースケース例
マーケットバスケットの共起性を計算する
どの製品が特定の製品と最も頻繁に購入されるかを知ることはしばしば役に立ちます。この例では、過去のショッピングバスケットを格納するために、単純なマーケットバスケットテーブル(Solrにインデックス付けされています)を使用します。テーブルのスキーマは非常にシンプルで、各行には basketID
と productID
が含まれています。これは、テーブルの各行がエッジを表すグラフと見なすことができます。また、グラフに数十億のエッジが含まれている場合でも、バスケットの共起性を計算するために非常に迅速にトラバースできます。
サンプル構文は次のとおりです。
top(n="5",
sort="count(*) desc",
nodes(baskets,
random(baskets, q="productID:ABC", fl="basketID", rows="500"),
walk="basketID->basketID",
fq="-productID:ABC",
gather="productID",
count(*)))
このトラバーサルが何をしているのかを正確に分析してみましょう.
-
最初に評価される式は、内部の
random
式で、baskets
コレクションから、productID
が "ABC" であるランダムな basketID を 500 個返します。random
式は、トラバーサルを固定数のバスケットに制限し、推奨事項にサプライズ要素を追加するため、推奨事項に非常に役立ちます。random
関数を使用すると、非常に大きなグラフから高速なサンプルセットを提供できます。 -
外側の
nodes
式は、手順1で生成された basketID のbaskets
コレクション内のすべてのレコードを見つけます。また、結果に表示されないようにproductID
"ABC" を除外します。次に、これらのバスケット全体の productID を収集してカウントします。 -
外側の
top
式は、手順2で出力された productID をカウントでランク付けし、上位5つを選択します。
要するに、この式は、過去のショッピングバスケットで product "ABC" と最も頻繁に共起する製品を見つけます。
scoreNodes 関数を使用して推奨事項を作成する
このユースケースは、productID:ABC と最も頻繁に共起する製品を計算する上記のマーケットバスケットの例に基づいています。ランク付けされた共起カウントは、推奨事項の候補を提供します。scoreNodes
関数を使用して候補をスコアリングし、最適な推奨事項を見つけることができます。
scoreNodes
関数の構文について詳しく説明する前に、生の共起カウントが最適な推奨事項を生成しない可能性がある理由を理解しておくと役立ちます。その理由は、生の共起カウントは、すべてのバスケットで頻繁に発生するアイテムを優先するためです。より良い推奨事項は、productID ABC と最も重要な関係を持つ製品を見つけることです。scoreNodes
関数は、単語頻度-逆文書頻度(TF-IDF)アルゴリズムを使用して、最も重要な関係を見つけます。
scoreNodes の仕組み
scoreNodes
関数は、nodes式によって出力された各ノードにスコアを割り当てます。デフォルトでは、scoreNodes
関数は、TF値として共起カウントであるcount(*)
集計を使用します。各ノードのIDF値は、ノードが収集されたコレクションから取得されます。次に、各ノードはTF*IDF式を使用してスコアリングされ、すべてのマーケットバスケットで頻度が低いノードにブーストが提供されます。
共起カウントとIDFを組み合わせることで、製品ID ABCと推奨候補間の関係の重要性を示すスコアが得られます。
scoreNodes
関数は、nodeScore
フィールドの各ノードにスコアを追加します。
scoreNodes構文の例
top(n="1",
sort="nodeScore desc",
scoreNodes(top(n="50",
sort="count(*) desc",
nodes(baskets,
random(baskets, q="productID:ABC", fl="basketID", rows="500"),
walk="basketID->basketID",
fq="-productID:ABC",
gather="productID",
count(*)))))
この例は、前の例「マーケットバスケットの共起カウントの計算」に基づいています。
-
最も内側の
top
関数は、製品ID ABCと最も頻繁に共起する上位50製品を取得していることに注意してください。これは、50の推奨候補を提供します。 -
次に、
scoreNodes
関数は、各ノードのTF*IDFに基づいて候補にスコアを割り当てます。 -
外側の
top
式は、最もスコアの高いノードを選択します。これが推奨事項です。
協調フィルタリングに基づくコンテンツの推奨
この例では、協調フィルタリングに基づいてユーザーにコンテンツを推奨します。この推奨は、userID
、articleID
、および実行されたアクションを含むログレコードを使用して行われます。このシナリオでは、各ログレコードはグラフのエッジと見なすことができます。userIDとarticleIDはノードであり、アクションはトラバーサルをフィルタリングするために使用されるエッジプロパティです。
サンプル構文は次のとおりです。
top(n="5",
sort="count(*) desc",
nodes(logs,
top(n="30",
sort="count(*) desc",
nodes(logs,
search(logs, q="userID:user1", fl="articleID", sort="articleID asc", fq="action:read", qt="/export"),
walk="articleID->articleID",
gather="userID",
fq="action:read",
maxDocFreq="10000",
count(*))),
walk="node->userID",
gather="articleID",
fq="action:read",
count(*)))
上記の式をステップバイステップで分解してみましょう。
-
最初に評価される式は、内側の
search
式です。この式は、logs
コレクションで「user1」と一致するすべてのレコードを検索します。これは、推奨を行うユーザーです。「action:read」のレコードのみを取得するために適用されるフィルターがあります。見つかった各レコードの
articleID
を返します。つまり、この式は「user1」が読んだすべての記事を返します。 -
内側の
nodes
式は、ステップ1から返されたarticleIDに対して動作します。見つかった各articleID
を取得し、articleID
フィールドに対して検索します。maxDocFreq
パラメーターを使用して高頻度ノードをスキップし、ログに10,000回以上出現する記事を除外していることに注意してください。userIDを収集し、各ユーザーのカウントを集計します。このステップでは、「user1」が読んだ記事と同じ記事を読んだユーザーを見つけ、同じ記事をいくつ読んだかをカウントします。 -
内側の
top
式は、ステップ2から出力されたユーザーをランク付けします。「user1」の読書リストと最も重複する上位30人のユーザーを出力します。 -
外側の
nodes
式は、ステップ3から出力されたユーザーの読書リストを収集します。収集されたarticleIDをカウントします。ステップ1(user1の読書リスト)で選択された記事は、サイクル検出のため、このステップには表示されません。そのため、このステップでは、「user1」と最も類似した読書習慣を持つユーザーが読んだ記事の中で、「user1」がまだ読んでいない記事が返されます。また、このユーザーグループ全体で各記事が読まれた回数をカウントします。
-
外側の
top
式は、ステップ4から出力された上位の記事を取得します。これが推奨事項です。
タンパク質経路トラバーサル
近年、科学者たちは、一部のがんの原因となるオンコジーンと呼ばれる変異タンパク質を標的とする薬剤を合理的に設計できるようになってきています。タンパク質は通常、経路と呼ばれる複数のタンパク質間の化学的相互作用の長い連鎖を通じて作用し、経路内のオンコジーンに対応する薬剤がない場合でも、経路内の別のタンパク質には薬剤が存在する可能性があります。タンパク質の相互作用と薬剤を記録するタンパク質コレクションのグラフトラバーサルは、候補となる可能性があります。(この例を提供してくれたNCBIのLewis Geerに感謝します)。
以下の例は、タンパク質経路トラバーサルを示しています。
nodes(proteins,
nodes(proteins,
walk="NRAS->name",
gather="interacts"),
walk="node->name",
gather="drug")
このトラバーサルが何をしているのかを正確に分析してみましょう.
-
内側の
nodes
式は、proteins
コレクションをトラバースします。タンパク質の名前が「NRAS」であるグラフ内のすべてのエッジを見つけます。次に、interacts
フィールドのタンパク質を収集します。これにより、「NRAS」と相互作用するすべてのタンパク質が収集されます。 -
外側の
nodes
式もproteins
コレクションで動作します。ステップ1から出力されたタンパク質に対応するすべての薬剤を収集します。 -
この段階的アプローチを使用すると、ルートタンパク質から任意のステップ数だけ離れた相互作用の経路に沿って薬剤を収集できます。
グラフの視覚化をサポートするためのGraphMLのエクスポート
上記の例では、nodes
式は、他のストリーミング式と同様にSolrの/stream
ハンドラーに送信されました。このアプローチは、他のストリーミング式と同じJSONタプル形式でノードを出力するため、他のストリーミング式と同様に扱うことができます。上記の推奨ユースケースのように、タプルを直接操作する必要がある場合は、/stream
ハンドラーを使用できます。
グラフの視覚化を含む他のグラフトラバーサルユースケースがあります。Solrは、nodes
式を受け取り、GraphMLで結果を出力する/graph
リクエストハンドラーを導入することで、これらのユースケースをサポートしています。
GraphMLは、Gephiなどのグラフ視覚化ツールでサポートされているXML形式です。Gephiは、グラフを統計的に分析および視覚化するための高度なオープンソースツールです。nodes
式を使用して、より大きなグラフの一部をGraphMLでエクスポートし、Gephiなどのツールにインポートできます。
GraphMLでグラフをエクスポートする際には、いくつかの注意点があります。
-
/graph
ハンドラーは、グラフ内のノードとエッジの両方をエクスポートできます。デフォルトでは、ノードのみをエクスポートします。エッジをエクスポートするには、nodes
式でtrackTraversal="true"
を設定する必要があります。 -
/graph
ハンドラーは現在、nodes
式を含む任意の複雑なストリーミング式を受け入れます。ストリーミング式にnodes
式が含まれていない場合、/graph
ハンドラーはGraphMLを正しく出力しません。 -
/graph
ハンドラーは現在、リクエストごとに1つの任意の複雑なネストされたnodes
式を受け入れます。これは、複数のnodes
式からのノードセットを結合または交差させるストリーミング式を送信できないことを意味します。/graph
ハンドラーは、単一のnodes
式内での任意のレベルのネストをサポートします。/stream
ハンドラーはノードセットの結合と交差をサポートしますが、/graph
ハンドラーは現在サポートしていません。
GraphMLリクエストの例
curl --data-urlencode 'expr=nodes(enron_emails,
nodes(enron_emails,
walk="kayne.coulter@enron.com->from",
trackTraversal="true",
gather="to"),
walk="node->from",
scatter="leaves,branches",
trackTraversal="true",
gather="to")' https://127.0.0.1:8983/solr/enron_emails/graph
GraphML出力の例
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<graph id="G" edgedefault="directed">
<node id="kayne.coulter@enron.com">
<data key="field">node</data>
<data key="level">0</data>
<data key="count(*)">0.0</data>
</node>
<node id="don.baughman@enron.com">
<data key="field">to</data>
<data key="level">1</data>
<data key="count(*)">1.0</data>
</node>
<edge id="1" source="kayne.coulter@enron.com" target="don.baughman@enron.com"/>
<node id="john.kinser@enron.com">
<data key="field">to</data>
<data key="level">1</data>
<data key="count(*)">1.0</data>
</node>
<edge id="2" source="kayne.coulter@enron.com" target="john.kinser@enron.com"/>
<node id="jay.wills@enron.com">
<data key="field">to</data>
<data key="level">1</data>
<data key="count(*)">1.0</data>
</node>
<edge id="3" source="kayne.coulter@enron.com" target="jay.wills@enron.com"/>
</graph></graphml>