Grad CAM 에 대한 이해

Topic Proposal : Option II


멤버

노창호 전자공학부 sshckdgh0889@naver.com

류다윤 경영학과 ryudy4977@naver.com

신동현 전자공학부 donghyun3966@naver.com

김창성 전자공학부 kcs933442@naver.com


목차

1 글소개

2 배경지식(Convolution, Backpropagation 등등)

3-1 CAM 정의 및 개요

3-2 CAM 계산

3-3 CAM의 문제점

4-1 Grad CAM 정의 및 개요

4-2 Grad CAM 계산

4-3 CAM과 Grad CAM 차이

5 예제 코드

6 참고문헌

7 맡은 파트









1.글소개

AI+X:딥러닝 수업에서 Image Classification하는 CNN(Convolutional Neural Network) 모델들을 직접 실행시키며 실습을 했습니다. 저희는 이러한 CNN 모델들이 어떻게 사진들을 보고 고양이나 강아지처럼 카테고리를 나누는지에 대한 궁금증이 생겼습니다. 이러한 궁금증을 해결하기 위해서 여러 솔루션들이 제안되고 있습니다.

최근 발표된 Selvaraju RR 등의 Grad CAM (Gradient-weighted Class Activation Mapping)은 CNN 모형에 적용할 수 있는 모형 해석 방법론입니다. 쉽게 설명하면 CNN 층(마지막)에 들어가는 Gradient를 가지고 자료의 어느 부분에 가중치를 주는지 계산하는 방법이 될 것 같습니다. 별다른 구조 변경이나 재훈련 없이 바로 적용해볼 수 있다는 점, CNN이 사용되는 영역이 무척 넓다는 점이 매력적이어서 여러 솔루션 중 Grad CAM을 선정하였습니다. 

 이 글에서는 CNN모델이 Image Classification을 어떻게 하는지를 알기 위해 CAM이란 무엇이며 Grad CAM과 CAM의 알고리즘 차이를 통해서 소개를 하고, 이미 학습된 CNN모델에 Grad CAM적용시켜 어떤 결과가 나오는지를 확인하고자 합니다.


2.배경지식


CAM 과 Grad-CAM에대한 본격적인 설명에 앞서 뒤의 내용들을 이해하기 전에 언급될 기초적인 지식에 대해 미리 알고간다면 이해가 쉬울것입니다. 이 파트에서는 배경지식(Convolution, Pooling, Zero-Padding, Backpropagation) 에대해 말하고자 합니다.


1) Convolution (합성곱)


Convolution(합성곱)은 CNN(Convolutional Neural Network)알고리즘에 사용되기 이전부터 영상처리(image processing)분야에서 사용되던 연산 기법입니다. 우선 이미지의 픽셀마다 존재하는 벡터값과 같은 위치에 존재하는 Mask(Filter, Kernel)값을 곱하고 이 값을을 모두 합한뒤 다시 Mask가 위치했던 부분의 중앙에 값을 대입한다. 그리고 이과정을 반복하여 feature map을 만들어내는 기법입니다.






그림의 과정은 input에 image를 입력하여 픽셀단위로 값을 부여한뒤 Mask(Filter, Kernel)를 합성곱하여 feature map을 만들어내는 과정입니다. 이 과정에서 Mask(Filter, Kernel)의 역할을 이해하기 쉽게 비유하자면 neural network에서 가중치와 같은 역할을 수행한다고 보아도 무방합니다.


 2) Zero-Padding(패딩)

 

위와 같은 과정을 거쳐서 feature map을 구성하게 되면 feature map의 크기가 input의 image보다 작아지게 되는데 이때 Convolution 레이어의 출력데이터가 줄어드는 것을 방지하기 위해서 사용되는 방법이 패딩(Padding)입니다. 



패딩은 그림과 같이 입력데이터의 외각에 지정된 픽셀만큼 특정데이터 값을 채워 넣는 것으로 이과정을 통해 feature map의 데이터값이 input값과 동일해집니다.


3) Pooling(풀링)


