본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 7장: 멀티모달 임베딩과 크로스모달 검색
2026년 2월 19일·AI / ML·

7장: 멀티모달 임베딩과 크로스모달 검색

CLIP 기반 멀티모달 임베딩의 원리, 텍스트-이미지 크로스모달 검색, 통합 벡터 스토어 설계, 그리고 실전 멀티모달 검색 시스템 구축을 다룹니다.

11분762자6개 섹션
llmmultimodalembedding
공유
multimodal-ai7 / 11
1234567891011
이전6장: 비디오 이해와 분석다음8장: 멀티모달 RAG 시스템 설계

6장에서 비디오 이해를 다뤘습니다. 이 장에서는 멀티모달 데이터를 벡터 공간에서 통합적으로 표현하고 검색하는 기법을 다룹니다. 텍스트로 이미지를 검색하거나, 이미지로 유사한 텍스트를 찾는 크로스모달 검색은 멀티모달 RAG(8장)의 핵심 기반입니다.

멀티모달 임베딩의 원리

공유 벡터 공간

멀티모달 임베딩의 핵심은 서로 다른 모달리티의 데이터를 같은 벡터 공간에 매핑하는 것입니다.

텍스트: "노을이 지는 해변"  → [Text Encoder]  → [0.12, -0.34, 0.56, ...]
이미지: 🌅 해변 사진        → [Image Encoder] → [0.11, -0.32, 0.58, ...]
                                                  ↑ 가까운 벡터 = 의미적 유사

텍스트: "소스 코드 리뷰"    → [Text Encoder]  → [-0.45, 0.67, -0.12, ...]
                                                  ↑ 먼 벡터 = 의미적 상이

이렇게 같은 공간에 매핑되면, 텍스트 벡터와 이미지 벡터 간의 코사인 유사도로 의미적 관련성을 측정할 수 있습니다.

주요 멀티모달 임베딩 모델

모델차원모달리티특징
OpenAI CLIP512/768텍스트, 이미지범용, 대조 학습
SigLIP256~1152텍스트, 이미지Sigmoid 손실, 효율적
OpenAI Embeddings v3256~3072텍스트텍스트 전용이지만 고성능
Cohere Embed v31024텍스트, 이미지다국어, 멀티모달
Voyage Multimodal1024텍스트, 이미지문서+이미지 통합
Jina CLIP v2768텍스트, 이미지다국어, 긴 텍스트

CLIP 임베딩 실전

기본 사용법

CLIP 임베딩 생성
python
import torch
from PIL import Image
from transformers import CLIPProcessor, CLIPModel
 
model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-large-patch14")
 
# 텍스트 임베딩
texts = ["해변의 석양", "도시의 야경", "숲 속의 오솔길"]
text_inputs = processor(text=texts, return_tensors="pt", padding=True)
 
with torch.no_grad():
    text_features = model.get_text_features(**text_inputs)
    text_features = text_features / text_features.norm(dim=-1, keepdim=True)
 
# 이미지 임베딩
image = Image.open("sunset_beach.jpg")
image_inputs = processor(images=image, return_tensors="pt")
 
with torch.no_grad():
    image_features = model.get_image_features(**image_inputs)
    image_features = image_features / image_features.norm(dim=-1, keepdim=True)
 
# 유사도 계산
similarities = (text_features @ image_features.T).squeeze()
for text, sim in zip(texts, similarities):
    print(f"{text}: {sim.item():.4f}")

API 기반 멀티모달 임베딩

Cohere Embed v3 멀티모달
python
import cohere
import base64
 
co = cohere.Client(api_key="...")
 
# 텍스트와 이미지를 같은 공간에 임베딩
response = co.embed(
    model="embed-v4.0",
    input_type="search_document",
    texts=["해변의 석양 사진"],
    images=[base64.standard_b64encode(open("sunset.jpg", "rb").read()).decode()],
)
 
text_embedding = response.embeddings.float_[0]
image_embedding = response.embeddings.float_[1]

크로스모달 검색 시스템 구축

벡터 스토어 설계

멀티모달 벡터 스토어
python
from dataclasses import dataclass
from typing import Literal
import numpy as np
 
@dataclass
class MultimodalDocument:
    id: str
    modality: Literal["text", "image", "audio", "video"]
    content_ref: str  # 원본 데이터 참조 (파일 경로 또는 텍스트)
    embedding: np.ndarray
    metadata: dict
 
