[AI 101] CNN – 이미지를 이해하는 AI의 비밀


핵심 요약

“사람은 이미지를 보지만, AI는 숫자를 본다”는 말이 있습니다. 합성곱 신경망(CNN, Convolutional Neural Network)이미지를 숫자 행렬로 변환하여 필터(커널)로 훑으며 엣지, 텍스처, 패턴을 자동 추출합니다. 3×3 필터 하나가 수백만 픽셀 이미지에서 수직선을 99% 정확도로 감지하고, 풀링(Pooling)은 이미지 크기를 4분의 1로 압축하면서도 핵심 정보는 유지합니다. 2012년 AlexNet이 ImageNet에서 84% 정확도로 우승하며 CNN 시대를 열었고, 현재는 얼굴 인식(99.9%), 자율주행, 의료 영상 진단까지 인간 수준을 넘어섰습니다. 합성곱 연산부터 Max Pooling, 실제 응용까지 CNN의 모든 것을 완벽하게 설명합니다.


📍 목차

  1. 합성곱 신경망(CNN) 구조
  2. 합성곱 연산과 필터의 개념
  3. 풀링(Pooling) 레이어의 역할
  4. CNN의 실제 응용: 이미지 인식, 얼굴 인식, 자율주행
  5. 실전 CNN 설계 가이드

1. 합성곱 신경망(CNN) 구조

1-1. CNN이란?

합성곱 신경망(Convolutional Neural Network, CNN)이미지 데이터를 효과적으로 처리하기 위해 설계된 특수한 신경망입니다.

핵심 질문:

“왜 일반 신경망(MLP)으로는 이미지 처리가 어려울까?”

문제: 파라미터 폭발

이미지 크기픽셀 수MLP 은닉층 (100 뉴런)파라미터 수
28×28 (MNIST)784784 → 10078,400개
224×224 (ImageNet)150,528150,528 → 10015,052,800개
1920×1080 (Full HD)2,073,6002,073,600 → 100207,360,000개

결과: 메모리 부족, 학습 불가능

해결책: CNN – 지역적 연결 + 파라미터 공유

1-2. CNN의 전체 구조

기본 구조:

입력 이미지
  ↓
[합성곱층 1] → [활성화(ReLU)] → [풀링층 1]
  ↓
[합성곱층 2] → [활성화(ReLU)] → [풀링층 2]
  ↓
[합성곱층 3] → [활성화(ReLU)] → [풀링층 3]
  ↓
[Flatten: 1차원 변환]
  ↓
[완전연결층(FC)]
  ↓
[출력층 (Softmax)]
  ↓
분류 결과

대표 CNN 아키텍처: LeNet-5 (1998)

입력: 32×32 이미지
  ↓
Conv1: 6개 필터 (5×5) → 28×28×6
  ↓
Pooling1: Max Pooling (2×2) → 14×14×6
  ↓
Conv2: 16개 필터 (5×5) → 10×10×16
  ↓
Pooling2: Max Pooling (2×2) → 5×5×16
  ↓
Flatten: 5×5×16 = 400
  ↓
FC1: 120 뉴런
  ↓
FC2: 84 뉴런
  ↓
Output: 10 클래스 (0~9 숫자)

1-3. CNN vs MLP 비교

항목MLP (Fully Connected)CNN (Convolutional)
연결 방식모든 뉴런 연결지역적 연결
파라미터매우 많음적음 (공유)
공간 정보무시 (1D 벡터)유지 (2D/3D)
이미지 처리비효율적최적화
학습 속도느림빠름
적합 데이터표 형식이미지, 비디오

파라미터 비교 (28×28 이미지):

# MLP
input_size = 28 * 28 = 784
hidden_size = 100
params = 784 * 100 = 78,400개

# CNN
filter_size = 3 * 3 = 9
num_filters = 32
params = 9 * 32 = 288개  # 272배 적음!

2. 합성곱 연산과 필터의 개념

2-1. 합성곱 연산이란?

