오픈 보캐뷸러리 비전 — CLIP(Open-Vocabulary Vision — CLIP)
이미지 인코더(image encoder)와 텍스트 인코더(text encoder)를 함께 학습해 짝이 맞는 (이미지, 캡션) 쌍(pair)이 공유 임베딩 공간(shared space)의 같은 지점에 오도록 만듭니다. 핵심 트릭은 이게 전부입니다.
유형: Build + Use
언어: Python
선수 학습: Phase 4 Lesson 14 (ViT), Phase 4 Lesson 17 (Self-Supervised)
소요 시간: 약 45분
학습 목표
- CLIP의 두 개의 타워 구조(two-tower architecture)와 대조 학습 목적 함수(contrastive training objective)를 설명합니다.
- 사전학습된(pretrained) CLIP 또는 SigLIP을 사용해 과제 특화 학습(task-specific training) 없이 제로샷 분류(zero-shot classification)를 수행합니다.
- 클래스 프롬프트(class prompt)를 인코딩하고 코사인 유사도(cosine similarity)를 계산한 뒤 최댓값 인덱스를 고르는(argmax) 방식으로 제로샷 분류를 처음부터 직접 구현합니다.
- 2026년 기준 CLIP, SigLIP, OpenCLIP, LLaVA/LLaMA-vision 모델이 각각 어떤 용도로 쓰이는지 구분합니다.
문제
전통적인 분류기(classifier)는 닫힌 어휘(closed-vocabulary) 구조입니다. 1,000개 클래스로 학습된 ImageNet 모델은 그 1,000개 레이블(label)만 예측할 수 있고, 새로운 범주(category)를 더하려면 그때마다 라벨이 달린 데이터(labelled data)와 다시 학습한 분류 헤드(retrained head)가 필요합니다.
CLIP(Radford et al., OpenAI 2021)은 웹에서 수집한 4억 개 (이미지, 캡션) 쌍으로 학습하면, 추론(inference) 시에 자연어로 설명된 임의의 범주 집합으로 분류할 수 있다는 사실을 보였습니다. 새 클래스를 추가하고 싶다면 문장을 하나 쓰는 것으로 충분합니다.
이러한 능력, 즉 제로샷 전이(zero-shot transfer) 덕분에 오늘날의 비전 시스템은 거의 항상 CLIP 계열 체크포인트(checkpoint)에서 출발합니다. 객체 탐지(detection: Grounding DINO, OWL-ViT), 영역 분할(segmentation: CLIPSeg, SAM), 검색(retrieval), 콘텐츠 검수(content moderation), 비전-언어 모델(Vision-Language Model; VLM), 텍스트-이미지 생성(text-to-image generation)이 모두 CLIP 방식의 결합 임베딩(joint embedding)을 토대로 만들어집니다.
개념
두 타워(Two towers)
flowchart LR
IMG["이미지"] --> IENC["이미지 인코더<br/>(ViT-L/14)"] --> IEMB["이미지 임베딩<br/>(1024,)"]
TXT["캡션"] --> TENC["텍스트 인코더<br/>(transformer)"] --> TEMB["텍스트 임베딩<br/>(1024,)"]
IEMB --> SIM["코사인 유사도"]
TEMB --> SIM
style IENC fill:#dbeafe,stroke:#2563eb
style TENC fill:#fef3c7,stroke:#d97706
style SIM fill:#dcfce7,stroke:#16a34a
두 인코더는 모두 같은 임베딩 차원(embedding dimension)으로 선형 사영(linear projection)되며 끝납니다. CLIP-B/32는 512차원, CLIP-L/14는 1024차원입니다. 그 뒤 임베딩을 L2 정규화(L2-normalise)하고 코사인 유사도를 계산합니다.
목적 함수(Objective)
N개의 (이미지, 캡션) 쌍으로 이루어진 배치(batch)가 주어지면 NxN 유사도 행렬(similarity matrix)을 만듭니다. 두 인코더는 대각선(diagonal), 즉 짝이 맞는 쌍의 유사도는 높이고, 대각선 바깥(off-diagonal), 즉 짝이 맞지 않는 쌍의 유사도는 낮추도록 학습됩니다.
sim_matrix = image_embeddings @ text_embeddings.T / tau
loss_i2t = cross_entropy(sim_matrix, targets=arange(N))
loss_t2i = cross_entropy(sim_matrix.T, targets=arange(N))
loss = (loss_i2t + loss_t2i) / 2
이미지-텍스트 검색(image-to-text retrieval)과 텍스트-이미지 검색(text-to-image retrieval)이 모두 잘 동작해야 하므로 손실은 대칭(symmetric)으로 구성합니다. tau(temperature; 온도)는 보통 스칼라 파라미터(scalar parameter)로 학습되며 0.07로 초기화합니다.
SigLIP: 더 나은 손실
SigLIP(Zhai et al., 2023)은 소프트맥스(softmax)를 쌍별 시그모이드(per-pair sigmoid)로 바꿨습니다.
loss = mean over pairs of log(1 + exp(-y_ij * sim_ij))
y_ij = +1 if matching, -1 otherwise
쌍별 손실(per-pair loss)은 CLIP이 필요로 하는 배치 수준 정규화(batch-level normalisation)를 제거합니다. 덕분에 SigLIP은 작은 배치 크기(batch size)에서도 더 잘 학습되고, 같은 데이터 규모에서 CLIP과 비슷하거나 더 나은 성능을 냅니다.
제로샷 분류(Zero-shot classification)
학습된 CLIP이 있으면 절차는 다음과 같습니다.
- 각 클래스에 대해 프롬프트를 만듭니다. 예: "a photo of a {class}".
- 모든 클래스 프롬프트를 텍스트 인코더로 인코딩해
(C, d) 형태(shape)의 T를 얻습니다.
- 테스트 이미지를 인코딩해
(1, d) 형태의 I를 얻습니다.
- 유사도 행렬
I @ T.T (형태 (1, C))를 계산합니다.
- 최댓값 인덱스(argmax)에 해당하는 클래스가 예측 클래스(predicted class)입니다.
프롬프트 엔지니어링(prompt engineering)은 중요합니다. OpenAI는 ImageNet용 프롬프트 템플릿(prompt template) 80개를 공개했습니다. 예를 들어 "a photo of a {}", "a blurry photo of a {}", "a sketch of a {}" 같은 식입니다. 클래스마다 이 모든 템플릿 임베딩을 평균하면 top-1 정확도(top-1 accuracy)가 1~3%p 상승합니다.
2026년에 CLIP 계열 모델이 쓰이는 곳
- 제로샷 분류(Zero-shot classification) — 모델을 그대로 가져다 씁니다.
- 이미지 검색(Image retrieval) — 모든 이미지를 한 번 인코딩해 두고, 추론 시점에는 질의(query)만 임베딩합니다.
- 텍스트 조건부 탐지(Text-conditioned detection) — Grounding DINO, OWL-ViT는 탐지기(detector) 위에 CLIP 텍스트 타워를 감싸는 형태로 동작합니다.
- 텍스트 조건부 분할(Text-conditioned segmentation) — CLIPSeg가 대표적이며, SAM도 CLIP을 통해 텍스트 프롬프트 입력을 받습니다.
- 비전-언어 모델(VLM) — LLaVA, Qwen-VL, InternVL은 CLIP 계열 비전 인코더를 LLM에 연결한 모델입니다.
- 텍스트-이미지 생성(Text-to-image generation) — Stable Diffusion, DALL-E 3는 CLIP 텍스트 임베딩을 조건(condition)으로 활용합니다.
공유 임베딩 공간이 한 번 만들어지고 나면, 비전과 언어를 함께 다루는 대부분의 과제는 결국 거리 계산(distance computation) 문제로 환원됩니다.
만들어보기
Step 1: 작은 두 타워 모델(two-tower model)
실제 CLIP은 ViT와 트랜스포머(transformer)를 결합한 구조입니다. 이 레슨에서는 CPU에서도 학습 신호(training signal)가 보일 만큼 작은 모델로 줄이기 위해, 미리 추출해 둔 특징(pre-extracted feature) 위에 얹은 작은 MLP 타워를 사용합니다.
import torch
import torch.nn as nn
import torch.nn.functional as F
class TwoTower(nn.Module):
def __init__(self, img_in=128, txt_in=64, emb=64):
super().__init__()
self.image_proj = nn.Sequential(nn.Linear(img_in, 128), nn.ReLU(), nn.Linear(128, emb))
self.text_proj = nn.Sequential(nn.Linear(txt_in, 128), nn.ReLU(), nn.Linear(128, emb))
self.logit_scale = nn.Parameter(torch.ones([]) * 2.6592)
def forward(self, img_feats, txt_feats):
i = F.normalize(self.image_proj(img_feats), dim=-1)
t = F.normalize(self.text_proj(txt_feats), dim=-1)
return i, t, self.logit_scale.exp()
두 개의 사영 계층(projection), 같은 차원의 출력(shared-dim output), 그리고 학습 대상이 되는 온도(learned temperature)로 이루어져 있고, 실제 CLIP API와 동일한 형태(shape)를 갖습니다.
Step 2: 대조 손실(Contrastive loss)
def clip_loss(image_emb, text_emb, logit_scale):
N = image_emb.size(0)
sim = logit_scale * image_emb @ text_emb.T
targets = torch.arange(N, device=sim.device)
l_i = F.cross_entropy(sim, targets)
l_t = F.cross_entropy(sim.T, targets)
return (l_i + l_t) / 2
손실은 대칭(symmetric)입니다. logit_scale이 클수록 소프트맥스 분포가 더 날카로워지고(sharp) 예측이 더 단호해지지만, 그만큼 학습이 불안정해질 위험(instability risk)도 커집니다.
Step 3: 제로샷 분류기(Zero-shot classifier)
@torch.no_grad()
def zero_shot_classify(model, image_feats, class_text_feats, class_names):
"""
image_feats: (N, img_in)
class_text_feats: (C, txt_in) 클래스별로 평균 낸 임베딩 하나
"""
i = F.normalize(model.image_proj(image_feats), dim=-1)
t = F.normalize(model.text_proj(class_text_feats), dim=-1)
sim = i @ t.T
pred = sim.argmax(dim=-1)
return [class_names[p] for p in pred.tolist()]
단계별로 정확히 한 줄씩입니다. 실제 운영용 CLIP 체크포인트에서도 이와 동일한 제로샷 절차를 그대로 사용합니다.
Step 4: 동작 확인(Sanity check)
torch.manual_seed(0)
model = TwoTower()
img = torch.randn(8, 128)
txt = torch.randn(8, 64)
i, t, scale = model(img, txt)
loss = clip_loss(i, t, scale)
print(f"batch size: {i.size(0)} loss: {loss.item():.3f}")
무작위로 초기화된(random initialised) 모델에서는 손실이 log(N) = log(8) = 2.08에 가까워야 합니다. 아직 아무 구조도 학습되지 않은 상태에서는 대칭 교차 엔트로피(symmetric cross-entropy)의 기댓값이 정확히 그 값이기 때문입니다.
활용하기
2026년 기준 OpenCLIP은 커뮤니티에서 사실상의 기본 선택지(community default)입니다.
import open_clip
import torch
from PIL import Image
model, _, preprocess = open_clip.create_model_and_transforms("ViT-B-32", pretrained="laion2b_s34b_b79k")
tokenizer = open_clip.get_tokenizer("ViT-B-32")
image = preprocess(Image.open("dog.jpg")).unsqueeze(0)
text = tokenizer(["a photo of a dog", "a photo of a cat", "a photo of a car"])
with torch.no_grad():
image_features = model.encode_image(image)
text_features = model.encode_text(text)
image_features = image_features / image_features.norm(dim=-1, keepdim=True)
text_features = text_features / text_features.norm(dim=-1, keepdim=True)
probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)
print(probs)
SigLIP은 더 최근에 나왔고 작은 규모(scale)에서도 더 잘 학습되므로, 새로 시작하는 작업이라면 google/siglip-base-patch16-224를 선호할 수 있습니다. Hugging Face가 둘 다 제공합니다.
산출물 만들기
이 레슨의 최종 산출물은 다음과 같습니다.
outputs/prompt-zero-shot-class-picker.md — 클래스 목록과 도메인(domain)을 받아 제로샷 CLIP용 클래스 템플릿을 설계하는 프롬프트입니다.
outputs/skill-image-text-retriever.md — 임의의 CLIP 체크포인트로 이미지 임베딩 인덱스를 만들고, 텍스트 질의(query-by-text)와 이미지 질의(query-by-image)를 모두 지원하는 스킬입니다.
연습문제
- (쉬움) 사전학습된 OpenCLIP ViT-B/32로 CIFAR-10에서 제로샷 분류를 수행하고, 클래스마다 80개 프롬프트 템플릿으로 이루어진 세트를 사용합니다. top-1 정확도는 대략 85~90% 부근이 나와야 합니다.
- (중간) 같은 CIFAR-10 과제에서 단일 템플릿("a photo of a {}")만 사용한 경우와, 80개 템플릿의 임베딩 평균을 사용한 경우를 비교합니다. 두 경우의 차이를 정량적으로 측정하고, 템플릿 평균이 왜 도움이 되는지 설명합니다.
- (어려움) 제로샷 이미지 검색 인덱스를 만듭니다. CLIP으로 이미지 1,000장을 임베딩하고 FAISS 인덱스를 구축한 뒤, 자연어 설명으로 검색합니다. 직접 작성한 평가용 질의(held-out query) 20개에 대해 recall@5를 측정해 보고합니다.
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| Two-tower | "Dual encoder" | 이미지 인코더와 텍스트 인코더가 각각 따로 존재하면서, 같은 차원의 사영 헤드(shared-dim projection head)로 끝나는 구조입니다. |
| Zero-shot | "No task-specific training" | 추론 시점에 텍스트로 설명한 클래스로 분류하는 방식입니다. 과제별 라벨을 학습에 사용하지 않습니다. |
| Temperature / logit_scale | "tau" | 소프트맥스를 적용하기 전에 유사도 행렬에 곱해 분포를 조절하는, 학습 대상이 되는 스칼라(scalar)입니다. |
| Prompt template | "A photo of a {}" | 클래스 이름을 감싸는 자연어 래퍼(wrapper)이며, 여러 템플릿의 임베딩을 평균하면 제로샷 정확도가 올라갑니다. |
| CLIP | "Image+text model" | OpenAI가 2021년에 발표한 모델이며, 2026년 비전-언어 분야에서 일종의 공용어처럼 쓰입니다. |
| SigLIP | "Sigmoid CLIP" | 소프트맥스를 쌍별 시그모이드로 바꾼 변형이며, 작은 배치에서 더 잘 학습됩니다. |
| OpenCLIP | "Open reproduction" | LAION 데이터로 커뮤니티가 학습한 CLIP 변형이며, 오픈소스 파이프라인의 사실상 표준입니다. |
| VLM | "Vision-language model" | CLIP 계열 인코더와 LLM을 연결해, 이미지에 대한 질문에 답하도록 학습시킨 모델입니다. |
더 읽을거리