CLIP은 2021년에 언어-이미지 쌍을 사전학습하는 방법을 제시한 연구입니다. (이미지, 이미지에 대한 텍스트)를 쌍으로 대조적학습(Contrastive learning)을 사전학습하여, 자연어처리에서와의 접근방식과 유사하게 비전문제에서도 사전학습결과후, 각 테스크에서 좋은 성능을 낼 수 있음을 보여줍니다.

 

Preliminary


  • Zero shot learning(제로샷 러닝): 모델이 훈련 데이터에서 직접 학습하지 않은 클래스 또는 레이블을 인식하고 분류할 수 있게 하는 학습방법

 

 

Introduction


언어모델에서의 MLM(Masked language model)같은 특정 테스트와 무관한 학습을 하는 것으로 자연어쪽에서는 성공적인 연구성과를 보여주었습니다. 흔히, 일반화된 아키텍처(General model)에서 다운스트림으로 제로샷(Zero shot)으로 전이학습하기가 용이해져서, 아키텍처에 예측할 해더(분류기레이어)등을 추가할필요가 없어져왔습니다. 그 결과로 GPT-3같이 여러 목적에도 다양하게 사용할 수 있는 모델이 데이터가 부족해도 사용할 수 있게 되었습니다. 이런 학습방법은 웹에서 수집한 데이터로 사전학습을 하니, 라벨링을 직접하지 않습니다. 그럼에도, 크라우드소싱으로 라벨링한 것보다 좋은 성능을 보였고, 사전학습을 하는 것이 표준적인 관행이 되었습니다. 하지만, 비전문제에서도 이런 접근방법이 가능한지에 대해서는 아직 연구된바 없었습니다. 특히, 비전문제에서는 보통 클라우드 라벨링으로 모은 ImageNet으로 사전학습데이터를 사용하는데, 웹에서 사용한 데이터로 이런 사전학습을 할 수 없을까?가 이 연구의 motivation입니다.

 

여태까지는, 이미지를 표현할 때, 언어를 이용한 표현을 해볼 수 있겠는지와 관련된 연구는 별로 없었습니다. 실제로, 이전의 연구성과들에서는성능이 거의 안나왔었습니다 (11.5% 정확도, 이 때, SOTA가 88.4%). 이후, 인스타그램 이미지에서 ImageNet 관련 이미지 해시테그를 예측 하는 사전학습을 이용하자, SOTA보다 더 좋은 결과들을 보였습니다 (역시, 데이터가 많아야..). 이후에도 비슷한 접근방식으로 JFT-300M dataset을 만들어 사전학습하여 성능향상을 보였던 논문도 있었습니다. 하지만, 이런 방식도 결국 클레스를 N개로 정하다보니, "zero-shot"의 학습성능을 제한한다는 것에 한계를 제안합니다(결국 softmax하여 분류기를 추가하기에 일반화 목적성능의 달성이 어려움). 생각해보면, 자연어는 이미지보다 더 많은 클레스의 표현이 가능하기에, 이런 지도학습에도 더 도움을 줄 수 있을 것 같습니다. 

 

CLIP 방법론: 이미지 표현 < 자연어표현이 더 풍부, 자연어기반 지도학습으로 zero-shot구현


CLIP의 핵심아이디어는 이미지의 인식방법이 자연어로부터 배울 수 있다는 것입니다. 이를 위해 아래의 단계대로 실험을 진행했습니다.

  1. 대규모 데이터셋 구축(creating a sufficient large dataset)
  2. CLIP(Pre-training method) 구성(selecting an efficient pretraining method)
  3. 모델 크기 선택(Choosing and scaling a model)
  4. 학습(Training)

 

 

1. 대규모 데이터셋구축

새로운 아이디어는 아니고, 이전에는 N-gram이나 크라우드소싱데이터를 이용했다면, CLIP에서는 대규모데이터를 학습하려 노력했습니다. 자연어로 지도가 됨을 보여주기 위해서, 대규모데이터가 필요했습니다. 

특히 1) 직접 클레스에 라벨링이 아니라 이미지에 대한 설명을 학습해볼 수도 있는 것이구요(이미지의 특징의 학습이 자연어로 지도됨). 2) 라벨링이 되어있지 않아도, 온라인상에서 일반적인 더 큰 이미지를 구축하려고 노력했습니다. ImageNet이나 MS-COCO 데이터셋 10만장밖에안되고, YFCC100M 데이터도 100million이지만 데이터 퀄리티가 좋지 않아(title, 설명등이 미포함), 전처리하고나면 ImageNet정도 사이즈밖에안되서 적다고합니다. 실제로 OpenAI에서는 4억장의 이미지-텍스트 쌍을 인터넷에서 구했다고 합니다. 이미지를 얻기위해서 5만개의 쿼리를 사용했고, 클레스 벨런스때문에, 쿼리당 20,000개의 이미지-텍스트 쌍을 넘지않도록했다고합니다. 이 데이터셋을 WebImageText(WIT)라 명명했습니다.

 

 

2. CLIP(Pre-training method) 

CLIP은 효율적인 사전학습을 찾으려고 노력한 결과물입니다. 연구 초기에 사전학습방식을 결정할때, 아래와 같은 3가지의 방법을 적용해보았습니다. 3가지 목적으로 각각 이미지는 ViT(Vision Transformer)로 인코딩하고, 텍스트는 Transformer로 인코딩한다음에 이미지의 캡션을 예측하도록 Joint traning을 진행했습니다. 각 3가지는 Transformer Language model은 언어 생성, Bag of words은 순서가 없는, Bag of words contrasative (CLIP)은 BOW에 대조적학습까지 적용한 방법입니다. 같은 파라미터라면, 성능은 CLIP이 훨씬 효율이 높아 CLIP으로 사전학습을 하는 것을 목표로 했습니다.

 