합성곱(Convolution)작은 필터(커널)로 이미지를 훑으며 특징을 추출하는 연산입니다.

수식:
[
(I * K)(i, j) = \sum_{m}\sum_{n} I(i+m, j+n) \cdot K(m, n)
]

  • (I): 입력 이미지
  • (K): 필터(커널)
  • (*): 합성곱 연산
  • ((i, j)): 출력 위치

2-2. 필터(커널)의 역할

필터(Filter) 또는 커널(Kernel)이미지에서 특정 패턴을 감지하는 작은 행렬입니다.

예시: 수직 엣지 감지 필터 (3×3)

필터 K:
[-1  0  1]
[-1  0  1]
[-1  0  1]

적용 과정:

입력 이미지 (5×5):
[0 0 0 1 1]
[0 0 0 1 1]
[0 0 0 1 1]
[0 0 0 1 1]
[0 0 0 1 1]

↓ (필터 적용)

특징 맵 (3×3):
[0  4  0]
[0  4  0]
[0  4  0]

해석: 수직선(경계)이 감지됨!

2-3. 합성곱 연산 상세

단계별 계산:

1단계: 필터 배치

이미지:          필터:
[1 2 3]         [0 1 0]
[4 5 6]    ×    [1 0 1]
[7 8 9]         [0 1 0]

2단계: 원소별 곱셈 + 합산

1×0 + 2×1 + 3×0 +
4×1 + 5×0 + 6×1 +
7×0 + 8×1 + 9×0 = 2+4+6+8 = 20

3단계: 필터 이동 (Stride)

Stride = 1:
위치 (0,0) → (0,1) → (0,2) → (1,0) → ...

Python 구현:

import numpy as np

def convolution_2d(image, kernel):
    """2D 합성곱 연산"""
    i_h, i_w = image.shape
    k_h, k_w = kernel.shape

    # 출력 크기 계산
    o_h = i_h - k_h + 1
    o_w = i_w - k_w + 1

    output = np.zeros((o_h, o_w))

    # 합성곱 연산
    for i in range(o_h):
        for j in range(o_w):
            # 이미지 일부 추출
            region = image[i:i+k_h, j:j+k_w]
            # 원소별 곱셈 후 합산
            output[i, j] = np.sum(region * kernel)

    return output

# 예시
image = np.array([
    [1, 2, 3, 0, 1],
    [4, 5, 6, 2, 3],
    [7, 8, 9, 4, 5],
    [1, 2, 3, 6, 7],
    [4, 5, 6, 8, 9]
])

# 수직 엣지 필터
kernel = np.array([
    [-1, 0, 1],
    [-1, 0, 1],
    [-1, 0, 1]
])

feature_map = convolution_2d(image, kernel)
print("특징 맵:\n", feature_map)

2-4. Stride와 Padding

Stride (보폭)

Stride: 필터가 이동하는 간격

Stride이동출력 크기
11칸씩큰 출력
22칸씩작은 출력
33칸씩매우 작은 출력

출력 크기 공식:
[
\text{Output Size} = \frac{n – f}{s} + 1
]

  • (n): 입력 크기
  • (f): 필터 크기
  • (s): Stride

예시:

# 입력: 5×5, 필터: 3×3, Stride=1
output_size = (5 - 3) / 1 + 1 = 3

# Stride=2
output_size = (5 - 3) / 2 + 1 = 2

Padding (패딩)

Padding: 입력 이미지 주변에 값(보통 0)을 추가

목적:

  1. 출력 크기 유지
  2. 가장자리 정보 보존

종류:

Padding설명출력 크기
Valid패딩 없음작아짐
Same패딩 추가입력과 동일

예시:

원본 (3×3):          Padding=1 추가 (5×5):
[1 2 3]             [0 0 0 0 0]
[4 5 6]      →      [0 1 2 3 0]
[7 8 9]             [0 4 5 6 0]
                    [0 7 8 9 0]
                    [0 0 0 0 0]

