Voyage AIのVoyager 3は、テキストと画像を同じ空間に埋め込むことができる最新のモデルだ。今回は、このマルチモーダル埋め込みを雑誌から抽出し、ベクトルデータベース(Weaviate)に格納し、同じ埋め込みベクトルを使ってテキストと画像の類似検索を行う方法を説明します。
画像とテキストを同じ空間に埋め込むことで、ウェブページ、PDFファイル、雑誌、書籍、パンフレット、各種論文などのマルチモーダルコンテンツを高精度に検索できるようになる。なぜこのテクニックが面白いのか?テキストと画像を同じ空間に埋め込むことの最大の利点は、特定の画像に関連するテキストを検索・取得できることであり、その逆もまたしかりである。例えば、猫を検索している場合、猫が写っている画像が見つかりますが、その画像に関連するテキストも取得できます。
従来のテキスト埋め込み類似度検索とマルチモーダル埋め込み空間の違いを示してみよう:
例題: この雑誌には猫についてどのように書かれていますか?
通常の類似検索回答
提供された検索結果には、猫に関する具体的な情報は含まれていない。動物の肖像画や写真技術については言及されているが、猫や猫に関する詳細については明確に言及されていない。
上記のように、「猫」という単語は出てこず、写真と動物の撮り方の説明だけがある。猫」という言葉が出てこないため、通常の類似検索では結果が得られなかった。
マルチモーダルな答え探し
本誌は猫のポートレートを取り上げ、その顔の特徴と性格を見事にとらえていることを強調している。文章では、よくできた動物の肖像画がいかに被写体の魂に届き、説得力のあるアイコンタクトを通じて見る者との感情的なつながりを生み出すかを強調している。
マルチモーダル検索を使って、猫の写真を見つけ、それに関連するテキストをリンクする。このデータをモデルに提供することで、モデルはより良い答えを出し、文脈を理解することができる。
マルチモーダル埋め込み・検索パイプラインの構築方法
このようなパイプラインがどのように機能するか、いくつかのステップで説明しよう:
- を使用する。 構造化されていない(データ抽出のための強力な Python ライブラリ) PDF ファイルからテキストと画像を抽出します。
- を使用する。 ボイジャー・マルチモーダル3 このモデルは、テキストと画像のマルチモーダルベクトルを同じベクトル空間に作成する。
- これをベクター・ストア(ウィービエイト)にある。
- 最後に、類似検索を行い、テキストと画像の結果を比較する。
ステップ1:ベクターストレージを設定し、文書(PDF)から画像とテキストを抽出する
ここでは手作業が必要だ。通常、Weaviateは非常に使いやすいベクトルストアで、データを自動的に変換し、挿入時に埋め込みを追加してくれる。しかし、Voyager Multimodal v3にはプラグインがないので、埋め込みを手動で計算しなければならない。この場合、ベクタライザー・モジュールを定義せずにコレクションを作らなければならない。
weaviate をインポートする
from weaviate.classes.config import Configure
client = weaviate.connect_to_local()
コレクション名 = "multimodal_demo"
client.collections.delete(collection_name)
try.
client.collections.create(
name=collection_name、
vectorizer_config=Configure.Vectorizer.none() # このコレクションにvectoriserを設定しない。
)
collection = client.collections.get(コレクション名)
except Exception.
コレクション = client.collections.get(コレクション名)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つのリストを出力する:
- ベクトルを作成するためにボイジャー3号に送るオブジェクトを含むリスト。
- Unstructuredによって抽出されたメタデータを含むリスト。このメタデータは、ベクターストアに追加するために必要です。フィルタリングするための追加属性を提供し、取得したデータについて教えてくれる。
from unstructured.staging.base import elements_from_base64_gzipped_json
import PIL.
インポート io
import base64
embedding_objects = [] (埋め込みオブジェクト)
embedding_metadatas = [] (埋め込みメタデータ)
for chunks in chunks:
embedding_object = [].
metedata_dict = { 以下のようになります。
"text": chunk.to_dict()["text"]、
"filename": chunk.to_dict()["メタデータ"]["ファイル名"]、
"page_number": chunk.to_dict()["メタデータ"]["ページ番号"]、
"last_modified": chunk.to_dict()["metadata"]["last_modified"]、
"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()["メタデータ"]["image_base64"].
image_data.append(base64_image)
pil_image = PIL.Image.open(io.BytesIO(base64.b64decode(base64_image)))
# 画像が1000x1000より大きい場合、縦横比を維持したままリサイズする
pil_image.size[0] > 1000または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)
このスクリプトの結果は、以下に示す内容のリストとなる:
[Location from \n Iceland KIRKJUFELL'.
、
], [<PIL.
['この象徴的な山はアイスランドでのロケ地の最有力候補で、行く前に近くの滝から撮った写真をたくさん見ていました。だからここは、日の出とともに私たちが最初に向かった場所だった。この滝はこの写真(上)のために完璧なクローズアップの面白さを提供してくれるし、カークジュフェルはこの角度から見ると完璧な尖った丘だ。私たちは1、2時間、この滝を探索し、いくつかの異なるアングルを見つけた。]
ステップ3:抽出したデータをベクトル化する
このステップでは、前のステップで作成したブロックに Voyager Pythonパッケージ それをVoyagerに送ると、埋め込まれたすべてのオブジェクトのリストが返ってくる。そしてこの結果を使用し、最終的にはWeaviateに保存することができる。
from dotenv import load_dotenv
インポート voyageai
load_dotenv()
vo = voyageai.Client()
# これは自動的に環境変数VOYAGE_API_KEYを使用します。
# vo = voyageai.Client(api_key="")を使用することもできます。
# テキスト文字列とPIL画像オブジェクトを含むサンプル入力
inputs = embedding_objects
#ベクトル化入力
result = vo.multimodal_embed(
input, model="voyage-multimodal_embed()
model="voyage-multimodal-3"、
truncation=False
)
result.embeddingsにアクセスすると、計算されたすべての埋め込みベクトルのリストを含むリストが得られます:
[[-0.052734375, -0.0164794921875, 0.050048828125, 0.01348876953125, -0.048095703125, ...]]
これで バッチ.add_オブジェクト
メソッドは、この埋め込みデータをWeaviateに単一のバッチとして保存します。propertiesパラメータにもメタデータを追加していることに注意してください。
with collection.batch.dynamic() as batch:
for i, data_row in enumerate(embedding_objects): batch.add_object(): バッチ.
バッチ.add_object(
properties=embedding_metadatas[i], vector=results.embeddings[i], vector=results.embeddings[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)))
幅、高さ = img.size
幅>500または高さ>500の場合
比率 = min(500/width, 500/height)
new_size = (int(width * ratio), int(height * ratio))
img = img.resize(new_size)
表示(img)
print(o.metadata.distance)
下の画像は、滝を検索すると、この検索クエリに関連するテキストと画像が返されることを示している。ご覧のように、写真には滝が映っているが、テキスト自体には滝のことは書かれていない。このテキストは滝が写っている画像に関するもので、そのためこの画像も検索されたのです。通常のテキスト埋め込み検索ではありえないことだ。
ステップ5:検索パイプライン全体に追加する
さて、雑誌からテキストと画像を抽出し、それらの埋め込みを作成し、Weaviateに追加し、類似検索を設定したので、全体の検索パイプラインに追加しようと思う。この例では、LangGraphを使うことにする。ユーザーがこの雑誌について質問し、パイプラインがその質問に答える。すべての作業が終わったので、この部分は通常のテキストを使った典型的な検索パイプラインをセットアップするのと同じくらい簡単です。
前のセクションで説明したロジックの一部を他のモジュールに抽象化した。LangGraphパイプラインにどのように組み込んだか、その例を挙げよう。
class MultiModalRetrievalState(TypedDict).
messages: Annotated[Sequence[BaseMessage], add_messages].
results: リスト[ドキュメント]
base_64_images: リスト[str]
class RAGNodes(BaseNodes).
def __init__(self, logger, mode="online", document_handler=None): super().
__init__(logger, mode="online", document_handler=None).__init__(logger, mode): super().
self.weaviate = Weaviate()
self.mode = mode
async def multi_modal_retrieval(self, state: MultiModalRetrievalState, config): collection_name = config.Get().
コレクション名 = 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 {"結果": 結果}.
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"
コレクション名: 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"]。
上記のコードは以下のチャートを生成する。
このトレースでは質問に答えるためにOpenAIに送られるコンテンツや画像を見ることができる。
評決を下す
マルチモーダルエンベッディングは、同じエンベッディング空間内で異なるデータタイプ(例えばテキストと画像)の情報を統合し検索する可能性を開く。Voyager Multimodal 3モデル、Weaviate、LangGraphといった最先端のツールを組み合わせることで、従来のテキストのみのアプローチよりも直感的にコンテンツを理解しリンクする、堅牢な検索パイプラインを構築する方法を示す。
このアプローチにより、雑誌、パンフレット、PDFなどの様々なデータソースの検索と検索の精度が大幅に向上する。また、明示的なキーワードがない場合でも、マルチモーダル埋め込みによって、画像を説明的なテキストにリンクさせることで、より豊かでコンテキストを意識した洞察を提供できることを実証しています。このチュートリアルでは、これらのテクニックを探求し、プロジェクトに適用することができます。
ノートブックの例:https://github.com/vectrix-ai/vectrix-graphs/blob/main/examples/multi-model-embeddings.ipynb