풀링은 Convolution레이어의 출력데이터를 입력으로 받아서 출력 데이터 (Activation map)의 크기를 줄이거나 특정 데이터를 강조하는 용도로 사용되는 과정입니다. 


풀링의 종류에는 Max Pooling, Average Pooling, Min Pooling이 있으며 지정된 픽셀의 영역의 값을 어떤식으로 처리하여 해상도를 낮추는지에 따라 종류가 구별됩니다. Max Pooling은 영역의 값중 최댓값을 최종값으로 처리하고 Average Pooling, Min Pooling은 각각 평균값, 최소값을 최종값으로 처리합니다. 

이 과정을 진행하면 이미지의 계산량이 줄어들고 메모리 공간이 줄어들어 효율적으로 데이터를 처리할수 있게되며 Shifted invariant 이미지를 처리하는 것이 가능해집니다.


4) Backpropagation(역전파)


역전파란 ANN(Artificial Neural Network)을 학습시키기 위한 기본적인 알고리즘으로 내가 뽑고자하는 target값과 실제모델이 계산한 output값이 얼마만큼 차이가 나는지 오차값을 구하고 그 오차값을 다시 뒤로 전파해 나아가면서 각 노드들의 변수값을 갱신해 나아가는 알고리즘입니다.


역전파 알고리즘의 순서는 다음과 같습니다. input값을 입력하여 가중치와 활성화 함수들을 거쳐서 정상적으로 output값을 출력하는 단계를 Feedfoward라 합니다. Feedfoward단계에서 출력된 output값과 목표로 하는 taget값 사이의 오차를 구한뒤 출력층에서 입력층 방향으로 경사하강법(Gradient descent method)을 사용하여 오차를 최소화하는 가중치w값을 찾아내 업데이트시키는 단계를 Backpropagation라 합니다. 




이러한 과정을 통해 효율적으로 가중치를 업데이트 해나갈수 있기 때문에 널리 사용되는 역전파 이지만 한계가 존재합니다. 바로 경사하강법(Gradient descent method)에서 오는 한계인데, 경사하강법 알고리즘은 항상 전역 최소값(global minimum)을 찾는다고 보장할수 없습니다. 극소값이 2개 이상 존재하는 함수에 대해 가장 작은 최소값을 찾는다고 할수 없는 것입니다.  


 3-1 CAM의 정의 및 개요

 

 CAM(Class Activation Map)은 CNN을 하는데 있어 이미지의 어떤 부분이 결정에 가장 큰 영향을 주는지에 대해 분석하는 목적으로 시작되었습니다. 대부분의 Image Classification 모델들은 여러 층의 Convolutional layer을 걸친 뒤 Fully-Connected Layer를 통해서 Classification을 진행하게 됩니다. 하지만 Convolutional layer는 layer를 거친 뒤에서 spatial information을 보존하지만 Fully-Connected Layer는 flatten 과정을 거치게 되면 spatial information의 손실이 발생하게 됩니다. 그래서 CAM은 Convolutional layer를 거친 후  Fully-Connected Layer를 바로 사용하고 GAP을 사용한 후 마지막 하나의 Fully-Connected Layer를 사용하였습니다. 여기서 CAM은 기존과 다르게  GAP(Gloval Average Pooling)을 적용시켜 마지막 판별 전까지 데이터 위치 정보가 훼손되지 않게 하였습니다. 

 예를 들자면, 우리는 CNN이 '개'의 이미지를 보고 '개'라고 예측을 할 때, 어느 부분을 보고 그 이미지를 '개'라고 판정을 했는지에 대해 궁금한 것 입니다. CAM이란 특정 분류의 이미지를 어떠한 분류라고 예측하게 한 그 이미지의 부분의 정보를 의미합니다.



