AIパーソナル・ラーニング
と実践的なガイダンス
ビーンバッグ・マースコード1

ハンズオン:Voyager-3とLangGraphによる強力なマルチモーダル検索の構築

Voyage AIのVoyager 3は、テキストと画像を同じ空間に埋め込むことができる最新のモデルだ。今回は、このマルチモーダル埋め込みを雑誌から抽出し、ベクトルデータベース(Weaviate)に格納し、同じ埋め込みベクトルを使ってテキストと画像の類似検索を行う方法を説明します。

 


画像とテキストを同じ空間に埋め込むことで、ウェブページ、PDFファイル、雑誌、書籍、パンフレット、各種論文などのマルチモーダルコンテンツを高精度に検索できるようになる。なぜこのテクニックが面白いのか?テキストと画像を同じ空間に埋め込むことの最大の利点は、特定の画像に関連するテキストを検索・取得できることであり、その逆もまたしかりである。例えば、猫を検索している場合、猫が写っている画像が見つかりますが、その画像に関連するテキストも取得できます。

従来のテキスト埋め込み類似度検索とマルチモーダル埋め込み空間の違いを示してみよう:

例題: この雑誌には猫についてどのように書かれていますか?

使用 Voyager-3 和 LangGraph 构建强大的多模态搜索-1

写真雑誌「OUTDOOR」のスクリーンショット

 

通常の類似検索回答

提供された検索結果には、猫に関する具体的な情報は含まれていない。動物の肖像画や写真技術については言及されているが、猫や猫に関する詳細については明確に言及されていない。

上記のように、「猫」という単語は出てこず、写真と動物の撮り方の説明だけがある。猫」という言葉が出てこないため、通常の類似検索では結果が得られなかった。

マルチモーダルな答え探し

本誌は猫のポートレートを取り上げ、その顔の特徴と性格を見事にとらえていることを強調している。文章では、よくできた動物の肖像画がいかに被写体の魂に届き、説得力のあるアイコンタクトを通じて見る者との感情的なつながりを生み出すかを強調している。

マルチモーダル検索を使って、猫の写真を見つけ、それに関連するテキストをリンクする。このデータをモデルに提供することで、モデルはより良い答えを出し、文脈を理解することができる。

 

マルチモーダル埋め込み・検索パイプラインの構築方法

このようなパイプラインがどのように機能するか、いくつかのステップで説明しよう:

  1. を使用する。 構造化されていない(データ抽出のための強力な Python ライブラリ) PDF ファイルからテキストと画像を抽出します。
  2. を使用する。 ボイジャー・マルチモーダル3 このモデルは、テキストと画像のマルチモーダルベクトルを同じベクトル空間に作成する。
  3. これをベクター・ストア(ウィービエイト)にある。
  4. 最後に、類似検索を行い、テキストと画像の結果を比較する。

ステップ1:ベクターストレージを設定し、文書(PDF)から画像とテキストを抽出する

ここでは手作業が必要だ。通常、Weaviateは非常に使いやすいベクトルストアで、データを自動的に変換し、挿入時に埋め込みを追加してくれる。しかし、Voyager Multimodal v3にはプラグインがないので、埋め込みを手動で計算しなければならない。この場合、ベクタライザー・モジュールを定義せずにコレクションを作らなければならない。

import weaviate
from weaviate.classes.config import Configure
client = weaviate.connect_to_local()
collection_name = "multimodal_demo"
client.collections.delete(collection_name)
try:
client.collections.create(
name=collection_name,
vectorizer_config=Configure.Vectorizer.none() # 不为此集合设置向量化器
)
collection = client.collections.get(collection_name)
except Exception:
collection = client.collections.get(collection_name)pyt

ここでは、ローカルのWeaviateインスタンスをDockerコンテナで実行している。

ステップ2:PDFから文書や画像を抽出

これはプロセスの作業における重要なステップです。ここでは、テキストと画像を含むPDFを取得します。次に、コンテンツ(画像とテキスト)を抽出し、関連するブロックに格納します。つまり、各ブロックは、文字列(実際のテキスト)と パイソンPIL画像 の要素のリスト。

を使用する。 構造化されていない ライブラリーを使用することで、重い仕事のいくつかをこなすことができるが、それでもいくつかのロジックを書き、ライブラリーのパラメーターを設定する必要がある。

from unstructured.partition.auto import partition
from unstructured.chunking.title import chunk_by_title
elements = partition(
filename="./files/magazine_sample.pdf",
strategy="hi_res",
extract_image_block_types=["Image", "Table"],
extract_image_block_to_payload=True)
chunks = chunk_by_title(elements)

ここでは ハイ・レス を使用する。 ペイロードへの画像ブロックの抽出 後で実際の埋め込みにこの情報が必要になるので、画像をペイロードにエクスポートします。すべての要素を抽出したら、ドキュメントのタイトルに基づいてブロックにグループ化します。

