개발 환경
os : linux
python == 3.10.12
torch == 2.1.0
torchvision == 0.16.0
grad-cam ==1.5.2
opencv-python == 4.8.0.74
Classification 프로젝트를 진행하면서 각 Class가 이미지의 어떤 부분의 보고 분류되었는지 시각적으로 나타낼 수 있다.
출처 : Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization (2019)
위 이미지와 같이 히트맵을 통해 모델이 이미지의 어느 부분을 보고 특정 클래스로 분류했는지를 시각적으로 알 수 있게 하는 XAI(eXplainable Artificial Intelligence) 기법이다.
현재 CAM 기법은 Grad-CAM, Ablation-CAM, Score-CAM,Grad-CAM++, Eigen-CAM 등 다양한 변형 기법이 존재한다.
이 글에서는 이론적인 부분보다는 파이토치 환경에서 사용하는 방법에 대해 알아본다.
grad_cam GitHub
https://github.com/jacobgil/pytorch-grad-cam
현재 CAM 시리즈는 파이썬 라이브러리로 제공되고 있다.
pip install grad-cam ==1.5.2
pip install opencv-python == 4.8.0.74
grad-cam 라이브러리 설치 후 라이브러리 import에서 cv2 충돌 오류가 발생한다.
grad-cam에 호환되는 opencv 버전을 찾아 해결해주었다.
주로 모델의 Evaluate 과정에서 사용하기 때문에 학습 중 Validation Dataset 평가 코드에 사용하거나 Test Data의 평가 코드에 부가적으로 사용하면된다.
import torch
import matplotlib.pyplot as plt
import seaborn as sns
from pytorch_grad_cam import GradCAM, AblationCAM, ScoreCAM, GradCAMPlusPlus, XGradCAM, EigenCAM
from pytorch_grad_cam.utils.image import show_cam_on_image
import os
import numpy as np
from PIL import Image
def evaluate(parameters...):
model.eval()
correct = 0
total = 0
# 마지막 컨볼루션 레이어 이름
target_layers = [model.LastLayerName]
cam = GradCAM(model=model, target_layers=target_layers)
with torch.no_grad():
...모델 평가 코드 ...
# Grad-CAM을 위해 requires_grad를 True로 설정
for idx, (inputs, labels) in enumerate(test_loader):
inputs, labels = inputs.to(device), labels.to(device)
inputs.requires_grad = True
outputs = model(inputs)
# Grad-CAM 시각화 1채널 데이터 사용
grayscale_cam = cam(input_tensor=inputs, targets=None)
for i in range(inputs.size(0)):
input_image = inputs[i].detach().cpu().numpy().transpose(1, 2, 0)
input_image = (input_image - input_image.min()) / (input_image.max() - input_image.min())
visualization = show_cam_on_image(input_image, grayscale_cam[i, :], use_rgb=True)
# PIL을 사용하여 이미지 저장
visualization = Image.fromarray(visualization)
visualization.save(f'./grad_cam_{idx}_{i}.png')
test_acc = correct / total
CAM은 컨볼루션의 제일 마지막 레이어의 그래디언트를 활용하여 나타낸다.
그렇기 때문에 target_layers라는 값이 반드시 필요하다.
이는 print(model)
을 출력해보면 마지막 레이어의 이름을 알 수 있다.
예시로 사용한 컨볼루션 기반의 분류 모델이다. 드래그 된 conv4가 [model.LastLayerName]의 LastLayerName 대신 사용해주면된다.
즉, target_layers = [model.conv4] 이렇게 입력하면 된다.
cam = GradCAM(model=model, target_layers=target_layers)
을 통해 사용할 CAM을 정의한다.
그리고 inputs.requires_grad = True
을 사용하용하였다. CAM은 입력 이미지에 대한 그래디언트를 계산하여 활성화 맵을 생성하기 때문에, 입력 텐서가 그래디언트를 계산할 수 있도록 requires_grad 속성을 True로 설정해야 한다. 코드를 추가하지 않을 경우 에러가 발생하였다.
visualization = show_cam_on_image(input_image, grayscale_cam[i, :], use_rgb=True)
show_cam_on_image 함수를 통해 CAM의 히트맵을 얻고 이를 저장해주면 코드는 종료된다.
위 코드는 1차원 Gray Scale 이미지를 기준으로 진행된 코드이며, 비공개 코드로 인하여 수정, 생략된 부분이 있습니다.
감사합니다.