딥러닝 파이토치 강좌, 설명 가능한 CNN

1. 서론: 딥러닝과 CNN의 발전

딥러닝은 인공지능(AI)의 한 분야로, 대량의 데이터로부터 패턴을 학습하고 예측하는 능력을 갖고 있습니다. 그 중에서도 합성곱 신경망(Convolutional Neural Networks, CNN)은 이미지 처리를 위한 강력한 도구로 자리 잡고 있습니다. CNN은 저차원 데이터에서 패턴을 효과적으로 추출하고, 고차원 특징을 학습할 수 있는 구조를 가지고 있습니다. 하지만 CNN의 내부 작동 방식을 이해하기 어려운 점이 있어, 설명 가능성(Explainability)은 현재 많은 연구자들이 주목하는 주제입니다.

2. 설명 가능한 딥러닝의 필요성

딥러닝 모델, 특히 CNN과 같은 복잡한 구조의 모델은 ‘블랙박스’로 인식되곤 합니다. 이는 모델이 어떻게 결정을 내리는지 이해하기 어렵다는 것을 의미합니다. 따라서, 설명 가능한 CNN 모델의 개발이 더욱 중요해졌습니다. 이는 모델의 예측 결과를 사용자가 이해할 수 있도록 도와주며, 모델의 신뢰성을 높이는 데 기여합니다.

3. PyTorch로 CNN 구현하기

먼저 CNN을 구현하기 위해 필요한 기본 설정을 하나씩 해보겠습니다. PyTorch는 강력한 머신러닝 라이브러리로, 우리의 CNN을 쉽게 구축할 수 있도록 도와줍니다. 가장 먼저 필요한 라이브러리를 설치하고, 데이터를 준비하겠습니다.

3.1 PyTorch 설치

pip install torch torchvision

3.2 데이터셋 준비

여기서는 CIFAR-10 데이터셋을 사용할 것입니다. CIFAR-10은 10개의 클래스가 포함된 60,000개의 32×32 픽셀 이미지로 구성되어 있습니다. PyTorch의 torchvision 라이브러리를 사용해서 쉽게 데이터셋을 로드할 수 있습니다.


import torch
import torchvision
import torchvision.transforms as transforms

# 데이터 변환
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# CIFAR-10 데이터셋 다운로드
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)
    

3.3 CNN 모델 정의하기

이제 CNN 모델을 정의하겠습니다. 간단한 CNN 구조를 사용하여 서로 다른 층을 쌓아올리겠습니다. 기본적으로 합성곱(Convolutional) 층과 풀링(Pooling) 층을 결합하여 모델을 구축합니다.


import torch.nn as nn
import torch.nn.functional as F

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)  # 3채널 입력, 6채널 출력, 커널 크기 5
        self.pool = nn.MaxPool2d(2, 2)   # 2x2 최대 풀링
        self.conv2 = nn.Conv2d(6, 16, 5) # 6채널 입력, 16채널 출력, 커널 크기 5
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # Fully connected layer
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)  # Flattening the output
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
    

3.4 모델 학습하기

모델을 정의했으면, 이제 학습 과정을 진행하겠습니다. 학습을 위해 손실 함수와 옵티마이저를 설정하고, 에포크 단위로 모델을 학습시킵니다.


import torch.optim as optim

# 모델 인스턴스 생성
net = SimpleCNN()

# 손실 함수와 옵티마이저 정의
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

# 모델 학습
for epoch in range(2):  # 반복횟수 설정
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        optimizer.zero_grad()  # 기존 그래디언트 초기화
        outputs = net(inputs)  # 모델 예측
        loss = criterion(outputs, labels)  # 손실 계산
        loss.backward()  # 그래디언트 계산
        optimizer.step()  # 파라미터 업데이트
        running_loss += loss.item()
        if i % 2000 == 1999:  # 매 2000번째 배치마다 출력
            print(f"[{epoch + 1}, {i + 1}] 손실: {running_loss / 2000:.3f}")
            running_loss = 0.0
    print("학습 완료!")
    

