'25 아키텍처 스터디 2주차
- 이번에는 CLIP
- LlaVA를 하려고 했는데 CLIP이 언급되길래 이것부터...
적은 양의 라벨이 붙은 고품질 데이터셋
을 사용한다.자연어처리 분야처럼 웹 상에서 수집된 테스트로부터 사전 학습을 하는 방식을 컴퓨터 비전 분야에 적용할 수 있을까?!
Contrasitive learning
레이블링 없이 학습하는 Self-supervised learning 방법론 중 하나. 특정 입력을 embedding network를 통해 임베딩 공간으로 이동시키고, 같은 class라면 임베딩 값의 거리를 최소화(d+), 다른 class라면 임베딩 값의 거리를 최대화(d-)하도록 embedding network를 학습하는 방법.
유사도
를 계산🥨 이미지 텍스트에서 추출한 특징 벡터는 어떤 형태로 저장될까?
- 실수 값으로 이루어진 1차원 배열 형태이며, L2 정규화를 거쳐 임베딩 공간에 표현됨
- 해당 특징 벡터는 이미지의 시각적 특징을 나타냄!
- 실제로 보고 싶당
🥨CLIP이 이미지를 미리 학습하지 않고도 새로운 클래스를 인식할 수 있는 능력은 어디서 비롯된 걸까?
- 대조 학습(Contrastive Learning)
- 방대한 양의 (이미지, 텍스트) 쌍 데이터셋을 사용해 대조 학습을 수행
- => 이 과정에서 모델은 이미지와 텍스트 간의 의미적 연관성을 학습
- 자연어 지도(Natural Language Supervision)
- CLIP은 이미지에 대한 텍스트 설명을 직접적인 지도 신호로 활용
- = 이미지의 객체, 속성, 스타일 등에 대한 텍스트 정보를 이용해 학습!
- "A photo of a [object]"와 같은 프롬프트 템플릿을 사용해 텍스트 설명을 생성하고, 이를 통해 다양한 클래스에 대한 일반화 능력 향상
- 의미적 임베딩 공간(Semantic Embedding Space)
- CLIP은 이미지와 텍스트를 공통의 의미적 임베딩 공간에 표현
- => 비슷한 의미를 가진 이미지와 테스트는 서로 가까이 위치하게 됨
- 새로운 클래스에 대한 텍스트 설명이 주어지면, CLIP은 해당 텍스트를 임베딩 공간에 투용하고, 가장 가까운 이미지를 찾음
=> 학습 데이터셋에 없는 새로운 클래스에 대해서도 이미지 분류를 수행할 수 있음!!=
비유
: 외국어를 배울 떄 단어와 그림을 함꼐 보면서 단어의 의미를 학습
🥯 학습 데이터셋의 크기가 클 수록 성능이 계속 향상될까?
- YES : 과적합을 방지하고 더 많은 의미의 연관성을 학습할 수 있음
- NO : 하지만 데이터 품질이 낮거나 모델의 용량이 부족하면 성능 향상폭이 줄어들 것!
Image encoder
: ResNet이나 Vision Transformer 사용text encoder
: Transformer 사용(CBOW는 뭐지)I[n, h, w, c]
: 이미지의 미니 배치n
: 배치 크기, h
: 높이, w
: 너비, c
: 채널 수T[n,1]
: 텍스트의 미니 배치n
: 배치 크기, 1
: 시퀀스 길이🥨 배치 vs 미니 배치
배치(Batch)
- 전체 훈련 데이터셋을 한 번의 반복(Iteration)에서 모델에 입력해 학습하는 방식
= 모델이 한 번 업데이트 할 때 전체 데이터를 사용- 장점 : 1) 정확한 Gradient 계산 2) 수렴 안정성
- 단점 : 1) 높은 계산 비용 2) 학습 속도 저하
미니배치(Mini-batch)
- 전체 훈련 데이터셋을 작은 부분집합으로 나누어, 각 미니배치를 사용해 모델을 업데이트하는 방식
- 장점 : 1) 낮은 계산 비용 2) 빠른 학습 속도 3) Regularization : Gradient에 노이즈를 추가해 Regularization 효과를 주어 모델의 일반화 성능을 향상 4) Local Minima 탈출
- 단점 : 1) 불안정한 Gradient 계산 2) 추가적인 하이퍼파라미터 튜닝 필요
W_i[d_i, d_e]
: 이미지를 임베딩하는 학습된 projectiond_i
: 이미지 특징의 차원, d_e
: 임베딩 공간의 차원W_t[d_t, d_e]
: 텍스트를 임베딩하는 학습된 projectiont
: 소프트맥스 함수에서 로짓의 범위를 조절하는 학습 가능한 온도 파라미터#extract feature representations of each modality : 각 modallity에서 특징 representation을 추출
I_f = image_encoder(I) #[n, d_i]
: 이미지 인코더를 통해 이미지 특징 I_f
추출n
: 배치크기, d_t
: 텍스트 특징 차원T_f = text_encoder(T) #[n, d_t]
: 텍스트 인코더를 통해 텍스트 특징 T_f
추출n
: 배치크기, d_t
: 텍스트 특징 차원#joint multimodal embedding [n, d_e] : 이미지와 텍스트를 joint multimodal 임베딩 공간으로 임베딩
I_e : l2_normalize(np.dot(I_f, W_i), axis=1)
: I_f
: 이미지 특징을 W_i
: 가중치 행렬로 투영한 후 L2 정규화 수행해서 이미지 임베딩을 얻음T_e : l2_normalize(np.dot(T_f, W_t), axis=1)
scaled pairwise cosine similarities [n, n] : 이미지와 텍스트 임베딩 간의 pairwise 코사인 유사도를 계산하고 온도 파라미터를 사용해 스케일링
logits = np.dor(I_e, T_e.T) * np.exp(t)
: 이미지 임베딩 I_e
와 텍스트 임베딩 T_e
간의 행렬 곱셈을 통해 코사인 유사도를 계산하고, 온도 파라미터의 지수 함수를 곱해 로짓을 얻기🥨 로짓을 얻는다?
- 로짓(Logit)은 확률 p를 odds(성공 확률 대 실패 확률의 비율)로 변환한 다음, odds에 로그를 취한 값
- 확률을 모델링하기 위해 사용되는 함수로, 0과 1 사이의 확률 값을 실수 전체 범위로 확장시켜 줌
- 로짓 값이 0에 가까울수록 모델은 예측에 대해 불확실함
CLIP에서 로짓을 얻는 이유
- 로짓은 이미지와 텍스트 임베딩 간의 유사도를 나타내는 점수이며, 높을 수록 이미지와 텍스트가 잘 맞는다고 판단
- 온도 파라미터 t를 이용해 로짓의 스케일을 조정함으로써, 모델은 올바른 쌍을 더 잘 식별하고 잘못된 쌍을 더 잘 구별할 수 있음
- 이후 로짓은 Softmax 함수의 입력으로 사용 -> 로짓 값을 확률 분포로 변환하여 각 클래스에 대한 확률을 얻을 수 있음
labels = np.arange(n)
: 0부터 n-1까지 숫자를 담은 배열을 생성loss_i = cross_entropy_loss(logits, labels, axis=0)
cross_entropy_loss
: 교차 엔트로피 손실 함수 계산. 두 확률 분포 간의 차이를 측정하는게 사용. 여기서는 예측된 유사도(logits)와 실제 레이블(labels) 간의 차이를 계산logits
: 이미지와 텍스트 임베딩 간의 유사도 점수 = 얼마나 잘 매칭되는지labels
: 정답 레이블axis=0
: logits의 0번째 축을 기준으로 계산. 즉, 각 이미지에 대한 손실 계산loss_t = cross_entropy_loss(logits, labels, axis=1)
: 위와 동일하지만 텍스트에 대한 손실을 계산loss = (loss_i + loss_t)/2
: 두 손실의 평균실험을 정말 엄청나게 많이 하심
Linear Probe
평가 : 사전 학습된 모델의 인코더 부분(특징 추출기)은 고정시키고, 간단한 선형 분류기를 추가로 학습시켜 성능을 평가하는 방식.🥨 Linear Probing
가장 단순한 분류기(ex. 특징만 보고 사진을 분류하는 알바생)를 붙여서 테스트 했을 때 성능이 얼마나 잘 나오는지 테스트해서 원래 있는 모델(ex. 사진의 특징을 뽑아내는 전문가)이 얼마나 데이터를 잘 이해하고 핵십 특징을 잘 뽑아내는지 평가하는 방법!
pip install git+https://github.com/openai/CLIP.git
일단 clip 모델을 설치해주고
import torch
import clip
from PIL import Image
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device) # preprocess 함수
model은 ViT 모델 가져옴!
preprocess 하는 함수는 ViT에게 특화된 파이프라인!
# 이미지 로드 및 전처리
image = preprocess(Image.open("danbibest.jpg")).unsqueeze(0).to(device)
queries = [
"basketball player", "a tylenol ER", "woman basketball player dribbling", "a runner", "so many basketball players"
]
text = clip.tokenize(queries).to(device) # torch.Size([5, 77])
참고로 내가 쓴 사진은 킹단비
with torch.no_grad():
image_features = model.encode_image(image) # [1. embed_dim] = [1, 512]
text_features = model.encode_text(text) # [5,512] <- [5,77]에서 변환
logits_per_image, logits_per_text = model(image, text)
probs = logits_per_image.softmax(dim=-1).cpu().numpy()
이미지와 텍스트의 특성을 가지고 와준다
# normalized features
# 유사도 계산에 코사인 유사도 사용하기 위해 각 특징 벡터의 크기를 1로 만듦
image_features = image_features / image_features.norm(dim=1, keepdim=True) #shape:[1,1]
text_features = text_features / text_features.norm(dim=1, keepdim=True) #shape:[5,1]
# 유사도 계산 및 스케일링
logit_scale = model.logit_scale.exp()
logits_per_image = logit_scale * image_features @ text_features.t() # (1, 512) @ (512, 5)
logits_per_text = logits_per_image.t() # 텍스트 관점에서의 유사도 [5,1]
이미지 특징 벡터와 텍스트 특징 벡터들을 사용해 이미지와 각 텍스트 설명 간의 유사도를 계산하고 확인하는 부분!
logit_scale=model.logit_scale.exp()
: 모델 내부의 학습 가능한 파라미터인 logit_scale
에 지수 함수 exp()
적용
print(logits_per_image.shape, logits_per_text.shape)
print(logits_per_image) # 원시 유사도 점수
print(logits_per_image.softmax(dim=-1)) #softmax 적용
---
>>> torch.Size([1, 5]) torch.Size([5, 1])
>>> tensor([[26.5781, 16.3438, 30.6094, 20.7812, 19.9844]], device='cuda:0',
dtype=torch.float16, grad_fn=<MmBackward0>)
>>> tensor([[1.7441e-02, 6.5565e-07, 9.8242e-01, 5.2989e-05, 2.3901e-05]],
device='cuda:0', dtype=torch.float16, grad_fn=<SoftmaxBackward0>)
유사도 계산 결과!
# 최종 확률 추출
confidences = logits_per_image.softmax(dim=-1).detach().cpu().numpy().ravel()
for confidence, query in zip(confidences, queries):
print(query, ":", confidence)
basketball player도 점수가 낮네...신기
woman 넣으니까 점수 확 늘어남...신기22..
VisionTransformer(
(conv1): Conv2d(3, 768, kernel_size=(32, 32), stride=(32, 32), bias=False)
(ln_pre): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(transformer): Transformer(
(resblocks): Sequential(
(0): ResidualAttentionBlock(
(attn): MultiheadAttention(
(out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)
)
(ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(mlp): Sequential(
(c_fc): Linear(in_features=768, out_features=3072, bias=True)
(gelu): QuickGELU()
(c_proj): Linear(in_features=3072, out_features=768, bias=True)
)
(ln_2): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
)
(1): ResidualAttentionBlock(
(attn): MultiheadAttention(
(out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)
)
(ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(mlp): Sequential(
(c_fc): Linear(in_features=768, out_features=3072, bias=True)
(gelu): QuickGELU()
...
)
)
(ln_post): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
)
Transformer(
(resblocks): Sequential(
(0): ResidualAttentionBlock(
(attn): MultiheadAttention(
(out_proj): NonDynamicallyQuantizableLinear(in_features=512, out_features=512, bias=True)
)
(ln_1): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
(mlp): Sequential(
(c_fc): Linear(in_features=512, out_features=2048, bias=True)
(gelu): QuickGELU()
(c_proj): Linear(in_features=2048, out_features=512, bias=True)
)
(ln_2): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
)
(1): ResidualAttentionBlock(
(attn): MultiheadAttention(
(out_proj): NonDynamicallyQuantizableLinear(in_features=512, out_features=512, bias=True)
)
(ln_1): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
(mlp): Sequential(
(c_fc): Linear(in_features=512, out_features=2048, bias=True)
(gelu): QuickGELU()
(c_proj): Linear(in_features=2048, out_features=512, bias=True)
)
(ln_2): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
...
(ln_2): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
)
)
)
끝~