토니의 연습장
BLIP / CLIP 본문
BLIP과 CLIP이 서로 다른 방식으로 유사도를 계산하는 주요 이유는 모델 구조와 학습 목표의 차이에서 비롯됩니다.
- BLIP(ITM) – Image–Text Matching Head
- BLIP은 사전학습 단계에서 이미지–텍스트 매칭(Image–Text Matching, ITM) 태스크를 추가로 학습합니다.
- use_itm_head=True일 때는, 이미지와 텍스트를 함께 입력해 매칭 여부(positive/negative) 를 분류하는 분류기 헤드(logits) 를 돌려줍니다.
- 반면 use_itm_head=False로 설정하면, 내부에서 추출된 이미지·텍스트 임베딩을 꺼내와 (정규화된) 내적 또는 코사인 유사도 형태로 반환합니다.
- 즉, ITM 헤드를 사용하면 “이 쌍이 매칭(올바른 캡션)”인지에 대한 분류 확신도를, 헤드를 쓰지 않으면 순수 임베딩 유사도를 얻을 수 있습니다.
- CLIP – Contrastive Embedding Alignment
- CLIP은 이미지 인코더와 텍스트 인코더를 대조 학습(contrastive learning) 방식으로 함께 훈련하여, 두 임베딩을 동일한 잠재공간(shared latent space)에 정렬합니다.
- 학습 시 양(positive) 쌍과 음(negative) 쌍을 구분하기 위해 코사인 유사도(cosine similarity) 를 손실 함수로 직접 사용합니다.
- 따라서 추론 단계에서는, 이미지와 텍스트 임베딩을 L2 정규화(unit‑norm) 한 뒤 내적(dot product) 만으로 코사인 유사도를 구하면 됩니다.
- 요약 비교
BLIP (ITM)CLIP
학습 목표 | 매칭 분류(ITM 헤드) + 생성/대조 학습 | 순수 대조 학습(이미지↔텍스트 정렬) |
유사도 계산 방식 | - ITM 헤드: 분류 로짓 |
- 헤드 미사용: 임베딩 내적 또는 코사인 유사도 | L2 정규화된 임베딩 간 내적 = 코사인 유사도 |
| 입력 처리 방식 | 이미지+텍스트를 동시에 모델에 입력 | 이미지 인코더·텍스트 인코더 분리 실행 |
| 반환 형태 | (batch_size,) 매칭 점수 벡터 | (batch_size, dim) 임베딩 → (batch, batch) 유사도 행렬 |
- BLIP은 “이 이미지와 이 텍스트가 얼마나 잘 맞는가”를 판별하도록 특화된 헤드가 있고, 필요시 헤드를 끄고 간단한 임베딩 유사도로도 사용할 수 있습니다.
- CLIP은 처음부터 이미지와 텍스트 임베딩 간 코사인 유사도를 직접 최적화하도록 설계되어, 별도의 매칭 헤드 없이 내적 방식으로 유사도를 구합니다.
이처럼 모델의 설계 철학과 학습 방식이 달라서, 각각의 유사도 계산 방법도 자연스럽게 다르게 구현되는 것입니다.
- 모델 학습 목표의 차이
- BLIP은 이미지–텍스트 매칭(ITM) 태스크와 생성 학습을 함께 수행하도록 설계되었습니다. ITM 헤드를 사용하면 “이 이미지와 이 텍스트가 올바르게 매칭되는가”를 분류하는 로짓(logits)을 반환합니다.
- 반면 CLIP은 이미지 인코더와 텍스트 인코더를 대조 학습(contrastive learning) 방식으로 훈련해, 양(positive) 쌍과 음(negative) 쌍을 구분하도록 코사인 유사도를 직접 최적화합니다.
- 유사도 계산 방식의 차이
- BLIP에서 use_itm_head=True일 때는 매칭 확신도를 나타내는 분류 점수를, use_itm_head=False일 때는 헤드 직전의 임베딩 벡터를 꺼내와 벡터 내적 또는 코사인 유사도(별도 정규화 시) 형태로 반환합니다.
- CLIP은 기본적으로 L2 정규화된(unit‑norm) 임베딩을 반환하므로, 이들을 내적(dot product)하기만 해도 곧바로 코사인 유사도에 해당합니다.
- ITM 헤드를 끈 BLIP과 CLIP이 완전히 동일한가?
- 두 모델 모두 “벡터 내적”을 통해 유사도 성분을 계산할 수 있지만, 자동 정규화 여부와 임베딩 공간의 분포가 다르기 때문에 동일하다고 보긴 어렵습니다.
- 정규화 여부
- CLIP 임베딩은 ‖v‖₂ = 1이 보장되므로, 내적(dot) 연산이 곧바로 코사인 유사도입니다.
- BLIP(use_itm_head=False) 임베딩은 정규화가 적용되지 않을 수 있어, 단순 내적은 “스케일된 유사도 점수”가 됩니다.
- 임베딩 공간의 차이
- 두 모델은 서로 다른 데이터와 목적을 갖고 학습되었으므로, 같은 텍스트·이미지 쌍이라도 임베딩 벡터의 분포와 스케일이 다릅니다.
- 따라서 숫자 자체를 비교하기보다는, 각 모델 내에서 순위(rank)나 Top‑K 성능으로 평가해야 합니다.
- 정규화 여부
- 두 모델 모두 “벡터 내적”을 통해 유사도 성분을 계산할 수 있지만, 자동 정규화 여부와 임베딩 공간의 분포가 다르기 때문에 동일하다고 보긴 어렵습니다.
- BLIP 임베딩도 코사인 유사도로 사용하고 싶을 때
- BLIP 임베딩에 대해서도 코사인 유사도를 정확히 계산하려면, 벡터를 수동으로 L2 정규화하신 후 내적을 적용하시면 됩니다.
# 예시 코드
img_norm = blip_image_embeds / np.linalg.norm(blip_image_embeds, axis=1, keepdims=True)
txt_norm = blip_text_embeds / np.linalg.norm(blip_text_embeds, axis=1, keepdims=True)
cosine_sim = np.dot(img_norm, txt_norm.T)
이렇게 하면 “정규화된 벡터 간 코사인 유사도”라는 의미론적 계산 방식은 CLIP과 동일해집니다.
정리하자면,
- ITM 헤드를 끈 BLIP과 CLIP 모두 벡터 내적 기반의 유사도를 반환할 수 있지만,
- CLIP은 기본적으로 단위 정규화된 임베딩을 사용해 진짜 코사인 유사도를,
- BLIP은 별도 정규화가 필요할 수 있는 스케일된 점수를 제공합니다.
- 필요에 따라 BLIP 임베딩도 수동 L2 정규화를 거치면, CLIP과 동일한 방식의 코사인 유사도로 활용할 수 있습니다.
import torch
import torch.nn.functional as F
from transformers import BlipProcessor, BlipForImageTextRetrieval
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
from tqdm import tqdm
import os
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from datasets import load_dataset
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# BLIP-ITM 모델 로드
blip_processor = BlipProcessor.from_pretrained("Salesforce/blip-itm-base-coco")
blip_model = BlipForImageTextRetrieval.from_pretrained("Salesforce/blip-itm-base-coco").to("cuda")
# CLIP 모델 및 프로세서 로드
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
def evaluate_retrieval(similarity_matrix, top_k=[1, 5, 10]):
"""Top-K 검색 정확도를 계산하는 함수"""
num_samples = similarity_matrix.shape[0]
correct_ranks = []
for i in range(num_samples):
sorted_indices = np.argsort(similarity_matrix[i])[::-1]
rank = (sorted_indices == i).nonzero()[0] # 정답 이미지의 랭킹
correct_ranks.append(rank)
results = {f"Top-{k}": sum(r < k for r in correct_ranks) / num_samples for k in top_k}
return results
def get_blip_itm_scores(images, texts):
inputs = blip_processor(images, texts, padding=True, return_tensors="pt").to("cuda")
with torch.no_grad():
# itm_scores = blip_model(**inputs)[0]
cosine_score = blip_model(**inputs, use_itm_head=False)[0]
# return itm_scores, cosine_score
return cosine_score
def get_clip_embeddings(images, texts):
inputs = clip_processor(text=texts, images=images, return_tensors="pt", padding=True, truncation=True)
with torch.no_grad():
image_embeds = clip_model.get_image_features(pixel_values=inputs["pixel_values"]).cpu().numpy()
text_embeds = clip_model.get_text_features(input_ids=inputs["input_ids"], attention_mask=inputs["attention_mask"]).cpu().numpy()
return image_embeds, text_embeds
dataset = load_dataset("clip-benchmark/wds_flickr8k")['test']
sample_size = 100 # 사용할 샘플 개수
subset = dataset.shuffle().select(range(sample_size))
# 이미지와 캡션 추출
images = subset['jpg']
captions = subset["txt"]
blip_similarity_matrix = get_blip_itm_scores(images, captions)
blip_similarity_matrix = blip_similarity_matrix.cpu().numpy()
# OpenCLIP 유사도 계산 (배치 연산 적용)
clip_image_embeddings, clip_text_embeddings = get_clip_embeddings(images, captions)
clip_similarity_matrix = np.dot(clip_image_embeddings, clip_text_embeddings.T) # Cosine similarity
# 검색 성능 평가
print("\nBLIP Retrieval Performance:")
blip_results = evaluate_retrieval(blip_similarity_matrix)
for k, v in blip_results.items():
print(f"{k}: {v.item():.4f}")
print("\nCLIP Retrieval Performance:")
clip_results = evaluate_retrieval(clip_similarity_matrix)
for k, v in clip_results.items():
print(f"{k}: {v.item():.4f}")
print('----------')
'언어 AI (NLP) > Multimodal (MLLM, LMM)' 카테고리의 다른 글
Text-to-Image (0) | 2025.08.08 |
---|---|
Multimodal 분기 처리 (0) | 2025.07.23 |
Multimodal LoRA (0) | 2025.07.23 |
MLLM 구현 (0) | 2025.05.26 |
MLLM / LMM (0) | 2025.04.07 |