위 사진을 보면 기본적인 구조는 Network in Networkdhk Goolenet과 비슷하지만 차이점을 보자면 CAM은 Conv Layer을 Fc-Layer로 납작하게 하지 않고, GAP을 통해 새로운 Weight값을 만들어 내고 있는 것입니다.  마지막 Conv Layer가 총 n개의 channel로 이루어져 있다면 각각의 채널들은 GAP에 의해 하나의 Weight 값으로 나타나게 되며, 총 n개의 Weight들이 생기게 됩니다. 마지막 Softmax 함수로 인해 연결되어 Weight들도 백프롭을 통해 학습시키는 것입니다. N 개의 Weight가 생겼다면 CAM은 이 Weight들과 마지막 n개의 Conv Layer들과 Weighted Sum을 하여 하나의 특정 분류의 이미지의 히트맵이 나오게 합니다. 위 사진의 아래쪽 사진을 보면 히트맵을 확인할 수 있는데 강아지의 얼굴부분의 중요도가 가장 큰 사실을 확인 할 수 있습니다.



3-2 CAM 계산




GAP(Global Average Pooling)방식을 적용하면 
위의 그림에서와 같이  채널에서  별로 average 하나의 값만을 가져오게 됩니다 이후에 FC 적용하는데 이때의 weight값이 CAM 만드는 w1, w2, w3, ... 값이 됩니다FC weight [n_Ch, n_classes] 크기를 갖는 matrix인데, FC matrix 클래스index  벡터가 각각 w1, w2, wn,... 됩니다.(*n_classes 분류하려는 객체의  weight들을 마지막 feature map 각각 곱한 후에 더해주면 CAM 됩니다.

이를 수식으로 더 자세히 표현해봅시다. 마지막 Conv layer를 거쳐서 얻은 feature map을 fk 라고 하고, k번째 채널의 값들 중 (x,y)에 위치한 값을 라고 표현합니다. 그렇다면 GAP를 거쳐 그림에서 각 원 하나에 해당하는 것, 는 x,yfk(x,y)가 됩니다. 이를 softmax에 집어넣기 위해 FC를 하나 추가해주고, 이 때의 weight들을 라고 합니다. 이것은 본질적으로 의 중요성을 나타내며 그 크기가 클수록 c에서 가 미치는 영향이 커지게 됩니다. class c에 대해서 softmax에 입력으로 주어지는 값, 는 가 됩니다. 식을 조금 더 변형하면 



가 됩니다. 



위의 사진을 식을 이용해 계산하여 CAM의 결과를 보면 빨간색에 가까울수록 수치가 크고 파란색에 가까울수록 수치가 작아지게 됩니다. 왼쪽의 사다새, 오른쪽의 오처드 오리올의 히트맵사진을 보게되면 물체의 위치를 따로 학습시키지 않고 classification task에 대해서만 학습해도 CAM이 실제 새의 위치를 측정하는 것을 확인할 수 있습니다.


3-3 CAM의 문제점

하지만 CAM 방법론은 GAP가 반드시 필요하다는 것이 가장 큰 단점입니다. GAP가 이미 포함되어 있는 경우는 괜찮지만 그렇지 않은 경우에는 마지막 convolutional layer 뒤에 GAP를 붙여서 다시 fine-tuning 해야 하며 그 결과 약간의 성능 감소가 동반될 수 있습니다. 따라서 GAP 사용하지 않고 CNN모델 마지막 feature값과 연결   있는 weight 활용할  있는 방법이 무엇일지에 대한 고민 끝에 나온 방법론이 Grad_CAM입니다.



4-1 GRAD-CAM의 정의 및 개요


CAM의 경우에는 활성화 맵을 얻기 위해서 GAP을 사용하고 그 뒤 Fully-Connected Layer를 사용하는 방식입니다. 또한 결과물을 CNN으로만 시각화 할 수 있습니다. 이러한 점을 보안하기 위해 Gad-CAM(Gradient CAM)은 이러한 모델의 구조가 제한되는 문제를 해결하고 다양한 모델의 구조를 해석할 수 있는 방법을 제안합니다. Grad-CAM을 사용하게 되면 CNN 기반의 네트워크는 굳이 모델 구조를 변경할 필요가 없으며, 분류 문제 외의 다른 테스크들을 유연하게 대처할 수 있습니다.


위의 이미지는 개와 고양이에 대해 Guided Backprop, Grad-CAM, Occlusion map의 시각화를 비교하는 이미지입니다. 여러 방법 중 Grad-CAM은 CAM이 가장 큰 분별력을 가지게 하는 것에 중점을 두었습니다. 


 위의 사진은 Grad-CAM의 전체적인 구조를 보여주는 사진입니다. 그림의 왼쪽에는 모델의 구조를 볼 수 있습니다. 위 과정은 처음 Original Image를 입력하여 CNN을 걸쳐 특정 맵을 추출하고 그 뒤에 테스크에 따라 여러 종류의 레이어들이 사용됩니다. 
 
 위 사진의 오른쪽의 'Image Classification', 'Image captioning', 'Visual Question Answering'은 Grad-CAM이 적용할 수 있는 다양한 컴퓨터의 버전의 문제를 설명하는 것 입니다. Image Classification는 이미지 분류로써 한장의 이미지를 알고리즘에 입력해주면, 그 이미지가 어떤 분류의 라벨에 속하고 있는지 알려주는 테스크입니다. Image captioning(이미지 캡셔닝)은 이미지를 보고 적절한 설명을 자동으로 붙이는 테스크이며, Visual Question Answering즉 프로젝트 시각적 질의 응답는 'VQA'라고도 하는데 이는 사진을 보고 다양한 질문에 답을 할 수 있는 시각언어 멍티 모달 체계이자 다랼한 종류의 질문에 대해 답을 하는 멀티테스팅입니다.

4-2 Grad- CAM알고리즘


위 그림은 논문(Grad-CAM: Visual Explanations from Deep Networks via Gradient- based Localization)에 나온 대략적인 Grad-CAM의 구조입니다. 구조를 살펴보면 Classification 문제에서 Backprop을 통해 gradient 값들을 얻어 Grad-CAM을 얻게 됩니다. 이때 얻는 gradient 값은 softmax 전 단계의 클래스에 대한 특징 맵에서 얻게 되는데 이를 식으로 쓰면 다음과 같습니다.




첫 번째 식에서 A는 Softmax의 전단계 k 번째 클래스에 대한 특징 맵을 뜻하고, y는 각 클래스에 대한 Score를 뜻합니다. 결국 첫 번째 식이 의미하는 것은 특징 맵 A에 대한 y의 gradient를 전부 합산한 값이 a이고 이는 CAM에서의 global average pooling을 한 값과 동일하다는 것입니다. 

두 번째 식을 보면 CAM과 동일하게 모든 클래스에 대한 a와 특징 맵을 곱한 뒤 더하면 Grad-CAM을 구할 수 있습니다. 여기서 활성화 함수인 ReLU가 들어가는데 이는 클래스의 interest에서 양의 값에만 관심이 있기 때문입니다. 


4-3 CAM과 Grad-CAM의 차이



ReLu를 제외하고 CAM과 Grad-CAM의 수식을 비교해 보면 w와 a의 차이에서 기인합니다. 우선 CAM의 w는 GAP에서 얻을 수 있는 Softmax layer의 weight값이고, Grad-CAM의 a는 Softmax 함수의 input값의 피쳐맵에 대한 편미분값을 구해 GAP방식으로 출력한 결과입니다.

하지만 아래의 수식을 보면 Grad-CAM의 a는 CAM의 w와 동일한 값을 얻을 수 있습니다. 결국에는 Grad-CAM의 수식은 CAM에서 얻은 수식을 일반화 시킨 값으로 GAP을 사용하지 않는 네트워크 모델에도 CAM과 동일한 결과가 나오도록 적용 할 수 있는 장점이 있습니다.

Grad-CAM 수식





5 예제 코드

이번에는 Image Classification에 널리 사용되고 있는 ResNet50에 Grad-CAM을 적용시킨 실제 코드를 가지고 CAM을 구하는 과정과 결과를 확인해 보겠습니다. ResNet50 Model은 Pytorch에서 기본적으로 제공되는 Model을 사용했고, ResNet50의 구조는 다음과 같이 생겼습니다.





Grad-CAM에서 사용되는 마지막 Layer를 확인해 보면 2048개의 채널을 가진 ( 7 x 7 )의 Size로 featuremap을 만들어 냅니다. Grad-CAM은 Output으로 가장 크게 나온 클래스에 해당하는 2048개의 gradient 값을 모든 7x7 사이즈의 featuremap에 곱해서 더해주기만 하면 구현할 수 있습니다.  


if __name__ == '__main__':
args = get_args()
model = models.resnet50(pretrained=True)
grad_cam = GradCam(model=model, feature_module=model.layer4, \
target_layer_names=["2"], use_cuda=args.use_cuda)
img = cv2.imread(args.image_path, 1)
img = np.float32(cv2.resize(img, (224, 224))) / 255
input = preprocess_image(img)
target_index = None
mask = grad_cam(input, target_index)
cam = show_cam_on_image(img, mask)
cv2.imwrite("cam.jpg", np.uint8(255 * cam))



model = models.resnet50(pretrained=True)
Pytorch의 기본적으로 내장되어 있는 resnet50 model을 불러옵니다.


grad_cam = GradCam(model=model, feature_module=model.layer4,target_layer_names=["2"], use_cuda=args.use_cuda)
GradCam()이라는 클래스를 grad_cam에 상속시켜줍니다. 이렇게 하면 grad_cam을 이용해 GradCam() 클래스 안에 있는 함수나, 상수들을 불러올 수 있습니다.


이번에는 클래스 GradCam()를 살펴보겠습니다.


class GradCam:
    def __init__(self, model, feature_module, target_layer_names, use_cuda):
        self.model = model
        self.feature_module = feature_module
        self.model.eval()
        self.cuda = use_cuda
        if self.cuda:
            self.model = model.cuda()

        self.extractor = ModelOutputs(self.model, self.feature_module, target_layer_names)

    def forward(self, input):
        return self.model(input)

    def __call__(self, input, index=None):
        if self.cuda:
            features, output = self.extractor(input.cuda())
        else:
            features, output = self.extractor(input)

        if index == None:
            index = np.argmax(output.cpu().data.numpy())

        one_hot = np.zeros((1, output.size()[-1]), dtype=np.float32)
        one_hot[0][index] = 1
        one_hot = torch.from_numpy(one_hot).requires_grad_(True)
        if self.cuda:
            one_hot = torch.sum(one_hot.cuda() * output)
        else:
            one_hot = torch.sum(one_hot * output)

        self.feature_module.zero_grad()
        self.model.zero_grad()
        one_hot.backward(retain_graph=True)

        grads_val = self.extractor.get_gradients()[-1].cpu().data.numpy()

        target = features[-1]
        target = target.cpu().data.numpy()[0, :]

        weights = np.mean(grads_val, axis=(2, 3))[0, :]
        cam = np.zeros(target.shape[1:], dtype=np.float32)

        for i, w in enumerate(weights):
            cam += w * target[i, :, :]

        cam = np.maximum(cam, 0)
        cam = cv2.resize(cam, input.shape[2:])
        cam = cam - np.min(cam)
        cam = cam / np.max(cam)
        return cam






처음에 클래스를 호출하여 객체에 상속시킬 때 __init__()을 실행하게 됩니다. 각각의 argument들을 클래스 내부 함수에서 사용할 수 있도록 self 인자에 저장을 해줍니다.

여기서 model 이란 Grad-CAM을 사용할 Model로서 저희는 Resnet50이 들어옵니다. feature_module은 Grad-CAM을 구하기 위해 필요한 Model의 마지막 레이어가 들어오고, target_layer_names는 feature_module에서 마지막 Bottleneck의 이름이 들어오게 됩니다. 마지막으로 use_cuda는 GPU를 사용할지에 대한 정보입니다. 

self.extractor = ModelOutputs(self.model, self.feature_module, target_layer_names)는 마지막 Bottleneck에 저장되어 있는 마지막 Conv(1x1x2048x7x7)의 gradient와 softmax를 통과한 각각의 클래스를 예측하는 값 Output을 구해오는 함수로 사용하게 됩니다.






__call__()는 클래스를 상속받은 객체를 호출할 때 실행하는 함수입니다. features와 outpur에는 각각 마지막 Conv의 gradient와 prediction한 output값들이 들어가고, index는 output의 가장 큰 값의 클래스 번호가 들어가게 됩니다. 만약에 Model이 Input Image를 강아지라고 인식하게 되면 강아지라는 클래스의 번호가 index에 들어갑니다. 






one_hot은 Model이 예측한 클래스가 들어가는데 zero_grad()와 backward(retain_graph=True)는 내부 버퍼가 변화하는 것을 막아줍니다.(모델 학습 방지)






위에서 설명했던 Grad-CAM을 적용하는 부분입니다. 



중간에 있는 for 문을 살펴보면 target이 마지막 conv를 통과한 feature(A)이고, w가 가중치(a)입니다. 2048개의 target과 w를 각각 곱해서 더해주고 이를 np.maximum(cam, 0)을 사용해 ReLU와 같은 효과를 줍니다. 그 이후 resize를 사용해 Input Image와 동일한 크기로 맞춘 후 마지막으로 cam - np.min(cam)과 cam / np.max(cam)을 통해 normalization을 해주면 Grad-CAM이 나오게 됩니다.



-결과





6 참고문헌 7맡은 파트

2.배경지식 

1. Convolution, Zero-Padding, Pooling

 블로그 자료

- http://taewan.kim/post/cnn/    

- https://gruuuuu.github.io/machine-learning/cnn-doc/#

- https://untitledtblog.tistory.com/150

 youtube자료

DeepLearning.ai 채널 강의 (Pooling)

- https://www.youtube.com/watch?v=8oOgPUO-TBY


2. Backpropagation

블로그 자료

- https://blog.naver.com/samsjang/221033626685

- https://goofcode.github.io/back-propagation


3-1 CAM의 배경지식 및 개요

블로그 자료

https://poddeeplearning.readthedocs.io/ko/latest/CNN/CAM%20-%20Class%20Activation%20Map/

https://jays0606.tistory.com/4

https://www.secmem.org/blog/2020/01/17/gradcam/

https://dryjelly.tistory.com/147

 

3-2 CAM의 계산, 3-3 CAM의 문제점

https://minimin2.tistory.com/39

https://kangbk0120.github.io/articles/2018-02/cam

https://www.secmem.org/blog/2020/01/17/gradcam/

http://dmqm.korea.ac.kr/activity/seminar/274


4-1 Grad-CAM의 배경지식 및 개요

블로그 자료

https://butter-shower.tistory.com/181

https://www.secmem.org/blog/2020/01/17/gradcam/

https://bskyvision.com/413

https://bskyvision.com/639    

https://www.skt.ai/kr/projects/detail.do?cd=AAAACC

참조: https://arxiv.org/abs/1610.02391


4-2 Grad-CAM 알고리즘, 4-3 CAM과 Grad-CAM의 차이점, 5 예제 코드

참고문헌
블로그 - https://dydeeplearning.tistory.com/10
            https://bskyvision.com/644
코드 - https://github.com/jacobgil/pytorch-grad-cam
논문 - Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization <<Ramprasaath R. Selvaraju · Michael Cogswell · Abhishek Das · Ramakrishna Vedantam · Devi Parikh · Dhruv Batra>> 2019

7맡은 파트

김창성: 2 배경지식 (Convolution, Zero-Padding, Pooling, Backpropagation)
신동현: 3-1 CAM의 정의 및 개요, 4-1 Grad-CAM의 정의 및 개요, 블로그 제작
류다윤: 3-2 CAM의 계산, 3-3 CAM의 문제점, 동영상 제작
노창호: 4-2 Grad-CAM의 계산, 4-3 CAM과 Grad CAM 차이, 5 예제 코드


댓글