paint-brush
モンスター狩りに革命を起こす: AI 強化 D&D エンカウンター@superlinked
新しい歴史

モンスター狩りに革命を起こす: AI 強化 D&D エンカウンター

Superlinked17m2025/01/31
Read on Terminal Reader

長すぎる; 読むには

この記事では、2 つの主要なアプローチを比較しながら、マルチ属性ベクトル検索について検討します。単純な方法では、各属性のベクトルを個別に保存し、個別の検索を実行してから、後処理によって結果をマージします。一方、スーパーリンク アプローチでは、すべての属性ベクトルを 1 つのベクトル ストアに結合し、クエリ時に属性を動的に重み付けできる統合検索を可能にして、後処理の必要性を排除します。 Dungeons & Dragons の例は、この方法の利点を示しており、外観、生息地、動作などの特定の基準に一致するモンスターを効率的に見つける方法を示しています。属性の重みを調整することで、このアプローチは従来の方法よりも正確で柔軟な検索結果を提供します。
featured image - モンスター狩りに革命を起こす: AI 強化 D&D エンカウンター
Superlinked HackerNoon profile picture
0-item
1-item

AI搭載の闇の軍隊

ゲームの夜です。友達はゲーム テーブルの周りに腰掛け、ダンジョンズ & ドラゴンズ (D&D) のどのキャラクターになり、どのクエストに乗り出すか待ち構えています。今夜、あなたはダンジョン マスター (ストーリーテラー兼ガイド) となり、プレイヤーに挑戦し魅了するスリリングな出会いを作り上げます。頼りになる D&D モンスター マニュアルには何千ものモンスターが収録されています。無数の選択肢の中から、それぞれの状況に最適なモンスターを見つけるのは大変なことです。理想的な敵は、その瞬間の設定、難易度、物語に合致している必要があります。


それぞれのシナリオに最も適したモンスターを瞬時に見つけるツールを作成できたらどうでしょうか?複数の要素を同時に考慮し、それぞれの遭遇が可能な限り没入感と興奮に満ちたものになるようにするツールでしょうか?


私たち自身の探求に乗り出しましょう: マルチ属性ベクトル検索の力を活用して、究極のモンスター検索システムを構築しましょう!

ベクター検索でクリーチャーを作成するのはなぜですか?

ベクトル検索は情報検索に革命をもたらします。コンテキストと意味論的意味を考慮するベクトル埋め込みにより、ベクトル検索はより関連性の高い正確な結果を返し、構造化データだけでなく非構造化データや複数の言語も処理し、拡張できるようになります。しかし、実際のアプリケーションで高品質の応答を生成するには、多くの場合、データ オブジェクトの特定の属性に異なる重みを割り当てる必要があります。

複数属性ベクトル検索には、2 つの一般的なアプローチがあります。どちらも、データ オブジェクトの各属性を個別に埋め込むことから始まります。これら 2 つのアプローチの主な違いは、埋め込みの保存方法と検索方法にあります。

  1. 単純なアプローチ - 各属性ベクトルを個別のベクトル ストア (属性ごとに 1 つ) に保存し、属性ごとに個別の検索を実行し、検索結果を結合し、必要に応じて後処理 (重み付けなど) を実行します。
  2. スーパーリンクアプローチ - すべての属性ベクトルを連結して同じベクトル ストアに保存します (スーパーリンクの組み込み機能を使用)。これにより、検索を 1 回だけ実行でき、効率が向上します。また、スーパーリンクのspaces使用すると、クエリ時に各属性に重みを付けて、後処理なしでより関連性の高い結果を表示できます。

多属性ベクトル検索への2つのアプローチ

以下では、これら 2 つのアプローチを使用して、複数属性ベクトル検索ツール (ダンジョンズ & ドラゴンズのモンスター ファインダー) を実装します。特に 2 番目のシンプルな実装では、ユース ケースに関係なく、複雑で多面的なクエリを簡単に処理できる、より強力で柔軟な検索システムを作成する方法を説明します。

ベクトル類似性検索を初めて使用する場合でも、心配しないでください。私たちがサポートします。ビルディング ブロックの記事をご覧ください。

よし、モンスター狩りに行こう!

データセット