class MultimodalVectorStore:
    def __init__(self, dimension: int = 768):
        self.documents: list[MultimodalDocument] = []
        self.dimension = dimension
 
    def add(self, doc: MultimodalDocument):
        assert doc.embedding.shape[0] == self.dimension
        self.documents.append(doc)
 
    def search(
        self,
        query_embedding: np.ndarray,
        top_k: int = 10,
        modality_filter: str | None = None,
    ) -> list[tuple[MultimodalDocument, float]]:
        """크로스모달 검색"""
        results = []
 
        for doc in self.documents:
            if modality_filter and doc.modality != modality_filter:
                continue
 
            similarity = np.dot(query_embedding, doc.embedding) / (
                np.linalg.norm(query_embedding) * np.linalg.norm(doc.embedding)
            )
            results.append((doc, float(similarity)))
 
        results.sort(key=lambda x: x[1], reverse=True)
        return results[:top_k]
 
    def text_to_image_search(
        self, text_embedding: np.ndarray, top_k: int = 10
    ) -> list[tuple[MultimodalDocument, float]]:
        """텍스트로 이미지 검색"""
        return self.search(text_embedding, top_k, modality_filter="image")
 
    def image_to_text_search(
        self, image_embedding: np.ndarray, top_k: int = 10
    ) -> list[tuple[MultimodalDocument, float]]:
        """이미지로 텍스트 검색"""
        return self.search(image_embedding, top_k, modality_filter="text")

프로덕션 벡터 DB 활용

Pinecone을 활용한 멀티모달 검색
python
from pinecone import Pinecone
 
pc = Pinecone(api_key="...")
 
# 멀티모달 인덱스 생성
index = pc.Index("multimodal-search")
 
# 이미지 인덱싱
index.upsert(
    vectors=[
        {
            "id": f"img_{i}",
            "values": image_embedding.tolist(),
            "metadata": {
                "modality": "image",
                "file_path": f"/images/{filename}",
                "description": auto_caption,
                "tags": detected_tags,
            },
        }
        for i, (filename, image_embedding, auto_caption, detected_tags)
        in enumerate(processed_images)
    ]
)
 
# 텍스트로 이미지 검색
text_query = "팀 회의 중인 사람들"
query_embedding = encode_text(text_query)
 
results = index.query(
    vector=query_embedding.tolist(),
    top_k=10,
    filter={"modality": {"$eq": "image"}},
    include_metadata=True,
)

멀티모달 검색 파이프라인

이미지 자동 캡셔닝

검색 품질을 높이기 위해 이미지에 자동으로 캡션을 생성하여 텍스트 임베딩과 함께 저장합니다.

자동 캡셔닝 파이프라인
python
async def index_image_with_caption(
    image_path: str,
    vlm_client,
    clip_model,
    vector_store,
):
    """이미지를 캡셔닝하고 멀티모달 인덱싱"""
    # 1. VLM으로 상세 캡션 생성
    caption = await generate_caption(vlm_client, image_path)
 
    # 2. CLIP 이미지 임베딩
    image_embedding = clip_model.encode_image(image_path)
 
    # 3. CLIP 캡션 텍스트 임베딩
    caption_embedding = clip_model.encode_text(caption)
 
    # 4. 두 임베딩을 결합하여 저장
    # (이미지 임베딩으로 시각적 유사도, 캡션 임베딩으로 의미적 유사도)
    vector_store.add(MultimodalDocument(
        id=f"img_{hash(image_path)}",
        modality="image",
        content_ref=image_path,
        embedding=image_embedding,
        metadata={
            "caption": caption,
            "caption_embedding": caption_embedding.tolist(),
        },
    ))

하이브리드 검색

시각적 유사도와 의미적 유사도를 결합한 하이브리드 검색입니다.

하이브리드 검색
python
def hybrid_search(
    query: str,
    clip_model,
    vector_store,
    visual_weight: float = 0.5,
    semantic_weight: float = 0.5,
    top_k: int = 10,
) -> list[dict]:
    """시각적 + 의미적 하이브리드 검색"""
    query_embedding = clip_model.encode_text(query)
 
    # 1. 시각적 유사도 (CLIP 이미지 임베딩 vs 쿼리)
    visual_results = vector_store.search(query_embedding, top_k=top_k * 2)
 
    # 2. 의미적 유사도 (캡션 임베딩 vs 쿼리)
    candidates = {}
    for doc, visual_score in visual_results:
        caption_emb = np.array(doc.metadata.get("caption_embedding", []))
        if caption_emb.size > 0:
            semantic_score = float(np.dot(query_embedding, caption_emb) / (
                np.linalg.norm(query_embedding) * np.linalg.norm(caption_emb)
            ))
        else:
            semantic_score = 0.0
 
        combined_score = (
            visual_weight * visual_score +
            semantic_weight * semantic_score
        )
        candidates[doc.id] = {
            "document": doc,
            "visual_score": visual_score,
            "semantic_score": semantic_score,
            "combined_score": combined_score,
        }
 
    # 3. 결합 점수로 정렬
    sorted_results = sorted(
        candidates.values(),
        key=lambda x: x["combined_score"],
        reverse=True,
    )
    return sorted_results[:top_k]