CLIP은 N개의 배치로 튜플인 (이미지, 텍스트)의 페어를 입력을 받습니다. 만일 N개가 배치로 들어온다면, N x N의 메트릭스를 만들 수 있고, N개의 이미지와 N개의 텍스트 각각 하나씩만 실제 연관된 이미지-텍스트여서 N개의 Positive로 정의하고, $N^{2}-N$의 negative 페어를 만들어냅니다. 이를 대조적학습으로 학습하는 방법입니다 (아래의 Figure 3). 

Figure 3. Numpy-like한 슈도코드

3. 모델 크기 선택(Choosing and scaling a model)

이미지 인코딩을 위한 아키텍처를 고를때는 처음에는 ResNet-50을 고르고 일부 변형했다고 합니다. 이는 이미 일반적으로 성공적인 성능을 보여왔기에 첫 선택이었다고 합니다. 그리고, 두 번째로는 ViT을 이용했다고합니다. 텍스트 인코딩은 Transformer을 이용했고, 65M Parameter + 12 Layer + 8 attention head + BPE 인코딩으로 얻어진 voca size은 49,152사이즈입니다.

4. 학습(Training)

학습은 ResNet 시리즈 5개(ResNet-50, 101 Efficientnet 아류 3개)랑 Vision transformer (B/32, B/16, L/14)을 학습했습니다. 그외엔 하이퍼파라미터는 Grid search/랜덤서치로 1에폭에 했구요.

 

결과


본문을 참고하시면 좋을 것 같습니다. 아래와 같은 내용이 담겨있습니다.

  1. Zero-shot Transfer 하는 경우 이미지분류에서도 압도적인 성능을 낼 수 있음
  2. 프롬프트엔지니어링하면, 기존대비 5%p이상의 성능을 낼 수 있음
  3. CLIP이 잘되는 /잘안되는 데이터셋마다 차이를 보이긴 함
  4. Linear probe(인코더+분류기 레이어 부착후 재학습)한 경우도 매우 좋음

 

사용예시


CLIP official github에 제시된 것처럼 clone 후에, jupyter notebook에서 모델을 다운로드 받아보겠습니다.

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)

 

CLIP.png은 아래와 같은 다이어그램입니다

위와 같은 다이어그램을 preprocessing (resize->CenterCrop -> RGB -> 텐서변환->정규화)를 거쳐 전처리하고 CLIP에 포워딩해서 백터를 얻어보겠습니다. 또한, CLIP에 넣을 3개의 배치로 텍스트를 넣어보겠습니다. 텍스트는 토큰화되어서 (3, 77)의 77개의 시퀀싱을 갖는 백터며, 각 순서에 맞춰 인덱싱으로 원소가 구성되어있습니다.

image = preprocess(Image.open("CLIP.png")).unsqueeze(0).to(device)
text = clip.tokenize(["a diagram", "a dog", "a cat"]).to(device) # torch.Size([3, 77])


------
print(model.visual) # Vision transformer은 768차원 반환하지만 추가로, 512차원으로 embedding해주는 레이어가 부착되어있습니다.
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(
  ...
  (ln_post): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
 )
 
-----
print(model.transformer) # transformer은 512차원의 벡터를 반환합니다.
Transformer(
  (resblocks): Sequential(
  ...
  (ln_2): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
    )
  )
)

 

CLIP논문에 맞춰, 이미지 표현(image_features), 텍스트 피쳐(text_features)을 구하고, 정규화 후 코사인유사도를 계산해보겠습니다. 유사도 계산후, 유사도(logit)으로 3차원의 벡터가 얻어졌고, "CLIP.png"와 "a diagram", "a dog", "a cat"의 각각의 쌍(pairwise)의 유사도가 [25.5528, 20.0899, 19.7495]로 얻어졌습니다. softmax로 구하면, 0.9928로 0번째 텍스트와 가장 유사도가 높음을 알 수 있습니다. CLIP은 이와 같이 이미지를 가장 잘 설명할 수 있는 text와의 유사도를 구하는 방식입니다.

# normalized features
image_features = image_features / image_features.norm(dim=1, keepdim=True)
text_features = text_features / text_features.norm(dim=1, keepdim=True)

# cosine similarity as logits
logit_scale = model.logit_scale.exp()
logits_per_image = logit_scale * image_features @ text_features.t()  # (1, 512) @ (512, 3)
logits_per_text = logits_per_image.t()

print(logits_per_image.shape, logits_per_text.shape)
print(logits_per_image)
print(logits_per_image.softmax(dim=-1))
# torch.Size([1, 3]) torch.Size([3, 1])
# tensor([[25.5528, 20.0899, 19.7495]], grad_fn=<MmBackward0>)
# tensor([[0.9928, 0.0042, 0.0030]], grad_fn=<SoftmaxBackward0>)

 

그외 사례에서도 아래와같이 적용해보면, 약학정보원에서 얻은 약(타이레놀)과 가장 설명이 유사한 텍스트를 잘 찾아줌을 확인할 수 있습니다.

반응형

 

요약


CNN을 이용한 영상분류에서는 사후해석으로 CAM, Grad-CAM등이 사용됩니다. 이 논문은 CAM방법론들에서 사용하는 Gradient을 이용하지 않고, Activation map에서의 가중치를 직접 획득하는 방식으로, 점수를 직접 산합니다.

 

Introduction: CAM-based explantation에서 사용하는 gradient을 해석에 충분한 방법이 못된다.


Score-CAM을 이해하기위해선 CAM부터 이해해야합니다. CAM은 GAP(Global average pooling)레이어가 꼭 포함되어야하는 방법론입니다 (Figure 1). Activation map(l-1번째 레이어) 이후에 GAP(l번쨰 레이어)가 들어가고 GAP 이후에 Fully connected layer(l+1번째 레이어)가 포함되어있습니다.

Figure 1. CAM을 사용하기위한 GAP가 포함된 네트워크 구성

