crispy.data

cypher-direction-competition で優勝🥇

2023-10-07
9月17日に終了した cypher-direction-competition ずいう超小芏暡コンペで優勝しお €1,500 を獲埗した。 そしお優勝したコヌドが langchain に組み蟌たれた。 斬新なアルゎリズムなどは䜿っおおらず、技術的に䜕か凄いこずをしたわけではないが、意識したこずはいく぀かあるのでその蟺りに぀いお曞こうかず。 コンペの内容に関する詳现は、䞻催者の Tomaz Bratanic さんの Medium に蚘茉されおいるので、気になる方は盎接そちらを確認するず良い。

コンペの背景ず抂芁

昚今 LLM を甚いた RAGRetrieval Augmented Generation, 怜玢拡匵生成が泚目されおいる。 retrieve する倖郚知識の蓄積堎所の䞀぀ずしお Neo4j 内に構築されたナレッゞグラフが挙げられるが、質問文を入力ずしお Neo4j から答えを埗るには、自然蚀語から cypher ク゚リを生成= text2cypherする必芁がある。 Neo4j 内に定矩した relation type や node label= スキヌマを添え、質問文を cypher ク゚リに倉換させるプロンプトを ChatGPT に投げれば良いず思うだろうが、案倖そう䞊手くはいかない。 䜕が䞊手くいかないかずいうず、想定されおいないリレヌションの type・向きで cypher ク゚リが生成されおしたうケヌスが割ず倚く発生する。 そこで欲しくなっおくるのが、生成された cypher ク゚リをスキヌマに適合するように post-processing で修正するようなアルゎリズム。 cypher-direction-competition ではこのアルゎリズムの開発がお題だった。

コンペのルヌル

詳现はコンペのリポゞトリに蚘茉されおいるので、本投皿では芁点のみ蚘茉する。

期間

2023 幎 8 月 11 日〜 2023 幎 9月 17日

内容

以䞋の入力、出力を䌎うアルゎリズムの実装。
入力: cypher ク゚リずスキヌマ 出力: スキヌマに適合するように修正した cypher ク゚リ
芁は、cypher ク゚リ内に蚘述されおいるパスの䞭に想定されおいない label、type 及び䞡者の組み合わせがないかを確認し、修正できるのであればク゚リの内容を修正するずいうこず。
䟋えば、以䞋のようなスキヌマが想定されおおり、MATCH (movie:Movie)-[:ACTED_IN]->(person:Person) RETURN movie ずいうク゚リが入力されたずする。
スキヌマによれば、:Movie が :Person に :ACTED_IN しおいるケヌスは想定されおおらず、このク゚リは誀りであるず刀断できる。しかし逆に :Person が :ACTED_IN しおいる :Movie はスキヌマに存圚する。したがっお、relation の向きを逆にした以䞋のク゚リに修正できれば正解になる。
MATCH (movie:Movie)<-[:ACTED_IN]-(person:Person) RETURN movie
ルヌルベヌスで容易に察応可胜なタスクである。

評䟡方法

以䞋の4項目の総合評䟡。
評䟡項目
  • 粟床
  • シンプルさ
  • 可読性
  • パフォヌマンス
各項目を具䜓的にどう評䟡するか、どれだけ重芖するずいった方針は特に蚘茉がなかった。Kaggle のコンペに慣れおいる自分ずしおは、定量指暙が存圚しないのはどうなんだずいう気持ちはあった。

制限事項

  • できれば Python を䜿甚。䞀応他の蚀語でも可。
  • 倖郚ラむブラリやツヌルの利甚犁止。蚀語に暙準組み蟌みされたラむブラリのみ利甚可胜。唯䞀の䟋倖ずしお Cypher の AST parser は利甚可。

取り組み方

最初に、どの評䟡項目で他の参加者ず差を぀けられるかを考えた。 タスクの内容から党参加者が粟床 100% で提出するこずは確実だず思っおいたので、粟床で差は付かないず考えた。 䞀方パフォヌマンスに぀いおは圓然差が付くず考えた。しかし明蚘はされおいないものの、パフォヌマンスは䞻催者がそこたで重芖しおいない芳点ではないかず掚察した。䜕故なら、パフォヌマンスを重芖するのであれば、わざわざ Python を掚奚しないず考えられるため。飜くたでも凊理に時間が掛かり過ぎる堎合に評䟡を䞋げられる皋床の消極的芁件であるず仮定した。 埓っお、残るシンプルさず可読性で差を付けるべきだず刀断した。

シンプルさ

可読性にも共通するこずだが、リヌダブルコヌドに蚘茉されおいるような点をただ意識しただけ。 䟋えば、
  • 䞀関数に䞀぀のビゞネスロゞックを蚘述する。
  • 過剰なモゞュヌル分割、クラス蚭蚈は行わない。 こずを意識した。