まず、大規模言語モデル (LLM) を実行して、モンスターの小さな合成データセットを生成します。


 Generate two JSON lists: 'monsters' and 'queries'. 1. 'monsters' list: Create 20 unique monsters with the following properties: - name: A distinctive name - look: Brief description of appearance (2-3 sentences) - habitat: Where the monster lives (2-3 sentences) - behavior: How the monster acts (2-3 sentences) Ensure some monsters share similar features while remaining distinct. 2. 'queries' list: Create 5 queries to search for monsters: - Each query should be in the format: {look: "...", habitat: "...", behavior: "..."} - Use simple, brief descriptions (1-3 words per field) - Make queries somewhat general to match multiple monsters Output format: { "monsters": [ {"name": "...", "look": "...", "habitat": "...", "behavior": "..."}, ... ], "queries": [ {"look": "...", "habitat": "...", "behavior": "..."}, ... ] }


LLM が生成したデータセットのサンプルを見てみましょう。注: LLM 生成は非決定論的であるため、結果が異なる場合があります。

最初の 5 体のモンスターは次のとおりです。

#

名前

見て

生息地

行動

0

ルミノス

光る羽と触角を持つ蛾のような生き物

発光植物が生い茂る密林とジャングル

心地よい光のパターンを発して、獲物とコミュニケーションをとり、引き寄せます。

1

アクアレイス

流れる水でできた半透明の人型像

河川、湖沼、沿岸地域

水域に溶け込むように形を変え、流れを制御する

2

ストーンハートゴーレム

絡み合った岩石で構成された巨大な人型生物

ロッキー山脈と古代遺跡

何世紀も冬眠し、縄張りを守るために目覚める

3

ウィスパリング・シェイド

光る目を持つ影のような不定形の存在

暗い森と廃墟

恐怖を糧に、不安をかき立てる真実を囁く

4

ゼファーダンサー

虹色の羽を持つ優雅な鳥類

高い山の頂上と風が吹き渡る平原

仲間を引き付ける魅惑的な空中ディスプレイを作成します



...そして生成されたクエリ:


見て

生息地

行動

0

輝く

暗い場所

光の操作

1

エレメンタル

極限環境

環境制御

2

シェイプシフティング

多様な風景

幻想の創造

3

結晶質

鉱物資源が豊富な地域

エネルギー吸収

4

エーテル

雰囲気

心の影響


元のデータセットとクエリの例については、こちらをご覧ください。

検索

以下では、ナイーブとスーパーリンクの両方のアプローチで使用するパラメータを設定しましょう。

ベクトル埋め込みは次のように生成します。

 sentence-transformers/all-mpnet-base-v2.

簡単にするために、出力を上位 3 つの一致に制限します。(必要なインポートとヘルパー関数を含む完全なコードについては、ノートブックを参照してください。)

 LIMIT = 3 MODEL_NAME = "sentence-transformers/all-mpnet-base-v2"

それでは、多属性モンスターの検索を始めましょう! まず、単純なアプローチを試してみます。

素朴なアプローチ

私たちの素朴なアプローチでは、属性を個別に埋め込み、異なるインデックスに保存します。クエリ時に、すべてのインデックスに対して複数の kNN 検索を実行し、すべての部分的な結果を 1 つに結合します。

まずクラスを定義する

NaiveRetriever

all-mpnet-base-v2で生成された埋め込みを使用して、データセットに対して類似性ベースの検索を実行します。

 class NaiveRetriever: def __init__(self, data: pd.DataFrame): self.model = SentenceTransformer(MODEL_NAME) self.data = data.copy() self.ids = self.data.index.to_list() self.knns = {} for key in self.data: embeddings = self.model.encode(self.data[key].values) knn = NearestNeighbors(metric="cosine").fit(embeddings) self.knns[key] = knn def search_key(self, key: str, value: str, limit: int = LIMIT) -> pd.DataFrame: embedding = self.model.encode(value) knn = self.knns[key] distances, indices = knn.kneighbors( [embedding], n_neighbors=limit, return_distance=True ) ids = [self.ids[i] for i in indices[0]] similarities = (1 - distances).flatten() # by definition: # cosine distance = 1 - cosine similarity result = pd.DataFrame( {"id": ids, f"score_{key}": similarities, key: self.data[key][ids]} ) result.set_index("id", inplace=True) return result def search(self, query: dict, limit: int = LIMIT) -> pd.DataFrame: results = [] for key, value in query.items(): if key not in self.knns: continue result_key = self.search_key(key, value, limit=limit) result_key.drop(columns=[key], inplace=True) results.append(result_key) merged_results = pd.concat(results, axis=1) merged_results["score"] = merged_results.mean(axis=1, skipna=False) merged_results.sort_values("score", ascending=False, inplace=True) return merged_results naive_retriever = NaiveRetriever(df.set_index("name"))


