콘텐츠로 이동

8차시: 합성곱과 풀링 - CNN이 이미지를 보는 방식

⏰ 80분 · 합성곱 · 커널/필터 · 풀링 · 특징 맵 · 난이도 ●●●○○

학습목표: 합성곱 연산의 원리를 커널·스트라이드·패딩 개념으로 설명하고, 풀링이 특징 맵을 어떻게 요약하는지 직접 계산하여 확인합니다.

오늘의 질문: “스마트폰 카메라가 고양이 사진에서 ‘고양이’를 알아볼 때, 컴퓨터는 픽셀을 어떤 순서로 들여다볼까요?”


작은 커널(돋보기)이 이미지 위를 미끄러지며 특징을 뽑아내는 원리

특징 맵을 요약해 크기를 줄이고 위치 변화에 둔감하게 만드는 방법

CNN Explainer로 엣지·블러 필터를 직접 적용하며 특징 맵 관찰

3×3 합성곱과 Max Pooling을 직접 코딩하여 개념 체득


1단계: 왜 Dense로는 이미지를 못 다룰까? (15분)

섹션 제목: “1단계: 왜 Dense로는 이미지를 못 다룰까? (15분)”

작은 28×28 이미지도 Dense 층으로 연결하면 파라미터가 수십만 개로 폭발합니다. 이미지가 한 픽셀만 옆으로 움직여도 완전히 다른 입력으로 인식되는 문제도 확인합니다.

2단계: 합성곱의 원리 — 커널·스트라이드·패딩 (20분)

섹션 제목: “2단계: 합성곱의 원리 — 커널·스트라이드·패딩 (20분)”

3×3 커널이 이미지 위를 순회하며 국소 패턴을 감지하는 방식을 수식과 계산으로 따라갑니다. 스트라이드와 패딩이 출력 크기를 어떻게 바꾸는지 공식으로 정리합니다.

3단계: 필터 시뮬레이터 체험 (15분)

섹션 제목: “3단계: 필터 시뮬레이터 체험 (15분)”

CNN Explainer 웹 도구와 직접 만든 NumPy 코드로 Sobel(엣지), Gaussian(블러) 필터를 이미지에 적용하고 결과를 비교합니다.

4단계: 풀링 — 특징 맵 요약하기 (15분)

섹션 제목: “4단계: 풀링 — 특징 맵 요약하기 (15분)”

Max Pooling과 Average Pooling을 수작업으로 계산한 뒤 NumPy로 구현합니다. 왜 다운샘플링이 필요한지 파라미터 수와 위치 불변성 관점에서 논의합니다.

5단계: 연습 문제 & 마무리 (15분)

섹션 제목: “5단계: 연습 문제 & 마무리 (15분)”

출력 크기 계산 문제와 직접 구현 과제를 풀며 개념을 굳힙니다. 형성 평가와 성찰로 차시를 정리합니다.


1. 왜 Dense 층만으로는 이미지를 다루기 어려운가?

섹션 제목: “1. 왜 Dense 층만으로는 이미지를 다루기 어려운가?”

MNIST 손글씨 숫자 이미지는 28×28 = 784픽셀입니다. 이것을 Dense 층(완전연결층)에 그대로 넣으면, 첫 은닉층 뉴런 하나당 가중치가 784개 필요합니다. 은닉 뉴런을 128개만 두어도 784 × 128 = 100,352개의 가중치가 생깁니다.

더 큰 문제는 위치 정보의 붕괴입니다. Dense 층은 이미지를 1차원 벡터로 평탄화(flatten)해서 받기 때문에, 숫자 “7”이 왼쪽에 있을 때와 오른쪽에 있을 때를 완전히 다른 입력으로 처리합니다. 같은 “7”인데도 모델은 매번 처음 보는 것처럼 학습해야 합니다.

한계Dense 방식우리가 원하는 것
파라미터 수입력 크기에 비례해 폭발이미지 크기와 무관하게 작음
위치 불변성없음 (위치 바뀌면 다른 입력)어디 있든 같은 특징으로 인식
공간 구조평탄화로 파괴됨인접 픽셀 관계 보존