이때, CAM으로 1번 클레스의 가중치를 구하려면, Activation map(L-1번째 레이어)의 각 채널(k)에서 얻어진 feature map에 GAP 이후의 연결되는 가중치($a_{k}^{c=1}$)에 대해서 선형결합후에 ReLU만 씌우면 됩니다. 그래서, CAM은 아래와 같이 계산합니다. 

$L^{c}_{CAM}=ReLU(\sum_{k}a_{k}^{c}A^{k}_{l-1})$

CAM은 각 activation map($A_{l}^{k}$)가 각각 채널별로 다른 공간정보를 담고 있다고 가정하여, 각 채널이 클레스에 미치는 가중치($a_{k}^{c}$)을 다시 선형결합해서 곱해줍니다. 하지만, CAM의 문제는 1) GAP layer가 없는 네트워크가 없는 경우에 사용할 수 없고, 2) fully connected layer가 없거나, 2개 이상인 경우에는 가중치($a_{k}^{c}$)을 구할 수 없기에 연산이 불가능합니다.

 

 

이후, 위의 문제를 개선한 Grad-CAM(2017)이 연구되었습니다. Grad-CAM가중치($a_{k}^{c}$)을 얻기위해서, gradient을 이용하여 아래와 같이 계산합니다. 차이점은 Activation map이($A^{k}_{l-1}$)에서 Activation map($A^{k}_{l-1}$)로 변경되었다는 것과 가중치($a_{k}^{c}$)의 정의가 아래와 같이 변경되었다는 것 입니다. 이 때문에 1) 네트워크의 global average pooling이 꼭 포함될 필요가 없으며, 2) fulley connected layer가 여럿이 포함되어도 됩니다.

$L^{c}_{Grad-CAM}=ReLU(\sum_{k} a_{k}^{c}A^{k}_{l} )$

where $a_{k}^{c}=GP(\frac{\partial Y^{c}}{\partial A_{l}^{k}})$ (GP은 global pooling operation)

Grad-CAM이나 Grad-CAM++이나 가중치($a_{k}^{c}$)을 gradient을 이용해서 정의하여 구합니다. Y(confidence)에 대한 예측을 gradient로 각 채널로 구하는 것이니 fully connected가 몇개여도 가능합니다. 각 채널별로의 중요도를 미분을 이용한 가중치($a_{k}^{c}$)로 구하는 것이죠. 하지만, 이 Grad-CAM도 "채널별 중요도"에 문제가 있습니다.

 

 


Gradient을 이용하는 경우의 문제점은 중요도를 정확히 계산할 수 없다는 것입니다. 이 이유를 2가지로 삼습니다: Saturation문제False confidence문제

1) Saturation문제는 gradient가 시각적으로 확인할 때, 중요점이 깔끔하게 떨어지지 앟는다는 문제입니다. 딥러닝 네트워크의 gradient가 꽤 노이즈(0이 아닌 소수점이 꽤 많은...)가 있기에, sigmoid function등을 쓰더라도 잔류하는 gradient가 많습니다. 그렇기에 Gradient based 방법론을 쓰면 지저분히 heatmap이 남습니다. sigmoid function 의 입력이 -2라고 하더라도 남고, -5라도 하더라도 뭔가 남고 -10이라고 하더라도 뭔가 소수점이 계속 남습니다.

 

Sigmoid function
Figure 4. Gradient 방법론이 꽤 노이지한 이유를 Saturation문제라고 하며, 근거로 든 figure

 

2) False confidence 문제는 Grad-CAM 자체가 각채널별 Activation map($A^{k}_{l}$)과 가중치($a_{k}^{c}$)의 선형결합이기에, 생기는 문제입니다. Grad-CAM에서는 activaton map의 가중치인 각 i번쨰, j번째 채널에 대해서, 두 가중치$a_{i}^{c}$ > $a_{j}^{c}$가 있는 상황이면 i번째 activation map ($A^{i}_{l}$) 이 더 중요하고, 더 예측에 많이 기여했을거라고 생각합니다. 하지만, 반례가 많이 보인것이 grad-cam의 모습이었습니다. 아래의 그림에서, (2)번의 그림은 가중치($a_{k}^{c}$)가 제일 컷음에도 실제 confidence값 기여에는  0.003으로 낮게 기여한 경우였습니다. 아마도 이 이유를 GP와 gradient vanishing문제떄문이라고 생각합니다. 이 때문에, gradient을 이용하지 않는 해석방법론을 연구한 듯 합니다.

 

Score-CAM: 모델 컨피던스를 증가시키는 것이 중요도 (Channel-wise increase of confidence, CIC)


모델 컨피던스는 인공지능모델이 예측에 어느정도 강한 예측을 보이는 정도이며 통상 [0, 1]로 정규화해서 얻어지는 값입니다(흔히, 확률이라고 부르기도 하는데 정확히는 확률은 아닙니다). 아무튼 Score-CAM은 이 모델의 반환값(model confidence)가 얼마만큼 증가하는지를 확인하고자하는 것 입니다. Score-CAM은 Increase of confidence의 정의부터 출발합니다.

Increase of confidence의 계산은 베이스라인 이미지에 대비해서 Activation이 활성화된 영역만을 남기고, 나머지부분을 마스킹 했을 때, 예측력이 얼마만큼 confidence을 올리는지 계산하는 방식입니다. 일반적인 CNN이 아니라, 벡터를 넣어 스칼라값을 뱉는 인공지능이라고 생각해서 일반화된 Increase of confidence은 아래와 같이 계산합니다.

$c_{i}=f(X_{b} \circ H_{i}) - f(X_{b}) $

  • $ H_{i} $: 이미지랑 같은 크기의 벡터이고, unmasking 용으로 사용됩니다.

*Hadamard Product: 요소별

 

위의 개념을 CNN에 도입해서 Channel-wise Increase of Confidence (CIC)을 계산합니다.