Same Padding 계산:
[
\text{Padding} = \frac{f – 1}{2}
]

TensorFlow/Keras 구현:

from tensorflow.keras.layers import Conv2D

# Valid Padding (패딩 없음)
conv_valid = Conv2D(filters=32, kernel_size=3, padding='valid')

# Same Padding (출력 크기 유지)
conv_same = Conv2D(filters=32, kernel_size=3, padding='same')

2-5. 다양한 필터 예시

1️⃣ 수평 엣지 감지

[[-1 -1 -1]
 [ 0  0  0]
 [ 1  1  1]]

2️⃣ 블러 필터 (평균)

[[1/9 1/9 1/9]
 [1/9 1/9 1/9]
 [1/9 1/9 1/9]]

3️⃣ 샤프닝 필터

[[ 0 -1  0]
 [-1  5 -1]
 [ 0 -1  0]]

핵심: CNN은 이런 필터를 사람이 설계하지 않고, 데이터로부터 자동 학습합니다!


3. 풀링(Pooling) 레이어의 역할

3-1. 풀링이란?

풀링(Pooling)특징 맵의 크기를 줄이는 다운샘플링 기법입니다.

목적:

  1. 파라미터 감소 → 계산 효율 증가
  2. 과적합 방지
  3. 평행이동 불변성 확보

3-2. Max Pooling

Max Pooling: 영역에서 최댓값만 선택

예시: 2×2 Max Pooling

입력 (4×4):          출력 (2×2):
[1  3  2  4]
[5  6  1  2]    →   [6  4]
[9  7  3  0]        [9  5]
[4  2  1  5]

계산 과정:

영역 1:  [1 3]  → Max = 6
         [5 6]

영역 2:  [2 4]  → Max = 4
         [1 2]

영역 3:  [9 7]  → Max = 9
         [4 2]

영역 4:  [3 0]  → Max = 5
         [1 5]

특징:

  • 주요 특징 강조
  • 노이즈 제거
  • 위치 불변성

3-3. Average Pooling

Average Pooling: 영역의 평균값 계산

입력 (4×4):          출력 (2×2):
[1  3  2  4]
[5  6  1  2]    →   [3.75  2.25]
[9  7  3  0]        [5.50  2.25]
[4  2  1  5]

계산:

영역 1: (1+3+5+6)/4 = 3.75
영역 2: (2+4+1+2)/4 = 2.25
영역 3: (9+7+4+2)/4 = 5.50
영역 4: (3+0+1+5)/4 = 2.25

3-4. Max Pooling vs Average Pooling

항목Max PoolingAverage Pooling
연산최댓값 선택평균값 계산
특징 강조⭐⭐⭐⭐⭐⭐⭐
노이즈 제거⭐⭐⭐⭐⭐⭐⭐
정보 손실높음낮음
사용 빈도매우 높음 (90%)낮음 (10%)
적합한 경우엣지, 텍스처 강조부드러운 변화

Python 구현:

import numpy as np

def max_pooling_2d(feature_map, pool_size=2):
    """2D Max Pooling"""
    f_h, f_w = feature_map.shape
    p_h = p_w = pool_size

    o_h = f_h // p_h
    o_w = f_w // p_w

    output = np.zeros((o_h, o_w))

    for i in range(o_h):
        for j in range(o_w):
            # 풀링 영역
            region = feature_map[
                i*p_h:(i+1)*p_h,
                j*p_w:(j+1)*p_w
            ]
            # 최댓값
            output[i, j] = np.max(region)

    return output

# 예시
feature_map = np.array([
    [1, 3, 2, 4],
    [5, 6, 1, 2],
    [9, 7, 3, 0],
    [4, 2, 1, 5]
])

pooled = max_pooling_2d(feature_map, pool_size=2)
print("Max Pooling 결과:\n", pooled)

TensorFlow/Keras 구현:

from tensorflow.keras.layers import MaxPooling2D, AveragePooling2D

# Max Pooling (2×2, stride=2)
max_pool = MaxPooling2D(pool_size=2, strides=2)

