SK플래닛 ai활용 데이터엔지니어 과정 2기/ML & DL

DL 3 - CNN

dev-lee 2026. 5. 26. 16:52

1. CNN 개요

CNN(Convolutional Neural Network, 합성곱 신경망)은 사람·동물의 눈이 사물을 인지하는 절차를 차용한 인공신경망 구조임. 이미지·영상 데이터의 인지·분류·탐지에 적합한 모델이며, 비전 계열 신경망의 출발점이 됨.

1.1 발전 흐름

  • 1989 — 첫 CNN 논문 발표
  • 2006 — CNN 명칭 적용, 일반화 논문 발표
  • 2012 — CNN 기반 모델이 이미지넷 경연대회에 출품, 딥러닝 패러다임 전환의 결정적 계기

1.2 CNN의 두 가지 핵심 관점

  • 이미지 관점 — 이미지 내의 공간 정보·인접 정보를 특징으로 추출하여 학습
  • 데이터 관점 — 특정 크기의 이미지 데이터로 시작해서 신경망 내부에서 점차 축소하면서 "같은 의미"임을 학습

CNN은 결국 "큰 이미지 → 작은 이미지 → 더 작은 이미지" 과정을 거치며 특징을 압축하는 구조임. 숫자 3을 인식한다면 다양한 크기의 3 이미지에서 특징을 뽑아 최종적으로 "3"이라는 출력에 수렴하도록 학습함.


2. CNN 구조

CNN은 크게 **특징 추출부**(합성곱+풀링)와 **이미지 분류부**(전결합층+출력층)로 나뉨. 은닉층은 출력층으로 갈수록 이미지 크기가 점점 작아지는 게 특징.

2.1 전체 구조

입력층
------------
은닉층
  ├─ 합성곱층(Convolution Layer) : 이미지 특징 추출
  ├─ 풀링층(Pooling Layer) : 특징 강화
  ├─ 합성곱층
  ├─ 풀링층
  ├─ ...
  └─ 전결합층(Fully Connected Layer) : 차원 조정
------------
출력층

2.2 입력층

  • 데이터 입력 — 원본 2D 이미지, 수직·수평 크기를 동등하게 설정(정사각형)
  • 데이터 부족 시 — 이미지 증폭(반전, 회전 등)으로 보완
  • 채널 기준 — 1채널(GrayScale) / 3채널(Color)

2.3 입력 데이터 포맷

  • NHWC — 개수(N), 높이(H), 너비(W), 채널(C) → Channel Last 포맷, TensorFlow 기본
  • NCHW — 개수(N), 채널(C), 높이(H), 너비(W) → Channel First 포맷, PyTorch 기본

같은 이미지라도 프레임워크에 따라 차원 순서가 다름. PyTorch에서 TF용 데이터를 쓰거나 그 반대일 때 반드시 차원 재배열이 필요함. CNN 디버깅에서 가장 흔한 실수 지점.


3. 합성곱층 (Convolution Layer)

이미지의 공간 정보와 인접 정보를 추출하는 CNN의 핵심 연산. 원본 이미지 위에서 필터(커널)를 슬라이딩하면서 행렬곱을 수행하여 특징을 뽑아냄.

3.1 동작 원리

  • 원본 이미지 위에 필터(커널) 행렬을 올림 — 예: 원본 4×4, 커널 2×2
  • 필터가 좌→우, 위→아래로 슬라이딩 — 1칸씩 이동(이를 stride라고 함)
  • 겹치는 구간에서 행렬곱 수행 — 공간 정보·인접 정보 등의 특징 추출
  • 결과물 누적 → Feature Map 완성
  • 이미지 경계 처리 — 이동하다 딱 안 맞는 경우 Padding으로 보정

3.2 구성 요소

  • 입력 x — 이전 층에서 나온 산출물, Shape를 반드시 기억해야 함
  • 커널(필터) k — 가중치(w)를 값으로 가지는 행렬, 통상 정방형(예: 2면 2×2)
  • bias — 커널에 편향값, 특정 방향성 이동(y절편 역할)
  • stride(s) — 커널 이동량
  • padding(p) — 경계 보정값
  • 출력 — Feature Map(특징값 행렬), 활성화 함수 통과 시 Activation Map

3.3 커널의 두 가지 활용 방식

  • 수동 필터 개발 — 가중치를 직접 설계 (수직, 수평, 가우시안 등) → 사진 앱의 "필터" 기능이 이런 방식
  • 학습을 통한 최적화 — 가중치를 랜덤 초기화 후 학습으로 조정 → 딥러닝 CNN의 목표

