회전변환시에 필요한 matrix을 roration matrix라고하며, 2D와 같이 표현할 수 있습니다.

$M(\theta)=
\begin{bmatrix}
    cos\theta & -sin\theta \\
    -sin\theta & cos\theta
\end{bmatrix}$

이 공식의 유도과정을 이해해보겠습니다.

그림1.

위 그림과 같이 구하고자하는 평면에 두 벡터가 있습니다. 이 그림의 요소들은 다음과 같습니다.

  • G(x, y): 회전시키기 전 벡터
  • G'(x',y'): G을 $\theta$만큼 회전시킨 벡터, 
  • r:G벡터와 G'벡터 길이
  • $\theta$: G을 G'으로 반시계방향(counter-clockwise)으로 회전한 벡터

위 그림에 따라 x, y은 아래와 같이 표현할 수 있습니다.

  • $ x=r cos v $  
  • $ y=r sin v $
  • $ x'=rcos(v+\theta)$
  • $ y'=rsin(v+\theta)$

3번 째, 4번째 식의 $ v+\theta$은 삼각함수의 덧셈정리로 풀면 아래와 같습니다.

  • $x'=rcos(v+\theta) = r(cos(v)\cdot cos (\theta) -sin(v)\cdot sin (\theta))$  (코코마사사)
  • $y'=rsin(v+\theta) = r(sin(v) \cdot cos (\theta) + cos(v) \cdot sin(\theta))$ (싸코코)

위 식에서 $cosv$ 와 $sinv$은 이미 알려져 있으니, 대입하여 알 수 있습니다. 

  • $x'=r(x \cdot cos(\theta) - y \cdot sin(\theta))$
  • $y'=r(x \cdot sin(\theta) + y \cdot cos(\theta))$  (x을 앞으로 정렬)

위 식에서 r이 1인 경우는 단순히 선형결합형태이므로, 선형결합 형태로 나타낼 수 있는 메트릭스로 표현 할 수 있습니다.

$\begin{bmatrix} x' \\ y' \end{bmatrix}= 
\begin{bmatrix}
    cos\theta & -sin\theta \\
    sin\theta & cos\theta
\end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix}$

 

Python implementation


파이썬으로 위의 방법을 구하면 아래와 같이 작성할 수 있습니다. 주의할 것은 여기서 는 각도이며, 여기서는 30도를 라디안 단위로 변환하여 사용해야 합니다.

import numpy as np

# 각도를 라디안으로 변환
theta = np.deg2rad(30)

# 회전 행렬 생성
R = np.array([[np.cos(theta), -np.sin(theta)],
              [np.sin(theta), np.cos(theta)]])

# G 벡터 생성 (임의의 값으로 설정)
G = np.array([1, 0])  # 예를 들어, G = (1, 0)으로 설정

# G' 벡터 계산
G_prime = np.dot(R, G)

print("G' 벡터:", G_prime)
print("회전 행렬:")
print(R)

 

References


https://www.cuemath.com/algebra/rotation-matrix/

반응형

요약


 

전자장비에서 처리하는 컬러이미지는 기본 요소인 픽셀(Pixel)을 구성하고, 이 픽셀을 R, G, B 값을 갖습니다. RGB은 컬러가 아니라, 숫자들의 조합을 컬러로 매핑한 컬러코드에 불과합니다. 이 컬러코드를 어떤식으로 매핑했느냐가 RGB, CIEXYZ, CIELAB의 흐름이됩니다. 요약하면 아래와 같습니다.

  • CIE 1931RGB컬러시스템: 사람이 인지적으로 구분할수 있는 컬러코드입니다.
  • CIEXYZ: CIE1931RGB컬러시스템의 빨강의 음수부를 양수로 변환한 컬러리스템입니다.
  • CIELab: 색차(컬러-컬러)간의 차이를 균일하게 만든 표준색공간으로 등간격인 색공간입니다. L(명도, lightness), A*(red & green), B*(Yellow & Blue)로 구성됩니다.

 