합성곱(Convolution)은 작은 필터를 이미지 전체에 공유하는 방식으로 세 가지 문제를 한 번에 해결합니다.


2. 합성곱 연산 — 커널, 스트라이드, 패딩

섹션 제목: “2. 합성곱 연산 — 커널, 스트라이드, 패딩”

🔍 비유: 돋보기로 사진 스캔하기

섹션 제목: “🔍 비유: 돋보기로 사진 스캔하기”

탐정이 증거 사진에서 지문을 찾는 장면을 떠올려 봅니다. 탐정은 사진 전체를 한 번에 보지 않고, 돋보기를 들고 왼쪽 위부터 오른쪽으로, 한 칸씩 내려가며 국소 영역을 확대합니다. 합성곱의 커널(kernel, 필터)이 바로 이 돋보기입니다. 같은 돋보기를 이미지 전체에 재사용하기 때문에 가중치가 공유되고, 어느 위치에서든 같은 패턴을 포착할 수 있습니다.

합성곱 연산은 입력 이미지 $X$와 커널 $K$ 사이에서 다음과 같이 정의됩니다.

$ Y[i,j] = \sum_{m=0}^{k-1} \sum_{n=0}^{k-1} X[i+m, j+n] \cdot K[m,n] $

여기서 $Y$는 출력 특징 맵(feature map), $k$는 커널 크기입니다. 커널의 각 원소를 해당 위치 픽셀과 곱해 모두 더한 값이 출력의 한 픽셀이 됩니다.

구체적인 예시를 보겠습니다. 4×4 입력 이미지에 3×3 커널을 적용할 때, 출력의 좌측 상단 $Y[0,0]$은 이렇게 계산됩니다.

입력 이미지와 커널:

입력 $X$커널 $K$
1230-101
4561-202
7892-101
1012

$Y[0,0]$ 계산 (입력의 좌상단 3×3 영역과 커널의 원소별 곱의 합):

$ \begin{aligned} Y[0,0] &= 1 \cdot (-1) + 2 \cdot 0 + 3 \cdot 1 \ &+ 4 \cdot (-2) + 5 \cdot 0 + 6 \cdot 2 \ &+ 7 \cdot (-1) + 8 \cdot 0 + 9 \cdot 1 \ &= -1 + 0 + 3 - 8 + 0 + 12 - 7 + 0 + 9 \ &= 8 \end{aligned} $

이렇게 커널을 한 칸씩 이동시키며 전체 영역을 훑으면 출력 특징 맵이 완성됩니다.

🎛️ 스트라이드(Stride)와 패딩(Padding)

섹션 제목: “🎛️ 스트라이드(Stride)와 패딩(Padding)”

스트라이드는 커널이 한 번에 이동하는 칸 수입니다. 스트라이드 1이면 한 칸씩, 2면 두 칸씩 건너뜁니다. 스트라이드가 크면 출력이 작아지고 계산량이 줄어듭니다.

패딩은 입력 이미지 테두리에 0을 덧붙이는 것입니다. 패딩 없이 합성곱을 하면 가장자리 픽셀이 중앙 픽셀보다 덜 참조되고, 출력 크기도 줄어듭니다. 패딩을 추가하면 출력 크기를 유지하거나 가장자리 정보 손실을 막을 수 있습니다.

출력 크기 공식은 다음과 같습니다.

$ O = \left\lfloor \dfrac{I + 2P - K}{S} \right\rfloor + 1 \tag{1} $

  • $I$: 입력 크기(한 변의 픽셀 수)
  • $K$: 커널 크기
  • $P$: 패딩 크기
  • $S$: 스트라이드
  • $O$: 출력 크기

예를 들어 28×28 입력에 3×3 커널, 패딩 1, 스트라이드 1을 적용하면 식 (1)에 의해 $O = \lfloor (28 + 2 - 3) / 1 \rfloor + 1 = 28$이 되어 크기가 보존됩니다.