詳しくは チャンキングに関する構造化されていない文書.

以下のスクリプトでは、これらのブロックを使って2つのリストを出力する:

  1. ベクトルを作成するためにボイジャー3号に送るオブジェクトを含むリスト。
  2. Unstructuredによって抽出されたメタデータを含むリスト。このメタデータは、ベクターストアに追加するために必要です。フィルタリングするための追加属性を提供し、取得したデータについて教えてくれる。
from unstructured.staging.base import elements_from_base64_gzipped_json
import PIL.Image
import io
import base64
embedding_objects = []
embedding_metadatas = []
for chunk in chunks:
embedding_object = []
metedata_dict = {
"text": chunk.to_dict()["text"],
"filename": chunk.to_dict()["metadata"]["filename"],
"page_number": chunk.to_dict()["metadata"]["page_number"],
"last_modified": chunk.to_dict()["metadata"]["last_modified"],
"languages": chunk.to_dict()["metadata"]["languages"],
"filetype": chunk.to_dict()["metadata"]["filetype"]
}
embedding_object.append(chunk.to_dict()["text"])
# 将图像添加到嵌入对象
if "orig_elements" in chunk.to_dict()["metadata"]:
base64_elements_str = chunk.to_dict()["metadata"]["orig_elements"]
eles = elements_from_base64_gzipped_json(base64_elements_str)
image_data = []
for ele in eles:
if ele.to_dict()["type"] == "Image":
base64_image = ele.to_dict()["metadata"]["image_base64"]
image_data.append(base64_image)
pil_image = PIL.Image.open(io.BytesIO(base64.b64decode(base64_image)))
# 如果图像大于 1000x1000,则在保持纵横比的同时调整图像大小
if pil_image.size[0] > 1000 or pil_image.size[1] > 1000:
ratio = min(1000/pil_image.size[0], 1000/pil_image.size[1])
new_size = (int(pil_image.size[0] * ratio), int(pil_image.size[1] * ratio))
pil_image = pil_image.resize(new_size, PIL.Image.Resampling.LANCZOS)
embedding_object.append(pil_image)
metedata_dict["image_data"] = image_data
embedding_objects.append(embedding_object)
embedding_metadatas.append(metedata_dict)

このスクリプトの結果は、以下に示す内容のリストとなる:

[['来自\n\n冰岛 KIRKJUFELL 的位置',
<PIL.Image.Image image mode=RGB size=1000x381>,
<PIL.Image.Image image mode=RGB size=526x1000>],
['这座标志性的山峰是我们冰岛拍摄地点的首选,而且在我们去那里之前,我们就看过许多从附近瀑布拍摄的照片。因此,这是我们在日出时前往的第一个地方 - 我们没有失望。这些瀑布为这张照片(顶部)提供了完美的近景趣味,而从这个角度来看,Kirkjufell 是一座完美的尖山。我们花了一两个小时简单地探索这些瀑布,找到了几个不同的角度。']]

ステップ3:抽出したデータをベクトル化する

このステップでは、前のステップで作成したブロックに Voyager Pythonパッケージ それをVoyagerに送ると、埋め込まれたすべてのオブジェクトのリストが返ってくる。そしてこの結果を使用し、最終的にはWeaviateに保存することができる。

from dotenv import load_dotenv
import voyageai
load_dotenv()
vo = voyageai.Client()
# 这将自动使用环境变量 VOYAGE_API_KEY。
# 或者,您可以使用 vo = voyageai.Client(api_key="<您的密钥>")
# 包含文本字符串和 PIL 图像对象的示例输入
inputs = embedding_objects
# 向量化输入
result = vo.multimodal_embed(
inputs,
model="voyage-multimodal-3",
truncation=False
)

result.embeddingsにアクセスすると、計算されたすべての埋め込みベクトルのリストを含むリストが得られます:

[[-0.052734375, -0.0164794921875, 0.050048828125, 0.01348876953125, -0.048095703125, ...]]

これで batch.add_object メソッドは、この埋め込みデータをWeaviateに単一のバッチとして保存します。propertiesパラメータにもメタデータを追加していることに注意してください。

with collection.batch.dynamic() as batch:
for i, data_row in enumerate(embedding_objects):
batch.add_object(
properties=embedding_metadatas[i],
vector=result.embeddings[i]
)

ステップ4:データの照会

これで類似性検索を実行し、データを照会することができる。このプロセスは、テキスト埋め込みに対して実行される通常の類似検索と似ているため、簡単である。WeaviateにはVoyager Multimodal用のモジュールがないので、検索クエリーベクトルをWeaviateに渡して類似検索を実行させる前に、自分で計算しなければならない。

