Elasticsearchを使ったマルチモーダルRAGシステムの構築:ゴッサム・シティの物語
テキスト、オーディオ、ビデオ、画像データを統合して、より豊富でコンテキストに基づいた情報検索を提供するマルチモーダル検索拡張生成 (RAG) システムの構築方法を学びます。
このブログでは、Elasticsearch を使用してマルチモーダル RAG (Retrieval-Augmented Generation) パイプラインを構築する方法を学びます。ImageBind を活用して、テキスト、画像、音声、深度マップなど、さまざまなデータ タイプの埋め込みを生成する方法について説明します。また、dense_vector と k-NN 検索を使用して、これらの埋め込みを Elasticsearch で効率的に保存および取得する方法についても説明します。最後に、大規模言語モデル (LLM) を統合して取得した証拠を分析し、包括的な最終レポートを生成します。
マルチモーダル RAG パイプラインはどのように機能しますか?
手がかりの収集→ ゴッサムの犯罪現場からの画像、音声、テキスト、深度マップ。
埋め込みの生成→ 各ファイルは、ImageBind マルチモーダル モデルを使用してベクトルに変換されます。
Elasticsearch でのインデックス作成→ ベクトルは効率的な検索のために保存されます。
類似度による検索→ 新しい手がかりが与えられると、最も類似したベクトルが取得されます。
LLM が証拠を分析→ GPT-4 モデルが応答を合成し、容疑者を特定します。
使用される技術
ImageBind → さまざまなモダリティの統合された埋め込みを生成します。
Elasticsearch → 高速かつ効率的なベクトル検索を可能にします。
LLM (GPT-4、OpenAI) → 証拠を分析し、最終レポートを生成します。
このブログは誰に向けたものですか?
マルチモーダルベクトル検索に興味のある Elastic ユーザー。
マルチモーダル RAG を実際に理解したい開発者。
複数のソースからのデータを分析するためのスケーラブルなソリューションを探している方。
マルチモーダルRAGの前提条件: 環境の設定
ゴッサム シティの犯罪を解決するには、テクノロジー環境を整える必要があります。次のステップバイステップガイドに従ってください。
1. 技術要件
成分 | 仕様 |
|---|---|
システムOS | Linux、macOS、またはWindows |
Python | 3.10以降 |
ラム | 最低8GB(16GBを推奨) |
グラフィックプロセッサ | オプションですが、ImageBind では推奨されます |
2. プロジェクトの設定
すべての調査資料は GitHub で公開されており、このインタラクティブな犯罪解決体験には Jupyter Notebook (Google Colab) を使用します。開始するには、次の手順に従ってください。
Jupyter Notebook の設定 (Google Colab)
1. ノートブックにアクセスする
すぐに使用できる Google Colab ノートブック「 Multimodal RAG with Elasticsearch」を開きます。
このノートブックには、従う必要があるすべてのコードと説明が含まれています。
2. リポジトリをクローンする
# Clone the repository with the multimodal RAG code
!git clone -b https://github.com/elastic/elasticsearch-labs.git
# Navigate to the project directory
cd elasticsearch-labs/supporting-blog-content/building-multimodal-rag-with-elasticsearch-gotham3. 依存関係をインストールする
# Install PyTorch and related libraries
!pip install torch>=2.1.0 torchvision>=0.16.0 torchaudio>=2.1.0
# Install vision processing libraries
!pip install opencv-python-headless pillow numpy
# Install the specific ImageBind fork
!pip install git+https://github.com/hkchengrex/ImageBind.git
# Install Elasticsearch and environment management
!pip install elasticsearch python-dotenv4. 資格情報を設定する
# Input your credentials securely
import getpass
ELASTICSEARCH_URL = input("Enter the Elasticsearch endpoint url: ")
ELASTICSEARCH_API_KEY = getpass.getpass("Enter the Elasticsearch API key: ")
OPENAI_API_KEY = getpass.getpass("Enter the OpenAI API key: ")
# Configure environment variables
import os
os.environ["ELASTICSEARCH_API_KEY"] = ELASTICSEARCH_API_KEY
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
os.environ["ELASTICSEARCH_URL"] = ELASTICSEARCH_URL注: ImageBind モデル (約 2 GB) は、最初の実行時に自動的にダウンロードされます。
準備はすべて整ったので、詳細を調べて犯罪を解決しましょう。
はじめに:ゴッサム・シティの犯罪
ゴッサム・シティの雨の夜、衝撃的な犯罪が街を揺るがす。ゴードン委員長は謎を解くためにあなたの助けを必要としています。手がかりは、ぼやけた画像、謎の音声、暗号化されたテキスト、さらには深度マップなど、さまざまな形式で散在しています。最先端の AI テクノロジーを駆使して事件を解決する準備はできていますか?
このブログでは、さまざまな種類のデータ ( 画像、音声、テキスト、深度マップ) を単一の検索空間に統合する マルチモーダル RAG (検索拡張生成) システムの 構築を段階的に説明します。ImageBindを使用してマルチモーダル埋め込みを生成し、 Elasticsearch を使用してこれらの埋め込みを保存および取得し、大規模言語モデル (LLM) を使用して証拠を分析し、最終レポートを生成します。
基礎:マルチモーダルRAGアーキテクチャ
マルチモーダル RAG とは何ですか?
検索拡張生成 (RAG) マルチモーダルの台頭により、AI モデルとの対話方法に革命が起きています。従来、RAG システムはテキストのみを処理し、応答を生成する前にデータベースから関連情報を取得します。しかし、世界はテキストに限定されません。画像、ビデオ、音声にも貴重な知識が存在します。このため、マルチモーダル アーキテクチャが注目を集めており、AI システムがさまざまな形式の情報を組み合わせて、より豊富で正確な応答を実現できるようになりました。
マルチモーダルRAGの3つの主なアプローチ
マルチモーダル RAG を実装するには、一般的に 3 つの戦略が使用されます。それぞれのアプローチには、ユースケースに応じて独自の利点と制限があります。
1. 共有ベクトル空間
さまざまなモダリティからのデータは、ImageBind などのマルチモーダル モデルを使用して共通のベクトル空間にマッピングされます。これにより、明示的な形式変換を行わずに、テキスト クエリで画像、ビデオ、オーディオを取得できるようになります。
利点:
明示的な形式変換を必要とせずにクロスモーダル検索を可能にします。
さまざまなモダリティ間のスムーズな統合を提供し、テキスト、画像、オーディオ、ビデオを直接取得できます。
さまざまなデータ タイプに拡張可能なので、大規模な検索アプリケーションに役立ちます。
デメリット:
トレーニングには大規模なマルチモーダル データセットが必要ですが、必ずしも利用できるとは限りません。
共有された埋め込み空間では意味ドリフトが生じる可能性があり、その場合、モダリティ間の関係は完全には保持されません。
マルチモーダル モデルのバイアスは、データセットの分布に応じて、検索精度に影響を及ぼす可能性があります。
2. 単一のグラウンデッドモダリティ
すべてのモダリティは、検索前に単一の形式(通常はテキスト)に変換されます。たとえば、画像は自動的に生成されたキャプションを通じて説明され、音声はテキストに転記されます。
利点:
すべてが 統一されたテキスト表現 に変換されるため、 検索が簡単になります 。
既存のテキストベースの検索エンジンと連携して動作し、特殊なマルチモーダル インフラストラクチャの必要性を排除します。
取得された結果は人間が読める形式であるため、解釈可能性が向上します。
デメリット:
情報の損失: 特定の詳細 (画像内の空間関係、音声のトーンなど) は、テキストの説明では完全には表現されない場合があります。
キャプション/文字起こしの品質に依存: 自動注釈のエラーにより、検索の有効性が低下する可能性があります。
変換プロセスによって重要なコンテキストが削除される可能性があるため、純粋に視覚的または聴覚的なクエリには最適ではありません。
3. 個別検索
各モダリティごとに個別のモデルを維持します。システムは各データ タイプごとに個別の検索を実行し、後で結果を結合します。
利点:
モダリティごとにカスタム最適化が可能になり、各データのタイプごとの検索精度が向上します。
複雑なマルチモーダル モデルへの依存が少なくなり、既存の検索システムとの統合が容易になります。
さまざまなモダリティからの結果を動的に組み合わせることができるため、ランキングと再ランキングをきめ細かく制御できます。
デメリット:
結果の融合が必要なため、検索とランキングのプロセスがより複雑になります。
異なるモダリティが矛盾する情報を返す場合、一貫性のない応答が生成されることがあります。
各モダリティごとに独立した検索が実行され、処理時間が長くなるため、計算コストが高くなります。
私たちの選択: ImageBindによる共有ベクトル空間
これらのアプローチの中で、私たちは共有ベクトル空間を選択しました。これは、効率的なマルチモーダル検索のニーズに完全に一致する戦略です。私たちの実装は、 共通のベクトル空間 で複数のモダリティ( テキスト、画像、音声、ビデオ )を表現できるモデルである ImageBind に基づいています。これにより、次のことが可能になります。
すべてをテキストに変換する必要なく、さまざまなメディア形式間でクロスモーダル検索を実行します。
非常に表現力豊かな埋め込みを使用して、さまざまなモダリティ間の関係を捉えます。
スケーラビリティと効率性を確保し、Elasticsearch で高速に検索できるように最適化された埋め込みを保存します。
このアプローチを採用することで、追加の前処理なしでテキストクエリで 画像や音声を直接取得 できる 堅牢なマルチモーダル検索パイプライン を構築しました。この方法は、大規模リポジトリでのインテリジェントな検索から高度なマルチモーダル推奨システムまで、実用的なアプリケーションを拡張します。
次の図は、マルチモーダル RAG パイプライン内のデータ フローを示しており、マルチモーダル データに基づくインデックス作成、検索、および応答生成のプロセスが強調表示されています。