사진 편집기의 "필터"가 바로 합성곱 커널과 같은 원리임. 차이점은 사진 앱은 사람이 설계한 고정 커널을 쓰고, CNN은 학습으로 최적 커널을 찾는다는 것뿐임. 가중치 학습 = 최적의 커널 찾기.

3.4 Stride

  • 정의 — 커널이 한 번에 움직이는 양
  • 가로·세로 각각 설정 — 통상 같은 값(예: stride=1이면 1×1)
  • 설계 자유도 — 이동량은 설계자가 자유롭게 결정

3.5 Padding 모드

  • SAME — 출력 Feature Map이 통상 원본과 같은 크기
  • VALID — 출력이 원본보다 작아짐 (유효한 구간만 계산하기 때문)

3.6 다채널 처리

  • 1채널 — Input 1개 + Filter 1개 → Feature Map 1개
  • 3채널(컬러) — Input 3개(R, G, B) + 커널도 3개 필요 → 최종 Feature Map은 1개로 합쳐짐

4. 풀링층 (Pooling Layer)

합성곱층의 결과물(Feature Map)을 강화하는 층. 학습 파라미터가 없고 단순 통계 연산만 수행함.

4.1 역할과 특징

  • 역할 — Feature Map을 강화(요약)하여 데이터 크기 축소
  • 가중치 없음 — 기법이 정해져 있어 학습 대상 아님
  • 필요 값 — 커널 크기만 필요. 이동량·보정은 기본값으로 처리, 최적화 대상 없음

4.2 풀링 기법

  • Max Pooling(최대 풀링) — 구간 내 최댓값 선택 → 외곽선이 특징이라면 선이 선명해짐
  • Average Pooling(평균 풀링) — 구간 내 평균값 선택 → 선이 퍼지는 느낌, 부동소수형 출력

풀링층은 학습 파라미터가 0개임. CNN 모델의 총 파라미터 수를 계산할 때 풀링층은 제외됨. "특징을 새로 뽑는 게 아니라 이미 뽑힌 특징을 정리한다"는 개념.


5. 전결합층과 출력층

  • 전결합층(Fully Connected Layer) — 2D → 1D 차원 조정, 데이터가 수렴하는 중간 단계. 출력층과 은닉층의 조정자 역할
  • 위치 — 합성곱 + 풀링 → 전결합층 → 출력층
  • 입력 — 이전 층의 산출물
  • 출력 — 출력층으로 연결

6. PyTorch로 합성곱층 직접 구현

PyTorch를 사용해 합성곱 연산을 직접 구현하면서 가중치·Feature Map의 흐름을 눈으로 확인함. API를 쓰기 전에 원리를 손으로 짜보는 단계.

6.1 데이터 로드 및 채널 변환

import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import torch

img = Image.open('/content/drive/MyDrive/6. ML_DL/img/torch_cnn_sample.png')

img_arr = np.array(img)
img_arr.shape   # (H, W, C) - 3채널 컬러

# 3채널 → 1채널(GrayScale) 변환
img = img.convert('L')
img_arr = np.array(img)
img_arr.shape   # (H, W), 값 범위 0~255
  • convert('L') — PIL의 흑백 변환, 1채널 GrayScale로 만들어 연산 단순화
  • 데이터 범위 — 8bit 그레이스케일이라 0 ~ 255 (= 2⁸-1)

6.2 Tensor 변환

img_tensor = torch.Tensor(img_arr)
img_tensor.size(), img_tensor.type(), img_tensor.dim(), img_tensor.max(), img_tensor.min()
  • torch.Tensor — PyTorch의 기본 데이터 단위
  • 텐서 특성 — 모든 구성원의 타입이 동일해야 함

6.3 커널 사전 정의

# 수직 커널 - 수직 성분(특징) 추출
ver_kernel = torch.Tensor([
    [1, 0, -1],
    [1, 0, -1],
    [1, 0, -1]
])

# 수평 커널 - 전치(transpose)로 생성
hor_kernel = ver_kernel.T
  • 수직 커널 — 왼쪽 양수 / 가운데 0 / 오른쪽 음수 → "왼→오른쪽으로 조명이 비추는" 효과로 수직 경계 강조
  • 수평 커널 — 수직 커널의 전치 행렬, 수평 경계 강조
  • 이 단계는 학습 없는 수동 필터 — 가중치를 사람이 직접 설계

7. 합성곱 연산을 직접 구현

for-loop로 커널이 이미지 위를 슬라이딩하면서 행렬곱을 수행하는 과정을 직접 짜봄.