flowchart LR
    A[입력 이미지<br/>28x28] -->|3x3 커널<br/>스트라이드 1<br/>패딩 1| B[특징 맵<br/>28x28]
    B -->|Max Pooling<br/>2x2| C[축소된 맵<br/>14x14]
    C --> D[다음 합성곱 층]

여러분이 매일 쓰는 인스타그램 필터스마트폰 카메라 보정이 바로 이 합성곱 연산을 사용합니다. 엣지 강조, 블러, 샤프닝 모두 서로 다른 커널일 뿐입니다.


3. 대표적인 커널 — 엣지와 블러

섹션 제목: “3. 대표적인 커널 — 엣지와 블러”

커널의 값에 따라 추출되는 특징이 달라집니다. 대표적인 두 필터를 보겠습니다.

Sobel 필터(수직 엣지 검출): 좌우 픽셀 값의 차이를 강조합니다. 세로선이 있는 곳에서 큰 값이 나옵니다.

$ K_{\text{sobel-x}} = \begin{bmatrix} -1 & 0 & 1 \ -2 & 0 & 2 \ -1 & 0 & 1 \end{bmatrix} $

Gaussian 블러 필터(평균화): 주변 픽셀을 부드럽게 섞어 이미지를 흐리게 만듭니다.

$ K_{\text{blur}} = \dfrac{1}{16}\begin{bmatrix} 1 & 2 & 1 \ 2 & 4 & 2 \ 1 & 2 & 1 \end{bmatrix} $

필터 종류커널 특징효과
Sobel-X좌우 대칭으로 부호 반대수직 엣지 검출
Sobel-Y상하 대칭으로 부호 반대수평 엣지 검출
Gaussian 블러모두 양수, 중앙이 큼이미지 흐림
샤프닝중앙 양수, 주변 음수경계 뚜렷

CNN이 학습할 때는 이런 커널 값을 사람이 설계하지 않고 역전파로 스스로 찾아냅니다. 학습된 첫 번째 합성곱 층 커널은 대부분 엣지나 색상 변화를 감지하는 형태가 됩니다.


4. 풀링(Pooling) — 특징 맵 요약하기

섹션 제목: “4. 풀링(Pooling) — 특징 맵 요약하기”

🗜️ 비유: 사진 썸네일 만들기

섹션 제목: “🗜️ 비유: 사진 썸네일 만들기”

큰 사진을 작은 썸네일로 줄일 때, 사진의 주요 내용(사람 얼굴, 건물 윤곽)은 남고 세부 픽셀은 사라집니다. 풀링은 이와 비슷하게 특징 맵을 더 작게 요약하는 연산입니다.

풀링은 특징 맵을 작은 영역(보통 2×2)으로 나누고, 각 영역을 하나의 값으로 대표시킵니다.

  • Max Pooling: 영역 내 최댓값을 선택. “이 영역에서 가장 강한 특징”을 남깁니다.
  • Average Pooling: 영역 내 평균을 계산. “이 영역의 평균적인 반응”을 남깁니다.

4×4 특징 맵에 2×2 Max Pooling(스트라이드 2)을 적용하는 예:

입력 특징 맵
1324
5678
3210
1243

좌상단 2×2 영역 {1, 3, 5, 6} → 최댓값 6. 우상단 {2, 4, 7, 8}8. 좌하단 {3, 2, 1, 2}3. 우하단 {1, 0, 4, 3}4.

결과:

Max Pool 결과
68
34
효과설명
다운샘플링특징 맵 크기가 절반으로 줄어 계산량·메모리 감소
위치 불변성작은 이동·왜곡이 있어도 같은 출력이 나올 가능성 증가
과적합 억제세부 정보를 버리고 핵심 특징만 남겨 일반화 성능 향상

Max Pooling이 가장 널리 쓰입니다. “특징이 있긴 있었다”는 신호를 보존하는 데 강하기 때문입니다.


실습 1: CNN Explainer 웹 도구 체험 (5분)

섹션 제목: “실습 1: CNN Explainer 웹 도구 체험 (5분)”