$C(A_{l}^{k})=f(X \circ H_{l}^{k}) - f(X_{b})$


  • $A_{l}^{k}$:l번째 convolution의 k채널의 Activation map을 의미합니다.
  • $X_{b}$: 베이스라인 이미지 입니다.
  • Up(): CNN이 통과되면서 작아진 사이즈를 다시 원래이미지에 맞춰 업샘플링하는 연산입니다.
  • s(): 매트릭스의 원소 값을 [0, 1]로 정규화하는 함수입니다. min-max scaler로 사용합니다. 실제로 activation을 넣어 $A_{l}^{k})=\frac{A_{l}^{k}-minA_{l}^{k}}{maxA_{l}^{k}-minA_{l}^{k}}$로 사용됩니다. 

Activation이 얼마만큼 기여하는지 정했다면 이제 Score-CAM을 정리할 차례입니다. Score-CAM이라고 (9)번식이 Grad-CAM과 다르지않습니다. 다만 10번의 가중치($a_{k}^{c}$)가 달라진 것이 큽니다.

$L^{c}_{Score-CAM}=ReLU(\sum_{k} a_{k}^{c}A^{k}_{l} )$  (9)

$a_{k}^{c}=C(A_{l}^{k})$ where C() denotes teh CIC score for activation map $ A_{l}^{k} $

 

위의 내용을 모두 정리하여, 아래의 알고리즘과 같이 정리할 수 있습니다.

  1. Activation을 구하기 위해서 X이미지를 딥러닝의 CNN에 forwarding하서 $A_{l}$을 구합니다.
  2.  $A_{l}$개수 만큼 C(채널)을 정의합니다.
  3. 각 C(채널)만큼 Activation map($A_{l}^{k}$)을 업샘플링하여 원본사이즈에 맞춘 M을 구합니다.
  4. activation map을 정규화합니다.  # $M_{l}^{k}=s(M_{l}^{k})$
  5. 정규화한 이미지랑 원본이미지랑 요소별 곱하여 리스트 M에 저장해둡니다. // Figure 3의 Phase 1의 완료
  6. 원본이미지에 Activation을 곱한 것을 딥러닝에 다시 태워서 logit 값의 차이를 구합니다. $S^{c}$
  7. 채널별로 얻어진  $S^{c}$ 을 softmax하여 합이 1이 되도록 맞춘 가중치 $a_{k}^{c}$을 구합니다.
  8. Acitvationmap과 선형결합하여 Score-CAM을 구합니다.

 

결과:


첫 번째 결과로, 정성적(Qualitative)로 사례기반으로 ScoreCAM이 다른 gradient방법론보다 노이즈가 적음을 보여줍니다. 아무래도 gradient 방법론들이 saturation problem이 있기 때문이라고 생각합니다.

두 번째 결과로, 하나의 타깃이 아니라 여러 타깃에도 더 객체를 또렷하게 구분하여 히트맵을 보여주었습니다.

그외에도 Deletetion, Insertion을 픽셀값을 중요도순으로 열거하고, Deletion curve에서는 얼마만큼 성능이 빨리 저하되는지, Insertion curve에서는 중요순으로 추가될수록 얼마만큼 빨리 오르는지를 정량적으로 보여주었습니다. X축 비율, Y축은 AUC입니다.

 

응용 사례


의학의 적용분야중, 자폐진단에 안저검사이미지가 얼마만큼 예측할 수 있는지 가능성(Feasbility)을 보여준 논문에서도, 이 연구가 정말 가능하다라는 결과를 보여주기위해 Score-CAM을 보여주었습니다. (A)은 이미지를 지워가면서 AUROC을 보여주었고, B은 Score-CAM결과에서 Score-CAM결과를 삭제해가면서 biomedical domain에 의미가 있는 영역이 분류에 중요한지를 보여주는 이미지입니다(링크).

반응형

 

요약


SimCLR을 이미지 데이터을 더 잘 구별하기위한, 대조적학습 (Constrative learning)을 이용한 사전학습 프레임워크*입니다. SimCLR은 비슷한 같은 데이터 증강(Data augmentation)을 이용하는데, 같은 데이터 소스로 부터 생성된 이미지는 가깝게, 다른 이미지소스로 부터 생성된 이미지는 멀게 학습하는 metric learning 방법입니다. 즉 이미지의 유사성/이질성을 학습하는 방법론입니다. 이 방법론을 사전학습으로 사용하면, 시각적표현을 더 잘학습할 수 있고, 지도학습 등에서의 적은 파라미터로도 더 높은 구별성능을 낼 수 있습니다. 

*프레임워크: 세부적인 방법론만 바꿔가면서 동일한 목적을 달성할 수 있도록하는 큰 틀을 의미합니다.

 

Introduction


- 비전관련 학습은 크게 생성이나, 구분문제(discrimitive approach)로 나뉩니다. 생성도 역시 2가질로 나뉘는데 모델을 학습하는 방법과, 픽셀수준까지 학습하는 방법으로 나뉩니다. 다만, 픽셀수준까지 생성하는것은 매우 비용이 많이듭니다. 한편, 분류문제(discrimitive approach)은 목적함수등을 이용해서 지도학습을 하게되며, 사전학습문제(pretext task)을 이용해서 라벨없는 데이터를 학습하기도합니다. Pretext task을 어떻게 꾸리냐에 따라 다르긴한데, 대부분 사람마다 다른 경험적인(heuristic)한 방법입니다. 그러다보니, 일반화성능에 미치지 못할 수 있습니다. 하지만 최근 들어서는 contrastive learning이 pretext task에 기여할 수 있다는 논문이 주류로 뜹니다.

- 저자들은 Contrastive learning을 할 수 있는 방법론을 하나 제시합니다. 그리고, Constrative learning을 잘하기 위한 주요요소들을 제시합니다.