# Average Pooling
avg_pool = AveragePooling2D(pool_size=2, strides=2)

3-5. 풀링의 효과

크기 감소:

입력: 224×224×64 특징 맵
  ↓ (2×2 Max Pooling)
출력: 112×112×64 (크기 1/4, 파라미터 1/4)

평행이동 불변성:

원본 이미지:         1픽셀 이동:
●●○○               ○●●○
●●○○      vs       ○●●○
○○○○               ○○○○
○○○○               ○○○○

Max Pooling 후:    Max Pooling 후:
[1  0]             [1  0]
[0  0]             [0  0]
→ 동일한 결과!

4. CNN의 실제 응용: 이미지 인식, 얼굴 인식, 자율주행

4-1. 이미지 분류 (ImageNet)

ImageNet 대회:

  • 데이터: 1,400만 이미지, 1,000 클래스
  • 과제: 고양이, 개, 자동차 등 분류

역사적 순간들:

연도모델층 수Top-5 Error혁신
2010전통 ML28.2%수작업 Feature
2012AlexNet816.4%CNN 최초 우승
2014VGGNet197.3%깊은 네트워크
2015ResNet1523.6%Skip Connection
2021ViT241.8%Transformer
인간5.1%CNN이 인간 넘어섬!

AlexNet 아키텍처 (2012):

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout

model = Sequential([
    # Conv1
    Conv2D(96, 11, strides=4, activation='relu', input_shape=(227, 227, 3)),
    MaxPooling2D(3, strides=2),

    # Conv2
    Conv2D(256, 5, padding='same', activation='relu'),
    MaxPooling2D(3, strides=2),

    # Conv3
    Conv2D(384, 3, padding='same', activation='relu'),

    # Conv4
    Conv2D(384, 3, padding='same', activation='relu'),

    # Conv5
    Conv2D(256, 3, padding='same', activation='relu'),
    MaxPooling2D(3, strides=2),

    # FC
    Flatten(),
    Dense(4096, activation='relu'),
    Dropout(0.5),
    Dense(4096, activation='relu'),
    Dropout(0.5),
    Dense(1000, activation='softmax')  # 1000 클래스
])

model.summary()

4-2. 얼굴 인식

FaceNet (Google, 2015):

  • 정확도: 99.63% (인간: 97.5%)
  • 응용: 스마트폰 잠금 해제, 출입 통제

작동 원리:

입력 얼굴 이미지
  ↓
[CNN] → 128차원 임베딩 벡터
  ↓
거리 계산 (Euclidean Distance)
  ↓
동일인 여부 판단 (임계값 비교)

Triplet Loss:

Anchor (기준)    Positive (동일인)    Negative (타인)
    A    ────────    P               N

목표: d(A, P) 

실전 예제:

from tensorflow.keras.applications import VGG16
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D

# 사전 학습 VGG16 사용
base_model = VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3))

# 커스텀 헤드
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(128, activation='relu')(x)  # 128차원 임베딩
output = Dense(1, activation='sigmoid')(x)  # 동일인 여부

model = Model(inputs=base_model.input, outputs=output)

# 학습
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

4-3. 객체 탐지 (Object Detection)

YOLO (You Only Look Once):

  • 속도: 45 FPS (실시간)
  • 정확도: 80% mAP

작동 방식:

입력 이미지
  ↓
[CNN] → 그리드 분할 (7×7)
  ↓
각 그리드마다:
  - 객체 유무 (Confidence)
  - 경계 상자 (Bounding Box)
  - 클래스 확률
  ↓
NMS (Non-Maximum Suppression)
  ↓
최종 탐지 결과

예시 출력:

[
  {'class': 'person', 'confidence': 0.98, 'box': [120, 50, 300, 400]},
  {'class': 'car', 'confidence': 0.95, 'box': [400, 200, 600, 350]},
  {'class': 'dog', 'confidence': 0.87, 'box': [50, 300, 200, 450]}
]

4-4. 자율주행