도구: CNN Explainer (https://poloclub.github.io/cnn-explainer/)

브라우저에서 CNN Explainer를 엽니다. 상단에 10가지 이미지(자동차, 피자, 고양이 등)가 보입니다. 하나를 클릭해 선택합니다.

conv_1_1 층의 특징 맵 하나를 클릭합니다. 3×3 커널이 입력 이미지 위를 이동하며 어떤 값이 계산되는지 애니메이션으로 확인합니다.

max_pool_1 층을 클릭해 2×2 Max Pooling이 특징 맵을 어떻게 축소하는지 봅니다. 입력과 출력의 크기 비율을 기록합니다.

실습 2: NumPy로 합성곱 직접 구현하기 (10분)

섹션 제목: “실습 2: NumPy로 합성곱 직접 구현하기 (10분)”

실행 환경: Python 3.10+, NumPy 설치 필요 (pip install numpy matplotlib)

v1: 가장 기본 — 한 위치에서만 계산

섹션 제목: “v1: 가장 기본 — 한 위치에서만 계산”
import numpy as np
# 5x5 입력 이미지 (간단한 숫자 행렬)
image = np.array([
[1, 2, 3, 0, 1],
[0, 1, 2, 3, 1],
[1, 2, 1, 0, 2],
[2, 1, 0, 1, 3],
[1, 0, 2, 1, 0]
])
# 3x3 Sobel-X 커널 (수직 엣지 검출)
kernel = np.array([
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
])
# 좌상단 한 위치만 계산 — 왜? 원리를 먼저 이해하기 위해
patch = image[0:3, 0:3] # <- 여기가 커널과 겹치는 영역
result = np.sum(patch * kernel) # <- 원소별 곱의 합 = 합성곱 1회
print(f"Y[0,0] = {result}")
Y[0,0] = 6

위 코드의 10번째 줄 patch = image[0:3, 0:3]이 바로 커널이 놓이는 국소 영역입니다. 11번째 줄 np.sum(patch * kernel)합성곱 정의식 $\sum X \cdot K$의 구현입니다.

v2: 전체 이미지에 대해 반복 — 이중 for 루프 추가

섹션 제목: “v2: 전체 이미지에 대해 반복 — 이중 for 루프 추가”
import numpy as np
image = np.array([
[1, 2, 3, 0, 1],
[0, 1, 2, 3, 1],
[1, 2, 1, 0, 2],
[2, 1, 0, 1, 3],
[1, 0, 2, 1, 0]
])
kernel = np.array([
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
])
def conv2d(image, kernel):
H, W = image.shape
kH, kW = kernel.shape
# 식 (1)에서 패딩=0, 스트라이드=1일 때 출력 크기
out_H = H - kH + 1
out_W = W - kW + 1
output = np.zeros((out_H, out_W))
for i in range(out_H): # <- 여기가 스트라이드 1로 세로 이동
for j in range(out_W): # <- 여기가 스트라이드 1로 가로 이동
patch = image[i:i+kH, j:j+kW]
output[i, j] = np.sum(patch * kernel)
return output
result = conv2d(image, kernel)
print(result)
[[ 6. 0. -6.]
[-2. -4. 4.]
[-4. -2. 6.]]

출력이 3×3인 이유는 식 (1)에 의해 $(5 - 3) / 1 + 1 = 3$이기 때문입니다. 5×5 입력이 3×3으로 줄었습니다.

v3: 실제 이미지에 Sobel 필터 적용이제 실제 사진에 필터를 적용해 눈으로 효과를 확인합니다. scikit-image의 샘플 이미지를 사용합니다.

섹션 제목: “v3: 실제 이미지에 Sobel 필터 적용이제 실제 사진에 필터를 적용해 눈으로 효과를 확인합니다. scikit-image의 샘플 이미지를 사용합니다.”
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, color
def conv2d(image, kernel):
H, W = image.shape
kH, kW = kernel.shape
out_H = H - kH + 1
out_W = W - kW + 1
output = np.zeros((out_H, out_W))
for i in range(out_H):
for j in range(out_W):
output[i, j] = np.sum(image[i:i+kH, j:j+kW] * kernel)
return output
# 샘플 이미지를 흑백으로 — 합성곱을 2D에서 단순하게 보기 위해
img = color.rgb2gray(data.astronaut())
sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
blur = np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]]) / 16.0
edge = conv2d(img, sobel_x) # <- 여기가 수직 엣지 검출
blurred = conv2d(img, blur) # <- 여기가 Gaussian 블러
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
axes[0].imshow(img, cmap='gray'); axes[0].set_title('원본')
axes[1].imshow(edge, cmap='gray'); axes[1].set_title('Sobel-X (엣지)')
axes[2].imshow(blurred, cmap='gray'); axes[2].set_title('Gaussian (블러)')
for ax in axes: ax.axis('off')
plt.tight_layout(); plt.show()
(3개의 이미지가 가로로 표시됩니다.
원본: 우주비행사 사진
Sobel-X: 수직 경계선만 밝게 남은 이미지
Gaussian: 전체적으로 흐려진 이미지)