上記で生成したリストの最初のクエリを使用し、 naive_retriever使用してモンスターを検索してみましょう。

 query = { 'look': 'glowing', 'habitat': 'dark places', 'behavior': 'light manipulation' } naive_retriever.search(query)

私たちの

naive_retriever

各属性に対して次の検索結果を返します。

id

スコア_ルック

見て

ウィスパリング・シェイド

0.503578

光る目を持つ影のような不定形の存在

サンドストームジン

0.407344

輝くシンボルが渦巻く砂の渦

ルミノス

0.378619

光る羽と触角を持つ蛾のような生き物


素晴らしい! 返されたモンスターの結果は関連性があり、すべて何らかの「光る」特性を持っています。

他の 2 つの属性を検索したときに、単純なアプローチで何が返されるかを見てみましょう。

id

スコア_生息地

生息地

ウィスパリング・シェイド

0.609567

暗い森と廃墟

真菌ネットワーク

0.438856

地下洞窟と湿った森

ソーンヴァインエレメンタル

0.423421

草木が生い茂る遺跡と密林


id

スコア_行動

行動

生きたグラフィティ

0.385741

周囲に溶け込むように形を変え、色素を吸収する

クリスタルウィング・ドレイク

0.385211

貴重な宝石を蓄え、光を屈折させて強力な光線を作ることができる

ルミノス

0.345566

心地よい光のパターンを発して、獲物とコミュニケーションを取り、引き寄せます。


取得したモンスターはすべて、必要な属性を備えています。一見すると、単純な検索結果は有望に思えるかもしれません。しかし、 3 つの属性すべてを同時に備えたモンスターを見つける必要があります。結果をマージして、モンスターがこの目標をどの程度達成しているかを確認しましょう。

id

スコア_ルック

スコア_生息地

スコア_行動

ウィスパリング・シェイド

0.503578

0.609567


サンドストームジン

0.407344



ルミノス

0.378619


0.345566

真菌ネットワーク


0.438856


ソーンヴァインエレメンタル


0.423421


生きたグラフィティ



0.385741

クリスタルウィング・ドレイク



0.385211


ここで、単純なアプローチの限界が明らかになります。評価してみましょう。

  1. 属性による関連性:
    • look : 3 体のモンスターが回収されました (Whispering Shade、Sandstorm Djinn、Luminoth)。
    • habitat : look結果から関連するモンスターは 1 体のみでした (Whispering Shade)。
    • behavior : look結果から関連するモンスターは 1 体のみ (Luminoth) でしたが、これはhabitatに関連するモンスターとは異なります。
  2. 全体的な関連性:
    • 3 つの属性すべてを同時に満たすモンスターは 1 体も見つかりませんでした。
    • 結果は断片化されており、異なるモンスターは異なる属性に関連します。

つまり、単純な検索アプローチでは、すべての条件を一度に満たすモンスターを見つけることができません。属性ごとに積極的にモンスターをさらに取得することで、この問題を解決できるかもしれません。属性ごとに 3 匹ではなく 6 匹のモンスターで試してみましょう。このアプローチで生成されるものを見てみましょう。

id

スコア_ルック

スコア_生息地

スコア_行動

ウィスパリング・シェイド

0.503578

0.609567


サンドストームジン

0.407344

0.365061


ルミノス

0.378619


0.345566

星雲クラゲ

0.36627


0.259969

ドリームウィーバータコ

0.315679



量子ホタル

0.288578



真菌ネットワーク


0.438856


ソーンヴァインエレメンタル


0.423421


ミストファントム


0.366816

0.236649

ストーンハートゴーレム


0.342287


生きたグラフィティ



0.385741

クリスタルウィング・ドレイク



0.385211

アクアレイス



0.283581