사전지식


  • CIE (Commision Internationale de l'Eclairage): 국제조명기구
  • 색도: 색의 정도를 의미합니다. 색은 밝기와 색도로 나누는데 흰색과 회색은 같은 색도이지만, 밝기만 차이납니다.

 

CIE 1931RGB color system


CIE 1931RGB color system은 국제조명기구가 인간의 시각에 기반한 컬러시스템을 만들었습니다. 이 컬러시스템 인간이 최초로 색을 정량적으로 표현한 것입니다.이 컬러시스템은 RGB컬러시스템과 전혀 관계가 없습니다. 단지 사람이 느끼기에, R, G, B 의 광량이 어느정도 합쳐지면 색이 되는지를 표시한것에 불과합니다. 이 실험을 "컬러 매칭 실험"이라고합니다. 또는 등색실험이라고 종종 번역됩니다.

1920년대에 W. 데이빗 라이트(Wright)와 존 길드(Guild)란 사람이 컬러매칭실험을 진행했습니다. 이 컬러매칭 실험은 단색광(Mocochromatic light)을 통해 특정 파장을 하나 빛추고, 실험광(R), 실험광(G), 실험광(B)의 광량을 조절해가면서 합성한후에, 단색광과 합성광이 색이 동일한지 외부관찰자한테 관찰하여, 동일한지만 확인하는 것입니다. 그렇기에, CIE 1931 RGB color system에서 구분될 수 있는 색상은 인간이 실제로 구분할 수 있는 색상수와 동일하다고 종종 여겨집니다. 스크린의 한 쪽에서는 단색광을 비추고(reference color), 다른 한쪽은 관찰자가 R,G,B을 합성해서 단색광과 동일할 때까지 밝기를 조절했습니다. 이후에 CIE 에서 이를 표준으로 채택하면서, 컬러 매칭실험에서 얻어진 광량의 조합을 표시한것이 CIE 1931RGB입니다 [1].

 

컬러매칭실험에서는 하나 문제가 있었습니다. 예상과는 달리, 컬러매칭시에 R,G,B로 어떻게 하더라도 만들 수 없는 색이 있었습니다. 예를 들어, 특정 청록색깔은 R,G,B을 어떻게해서도 합성이 안되었습니다. 이경우에는 테스트 색깔인 청록에 R을 혼합하고, 청록+R을 테스트컬러로 두었습니다. 그리고, 나머지 G와 B의 조합으로 등색을 만들었습니다. 이렇게 RGB와의 혼합량에 의해 나타내는 컬러를 그래프로 나타낸 것을 "색 대응 함수"라고 합니다.  그리고, 이 R,G,B의 각각의 광량을 삼색 자극값(Tristriumulus value)라고합니다(또는 삼자극치...).

이 실험은 사람의 눈에 있는 빛의 수용기인 원추세포(Cone cells)를 기반으로 합니다. 이 원추세포는 3가지 종류(subtype)이 있고, 이 3가지 종류가 각각 다른파장을 인지하도록 되어있습니다. 단파장(Short)은 420nm~440nm, 중파장(M)은 530nm~540nm,이며, 장파장(long) 560nm~580nm입니다. 이 세가지 원추세포를 가중치를 두어 조절하면, 특정 파장길이의 색상을 인지할 수 있습니다. 즉, 어떤 색이든 이 삼원색의 조합으로 다 만들 수 있으니, 단색광 하나와의 비교로 광량을 기록하여 두면 되지 않겠느냐는 것입니다. 이 세가지의 광원의 광량을 "삼자극치(Tristimulus)"라고 합니다. ("삼", "자극", "치"라고 이해하면됩니다)

그림2. CIE 1931 RGB 색 대응 함수 (Wikipedia): X축 단색광의 파장, Y축 각각 필요한 R,G,B 삼원색량의 광

 

이 색 대응함수는 아래와 같습니다. X축은 단색광의 파장을 의미하고, 이 단색광의 파장을 재현하기위한 광량을 Y축에 그린 것입니다. Y축은 절대적인 밝기는 아니고, 상대적인 밝기로 색 대응함수 R,G,B의 아래 면적의합이 모두 1이 되도록 정규화한것입니다. 그래서, 각 RGB은 0에서 1값을 갖습니다. 이 RGB색공간은 컬러매칭을 통해 얻어지는 광량의 조합이기에,  전자장비에서 얻어진 RGB와는 다릅니다.

 

CIE 1931XYZ color system


컬러매칭시험에서 색 대응함수가 음수값을 보이는지점이 있어 ,색재현 등에서 단점으로 꼽혔습니다(그림 2). 그림2에서 r의 색대응함수에서 500nm 파장대가 0이하인 것을 볼수 있습니다. 즉, 이 마이너스 부분을 플러스로 변환한 것이 CIEXYZ입니다. 이 RGB 컬러 매칭함수의 마이너스 값을 없애기 위해서, Dean Judd 마이너스부분을 없앨수 있는 컬러 매칭 함수를 제안했다. 그래서, RGB매칭함수를 XYZ컬러매칭함수로 변환하는 함수가 아래와 같습니다. 

XYZ 컬러 공간에서도 좌표는 각 컬러가 0에서 1사이 입니다. 

이 XYZ 컬러 공간은 아래와 같이 특징을 가집니다.

  • CIE RGB색공간을 단순히 선형변환 한 것입니다.
  • X,Y,Z의 모든 값이 0또는 양수 값을 가집니다.
  • 인간이 볼 수 있는 색은 X, Y, Z의 [0, 1]로 이루어져야한다.
  • 흰색은 x=y=z=1/3인 지점으로 정의되어야한다.

이 XYZ컬러 공간은 CIE RGB color system의 얻어진 RGB 이므로, 전자장비에서 처리되는 RGB값을 매칭해도 의미가 없습니다.

 

 

CIE xyY Color system


CIE RGB로부터 CIE XYZ로 컬러매칭함수를 변경했습니다. 이제는 XYZ가 삼자극치가 됩니다. 이 X, Y, Z을 다른 공간인 xyz에 매핑할 수 있습니다. 

아래의 그림에서보면, XYZ좌표가 있고, 각 X, Y, Z의 점은 RGB가 적절하게 섞인 값이고 각 축이 1이며, 나머지는 0인좌표들입니다([1,0,0], [0, 1, 0], [0, 0, 1]). 근데 이공간을 잘보면, Y축에 대해서 XY평면에 정사영(Projection)시키고, 그 밝기(또는 조도)를 Y축이라고하면 색깔을 똑같이 얻을 수 있습니다.

그림4. https://mymusing.co/cie-rgb-and-cie-xyz-color-space/

 

x = X/(X+Y+Z)
y = Y/(X+Y+Z)
z = Z/(X+Y+Z)  # z=1-x-y

이렇게 정사영된 좌표를 xyY color space라고 합니다. 아래의 수식과 같이 X Y Z의 합이 1이도록 만들들면, x+y+z=1인 등식이 하나 생겨서 z=1-x-y가 될 수 있고, 이 때의 소문자 x,y와, 대문자 Y(조도)로 구성된 컬러공간이 xyY clor space입니다.

그렇기에 아래와 같은 CIE Chromaticity diagram에서는 x축이 한색깔이 아니라 여러색깔을 의미하고, 이에 해당하는 소문자 x, y축 또한 한 색깔이아닌 여러색깔이며 소문자 y축으로 표현합니다. 그리고, XYZ color space와 동일하게 정사영된것뿐이므로, 중앙은 흰색이됩니다. 그리고, 아래의 chromaticity diagram에서는 색만 보기에, 명도(밝기)인 Y가 없어 보이지 않습니다. 

CIE Chromacitiy diagram

 

HSV color system


HSV color system은 색을 표현하는 또 하나의 방법입니다. 색상(Hue), 채도(Saturation) 명도(value)를 각각 이용하여 색상을 표현합니다. 이 세가지 개념은 아래와 같습니다.

  • 색상(hue): 가장 기본적인 색을 빨강으로 하여, 빨강을 기준으로 0도로 각 스펙트럼을 360도로 표현합니다. 360도와 0도에 위치하는 색상은 모두 빨강입니다. 이 색상(hue)은 학문적으론, 빨강 노랑, 초록, 파랑 중 하나 또는 이 두가지의 색의 조합과 유사하게보이는 시각적인 감각이라고 합니다. 즉, 순수한 색에서의 다양성만 의미합니다[3] . 
  • 채도(Saturation): 채도는 가장 진한 상태를 100%로 하는 색상의 진한정도입니다. 흔히, 진한 빨강와 연한 빨강이라고 색상의 진한정도를 표현하듯이, 이는 채도를 의미합니다. 아래의 그림6과 같이, 진한 빨강화 연한 빨강 (세탁한 다음에 물빠진 옷 같은 색상)과 같이 사람은 색상의 진한 정도를 파악할 수 있고, 이 개념을 채도라고 합니다.
  • 명도(Value): 색상의 밝음과 어두움을 나타내며, 모든 색상의 명도가 0은 검정입니다. 단 모든 색상의 명도가 100%인 경우는 흰색은 아닙니다.

HSV의 탄생배경은 대부분의 컴퓨터나 텔레비전의 디스플레이에서 사용되는 광원은 RGB각각의 광원의 강도로 조절이 가능합니다(이를 RGB additive primary color system)이라고합니다. 이 RGB은 색공간(Gamut)에서 모두가 R,G,B광원의 강도로 비슷하게하니 재현성은 좋았지만, 예술계쪽에서 사용하기 직관적이지 않았습니다. 일반인들은 와닿지 않지만, 아티스트들은 색조, 음영에 기반하여 작업하기때문에, 이를 H,S,V에 재매핑 할 필요가 있었습니다. 그림6과 같이 아티스트들은 어떤색에, 흰색을 조합한 것을 "틴트", 검정과의 조합은 "쉐이드", 이 두 조합을 "톤"이라고 구분하여 색상을 정하고 있었는데, RGB은 다른 표현방식이기에 직관적이지 않습니다. 예를 들어, 그림7과 같이 오렌지색의 색감을 줄이고싶은데, R,G,B에서는 각각 얼마나 줄여야하는지 와닿지 않습니다. 하지만 HSV에서는 Saturation만 줄이면되기에 상대적으로 더 직관적입니다. 즉, 인간이 느끼는 색의 변화가 HSV가 보다 직관적이고, HSV에서 원하는 색을 찾기위한 가감(additive, subtract)가 더 계산이 편하기에 HSV가 1970년대에 제시되었습니다. (동일한 문제를 제기하면서, HSL color system도 비슷한시기에 제안되었습니다)

그림6. https://en.wikipedia.org/wiki/HSL_and_HSV
그림7. 오렌지색의 색감을 절반으로 떨어뜨리고자할 때의 RGB 변화

보통 HSV color system은 원뿔형 또는 원기둥형으로 나타내기도 합니다. 원기둥형과 원뿔형 모두에서 각 각도는 색상을 의미합니다. 위에서 언급했듯이, 명도가 충분히 밝지 않으면 모든 색깔은 검정이기에 원뿔형을 나타냅니다. 반대로 말하면, 색상이 진하지 않으면 명도의 변화게 크게 됩니다. 이와 다르게 원기둥 모형에서는 각 색상의 명도가 0인부분이 중복되어 검정으로 표시되며, 원뿔형의 여집합에 해당하는 부분은 사실상 중복이됩니다. 

Reference

Fairman H.S., Brill M.H., Hemmendinger H. (February 1997). "How the CIE 1931 Color-Matching Functions Were Derived from the Wright–Guild Data". Color Research and Application 22 (1): 11-23. 및 Fairman H.S., Brill M.H., Hemmendinger H. (August 1998). "Erratum: How the CIE 1931 Color-Matching Functions Were Derived from the Wright–Guild Data". Color Research and Application 23 (4): 259.

https://en.wikipedia.org/wiki/HSL_and_HSV 

[3] Fairchild (2005), pp. 83–93

반응형

 


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을 진행하고 다운스트림테스크에서 더 나은 성능을 보여주었습니다. 그리고, 그 각 세부요소들 중에, 어떤 조합/하이퍼 파라미터가 좋은지 결과를 함께 보여주었습니다. 

반응형

 

 

에지(edge)


에지(엣지, edge)은 서로 다른 물체의 경계면에 나타내는 경계선을 의미합니다. 이 경계 주변이 픽셀들의 분포를 생각해보면, 명암의 급격한 변화가 있게 됩니다. 아래의 강아지 사진을 보면, 강아지의 눈을 주변으로 경계는 명암차이가 극명합니다(Figure 1). 이러한 명암차이로, 흰색강아지털과 검은색 눈동자의 경계면을 사람도 인식할 수 있습니다. 에지 검출알고리즘은 이렇듯 명암차이가 극명하게 나는 지점을 추출하기 위한, 여러 연산자(+알고리즘)을 사용하는 것이 공통적입니다.

Figure 1. 포메라니안 강아지

 

 

미분을 이용한 에지의 검출


에지의 검출은 명암변화가 급격히 일어나는 지점을 찾는 것이라고 했습니다. 수학적으로는 변화가 급격하게 일어나는 지점을 찾는 것은 미분을 이용합니다. 하지만, 디지털영상에서는 변화가 급격하게 일어나는 지점을 찾으려면 어떻게 해야할까요? 미분을 이용하고 싶지만, 아래와 같이 미분연산에서 극한을 취급할 수 없습니다. 왜냐하면, 디지털영상은 이산형이이기 때문에(=연속이 아님), 미분을 취급하기 어렵습니다. 따라서, 아래와 같이 옆 픽셀과의 차이로 계산합니다.

$f'(x)=\frac{df}{dx}=\frac{f(x+1)-f(x)}{\Delta x}=f(x+1)-f(x)$ (픽셀위치차이를 1로 고려하여)

함수 식은 위와 같고, 이를 영상에 적용려면, 필터(연산자, 커널)를 하나 정할 수 있습니다. 바로 [-1, 1]커널 입니다. 아래와 같이 f(x+1), f(x)인 박스(픽셀)에 [-1, 1] 어레이를 곱하면, 미분값을 구할 수 있습니다. 

하지만, 현실세계에서는 이와 같이 경계면이 확확 바뀌지않습니다. 위의 식으로 아래의 그림(figure 3)의 좌측을 계산한다면, 엣지가 f(x+1), f(x)의 차이가 커서 구분이 잘되겠지만, 우측의 램프엣지(ramp edge)의 형태인 경우는 램프엣지 구간의 명암차이가 일정하게 나타날 것입니다. 그래서, 이 방법론으로는 한계가 있습니다.

Figure 3. 이상적인 엣지(계단형 엣지)와 램프엣지의 형태 (10.14445/22312803/IJCTT-V23P110)

 

하지만, 1차미분이아닌 2차미분을 이용하면 램프엣지(ramp edge)에서도 경계면을 찾을 수 있습니다. 아래의 그림의 좌측(Figure 4), 램프엣지가 있다고할 때, 이 명암의 크기는 우측상단의 그림처럼 보여질 것 입니다. 그리고 이를 1차미분하면 위의 블록이 하나 있는 것처럼 보이는 봉우리가 나타날 것입니다. 그리고, 여기서 한번 더 미분한 (2차미분) 값을 구하면, 1차미분에서 봉우리의 시작점, 출발점 사이의 가상의 선이 무조건 0을 지나게됩니다. 즉, 2차 도함수를 이용하여, 영교차(zero-crossing)하는 지점을 찾으면, 램프엣지에서도 경계선을 찾을 수 있습니다.

Figure 4. 램프엣지에서의 경계구하기 https://www.52coding.com.cn/2018/12/04/RS%20-%20Recognizing%20Image%20Features%20and%20Patterns/

위의 영상은 1차원의 공간에서 명암을 기준으로 했기에, 이제 2차원으로 확장해봅니다. 단순히 1차원을 2차원으로 확장하면, x, y축에서의 미분값을 구하면됩니다. 그 식은 아래와 같습니다. 1차도함수 먼저 정의하고, 1차도함수를 2번사용하면 2차도함수를 구할 수 있습니다.

$\nabla f(y,x)=f'(y, x)=(f(y+1, x)-f(y,x), f(y, x+1)-f(y,x))= (d_{y},d_{x})$

하지만, 실제로는 잡음이 있어서 위와 같이 픽셀 하나차이의로만은 노이즈가 있어, 두 픽셀 사이로 영교차 이론을 적용합니다. 그렇기에 실무적으로 쓰이는 공식은 아래와 같습니다.  이렇게 되면 실제 마스크(kernel)은 [-1, 0, 1]와 같은 커널을 적용할 수 있습니다.

$\nabla f(y,x)=f'(y, x)=(f(y+1, x)-f(y-1,x), f(y, x+1)-f(y,x+1))= (d_{y},d_{x})$

위와 같이 엣지를 구할 수 있으면, 추가적으로 다음의 두 개념(엣지강도, 그레디언트 방향)을 구할 수 있습니다.

  • manitude($\nabla f(y,x)$) = $\sqrt{d_{y}^{2}+d_{x}^{2}}$
  • Gradient orientation: $ \theta = tan^{-1}(\frac{d_{y}}{d_{x}}) $

 

Sobel filter: 1차원 가우시안 필터(Smoothing)을 이용한 필터  + 미분


현실세계에서는 필터를 적용할 때, 이미지의 픽셀이 노이즈가 많기 때문에 이를 리덕션해줄 필요가 있습니다. 가우시안 필터는 이러한 경우 흐릿하게하는 블러(blurring)에 사용되는데요. 소벨필터는 이 가우시안 필터와 x,y 축별로의 그레디언트를 혼합하여 사용하는 필터입니다. 그렇기에 상대적으로 노이즈에 덜 민감한 편입니다.

Sobel filter 및 분해

 

 

Prewitt operation: 평균필터 + x,y 미분


프레윗 연산은 소벨과 다르게, 평균으로만 스무딩필터를 적용합니다. 그 결과 아래와 같은 커널을 얻을 수 있습니다.

Prewitt filter 및 분해

[1] https://theailearner.com/tag/cv2-sobel/

반응형

요약: 그레프 컷을 이용한, 객체 분리를 위한 이미지 분할 알고리즘


GrabCut 알고리즘은 이미지 분할 알고리즘 중 하나로, 이미지에서 객체를 분리하는 기술입니다. 이 알고리즘은 Microsoft Research에서 2004년에 개발되었으며, 이미지 분할에서 높은 성능을 보입니다.

 

방법론


GrabCut 알고리즘은 기본적으로 그래프 컷(Graph Cut) 알고리즘을 기반으로 하며, 다음과 같은 단계로 이루어집니다.

  1. 초기화: 입력 이미지에서 전경(foreground)과 배경(background)을 구분할 수 있는 초기 마스크(mask)생성:. 예를 들어, 사용자가 수동으로 객체를 지정하거나 또는 머신러닝 알고리즘 등을 사용하여 초기 마스크를 생성할 수 있습니다.
    초기 마스크에는 전경, 배경, 그리고 불확실한 영역이 있습니다. 불확실한 영역은 전경인지 배경인지 확실하지 않은 영역입니다.
  2. 가우시안 혼합 모델(Gaussian Mixture Model) 적용: 전경과 배경의 색상 분포를 추정하는 가우시안 혼합 모델(GMM)을 적용합니다. 전경과 배경은 각각 다른 GMM으로 모델링됩니다. 불확실한 영역에 대해서는 두 GMM을 혼합한 모델을 사용합니다.
  3. 에너지 함수 계산: 각 픽셀의 전경일 가능성과 배경일 가능성을 기반으로 에너지 함수를 계산합니다. 에너지 함수는 전경과 배경에 속할 가능성이 높은 픽셀에 대해서는 에너지가 작게, 그렇지 않을 경우에는 에너지가 크게 계산됩니다.
  4. 그래프 컷(Graph Cut) 수행: 그래프 컷 알고리즘을 사용하여 전경과 배경을 분리합니다. 그래프는 노드(node)와 엣지(edge)로 이루어져 있으며, 각 픽셀은 노드에 대응되고, 인접한 픽셀 간에는 엣지가 존재합니다. 엣지에는 가중치가 부여되는데, 이 가중치는 인접한 노드의 색상 및 에너지 함수를 기반으로 계산됩니다. 그래프 컷 알고리즘을 적용하여 전경과 배경을 구분합니다.
  5. 반복
    전경과 배경이 충분히 분리될 때까지 단계 2에서 4를 반복

 

코드: Python3


1. Grabcut할 이미지를 불러옵니다.

import cv2
import numpy as np

# 입력 이미지 로드
img = cv2.imread('input_image.jpg')

2. 초기화: 초기 마스크, 전경 후경의 모델(Gaussian mixture model)에 사용되는 특징값 저장.

# 초기 마스크 생성 (전경, 배경, 불확실한 영역)
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1,65), np.float64)
fgdModel = np.zeros((1,65), np.float64)

