AI搭載の闇の軍隊 ゲームの夜です。友達はゲーム テーブルの周りに腰掛け、ダンジョンズ & ドラゴンズ (D&D) のどのキャラクターになり、どのクエストに乗り出すか待ち構えています。今夜、あなたはダンジョン マスター (ストーリーテラー兼ガイド) となり、プレイヤーに挑戦し魅了するスリリングな出会いを作り上げます。頼りになる D&D モンスター マニュアルには のモンスターが収録されています。無数の選択肢の中から、それぞれの状況に最適なモンスターを見つけるのは大変なことです。理想的な敵は、その瞬間の設定、難易度、物語に合致している必要があります。 何千も それぞれのシナリオに最も適したモンスターを瞬時に見つけるツールを作成できたらどうでしょうか? でしょうか? 複数の要素を同時に考慮し、それぞれの遭遇が可能な限り没入感と興奮に満ちたものになるようにするツール 私たち自身の探求に乗り出しましょう: マルチ属性ベクトル検索の力を活用して、究極のモンスター検索システムを構築しましょう! ベクター検索でクリーチャーを作成するのはなぜですか? 情報検索に革命をもたらします。コンテキストと意味論的意味を考慮するベクトル埋め込みにより、ベクトル検索はより関連性の高い正確な結果を返し、構造化データだけでなく非構造化データや複数の言語も処理し、拡張できるようになります。しかし、実際のアプリケーションで高品質の応答を生成するには、多くの場合、データ オブジェクトの特定の属性に異なる重みを割り当てる必要があります。 ベクトル検索は 複数属性ベクトル検索には、2 つの一般的なアプローチがあります。どちらも、データ オブジェクトの各属性を個別に埋め込むことから始まります。これら 2 つのアプローチの主な違いは、埋め込みの 方法と にあります。 保存 検索方法 アプローチ - 各属性ベクトルを個別のベクトル ストア (属性ごとに 1 つ) に保存し、属性ごとに個別の検索を実行し、検索結果を結合し、必要に応じて後処理 (重み付けなど) を実行します。 単純な アプローチ - すべての属性ベクトルを連結して同じベクトル ストアに保存します (スーパーリンクの組み込み機能を使用)。これにより、 実行でき、効率が向上します。 スーパーリンクの 使用すると、クエリ時に各属性に重みを付けて、後処理なしでより関連性の高い結果を表示できます。 スーパーリンク 検索を 1 回だけ また、 spaces 以下では、これら 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 ここで、単純なアプローチの限界が明らかになります。評価してみましょう。 属性による関連性: : 3 体のモンスターが回収されました (Whispering Shade、Sandstorm Djinn、Luminoth)。 look : 結果から関連するモンスターは 1 体のみでした (Whispering Shade)。 habitat look : 結果から関連するモンスターは 1 体のみ (Luminoth) でしたが、これは に関連するモンスターとは異なります。 behavior look habitat 全体的な関連性: 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 体以上)、問題は解決する が、追加の問題が発生します。 かもしれません 運用環境では、より多くの結果を取得すると(複数の kNN 検索)、検索時間が大幅に長くなります。 新しい属性を導入するたびに、クエリ内のすべての属性を備えた「完全な」モンスターが見つかる可能性は指数関数的に低下します。これを防ぐには、さらに多くの最も近い近傍モンスター (モンスター) を取得する必要があり、取得されるモンスターの総数は指数関数的に増加します。 希望する属性をすべて備えたモンスターが見つかる保証はまだありません。 すべての条件を満たすモンスターを一度に取得できたとしても、結果を調整するために追加のオーバーヘッドを費やす必要があります。 要するに、単純なアプローチは、特に本番環境で実行可能な複数属性検索には不確実性が高く非効率的です。 スーパーリンクアプローチ 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 は、 スコア (0.006) に反映されているように、光の操作という点では関連性が低いものの、「光る」特徴と暗い環境により、 (0.168) と (0.203) のスコアが非常に高く、合計スコアが最も高く (0.377)、全体的に最も関連性の高いモンスターとなっています。なんと素晴らしい進歩でしょう! behavior look habitat 結果を再現できるでしょうか? 別のクエリを試して確認してみましょう。 query = { 'look': 'shapeshifting', 'habitat': 'varied landscapes', 'behavior': 'illusion creation' } id スコア 見て 生息地 行動 ミストファントム 0.489574 変化する特徴を持つ、幽玄で霧のようなヒューマノイド 沼地、荒野、霧の海岸線 幻想とささやきで旅人を惑わす ゼファーダンサー 0.342075 虹色の羽を持つ優雅な鳥類 高い山の頂上と風が吹き渡る平原 仲間を引き付ける魅惑的な空中ディスプレイを作成します ウィスパリング・シェイド 0.337434 光る目を持つ影のような不定形の存在 暗い森と廃墟 恐怖を糧に、不安をかき立てる真実を囁く 素晴らしい!私たちの成果は今回も素晴らしいです。 データセットから特定のモンスターに似たモンスターを見つけたい場合はどうすればよいでしょうか。まだ見たことのないモンスター、Harmonic Coral で試してみましょう。このモンスターの属性を抽出し、クエリ パラメータを手動で作成すること 。ただし、Superlinked にはクエリ オブジェクトで使用できる メソッドがあります。各モンスターの ID は名前であるため、次のように簡単にリクエストを表現できます。 もできます with_vector app.query( monster_query.with_vector(monster, "Harmonic Coral"), **default_weights, limit=LIMIT ) id スコア 見て 生息地 行動 ハーモニックコーラル 1 振動する巻きひげを持つ、分岐した楽器のような構造 浅い海と潮だまり 複雑なメロディーを創り、感情を伝え、影響を与える ドリームウィーバータコ 0.402288 オーロラのように光る触手を持つ頭足動物 深海の海溝と水中洞窟 近くの生き物の夢に影響を与える アクアレイス 0.330869 流れる水でできた半透明の人型像 河川、湖沼、沿岸地域 水域に溶け込むように形を変え、流れを制御する 一番上の結果は、予想通り、最も関連性の高いハーモニック コーラルそのものです。検索で取得される他の 2 つのモンスターは、ドリームウィーバー オクトパスとアクア レイスです。どちらも、ハーモニック コーラルと重要なテーマ ( ) 要素を共有しています。 属性 水生生息地( ) habitat 環境に影響を与えたり操作したりする能力( ) behavior 動的または流動的な視覚特性( ) look 属性の重み付け ここで、 属性にもっと重点を置きたいとします。スーパーリンク フレームワークを使用すると、クエリ時に重みを簡単に調整できます。簡単に比較できるように、 優先するように重みを調整して、Harmonic Coral に似たモンスターを検索します。 look look 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 パチパチと音を立てるエネルギーでできた蛇のような生き物 雷雨と発電所 電流を供給し、技術を短絡させる可能性がある 私たちの結果はすべて(当然のことながら)似たような外観をしています - 「振動する巻きひげで枝分かれしている」、「ねじれた蔓とトゲでできた体を持つ植物のような生き物」、「ヘビのような」。 ここで、外見を無視して、 と の点で同時に類似するモンスターを探す別の検索を実行してみましょう。 habitat behavior 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 の触角は似た特徴ですが、 を 0.5 だけ下げただけで、2 体のモンスターの類似点はそれだけです。) look_weight これらのモンスターの総合スコアが個々の属性ごとにどのようになっているかを見てみましょう。 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 に負の重み付け (-1.0) をすることで、同様の生息地を持つモンスターを意図的に「押しのけ」、代わりに Harmonic Coral とは異なる環境を持つモンスターを表面に出します。これは、Luminoth と Zephyr Dancer の負の生息 スコアに見られるとおりです。Luminoth と Zephyr Dancer の スコアは比較的高く、Harmonic Coral との行動の類似性を示しています。 スコアは正ですが、低く、Harmonic Coral との視覚的な類似性が が極端ではないことを反映しています。 habitat_weight habitat behavior look 多少ある つまり、 を -1.0 に、 を 0.5 に下げ、 1.0 のままにする戦略は、Harmonic Coral と主要な行動特性を共有しながらも、環境が大きく異なり、少なくとも多少見た目が異なるモンスターを表面化させるのに効果的であることが証明されています。 habitat_weight look_weight behavior_weight 結論 マルチ属性ベクトル検索は情報検索の大きな進歩であり、基本的な意味的類似性検索よりも高い精度、コンテキスト理解、柔軟性を提供します。 、属性ベクトルを個別に保存して検索し、結果を結合する単純なアプローチ (上記) は、複数の同時属性を持つオブジェクトを取得する必要がある場合、機能、繊細さ、効率の点で制限があります。(さらに、複数の kNN 検索は、連結されたベクトルを使用した単一の検索よりも時間がかかります。) それでも このようなシナリオに対処するには、すべての属性ベクトルを同じベクトル ストアに保存し、 を実行して、クエリ時に属性に重み付けする方が適切です。スーパーリンク アプローチは、高速で信頼性が高く、微妙な違いのある複数の属性ベクトルの取得を必要とするあらゆるアプリケーションにおいて、単純なアプローチよりも正確で効率的、かつスケーラブルです。ユース ケースが、e コマースや推奨システムにおける現実世界のデータ課題への取り組みであっても、モンスターとの戦いなどまったく異なるものであっても同じです。 単一の検索 寄稿者 アンドレイ・ピクノフ、著者 モル・カプロンツァイ、編集者 元々は 公開されました。 ここで