Tip

하이브리드 검색에서 visual_weight와 semantic_weight의 최적 비율은 데이터와 사용 사례에 따라 다릅니다. 일반적으로 제품 이미지 검색은 시각적 유사도(0.7:0.3), 개념적 검색은 의미적 유사도(0.3:0.7)를 높이는 것이 효과적입니다.

성능 최적화

임베딩 캐싱

임베딩 캐시
python
import hashlib
import json
 
class EmbeddingCache:
    def __init__(self, cache_dir: str = ".embedding_cache"):
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(exist_ok=True)
 
    def _key(self, content: str, model: str) -> str:
        return hashlib.sha256(f"{model}:{content}".encode()).hexdigest()
 
    def get(self, content: str, model: str) -> np.ndarray | None:
        path = self.cache_dir / f"{self._key(content, model)}.npy"
        if path.exists():
            return np.load(path)
        return None
 
    def set(self, content: str, model: str, embedding: np.ndarray):
        path = self.cache_dir / f"{self._key(content, model)}.npy"
        np.save(path, embedding)

차원 축소

높은 차원의 임베딩은 저장 비용과 검색 속도에 영향을 줍니다.

Matryoshka 임베딩 활용
python
# 일부 최신 모델은 Matryoshka Representation Learning을 지원
# 임베딩의 앞부분만 잘라도 유의미한 유사도 측정 가능
 
full_embedding = model.encode(text)  # 1024 차원
reduced_embedding = full_embedding[:256]  # 256 차원으로 축소
 
# 정규화
reduced_embedding = reduced_embedding / np.linalg.norm(reduced_embedding)

정리

멀티모달 임베딩은 서로 다른 모달리티의 데이터를 통합 벡터 공간에서 비교하고 검색할 수 있게 합니다. CLIP 계열 모델로 이미지와 텍스트를 같은 공간에 매핑하고, 자동 캡셔닝과 하이브리드 검색을 결합하면 높은 정확도의 크로스모달 검색 시스템을 구축할 수 있습니다.

다음 장에서는 이 임베딩 기반 위에 멀티모달 RAG 시스템을 설계합니다. 텍스트, 이미지, 표 등 다양한 모달리티의 문서를 통합적으로 검색하고 LLM에 전달하는 파이프라인을 구축합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#llm#multimodal#embedding

관련 글

AI / ML

8장: 멀티모달 RAG 시스템 설계

텍스트, 이미지, 표, 차트 등 다양한 모달리티를 통합하는 멀티모달 RAG 시스템의 설계와 구현을 다룹니다. ColPali, 비전 기반 검색, 문서 파싱 전략을 배웁니다.

2026년 2월 21일·13분
AI / ML

6장: 비디오 이해와 분석

멀티모달 AI를 활용한 비디오 이해 기법 — 프레임 추출 전략, 시간적 추론, 영상 요약, 그리고 실시간 비디오 분석 파이프라인 설계를 다룹니다.

2026년 2월 17일·12분
AI / ML

9장: 멀티모달 에이전트 구축

시각적 이해 능력을 갖춘 AI 에이전트의 설계와 구현 — 화면 상호작용 에이전트, 멀티모달 도구 호출, Computer Use, 그리고 실전 에이전트 패턴을 다룹니다.

2026년 2월 23일·12분
이전 글6장: 비디오 이해와 분석
다음 글8장: 멀티모달 RAG 시스템 설계

댓글

목차

약 11분 남음
  • 멀티모달 임베딩의 원리
    • 공유 벡터 공간
    • 주요 멀티모달 임베딩 모델
  • CLIP 임베딩 실전
    • 기본 사용법
    • API 기반 멀티모달 임베딩
  • 크로스모달 검색 시스템 구축
    • 벡터 스토어 설계
    • 프로덕션 벡터 DB 활용
  • 멀티모달 검색 파이프라인
    • 이미지 자동 캡셔닝
    • 하이브리드 검색
  • 성능 최적화
    • 임베딩 캐싱
    • 차원 축소
  • 정리