3.5 모델 평가하기

학습이 완료된 모델을 테스트 데이터셋을 사용하여 평가하겠습니다. 정확도를 측정하는 것으로, 모델이 얼마나 잘 학습되었는지 확인할 수 있습니다.


correct = 0
total = 0

with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f'정확도: {100 * correct / total:.2f}%')
    

4. 설명 가능한 CNN 구현하기

이제 CNN을 설명 가능하게 만드는 방법에 대해 알아보겠습니다. 하나의 접근법으로는 Grad-CAM (Gradient-weighted Class Activation Mapping) 기법을 사용하여, 모델의 어떤 부분이 예측에 큰 영향을 미쳤는지를 시각화할 수 있습니다.

4.1 Grad-CAM 정의하기

Grad-CAM은 CNN의 예측에 대한 기여도를 시각화하는 방법입니다. 이를 통해 사용자에게 모델의 해석 가능성을 제공할 수 있습니다. 다음은 Grad-CAM을 구현하는 코드입니다.


import cv2
import numpy as np
import matplotlib.pyplot as plt

def grad_cam(input_model, image, category_index):
    # 모델의 마지막 합성곱 층을 가져옵니다.
    final_conv_layer = 'conv2'
    grad_model = nn.Sequential(*list(input_model.children())[:-1])
    
    with torch.enable_grad():
        # 입력 이미지를 텐서로 변환
        inputs = image.unsqueeze(0)  # 배치 차원 추가
        inputs.requires_grad = True  # 그래디언트 계산 가능하게 설정
        preds = grad_model(inputs)  # 예측
        class_channel = preds[0][category_index]  # 관심있는 클래스 채널
        
        # 예측 클래스에 대한 그래디언트를 계산
        grad_model.zero_grad()
        class_channel.backward()
        
        # 마지막 합성곱 층의 출력과 그래디언트 가져오기
        conv_layer_output = grad_model[-1].forward(inputs).cpu().data.numpy()
        gradients = grad_model[-1].weight.grad.cpu().data.numpy()
        
        # Grad-CAM을 생성하기 위한 비율 계산
        alpha = np.mean(gradients, axis=(2, 3))[0, :]
        cam = np.dot(alpha, conv_layer_output[0])  # 기여도 계산
        cam = np.maximum(cam, 0)  # ReLU 적용
        cam = cam / np.max(cam)  # 정규화
        
        # 원본 이미지에 덧붙입니다.
        return cam
    

4.2 Grad-CAM 적용하기

이제 학습된 모델에 Grad-CAM을 적용하여 몇 가지 이미지를 시각화해 보겠습니다.


# 예시 이미지 불러오기
image, label = testset[0]
category_index = label  # 관심 클래스 인덱스
cam = grad_cam(net, image, category_index)

# 원본 이미지와 Grad-CAM 열맵 시각화
plt.subplot(1, 2, 1)
plt.imshow(image.permute(1, 2, 0))
plt.title('원본 이미지')

plt.subplot(1, 2, 2)
plt.imshow(cam, cmap='jet', alpha=0.5)  # 색상 맵 적용
plt.title('Grad-CAM 열맵')
plt.show()
    

5. 결론

딥러닝에서 설명 가능성은 점점 더 중요한 주제가 되고 있습니다. CNN의 내부 작작을 이해하고 그 결과를 시각적으로 설명할 수 있는 방법이 필요합니다. PyTorch를 활용하여 CNN을 구현하고, Grad-CAM을 통해 모델의 예측을 해석하는 방법을 살펴보았습니다.

이 과정은 단순한 CNN 모델을 학습시키는 것에서 시작하여, 최신의 설명 가능한 딥러닝 기법인 Grad-CAM을 활용하여 CNN의 예측을 해석하고 시각화하는 데까지 이어졌습니다. 앞으로도 다양한 시도를 통해 더욱 복잡한 모델과 방법론을 탐구해야 할 것입니다. 딥러닝의 발전과 동시에 설명 가능한 AI 시스템 개발이 꼭 필요합니다.

6. 레퍼런스