7.1 슬라이딩 로직

STRIDE = 1
k_h, k_w = ver_kernel.size()
img_h, img_w = img_tensor.size()

final_buf = []
for i in range(0, img_h - k_h + 1, STRIDE):       # 위 → 아래
    feature_map_buf = []
    for j in range(0, img_w - k_w + 1, STRIDE):   # 왼쪽 → 오른쪽
        # 커널과 일치하는 구간 추출 (3×3)
        img_target = img_tensor[i:i+k_h, j:j+k_w]
        # 같은 자리끼리 곱하고 모두 합산
        feature_map_buf.append(torch.sum(img_target * ver_kernel))
    final_buf.append(feature_map_buf)

feature_map = torch.Tensor(final_buf)
feature_map.shape   # (148, 148) - 원본(150,150)에서 stride 1로 줄어든 크기
  • 이동 패턴 — 커널이 Z 형태로 이동 (오른쪽 끝→다음 줄로 내려감)
  • 연산 — img_target * ver_kernel은 원소별 곱(elementwise), torch.sum()으로 모두 합산
  • 출력 크기 — (img_h - k_h + 1, img_w - k_w + 1) = (148, 148), 원본보다 작아짐(padding 없는 VALID 모드)

7.2 커스텀 함수로 추상화

def cus_convolution(x, kernel, stride):
    img_h, img_w = x.size()
    k_h, k_w = kernel.size()
    s_h, s_w = stride, stride
    
    final_buf = []
    for i in range(0, img_h - k_h + 1, s_h):
        feature_map_buf = []
        for j in range(0, img_w - k_w + 1, s_w):
            img_target = x[i:i+k_h, j:j+k_w]
            feature_map_buf.append(torch.sum(img_target * kernel))
        final_buf.append(feature_map_buf)
    return torch.Tensor(final_buf)

ver_feature_map = cus_convolution(img_tensor, ver_kernel, STRIDE)
hor_feature_map = cus_convolution(img_tensor, hor_kernel, STRIDE)
mix_feature_map = cus_convolution(img_tensor, ver_kernel + hor_kernel, STRIDE)
  • 수직·수평·합성 세 가지 Feature Map을 동일 함수로 추출
  • 시각화 — plt.imshow(feature_map, cmap='binary')로 결과 비교 가능

8. 활성화 함수로 노이즈 보정

필터에 `-1`이 있어 Feature Map에 음수가 발생함. 그레이스케일은 0~255 범위라 음수는 노이즈로 표현됨 → 활성화 함수(ReLU)로 보정.

8.1 문제 인식

ver_feature_map.min(), ver_feature_map.max()
# 최솟값이 음수 → 시각화 시 "먹먹한" 노이즈 발생

8.2 ReLU로 음수 영역 제거

_, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=((5+1)*3, 5))
ax1.imshow(torch.relu(ver_feature_map), cmap='binary')
ax2.imshow(torch.relu(hor_feature_map), cmap='binary')
ax3.imshow(torch.relu(mix_feature_map), cmap='binary')
plt.show()
  • ReLU — 음수는 0으로 클리핑, 양수는 그대로 유지
  • 여기서의 역할 — 사실상 전처리 역할. CNN 내부에서는 비선형성 부여가 주 목적이지만, 이 단계에서는 음수 픽셀 보정용으로 작동

합성곱 출력 → ReLU 통과 = Activation Map. 합성곱층 다음에 활성화 함수가 따라붙는 이유가 바로 이 보정 효과 때문임. 음수를 0으로 만드는 단순한 연산이 시각적으로는 노이즈 제거 효과를 냄.


9. PyTorch CNN API로 구현

직접 짠 합성곱 함수와 동일한 결과를 `nn.Conv2d` API로 구현. 직접 구현 결과와 API 결과가 완전히 일치하는지 검증함.

9.1 nn.Conv2d 레이어 생성

import torch.nn as nn

# Conv2d(입력채널, 출력채널, 커널크기, stride, bias)
conv_layer = nn.Conv2d(1, 1, 3, 1, bias=False)
conv_layer
  • 인자 의미 — in_channels=1, out_channels=1, kernel_size=3, stride=1, bias=False
  • bias=False — 직접 구현 시 bias를 안 썼으므로 동일 조건으로 맞춤

9.2 가중치 초기 상태 확인

conv_layer.weight.data, conv_layer.weight.data.size()
# 랜덤 초기값으로 채워져 있음, size = (1, 1, 3, 3)
  • 초기값 — 학습 전에는 랜덤값으로 자동 초기화됨
  • 차원 — (out_channels, in_channels, k_h, k_w) = (1, 1, 3, 3)