1. 여러가지 data augmentation 방법을 함께 이용하는 것이  매우 중요함.
2. Augmentation 후에 learnable 비선형변환의 표현이 표현 성능이 더 좋음
3. Contrastive corss entropy을 사용하면 더 좋음
4. 큰 사이즈의 배치사이즈와 긴 훈련시간을 사용하면 좋음

 

Methods


  • Contrastive learning framework - 목적: 같은 데이터에 다른 방법의 Augmentation을 사용하여 비선형매핑을 하더라도 동일함 이미지로 표현됨

구성요소

  • Data augmentation: 한 이미지로부터 각각 다른 데이터증강방법을 이용해서 (x_i), (x_j)을 생성합니다. 이 두 이미지를 positive 데이터로 만듭니다. 위의 그림에서 t~T에해당하는 것이 data augmentation 방법입니다. 그 결과는 $\tilde{x}_{i}$, $ \tilde{x}_{j}$에 해당합니다.
  • Base encoder: 인코더 $f()$. 이 인코더를 통과한 이후 d차원의 벡터를 만들어 냅니다.
  • Projection head: $g()$. base encoder가 생성한 벡터를 비선형변환을 하는 MLP을 의미합니다. 그 결과 벡터 $z$가 생산됩니다.
  • contrastive loss function: negative + positive 이미지가 포함되었을때, x_i을 모델에 입력한 경우 x_j을 선별하는 과정을 목적 정의 되었습니다. 이 논문에서는 미니배치를 N개로 두었습니다. N개의 이미지로부터 각각 2개씩 데이터증강을 하니 총 2N의 데이터포인트가 생깁니다. 이 2N에서 positive을 하나 i라고하면 i와 짝이되는 j 은 positive이고 그외는 negative가됩니다. 2(N-1)이 negative pair가 됩니다. 예를 들어, 5개의 이미지를 미니배치라고하면 각각의 5개로 만들어낸 10개는 유사도: sim(u, v). 두 벡터 u, v가 들어오는 경우 $u^{T}v / ||u||||v||$을 하는 과정으로 cosimilarity을 의미합니다. 위의 유사도를 이용해서 아래의 손실함수를 정의합니다. 

Equation 1. SimCLR에서 사용하는 loss function

 

 

전체적인 SimCLR의 알고리즘은 아래와 같습니다. 그리고, 이를 시각화하면 아래와 같은 메트릭스 연산을 하는 것과 동일합니다.

1. 알고리즘의 첫번째 단계로는 각 이미지를 Augmentation하는 것입니다. N개의 이미지가 배치사이즈라고하면 각 N개의 이미지를 2개씩 각각 다른 방법론으로 augmentation 시킵니다.

2. 유사도 메트릭스를 생성합니다. $_{i,j}$가 유사도 메트릭스에 해당하고, i와 i은 같은 이미지이므로 1이어야하고, i와 바로 +1차이나는 j도 같은 이미지로부터 augmentation 된것이니(=1단계:Augmentation), 같은 이미지로 유사도가 1이어야합니다.

 

3. 손실함수 계산: 유사도 메트릭스 $s_{i,j}$에 대해서 i,j의 순서를 바꿔가면서 손실함수를 계산합니다. 분모는 한 행을 의미하고, 분자는 i또는 j에 대해서 계산합니다.

 

 

Result


사전작업(Pre-text)을 학습하는 경우, 이 모델을 fix하고 linear 모델만 단 경우는 여러 데이터셋에서 성능의 우위를 가리기어렵지만, 이를 함께 fine-tuning하는 경우 지도학습모다 더 좋은 성능을 기대할 수 있었습니다.

다른 모델과의 비교에서도 동일한 파라미터내에서는 더 잘 시각적인 표현을 학습해서 예측을 잘한 결과를 보여주고 있습니다 (Figure 6). 또한, ImageNet에서도 라벨이 거의 없는 이미지에 대해서도 Top 5예측성능이 우수하게 나오고있습니다.

 

두 번째 결과로는, "한 이미지로부터 두 Augmentation을 해야하는데, 어떤 조합으로 하는게 좋을까?"를 실험적으로 증명합니다. Augmentation 방법로는 아래의 10가지 말고도 여러가지 방법론이 있습니다. Data augmentation 단계에서 하는게 좋을 지 고민을 해야합니다. 

 

이 고민에 대해서 실험적으로 여러 짝으로 진행해보았고, 그 결과로 컬러왜곡(color distortion)과 Crop의 조합이 가장좋았습니다. 

컬러 왜곡이 중요한 이유를 추가적으로 설명했는데, 랜덤크롭한 영상의 이미지가 비슷한 컬러를 보여주기 때문에, 컬러만 가지고 구분이 될 수 있다는 내용을 언급합니다. 그렇기에 이것을 좀더 어렵게하더라도, 더 잘 맞추기위해(=일반화 성능향상) 컬러 왜곡을 하는 것이 중요하다고 합니다.

이러한 컬러 왜곡을 더 강하게 줄 수록, 비지도학습에서의 성능이 더 향상된다고합니다. 지도학습보다 더 좋은 성능이다라는 것이 아니라, 컬러왜곡을 지도학습과 비지도학습에 둘다 적용할때, 강도를 세게했을때는 SimCLR에서 더 효과가 좋았다라는 것입니다.

 

에폭이 적을때는 배치사이즈가 더 큰 사이즈로하는 것이 좋지만, 충분히 학습시킬 수 있는 epoch이라면 배치아시즈가 그렇게 영향을 주는 것 같지 않습니다. 이는 배치사이즈마다 샘플링을 각각 다르게하니, 시각적인 학습을 충분히 더 할 수 있음에도 100에폭밖에 학습을 못해서 충분히 학습이 안된것처럼 보입니다. Constrative learning에서는 더 큰 사이즈의 배치를 넣으면 negative가 엄청 많아지기에, 더 빨리 학습의 수렴을 유도하는것으로 해석해볼 수 있습니다(=더 빠른 이미지의 구별능력)

 