埋め込みスペースはどのように機能しますか?
従来、テキスト埋め込みは言語モデル (BERT、GPT など) から得られます。現在、Meta AI のImageBindのようなネイティブ マルチモーダル モデルにより、複数のモダリティのベクトルを生成するバックボーンが得られます。
テキスト: 文と段落は同じ次元のベクトルに変換されます。
画像 (視覚) : ピクセルは、テキストに使用されるのと同じ次元空間にマッピングされます。
オーディオ: サウンド信号は、画像やテキストに匹敵する埋め込みに変換されます。
深度マップ: 深度データが処理され、ベクターが生成されます。
したがって、 コサイン類似度 などのベクトル類似度メトリックを使用して、任意の手がかり ( テキスト、画像、音声、深度) を他の手がかりと比較できます。笑い声の音声サンプルと容疑者の顔の画像がこの空間で「近い」場合、何らかの相関関係(同じ身元など)を推測できます。
ステージ1 - 犯罪現場の手がかりを集める
証拠を分析する前に、それを収集する必要があります。ゴッサムでの犯罪は、画像、音声、テキスト、さらには深度データに隠されている可能性のある痕跡を残しました。これらの手がかりを整理してシステムに取り入れてみましょう。
何があるでしょうか?
ゴードン委員は、犯罪現場から4つの異なる方法で収集された証拠を含む以下のファイルを私たちに送信しました。
トラックの説明とモダリティ
a) 画像(写真2枚)
crime_scene1.jpg, crime_scene2.jpg→ 犯罪現場から撮影された写真。地面に怪しい痕跡が残っている。suspect_spotted.jpg→現場から逃走するシルエットが映った防犯カメラの映像。



