실습: Voyager-3 및 LangGraph로 강력한 멀티모달 검색 구축하기

AI 실습 튜토리얼7개월 전에 게시 됨 AI 공유 서클
2.1K 00

Voyage AI의 Voyager 3는 텍스트와 이미지를 같은 공간에 임베드할 수 있는 새로운 최첨단 모델입니다. 이 글에서는 잡지에서 이러한 멀티모달 임베딩을 추출하여 벡터 데이터베이스(Weaviate)에 저장하고 동일한 임베딩 벡터를 사용하여 텍스트와 이미지의 유사성 검색을 수행하는 방법에 대해 설명합니다.

 

이미지와 텍스트를 같은 공간에 포함하면 웹 페이지, PDF 파일, 잡지, 책, 브로셔, 각종 논문과 같은 멀티모달 콘텐츠에 대해 매우 정확한 검색을 수행할 수 있습니다. 이 기술이 왜 그렇게 흥미로운가요? 텍스트와 이미지를 같은 공간에 임베드할 때 가장 흥미로운 점은 특정 이미지와 관련된 텍스트를 검색하고 검색할 수 있고 그 반대의 경우도 가능하다는 것입니다. 예를 들어 고양이를 검색하는 경우 고양이를 보여주는 이미지를 찾을 수 있지만 텍스트에 '고양이'라는 단어가 명시적으로 포함되어 있지 않더라도 해당 이미지를 참조하는 텍스트도 얻을 수 있습니다.

기존 텍스트 임베딩 유사도 검색과 멀티모달 임베딩 공간의 차이점을 보여드리겠습니다:

예시 질문: 잡지에서 고양이에 대해 뭐라고 말하나요?

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

사진 잡지의 스크린샷 - 아웃도어

 

일반 유사도 검색 답변

제공된 검색 결과에는 고양이에 대한 구체적인 정보가 포함되어 있지 않습니다. 동물 초상화 및 사진 촬영 기법은 언급되어 있지만 고양이 또는 고양이와 관련된 세부 정보는 명시적으로 언급되어 있지 않습니다.

위와 같이 '고양이'라는 단어는 언급되지 않았으며, 동물 사진 촬영 방법에 대한 설명과 사진만 있습니다. "고양이"라는 단어가 언급되지 않았기 때문에 일반 유사도 검색에서는 결과가 나오지 않았습니다.

멀티모달 검색으로 답변 찾기

이 잡지에는 고양이의 초상화가 실려 있으며, 고양이의 얼굴 특징과 성격을 섬세하게 포착한 점이 돋보입니다. 이 글은 잘 만들어진 동물 초상화가 어떻게 피사체의 영혼에 다가가고 매력적인 눈맞춤을 통해 보는 사람과 감정적 교감을 형성하는지를 강조합니다.

복합 검색을 사용하여 고양이 사진을 찾은 다음 관련 텍스트를 연결합니다. 이 데이터를 모델에 제공하면 모델이 더 잘 대답하고 문맥을 이해할 수 있습니다.

 

멀티모달 임베딩 및 검색 파이프라인을 구축하는 방법

이제 이러한 파이프라인이 어떻게 작동하는지 몇 가지 단계로 설명하겠습니다:

  1. 우리는 비정형(데이터 추출을 위한 강력한 Python 라이브러리) PDF 파일에서 텍스트와 이미지를 추출합니다.
  2. 우리는 Voyager 멀티모달 3 이 모델은 동일한 벡터 공간에서 텍스트와 이미지에 대한 멀티모달 벡터를 생성합니다.
  3. 벡터 스토어에 삽입합니다(Weaviate)를 입력합니다.
  4. 마지막으로 유사도 검색을 수행하여 텍스트와 이미지의 결과를 비교합니다.

1단계: 벡터 저장소를 설정하고 문서(PDF)에서 이미지와 텍스트를 추출합니다.

여기서 우리는 몇 가지 수동 작업을 해야 합니다. 일반적으로 Weaviate는 데이터를 자동으로 변환하고 삽입 시 임베딩을 추가하는 매우 사용하기 쉬운 벡터 저장소입니다. 하지만 보이저 멀티모달 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