pytorch구현


파이토치에서 augmentation된 이미지 i, j의 유사도를 구하는 방법은 아래와 같습니다. 

  1. 유사도 메트릭스 ($s_{i,j}$) 내에 각 행에는 2개의 positive 샘플이 있습니다. 하나는 자기자신이고, 나머지는 augmentation된 이미지 2장이 같은 origin이라는 positive sample pair입니다.
  2. 자기자신(i=j)인 인덱스만 지워줍니다. 이 지운 결과는 벡터로 나옵니다.
  3. 벡터에서 positive만 따로 가져오고, 나머지 negative을 따로 구한 후 ,concat 합니다. positive와 negative순서로 concat합니다.
  4. 이 때, 라벨은 positive sample은 항상 0번컬럼에 존재하기에 np.zeros(2N)으로 만듭니다. 왜냐하면 torch.nn.CrossEntropy 구현체에서는 라벨에는 항상 정답라벨의 클레스번호가 들어가기 때문입니다. 정답라벨이 항상 0이기에, 그냥 np.zeros()로 들어갑니다.

 

위 내용을 이용해 구현한 코드는 아래에 있습니다.

 

Conclusion


SimCLR 프레임워크는 Constrative learning을 이용해서 self-supervised learning을 진행하고 다운스트림테스크에서 더 나은 성능을 보여주었습니다. 그리고, 그 각 세부요소들 중에, 어떤 조합/하이퍼 파라미터가 좋은지 결과를 함께 보여주었습니다. 

반응형

 

요약


마르코브 랜덤필드(Markov random field, MRF)은 이산확률변수사이에 상호의존성을 표현하는 그래프모델입니다. 주요한 특징은 무방향성(Undirected)이며 인접한 경우에만 상호작용하는(pairwise interaction)하는 그래프의 성질을 지니고 있습니다. 관찰된 데이터로부터 알려지지 않은 변수를 추론하기위해서 주로 사용되며, 특히 이미지에서는 이미지 복원, 이미지 세그멘테이션, 객체인식 등에 주로 쓰입니다.

 

그림1. 마르코프 랜덤 필드의 예시(Pairwise random feild)

 

 

 

마르코브 랜덤 필드 정의(MRF, Markov random fields)


마르코브 랜덤필드는 마르코브 속성을 가진 그래프를 의미합니다. 이산확률변수(노드)사이의 상호의존성(edge)을 표현하는 그래프 모댈입니다. 이 노드 사이에서는 무방향성(undirect, 무향)입니다.

  • 일반적인 Markov network (=마르코브 속성(Markov property)가 있어야함)
  • 그래프모형이기에 노드(node)와 엣지(edge)가 존재하며, node은 확률변수, edge은 확률변수사이에 종속성을 의미함.

 

그림 표현

  • 검정색원(filled circle)은 관찰된 상태의 노드를 의미
  • 흰 원(empty circle)은 관찰되지 않은 상태의 노드를 의미

위의 그림1은 마르코브 랜덤필드의 흔한 예시입니다. 격자형으로 마르코브 속성을 가집니다. 쉽게 말해서 각 노드들이 확률변수 일때, 변수들 변수들간의 상호 의존성이 존재한다는 것입니다. 마르코브 속성은 현재상태가 과거상태에만 의존한다는 것인데, 이전 과거의 T상태전까지(T memory markcov라고도 함)영향을 받을 수 있음을 의미합니다. 위의 격자모형의 그래프에서는 상호의존성을 인접한 노드에서 받을 수 있습니다. 좀 더 나아가서 인접한 노드라고하지만, 이 인접한 노드들은 주변의 인접한 노드가 또 있기에(2칸 떨어진) 이전 T상태전까지 영향을 받는것처럼 생각할 수도 있습니다(하지만 2칸떨어진 노드가 직접영향을 주는 것은 절대아닙니다. 그럴거면 2칸 떨어진 노드에 직접 연결되어있어야합니다.).

 

만일 노드 3개(A, B, C)가 주어져있고, 노드A가 C을 통하지 않으면 B로갈 경로가 없다고하면 다음과 같이 표기합니다. 만일 이러한 경로가 있으면 이 노드들의 구성은 dependent한것입니다.

$X_A \perp X_B \,|\, X_C$

 

 

수식표현

  • $x_{i}$: 확률변수. 노드를 의미. 변수라고도하고 node라도 합니
  • $\psi(x_{i}, x_{j})$: 관찰되지않은 노드($x_{i}$)와 관찰되지 않은노드($x_{j}$)의 종속성을 의미. 발음은 프사이라고 합니다. 이 종속성은 메트릭스로도 표현할 수 있습니다. 통상 대칭인 정방행렬(symmetric, square matrix)여서 노드 i에서 j로 가거나 노드 j에서 노드i로 가는 것이 동일한 종속성입니다. 하지만, 이 두 노드 사이가 비대칭이어도 상관없습니다. 영문표기로는 compatible function라고합니다.
  • $\phi(x_{i}, y_{j})$: 관찰되지않은 노드($x_{i}$)와 관찰된 노드($y_{j}$)의 종속성을 의미합니다. 위의 표기와 다르게 한쪽이 관찰되지 않을 때 쓰는 notation입니다. 영문표기로는 evidence for x라고 합니다. 이미 y은 결정되어있으니 x을 구하기위한 근거라는 것이죠.
  • $N_{n}=\{m \in \mathbb{N} \,|\, (n,m) \in \varepsilon\}$: 노드 n의 엣지로 연결된 노드의 집합 (=노드N의 이웃집합)
  • $P(X_{n} \| X_{N_{n}})$: 노드n의 인접한노드(=로컬 조건부확률)로만 알고있으면$ X_{n}$의 확률을 알 수 있음을 의미합니다. 마르코브 속성을 의미합니다. [1]

 

 