3. ROI 계산

# 전경과 배경 좌표 지정
rectangle = (365, 362, 44, 37)
# 또는 전경과 배경 지정 (마우스로)
rectangle = cv2.selectROI(img)

4. Grabcut 적용

# GrabCut 알고리즘 적용
cv2.grabCut(img, mask, rectangle, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)

5. 결과 출력: "np.where((mask==2)|(mask==0), 0, 1)"는 NumPy의 where() 함수를 사용하여 마스크에서 전경에 해당하는 부분을 추출하는 코드입니다. mask는 GrabCut 알고리즘을 적용한 결과 생성된 마스크이며, 마스크의 값은 0, 1, 2, 3 네 가지 값으로 구성됩니다. 이 중에서 값이 0과 2는 배경을 나타내고, 값이 1과 3은 전경을 나타냅니다. 따라서, mask==2 또는 mask==0인 부분은 배경에 해당하므로 0으로, 그 외의 부분은 전경에 해당하므로 1로 변환하여 반환합니다. 이를 통해 전경 부분을 추출할 수 있습니다.

# 결과 출력
mask2 = np.where((mask==2)|(mask==0), 0, 1).astype('uint8')
result = img * mask2[:, :, np.newaxis]

cv2.imshow('Input Image', img)
cv2.imshow('GrabCut Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

전체코드


// 마우스로 좌표측정방식
import cv2
import numpy as np

# 입력 이미지 로드
img = cv2.imread('input_image.jpg')

# 초기 마스크 생성 (전경, 배경, 불확실한 영역)
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1,65), np.float64)
fgdModel = np.zeros((1,65), np.float64)