Tesla Autopilot CNN 구조:

8개 카메라 입력
  ↓
[Multi-scale CNN]
  ├─ 차선 감지
  ├─ 차량 감지
  ├─ 보행자 감지
  ├─ 신호등 인식
  └─ 표지판 인식
  ↓
[Fusion Layer]
  ↓
[제어 시스템]
  ├─ 조향
  ├─ 가속
  └─ 제동

핵심 기술:

기술설명CNN 역할
Lane Detection차선 인식엣지 감지
Object Detection차량/보행자YOLO, Faster R-CNN
Semantic Segmentation픽셀 단위 분류FCN, U-Net
Depth Estimation거리 측정Stereo CNN

실시간 처리:

import cv2
from tensorflow.keras.models import load_model

# 사전 학습 모델 로드
model = load_model('lane_detection.h5')

# 웹캠/차량 카메라
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # 전처리
    img = cv2.resize(frame, (224, 224))
    img = img / 255.0
    img = np.expand_dims(img, axis=0)

    # 예측
    prediction = model.predict(img)

    # 시각화
    cv2.imshow('Lane Detection', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

4-5. 의료 영상 진단

폐암 탐지 (Google, 2019):

  • 정확도: 94.4% (의사: 88.9%)
  • 데이터: CT 스캔 이미지

작동 원리:

CT 스캔 이미지
  ↓
[3D CNN] (512×512×100 슬라이스)
  ↓
특징 추출:
  - 결절(nodule) 위치
  - 크기
  - 모양
  - 밀도
  ↓
[분류기]
  ├─ 양성 (0.05)
  └─ 악성 (0.95)
  ↓
CAM (Class Activation Map)
  - 의사에게 근거 제시

5. 실전 CNN 설계 가이드

5-1. CNN 설계 체크리스트

1단계: 데이터 확인

  • [ ] 이미지 크기: 224×224, 299×299 등
  • [ ] 채널: RGB (3), Grayscale (1)
  • [ ] 클래스 수: 2 (이진), 10, 1000 등

2단계: 아키텍처 선택

목적추천 모델크기정확도
빠른 프로토타입MobileNet작음중간
일반 분류ResNet-50중간높음
최고 정확도EfficientNet-B7최고
실시간 처리YOLO, MobileNet작음중간

3단계: 전이 학습 vs 처음부터

데이터 크기방법이유
전이 학습 (Fine-tuning)데이터 부족
1,000 ~ 10,000개전이 학습 + 일부 학습효율성
> 10,000개처음부터 학습 가능충분한 데이터

5-2. 전이 학습 실전 예제

CIFAR-10 분류 (10개 클래스)

from tensorflow.keras.applications import ResNet50
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical

# 데이터 로드
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
X_train = X_train / 255.0
X_test = X_test / 255.0
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)

# 사전 학습 ResNet50 (ImageNet)
base_model = ResNet50(
    weights='imagenet',
    include_top=False,  # 최상위 FC층 제외
    input_shape=(32, 32, 3)
)

# Base model 동결 (학습 안 함)
base_model.trainable = False

# 커스텀 헤드 추가
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(256, activation='relu')(x)
x = Dropout(0.5)(x)
output = Dense(10, activation='softmax')(x)

model = Model(inputs=base_model.input, outputs=output)

# 컴파일
model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

# 학습
history = model.fit(
    X_train, y_train,
    validation_data=(X_test, y_test),
    epochs=10,
    batch_size=64
)

# 평가
test_loss, test_acc = model.evaluate(X_test, y_test)
print(f"Test Accuracy: {test_acc:.2%}")

5-3. 하이퍼파라미터 튜닝

필터 개수:

필터 개수이유
Conv132~64저수준 Feature
Conv264~128중수준 Feature
Conv3128~256고수준 Feature
Conv4+256~512추상적 Feature

필터 크기:

크기장점단점사용
1×1채널 조정, 빠름공간 정보 없음차원 축소
3×3균형 좋음기본 (90%)
5×5넓은 수용 영역느림초기 층
7×7매우 넓음매우 느림첫 번째 층만