from weaviate.classes.query import MetadataQuery
question = "杂志上关于瀑布说了什么?"
vector = vo.multimodal_embed([[question]], model="voyage-multimodal-3")
vector.embeddings[0]
response = collection.query.near_vector(
near_vector=vector.embeddings[0], # 您的查询向量在此处
limit=2,
return_metadata=MetadataQuery(distance=True)
)
# 显示结果
for o in response.objects:
print(o.properties['text'])
for image_data in o.properties['image_data']:
# 使用 PIL 显示图像
img = PIL.Image.open(io.BytesIO(base64.b64decode(image_data)))
width, height = img.size
if width > 500 or height > 500:
ratio = min(500/width, 500/height)
new_size = (int(width * ratio), int(height * ratio))
img = img.resize(new_size)
display(img)
print(o.metadata.distance)

下の画像は、滝を検索すると、この検索クエリに関連するテキストと画像が返されることを示している。ご覧のように、写真には滝が映っているが、テキスト自体には滝のことは書かれていない。このテキストは滝が写っている画像に関するもので、そのためこの画像も検索されたのです。通常のテキスト埋め込み検索ではありえないことだ。

Voyager-3とLangGraph-1による強力なマルチモーダル検索の構築

類似検索の結果を示す画像

 

ステップ5:検索パイプライン全体に追加する

さて、雑誌からテキストと画像を抽出し、それらの埋め込みを作成し、Weaviateに追加し、類似検索を設定したので、全体の検索パイプラインに追加しようと思う。この例では、LangGraphを使うことにする。ユーザーがこの雑誌について質問し、パイプラインがその質問に答える。すべての作業が終わったので、この部分は通常のテキストを使った典型的な検索パイプラインをセットアップするのと同じくらい簡単です。

前のセクションで説明したロジックの一部を他のモジュールに抽象化した。以下は、それをどのように ラングラフ パイプラインの例。

class MultiModalRetrievalState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
results: List[Document]
base_64_images: List[str]
class RAGNodes(BaseNodes):
def __init__(self, logger, mode="online", document_handler=None):
super().__init__(logger, mode)
self.weaviate = Weaviate()
self.mode = mode
async def multi_modal_retrieval(self, state: MultiModalRetrievalState, config):
collection_name = config.get("configurable", {}).get("collection_name")
self.weaviate.set_collection(collection_name)
print("正在运行多模态检索")
print(f"正在搜索 {state['messages'][-1].content}")
results = self.weaviate.similarity_search(
query=state["messages"][-1].content, k=3, type="multimodal"
)
return {"results": results}
async def answer_question(self, state: MultiModalRetrievalState, config):
print("正在回答问题")
llm = self.llm_factory.create_llm(mode=self.mode, model_type="default")
include_images = config.get("configurable", {}).get("include_images", False)
chain = self.chain_factory.create_multi_modal_chain(
llm,
state["messages"][-1].content,
state["results"],
include_images=include_images,
)
response = await chain.ainvoke({})
message = AIMessage(content=response)
return {"messages": message}
# 定义配置
class GraphConfig(TypedDict):
mode: str = "online"
collection_name: str
include_images: bool = False
graph_nodes = RAGNodes(logger)
graph = StateGraph(MultiModalRetrievalState, config_schema=GraphConfig)
graph.add_node("multi_modal_retrieval", graph_nodes.multi_modal_retrieval)
graph.add_node("answer_question", graph_nodes.answer_question)
graph.add_edge(START, "multi_modal_retrieval")
graph.add_edge("multi_modal_retrieval", "answer_question")
graph.add_edge("answer_question", END)
multi_modal_graph = graph.compile()
__all__ = ["multi_modal_graph"]

上記のコードは以下のチャートを生成する。

Voyager-3とLangGraph-1による強力なマルチモーダル検索の構築

作成されたチャートの視覚的表現

 

Voyager-3とLangGraph-1による強力なマルチモーダル検索の構築

このトレースでは質問に答えるためにOpenAIに送られるコンテンツや画像を見ることができる。

 

評決を下す

マルチモーダルエンベッディングは、同じエンベッディング空間内で異なるデータタイプ(例えばテキストと画像)の情報を統合し検索する可能性を開く。Voyager Multimodal 3モデル、Weaviate、LangGraphといった最先端のツールを組み合わせることで、従来のテキストのみのアプローチよりも直感的にコンテンツを理解しリンクする、堅牢な検索パイプラインを構築する方法を示す。

このアプローチにより、雑誌、パンフレット、PDFなどの様々なデータソースの検索と検索の精度が大幅に向上する。また、明示的なキーワードがない場合でも、マルチモーダル埋め込みによって、画像を説明的なテキストにリンクさせることで、より豊かでコンテキストを意識した洞察を提供できることを実証しています。このチュートリアルでは、これらのテクニックを探求し、プロジェクトに適用することができます。

ノートブックの例:https://github.com/vectrix-ai/vectrix-graphs/blob/main/examples/multi-model-embeddings.ipynb

無断転載を禁じます:チーフAIシェアリングサークル " ハンズオン:Voyager-3とLangGraphによる強力なマルチモーダル検索の構築
ja日本語