MultiVector Retriever とMultimodal RAG について
生成AI 関係でマルチ何とかが注目されているが割と誤解されてる感もあるので、MultiVector Retriever とMultimodal RAG についてまとめておく。
参考:Multi-Vector Retriever for RAG on tables, text, and images(LangChain のブログ)
結論
MultiVector Retriever とMultimodal RAG は全くの別概念である。
Multimodal RAG
マルチモーダルはよく耳にすると思う。multi(複数の)modality(入出力形式)なので、文章と動画を一緒に生成AI に入力できるとか、文章と画像が一緒に出力できるのがMultimodal と言える。
さてMultimodal RAG とは、RAG の参考情報が文章と画像など複数の形式に跨ったものであり、質問文に関連した参考情報をどのようにして選択するか?が鍵となる。
簡単のため、参考情報に文章と画像が含まれている場合を想定して以下説明するが、Embedding モデルやLLM が対応さえしていれば、画像以外の形式でも以下全く同じ議論となる。
上記のLangChain のブログでは、以下の3パターンに言及している。どれが最適かはケースバイケースなのだろう。
Option1:マルチモーダル埋め込みを使う
マルチモーダル埋め込み(Multimodal embedding)は、AWS のAmazon Titan Multimodal Embeddings G1 や、Google のmultimodalembedding などがそれ。
要は、文章だろうが画像だろうが、同一のベクトル空間に埋め込んでしまう技術である。すなわち、文章と画像の類似度が計算出来る。
選択する参考情報は、質問文に類似したものにすれば良い。ブログに記載はないが、画像を直接扱うので、後述のマルチモーダルLLM も必須になる。
Option2:マルチモーダルLLM を利用し、画像の要約文を作成しておく
マルチモーダルLLM(Multimodal LLM)は、OpenAI のGPT-4o や、Google のGemini 1.5 Pro などがそれ。要は文章と一緒に画像を入力できるLLM。
マルチモーダルLLM に、参考情報の画像を入力して、それがどんな画像かについて説明する文章を生成させておく。
あとは通常のRAG と同様、質問文に近い参考情報の文章を選んでLLM に入力すれば良い。
一度マルチモーダルLLM で画像から文章を生成しておけば、埋め込むのもLLM に入力するのも文章のみなので、通常の(=マルチモーダル非対応の)埋め込みモデルやLLM が利用できるのが面白い。
Option3:要約文+マルチモーダルLLM
マルチモーダルLLM を用いて、参考情報について説明する文章を生成させるまではOption 2と同じ。
質問文に近い参考情報としてその説明文が選ばれた場合、Option 2ではその説明文をLLM に入力していたが、今回は元の画像をLLM に入力する点が異なる。
画像を入力する可能性があるためマルチモーダルLLM が必要だが、マルチモーダル埋め込みは不要。
MultiVector Reriever
通常のRetriever では、参考情報とそれを埋め込んだベクトルが1:1で対応する。MultiVector Retrieverは、同一の参考情報が、異なるベクトルとして複数埋め込まれている(可能性がある)点で異なる。当然、参考情報の形式が何であるかは本質ではない。
上記のブログでは、参考情報(テキスト形式)の要約文を作成しておいて、質問文が参考情報そのもの・その要約文のどちらかに近ければ選択されることを提案している。
参考情報に複雑な言い回しが使われている等の場合に、質問文と関連しているのに選ばれない可能性を減らしたいのだと思われる。勿論、関連しない参考情報が選ばれてしまう可能性もあるので、一概にどちらが良いかは難しそうだが。
余談
LangChain のMultiVectorRetriever のソースコードを見るとより理解が深まると思う。(以下はv0.3 のもの)
ポイントはd.metadata[self.id_key] not in ids
の部分であり、返す参考情報がダブらないようにしている。
通常のRetriever のように参考情報とベクトルが1:1対応するなら不必要な処理なわけで、MultiVector ならでは感がある。
def _get_relevant_documents(
self, query: str, *, run_manager: CallbackManagerForRetrieverRun
) -> List[Document]:
"""Get documents relevant to a query.
Args:
query: String to find relevant documents for
run_manager: The callbacks handler to use
Returns:
List of relevant documents
"""
if self.search_type == SearchType.mmr:
sub_docs = self.vectorstore.max_marginal_relevance_search(
query, **self.search_kwargs
)
elif self.search_type == SearchType.similarity_score_threshold:
sub_docs_and_similarities = (
self.vectorstore.similarity_search_with_relevance_scores(
query, **self.search_kwargs
)
)
sub_docs = [sub_doc for sub_doc, _ in sub_docs_and_similarities]
else:
sub_docs = self.vectorstore.similarity_search(query, **self.search_kwargs)
# We do this to maintain the order of the ids that are returned
ids = []
for d in sub_docs:
if self.id_key in d.metadata and d.metadata[self.id_key] not in ids:
ids.append(d.metadata[self.id_key])
docs = self.docstore.mget(ids)
return [d for d in docs if d is not None]
Discussion