마르코브 랜덤 필드의 예시


아래의 그림은 MRF의 예시입니다. 4개의 노드가 존재합니다. 3개의 노드($x_{1}, x_{2}, x_{3}$)은 관찰되지 않은 노드이고, 유일하게 node 4($y_{2}$)만 관찰된 노드입니다.

 

위 MRF을 수식으로 표현해보겠습니다.

  • 노드: 관찰되지 않은 노드1은 $x_{1}$, ..., 노드3은 $x_{3}$입니다.  반면 관찰된 노드4은 $y_{4}$입니다.
  • MRF은 각 노드사이의 종속성을 표현한다고 했습니다. 각 노드의 종속성을 아래와 같이  표현할 수 있습니다.

 

에너지함수(energy function)와 포탠셜(Potential)


마르코브 랜덤필드의 확률분포는 에너지함수를 사용하여 계산할 수 있습니다. 즉, 이런 마르코브랜덤 필드가 나오려면 어떤 확률분포였겠드냐를 의미합니다. 여기서 에너지함수라는 것을사용하는데,에너지함수는 다음과 같은 아이디에 기반합니다.

  • 높은 에너지를 가지고 있는 상태는 불안정하니 낮은 확률을 갖는다는 것
  • 에너지함수는 확률변수의 집합의 각 상태에 대한 에너지값을 부여하며, 에너지가 낮을수록 높은확률

 

깁스 분포(Gibbs distribution)을 이용해서 마르코브 랜덤필드를 정의해보겠습니다. (깁스 분포는 결합확률분포가 각 확률변수의 조건부 독립적이어서 곱한것을 의미)

 

$P(X)= \frac {1}{Z} \prod _{c\in C} \phi_{c} (x_{c})$

여기서 C은 maximal clique을 의미합니다. 즉 이 그래프에서 클리크에 속하는 모든 노드들의 확률을 다 하나하나 곱하면 전체 그래프가 될 수 있다는 것을 의미합니다. 그리고 여기서 Z은 보통 정규화 상수인데, 이 그래프에서는 다음과 같이 표현합니다.

$Z=\sum_{x}\prod _{c \in C}\phi_{c}(x_{c})$

 

이 그래프에서 $phi_{c}(x_{c})$을 다음과 같이 쓸 수 있습니다. 보통 여기서 T은 온도(temperature)라고해서 1을 쓰씁니다. 

$\phi_{c}(x_{c})=e^{-1 \frac{1}{T}V_{c}(x_{c})}$

 

각각 Z랑 $phi_{c}(x_{c})$을 알고있으니, 깁스분포에 대입해보면, 아래와 같고, 지수의 승을 덧셈으로 표현할 수 있으니까 결국 두번째식까지 얻어낼 수 있습니다. 

$P(x)=\frac{1}{Z}\prod _{c \in C} e^{-\frac{1}{T}V_{c}(x_{c})}$

$P(X)=\frac{1}{Z}e^{-\frac{1}{T}\sum_{c \in C}V_{c}(x_{c})}$

이때, $\sum_{c \in C}V_{c}(x_{c})$을 보통  $U(x)$라고하고, 에너지라고합니다. 그리고 V은 클리크 포탠셜(clique potential)이라고합니다.

 

 

마르코브 랜덤 필드와 베이지안 네트워크의 차이


마르코브랜덤필드는 베이지안 네트워크와 유사합니다. 표로 정리하면 아래와 같은 차이점이 있습니다.

  마르코브 랜덤 필드 베이지안 네트워크
정의 확률 변수 간의 조건부 독립 관계를 나타내는 무방향 그래프 확률 변수의 조건부 독립 관계를 나타내는 방향성 있는 그래프(Acyclic)
방향 Undirected Directed
두 노드의 확률 compatible function ψ_ij (x_i,x_j) Conditional probability p(x_i |x_j)
변수타입 이산 이산, 연속
순환가능 가능 불가능

 

 

 

 

Python example


MRF의 예시

import networkx as nx
import matplotlib.pyplot as plt

# 그래프 생성
G = nx.Graph()

# 노드 추가
nodes = ["x1", "x2", "x3", "y2"]
G.add_nodes_from(nodes)

# 간선 추가 및 가중치 설정
edges = [("x1", "x2", 0.9), ("x2", "x3", 0.1), ("y2", "x2", 0.1)]
G.add_weighted_edges_from(edges)

# 각 노드에 확률 변수 값을 할당 (예: 이진 랜덤 변수)
for node in G.nodes:
    G.nodes[node]['value'] = 0  # 초기값을 0 또는 1로 설정

# 노드 위치 지정
pos = {
    "x1": (-1, 0),  # 왼쪽에 위치
    "x2": (0, 0),  # 6시 방향에 위치
    "x3": (1, 0),   # 오른쪽에 위치
    "y2": (0, 1)    # 12시 방향에 위치
}

# 그래프 시각화
nx.draw(G, pos, with_labels=True, font_weight='bold', node_size=800, node_color='lightblue', font_color='black')

plt.show()

 

MRF을 이용한 iterated conditional modes(ICM)을 적용

import cv2
import numpy as np
from PIL import Image

# 이미지 불러오기
image = cv2.imread('/home/heon/repositories/detection_models/sample.png', cv2.IMREAD_GRAYSCALE)
image = cv2.resize(image, (128, 128))


noisy_image = image + np.random.normal(0, 15, image.shape).astype(np.uint8)
binary_image = cv2.threshold(noisy_image, 100, 255, cv2.THRESH_BINARY)[1]

Image.fromarray(image)
Image.fromarray(noisy_image)
Image.fromarray(binary_image)

def calculate_cost(y_p, x_p, alpha, beta, neighbors, y_k):
    cost = alpha * (1 - (y_p == x_p))
    for q in neighbors:
        cost += beta * (1 - (y_p == y_k[q]))
    return cost