これまでに 13 体のモンスター (小さなデータセットの半分以上) を取得しましたが、依然として同じ問題が残っています。つまり、3 つの属性すべてについて、これらのモンスターの 1 体も取得されなかったのです。


回収するモンスターの数を増やすと(6 体以上)、問題は解決するかもしれませんが、追加の問題が発生します。

  1. 運用環境では、より多くの結果を取得すると(複数の kNN 検索)、検索時間が大幅に長くなります。
  2. 新しい属性を導入するたびに、クエリ内のすべての属性を備えた「完全な」モンスターが見つかる可能性は指数関数的に低下します。これを防ぐには、さらに多くの最も近い近傍モンスター (モンスター) を取得する必要があり、取得されるモンスターの総数は指数関数的に増加します。
  3. 希望する属性をすべて備えたモンスターが見つかる保証はまだありません。
  4. すべての条件を満たすモンスターを一度に取得できたとしても、結果を調整するために追加のオーバーヘッドを費やす必要があります。

要するに、単純なアプローチは、特に本番環境で実行可能な複数属性検索には不確実性が高く非効率的です。

スーパーリンクアプローチ

2 番目のアプローチを実装して、単純なアプローチよりも優れているかどうかを確認しましょう。

まず、スキーマ、スペース、インデックス、クエリを定義します。

 @schema class Monster: id: IdField look: String habitat: String behavior: String monster = Monster() look_space = TextSimilaritySpace(text=monster.look, model=MODEL_NAME) habitat_space = TextSimilaritySpace(text=monster.habitat, model=MODEL_NAME) behavior_space = TextSimilaritySpace(text=monster.behavior, model=MODEL_NAME) monster_index = Index([look_space, habitat_space, behavior_space]) monster_query = ( Query( monster_index, weights={ look_space: Param("look_weight"), habitat_space: Param("habitat_weight"), behavior_space: Param("behavior_weight"), }, ) .find(monster) .similar(look_space.text, Param("look")) .similar(habitat_space.text, Param("habitat")) .similar(behavior_space.text, Param("behavior")) .limit(LIMIT) ) default_weights = { "look_weight": 1.0, "habitat_weight": 1.0, "behavior_weight": 1.0 }


次に、エグゼキュータを起動してデータをアップロードします。

 monster_parser = DataFrameParser(monster, mapping={monster.id: "name"}) source: InMemorySource = InMemorySource(monster, parser=monster_parser) executor = InMemoryExecutor(sources=[source], indices=[monster_index]) app = executor.run() source.put([df])


上記の単純なアプローチの実装で実行したのと同じクエリを実行してみましょう。

 query = { 'look': 'glowing', 'habitat': 'dark places', 'behavior': 'light manipulation' } app.query( monster_query, limit=LIMIT, **query, **default_weights )

id

スコア

見て

生息地

行動

ウィスパリング・シェイド

0.376738

光る目を持つ影のような不定形の存在

暗い森と廃墟

恐怖を糧に、不安をかき立てる真実を囁く

ルミノス

0.340084

光る羽と触角を持つ蛾のような生き物

発光植物が生い茂る密林とジャングル

心地よい光のパターンを発して、獲物とコミュニケーションをとり、引き寄せます。

生きたグラフィティ

0.330587

平面に生息する二次元的でカラフルな生き物

都市部、特に壁や看板

周囲に溶け込むように形を変え、色素を吸収します


さあ、出来上がりです! 今回は、戻ってきた上位のモンスターは、モンスターに持たせたい 3 つの特性の「平均」を表すスコアで高い順位にランクされています。各モンスターのスコアを詳しく見てみましょう。

id

見て

生息地

行動

合計

ウィスパリング・シェイド

0.167859

0.203189

0.005689

0.376738

ルミノス

0.126206

0.098689

0.115189

0.340084

生きたグラフィティ

0.091063

0.110944

0.12858

0.330587


2 番目と 3 番目の結果である Luminoth と Living Graffiti は、どちらも 3 つの望ましい特性をすべて備えています。トップの結果である Whispering Shade は、 behaviorスコア (0.006) に反映されているように、光の操作という点では関連性が低いものの、「光る」特徴と暗い環境により、 look (0.168) とhabitat (0.203) のスコアが非常に高く、合計スコアが最も高く (0.377)、全体的に最も関連性の高いモンスターとなっています。なんと素晴らしい進歩でしょう!