9.3 가중치를 수직 커널로 교체

# 2D (3,3) → 4D (1,1,3,3)로 차원 확장
ver_kernel.expand(1, 1, ver_kernel.size(0), ver_kernel.size(1)).size()

# 가중치 교체
conv_layer.weight.data = ver_kernel.expand(
    1, 1, ver_kernel.size(0), ver_kernel.size(1)
)
  • 차원 맞추기 — PyTorch Conv2d는 4D 가중치 텐서 요구 → expand()로 차원 추가
  • 수동 가중치 주입 — 학습이 아닌 사전 정의된 커널을 강제로 주입

학습으로 얻을 가중치를 사람이 손으로 끼워넣는 방식. 실제 CNN 학습 시에는 이 가중치가 역전파를 통해 자동으로 최적값으로 조정됨. "학습 = 이 weight.data를 자동으로 튜닝하는 과정" 이라는 본질을 이 코드로 직관적으로 확인 가능.

9.4 데이터 차원 조정 후 통과

# PyTorch는 Channel First (NCHW)
# (150, 150) → (1, 1, 150, 150)
cur_img = img_tensor.expand(1, 1, img_tensor.size(0), img_tensor.size(1))
cur_img.size()   # torch.Size([1, 1, 150, 150])

feature_map = conv_layer(cur_img)
feature_map.size()   # torch.Size([1, 1, 148, 148])
  • PyTorch 입력 포맷 — NCHW (N=배치, C=채널, H=높이, W=너비)
  • 출력 크기 — 직접 구현과 동일하게 (148, 148)

9.5 직접 구현 결과와 API 결과 비교

torch.sum(feature_map == ver_feature_map)
# 148 * 148 = 21904 (전부 일치)
  • 완전 일치 검증 — True의 개수가 148×148과 같으면 모든 픽셀이 동일
  • 결론 — 직접 짠 합성곱과 nn.Conv2d는 수학적으로 동일한 연산

9.6 최종 시각화

plt.imshow(
    torch.relu(conv_layer(cur_img)).detach().numpy().reshape(148, 148),
    cmap='binary'
)
  • detach() — 텐서를 계산 그래프에서 분리, 역전파용 grad 추적 해제
  • numpy() — 시각화를 위해 PyTorch Tensor → NumPy 배열로 변환
  • reshape(148, 148) — 4D (1,1,148,148) → 2D (148,148)로 차원 축소

10. 요약

  • CNN의 본질 — 큰 이미지에서 작은 이미지로 축소하면서 특징을 추출·강화·압축하여 최종 분류에 수렴
  • 합성곱층 — 학습 대상은 커널의 가중치 + bias. 사진 앱의 필터처럼 사람이 설계할 수도 있고, 학습으로 자동 최적화도 가능
  • 풀링층 — 학습 파라미터 0개. Max Pooling은 선명하게, Average Pooling은 부드럽게
  • 활성화 함수의 두 역할 — CNN 내부 비선형성 부여 + 음수 픽셀 노이즈 보정(시각적 효과)
  • PyTorch는 NCHW, TensorFlow는 NHWC — 차원 순서가 다르므로 프레임워크 전환 시 반드시 재배열
  • nn.Conv2d의 weight.data — 학습이 자동으로 조정하는 핵심 파라미터. 직접 주입도 가능

CNN을 처음 배울 때 가장 헷갈리는 부분이 "가중치가 도대체 어디 있고 무엇이 학습되는가"임. 합성곱층의 weight는 커널 그 자체이고, 학습은 이 커널값을 데이터에 맞게 자동으로 튜닝하는 과정임. for-loop로 직접 합성곱을 구현해보고 nn.Conv2d.weight.data에 수동으로 값을 주입해보면, "학습된 모델 = 최적의 커널들을 찾아낸 모델"이라는 본질이 명확해짐. 회귀의 w → 퍼셉트론의 가중치 → CNN의 커널은 모두 같은 개념이며, 단지 차원과 적용 방식이 다를 뿐임.

'SK플래닛 ai활용 데이터엔지니어 과정 2기 > ML & DL' 카테고리의 다른 글

DL 5 - NLP 실습  (0) 2026.05.27
DL 4 - NLP 모델  (0) 2026.05.26
DL 2 - 전이학습  (0) 2026.05.26
DL 1 - 딥러닝 개요  (0) 2026.05.26
ML 3 - 머신러닝 지도학습 (회귀)  (0) 2026.05.25