def icm_reconstruction(observed_image, alpha, beta, max_iterations):
    height, width = observed_image.shape
    y_k = np.copy(observed_image)  # Initialize the restored image as the observed image.
    
    neighbors = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
    
    for _ in range(max_iterations):
        updated_y = np.copy(y_k)
        
        for i in range(1, height - 1):
            for j in range(1, width - 1):
                current_pixel = observed_image[i, j]
                best_cost = float('inf')
                best_pixel_value = None
                
                for y_p in [0, 1]:
                    cost = calculate_cost(y_p, current_pixel, alpha, beta, neighbors, y_k)
                    # print(i,j,cost, best_cost)
                    if cost < best_cost:
                        best_cost = cost
                        best_pixel_value = y_p
                updated_y[i, j] = best_pixel_value
        
        if np.array_equal(updated_y, y_k):
            break
        
        y_k = updated_y
    
    return y_k

 

Reference


[1]https://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/AV0809/ORCHARD/#:~:text=A%20Markov%20Random%20Field%20(MRF,the%20set%20of%20nodes%20S.

반응형

요약


FastAPI middleware은 어플리케이션의 요청 및 응답처리과정중에 중간에 위치하여, 여러 작업을 수행할 수 있는 요청->동작 사이, 응답->동작사이에 여러 기능을 수행
주로 요청 및 응답을 변형하거나(예, 암호화), 인증, 로깅및 모니터링, 예외처리등을 수행.
ASGI (Asynchronous Server Gateway Interface) 프로토콜을 따르는 웹 애플리케이션에서 비동기 요청을 처리하기 위해, request._receive속성에 설정하도록함


fastAPI의 미들웨어는 @app.middleware("http")와같이 함수위의 데코레이터로 사용가능합니다.
async def set_body(request: Request, body: bytes) 함수는 원래 요청 객체에 바이트로 된 본문을 설정하기 위해 사용됩니다. 
아래의 함수 내에서 async def receive() -> Message 함수를 정의하고, 이 함수는 request 객체의 _receive 속성으로 설정됩니다. 
따라서 ASGI 프레임워크는 요청을 수신할 때 이 _receive 함수를 호출하여 요청 객체의 본문을 설정하여 로깅할 수 있습니다.

async def set_body(request: Request, body: bytes):
    async def receive() -> Message:
        return {"type": "http.request", "body": body}

    request._receive = receive


@app.middleware("http")
async def log_request_payload(request: Request, call_next):
    payload = await request.body()
    await set_body(request, payload)
    request.app.state.logger.debug(f"Request payload: {payload.decode()}")
    response = await call_next(request)
    return response

 



아래의 call_next의 인자가 callable로 (reqeust)을다시 인자로 받아야하는데, FastAPI.reqeust은 아래와 같이 구성되어있습니다.
request._receive 속성이 property로 설정되어있고 타입이 empty_receive라 Courutine의 타입을 받게되어있습니다.
그래서 async def로 정의한 함수를 넣어주도록하고, 그 메시지의 형태를 stream내애 얻어지는 형태랑 유사하게 받도록합니다.

class Request(HTTPConnection):
    _form: typing.Optional[FormData]

    def __init__(
        self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send
    ):
        super().__init__(scope)
        assert scope["type"] == "http"
        self._receive = receive
        self._send = send
        self._stream_consumed = False
        self._is_disconnected = False
        self._form = None

    @property
    def method(self) -> str:
        return self.scope["method"]

    @property
    def receive(self) -> Receive:
        return self._receive

    async def stream(self) -> typing.AsyncGenerator[bytes, None]:
        if hasattr(self, "_body"):
            yield self._body
            yield b""
            return
        if self._stream_consumed:
            raise RuntimeError("Stream consumed")
        self._stream_consumed = True
        while True:
            message = await self._receive()
            if message["type"] == "http.request":
                body = message.get("body", b"")
                if body:
                    yield body
                if not message.get("more_body", False):
                    break
            elif message["type"] == "http.disconnect":
                self._is_disconnected = True
                raise ClientDisconnect()
        yield b""
반응형

conda 설치후 아래와 같은 에러가 발생


$ conda activate myenv
usage: conda [-h] [--no-plugins] [-V] COMMAND ...
conda: error: argument COMMAND: invalid choice: 'activate' (choose from 'clean', 'compare', 'config', 'create', 'info', 'init', 'install', 'list', 'notices', 'package', 'remove', 'uninstall', 'rename', 'run', 'search', 'update', 'upgrade', 'build', 'content-trust', 'convert', 'debug', 'develop', 'doctor', 'index', 'inspect', 'metapackage', 'render', 'skeleton', 'verify', 'pack', 'token', 'server', 'env', 'repo')

 

 

해결방법: conda 초기화가 안되어 발생하는 문제


1. Conda 업데이트: 먼저 Conda를 최신 버전으로 업데이트하십시오. 아래 명령어를 실행하여 Conda를 업데이트합니다.

$ conda update -n base -c defaults conda

 

2. 시스템 환경 변수 확인: Conda가 설치된 경로를 시스템 환경 변수에 제대로 추가했는지 확인하십시오. 다음 명령어를 사용하여 Conda의 경로를 확인하고 확인하세요. "/home/사용자/anaconda3/bin" 이러한 경로가 존재해야합니다.

$ echo $PATH
/home/<사용자>/anaconda3/bin:/home/heon/anaconda3/condabin:/home/heon/.vscode-server/bin/e7e037083ff4455cf320e344325dacb480062c3c/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0:/mnt/c/Windows/System32/OpenSSH:/mnt/c/Users/4pygm/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/4pygm/AppData/Local/Programs/Microsoft VS Code/bin:/snap/bin

 

3. shell 재시작

$ source ~/.bashrc  # 또는 ~/.zshrc

 

4. Conda 초기화

$ conda init

 

반응형

+ Recent posts