CNN(Convolutional Neural Network)은 이미지 속 특징을 추출하기 위해 컨볼루션(Convolution) 연산을 사용한다. 하지만 컨볼루션 커널이 실제로 어떤 방식으로 특징을 찾는지는 처음 공부할 때 다소 추상적으로 느껴질 수 있다. 이번 실습에서는 PyTorch를 이용하여 서로 다른 크기의 X 패턴을 생성하고, 동일한 형태의 커널을 적용하여 CNN이 특정 패턴을 어떻게 탐지하는지 살펴보자.
1.컨볼루션(Convolution)이란?
컨볼루션은 작은 필터(Kernel)를 입력 이미지 위에서 한 칸씩 이동시키면서 계산하는 연산이다.
각 위치에서 다음과 같은 연산이 수행된다.
- 입력 이미지의 일부 영역 추출
- 커널과 원소별(Element-wise) 곱셈 수행
- 모든 값을 더하여 하나의 결과값 생성
이 결과값들이 모여 Feature Map을 구성하게 된다.
즉, Feature Map은 커널이 이미지 내부에서 얼마나 강하게 반응했는지를 보여주는 지도(Map)라고 볼 수 있다.
Image:

Kernel:

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.실험 결과

위 결과는 다음 네 개의 그림을 한 번에 보여준다.
- Input Image
- Feature Map (3×3 Kernel)
- Feature Map (5×5 Kernel)
- 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의 관계를 보다 직관적으로 이해할 수 있을 것이다.

