Vision Language Models (VLM): 이미지와 텍스트의 멀티모달 학습
Vision Language Models (VLM): 이미지와 텍스트의 멀티모달 학습
개요
Vision Language Models(VLM)는 이미지와 텍스트를 동시에 이해하는 인공지능 모델입니다. 기존의 이미지 분류 모델이나 텍스트 분석 모델과 달리, VLM은 두 가지 모달리티(Modality) 사이의 의미적 관계를 학습하여 더욱 유연하고 강력한 표현 능력을 제공합니다.
이 글에서는 VLM의 대표 모델인 **CLIP(Contrastive Language-Image Pre-training)**을 중심으로, 멀티모달 학습의 원리부터 실무 응용까지 상세히 설명합니다.
1. 문제 정의: 전통적 AI의 한계
1.1 단일 모달리티의 문제점
기존의 인공지능 모델들은 한 가지 종류의 데이터만 처리했습니다.
이미지 분류 (Image Classification)
입력: 고양이 사진
출력: "고양이" (라벨만)
한계: 이미지가 무엇인지만 알고, 그 이미지를 텍스트로 설명하지 못함
텍스트 분류 (Text Classification)
입력: "이 영화는 정말 재미있었다"
출력: "긍정" (감정 분류)
한계: 텍스트는 이해하지만, 관련 이미지와 연결하지 못함
Cross-Modal 문제
질문: 이 사진과 이 설명이 같은 물건인가?
기존 방식: 이미지 모델과 텍스트 모델 각각 돌려야 하고,
두 결과를 어떻게 비교할지 명확하지 않음
1.2 멀티모달 학습의 필요성
현실의 인지는 항상 멀티모달입니다.
사람이 사진을 본다 = 시각 + 언어 기억 + 맥락
ChatGPT가 이미지를 본다 = 이미지를 텍스트로 이해
멀티모달 학습은 이런 현실을 모델링합니다.
실제 사용 사례:
- 이미지 검색 (Pinterest, Google Lens)
- 이미지 캡셔닝 (사진에 자동으로 설명 추가)
- 시각 질의응답 (VQA: “이 사진에서 뭐가 보이나?”)
- 추천 시스템 (유사한 스타일의 상품 추천)
2. CLIP: Contrastive Language-Image Pre-training
2.1 핵심 아이디어
CLIP은 2021년 OpenAI가 발표한 모델로, 멀티모달 학습의 패러다임을 바꿨습니다.
CLIP의 핵심 철학:
"이미지와 텍스트를 같은 벡터 공간(Embedding Space)에 표현하고,
같은 쌍의 이미지-텍스트는 가깝게,
다른 쌍은 멀게 배치한다"
2.2 아키텍처
CLIP은 두 개의 인코더로 구성됩니다.
┌─────────────────────────────────────────────────────────────┐
│ CLIP Architecture │
├─────────────────────────────────────────────────────────────┤
│ │
│ Image Encoder Text Encoder │
│ (Vision Transformer) (Transformer) │
│ │ │ │
│ [ Image ] [Text] │
│ (224×224) "A photo │
│ │ of a cat" │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ ViT Encoder │ │ Text Encoder│ │
│ │ (12 layers) │ │ (12 layers) │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ Image Vector Text Vector │
│ (Shape: 512) (Shape: 512) │
│ │ │ │
│ └────────┬───────────┘ │
│ │ │
│ Cosine Similarity Computation │
│ (비교) │
│ │
└─────────────────────────────────────────────────────────────┘
2.3 학습 과정 (Contrastive Learning)
CLIP의 학습은 **대조적 학습(Contrastive Learning)**을 사용합니다.
배치 구성:
배치에 N개의 이미지-텍스트 쌍이 있습니다.
배치: [
(Image₁, Text₁), ✓ 같은 쌍 (일치)
(Image₂, Text₂), ✓ 같은 쌍 (일치)
...
(Imageₙ, Textₙ) ✓ 같은 쌍 (일치)
]
배치 크기: N (보통 256 또는 512)
손실함수 (Contrastive Loss):
각 이미지에 대해, 모든 N개의 텍스트와 유사도를 계산합니다.
예시 (이미지 1 기준):
Image₁ vs Text₁: 0.95 ✓ 높은 유사도 (원하는 것)
Image₁ vs Text₂: 0.15 ✗ 낮은 유사도 (원하는 것)
Image₁ vs Text₃: 0.08 ✗ 낮은 유사도 (원하는 것)
...
Image₁ vs Textₙ: 0.10 ✗ 낮은 유사도 (원하는 것)
손실함수는 이 확률들을 교차 엔트로피로 계산합니다.
수학적 표현:
similarity(image_i, text_j) = cosine_similarity(I_i, T_j)
Loss = -log(exp(sim(i,i) / τ) / Σⱼ exp(sim(i,j) / τ))
여기서:
- i: 이미지 인덱스
- j: 텍스트 인덱스
- τ (tau): Temperature parameter (보통 0.07)
→ 유사도의 차이를 더 극단적으로 만들어줌
핵심:
- 맞는 쌍(i=j)의 유사도는 높게
- 틀린 쌍(i≠j)의 유사도는 낮게
- 이를 동시에 최적화하는 것이 CLIP의 학습
2.4 왜 이 방식이 효과적인가?
기존의 supervised learning과의 비교:
기존 방식:
- 라벨 필요: "이 사진은 고양이" (정해진 카테고리만)
- 새로운 종류 추가: 모델 재학습 필요
- 데이터: ImageNet (1000개 카테고리만)
CLIP:
- 라벨 불필요: 이미지-텍스트 쌍만 있으면 됨
- 새로운 종류 추가: 텍스트 설명만 추가 (즉시 작동)
- 데이터: 인터넷 전체 (400M 이미지-텍스트 쌍)
→ 훨씬 더 다양한 개념 학습 가능
3. CLIP의 강점: Zero-Shot Learning
3.1 개념
CLIP의 가장 혁신적인 특징은 **제로샷 학습(Zero-Shot Learning)**입니다.
"제로샷"이란?
= 모델이 본 적 없는 데이터도 분류할 수 있음
= 학습 없이(Zero) 새로운 작업(Shot)을 바로 수행
3.2 동작 원리
예시: 이미지 분류
# 모델 준비
model = CLIP("ViT-B/32") # 400M 데이터로 사전학습됨
# 이미지 로드
image = load_image("cat.jpg")
# 분류할 카테고리 (모델은 이것들을 본 적 없음!)
categories = [
"a photo of a cat",
"a photo of a dog",
"a photo of a bird"
]
# 유사도 계산
similarities = model.compute_similarity(image, categories)
# [0.95, 0.02, 0.01]
# 결과
predicted_class = categories[argmax(similarities)]
# "a photo of a cat" → 확률 95%
이게 가능한 이유:
CLIP은 "고양이", "개", "새" 따로 배우지 않았습니다.
대신 400M 이미지-텍스트 쌍으로부터:
- "동물"의 시각적 특징들
- "털", "귀", "발" 같은 개념들
- "고양이 같은" vs "개 같은" 시각적 차이들
을 모두 학습했습니다.
따라서 새로운 카테고리도 이 지식을 조합해 이해할 수 있습니다.
3.3 Zero-Shot vs Traditional 비교
┌─────────────────────────────────────────────────────────────┐
│ Zero-Shot Learning Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ Traditional (Fine-tuning 필요): │
│ 고양이 100장 + 개 100장 → 모델 재학습 → 분류 │
│ (시간: 1시간, 데이터: 200장 필요) │
│ │
│ CLIP Zero-Shot (즉시): │
│ "a photo of a cat" 텍스트만 준비 → 즉시 분류 │
│ (시간: 1초, 데이터: 0장 필요) │
│ │
└─────────────────────────────────────────────────────────────┘
4. 실무 응용: 3가지 주요 사례
4.1 이미지 검색 (Visual Search)
문제: 사용자가 찍은 사진과 비슷한 상품을 추천해야 한다.
전통적 방식 (어렵고 비쌈):
User Photo → 특징 추출 → Database의 모든 사진과 비교
문제: 어떤 특징을 추출할지 모호함
(색상? 모양? 질감? 모두?)
CLIP 기반 방식:
Database의 모든 상품 사진 → CLIP Image Encoder → 벡터화
(한 번만 처리)
User Photo → CLIP Image Encoder → 벡터화
↓
벡터들 간 거리 계산 (매우 빠름)
↓
상위 K개의 비슷한 상품 추천
장점:
- 매우 빠름 (벡터 간 유사도는 O(n))
- 의미적 유사성 학습됨 (스타일, 패턴 등)
- 텍스트 검색도 가능 (“청색 운동화” → 연관된 사진)
산업 적용:
- 핀터레스트: 핀의 이미지로 비슷한 핀 찾기
- 아마존: “이 신발과 비슷한 상품”
- 구글 렌즈: 사진을 촬영해서 구매 링크 찾기
4.2 이미지 캡셔닝 (Image Captioning)
문제: 사진에 자동으로 설명 텍스트를 추가해야 한다.
전통적 방식 (CNN + RNN):
Image → CNN Feature Extraction → RNN Text Generation
"고양이가 의자에 앉아있다"
문제: 고정된 크기의 텍스트만 생성 가능
자연스러운 문장을 만들기 어려움
CLIP + LLM 기반 (최신):
Image → CLIP Image Encoder → 이미지 벡터
↓
Large Language Model (GPT, LLaMA)
↓
"부드러운 회색 털을 가진 고양이가
나무로 만든 갈색 의자에 편안하게 앉아있으며,
창문 너머로 햇빛이 들어오고 있다."
장점:
- CLIP이 이미지 이해 → LLM이 자연스러운 문장 생성
- 훨씬 더 디테일하고 정확한 설명
- 이미지의 맥락을 잘 이해함
코드 예시:
from transformers import CLIPVisionModel, GPT2LMHeadModel
# 이미지를 벡터로
image_features = clip_model.vision_model(image)
# LLM이 이미지 벡터를 입력받아 텍스트 생성
caption = gpt2_model.generate(
inputs=image_features,
max_length=50,
temperature=0.7
)
4.3 Visual Question Answering (VQA)
문제: 이미지와 질문이 주어졌을 때, 질문에 답변해야 한다.
예시:
이미지: 식당 사진
질문: "이 식당의 분위기는 어떤가요?"
기대 답변: "따뜻하고 현대적인 분위기의 고급 식당입니다"
이미지: 도로 신호등 사진
질문: "신호등이 무슨 색인가요?"
기대 답변: "빨간색입니다"
CLIP 기반 VQA 파이프라인:
Image → CLIP Image Encoder → 이미지 벡터
↓
Question → CLIP Text Encoder → 질문 벡터
↓
두 벡터를 LLM에 입력
↓
답변 생성 (Fine-tuned LLM 사용)
실무 사례:
- 의료: “이 CT 스캔에서 종양이 보이는가?” → “네, T4 위치에서”
- 자율주행: “앞의 신호등은 무슨 색인가?” → “빨간색”
- 로봇: “파란 도구는 어디에 있는가?” → “선반의 왼쪽 상단”
- 접근성: 시각장애인을 위한 이미지 설명
5. Fine-Tuning: CLIP 커스터마이징
5.1 왜 Fine-Tuning이 필요한가?
CLIP은 일반적인 인터넷 데이터로 학습되었으므로, 특정 도메인에서는 성능이 낮을 수 있습니다.
문제 사례:
CLIP (General):
"병충해 사진" → "곤충" (너무 일반적)
Domain-Specific CLIP (Fine-tuned):
"병충해 사진" → "총채벌레" (정확함)
5.2 Fine-Tuning 전략
데이터 양에 따라 다른 전략을 사용합니다.
데이터 많음 (100K+):
Strategy: 모든 레이어 재학습
Learning Rate: 1e-4 (낮음)
Epochs: 10~20
GPU: V100 이상, 8시간 이상 필요
코드:
for param in model.parameters():
param.requires_grad = True # 모든 파라미터 학습
optimizer = Adam(model.parameters(), lr=1e-4)
데이터 중간 (1K~100K):
Strategy: Text Encoder + Image Encoder만 재학습
Learning Rate: 1e-3
Epochs: 5~10
GPU: T4 (Google Colab), 30분~2시간
코드:
# Vision Transformer의 마지막 블록만
for param in model.vision_model.transformer.resblocks[-4:].parameters():
param.requires_grad = True
optimizer = Adam(trainable_params, lr=1e-3)
데이터 적음 (<1K):
Strategy: Linear Head만 (또는 가볍게 Fine-tune)
Learning Rate: 1e-2 (높음)
Epochs: 3~5
GPU: CPU 가능, 수분
코드:
# 인코더는 고정, 새로운 분류 헤드만 학습
for param in model.parameters():
param.requires_grad = False
# 새로운 헤드만 추가
new_head = Linear(512, num_classes)
optimizer = Adam(new_head.parameters(), lr=1e-2)
5.3 Fine-Tuning 코드 예시
import torch
import clip
from torch.utils.data import DataLoader
from torch.optim import Adam
# 1. 모델 로드
device = "cuda"
model, preprocess = clip.load("ViT-B/32", device=device)
# 2. 데이터 로더 준비
# (이미지-텍스트 쌍으로 구성된 커스텀 데이터셋)
train_loader = DataLoader(
CustomDataset(images, texts),
batch_size=32,
shuffle=True
)
# 3. 학습 가능한 파라미터 설정
for param in model.parameters():
param.requires_grad = False
for param in model.transformer.resblocks[-4:].parameters():
param.requires_grad = True
# 4. 옵티마이저
optimizer = Adam(
[p for p in model.parameters() if p.requires_grad],
lr=1e-3
)
# 5. 학습 루프
for epoch in range(5):
for batch_idx, (images, texts) in enumerate(train_loader):
images = images.to(device)
# 인코딩
image_features = model.encode_image(images)
text_features = model.encode_text(texts)
# 정규화 (CLIP의 특징)
image_features /= image_features.norm(dim=-1, keepdim=True)
text_features /= text_features.norm(dim=-1, keepdim=True)
# 유사도 행렬 계산
similarity = image_features @ text_features.t()
# 손실 함수 (Contrastive Loss)
loss = contrastive_loss(similarity)
# 역전파
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch_idx % 100 == 0:
print(f"Epoch {epoch}, Batch {batch_idx}, Loss: {loss:.4f}")
# 6. 모델 저장
torch.save(model.state_dict(), "clip_finetuned.pt")
6. 최신 VLM 트렌드 (2024~2025)
6.1 CLIP 이후의 진화
CLIP (2021) 이후, VLM은 급속도로 발전했습니다.
Timeline:
2021 CLIP
↓ (OpenAI의 기초 확립)
2023 LLaVA (CLIP + LLaMA)
↓ (대형 언어모델과의 결합)
2024 GPT-4V, Gemini Pro Vision
↓ (멀티모달 이해 고도화)
2025 Multimodal Agents
↓ (VLM이 행동까지 가능)
6.2 주요 모델들
LLaVA (Large Language and Vision Assistant)
구조: CLIP Image Encoder + LLaMA Language Model
특징: 이미지 이해 + 자연스러운 대화 가능
용도: Visual Chatbot ("이 사진에 대해 물어봐도 돼?")
성능: CLIP보다 높은 이해도, 하지만 느림
PaliGemma (Google’s Efficient VLM)
구조: 경량 비전 모델 + 언어 모델
특징: 빠르고 가벼움 (모바일 지원)
용도: 엣지 디바이스에서 실행
성능: 대형 모델보다 낮지만 빠름
GPT-4V / Claude Vision
구조: Proprietary (상세 공개 안 됨)
특징: 매우 높은 이해도
용도: 고도의 이미지 분석 필요한 작업
성능: 현재 가장 우수
비용: 높음 (API 기반)
6.3 발전 방향
1. 더 큰 모델
CLIP: 400M 데이터
→ 현재 모델들: 1B~10B 데이터 학습
→ 앞으로: 100B 데이터 규모
2. 멀티태스킹
CLIP: 이미지 분류만
→ 현재: 분류 + 검색 + 캡셔닝 동시
→ 앞으로: + 객체 감지 + 세그멘테이션
3. 비디오 멀티모달
CLIP: 정적 이미지
→ 현재: 비디오도 이해 시작
→ 앞으로: 비디오 + 오디오 + 텍스트
4. 행동 가능한 에이전트
현재: "이 사진을 설명해줘" (말만 함)
앞으로: "그 물건을 집어줘" (로봇이 실제로 행동)
7. 제한점과 윤리적 고려사항
7.1 기술적 제한점
1. 편향 (Bias)
문제: CLIP은 인터넷 데이터로 학습됨
→ 특정 문화/인종/성별에 편향됨
예시:
- "의사" 검색 → 백인 남성 사진 많음
- "간호사" 검색 → 여성 사진 많음
해결책:
- 데이터셋 균형 맞추기
- Fine-tuning 시 다양한 데이터 사용
- 결과 검증 및 모니터링
2. 미세한 이해의 어려움
쉬움:
- "고양이" vs "개" (명확한 차이)
어려움:
- "3명 vs 4명" (정확한 수 세기)
- "물체 A가 B 위에 있는가?" (공간 관계)
- "감정 표현" (얼굴의 미세한 표정)
3. 높은 계산 비용
CLIP 추론 비용:
- 이미지 + 텍스트 동시 처리
- 단일 모델보다 2배 느릴 수 있음
- 대규모 시스템에서는 비용 문제
7.2 윤리적 고려사항
개인정보 보호:
이미지 검색 시스템:
- 사용자의 사진 데이터베이스화
- 개인정보 보호 필수
해결:
- On-device processing (스마트폰에서 처리)
- 데이터 암호화
- 명확한 동의 절차
저작권:
CLIP의 학습 데이터:
- 인터넷의 수십억 이미지 사용
- 저작권 논쟁 발생 가능
해결:
- 라이선스 확인 필수
- 오픈소스 데이터셋 우선 사용
8. 실제 구현: Python 코드
8.1 기본 사용
import torch
import clip
from PIL import Image
# 1. 장비 설정
device = "cuda" if torch.cuda.is_available() else "cpu"
# 2. 모델 로드 (자동 다운로드)
model, preprocess = clip.load("ViT-B/32", device=device)
# 3. 이미지 준비
image = preprocess(Image.open("example.jpg")).unsqueeze(0).to(device)
# 4. 카테고리 정의
labels = [
"a cat",
"a dog",
"a bird",
"a car",
"a person"
]
# 5. 텍스트 토큰화
text_inputs = clip.tokenize(labels).to(device)
# 6. 추론
with torch.no_grad():
image_features = model.encode_image(image)
text_features = model.encode_text(text_inputs)
# 정규화
image_features /= image_features.norm(dim=-1, keepdim=True)
text_features /= text_features.norm(dim=-1, keepdim=True)
# 유사도 계산
similarity = (100.0 * image_features @ text_features.T).softmax(dim=-1)
# 7. 결과
print(f"Image classification results:")
for label, prob in zip(labels, similarity[0].cpu().numpy()):
print(f" {label}: {prob*100:.2f}%")
# 출력 예시:
# Image classification results:
# a cat: 94.32%
# a dog: 3.21%
# a bird: 1.45%
# a car: 0.68%
# a person: 0.34%
8.2 배치 처리 (여러 이미지)
from torch.utils.data import DataLoader
# 이미지 배치
image_batch = torch.stack([
preprocess(Image.open(f"image_{i}.jpg"))
for i in range(10)
]).to(device)
# 추론
with torch.no_grad():
image_features = model.encode_image(image_batch) # Shape: (10, 512)
text_features = model.encode_text(text_inputs) # Shape: (5, 512)
# 배치 유사도
similarity = (100.0 * image_features @ text_features.T).softmax(dim=-1)
# Shape: (10, 5)
# 각 이미지의 상위 카테고리
for i, scores in enumerate(similarity):
top_category = labels[scores.argmax().item()]
top_score = scores.max().item()
print(f"Image {i}: {top_category} ({top_score*100:.2f}%)")
8.3 이미지 검색
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# 데이터베이스의 모든 이미지를 벡터화 (사전 처리)
database_images = [...] # 이미지 경로 리스트
database_features = []
for img_path in database_images:
img = preprocess(Image.open(img_path)).unsqueeze(0).to(device)
with torch.no_grad():
feat = model.encode_image(img)
feat /= feat.norm(dim=-1, keepdim=True)
database_features.append(feat.cpu().numpy())
database_features = np.vstack(database_features) # Shape: (N, 512)
# 쿼리 이미지
query_image = preprocess(Image.open("query.jpg")).unsqueeze(0).to(device)
with torch.no_grad():
query_feature = model.encode_image(query_image)
query_feature /= query_feature.norm(dim=-1, keepdim=True)
query_feature = query_feature.cpu().numpy()
# 유사도 계산
similarities = cosine_similarity(query_feature, database_features)[0]
top_k_indices = np.argsort(similarities)[-5:][::-1]
# 상위 5개 유사 이미지
print("Top 5 similar images:")
for rank, idx in enumerate(top_k_indices, 1):
print(f"{rank}. {database_images[idx]} (similarity: {similarities[idx]:.4f})")
9. 다음 스텝: 당신이 할 수 있는 것
9.1 기초 실습
Step 1: CLIP 설치 (15분)
$ pip install clip-by-openai
Step 2: 위의 코드 실행 (10분)
- 자신의 사진으로 분류 테스트
- 여러 카테고리 시도
Step 3: 결과 분석 (15분)
- 잘 분류된 이미지
- 실패한 이미지 분석
9.2 중급: Fine-Tuning
Step 1: 도메인 데이터 수집 (데이터 크기에 따라)
- 100장 이상 목표
Step 2: CLIP Fine-tuning 코드 작성
- 위의 5.3 코드 참고
Step 3: 성능 평가
- Fine-tuning 전후 비교
- F1-score, Accuracy 계산
9.3 고급: 애플리케이션 구축
프로젝트 아이디어:
1. 농구 장면 자동 분류
"슈팅" vs "드리블" vs "패스" 자동 인식
2. 의료 이미지 검색
유사한 CT/X-ray 사례 찾기
3. 이커머스 이미지 검색
사진으로 상품 찾기
4. 음악 앨범 아트 검색
특정 스타일의 앨범 찾기
결론
Vision Language Models, 특히 CLIP은 인공지능이 두 가지 모달리티를 동시에 이해하는 능력을 보여줍니다.
핵심 요점:
- Contrastive Learning: 이미지와 텍스트를 같은 공간에 표현
- Zero-Shot Learning: 새로운 작업도 추가 학습 없이 수행 가능
- 멀티모달 이해: 이미지만, 텍스트만이 아닌 둘의 조합 이해
- 실무 응용: 검색, 캡셔닝, VQA, 추천 등 다양한 분야
앞으로 AI는 더욱 멀티모달 중심으로 발전할 것이며, VLM의 이해는 현대 AI 엔지니어의 필수 지식이 됩니다.
참고자료
- CLIP 논문: https://arxiv.org/abs/2103.00020
- OpenAI CLIP GitHub: https://github.com/openai/CLIP
- Hugging Face Transformers: https://huggingface.co/models
- CLIP Fine-tuning Guide: https://github.com/openai/CLIP/blob/main/notebooks/Interacting_with_CLIP.ipynb
질문 및 피드백
이 글에 대한 질문이나 피드백은 댓글로 남겨주세요. 다음 글에서는 CLIP Fine-tuning의 실제 프로젝트 사례를 다루겠습니다.
댓글