위 코드의 19번째 줄 edge = conv2d(img, sobel_x)가 바로 엣지 검출 합성곱입니다. 커널 값만 바꿨을 뿐인데 완전히 다른 특징이 뽑혀 나옵니다. CNN의 학습은 결국 이 커널 값을 데이터로부터 찾는 과정입니다.


실습 3: Max Pooling 직접 구현 (5분)

섹션 제목: “실습 3: Max Pooling 직접 구현 (5분)”
import numpy as np
def max_pool(feature_map, pool_size=2, stride=2):
H, W = feature_map.shape
out_H = (H - pool_size) // stride + 1
out_W = (W - pool_size) // stride + 1
output = np.zeros((out_H, out_W))
for i in range(out_H):
for j in range(out_W):
# 풀링 영역을 잘라서 최댓값만 추출 — 핵심 특징만 남기기 위해
region = feature_map[
i*stride : i*stride+pool_size,
j*stride : j*stride+pool_size
]
output[i, j] = np.max(region) # <- 여기가 Max Pooling
return output
fmap = np.array([
[1, 3, 2, 4],
[5, 6, 7, 8],
[3, 2, 1, 0],
[1, 2, 4, 3]
])
print(max_pool(fmap, pool_size=2, stride=2))
[[6. 8.]
[3. 4.]]

앞서 손으로 계산한 결과와 일치합니다. np.maxnp.mean으로 바꾸면 그대로 Average Pooling이 됩니다.


⚠️ 에러 경험: 출력 크기 계산을 잘못하면?

섹션 제목: “⚠️ 에러 경험: 출력 크기 계산을 잘못하면?”

아래 코드는 의도적으로 틀렸습니다. 실행하면 어떤 에러가 날까요?

import numpy as np
image = np.random.rand(28, 28)
kernel = np.random.rand(5, 5)
# 출력 크기를 '입력과 같다'고 잘못 가정
output = np.zeros((28, 28))
for i in range(28):
for j in range(28):
patch = image[i:i+5, j:j+5]
output[i, j] = np.sum(patch * kernel)
ValueError: operands could not be broadcast together with shapes (4,5) (5,5)

원인 분석: i = 24가 되면 image[24:29, ...]인데 이미지는 28행까지만 있어 실제로는 4행만 잘립니다. 4×5 patch와 5×5 kernel의 곱셈이 불가능해 에러가 발생합니다.

수정: 식 (1)로 출력 크기를 정확히 계산합니다. 패딩 0, 스트라이드 1이면 $28 - 5 + 1 = 24$이므로 반복 범위를 range(24)로 고쳐야 합니다.

out_size = 28 - 5 + 1 # <- 여기가 식 (1)에 의한 올바른 출력 크기
output = np.zeros((out_size, out_size))
for i in range(out_size):
for j in range(out_size):
output[i, j] = np.sum(image[i:i+5, j:j+5] * kernel)

출력 크기를 유지하고 싶다면 입력 가장자리에 2픽셀씩 패딩을 추가하면 됩니다(np.pad 사용).


활동 유형: 짝 활동 (2인 1조, 10분)

