cnn convolution

CNN은 어떻게 특정 패턴을 찾을까? PyTorch로 이해하는 Convolution과 Kernel

CNN(Convolutional Neural Network)은 이미지 속 특징을 추출하기 위해 컨볼루션(Convolution) 연산을 사용한다. 하지만 컨볼루션 커널이 실제로 어떤 방식으로 특징을 찾는지는 처음 공부할 때 다소 추상적으로 느껴질 수 있다. 이번 실습에서는 PyTorch를 이용하여 서로 다른 크기의 X 패턴을 생성하고, 동일한 형태의 커널을 적용하여 CNN이 특정 패턴을 어떻게 탐지하는지 살펴보자.


1.컨볼루션(Convolution)이란?

컨볼루션은 작은 필터(Kernel)를 입력 이미지 위에서 한 칸씩 이동시키면서 계산하는 연산이다.

각 위치에서 다음과 같은 연산이 수행된다.

  1. 입력 이미지의 일부 영역 추출
  2. 커널과 원소별(Element-wise) 곱셈 수행
  3. 모든 값을 더하여 하나의 결과값 생성

이 결과값들이 모여 Feature Map을 구성하게 된다.

즉, Feature Map은 커널이 이미지 내부에서 얼마나 강하게 반응했는지를 보여주는 지도(Map)라고 볼 수 있다.

Image:

Kernel:

Convolution:

convolution
convolution

2.실험 목표

이번 실습에서는 하나의 입력 이미지 안에 다음과 같은 세 가지 크기의 X 패턴을 배치하였다.

  • 3×3 X 패턴
  • 5×5 X 패턴
  • 7×7 X 패턴

그리고 동일한 형태를 가진

  • 3×3 Kernel
  • 5×5 Kernel
  • 7×7 Kernel

을 각각 적용하여 어떤 위치에서 가장 큰 응답(Response)이 발생하는지 확인한다.


3.입력 이미지 구성

실험에 사용된 입력 이미지는 21×21 크기의 행렬이다.

이미지 내부에는 다음과 같이 서로 다른 크기의 X 패턴이 포함되어 있다.

3×3 X

      5×5 X

                  7×7 X

CNN은 이러한 패턴을 컨볼루션 연산을 통해 탐지하게 된다.


4.실습 코드

아래는 이번 실험에 사용한 전체 코드이다.

import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np

# ==========================================
# 1. 입력 이미지 생성 (21x21)
# ==========================================

image = torch.zeros((21, 21), dtype=torch.float32)

# ==========================================
# 2. 3x3 X 패턴
# ==========================================

x3 = torch.tensor([
    [1,0,1],
    [0,1,0],
    [1,0,1]
], dtype=torch.float32)

image[1:4, 1:4] = x3

# ==========================================
# 3. 5x5 X 패턴
# ==========================================

x5 = torch.tensor([
    [1,0,0,0,1],
    [0,1,0,1,0],
    [0,0,1,0,0],
    [0,1,0,1,0],
    [1,0,0,0,1]
], dtype=torch.float32)

image[6:11, 6:11] = x5

# ==========================================
# 4. 7x7 X 패턴
# ==========================================

x7 = torch.tensor([
    [1,0,0,0,0,0,1],
    [0,1,0,0,0,1,0],
    [0,0,1,0,1,0,0],
    [0,0,0,1,0,0,0],
    [0,0,1,0,1,0,0],
    [0,1,0,0,0,1,0],
    [1,0,0,0,0,0,1]
], dtype=torch.float32)

image[13:20, 13:20] = x7

# ==========================================
# CNN 입력 형태
# ==========================================

image_tensor = image.unsqueeze(0).unsqueeze(0)

# ==========================================
# 5. Kernel 생성
# ==========================================

kernel3 = x3.unsqueeze(0).unsqueeze(0)
kernel5 = x5.unsqueeze(0).unsqueeze(0)
kernel7 = x7.unsqueeze(0).unsqueeze(0)

# ==========================================
# 6. Convolution 수행
# ==========================================

feature3 = F.conv2d(image_tensor, kernel3)

feature5 = F.conv2d(image_tensor, kernel5)

feature7 = F.conv2d(image_tensor, kernel7)

# ==========================================
# 7. Feature Map 출력
# ==========================================

print("\n==============================")
print("3x3 Kernel Feature Map")
print("==============================")
print(feature3.squeeze())

print("\n==============================")
print("5x5 Kernel Feature Map")
print("==============================")
print(feature5.squeeze())

print("\n==============================")
print("7x7 Kernel Feature Map")
print("==============================")
print(feature7.squeeze())

# ==========================================
# 8. 최대 응답값 및 위치
# ==========================================

for name, feature in [
    ("3x3", feature3),
    ("5x5", feature5),
    ("7x7", feature7)
]:

    fmap = feature.squeeze()

    max_value = fmap.max()

    max_pos = torch.nonzero(fmap == max_value)

    print(f"\n{name} Kernel")
    print("최대 응답값 =", max_value.item())
    print("최대 응답 위치 =")
    print(max_pos)

# ==========================================
# 9. 시각화
# ==========================================

fig, axes = plt.subplots(
    2,
    2,
    figsize=(14,10)
)

# ------------------------------------------
# Input Image
# ------------------------------------------

axes[0,0].imshow(
    image,
    cmap='gray'
)

axes[0,0].set_title(
    "Input Image"
)

axes[0,0].axis('off')

# ------------------------------------------
# Feature Map 3x3
# ------------------------------------------

f3 = feature3.squeeze().numpy()

im1 = axes[0,1].imshow(
    f3,
    cmap='jet'
)