여기서는 Docker 컨테이너에서 로컬 Weaviate 인스턴스를 실행하고 있습니다.

2단계:PDF에서 문서와 이미지 추출

이것은 프로세스 작업의 핵심 단계입니다. 여기에서 텍스트와 이미지가 포함된 PDF를 가져온 다음 콘텐츠(이미지와 텍스트)를 추출하여 관련 블록에 저장합니다. 따라서 각 블록은 문자열(실제 텍스트)과 Python 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)

여기서는 hi_res 전략을 사용하고 추출_이미지_블록_투_페이로드 나중에 실제 임베딩을 위해 이 정보가 필요하므로 이미지를 페이로드로 내보냅니다. 모든 요소를 추출한 후에는 문서의 제목을 기준으로 요소를 블록으로 그룹화합니다.

자세한 내용은 다음을 확인하세요. 청킹에 대한 비정형 문서.

다음 스크립트에서는 이러한 블록을 사용하여 두 개의 목록을 출력합니다:

  1. 벡터를 생성하기 위해 Voyager 3에 보낼 객체가 포함된 목록입니다.
  2. 비정형에서 추출한 메타데이터가 포함된 목록입니다. 이 메타데이터는 벡터 저장소에 추가해야 하므로 필수입니다. 필터링할 수 있는 추가 속성을 제공하고 검색된 데이터에 대한 정보를 알려줍니다.
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로 보내면 모든 임베디드 객체 목록을 반환합니다. 그런 다음 이 결과를 사용하여 최종적으로 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에 단일 배치로 저장합니다. 속성 매개변수에 메타데이터도 추가했습니다.

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에는 보이저 멀티모달용 모듈이 없으므로 유사도 검색을 수행하려면 검색 쿼리 벡터를 직접 계산한 후 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 构建强大的多模态搜索

유사도 검색 결과를 보여주는 이미지

 

5단계: 전체 검색 파이프라인에 추가하기

이제 잡지에서 텍스트와 이미지를 추출하고, 임베딩을 생성하고, Weaviate에 추가하고, 유사성 검색을 설정했으므로, 이를 전체 검색 파이프라인에 추가하겠습니다. 이 예제에서는 사용자가 이 잡지에 대해 질문하면 파이프라인이 그 질문에 답변하는 LangGraph를 사용하겠습니다. 이제 모든 작업이 완료되었으므로 이 부분은 일반 텍스트를 사용하여 일반적인 검색 파이프라인을 설정하는 것만큼이나 쉽습니다.

이전 섹션에서 논의한 로직 중 일부를 다른 모듈로 추상화했습니다. 이를 다음과 같이 통합한 방법은 다음과 같습니다. 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 构建强大的多模态搜索

생성된 차트의 시각적 표현

 

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

이 추적에서질문에 대한 답변을 위해 OpenAI로 전송되는 콘텐츠와 이미지를 확인할 수 있습니다.

 

평결에 도달하기

멀티모달 임베딩은 동일한 임베딩 공간 내에서 서로 다른 데이터 유형(예: 텍스트와 이미지)의 정보를 통합하고 검색할 수 있는 가능성을 열어줍니다. 보이저 멀티모달 3 모델, Weaviate, LangGraph와 같은 최첨단 도구를 결합하여 기존의 텍스트 전용 접근 방식보다 콘텐츠를 더 직관적으로 이해하고 연결하는 강력한 검색 파이프라인을 구축하는 방법을 보여드립니다.

이 접근 방식은 잡지, 브로셔, PDF 등 다양한 데이터 소스에 대한 검색 및 검색 정확도를 크게 향상시킵니다. 또한 멀티모달 임베딩이 명시적인 키워드가 없는 경우에도 이미지를 설명 텍스트에 연결하여 보다 풍부하고 맥락에 맞는 인사이트를 제공하는 방법을 보여줍니다. 이 튜토리얼을 통해 이러한 기술을 살펴보고 프로젝트에 적용할 수 있습니다.

노트북 예시: https://github.com/vectrix-ai/vectrix-graphs/blob/main/examples/multi-model-embeddings.ipynb

© 저작권 정책

관련 문서

댓글 없음

댓글에 참여하려면 로그인해야 합니다!
지금 로그인
없음
댓글 없음...