b)音声(録音1件)
joker_laugh.wav→ 犯罪現場近くのマイクが不気味な笑い声を捉えた。

c) テキスト(1件)
Riddle.txt, note2.txt→ その場所で、犯人が残したと思われる謎のメモが見つかりました。

d) 深度(深度マップ1枚)
depth_suspect.png→ 深度センサー付きの防犯カメラが近くの路地に容疑者を捉えた。jdancing-depth.png→ 深度センサー付きの防犯カメラが、地下鉄の駅に降りていく容疑者を捉えた。


これらの証拠は形式が異なり、同じ方法で直接分析することはできません。これらを埋め込み、つまりモーダル間の比較を可能にする数値ベクトルに変換する必要があります。
ファイル構成
処理を開始する前に、パイプラインがスムーズに実行されるように、すべての手がかりが data/ ディレクトリ内で適切に整理されていることを確認する必要があります。
予想されるディレクトリ構造:
data/
├── images/
│ ├── crime_scene1.jpg
│ ├── suspect_spotted.jpg
│ ...
├── audios/
│ ├── joker_laugh.wav
│ ...
├── texts/
│ ├── riddle.txt
│ ...
├── depths/
│ ├── depth_suspect.png手がかりの構成を確認するためのコード
続行する前に、必要なファイルがすべて正しい場所にあることを確認しましょう。
import os
# Base directory for clues
data_dir = "data"
# List of expected files
evidences = {
"images": ["crime_scene1.jpg","crime_scene1.jpg", "joker_alley.jpg"],
"audios": ["joker_laugh.wav"],
"texts": ["riddle.txt", "note2.txt”],
"depths": ["depth_suspect.png", "jdancing-depth.png"]
}
# Create directories if they don't exist
for category, files in evidences.items():
category_path = os.path.join(data_dir, category)
os.makedirs(category_path, exist_ok=True)
for file in files:
file_path = os.path.join(category_path, file)
if not os.path.exists(file_path):
print(f"Warning: {file} not found in {category_path}.")
print("All files are correctly organized!")ファイルを実行する
python stages/01-stage/files_check.py予想される出力(すべてのファイルが正しい場合):
All files are correctly organized!予想される出力(ファイルが欠落している場合):
Warning: joker_laugh.wav not found in data/audios/
Warning: depth_suspect.png not found in data/depths/このスクリプトは、埋め込みを生成して Elasticsearch にインデックス付けする前にエラーを防ぐのに役立ちます。
ステージ2 - 証拠の整理
ImageBindによる埋め込みの生成
手がかりを統合するには、手がかりを埋め込み、つまり各様相の意味を捉えるベクトル表現に変換する必要があります。ここでは、共有ベクトル空間内でさまざまなデータ タイプ ( 画像、音声、テキスト、深度マップ) の埋め込みを生成する Meta AI のモデルである ImageBind を 使用します。

ImageBind はどのように機能しますか?
さまざまな種類の証拠 (画像、音声、テキスト、深度マップ) を比較するには、 ImageBindを使用してそれらを数値ベクトルに変換する必要があります。このモデルにより、あらゆるタイプの入力を同じ埋め込み形式に変換できるため、モダリティ間のクロスモーダル検索が可能になります。
以下は、各モダリティに適したプロセッサを使用して、あらゆるタイプの入力の埋め込みを生成するための最適化されたコード ( src/embedding_generator.py ) です。
class EmbeddingGenerator:
"""Class for generating multimodal embeddings using ImageBind."""
def __init__(self):
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.model = self._load_model()
def _load_model(self):
"""Loads the ImageBind model and sets it to inference mode."""
model = imagebind_model.imagebind_huge(pretrained=True)
model.eval()
model.to(self.device)
return model
def generate_embedding(self, input_data, modality):
"""Generates embedding for different modalities"""
processors = {
"vision": lambda x: data.load_and_transform_vision_data(x, self.device),
"audio": lambda x: data.load_and_transform_audio_data(x, self.device),
"text": lambda x: data.load_and_transform_text(x, self.device),
"depth": self.process_depth
}
try:
# Input type verification
if not isinstance(input_data, list):
raise ValueError(f"Input data must be a list. Received: {type(input_data)}")
# Convert input data to a tensor format that the model can process
# For images: [batch_size, channels, height, width]
# For audio: [batch_size, channels, time]
# For text: [batch_size, sequence_length]
inputs = {modality: processors[modality](input_data)}
with torch.no_grad():
embedding = self.model(inputs)[modality]
return embedding.squeeze(0).cpu().numpy()
except Exception as e:
logger.error(f"Error generating {modality} embedding: {str(e)}", exc_info=True)
raiseテンソルは、特に ImageBind のようなモデルを扱う場合の、機械学習とディープラーニングにおける基本的なデータ構造です。私たちの文脈では:
input_tensor = processors[modality]([input_data], self.device)ここで、テンソルは、モデルが処理できる数学的形式に変換された入力データ (画像、音声、またはテキスト) を表します。具体的には:
画像の場合: テンソルは、画像を数値 (高さ、幅、およびカラー チャネル別に整理されたピクセル) の多次元行列として表現します。
オーディオの場合: テンソルは、音波を時間の経過に伴う一連の振幅として表します。
テキストの場合: テンソルは単語またはトークンを数値ベクトルとして表します。
埋め込み生成のテスト:
次のコードを使用して埋め込み生成をテストしてみましょう。これを 02-stage/test_embedding_generation.py に保存し、次のコマンドで実行します。
python stages/02-stage/test_embedding_generation.pygenerator = EmbeddingGenerator()
image_embedding = generator.generate_embedding("data/images/crime_scene1.jpg","vision")
print(image_embedding.shape)期待される出力:
(1024,)これで、画像は1024 次元のベクトルに変換されました。
ステージ3 - Elasticsearchでの保存と検索
証拠の埋め込みを生成したので、効率的な検索を可能にするために、それらをベクトル データベースに保存する必要があります。このため、密なベクトル ( dense_vector ) をサポートし、類似性検索を可能にするElasticsearchを使用します。
このステップは、主に次の 2 つのプロセスで構成されます。
埋め込みのインデックス作成→ 生成されたベクトルを Elasticsearch に保存します。
類似検索→ 新しい証拠に最も類似したレコードを取得します。
Elasticsearchで証拠をインデックスする
ImageBindによって処理される各証拠 (画像、音声、テキスト、深度) は、1024 次元のベクトルに変換されます。将来の検索を可能にするために、これらのベクトルをElasticsearchに保存する必要があります。
次のコード ( src/elastic_manager.py ) は、Elasticsearch にインデックスを作成し、埋め込みを格納するためのマッピングを構成します。
from elasticsearch import Elasticsearch, helpers
...
class ElasticsearchManager:
"""Manages multimodal operations in Elasticsearch"""
def __init__(self):
load_dotenv() # Load variables from .env
self.es = self._connect_elastic()
self.index_name = "multimodal_content"
self._setup_index()
def _connect_elastic(self):
"""Connects to Elasticsearch"""
return Elasticsearch(
os.getenv("ELASTICSEARCH_URL"), # Elasticsearch endpoint
api_key=os.getenv("ELASTICSEARCH_API_KEY")
)
def _setup_index(self):
"""Sets up the index if it doesn't exist"""
if not self.es.indices.exists(index=self.index_name):
mapping = {
"mappings": {
"properties": {
"embedding": {
"type": "dense_vector",
"dims": 1024,
"index": True,
"similarity": "cosine"
},
"modality": {"type": "keyword"},
"content": {"type": "binary"},
"description": {"type": "text"},
"metadata": {"type": "object"},
"content_path": {"type": "text"}
}
}
}
self.es.indices.create(index=self.index_name, body=mapping)
def index_content(self, embedding, modality, content=None, description="", metadata=None, content_path=None):
"""Indexes multimodal content"""
doc = {
"embedding": embedding.tolist(),
"modality": modality,
"description": description,
"metadata": metadata or {},
"content_path": content_path
}
if content:
doc["content"] = base64.b64encode(content).decode() if isinstance(content, bytes) else content
return self.es.index(index=self.index_name, document=doc)
def search_similar(self, query_embedding, modality=None, k=5):
"""Searches for similar contents"""
query = {
"knn": {
"field": "embedding",
"query_vector": query_embedding.tolist(),
"k": k,
"num_candidates": 100,
"filter": [{"term": {"modality": modality}}] if modality else []
}
}
try:
response = self.es.search(
index=self.index_name,
query=query,
size=k
)
# Return both source data and score for each hit
return [{
**hit["_source"],
"score": hit["_score"]
} for hit in response["hits"]["hits"]]
except Exception as e:
print(f"Error: processing search_evidence: {str(e)}")
return "Error generating search evidence"インデックス作成の実行
それでは、プロセスをテストするために証拠をインデックスしてみましょう。
# Example: Indexing an image from the crime scene
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager(cloud_id="YOUR_CLOUD_ID", api_key="YOUR_API_KEY")
image_embedding = generator.generate_embedding("data/images/crime_scene1.jpg", "vision")
response = es_manager.index_content(
embedding=image_embedding,
modality="vision",
description="Photo of the crime scene with suspicious traces",
content_path="data/images/crime_scene1.jpg"
)
print(json.dumps(response, indent=2))Elasticsearch での予想される出力 (インデックスされたドキュメントの概要):
{
"embedding": [0.12, -0.53, 0.89, ...],
"modality": "vision",
"description": "Photo of the crime scene with suspicious traces",
"content_path": "data/images/crime_scene1.jpg"
}すべてのマルチモーダル証拠をインデックスするには、次の Python コマンドを実行してください。
python stages/03-stage/index_all_modalities.pyこれで、証拠はElasticsearchに保存され、必要なときに取得できるようになりました。
インデックス作成プロセスの検証
インデックス作成スクリプトを実行した後、すべての証拠が Elasticsearch に正しく保存されているかどうかを確認しましょう。Kibana の開発ツールを使用して、いくつかの検証クエリを実行できます。
1. まず、インデックスが作成されたかどうかを確認します。
GET _cat/indices/multimodal_content?v2. 次に、モダリティごとのドキュメント数を確認します。
GET multimodal_content/_search
{
"size": 0,
"aggs": {
"modalities": {
"terms": {
"field": "modality.keyword"
}
}
}
}3. 最後に、インデックスされたドキュメントの構造を調べます。
GET multimodal_content/_search
{
"size": 1,
"query": {
"match_all": {}
}
}期待される結果:
`multimodal_content` という名前のインデックスが存在する必要があります。
さまざまなモダリティ(視覚、音声、テキスト、深度)に分散された約 7 つのドキュメント。
各ドキュメントには、埋め込み、モダリティ、説明、メタデータ、content_path フィールドが含まれている必要があります。
この検証手順により、類似性検索に進む前に証拠データベースが適切に設定されていることが保証されます。
Elasticsearchで類似の証拠を検索する
証拠がインデックス化されたので、検索を実行して新しい手がかりに最も類似した記録を見つけることができます。この検索では、ベクトル類似性を使用して、埋め込み空間内の最も近いレコードを返します。
次のコードはこの検索を実行します。
def search_similar_evidence(self, query_embedding, k=5, modality=None):
"""Performs a kNN search to find the most similar clues."""
knn_query = {
"field": "embedding",
"query_vector": query_embedding.tolist(),
"k": k,
"num_candidates": 100
}
query_body = {"knn": knn_query}
if modality:
query_body = {
"bool": {
"must": [
query_body,
{"term": {"modality": modality}}
]
}
}
try:
results = self.es.search(
index=self.index_name,
query=query_body,
_source_includes=["description", "modality", "content_path"],
size=k
)
except Exception as e:
print(f"Error processing search_evidence: {str(e)}")
return "Error generating search evidence”
return results["hits"]["hits"]検索のテスト - マルチモーダル結果のクエリとして音声を使用する
それでは、疑わしい音声ファイルを使用して証拠の検索をテストしてみましょう。同じ方法でファイルの埋め込みを生成し、同様の埋め込みを検索する必要があります。
python stages/03-stage/search_by_audio.py# Initialize classes
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager(cloud_id="YOUR_CLOUD_ID", api_key="YOUR_API_KEY")
# Generate embedding for a suspicious audio
audio_embedding = generator.generate_embedding("data/audios/mysterious_laugh.wav", "audio")
# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar_evidence(audio_embedding, k=3)
# Display the retrieved results
print("\n🔎 Similar evidence found:\n")
for i, evidence in enumerate(similar_evidences, start=1):
description = evidence['_source']['description']
modality = evidence['_source']['modality']
score = evidence['_score']
content_path = evidence['_source'].get('content_path', 'N/A')
print(f"{i}. {description} ({modality})")
print(f" Similarity: {score:.4f}")
print(f" File path: {content_path}\n")ターミナルに期待される出力:
🔎 Similar evidence found:
1. A sinister laugh captured near the crime scene (audio)
Similarity: 0.9985
File path: data/audios/joker_laugh.wav
2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
Similarity: 0.6068
File path: data/images/joker_laughing.png
3. Suspect dancing (vision)
Similarity: 0.5591
File path: data/images/jdancing.png
これで、取得した証拠を分析し、事件との関連性を判断できます。
オーディオを超えて - マルチモーダル検索の探求
役割の逆転:あらゆるモダリティは「質問」になり得る
当社のマルチモーダル RAGシステムでは、あらゆるモダリティが潜在的な検索クエリとなります。音声の例を超えて、他のデータ タイプで調査を開始する方法を見てみましょう。
1. テキストによる検索(犯人のメモの解読)
シナリオ:暗号化されたテキスト メッセージを発見し、関連する証拠を見つけたいと考えています。
python stages/03-stage/search_by_text.py# Generate embedding from text
text = "Why so serious?"
embedding_text = generator.generate_embedding([text], "text")
# Search for related evidence
similar_evidences = es_manager.search_similar(
query_embedding=embedding_text,
k=3
)期待される結果:
🔎 Similar evidence found:
1. Mysterious note found at the location (text)
Similarity: 0.7639
File path: data/texts/riddle.txt
2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
Similarity: 0.7161
File path: data/images/joker_laughing.png
3. Why so serious (text)
Similarity: 0.7132
File path: data/texts/note2.txt2. 画像検索(不審な犯罪現場の追跡)
シナリオ:新しい犯罪現場( crime_scene2.jpg ) を他の証拠と比較する必要があります。
python stages/03-stage/search_by_image.py# Generate embedding for a suspicious image
vision_embedding = generator.generate_embedding(["data/images/crime_scene2.jpg"], "vision")
# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar(
query_embedding=vision_embedding,
k=3
)アウトプット:
🔎 Similar evidence found:
1. Photo of the crime scene: A dark, rain-soaked alley is filled with playing cards, while a sinister graffiti of the Joker laughing stands out on the brick wall. (vision)
Similarity: 0.8258
File path: data/images/crime_scene1.jpg
2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
Similarity: 0.6897
File path: data/images/joker_laughing.png
3. Suspect dancing (vision)
Similarity: 0.6588
File path: data/images/jdancing.png3. 深度マップ探索(3D追跡)
シナリオ:深度マップ( jdancing-depth.png ) により、画像のエスケープ パターンが明らかになります。
python stages/03-stage/search_by_depth.py# Generate embedding for a suspicious depth map
vision_embedding = generator.generate_embedding(["data/depths/jdancing-depth.png"], "depth")
# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar(
query_embedding=vision_embedding,
modality="vision",
k=3
)出力
🔎 Similar evidence found:
1. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
Similarity: 0.5329
File path: data/images/joker_laughing.png
2. Photo of the crime scene: A dark, rain-soaked alley is filled with playing cards, while a sinister graffiti of the Joker laughing stands out on the brick wall. (vision)
Similarity: 0.5053
File path: data/images/crime_scene1.jpg
3. Suspect dancing (vision)
Similarity: 0.4859
File path: data/images/jdancing.png
なぜこれが重要なのでしょうか?
それぞれのモダリティは独自の接続を明らかにします。
テキスト→ 容疑者の言語パターン。
画像→場所や物体の認識。
深度→ 3D シーンの再構築。
現在、 Elasticsearch には 構造化された証拠データベース があり、 マルチモーダル証拠を効率的に保存および取得 できます。
私たちが行ったことの要約:
Elasticsearch にマルチモーダル埋め込みを保存しました。
類似性検索を実行し、新たな手がかりに関連する証拠を見つけました。
疑わしい音声ファイルを使用して検索をテストし、システムが正しく動作することを確認しました。
次のステップ: LLM (大規模言語モデル) を使用して、取得した証拠を分析し、最終レポートを生成します。
ステージ4 - LLMで点と点をつなぐ
証拠が Elasticsearch で インデックス化され 、類似性に基づいて検索できるようになったので、それを 分析し 、ゴードン委員に送信する 最終レポート を生成する LLM (大規模言語モデル) が必要になります。LLM は、取得した証拠に基づいてパターンを識別し、手がかりを結び付け、容疑者を提案する責任を負います。
このタスクでは、 GPT-4 Turbo を使用して、モデルが結果を効率的に 解釈 できるように 詳細なプロンプト を作成します。
LLM統合
LLM を システムに統合するために、 Elasticsearch から 取得した証拠 を受け取り、この証拠をプロンプト コンテキストとして使用して フォレンジック レポート を生成する LLMAnalyzer クラス (src/llm_analyzer.py ) を作成しました。
import os
from openai import OpenAI
import logging
from dotenv import load_dotenv
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class LLMAnalyzer:
"""Evidence analyzer using GPT-4"""
def __init__(self):
load_dotenv()
self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def analyze_evidence(self, evidence_results):
"""
Analyzes multimodal search results and generates a report
Args:
evidence_results: Dict with results by modality
{
'vision': [...],
'audio': [...],
'text': [...],
'depth': [...]
}
"""
# Format evidence for the prompt
evidence_summary = self._format_evidence(evidence_results)
# final prompt
prompt = f"""
You are a highly experienced forensic detective specializing in multimodal evidence analysis. Your task is to analyze the collected evidence (audio, images, text, depth maps) and conclusively determine the **prime suspect** responsible for the Gotham Central Bank case.
---
### **Collected Evidence:**
{evidence_summary}
### **Task:**
1. **Analyze all the evidence** and identify cross-modal connections.
2. **Determine the exact identity of the criminal** based on behavioral patterns, visual/auditory/textual clues, and symbolic markers.
3. **Justify your conclusion** by explaining why this suspect is definitively responsible.
4. **Assign a confidence score (0-100%)** to your conclusion.
---
### **Final Output Format (Strictly Follow This Format):**
- **Prime Suspect:** [Full Name or Alias]
- **Evidence Supporting Conclusion:** [Detailed breakdown of visual, auditory, textual, and behavioral evidence]
- **Behavioral Patterns:** [Key actions, motives, and criminal signature]
- **Confidence Level:** [0-100%]
- **Next Steps (if any):** [What additional evidence would further confirm the identity? If none, state "No further evidence required."]
If there is **insufficient evidence**, specify exactly what is missing and suggest what additional data would be needed for a conclusive identification.
This report must be **direct and definitive**--avoid speculation and provide a final, actionable determination of the suspect's identity.
"""
try:
response = self.client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[
{
"role": "system",
"content": "You are a forensic detective specialized in multimodal evidence analysis."
},
{"role": "user", "content": prompt_01}
],
temperature=0.5,
max_tokens=1000
)
report = response.choices[0].message.content
logger.info("\n📋 Forensic Report Generated:")
logger.info("=" * 50)
logger.info(report)
logger.info("=" * 50)
return report
except Exception as e:
logger.error(f"Error generating report: {str(e)}")
return NoneLLM分析における温度設定:
当社の法医学分析システムでは、0.5 という適度な温度を使用します。このバランスの取れた設定が選択された理由は次のとおりです。
これは、決定論的(厳しすぎる)出力と非常にランダムな出力の中間地点を表します。
0.5 では、モデルは論理的かつ正当な法医学的結論を提供するのに十分な構造を維持します。
この設定により、モデルは合理的なフォレンジック分析パラメータの範囲内でパターンを識別し、接続を確立できます。
一貫性と信頼性の高い出力の必要性と洞察力に富んだ分析を生成する能力のバランスをとります。
この適度な温度設定により、過度に厳格で過度に推測的な結論を回避しながら、法医学的分析の信頼性と洞察力を高めることができます。
証拠分析の実行
LLM 統合が完了したので、すべてのシステム コンポーネントを接続するスクリプトが必要です。このスクリプトは次のことを行います。
Elasticsearch で 同様の証拠を検索します 。
LLM を使用して 取得した証拠を分析し 、 最終レポートを生成します。
コード: 証拠分析スクリプト
python stages/04-stage/rag_crime_analyze.pyimport sys
import os
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src'))
from embedding_generator import EmbeddingGenerator
from elastic_manager import ElasticsearchManager
from llm_analyzer import LLMAnalyzer
import json
import logging
from dotenv import load_dotenv
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Load environment variables
load_dotenv()
# Initialize classes
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager()
llm = LLMAnalyzer()
logger.info("✅ All components initialized successfully")
try:
evidence_data = {}
# Get data for each modality
test_files = {
'vision': 'data/images/crime_scene2.jpg',
'audio': 'data/audios/joker_laugh.wav',
'text': 'Why so serious?',
'depth': 'data/depths/jdancing-depth.png'
}
logger.info("🔍 Collecting evidence...")
for modality, test_input in test_files.items():
try:
if modality == 'text':
embedding = generator.generate_embedding([test_input], modality)
else:
embedding = generator.generate_embedding([str(test_input)], modality)
results = es_manager.search_similar(embedding, k=2)
if results:
evidence_data[modality] = results
logger.info(f"✅ Data retrieved for {modality}: {len(results)} results")
else:
logger.warning(f"⚠️ No results found for {modality}")
except Exception as e:
logger.error(f"❌ Error retrieving {modality} data: {str(e)}")
if not evidence_data:
raise ValueError("No evidence data found in Elasticsearch!")
# Test forensic report generation
logger.info("\n📝 Generating forensic report...")
report = llm.analyze_evidence(evidence_data)
if report:
logger.info("✅ Forensic report generated successfully")
logger.info("\n📊 Report Preview:")
logger.info("+" * 50)
logger.info(report)
logger.info("+" * 50)
else:
raise ValueError("Failed to generate forensic report")
except Exception as e:
logger.error(f"❌ Error in analysis : {str(e)}")期待されるLLM出力
**Prime Suspect:** The Joker
**Evidence Supporting Conclusion:**
- **Visual Evidence:**
- The photo of the crime scene with playing cards scattered around and the graffiti of the Joker laughing matches the Joker's known calling cards and thematic elements. The similarity score of 0.83 indicates a high likelihood that these elements are directly associated with the Joker.
- The image of the Joker with green hair, white face paint, and a sinister smile in an urban night setting, although with a lower similarity score of 0.69, still supports the presence or recent activity of the Joker in areas consistent with the crime scene's characteristics.
- **Auditory Evidence:**
- The captured sinister laugh with a similarity score of 1.00 perfectly matches known audio profiles of the Joker, making it a direct auditory signature of his presence at or near the crime scene.
- Despite the lower similarity score of 0.61, the second audio piece further corroborates the Joker's involvement through thematic consistency.
- **Textual Evidence:**
- The mysterious note found at the location, with a similarity score of 0.76, likely contains thematic or direct references to the Joker's modus operandi or signature phrases, further implicating him in the crime.
- The similarity score of 0.72 for the Joker's description in textual evidence reinforces the thematic connection to the crime scene.
- **Depth Evidence:**
- Depth sensor capture of the suspect with a similarity score of 0.77 suggests a physical presence matching the Joker's known dimensions or characteristic movements.
- The lower similarity score of 0.53 in the second depth evidence still contributes to the overall pattern of evidence pointing towards the Joker, albeit with less certainty.
**Behavioral Patterns:**
- The Joker is known for his theatrical crimes, often leaving behind a signature trail of chaos, including playing cards, sinister laughter, and thematic graffiti. These elements are not only consistent with his known criminal signature but also directly observed at the crime scene.
- His motives often include creating chaos, drawing attention to his acts, and challenging his arch-nemesis, Batman, making a high-profile bank heist fitting within his behavioral patterns.
**Confidence Level:** 95%
**Next Steps:** No further evidence required.
The combination of visual, auditory, textual, and depth evidence strongly points to the Joker as the prime suspect. The thematic consistency across multiple modes of evidence, combined with known behavioral patterns and criminal signature, leaves little doubt regarding his involvement. While there is always a small margin of uncertainty in forensic analysis, the evidence at hand provides a compelling case against the Joker with a high degree of confidence.
結論:事件は解決した
マルチモーダル RAG システムは 、すべての 手がかりを集めて分析し 、容疑者を ジョーカー として特定しました。
ImageBind を使用して 画像、音声、テキスト、深度マップを 共有ベクトル空間 に結合することにより、システムは手動では識別不可能だった 接続を検出 できるようになりました。Elasticsearch は高速かつ効率的な検索を保証し、 LLM は証拠を統合して明確で決定的なレポートを作成しました。
しかし、このシステムの真の力はゴッサム シティだけにとどまりません。マルチモーダル RAG アーキテクチャは、数多くの実際のアプリケーションへの扉を開きます。
都市監視:画像、音声、センサーデータに基づいて容疑者を特定します。
法医学的分析:複数の情報源からの証拠を相関させて複雑な犯罪を解決します。
マルチメディア推奨: マルチモーダルコンテキスト を理解する 推奨システム の作成 (例: 画像やテキストに基づいて 音楽 を提案する)。
ソーシャル メディアのトレンド:さまざまなデータ形式にわたってトレンドのトピックを検出します。
マルチモーダル RAG システムの構築方法を学習したので、独自の手がかりを使ってテストしてみませんか?
あなたの発見を私たちと共有し 、 マルチモーダル AI の分野での コミュニティ の進歩に貢献してください。
特別な感謝
このコードのデプロイメント アーキテクチャを定義するプロセス中に貴重な貢献とレビューをしてくれた Adrian Cole に感謝します。
参照資料
よくあるご質問
マルチモーダル RAG とは何ですか?
マルチモーダル RAG は、AI システムがさまざまな形式 (画像、ビデオ、オーディオなど) の情報を組み合わせて、より豊富で正確な応答を実現できるようにする方法です。
マルチモーダル RAG を実装するにはどうすればよいでしょうか?
マルチモーダルRAGを実装するためには、共有ベクトル空間、単一のグラウンデッドモダリティ、および個別の検索という3つの戦略が一般的に使用されます。