# 전경과 배경 지정 (마우스로)
rect = cv2.selectROI(img)

# GrabCut 알고리즘 적용
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)

# 결과 출력
mask2 = np.where((mask==2)|(mask==0), 0, 1).astype('uint8')
result = img * mask2[:, :, np.newaxis]

cv2.imshow('Input Image', img)
cv2.imshow('GrabCut Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

References


1. “GrabCut” — Interactive Foreground Extraction using Iterated Graph Cuts https://cvg.ethz.ch/teaching/cvl/2012/grabcut-siggraph04.pdf

반응형

요약


Non maximal supression(NMS)은 예측된 오브젝트의 바운딩박스(bounding box)가 중복으로 여러 개가 할당 될 수 있을 때, 중복되는 바운딩박스를 제거하는 기술입니다. 기계적으로 무엇인가 학습하여 처리하는 것이 아닌, 사람이 고아낸 로직대로 제거하는 방식이기 때문에, 휴리스틱한(또는 hand-craft)한 방법으로도 논문에서 주로 소개가 됩니다. 

 

Figure 1. 한 오브젝트(포메라니안)에 다수의 바운딩박스가 예측된 예시. NMS은 이러한 여러박스 중에 최적의 박스남기는 작업이다.

 

NMS을 구현하는 방식은 아래와 같습니다.

  1. 각 바운딩 박스별로 면적(area)을 구합니다.
  2. 특정 박스를 하나 정하고, 정해진 박스외에 나머지 박스와의 오버렙 비율을 구합니다. 
    1.  두 박스의 x_min, y_min중에 최대를 구하면, 아래의 박스(//)의 좌상단 좌표를 구합니다.
    2. 두 박스의 x_max, y_max중에 최소를 구하면, 아래의 박스(//)의 우하단 좌표를 구합니다
  3. 교집합의 박스와, 다른 박스들과의 오버렙 비율이 특정값(cutoff)이상을 넘기면, 1에서 지정한 박스를 삭제합니다(=인덱스를 제거합니다)
  4. 이를 계속 반복합니다.

 

 

가령 아래와 같이 COCO 포맷(좌상단X, 좌상단Y, 가로, 세로)의 바운딩박스가 예측되었다고 생각합시다. 그러면, 첫 번째 [317, 86]의 바운딩박스에 대해서, 첫번째 바운딩 박스를 제외하고, 모든 바운딩박스와의 오버렙을 계산하면 됩니다.

// Array
[317, 86, 38, 41],
[464, 90, 40, 42],  # duplicated
[464, 90, 40, 42],  # duplicated

 

구체적인 방법은 아래와 같습니다.

def process_non_maximal_supression(
    boxes: np.ndarray, overlap_cutoff: float = 0.8
) -> np.ndarray:
    """NMS(Non maximal supression)을 시행함.

    Note:
        NMS 프로세스는 아래와 같다.
        1) 각 bounding box별로 Area(면적)을 계산함
        2) 특정 박스 하나에 대해서, 나머지박스와의 Overlap비율을 계산함
         - 각 박스는 (x_min, y_min, w, h)
         - 두 박스의 x_min, y_min중에 최대를 구하면, 아래의 박스(//)의 좌상단 좌표를 구함
         - 두 박스의 x_max, y_max중에 최소를 구하면, 아래의 박스(//)의 우하단 좌표를 구함
            |---------|
            |         |
            |----|------------|
            |    |////|       |
            |----|----|       |
                 |------------|

        3) 오버렙 컷오프(overlap_cutoff)가 주어진 cutoff보다 큰 경우, 2) 에서 선택된
        박스의 인덱스를 삭제
        4) 1~3)을 반복하여 남은 박스만 선택

    Args:
        boxes (np.ndarray): 각 array은 COCO Style box.
        overlap_cutoff (float, optional): _description_. Defaults to 0.7.

    Returns:
        np.ndarray: _description_
    """
    if len(boxes) == 0:
        return list()

    width = boxes[:, 2]
    height = boxes[:, 3]
    areas = width * height

    box_indices = np.arange(len(boxes))
    for box_idx, box in enumerate(boxes):

        # 자기자신을 제외한 다른 박스들 인덱스를 구함
        other_box_indices = box_indices[box_indices != box_idx]

        # 오버렙 계산을 위해 좌표를 구함
        xx1 = np.maximum(box[0], boxes[other_box_indices, 0])
        yy1 = np.maximum(box[1], boxes[other_box_indices, 1])
        xx2 = np.minimum(
            box[0] + box[2], boxes[other_box_indices, 0] + boxes[other_box_indices, 2]
        )
        yy2 = np.minimum(
            box[1] + box[3], boxes[other_box_indices, 1] + boxes[other_box_indices, 3]
        )

        w = np.maximum(0, xx2 - xx1 + 1)
        h = np.maximum(0, yy2 - yy1 + 1)

        overlap = (w * h) / areas[other_box_indices]
        if np.any(overlap > overlap_cutoff):
            box_indices = box_indices[box_indices != box_idx]

    return boxes[box_indices].astype(int)
반응형

요약


Morphological transformation (형태변형)은 이미지의 형태를 변형하는 작업들 중 하나를 말한다. 이미지의 형태를 변형하는 하는 방법은 여러가지가 있는데, 주로 "입력 이미지"와 "구조적 원소(=커널이미지)"을 이용한 경우를 말한다. 그리고 형태변형은 주로 이진화 이미지(Binary image, 흑백이미지으로 0(흑), 1(백)으로만 이뤄져있는 이미지)를 연산하는 것을 의미한다[1]*. 주로 Erosion, Dilation이 주로 연산이며 Open, closing은 이것들의 변형들이다 . 

*주로라는 것은 이진화 이미지가 아닌 경우도 쓸 수 있다(예, grayscale)

 

 

주로 아래와 같은 연산들이 대표적으로 쓰인다. 아래의 글을 봐도 잘 이해가 안되는데, 예시와 함께 보는 것을 권장한다.

  1. Erosion (부식, 침식, Shrink, Reduce): 이미지의 boundary(가장자리)를 부식시키는 작업. 각 오브젝트의 두께가 줄여진다. [2]
  2. Dilation (확장):이미지의 boundary(가장자리)를 확장시키는 작업. 각 오브젝트의 두께가 커진다.
  3. Opening (오프닝): Erosion후에 Dilation을 적용하는 연산. Erosion으로 노이즈 같은 점을 없애주고 이미지를 확장하기에 noise reduction용으로도 쓰는듯하다.
  4. Closing (클로징): Dilation 후에 Erosion을 적용하는 연산. 반면에 Closing은 Object내에 이미지를 패딩해주는효과가 있다.

위의 모든 단계는 커널(kernel, structuring element 또는 probe 라고도 함)을 이용하는데, 이는 입력이미지와 연산할 매개체를 의미한다. 예를 들어, 입력이미지가 A, 연산이 @, 커널이 B면, A@B의 연산을 한다. 주로 @은 위와 같이 종류가 다양하고, B에 해당하는 커널을 바꾸면서도 여러 종류의 연산이 가능하다.

 

 

알아야할 개념: Kernel, Fit, Hit, Miss. 위의 알고리즘을 설명하기 위해서는 아래의 이미지 처리시 개념을 선행해야한다.


  • Kernel: 이미지 연산에 필요한 구조적인 요소(Structuring element). 이미지인데, 연산을 해줄 0또는 1로만 이뤄진 이미지를 의미한다. 입력이미지를 덮어가면서 연산할 이미지를 의미한다[3].
  • Fit: Kernel 이미지의 픽셀이 모든 입력이미지에 커버가 되는 경우.
  • Hit: Kernel 이미지의 픽셀 중 하나라도, 입력이미지에 커버가 되는 경우.
  • Miss: Kernel 이미지의 픽셀 중 하나라도 입력이미지에 커버가 되지 않는 경우.

Input 이미지와 Kernel이 주어진 경우, Hit, Fit, Miss의 예시

 

 

Erode (침식): 주로 오브젝트의 사이즈를 조금 줄일 때 사용


침식은 수학적인 표현으로는 주로 "$\ominus $"라는 표현을할 수 있다. 침식(Erode)은 모든 형태변형 알고리즘 연산과 같이 2개의 값을 요구한다. 하나는 입력이미지(침식 시킬 이미지, $A$), 또 하나는 커널(Kernel, $B$)이다. A를 커널 B로 침식시킨다는 표현은 $A\ominus B$로 표현할 수 있다.

erosion은 다음의 규칙으로 연산한다.

$A\ominus B$: A와 커널 B가 fit이면 1, 아니면 0을 채운다. 이 과정을 kernel의 중앙부가 0인 지점에따라 계산한다.

import cv2 as cv
import numpy as np

image = np.array(
    [
        [0,0,0,0,0],
        [0,1,1,1,0],
        [0,1,1,1,0],
        [0,1,1,0,0],
        [0,1,0,0,0],
    ],
    np.uint8
)

kernel = np.array(
    [[0,1,0],
     [1,1,1],
     [0,1,0]
     ],
     np.uint8
)

cv.erode(image, kernel)

그림으로 표현하면 다음과 같다. 커널의 중심부가 배경(0)인 지점을 모두 순회하면서 hit인 부분이 있으면 모두 0으로 바꿔준다.

그러면 가운데 하나만 1만 남게된다.

array([[0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]], dtype=uint8)

응용 예) 주로 두꺼운 펜으로 작성한 글씨나, 번져서온 이미지인 경우 Erode을 시키면 세밀해진 이미지를 얻을 수 있다.

 

erosion은 다음의 규칙으로 연산한다.

$A\ominus B$: A와 커널 B가 fit이면 1, 아니면 0을 채운다. 이 과정을 kernel의 중앙부가 0인 지점에따라 계산한다.

import cv2 as cv
import numpy as np

image = np.array(
    [
        [0,0,0,0,0],
        [0,1,1,1,0],
        [0,1,1,1,0],
        [0,1,1,0,0],
        [0,1,0,0,0],
    ],
    np.uint8
)

kernel = np.array(
    [[0,1,0],
     [1,1,1],
     [0,1,0]
     ],
     np.uint8
)

cv.erode(image, kernel)

그림으로 표현하면 다음과 같다. 커널의 중심부가 배경(0)인 지점을 모두 순회하면서 hit인 부분이 있으면 모두 0으로 바꿔준다.

그러면 가운데 하나만 1만 남게된다.

array([[0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]], dtype=uint8)

응용 예) 주로 두꺼운 펜으로 작성한 글씨나, 번져서온 이미지인 경우 Erode을 시키면 세밀해진 이미지를 얻을 수 있다.

 

 

Dilation (팽창): 주로 오브젝트보다 약간 큰 사이즈의 이미지를 얻기 위함.


방법: Dliation도 erosion과 마찬가지로, kernel에 hit인 부분을 0으로 바꾸는 대신에, 1로 채워준다. 좀 더 큰 이미지의 영역을 확보하기 위함이다.

응용사례: Pill(알약)의 이미지보다 약간 큰 이미지 영역을 확보하기위해서, 이미지를 이진화(Binarization)시킨 다음, Dilation시켜서 좀더 큰 사이즈의 영역을 확보한다. 이는 약의 그림자 등 조금 큰 영역을 확보해서 Dection하는게 더 성능에 유리하기 때문에 이렇게 진행한다 

Pill Detection Model for Medicine Inspection Based on Deep Learning, ref : https://www.mdpi.com/2227-9040/10/1/4/pdf

 

 

Opening:주로 노이즈를 지우기 위해 사용.


응용 예:  이진화한 이미지에서 작은 점(노이즈)를 지울 때 사용한다. 또는 인공지능이 Segmentation한 영역에서 Background이미지에서 지저분하게 잘못 예측한 영역들을 오프닝으로 지울 수 있다.

 

 

 

Closing: 이미지 내 작은 구멍 및 점들은 채우기 위한 방법으로 사용.


의료영역에서 Closing 사용

Unet의 후속모델인 ResnetUnet을 이용하여 심장부와 폐부를 Segmentation했다. 그리고 그 예측결과 중에, predicted Mask처럼 구멍뚤린 부위를 후처리(Closing)을 이용해서 매꿔주어 성능을 향상시킬 수 있다.

 

 

[1] https://en.wikipedia.org/wiki/Binary_image

[2] https://homepages.inf.ed.ac.uk/rbf/HIPR2/erode.htm 

[3] https://www.cs.auckland.ac.nz/courses/compsci773s1c/lectures/ImageProcessing-html/topic4.htm

[4] https://docs.opencv.org/4.x/d9/d61/tutorial_py_morphological_ops.html

 

 

 

 

 

반응형

 

Grad-CAM의 FeatureMap의 기여도를 찾는 방법은 각 FeatureMap의 셀로판지가 최종 색깔에 어느정도 기여하는지와 동일하다.

 

요약


Grad-CAM은 CNN 기반의 모델을 해석할 때 사용되는 방법이다. 인공지능의 해석방법(XAI)에서는 Grad-CAM은 흔히 Post-hoc으로 분류되고(일단, 모델의 결과(Y)가 나오고 나서 다시 분석사는 방법)으로 취급된다. 또한, 딱 CNN에서만 사용되기 때문에 Model-specific 방법이다. 이 Grad-CAM의 가장 큰 쉬운 마지막 CNN이 나오고 나서 반환되는 Feature Map(본문에서:A)이 평균적으로 Y의 분류에 어느정도 되는지를 계산하고, 각 픽셀별로 이를 선형으로 곱하는 방법이다.

 

 

상세 내용


Grad-CAM은 아래와 같이 계산할 수 있다. 

식1: Grad-CAM의 핵심적인 계산과정

 

각, 기호에 대한 설명은 아래와 같다. y은 모델이 내뱉는 확률이며, c는 특정 라벨을 의미한다. 개 vs 고양이의 분류기이면 c의 최대값은 2이 된다(=1, 2의 분류). 그 다음, $A^k$은 각 Feature Map을 의미한다. Feature Map은 이미지가 CNN을 통과한 결과를 의미한다. $k$가 붙는 이유는 CNN을 통과하고나서 CNN의 필터의 개수(k)만큼 반환되는 값(feature map)의 k채널이 생기기 때문이다. 일종의 채널과 같다. 

마지막으로 $a_{k}^{c}$은 $A^{k}$의 필터맵이 이미지와 유사한데, 이 필터맵의 각 픽셀들이 분류에 어느정도 기여했는지를 의미한다. 아래의 그림(Figure 1)을 보자. Feature maps이 4개가 있으니 $k=4$인 예시이다. 녹색의 feature maps을 보면, 5x5로 이미지가 이루어져 있는데, 각 $i, j$에 해당하는 픽셀이 $y^{c}$에 어느정도 기여했는지를 구한 것이다.

 

Figure 1. GAP의 그림

즉, Grad-CAM은 각 feature Map이 어느정도 모델의 결과값($y$)에 기여했는지($A^{k}$)와 각 픽셀이 모델의 결과값에 어느정도 기여했는지($a_{k}^{c}$)을 곱하는 것이다. 결국, 각 픽셀이 feature maps을 고려하였을 때(가중하였을 때) 결과값에 어느정도 영향을 미쳤는지를 계산할 수 있다.

 

구현:


torch에서는 register_foward_hook과 register_backward_hook을 이용하여 grad cam을 계산할 수 있습니다. register_forward_hook과 register_backward_hook은 PyTorch에서 모델의 레이어에 대한 forward pass와 backward pass 중간에 호출되는 함수를 등록하는 메서드입니다. 이를 통해 레이어의 활성화 맵이나 그래디언트를 추출하거나 조작할 수 있습니다.

register_forward_hook:
이 메서드는 모델의 레이어에 대해 forward pass가 수행될 때 호출되는 함수를 등록합니다. 등록된 함수는 해당 레이어의 출력을 인자로 받아 다양한 작업을 수행할 수 있습니다. 주로 활성화 맵 등 중간 결과를 추출하거나 조작하는 데 사용됩니다.

def forward_hook(module, input, output):
    # module: 레이어 인스턴스
    # input: forward pass의 입력
    # output: forward pass의 출력
    pass

target_layer.register_forward_hook(forward_hook)

 

register_backward_hook:
이 메서드는 모델의 레이어에 대해 backward pass가 수행될 때 호출되는 함수를 등록합니다. 등록된 함수는 해당 레이어의 그래디언트를 인자로 받아 다양한 작업을 수행할 수 있습니다. 주로 그래디언트를 조작하거나 특정 그래디언트 정보를 추출하는 데 사용됩니다.

def backward_hook(module, grad_input, grad_output):
    # module: 레이어 인스턴스
    # grad_input: 입력 그래디언트
    # grad_output: 출력 그래디언트
    pass

target_layer.register_backward_hook(backward_hook)

 

위의 두 함수를 이용하여 아래와 같이 구현할 수 있습니다.

def grad_cam(
    model: torch.nn.Module,
    image: np.ndarray,
    target_layer: torch.nn.Module,
) -> np.ndarray:
    """
    Args:
        model (torch.nn.Module): Grad-CAM을 적용할 딥러닝 모델.
        image (np.ndarray): Grad-CAM을 계산할 입력 이미지.
        target_layer (Type[torch.nn.Module]): Grad-CAM을 계산할 대상 레이어.

    Returns:
        np.ndarray: Grad-CAM 시각화 결과.
    """

    def forward_hook(module, input, output):
        grad_cam_data["feature_map"] = output

    def backward_hook(module, grad_input, grad_output):
        grad_cam_data["grad_output"] = grad_output[0]

    grad_cam_data = {}
    target_layer.register_forward_hook(forward_hook)
    target_layer.register_backward_hook(backward_hook)

    output = model(image)  # 모델의 출력값을 계산합니다. y_c에 해당
    model.zero_grad()

    # 가장 예측값이 높은 그레디언트를 계산합니다. output[0,]은 차원을 하나 제거
    output[0, output.argmax()].backward()

    feature_map = grad_cam_data["feature_map"]
    grad_output = grad_cam_data["grad_output"]
    weights = grad_output.mean(dim=(2, 3), keepdim=True)
    cam = (weights * feature_map).sum(1, keepdim=True).squeeze()
    cam = cam.detach().cpu().numpy()

    return cam
반응형

+ Recent posts