結果を再現できるでしょうか? 別のクエリを試して確認してみましょう。

 query = { 'look': 'shapeshifting', 'habitat': 'varied landscapes', 'behavior': 'illusion creation' }


id

スコア

見て

生息地

行動

ミストファントム

0.489574

変化する特徴を持つ、幽玄で霧のようなヒューマノイド

沼地、荒野、霧の海岸線

幻想とささやきで旅人を惑わす

ゼファーダンサー

0.342075

虹色の羽を持つ優雅な鳥類

高い山の頂上と風が吹き渡る平原

仲間を引き付ける魅惑的な空中ディスプレイを作成します

ウィスパリング・シェイド

0.337434

光る目を持つ影のような不定形の存在

暗い森と廃墟

恐怖を糧に、不安をかき立てる真実を囁く


素晴らしい!私たちの成果は今回も素晴らしいです。

データセットから特定のモンスターに似たモンスターを見つけたい場合はどうすればよいでしょうか。まだ見たことのないモンスター、Harmonic Coral で試してみましょう。このモンスターの属性を抽出し、クエリ パラメータを手動で作成することもできます。ただし、Superlinked にはクエリ オブジェクトで使用できるwith_vectorメソッドがあります。各モンスターの ID は名前であるため、次のように簡単にリクエストを表現できます。

 app.query( monster_query.with_vector(monster, "Harmonic Coral"), **default_weights, limit=LIMIT )


id

スコア

見て

生息地

行動

ハーモニックコーラル

1

振動する巻きひげを持つ、分岐した楽器のような構造

浅い海と潮だまり

複雑なメロディーを創り、感情を伝え、影響を与える

ドリームウィーバータコ

0.402288

オーロラのように光る触手を持つ頭足動物

深海の海溝と水中洞窟

近くの生き物の夢に影響を与える

アクアレイス

0.330869

流れる水でできた半透明の人型像

河川、湖沼、沿岸地域

水域に溶け込むように形を変え、流れを制御する