배치 크기:

# GPU 메모리에 따라
batch_sizes = {
    '2GB': 16,
    '4GB': 32,
    '8GB': 64,
    '16GB': 128,
    '24GB': 256
}

5-4. 데이터 증강 (Data Augmentation)

과적합 방지 + 일반화 향상

from tensorflow.keras.preprocessing.image import ImageDataGenerator

# 데이터 증강 설정
datagen = ImageDataGenerator(
    rotation_range=20,       # 20도 회전
    width_shift_range=0.2,   # 가로 20% 이동
    height_shift_range=0.2,  # 세로 20% 이동
    horizontal_flip=True,    # 좌우 반전
    zoom_range=0.2,          # 20% 확대/축소
    shear_range=0.2,         # 전단 변환
    fill_mode='nearest'      # 빈 공간 채우기
)

# 학습
model.fit(
    datagen.flow(X_train, y_train, batch_size=32),
    validation_data=(X_test, y_test),
    epochs=50
)

효과:

원본 데이터증강 후 데이터정확도 향상
1,000개10,000개 (10배)+15%p

FAQ: 초보자가 자주 묻는 질문

Q1. CNN은 왜 이미지 처리에 특화되어 있나요?

A. 3가지 이유: (1) 지역적 연결 – 이미지는 인접 픽셀끼리 관련 높음, (2) 파라미터 공유 – 같은 필터를 전체 이미지에 적용 → 파라미터 272배 감소, (3) 계층적 특징 – 층마다 엣지 → 텍스처 → 객체 순으로 학습.

Q2. 필터는 어떻게 학습되나요?

A. 역전파 알고리즘으로 자동 학습됩니다. 처음엔 랜덤 값으로 시작 → 손실함수 계산 → 경사하강법으로 필터 값 업데이트 → 반복. 최종적으로 수직선, 곡선, 눈, 얼굴 등을 감지하는 필터가 자동으로 생성됩니다.

Q3. Max Pooling vs Average Pooling, 언제 뭘 쓰나요?

A. Max Pooling을 90% 이상 사용합니다. 이유: (1) 주요 특징 강조, (2) 노이즈 제거, (3) 더 좋은 성능. Average Pooling은 마지막 Global Pooling에서만 가끔 사용 (전체 평균).

Q4. 전이 학습은 왜 성능이 좋나요?

A. ImageNet 1,400만 이미지로 사전 학습된 모델은 이미 엣지, 텍스처, 패턴 감지 능력을 갖추고 있습니다. 이를 내 데이터(1,000개)에 Fine-tuning하면 처음부터 학습(10만 개 필요)보다 훨씬 적은 데이터로 높은 성능 달성.

Q5. CNN은 이미지 외에 다른 데이터에도 사용 가능한가요?

A. Yes! (1) 시계열 데이터: 1D CNN으로 주가, 심전도 분석, (2) 오디오: 스펙트로그램을 이미지로 변환, (3) 자연어: 문장을 2D 행렬로 표현, (4) 동영상: 3D CNN으로 행동 인식.


외부 참고 자료

CNN을 더 깊게 배우고 싶다면:


정리: 이 글에서 배운 것

CNN 구조: Conv → ReLU → Pooling → FC → Softmax
합성곱 연산: 필터로 이미지 훑기, 특징 맵 생성
필터: 엣지, 텍스처 자동 학습, 파라미터 공유
Max Pooling: 최댓값 선택, 크기 1/4, 특징 강조
응용: ImageNet 95%, 얼굴 인식 99.6%, 자율주행
전이 학습: ResNet-50 사전 학습 → Fine-tuning

다음 편에서는 RNN과 LSTM – 시퀀스 데이터를 이해하는 AI에 대해 자세히 알아봅니다. 특히 순환 신경망, 기울기 소실, LSTM 구조를 완벽 설명하겠습니다.


같이보기

답글 남기기

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