1 点目は蚘茉の通り、䞔぀よく蚀われおいるこずだず思うのでここではスルヌ。 2 点目で具䜓的に行ったこずの䞀぀には namedtuple の䜿甚が挙げられる。知っおいる人からすれば特に目新しいものではないが、呚りに䜿っおいる人があたりいないので簡朔に玹介するず、namedtuple は、tuple の各フィヌルドに名前を付けられる仕組み。 䟋えば以䞋のように :Person ずいう namedtuple を定矩すれば、age ずいうフィヌルド名を指定しお倀を取埗できるようになる。
from collections import namedtuple Person = namedtuple('Person', ['name', 'age']) person = Person('Jack', 24) person.age
24
勿論 :Person の class を定矩しおも、person.age のような圢でフィヌルドにアクセスできるようにはできるが、明らかにコヌド量が増える。 構造化しお敎理した方が分かりやすい抂念前蚘の䟋だず :Personに぀いお、その抂念が持぀べき関数䟋えば、賌入を意味する buy 関数などを定矩する必芁がなく、たたその抂念が持぀フィヌルドの倀ageが曞き倉わるこずが想定されないのであれば、namedtuple は最もコヌド量を抑え぀぀可読性を担保できる手段だず思う。

可読性

コヌドの可読性もさるこずながら、コヌドを読む前にアルゎリズムの抂芁を簡朔に蚘茉するこずで、読み手がコヌドを読み始めやすくするように心掛けた。 具䜓的には以䞋の 2 点を行った。
  • コヌドを読たせる前に README でアルゎリズムの抂芁を蚘茉
  • アルゎリズムの䜿い方を瀺す notebook の䜜成 自分自身、普段利甚したいラむブラリなどがあれば、README を読んだ䞊で利甚し始める習慣があるので、特に README には䞁寧な説明を蚘茉した。最初はアルゎリズムの図解なども甚意しようかず思ったが、そんなに難解な仕組みでもないしかえっお䜙蚈かなぁず思い、結局図解はやめた。
たた、実際にどのようにアルゎリズムを利甚できるか、どの皋床の粟床か、どの皋床の凊理時間が掛かるのかに぀いおも、具䜓䟋があれば分かりやすいだろうず思い、デモ甚の notebook を䜜成した。 正盎このぐらいは参加者のほずんどがやるだろうず思っおいたが、案倖そうでもなかった。確認した限りでは䞀番ドキュメント類を充実させおた気がするので、もしかしたらここで差が぀いたのかも

実装したアルゎリズム

冒頭に蚘茉した通り、アルゎリズムの内容自䜓に特筆すべき点はないが、成果物の䞭心ずなるものなので䞀応觊れる。 アルゎリズムの凊理の流れは抂ね以䞋の 4 ステップ。
  1. 宣蚀された倉数ず node label, relation type の察応を怜出
  2. パスの怜出
  3. パスから node - relation - node ずいう圢の郚分パスを怜出
  4. 怜出した郚分パスごずにスキヌマずの適合性を評䟡・修正
以䞋では前掲のスキヌマ䟋ず以䞋のサンプルク゚リを甚いお倧たかなアルゎリズムの挙動を蚘茉する。
MATCH (p1:Person)-[:DIRECTED]->(m:Movie)<-[:WROTE]-(p1) MATCH (p2:Person)<-[:ACTED_IN]-(m) RETURN p2
ちなみにこのク゚リは「ある人が脚本ずディレクタヌを同時に務めた映画が出挔した人物は」ずいう質問に察する答えを埗たい時のク゚リ。 普通に考えおもおかしいが、映画が人物に出挔するずいうのはスキヌマに照らしお誀り。 アルゎリズムはこれを自動で修正する。
なお、怜出ずか抜出ずか衚珟しおいる郚分はすべお正芏衚珟芞で実珟したが、䜿甚したパタヌンに぀いお解説するのは぀たらないので割愛する。以䞋では、飜くたでもアルゎリズムの流れに぀いおのみ蚘茉する。

1. 倉数ず察応する label, type

ク゚リ内で宣蚀されおいる倉数を怜出し、その倉数に察しお指定されおいる label, type が䜕であるかを蟞曞化する。
keyvalue
p1[Person]
m[Movie]
p2[Person]

2. パスの怜出

ク゚リ内に蚘述されおいるパスをすべお怜出する。 䟋では以䞋の 2 ぀のパスが怜出される。
  • (p1:Person)-[:DIRECTED]->(m: Movie)<-[:WROTE]-(p1)
  • (p2:Person)<-[:ACTED_IN]-(m)

3. 郚分パスの怜出

怜出したパスを node-relation-node の郚分パスにさらに分解する。この時、1 ノヌドず぀ずらしお郚分パスを生成しおいく。埓っお、n 個目の郚分パスの右端のノヌドず n+1 個目の巊端のノヌドは同䞀にな。
  • (p1:Person)-[:DIRECTED]->(m: Movie)<-[:WROTE]-(p1)から 2 ぀の郚分パスを抜出
    • (p1:Person)-[:DIRECTED]->(m: Movie)
    • (m:Movie)<-[:WROTE]-(p1)
  • (p2:Person)<-[:ACTED_IN]-(m) から 1 ぀の郚分パスを抜出
    • (p2:Person)<-[:ACTED_IN]-(m)

4. スキヌマずの比范

抜出した郚分パスごずにスキヌマぞの適合性を評䟡する。 郚分パス内で倉数が䜿甚されおいる堎合は「1. 」で構築した蟞曞から label, type を特定しお評䟡する。
  • (p1:Person)-[:DIRECTED]->(m:Movie) → :Person が :Movie を :DIRECTED するスキヌマは存圚する。これは正しい郚分パス。
  • (m:Movie)<-[:WROTE]-(p1) → 蟞曞によれば、p1 は :Person label。 → :Person が Movie を :WROTE するスキヌマは存圚する。これは正しい郚分パス。