一番上の結果は、予想通り、最も関連性の高いハーモニック コーラルそのものです。検索で取得される他の 2 つのモンスターは、ドリームウィーバー オクトパスとアクア レイスです。どちらも、ハーモニック コーラルと重要なテーマ (属性) 要素を共有しています。

  1. 水生生息地( habitat
  2. 環境に影響を与えたり操作したりする能力( behavior
  3. 動的または流動的な視覚特性( look

属性の重み付け

ここで、 look属性にもっと重点を置きたいとします。スーパーリンク フレームワークを使用すると、クエリ時に重みを簡単に調整できます。簡単に比較できるように、 look優先するように重みを調整して、Harmonic Coral に似たモンスターを検索します。

 weights = { "look_weight": 1.0, "habitat_weight": 0, "behavior_weight": 0 } app.query( monster_query.with_vector(monster, "Harmonic Coral"), limit=LIMIT, **weights )


id

スコア

見て

生息地

行動

ハーモニックコーラル

0.57735

振動する巻きひげを持つ、枝分かれした楽器のような構造

浅い海と潮だまり

複雑なメロディーを創り、感情を伝え、影響を与える

ソーンヴァインエレメンタル

0.252593

ねじれた蔓と棘の体を持つ植物のような生き物

草木が生い茂る遺跡と密林

急速に成長し、周囲の植物を制御する

プラズマサーペント

0.243241

パチパチと音を立てるエネルギーでできた蛇のような生き物

雷雨と発電所

電流を供給し、技術を短絡させる可能性がある


私たちの結果はすべて(当然のことながら)似たような外観をしています - 「振動する巻きひげで枝分かれしている」、「ねじれた蔓とトゲでできた体を持つ植物のような生き物」、「ヘビのような」。

ここで、外見を無視して、 habitatbehaviorの点で同時に類似するモンスターを探す別の検索を実行してみましょう。

 weights = { "look_weight": 0, "habitat_weight": 1.0, "behavior_weight": 1.0 }


id

スコア

見て

生息地

行動

ハーモニックコーラル

0.816497

振動する巻きひげを持つ、分岐した楽器のような構造

浅い海と潮だまり

複雑なメロディーを創り、感情を伝え、影響を与える

ドリームウィーバータコ

0.357656

オーロラのように光る触手を持つ頭足動物

深海の海溝と水中洞窟

近くの生き物の夢に影響を与える

ミストファントム

0.288106

変化する特徴を持つ、幽玄で霧のようなヒューマノイド

沼地、荒野、霧の海岸線

幻想とささやきで旅人を惑わす


ここでも、スーパーリンク アプローチは素晴らしい結果を生み出します。3 つのモンスターはすべて水辺に生息し、マインド コントロール能力を備えています。

最後に、3 つの属性すべてに異なる重み付けをして、別の検索を試してみましょう。ハーモニック コーラルと比較して、見た目が多少似ていて、生息地が大きく異なり、行動も非常に似ているモンスターを見つけるためです。

 weights = { "look_weight": 0.5, "habitat_weight": -1.0, "behavior_weight": 1.0 }


id

スコア

見て

生息地

行動

ハーモニックコーラル

0.19245

振動する巻きひげを持つ、枝分かれした楽器のような構造

浅い海と潮だまり

複雑なメロディーを創り、感情を伝え、影響を与える

ルミノス

0.149196

光る羽と触角を持つ蛾のような生き物

発光植物が生い茂る密林とジャングル

心地よい光のパターンを発して、獲物とコミュニケーションをとり、引き寄せます。

ゼファーダンサー

0.136456

虹色の羽を持つ優雅な鳥類

高い山の頂上と風が吹き渡る平原

仲間を引き付ける魅惑的な空中ディスプレイを作成します



また素晴らしい結果です! 回収した他の 2 体のモンスター、Luminoth と Zephyr Dancer は、Harmonic Coral と似た行動をしますが、Harmonic Coral とは異なる生息地に生息しています。また、Harmonic Coral とは見た目も大きく異なります。(Harmonic Coral の触手と Luminoth の触角は似た特徴ですが、 look_weightを 0.5 だけ下げただけで、2 体のモンスターの類似点はそれだけです。)

これらのモンスターの総合スコアが個々の属性ごとにどのようになっているかを見てみましょう。

id

見て

生息地

行動

合計

ハーモニックコーラル

0.19245

-0.3849

0.3849

0.19245

ルミノス

0.052457

-0.068144

0.164884

0.149196

ゼファーダンサー

0.050741

-0.079734

0.165449

0.136456


habitat_weightに負の重み付け (-1.0) をすることで、同様の生息地を持つモンスターを意図的に「押しのけ」、代わりに Harmonic Coral とは異なる環境を持つモンスターを表面に出します。これは、Luminoth と Zephyr Dancer の負の生息habitatスコアに見られるとおりです。Luminoth と Zephyr Dancer のbehaviorスコアは比較的高く、Harmonic Coral との行動の類似性を示しています。 lookスコアは正ですが、低く、Harmonic Coral との視覚的な類似性が多少あるが極端ではないことを反映しています。

つまり、 habitat_weightを -1.0 に、 look_weightを 0.5 に下げ、 behavior_weight 1.0 のままにする戦略は、Harmonic Coral と主要な行動特性を共有しながらも、環境が大きく異なり、少なくとも多少見た目が異なるモンスターを表面化させるのに効果的であることが証明されています。

結論

マルチ属性ベクトル検索は情報検索の大きな進歩であり、基本的な意味的類似性検索よりも高い精度、コンテキスト理解、柔軟性を提供します。それでも、属性ベクトルを個別に保存して検索し、結果を結合する単純なアプローチ (上記) は、複数の同時属性を持つオブジェクトを取得する必要がある場合、機能、繊細さ、効率の点で制限があります。(さらに、複数の kNN 検索は、連結されたベクトルを使用した単一の検索よりも時間がかかります。)

このようなシナリオに対処するには、すべての属性ベクトルを同じベクトル ストアに保存し、単一の検索を実行して、クエリ時に属性に重み付けする方が適切です。スーパーリンク アプローチは、高速で信頼性が高く、微妙な違いのある複数の属性ベクトルの取得を必要とするあらゆるアプリケーションにおいて、単純なアプローチよりも正確で効率的、かつスケーラブルです。ユース ケースが、e コマースや推奨システムにおける現実世界のデータ課題への取り組みであっても、モンスターとの戦いなどまったく異なるものであっても同じです。

寄稿者


元々はここで公開されました。