짝과 함께 아래 세 질문에 대해 의견을 나누고, 각자 한 문장으로 정리합니다.

  1. Dense 층 하나(784 → 128)는 약 10만 개의 파라미터를 쓰는데, 3×3 합성곱 커널 하나는 9개의 파라미터만 사용합니다. 왜 이 적은 수로도 이미지 인식이 가능할까요? (힌트: 커널이 이미지 위를 어떻게 이동하는지 떠올려 봅니다)
  2. Max Pooling을 쓰면 정보 손실이 생깁니다. 그런데도 CNN 성능이 오히려 좋아지는 경우가 많습니다. 왜일까요?
  3. 고양이 얼굴이 사진 왼쪽에 있을 때와 오른쪽에 있을 때, Dense 모델과 CNN 모델의 반응은 어떻게 다를까요?

32×32 크기의 컬러 이미지에 5×5 커널, 패딩 0, 스트라이드 1을 적용했을 때 출력 특징 맵의 크기는 얼마입니까?

힌트

식 (1): $O = \lfloor (I + 2P - K) / S \rfloor + 1$에 $I=32, K=5, P=0, S=1$을 대입합니다.

정답

$O = (32 + 0 - 5) / 1 + 1 = 28$

출력은 28×28입니다.

응용: 출력 크기 보존 패딩 찾기

섹션 제목: “응용: 출력 크기 보존 패딩 찾기”

28×28 입력에 3×3 커널, 스트라이드 1을 적용하면서 출력 크기를 28×28로 유지하고 싶습니다. 패딩을 얼마로 해야 합니까?

힌트

식 (1)에서 $O = I$가 되려면 $2P - K + 1 = 0$이어야 합니다.

정답

$28 = (28 + 2P - 3) / 1 + 1$

$28 = 26 + 2P$

$P = 1$

패딩 1을 주면 입력 크기가 보존됩니다. 이를 “same padding”이라고 부릅니다.

위의 max_pool 함수를 참고하여, 4×4 특징 맵에 2×2 Average Pooling(스트라이드 2)을 적용하는 avg_pool 함수를 직접 작성하고, 아래 입력에 적용한 결과를 제출하세요.

fmap = np.array([
[2, 4, 6, 8],
[1, 3, 5, 7],
[0, 2, 4, 6],
[1, 1, 3, 3]
])
힌트

np.max(region)np.mean(region)으로 바꾸기만 하면 됩니다.

정답 코드와 출력
import numpy as np
def avg_pool(feature_map, pool_size=2, stride=2):
H, W = feature_map.shape
out_H = (H - pool_size) // stride + 1
out_W = (W - pool_size) // stride + 1
output = np.zeros((out_H, out_W))
for i in range(out_H):
for j in range(out_W):
region = feature_map[
i*stride : i*stride+pool_size,
j*stride : j*stride+pool_size
]
output[i, j] = np.mean(region) # <- Max → Mean으로 변경
return output
fmap = np.array([
[2, 4, 6, 8],
[1, 3, 5, 7],
[0, 2, 4, 6],
[1, 1, 3, 3]
])
print(avg_pool(fmap))
[[2.5 6.5]
[1. 4. ]]

좌상단 영역 {2, 4, 1, 3}의 평균은 $10 / 4 = 2.5$입니다.


합성곱과 풀링은 사람이 직접 코딩하는 대신, 실무에서는 TensorFlow/Keras나 PyTorch의 레이어 API로 한 줄에 쓰입니다.

import tensorflow as tf
from tensorflow.keras import layers, models
model = models.Sequential([
layers.Conv2D(32, (3, 3), padding='same', activation='relu',
input_shape=(28, 28, 1)), # <- 합성곱 층
layers.MaxPooling2D((2, 2)), # <- 풀링 층
layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(10, activation='softmax') # <- 분류용 Dense 층
])
model.summary()

실무 프로젝트에서 이 패턴은 다음과 같은 곳에 그대로 쓰입니다.

  • 의료 영상 분석: X-ray, CT에서 병변 위치 검출 (U-Net 같은 CNN 변형)
  • 자율주행: 카메라 영상에서 차선·보행자·표지판 인식
  • 스마트폰 카메라: 인물 모드의 배경 분리, 야간 모드 잡음 제거
  • 제조업 품질 검사: 제품 표면 결함을 자동으로 검출

