본문 바로가기
AI Naver boost camp/[Week 04] CV 1

Class Activation Mapping (CAM)과 Grad-CAM

by AI-BT 2024. 8. 29.
728x90
반응형

Class Activation Mapping (CAM)Gradient-weighted Class Activation Mapping (Grad-CAM)은 딥러닝 모델,
특히 CNN(Convolutional Neural Network)의 예측 과정을 시각화하는 강력한 도구들입니다.
이 방법들은 모델이 이미지의 어느 부분을 보고 특정 클래스를 예측했는지 시각적으로 이해할 수 있게 도와줍니다.

1. Class Activation Mapping (CAM)

CAM은 특정 클래스에 대한 예측에 기여하는 이미지의 영역을 시각화하는 방법입니다.
이 방법은 일반적으로 모델의 마지막 컨볼루션 레이어와 완전 연결(FC) 레이어 사이에서 이루어집니다.
CAM의 기본 아이디어는 각 클래스에 대해, 모델이 어느 영역에 주목했는지를 확인하는 것입니다.

CAM의 작동 방식

  • 모델 구조 변경
    CAM을 사용하려면 모델 구조를 약간 변경해야 합니다. 특히, 전통적인 완전 연결 레이어를
    글로벌 평균 풀링(Global Average Pooling, GAP) 레이어로 대체합니다.

  • Class Activation Map 생성:
    GAP 레이어와 그 뒤에 이어진 FC 레이어의 가중치로부터 각 클래스에 대한 활성화 맵을 생성합니다.
    이 맵은 이미지의 특정 부분이 모델의 예측에 얼마나 기여했는지를 나타냅니다.

CAM의 한계

  • CAM은 모델 구조를 수정해야 하며, 기존의 잘 학습된 모델에는 적용하기 어렵습니다.
  • 또한, 마지막 컨볼루션 레이어와 FC 레이어 사이의 정보만을 시각화할 수 있기 때문에,
    보다 세밀한 시각화에는 적합하지 않을 수 있습니다.

2. Gradient-weighted Class Activation Mapping (Grad-CAM)

Grad-CAM은 CAM의 확장된 버전으로, 모델 구조를 변경하지 않고도 활성화 맵을 생성할 수 있는 방법입니다.
Grad-CAM은 이미지에서 특정 클래스에 대한 활성화 맵을 계산할 때, 해당 클래스의 그라디언트(gradient)를 이용합니다. 이 방법은 기존에 학습된 모델에도 적용할 수 있으며, 더 많은 유연성을 제공합니다.

Grad-CAM의 작동 방식:

  • 모델의 마지막 컨볼루션 레이어 선택
    Grad-CAM은 모델의 마지막 컨볼루션 레이어를 사용하여, 그 레이어의 출력을 기반으로 활성화 맵을 생성합니다.

  • Gradient 계산
     특정 클래스에 대한 출력의 그라디언트를 계산하여, 각 특징 맵의 중요도를 측정합니다.

  • 가중치 합산 
    각 특징 맵을 그 중요도에 따라 가중치합을 수행하여 최종 Class Activation Map을 생성합니다.
  • ReLU 적용 
    음수의 기여도를 제거하기 위해 ReLU 함수를 적용하여 최종적으로 양수의 영역만 강조합니다.

 


3. Grad-CAM 적용 코드

import torch
import torch.nn.functional as F
from torchvision import models, transforms
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
from torchvision.models import VGG16_Weights
import os

# 사전 학습된 모델(VGG16)을 로드하고 평가 모드로 설정
model = models.vgg16(weights=VGG16_Weights.IMAGENET1K_V1)
model.eval()

# 이미지를 전처리하는 함수
preprocess = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.Lambda(lambda img: img.convert("RGB")),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

# 이미지를 로드하고 전처리
img = Image.open('./tiger.jpg')
input_tensor = preprocess(img)
input_batch = input_tensor.unsqueeze(0)  # 배치 차원을 추가


# Grad-CAM을 계산하기 위해 마지막 컨볼루션 레이어의 출력을 추출
def get_activation(model, input_batch):
    activation = None

    def hook(model, input, output):
        nonlocal activation
        activation = output
        activation.retain_grad()  # 이 위치에서 retain_grad() 호출
    layer = model.features[-1]  # VGG16의 마지막 컨볼루션 레이어
    hook_handle = layer.register_forward_hook(hook)
    output = model(input_batch)
    hook_handle.remove()
    return activation, output


# 그라디언트를 추출하고 Grad-CAM 맵을 생성
def compute_gradcam(activation, model_output, target_class):
    model_output[:, target_class].backward(retain_graph=True)  # 타겟 클래스에 대한 그라디언트 계산
    gradients = activation.grad  # 그라디언트 추출
    pooled_gradients = torch.mean(gradients, dim=[0, 2, 3])  # 채널별로 평균
    for i in range(activation.shape[1]):
        activation[:, i, :, :] *= pooled_gradients[i]  # 가중 합산
    gradcam = torch.mean(activation, dim=1).squeeze()  # 채널 평균
    gradcam = F.relu(gradcam)  # ReLU 적용
    return gradcam


# 모델의 출력을 계산하고 타겟 클래스를 설정
activation, output = get_activation(model, input_batch)
target_class = output.argmax(dim=1).item()

# Grad-CAM 맵 계산
gradcam = compute_gradcam(activation, output, target_class)

# Grad-CAM 맵을 시각화
gradcam = gradcam.detach().numpy()
gradcam = np.maximum(gradcam, 0)  # ReLU를 다시 적용 (혹시 모를 음수 제거)
gradcam = gradcam / gradcam.max()  # 정규화

# 원본 이미지와 Grad-CAM을 오버레이하여 시각화
gradcam = Image.fromarray(np.uint8(gradcam * 255), 'L')
gradcam = gradcam.resize((img.size), Image.Resampling.LANCZOS)
gradcam = np.array(gradcam)

plt.imshow(img)
plt.imshow(gradcam, alpha=0.5, cmap='jet')
plt.axis('off')
plt.show()

 

 

결과

 

빨간색 영역 
모델이 가장 주목한 부분입니다. 이 영역이 예측에 가장 큰 영향을 미쳤으며, 모델이 이 부분을 통해 클래스를 결정했다고 볼 수 있습니다.

 

노란색과 녹색 영역
이 부분도 예측에 기여했지만, 빨간색 영역보다는 덜 중요하게 작용했습니다.

 

파란색 영역
모델이 상대적으로 덜 주목하거나 거의 주목하지 않은 부분입니다.

 

 

 

728x90
반응형

댓글