오픈 보캐뷸러리 비전 — 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)을 토대로 만들어집니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.CLIP의 대조 손실(contrastive loss)은 대칭(symmetric) 구조입니다(이미지→텍스트 + 텍스트→이미지). 왜 양방향을 모두 학습하나요?

2.CLIP의 제로샷 분류(zero-shot classification)는 어떤 원리로 동작하나요?

0/2 답변 완료

개념

두 타워(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이 있으면 절차는 다음과 같습니다.

  1. 각 클래스에 대해 프롬프트를 만듭니다. 예: "a photo of a {class}".
  2. 모든 클래스 프롬프트를 텍스트 인코더로 인코딩해 (C, d) 형태(shape)의 T를 얻습니다.
  3. 테스트 이미지를 인코딩해 (1, d) 형태의 I를 얻습니다.
  4. 유사도 행렬 I @ T.T (형태 (1, C))를 계산합니다.
  5. 최댓값 인덱스(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)  # ln(1/0.07)

    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)를 모두 지원하는 스킬입니다.

연습문제

  1. (쉬움) 사전학습된 OpenCLIP ViT-B/32로 CIFAR-10에서 제로샷 분류를 수행하고, 클래스마다 80개 프롬프트 템플릿으로 이루어진 세트를 사용합니다. top-1 정확도는 대략 85~90% 부근이 나와야 합니다.
  2. (중간) 같은 CIFAR-10 과제에서 단일 템플릿("a photo of a {}")만 사용한 경우와, 80개 템플릿의 임베딩 평균을 사용한 경우를 비교합니다. 두 경우의 차이를 정량적으로 측정하고, 템플릿 평균이 왜 도움이 되는지 설명합니다.
  3. (어려움) 제로샷 이미지 검색 인덱스를 만듭니다. 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을 연결해, 이미지에 대한 질문에 답하도록 학습시킨 모델입니다.

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 2개

skill-image-text-retriever

Build an image embedding index with any CLIP checkpoint; support query-by-text and query-by-image

Skill
prompt-zero-shot-class-picker

Design prompt templates for zero-shot CLIP given a list of classes and a domain

Prompt

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.SigLIP은 CLIP의 소프트맥스(softmax)를 쌍별 시그모이드 손실(per-pair sigmoid loss)로 바꿉니다. 어떤 이점이 있나요?

2.CLIP ViT-B/32로 CIFAR-10 제로샷 top-1 88%를 얻었고, 클래스마다 80개 프롬프트 템플릿을 평균하니 90%가 되었습니다. 템플릿 평균이 도움이 되는 이유는 무엇인가요?

3.최근의 VLM(LLaVA, Qwen-VL, InternVL)이 지도학습된 ImageNet ResNet 대신 CLIP 계열 비전 인코더를 사용하는 이유는 무엇인가요?

0/3 답변 완료