다음 차시에서는 이 레이어들을 쌓아 실제 이미지 분류 모델을 학습시킵니다.



객관식 1. 28×28 이미지에 5×5 커널, 패딩 0, 스트라이드 1로 합성곱을 적용하면 출력 특징 맵의 크기는?

① 22×22 ② 24×24 ③ 26×26 ④ 28×28

정답 확인

정답: ② 식 (1)에 의해 $O = (28 - 5) / 1 + 1 = 24$. 패딩이 없으므로 커널 크기만큼 출력이 줄어듭니다.

객관식 2. 다음 중 Max Pooling의 효과로 보기 어려운 것은?

① 특징 맵의 크기를 줄여 계산량 감소 ② 작은 위치 이동에 대한 불변성 증가 ③ 학습해야 할 파라미터 수 증가 ④ 핵심 특징(강한 반응)의 보존

정답 확인

정답: ③ Max Pooling은 파라미터가 없는 연산입니다. 단순히 영역에서 최댓값을 뽑을 뿐이므로 학습 파라미터는 오히려 증가하지 않습니다.

객관식 3. Dense 층과 비교했을 때 합성곱 층의 가장 본질적인 차이는?

① 활성화 함수를 쓴다 ② 같은 커널을 이미지 전체 위치에서 공유한다 ③ 편향(bias)이 없다 ④ 입력을 평탄화해서 받는다

정답 확인

정답: ② 합성곱의 핵심은 가중치 공유(weight sharing)입니다. 한 커널이 이미지의 모든 위치에서 재사용되기 때문에 파라미터 수가 적고, 위치 불변성이 생깁니다.

서술형 1. 3×3 Sobel-X 커널을 고양이 사진에 적용하면 고양이의 어떤 특징이 강조되는지, 그리고 같은 사진에 Gaussian 블러 커널을 적용한 결과와 어떻게 다른지 설명하세요.

예시 답안

Sobel-X 커널은 좌우로 부호가 반대인 값을 가지므로, 좌우 픽셀 값의 차이가 큰 곳, 즉 수직 방향 경계선에서 큰 출력을 만듭니다. 고양이 사진에 적용하면 고양이의 옆 윤곽선, 귀의 수직 모서리, 수염의 경계 같은 세로 엣지가 밝게 강조되고 평평한 영역(몸통 털의 매끈한 부분)은 0에 가까워집니다.

반면 Gaussian 블러 커널은 모든 값이 양수이고 중앙이 가장 크며 합이 1인 가중평균입니다. 따라서 주변 픽셀을 섞어 이미지를 부드럽게 흐리게 만듭니다. 엣지는 약해지고 잡음도 줄어듭니다.

두 결과는 정반대의 목적을 가집니다. Sobel은 경계 정보를 뽑아내는 용도로, Gaussian은 잡음을 줄이거나 세부를 부드럽게 하는 용도로 쓰입니다. CNN은 학습 과정에서 이런 다양한 커널을 스스로 찾아냅니다.

자기점검 체크리스트

  • 합성곱의 커널·스트라이드·패딩 개념을 식 (1)로 설명하고 출력 크기를 계산할 수 있다
  • Max Pooling과 Average Pooling의 차이와 각각의 효과를 설명할 수 있다
  • NumPy로 3×3 합성곱과 2×2 풀링을 직접 구현할 수 있다
  • “컴퓨터는 이미지를 어떤 순서로 들여다볼까?”라는 오늘의 질문에 내 말로 답할 수 있다

32×32 입력에 3×3 커널, 패딩 1, 스트라이드 1을 적용한 출력 특징 맵의 크기는?

4×4 특징 맵에 2×2 Max Pooling(스트라이드 2)을 적용하면 출력 크기는?

합성곱 층이 Dense 층에 비해 이미지 처리에 유리한 가장 핵심적인 이유는?