axes[0,1].set_title(
    "Feature Map (3x3 Kernel)"
)

axes[0,1].axis('off')

for y in range(f3.shape[0]):
    for x in range(f3.shape[1]):
        axes[0,1].text(
            x,
            y,
            int(f3[y,x]),
            ha='center',
            va='center',
            color='white',
            fontsize=8
        )

# ------------------------------------------
# Feature Map 5x5
# ------------------------------------------

f5 = feature5.squeeze().numpy()

im2 = axes[1,0].imshow(
    f5,
    cmap='jet'
)

axes[1,0].set_title(
    "Feature Map (5x5 Kernel)"
)

axes[1,0].axis('off')

for y in range(f5.shape[0]):
    for x in range(f5.shape[1]):
        axes[1,0].text(
            x,
            y,
            int(f5[y,x]),
            ha='center',
            va='center',
            color='white',
            fontsize=8
        )

# ------------------------------------------
# Feature Map 7x7
# ------------------------------------------

f7 = feature7.squeeze().numpy()

im3 = axes[1,1].imshow(
    f7,
    cmap='jet'
)

axes[1,1].set_title(
    "Feature Map (7x7 Kernel)"
)

axes[1,1].axis('off')

for y in range(f7.shape[0]):
    for x in range(f7.shape[1]):
        axes[1,1].text(
            x,
            y,
            int(f7[y,x]),
            ha='center',
            va='center',
            color='white',
            fontsize=8
        )

plt.tight_layout()
plt.show()

5.실험 결과

위 결과는 다음 네 개의 그림을 한 번에 보여준다.

  1. Input Image
  2. Feature Map (3×3 Kernel)
  3. Feature Map (5×5 Kernel)
  4. Feature Map (7×7 Kernel)

Feature Map 내부에 표시된 숫자는 실제 컨볼루션 연산 결과값이다.

색상이 밝을수록 해당 위치에서 커널이 강하게 반응했다는 의미이다.


6.3×3 Kernel 분석

3×3 커널은 다음과 같은 구조를 가진다.

1 0 1
0 1 0
1 0 1

입력 이미지의 3×3 X 패턴과 완전히 일치할 경우

1×1 + 0×0 + 1×1
+0×0 + 1×1 + 0×0
+1×1 + 0×0 + 1×1

결과는

5

가 된다.

따라서 Feature Map에서 최대 응답값은 5가 된다.

코드 실행 결과에서도 확인할 수 있다.

3x3 Kernel
최대 응답값 = 5

이는 3×3 커널이 3×3 X 패턴을 정확하게 탐지했음을 의미한다.


7.5×5 Kernel 분석

5×5 커널은 다음과 같은 X 형태를 가진다.

1 0 0 0 1
0 1 0 1 0
0 0 1 0 0
0 1 0 1 0
1 0 0 0 1

이 커널에는 총 9개의 1이 존재한다.

따라서 입력 이미지의 5×5 X 패턴과 완전히 일치하는 경우

최대 응답값 = 9

이 생성된다.

실행 결과 역시 동일하다.

5x5 Kernel
최대 응답값 = 9

8.7×7 Kernel 분석

7×7 커널은 더 큰 크기의 X 패턴을 탐지하기 위해 사용된다.

7×7 X 구조 내부에는 총 13개의 1이 존재한다.

따라서 완전히 일치하는 경우

최대 응답값 = 13

이 생성된다.

실행 결과는 다음과 같다.

7x7 Kernel
최대 응답값 = 13

이는 7×7 커널이 입력 이미지 내부의 7×7 X 패턴을 정확하게 탐지했다는 의미이다.


9.Feature Map의 숫자는 무엇을 의미할까?

이번 실습에서는 Feature Map 위에 실제 연산 결과값을 표시하였다.

예를 들어 다음과 같은 값이 있다고 가정해 보자.

0 0 1 0
1 5 2 0
0 2 1 0

여기서

  • 5는 매우 높은 유사도
  • 2는 일부만 일치
  • 0은 거의 일치하지 않음

을 의미한다.

즉, CNN은 Feature Map의 높은 응답값을 이용하여 원하는 특징이 어디에 존재하는지를 판단한다.


10.CNN은 왜 다양한 크기의 커널을 사용할까?

실제 이미지에는 크기가 서로 다른 특징들이 존재한다.

예를 들어 사람 얼굴을 인식하는 경우

  • 눈 → 작은 특징
  • 코 → 중간 특징
  • 얼굴 윤곽 → 큰 특징

으로 볼 수 있다.

따라서 하나의 커널만 사용하면 특정 크기의 특징만 잘 찾을 수 있다.

반면 다양한 크기의 커널을 사용하면 여러 규모의 특징을 동시에 추출할 수 있다.

이것이 CNN이 복잡한 이미지를 효과적으로 분석할 수 있는 이유 중 하나이다.


11.마무리

이번 실습에서는 PyTorch를 이용하여 3×3, 5×5, 7×7 크기의 X 패턴을 생성하고 각각의 커널을 적용해 보았다.

실험 결과를 통해 커널은 자신과 유사한 형태와 크기를 가진 패턴에 가장 강하게 반응한다는 사실을 확인할 수 있었다.

또한 Feature Map에 표시된 숫자를 통해 컨볼루션 연산이 단순한 이미지 처리 기법이 아니라, 특정 특징을 탐지하기 위한 패턴 매칭 과정이라는 점도 확인할 수 있었다.

CNN을 처음 공부하는 분이라면 이번 실습을 통해 Convolution, Kernel, Feature Map의 관계를 보다 직관적으로 이해할 수 있을 것이다.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다