이미지 로딩 중...

CNN 기초 Convolution과 Pooling 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 23. · 0 Views

CNN 기초 Convolution과 Pooling 완벽 가이드

CNN의 핵심인 Convolution과 Pooling을 초급자도 쉽게 이해할 수 있도록 설명합니다. 이미지 인식의 원리부터 실제 코드 구현까지, 실무에서 바로 활용 가능한 내용을 담았습니다.


목차

  1. CNN이란 무엇인가
  2. Convolution 연산의 원리
  3. Pooling으로 핵심만 남기기
  4. 필터와 특징 맵의 관계
  5. Stride와 Padding 이해하기
  6. 활성화 함수 ReLU의 역할
  7. CNN 층 쌓기 전략
  8. 실전 CNN 학습하기

1. CNN이란 무엇인가

시작하며

여러분이 사진 속에서 고양이를 찾으려고 할 때, 우리 눈은 어떻게 작동할까요? 먼저 귀의 뾰족한 모양을 찾고, 수염을 찾고, 둥근 눈을 찾습니다.

작은 특징들을 하나씩 모아서 "아, 이건 고양이구나!"라고 판단하죠. 컴퓨터도 똑같은 방식으로 이미지를 이해합니다.

하지만 일반적인 프로그램은 이미지 전체를 한 번에 보려고 해서 매우 비효율적입니다. 마치 책을 한 글자씩 읽지 않고 한 페이지를 통째로 외우려는 것과 같습니다.

바로 이럴 때 필요한 것이 CNN(Convolutional Neural Network)입니다. CNN은 이미지를 작은 조각으로 나누어 보면서 중요한 특징들을 자동으로 찾아내는 똑똑한 방법입니다.

개요

간단히 말해서, CNN은 이미지를 이해하는 인공지능의 눈입니다. 사람의 눈이 물체를 볼 때 작은 부분부터 전체를 파악하듯이, CNN도 같은 방식으로 작동합니다.

왜 CNN이 필요할까요? 일반적인 방법으로 이미지를 분석하면 100x100 픽셀 이미지만 해도 10,000개의 숫자를 한 번에 처리해야 합니다.

이는 컴퓨터에게 엄청난 부담이고, 학습도 매우 느립니다. 예를 들어, 고양이 사진 1,000장을 학습시키려면 몇 시간이 걸릴 수도 있습니다.

기존에는 이미지의 모든 픽셀을 직접 연결했다면, CNN은 작은 영역씩 나누어 처리합니다. 마치 퍼즐을 맞출 때 한 조각씩 보는 것처럼요.

CNN의 핵심 특징은 세 가지입니다. 첫째, 지역적 패턴을 찾습니다(귀, 눈, 수염 같은 것들).

둘째, 같은 패턴을 이미지 어디서든 찾을 수 있습니다. 셋째, 단계적으로 복잡한 패턴을 만들어갑니다(선 → 곡선 → 귀 → 고양이).

이러한 특징들이 CNN을 이미지 인식에서 가장 강력한 도구로 만들어줍니다.

코드 예제

import tensorflow as tf
from tensorflow import keras

# CNN 모델의 기본 구조
model = keras.Sequential([
    # 입력: 28x28 크기의 흑백 이미지
    keras.layers.Input(shape=(28, 28, 1)),

    # Convolution: 작은 패턴 찾기 (32개의 필터 사용)
    keras.layers.Conv2D(32, kernel_size=3, activation='relu'),

    # Pooling: 중요한 정보만 남기기
    keras.layers.MaxPooling2D(pool_size=2),

    # 평탄화: 1차원으로 펴기
    keras.layers.Flatten(),

    # 최종 분류: 10개 클래스로 분류
    keras.layers.Dense(10, activation='softmax')
])

설명

이것이 하는 일: CNN은 이미지 데이터를 여러 층(layer)을 거치면서 점점 더 복잡한 특징을 학습합니다. 마치 아이가 처음에는 선을 그리고, 그 다음 도형을, 나중에는 사람 얼굴을 그리게 되는 것처럼요.

첫 번째로, Input layer는 이미지를 받아들입니다. 28x28 크기의 흑백 이미지라면 784개의 숫자로 이루어진 격자무늬입니다.

각 숫자는 0(검정)부터 255(하양)까지의 밝기를 나타냅니다. 이 숫자들이 CNN의 원료가 됩니다.

그 다음으로, Conv2D layer가 실행되면서 이미지에서 패턴을 찾습니다. 3x3 크기의 작은 창문(필터)을 이미지 위에서 슬라이드하면서 "이 부분에 가로선이 있나?", "여기 둥근 모양이 있나?" 같은 질문을 합니다.

32개의 서로 다른 필터가 각각 다른 패턴을 찾아냅니다. MaxPooling2D layer는 찾아낸 패턴 중 가장 중요한 것만 남깁니다.

2x2 영역을 하나로 합치면서 크기를 절반으로 줄입니다. 이렇게 하면 계산량이 줄어들고, 중요한 특징만 남게 됩니다.

마치 요약본을 만드는 것과 같습니다. Flatten layer는 2차원 이미지를 1차원 리스트로 펴줍니다.

그리고 마지막 Dense layer가 이 정보를 바탕으로 "이건 0이다", "이건 1이다" 같은 최종 판단을 내립니다. 여러분이 이 코드를 사용하면 손글씨 숫자를 95% 이상의 정확도로 인식할 수 있습니다.

불과 몇 줄의 코드로 이미지 인식 AI를 만들 수 있다는 게 놀랍지 않나요? 실무에서는 이 구조를 기반으로 얼굴 인식, 의료 영상 분석, 자율주행차 등에 활용합니다.

실전 팁

💡 처음에는 작은 데이터셋(MNIST, CIFAR-10)으로 연습하세요. 큰 데이터로 시작하면 학습에 시간이 오래 걸려서 실험하기 어렵습니다.

💡 흔한 실수: 이미지 크기를 정규화하지 않는 것입니다. 픽셀 값을 0-1 사이로 나누면 학습이 훨씬 빨라집니다 (예: x_train / 255.0).

💡 모델이 너무 복잡하면 과적합(overfitting)이 발생합니다. 검증 데이터의 정확도가 떨어지면 Dropout layer를 추가하세요.

💡 GPU를 사용하면 학습 속도가 10-100배 빨라집니다. Google Colab을 사용하면 무료로 GPU를 쓸 수 있습니다.

💡 model.summary()를 호출하면 각 층의 크기와 파라미터 개수를 볼 수 있어서 디버깅에 매우 유용합니다.


2. Convolution 연산의 원리

시작하며

여러분이 사진에서 얼굴을 찾는다고 상상해보세요. 사진 전체를 한 번에 보는 것보다, 작은 돋보기로 한 부분씩 살펴보면서 "여기 눈이 있네", "여기 코가 있네"라고 찾는 게 더 쉽지 않을까요?

컴퓨터도 똑같습니다. 이미지 전체를 한 번에 분석하려면 너무 많은 계산이 필요합니다.

게다가 같은 특징(예: 눈)이 이미지의 왼쪽에 있든 오른쪽에 있든 같은 방법으로 찾아야 효율적입니다. 바로 이럴 때 필요한 것이 Convolution(합성곱) 연산입니다.

작은 필터를 이미지 위에서 슬라이드하면서 패턴을 찾아내는 아주 똑똑한 방법입니다.

개요

간단히 말해서, Convolution은 작은 창문(필터)을 이미지 위에서 미끄러뜨리면서 패턴을 찾는 연산입니다. 마치 보물찾기를 할 때 금속탐지기를 땅 위에서 천천히 움직이는 것과 같습니다.

왜 Convolution이 필요한지 구체적으로 살펴볼까요? 일반적인 신경망은 이미지의 위치 정보를 무시합니다.

하지만 이미지에서는 "가까운 픽셀끼리는 관련이 있다"는 사실이 매우 중요합니다. 예를 들어, 눈동자의 검은 점 주변에는 흰자가 있고, 그 주변에는 눈꺼풀이 있죠.

Convolution은 이런 지역적 패턴을 완벽하게 포착합니다. 기존에는 이미지의 모든 픽셀에 가중치를 하나씩 연결했다면, Convolution은 작은 필터 하나를 이미지 전체에 재사용합니다.

100x100 이미지라도 3x3 필터 하나면 충분합니다. Convolution의 핵심 특징은 다음과 같습니다.

첫째, 파라미터 공유(parameter sharing)로 메모리를 절약합니다. 둘째, 위치 불변성(translation invariance)으로 패턴이 어디 있든 찾아냅니다.

셋째, 계층적 특징 추출(hierarchical feature extraction)로 점점 복잡한 패턴을 만들어갑니다. 이러한 특징들이 Convolution을 CNN의 핵심 연산으로 만들어줍니다.

코드 예제

import numpy as np

# 5x5 입력 이미지 (간단한 예시)
image = np.array([
    [1, 2, 3, 2, 1],
    [2, 4, 5, 4, 2],
    [3, 5, 8, 5, 3],  # 중앙이 밝은 이미지
    [2, 4, 5, 4, 2],
    [1, 2, 3, 2, 1]
])

# 3x3 필터 (가로선 감지)
kernel = np.array([
    [-1, -1, -1],  # 위쪽은 어둡게
    [ 0,  0,  0],  # 중간은 무시
    [ 1,  1,  1]   # 아래쪽은 밝게
])

# Convolution 연산 수행
output = np.zeros((3, 3))  # 출력 크기: (5-3+1) x (5-3+1)
for i in range(3):
    for j in range(3):
        # 3x3 영역을 잘라서 필터와 곱한 후 합계
        region = image[i:i+3, j:j+3]
        output[i, j] = np.sum(region * kernel)

print("Convolution 결과:")
print(output)

설명

이것이 하는 일: Convolution 연산은 필터(커널)라는 작은 행렬을 이미지 위에서 한 칸씩 이동하면서, 각 위치에서 필터와 이미지의 일부분을 곱해서 합산합니다. 그 결과가 해당 위치에서 패턴이 얼마나 강하게 나타나는지를 알려줍니다.

첫 번째로, 필터를 정의합니다. 위 코드의 kernel은 가로선을 감지하는 필터입니다.

위쪽 행은 -1(어두워야 함), 아래쪽 행은 +1(밝아야 함)로 설정되어 있습니다. 만약 이미지에서 위는 어둡고 아래는 밝은 부분이 있다면, 이 필터는 큰 양수 값을 출력합니다.

그 다음으로, 이중 for 루프가 이미지를 순회합니다. i=0, j=0일 때는 왼쪽 위 3x3 영역을, i=0, j=1일 때는 한 칸 오른쪽으로 이동한 3x3 영역을 선택합니다.

각 위치에서 region * kernel로 요소별 곱셈을 하고, np.sum()으로 모두 더합니다. 예를 들어 설명해볼까요?

만약 이미지의 어떤 부분이 [[1,1,1], [0,0,0], [5,5,5]]라면 (위는 어둡고 아래는 밝음), 필터와 곱하면 (-1-1-1) + (0+0+0) + (5+5+5) = 12가 됩니다. 큰 양수죠!

이는 "여기 가로선이 있다!"는 신호입니다. 출력 크기는 (입력 크기 - 필터 크기 + 1)이 됩니다.

5x5 이미지에 3x3 필터를 적용하면 3x3 결과가 나옵니다. 이는 필터가 이동할 수 있는 위치의 개수와 같습니다.

여러분이 이 코드를 실행하면 Convolution이 어떻게 작동하는지 직관적으로 이해할 수 있습니다. 실무에서는 TensorFlow나 PyTorch가 이 연산을 GPU로 초고속으로 처리해줍니다.

수백 개의 필터를 동시에 적용해도 1초도 안 걸립니다.

실전 팁

💡 필터 크기는 보통 3x3 또는 5x5를 사용합니다. 3x3이 가장 효율적이고 널리 쓰입니다(VGG, ResNet 등).

💡 stride를 2로 설정하면 필터가 두 칸씩 이동해서 출력 크기가 절반이 됩니다. Pooling 대신 사용할 수도 있습니다.

💡 padding='same'을 설정하면 입력과 출력 크기가 같아집니다. 이미지 가장자리에 0을 채워서 정보 손실을 막습니다.

💡 필터의 초기값은 랜덤으로 설정됩니다. 학습 과정에서 자동으로 최적의 패턴을 찾아가므로 걱정하지 마세요.

💡 첫 번째 층의 필터를 시각화해보세요. 학습 후 필터를 보면 가로선, 세로선, 대각선 같은 기본 패턴들이 나타납니다.


3. Pooling으로 핵심만 남기기

시작하며

여러분이 책을 읽고 중요한 내용을 노트에 정리한다고 생각해보세요. 모든 문장을 다 베껴 쓰나요?

아니죠. 핵심 키워드와 중요한 문장만 골라서 적습니다.

이렇게 하면 나중에 복습할 때 훨씬 효율적입니다. CNN도 마찬가지입니다.

Convolution을 거치면 이미지가 여러 개의 특징 맵(feature map)으로 변환됩니다. 하지만 이 맵들에는 너무 많은 정보가 들어있어서 계산량이 많고, 메모리도 많이 차지합니다.

게다가 세세한 위치 정보는 최종 분류에 별로 중요하지 않습니다. 바로 이럴 때 필요한 것이 Pooling입니다.

특징 맵에서 가장 중요한 정보만 추출해서 크기를 줄이는 똑똑한 방법입니다.

개요

간단히 말해서, Pooling은 이미지를 작은 구역으로 나누고, 각 구역에서 대표값 하나만 선택하는 연산입니다. 마치 100명의 의견을 모을 때 10명씩 그룹을 만들어 각 그룹의 대표 의견만 듣는 것과 같습니다.

왜 Pooling이 필요할까요? 첫째, 계산량을 줄입니다.

2x2 Pooling을 사용하면 데이터 크기가 1/4로 줄어듭니다. 둘째, 위치 변화에 강건해집니다.

고양이가 사진의 정확히 중앙에 있든 약간 왼쪽에 있든 같은 패턴으로 인식합니다. 예를 들어, 얼굴 인식 앱은 사람이 카메라 앞에서 조금 움직여도 정확하게 인식해야 합니다.

기존에는 모든 픽셀 정보를 유지했다면, Pooling은 "이 영역에서 가장 강한 신호만 있으면 충분해"라고 판단합니다. 이는 과적합을 방지하는 효과도 있습니다.

Pooling의 핵심 특징은 다음과 같습니다. 첫째, 파라미터가 없어서 학습할 필요가 없습니다.

둘째, 각 채널(필터)에 독립적으로 적용됩니다. 셋째, 공간적 크기만 줄이고 채널 수는 유지합니다.

이러한 특징들이 Pooling을 CNN의 필수 구성요소로 만들어줍니다.

코드 예제

import numpy as np

# 4x4 특징 맵 (Convolution 결과)
feature_map = np.array([
    [1, 3, 2, 4],
    [5, 6, 1, 2],
    [7, 8, 3, 1],
    [2, 1, 4, 5]
])

print("원본 특징 맵 (4x4):")
print(feature_map)

# Max Pooling (2x2, stride=2)
max_pooled = np.zeros((2, 2))
for i in range(2):
    for j in range(2):
        # 2x2 영역에서 최댓값 선택
        region = feature_map[i*2:(i+1)*2, j*2:(j+1)*2]
        max_pooled[i, j] = np.max(region)

print("\nMax Pooling 결과 (2x2):")
print(max_pooled)

# Average Pooling (2x2, stride=2)
avg_pooled = np.zeros((2, 2))
for i in range(2):
    for j in range(2):
        # 2x2 영역의 평균값 계산
        region = feature_map[i*2:(i+1)*2, j*2:(j+1)*2]
        avg_pooled[i, j] = np.mean(region)

print("\nAverage Pooling 결과 (2x2):")
print(avg_pooled)

설명

이것이 하는 일: Pooling은 특징 맵을 일정한 크기의 윈도우로 나누고, 각 윈도우에서 하나의 대표값을 선택합니다. Max Pooling은 최댓값을, Average Pooling은 평균값을 선택합니다.

첫 번째로, 원본 특징 맵을 준비합니다. 4x4 크기의 맵은 Convolution 연산 후의 결과물입니다.

각 숫자는 해당 위치에서 특정 패턴이 얼마나 강하게 나타나는지를 의미합니다. 숫자가 클수록 패턴이 강합니다.

그 다음으로, 2x2 윈도우를 적용합니다. 왼쪽 위 2x2 영역 [[1,3], [5,6]]을 보면, Max Pooling은 가장 큰 값인 6을 선택합니다.

이는 "이 영역에서 패턴이 가장 강하게 나타난 곳은 6이다"는 의미입니다. Average Pooling은 (1+3+5+6)/4 = 3.75를 계산합니다.

stride=2로 설정했기 때문에 윈도우가 겹치지 않게 2칸씩 이동합니다. 따라서 4x4 입력이 2x2 출력으로 정확히 1/4 크기로 줄어듭니다.

만약 stride=1이었다면 윈도우가 겹치면서 이동하므로 크기가 덜 줄어듭니다. Max Pooling vs Average Pooling: 실무에서는 Max Pooling이 훨씬 많이 쓰입니다.

왜냐하면 "가장 강한 신호"가 "평균 신호"보다 더 중요한 정보이기 때문입니다. 예를 들어, 눈동자를 찾을 때 "이 영역 어딘가에 눈동자가 있다"는 정보가 중요하지, 정확히 어디 있는지는 덜 중요합니다.

여러분이 이 코드를 실행하면 4x4 맵이 2x2로 줄어드는 것을 직접 확인할 수 있습니다. 실무에서 224x224 이미지는 여러 번의 Pooling을 거쳐 7x7까지 줄어들고, 최종적으로 Flatten되어 완전연결층으로 들어갑니다.

이 과정에서 계산량이 수백 배 줄어듭니다.

실전 팁

💡 2x2 Max Pooling with stride=2가 가장 일반적입니다. 3x3이나 4x4는 정보 손실이 너무 커서 잘 쓰이지 않습니다.

💡 최근 트렌드: Global Average Pooling을 마지막 층에 사용하면 파라미터 수를 크게 줄일 수 있습니다(예: ResNet, MobileNet).

💡 Pooling 대신 stride=2인 Convolution을 쓰는 방법도 있습니다. 이를 "strided convolution"이라 하며, 학습 가능한 다운샘플링입니다.

💡 디버깅 팁: Pooling 후 특징 맵을 시각화해보세요. 중요한 패턴이 사라졌다면 Pooling을 줄이거나 없애는 것을 고려하세요.

💡 Pooling은 위치 정보를 잃게 만듭니다. 위치가 중요한 작업(세그멘테이션 등)에서는 조심해서 사용해야 합니다.


4. 필터와 특징 맵의 관계

시작하며

여러분이 음악을 들을 때를 생각해보세요. 베이스, 드럼, 멜로디, 보컬...

각 악기 소리를 따로 들을 수 있다면 음악을 훨씬 깊이 이해할 수 있겠죠? 우리 귀는 여러 주파수를 동시에 분석해서 이런 일을 합니다.

CNN도 똑같은 방식으로 이미지를 이해합니다. 하나의 필터로는 한 가지 패턴밖에 찾지 못합니다.

하지만 여러 개의 필터를 사용하면 가로선, 세로선, 대각선, 곡선, 색깔 변화 등 다양한 특징을 동시에 찾아낼 수 있습니다. 바로 이럴 때 필요한 것이 다중 필터입니다.

각 필터가 서로 다른 패턴을 학습하고, 그 결과를 특징 맵이라는 여러 장의 이미지로 만들어냅니다.

개요

간단히 말해서, 필터는 패턴 감지기이고, 특징 맵은 그 결과물입니다. 32개의 필터를 사용하면 32장의 특징 맵이 생성되고, 각 맵은 "내가 찾는 패턴이 이미지의 어디에 있나?"를 보여줍니다.

왜 여러 개의 필터가 필요할까요? 이미지는 복잡합니다.

고양이 사진을 인식하려면 귀의 뾰족함, 수염의 선, 눈의 둥근 형태, 털의 질감 등 수십 가지 특징을 찾아야 합니다. 하나의 필터로는 불가능하죠.

예를 들어, 첫 번째 층에서는 기본 도형을, 두 번째 층에서는 눈·코·귀를, 세 번째 층에서는 얼굴 전체를 인식합니다. 기존에는 사람이 직접 특징을 정의했다면(SIFT, HOG 등), CNN은 필터가 자동으로 최적의 패턴을 학습합니다.

학습 데이터를 많이 줄수록 더 좋은 필터가 만들어집니다. 필터와 특징 맵의 핵심 관계는 다음과 같습니다.

첫째, 필터 개수 = 특징 맵 개수입니다. 둘째, 얕은 층의 필터는 간단한 패턴을, 깊은 층의 필터는 복잡한 패턴을 학습합니다.

셋째, 각 필터는 독립적으로 작동하지만 최종 판단은 모든 특징 맵을 종합합니다. 이러한 관계가 CNN을 강력하게 만들어줍니다.

코드 예제

from tensorflow import keras
import numpy as np

# 간단한 CNN 모델 생성
model = keras.Sequential([
    # 첫 번째 Convolution: 16개 필터
    keras.layers.Conv2D(16, kernel_size=3, activation='relu',
                       input_shape=(28, 28, 1)),

    # 두 번째 Convolution: 32개 필터
    keras.layers.Conv2D(32, kernel_size=3, activation='relu'),

    # 세 번째 Convolution: 64개 필터
    keras.layers.Conv2D(64, kernel_size=3, activation='relu')
])

# 더미 이미지 생성 (1장의 28x28 흑백 이미지)
dummy_image = np.random.rand(1, 28, 28, 1)

# 각 층을 거친 후의 특징 맵 확인
for i, layer in enumerate(model.layers):
    # 중간 모델 생성 (입력부터 현재 층까지)
    intermediate_model = keras.Model(inputs=model.input,
                                    outputs=layer.output)
    feature_maps = intermediate_model.predict(dummy_image, verbose=0)

    print(f"\n{i+1}번째 층 ({layer.name}):")
    print(f"  특징 맵 크기: {feature_maps.shape}")
    print(f"  의미: {feature_maps.shape[3]}개의 서로 다른 패턴을 감지")

설명

이것이 하는 일: 이 코드는 3개의 Convolution 층을 쌓고, 각 층에서 생성되는 특징 맵의 크기와 개수를 확인합니다. 더미 이미지를 입력해서 실제로 특징 맵이 어떻게 변하는지 관찰합니다.

첫 번째로, 3개의 Conv2D 층을 정의합니다. 16개 → 32개 → 64개로 필터 개수가 증가합니다.

이는 일반적인 패턴입니다. 층이 깊어질수록 더 복잡한 특징을 찾아야 하므로 더 많은 필터가 필요합니다.

VGG, ResNet 같은 유명한 모델들도 이런 구조를 따릅니다. 그 다음으로, 각 층의 출력을 확인하기 위해 intermediate_model을 만듭니다.

이는 디버깅과 시각화에 매우 유용한 기법입니다. 예를 들어, 첫 번째 층의 출력은 (1, 26, 26, 16) 형태입니다.

28에서 26으로 줄어든 이유는 padding='valid'가 기본값이기 때문입니다 (양쪽 가장자리 1픽셀씩 손실). 세 번째로, feature_maps.shape를 출력합니다.

(batch_size, height, width, channels) 형태입니다. channels가 바로 특징 맵의 개수입니다.

첫 번째 층은 16개, 두 번째 층은 32개, 세 번째 층은 64개의 특징 맵을 생성합니다. 각 특징 맵이 무엇을 의미하는지 이해해봅시다.

첫 번째 층의 16개 맵은 "가로선이 어디 있나?", "세로선이 어디 있나?" 같은 기본 질문에 대한 답입니다. 두 번째 층의 32개 맵은 "T자 모양이 있나?", "동그라미가 있나?" 같은 조금 더 복잡한 질문에 대한 답입니다.

세 번째 층의 64개 맵은 "숫자 8의 윗부분 같은 게 있나?" 같은 매우 구체적인 질문에 대한 답입니다. 여러분이 이 코드를 실행하면 특징 맵의 크기가 점점 작아지는 것을 볼 수 있습니다 (26 → 24 → 22).

이는 각 Convolution 연산마다 가장자리가 잘려나가기 때문입니다. 실무에서는 padding='same'을 사용해서 크기를 유지하고, Pooling으로 명시적으로 크기를 줄입니다.

실전 팁

💡 필터 개수는 보통 2의 거듭제곱을 사용합니다 (16, 32, 64, 128, 256). 이는 GPU 연산에 최적화되어 있기 때문입니다.

💡 첫 번째 층의 필터를 시각화하면 학습이 잘 되고 있는지 확인할 수 있습니다. 랜덤 노이즈가 아닌 명확한 패턴이 보여야 합니다.

💡 특징 맵이 너무 많으면 과적합 위험이 있습니다. Dropout이나 Batch Normalization을 함께 사용하세요.

💡 층마다 필터 개수를 2배씩 늘리는 것이 일반적이지만, MobileNet처럼 효율성을 위해 적게 쓰는 모델도 있습니다.

💡 model.summary()를 보면 각 층의 파라미터 개수를 알 수 있습니다. Conv2D(64, 3x3)는 이전 층이 32개 필터였다면 3233*64 = 18,432개 파라미터를 가집니다.


5. Stride와 Padding 이해하기

시작하며

여러분이 긴 복도를 걸으면서 벽에 붙은 포스터들을 확인한다고 상상해보세요. 한 걸음씩 걸으면 모든 포스터를 꼼꼼히 볼 수 있지만 시간이 오래 걸립니다.

두 걸음씩 걸으면 빠르지만 일부 포스터를 놓칠 수도 있죠. CNN에서 Stride는 바로 이 "걸음 크기"입니다.

필터를 한 칸씩 움직일지, 두 칸씩 움직일지 결정합니다. 또한 복도의 양쪽 끝을 어떻게 처리할지도 중요합니다.

끝에 도달하면 그냥 멈출지, 아니면 빈 공간을 추가해서 끝까지 확인할지 결정해야 합니다. 바로 이럴 때 필요한 것이 Stride와 Padding입니다.

이 두 매개변수는 특징 맵의 크기와 정보 손실을 조절하는 핵심 도구입니다.

개요

간단히 말해서, Stride는 필터가 이동하는 간격이고, Padding은 이미지 가장자리에 추가하는 여백입니다. Stride를 늘리면 출력이 작아지고 계산이 빨라지며, Padding을 추가하면 가장자리 정보를 보존할 수 있습니다.

왜 Stride와 Padding이 필요할까요? Stride는 계산량과 정보량의 균형을 맞춥니다.

stride=1은 모든 위치를 확인하므로 정보 손실이 적지만 느립니다. stride=2는 절반만 확인하므로 빠르지만 일부 정보를 놓칩니다.

예를 들어, 고해상도 이미지를 실시간으로 처리하는 자율주행차에서는 stride를 크게 설정해서 속도를 높입니다. Padding은 가장자리 문제를 해결합니다.

padding 없이 Convolution을 반복하면 이미지가 계속 작아져서 결국 몇 픽셀만 남게 됩니다. 또한 가장자리 픽셀은 한 번만 계산에 참여하지만 중앙 픽셀은 여러 번 참여해서 불공평합니다.

Stride와 Padding의 핵심 관계는 다음과 같습니다. 출력 크기 = (입력 크기 - 필터 크기 + 2*패딩) / stride + 1입니다.

이 공식을 이해하면 원하는 출력 크기를 얻기 위해 어떤 설정을 써야 할지 계산할 수 있습니다. 실무에서는 보통 padding='same'과 stride=1을 함께 써서 크기를 유지하거나, padding='valid'와 stride=2로 크기를 절반으로 줄입니다.

코드 예제

from tensorflow import keras
import numpy as np

# 테스트용 8x8 이미지
test_image = np.ones((1, 8, 8, 1))

print("입력 이미지 크기: 8x8\n")

# 1. Stride=1, Padding=valid (기본)
model1 = keras.Sequential([
    keras.layers.Conv2D(1, kernel_size=3, strides=1,
                       padding='valid', input_shape=(8, 8, 1))
])
output1 = model1.predict(test_image, verbose=0)
print(f"Stride=1, Padding=valid")
print(f"  출력 크기: {output1.shape[1:3]}")
print(f"  계산: (8 - 3 + 0) / 1 + 1 = 6")

# 2. Stride=2, Padding=valid
model2 = keras.Sequential([
    keras.layers.Conv2D(1, kernel_size=3, strides=2,
                       padding='valid', input_shape=(8, 8, 1))
])
output2 = model2.predict(test_image, verbose=0)
print(f"\nStride=2, Padding=valid")
print(f"  출력 크기: {output2.shape[1:3]}")
print(f"  계산: (8 - 3 + 0) / 2 + 1 = 3")

# 3. Stride=1, Padding=same
model3 = keras.Sequential([
    keras.layers.Conv2D(1, kernel_size=3, strides=1,
                       padding='same', input_shape=(8, 8, 1))
])
output3 = model3.predict(test_image, verbose=0)
print(f"\nStride=1, Padding=same")
print(f"  출력 크기: {output3.shape[1:3]}")
print(f"  계산: padding=1을 자동 추가하여 (8 - 3 + 2) / 1 + 1 = 8")

# 4. Stride=2, Padding=same
model4 = keras.Sequential([
    keras.layers.Conv2D(1, kernel_size=3, strides=2,
                       padding='same', input_shape=(8, 8, 1))
])
output4 = model4.predict(test_image, verbose=0)
print(f"\nStride=2, Padding=same")
print(f"  출력 크기: {output4.shape[1:3]}")
print(f"  계산: 입력 크기 / stride = 8 / 2 = 4")

설명

이것이 하는 일: 이 코드는 Stride와 Padding의 4가지 조합을 테스트하고, 각각 출력 크기가 어떻게 달라지는지 보여줍니다. 실제 계산 공식도 함께 확인할 수 있습니다.

첫 번째로, Stride=1, Padding=valid 조합을 봅시다. 이는 가장 기본적인 설정입니다.

3x3 필터가 8x8 이미지 위를 한 칸씩 이동하면서 6x6 출력을 만듭니다. 양쪽 가장자리 1픽셀씩 총 2픽셀이 손실됩니다.

이 설정은 정보를 최대한 보존하지만 이미지가 계속 줄어듭니다. 그 다음으로, Stride=2로 바꾸면 어떻게 될까요?

필터가 두 칸씩 이동하므로 출력이 3x3으로 크게 줄어듭니다. (8-3)/2 + 1 = 3.5인데 정수로 내림해서 3이 됩니다.

이 설정은 Pooling과 비슷한 효과를 내면서 다운샘플링합니다. 최근 모델들은 Pooling 대신 이 방법을 쓰기도 합니다.

세 번째로, Padding=same을 사용하면 마법 같은 일이 일어납니다. TensorFlow가 자동으로 필요한 만큼 0을 가장자리에 추가해서 출력 크기를 입력과 같게 만듭니다.

3x3 필터의 경우 양쪽에 1픽셀씩 추가하면 됩니다. 이 설정은 깊은 네트워크에서 크기를 유지하는 데 필수적입니다.

마지막으로, Stride=2, Padding=same 조합은 "크기를 정확히 절반으로 줄이고 싶을 때" 사용합니다. 8x8 → 4x4처럼 깔끔하게 절반이 됩니다.

ResNet, EfficientNet 같은 모델들이 이 방법으로 단계적으로 이미지를 축소합니다. 실무 팁: padding='same'과 stride=1은 함께, padding='valid'와 stride=2는 함께 쓰는 것이 일반적입니다.

전자는 정보 보존, 후자는 효율성에 중점을 둡니다. 여러분이 이 코드를 실행하면 각 조합의 효과를 명확히 이해할 수 있습니다.

모델을 설계할 때 "출력 크기를 몇으로 만들고 싶은가?"를 먼저 생각하고, 거기에 맞는 Stride와 Padding을 선택하세요.

실전 팁

💡 padding='same'은 내부적으로 양쪽에 균등하게 0을 추가합니다. 홀수 크기 필터(3x3, 5x5)에서 완벽하게 작동합니다.

💡 stride>1은 Pooling을 대체할 수 있습니다. Strided convolution은 학습 가능한 다운샘플링이라 더 유연합니다.

💡 출력 크기를 계산할 때 주의: (8-3+0)/2 = 2.5는 2로 내림됩니다. 소수점은 항상 버려집니다.

💡 디버깅 시 model.summary()로 각 층의 출력 크기를 확인하세요. 예상과 다르면 Stride/Padding 설정을 다시 확인하세요.

💡 가장자리 픽셀이 중요한 작업(의료 영상 등)에서는 padding='same'을 필수로 사용하여 정보 손실을 최소화하세요.


6. 활성화 함수 ReLU의 역할

시작하며

여러분이 온도를 측정하는데, 음수가 나왔다고 상상해보세요. 물론 섭씨로는 가능하지만, 만약 "활성화 정도"를 나타낸다면 음수는 의미가 없을 수 있습니다.

켜져 있거나 꺼져 있거나, 강하거나 약하거나 하는 개념이죠. CNN에서도 마찬가지입니다.

Convolution 연산의 결과는 양수일 수도, 음수일 수도 있습니다. 하지만 "이 위치에서 패턴이 얼마나 활성화되었는가?"를 표현하려면 음수를 제거하는 것이 도움이 됩니다.

게다가 음수를 포함한 선형 연산만으로는 복잡한 패턴을 학습하기 어렵습니다. 바로 이럴 때 필요한 것이 ReLU(Rectified Linear Unit) 활성화 함수입니다.

음수를 0으로 바꾸고 양수는 그대로 두는 아주 간단하지만 강력한 함수입니다.

개요

간단히 말해서, ReLU는 f(x) = max(0, x)입니다. 입력이 음수면 0을 출력하고, 양수면 그대로 출력합니다.

엄청나게 단순하지만 딥러닝 혁명의 핵심 요소 중 하나입니다. 왜 ReLU가 필요할까요?

첫째, 비선형성(non-linearity)을 제공합니다. 활성화 함수 없이 층을 아무리 쌓아도 결국 하나의 선형 함수와 같습니다.

비선형성이 있어야 복잡한 패턴을 학습할 수 있습니다. 둘째, 계산이 매우 빠릅니다.

단순 비교 연산이므로 sigmoid나 tanh보다 훨씬 효율적입니다. 예를 들어, 수백만 개의 뉴런에 ReLU를 적용해도 밀리초 단위로 처리됩니다.

기존에는 sigmoid(01 사이 값)나 tanh(-11 사이 값)를 썼다면, ReLU는 0 이상의 모든 값을 허용합니다. 이는 "그래디언트 소실 문제"를 크게 완화시켜서 깊은 네트워크 학습을 가능하게 만들었습니다.

ReLU의 핵심 특징은 다음과 같습니다. 첫째, 희소성(sparsity)을 만듭니다.

많은 뉴런이 0을 출력하므로 네트워크가 효율적입니다. 둘째, 그래디언트가 1 또는 0이므로 역전파가 간단합니다.

셋째, 생물학적 뉴런과 유사합니다(일정 임계값 이상일 때만 활성화). 이러한 특징들이 ReLU를 현대 CNN의 표준으로 만들어줍니다.

코드 예제

import numpy as np
import matplotlib.pyplot as plt

# ReLU 함수 구현
def relu(x):
    return np.maximum(0, x)

# Convolution 결과 (음수와 양수 포함)
conv_output = np.array([
    [-2.3,  1.5, -0.8,  3.2],
    [ 0.5, -1.7,  2.1, -0.3],
    [ 4.0, -3.1,  0.0,  1.8],
    [-1.2,  2.7, -0.5,  0.9]
])

print("Convolution 출력 (활성화 전):")
print(conv_output)

# ReLU 적용
activated_output = relu(conv_output)

print("\nReLU 적용 후:")
print(activated_output)

# 통계 확인
total_values = conv_output.size
zero_values = np.sum(activated_output == 0)
print(f"\n전체 값: {total_values}개")
print(f"0으로 변환된 값: {zero_values}개 ({zero_values/total_values*100:.1f}%)")
print(f"활성화된 값: {total_values - zero_values}개")

# TensorFlow에서 ReLU 사용 예제
from tensorflow import keras

model = keras.Sequential([
    # Convolution 후 자동으로 ReLU 적용
    keras.layers.Conv2D(32, 3, activation='relu', input_shape=(28, 28, 1)),
    # 또는 별도 층으로 추가 가능
    # keras.layers.Activation('relu')
])

설명

이것이 하는 일: ReLU는 Convolution 연산 결과에서 음수를 모두 0으로 바꿉니다. 이를 통해 신경망에 비선형성을 추가하고, 특정 뉴런을 "비활성화"시켜 효율성을 높입니다.

첫 번째로, Convolution 출력을 봅시다. -2.3, 1.5, -0.8 같은 값들이 섞여 있습니다.

음수는 "필터와 반대되는 패턴이 있다"는 의미입니다. 예를 들어, 가로선 감지 필터에서 세로선이 나타나면 음수가 나올 수 있습니다.

그 다음으로, relu() 함수를 적용하면 마법이 일어납니다. -2.3 → 0, 1.5 → 1.5, -0.8 → 0, 3.2 → 3.2처럼 변환됩니다.

결과적으로 16개 값 중 7개가 0이 되었습니다(43.75%). 이것이 바로 희소성입니다.

네트워크의 절반 가량의 뉴런이 꺼져 있으므로 계산량과 메모리 사용량이 줄어듭니다. 왜 이게 중요할까요?

만약 모든 뉴런이 항상 활성화되어 있다면, 네트워크는 모든 정보를 무차별적으로 전달합니다. 하지만 ReLU로 일부를 차단하면 "중요한 신호만 통과"시키는 효과가 있습니다.

이는 노이즈 제거와 비슷합니다. TensorFlow에서는 activation='relu' 파라미터 하나로 ReLU를 적용할 수 있습니다.

내부적으로 Convolution 후 즉시 ReLU가 실행됩니다. 또는 keras.layers.Activation('relu')를 별도 층으로 추가할 수도 있습니다.

두 방법은 동일한 결과를 만듭니다. 역전파 관점에서 보면 ReLU는 매우 효율적입니다.

양수 영역에서 그래디언트가 1이므로 그대로 전달되고, 음수 영역에서는 0이므로 차단됩니다. 이는 sigmoid(그래디언트가 최대 0.25)보다 훨씬 강력한 학습을 가능하게 합니다.

여러분이 이 코드를 실행하면 ReLU가 얼마나 많은 값을 0으로 만드는지 직접 확인할 수 있습니다. 실제 모델에서는 전체 뉴런의 30-60%가 비활성화되는 것이 일반적입니다.

이는 정상적이며 오히려 바람직한 현상입니다.

실전 팁

💡 "Dying ReLU" 문제: 일부 뉴런이 영구적으로 0만 출력할 수 있습니다. 학습률을 너무 크게 설정하면 발생합니다. 해결책: Leaky ReLU 사용.

💡 Leaky ReLU는 f(x) = max(0.01x, x)로 음수에 작은 기울기를 줍니다. keras.layers.LeakyReLU(alpha=0.01)로 사용 가능합니다.

💡 ReLU6는 f(x) = min(max(0, x), 6)로 최댓값을 6으로 제한합니다. 모바일 기기에서 양자화에 유리합니다.

💡 Batch Normalization과 함께 쓰면 효과가 배가됩니다. BN → ReLU 순서가 일반적입니다(일부는 ReLU → BN 선호).

💡 마지막 출력 층에는 ReLU를 쓰지 마세요. 분류는 softmax, 회귀는 linear(활성화 함수 없음)를 사용합니다.


7. CNN 층 쌓기 전략

시작하며

여러분이 레고로 탑을 쌓는다고 생각해보세요. 작은 블록으로 시작해서 점점 큰 구조를 만들어가죠.

맨 아래는 넓고 튼튼하게, 위로 갈수록 좁고 정교하게 만듭니다. 아무렇게나 쌓으면 금방 무너집니다.

CNN도 똑같습니다. 층을 어떤 순서로, 얼마나 깊게 쌓느냐에 따라 성능이 크게 달라집니다.

얕은 네트워크는 간단한 패턴만 학습하고, 너무 깊으면 학습이 어렵거나 과적합이 발생합니다. 적절한 균형이 필요합니다.

바로 이럴 때 필요한 것이 체계적인 층 쌓기 전략입니다. 수십 년의 연구를 통해 검증된 패턴들이 있고, 이를 따르면 높은 성능을 얻을 수 있습니다.

개요

간단히 말해서, CNN 층 쌓기는 "점진적 추상화" 원칙을 따릅니다. 처음에는 작고 간단한 특징을 찾고, 층이 깊어질수록 크고 복잡한 특징을 찾습니다.

동시에 공간 크기는 줄이고 채널 수는 늘립니다. 왜 이런 전략이 필요할까요?

첫째, 계산 효율성입니다. 초반에 큰 이미지에서 많은 채널을 쓰면 메모리가 폭발합니다.

224x224x256 = 12,845,056개의 값을 저장해야 합니다. 둘째, 학습 효율성입니다.

복잡한 특징을 바로 학습하는 것보다 간단한 특징부터 조합하는 것이 쉽습니다. 예를 들어, VGG-16은 이 원칙을 완벽하게 따라서 ImageNet에서 우승했습니다.

기존에는 얕은 네트워크(5-10층)를 썼다면, 현대 CNN은 수십~수백 층을 쌓습니다. 하지만 무작정 깊게 쌓는 게 아니라 Residual Connection, Batch Normalization 같은 기법을 함께 사용합니다.

층 쌓기의 핵심 원칙은 다음과 같습니다. 첫째, Conv-ReLU-Pool 블록을 반복합니다.

둘째, Pooling 후 채널 수를 2배로 늘립니다(16→32→64→128). 셋째, 마지막에 Flatten → Dense로 분류합니다.

넷째, Dropout이나 Batch Normalization으로 정규화합니다. 이러한 원칙들이 안정적인 학습과 높은 성능을 보장합니다.

코드 예제

from tensorflow import keras

# 체계적인 CNN 아키텍처 (CIFAR-10용)
model = keras.Sequential([
    # 블록 1: 32x32 → 16x16, 필터 32개
    keras.layers.Conv2D(32, 3, padding='same', activation='relu',
                       input_shape=(32, 32, 3)),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.2),

    # 블록 2: 16x16 → 8x8, 필터 64개
    keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.3),

    # 블록 3: 8x8 → 4x4, 필터 128개
    keras.layers.Conv2D(128, 3, padding='same', activation='relu'),
    keras.layers.BatchNormalization(),
    keras.layers.Conv2D(128, 3, padding='same', activation='relu'),
    keras.layers.MaxPooling2D(2),
    keras.layers.Dropout(0.4),

    # 분류층
    keras.layers.Flatten(),
    keras.layers.Dense(128, activation='relu'),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(10, activation='softmax')  # 10개 클래스
])

# 모델 구조 확인
model.summary()

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

설명

이것이 하는 일: 이 코드는 CIFAR-10 데이터셋(32x32 컬러 이미지, 10개 클래스)을 분류하는 체계적인 CNN을 구현합니다. 3개의 블록을 쌓고 각 블록마다 크기를 절반으로, 필터를 2배로 늘립니다.

첫 번째 블록을 자세히 봅시다. Conv2D(32개) → BatchNorm → Conv2D(32개) → MaxPooling → Dropout 순서입니다.

왜 Conv를 2번 연속 쓸까요? 한 번의 Convolution으로는 복잡한 패턴을 잡기 어렵습니다.

2번 연속하면 3x3 → 5x5 효과(receptive field 확장)가 나면서 파라미터는 적게 유지됩니다. VGG가 증명한 강력한 전략입니다.

BatchNormalization은 각 층의 출력을 정규화해서 학습을 안정화시킵니다. 이게 없으면 깊은 네트워크 학습이 매우 어렵습니다.

내부적으로 평균을 빼고 분산으로 나눈 후 학습 가능한 스케일과 시프트를 적용합니다. MaxPooling(2)는 크기를 정확히 절반으로 줄입니다.

32x32 → 16x16 → 8x8 → 4x4처럼 진행됩니다. 동시에 채널 수는 32 → 64 → 128로 2배씩 증가합니다.

이는 "공간 해상도가 줄어드는 대신 특징 다양성을 늘린다"는 의미입니다. Dropout은 과적합을 방지합니다.

0.2는 20%의 뉴런을 무작위로 끄고, 0.3은 30%, 0.5는 50%를 끕니다. 층이 깊어질수록 Dropout 비율을 높이는 것이 일반적입니다.

마지막 Dense 층 전에는 50%로 강하게 적용합니다. Flatten은 4x4x128 = 2,048개의 값을 1차원 벡터로 펴줍니다.

그리고 Dense(128)이 이를 128개의 추상적 특징으로 요약하고, 최종 Dense(10)이 10개 클래스 확률을 출력합니다. softmax는 확률 합이 1이 되도록 정규화합니다.

여러분이 model.summary()를 실행하면 각 층의 출력 크기와 파라미터 수를 볼 수 있습니다. 총 파라미터가 수백만 개 되는 것을 확인하세요.

이 모델은 CIFAR-10에서 80-85% 정확도를 달성할 수 있습니다.

실전 팁

💡 VGG 스타일: 같은 크기에서 Conv를 2-3번 반복 후 Pooling. 간단하고 안정적이며 초보자에게 추천합니다.

💡 ResNet 스타일: Skip connection을 추가하면 50층 이상도 학습 가능합니다. keras.layers.Add()로 구현합니다.

💡 첫 번째 층은 필터를 적게(16-32개) 시작하세요. 너무 많으면 초반에 메모리를 과도하게 사용합니다.

💡 Data Augmentation을 추가하면 성능이 5-10% 향상됩니다. keras.layers.RandomFlip(), RandomRotation() 등을 모델 맨 앞에 추가하세요.

💡 학습 시 EarlyStopping과 ModelCheckpoint 콜백을 사용하세요. 검증 손실이 더 이상 개선되지 않으면 자동으로 멈춥니다.


8. 실전 CNN 학습하기

시작하며

여러분이 자전거 타는 법을 배운다고 상상해보세요. 처음에는 넘어지고, 조금씩 균형을 잡고, 결국 자연스럽게 탑니다.

이 과정에서 "얼마나 자주 연습하나(batch size)", "얼마나 과감하게 시도하나(learning rate)"가 중요합니다. CNN 학습도 똑같습니다.

모델 구조를 만들었다고 끝이 아닙니다. 데이터를 어떻게 준비하고, 어떤 옵티마이저를 쓰고, 학습률을 얼마로 설정하느냐에 따라 결과가 천차만별입니다.

잘못 설정하면 며칠 학습해도 정확도가 10%에 머물 수 있습니다. 바로 이럴 때 필요한 것이 체계적인 학습 전략입니다.

데이터 준비부터 학습, 평가까지 전 과정을 실전처럼 다뤄봅시다.

개요

간단히 말해서, CNN 학습은 반복적으로 예측하고, 오차를 계산하고, 가중치를 업데이트하는 과정입니다. 이를 수천~수만 번 반복하면서 점점 정확한 모델이 만들어집니다.

왜 체계적인 학습 과정이 필요할까요? 첫째, 데이터 전처리가 중요합니다.

픽셀 값을 0-1로 정규화하지 않으면 학습이 제대로 안 됩니다. 둘째, 배치 크기가 중요합니다.

너무 작으면 불안정하고, 너무 크면 메모리가 부족합니다. 예를 들어, GPU 메모리가 8GB라면 배치 크기 32-64가 적당합니다.

기존에는 SGD(Stochastic Gradient Descent)를 썼다면, 현대 CNN은 Adam 옵티마이저를 많이 씁니다. Adam은 학습률을 자동으로 조절해서 수렴이 빠르고 안정적입니다.

실전 학습의 핵심은 다음과 같습니다. 첫째, 데이터를 train/validation/test로 분리합니다.

둘째, 에폭마다 전체 데이터를 한 번씩 봅니다. 셋째, 검증 손실을 모니터링하며 과적합을 감지합니다.

넷째, 조기 종료(Early Stopping)로 불필요한 학습을 방지합니다. 이러한 과정들이 실무 수준의 모델을 만들어줍니다.

코드 예제

from tensorflow import keras
import numpy as np

# 1. 데이터 로드 (MNIST 손글씨 숫자)
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# 2. 데이터 전처리
x_train = x_train.reshape(-1, 28, 28, 1).astype('float32') / 255.0
x_test = x_test.reshape(-1, 28, 28, 1).astype('float32') / 255.0

print(f"학습 데이터: {x_train.shape}, 레이블: {y_train.shape}")
print(f"테스트 데이터: {x_test.shape}, 레이블: {y_test.shape}")

# 3. 모델 정의
model = keras.Sequential([
    keras.layers.Conv2D(32, 3, activation='relu', input_shape=(28, 28, 1)),
    keras.layers.MaxPooling2D(2),
    keras.layers.Conv2D(64, 3, activation='relu'),
    keras.layers.MaxPooling2D(2),
    keras.layers.Flatten(),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(10, activation='softmax')
])

# 4. 컴파일 (옵티마이저, 손실 함수, 평가 지표 설정)
model.compile(
    optimizer='adam',  # 또는 keras.optimizers.Adam(learning_rate=0.001)
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

# 5. 콜백 설정
callbacks = [
    # 검증 손실이 3번 연속 개선 안 되면 학습 중단
    keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True),
    # 학습률 감소: 정체되면 1/10로 줄임
    keras.callbacks.ReduceLROnPlateau(factor=0.1, patience=2)
]

# 6. 학습 시작
history = model.fit(
    x_train, y_train,
    batch_size=128,      # 한 번에 128개씩 처리
    epochs=10,           # 전체 데이터를 10번 반복
    validation_split=0.2,  # 학습 데이터의 20%를 검증용으로
    callbacks=callbacks,
    verbose=1
)

# 7. 평가
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
print(f"\n테스트 정확도: {test_acc*100:.2f}%")

# 8. 예측 예시
sample_image = x_test[0:1]  # 첫 번째 테스트 이미지
prediction = model.predict(sample_image, verbose=0)
predicted_digit = np.argmax(prediction)
print(f"예측 결과: {predicted_digit}, 실제 값: {y_test[0]}")

설명

이것이 하는 일: 이 코드는 MNIST 데이터셋으로 손글씨 숫자 인식 CNN을 처음부터 끝까지 학습시킵니다. 데이터 준비, 모델 정의, 학습, 평가, 예측의 전체 파이프라인을 보여줍니다.

첫 번째로, 데이터 전처리를 정확히 해야 합니다. reshape(-1, 28, 28, 1)은 (60000, 28, 28)을 (60000, 28, 28, 1)로 바꿉니다.

마지막 1은 채널 수(흑백이므로 1)입니다. 그리고 /255.0으로 0-255 값을 0-1로 정규화합니다.

이게 없으면 학습이 매우 느리거나 실패합니다. 그 다음으로, compile()에서 학습 방법을 설정합니다.

optimizer='adam'은 적응형 학습률을 사용하는 강력한 알고리즘입니다. loss='sparse_categorical_crossentropy'는 다중 클래스 분류에 최적입니다('sparse'는 레이블이 정수형일 때 사용).

metrics=['accuracy']는 정확도를 함께 모니터링합니다. 콜백은 학습 과정을 자동으로 제어합니다.

EarlyStopping은 검증 손실이 3번(patience=3) 연속 개선되지 않으면 학습을 중단하고 가장 좋았던 가중치로 복원합니다. ReduceLROnPlateau는 정체 상태가 2번 지속되면 학습률을 1/10로 줄여서 미세 조정 모드로 전환합니다.

fit() 함수가 실제 학습을 수행합니다. batch_size=128은 128개씩 묶어서 처리합니다(1개씩 하면 너무 느리고, 전체를 한 번에 하면 메모리 부족).

validation_split=0.2는 학습 데이터의 20%를 검증용으로 자동 분리합니다. 각 에폭마다 학습 정확도와 검증 정확도를 출력해서 과적합 여부를 확인할 수 있습니다.

evaluate()는 테스트 데이터로 최종 성능을 측정합니다. 보통 98-99% 정확도가 나옵니다.

predict()는 새로운 이미지에 대한 예측을 합니다. 10개 클래스에 대한 확률을 반환하므로 np.argmax()로 가장 높은 확률의 클래스를 선택합니다.

여러분이 이 코드를 실행하면 GPU에서 1-2분, CPU에서 5-10분 정도 걸립니다. history 객체에는 에폭마다의 손실과 정확도가 저장되어 있어서 그래프로 그릴 수 있습니다.

실전 팁

💡 학습 곡선 그리기: plt.plot(history.history['accuracy'])와 plt.plot(history.history['val_accuracy'])를 그려서 과적합을 시각적으로 확인하세요.

💡 배치 크기는 2의 거듭제곱(32, 64, 128, 256)을 사용하세요. GPU 연산에 최적화되어 있어 더 빠릅니다.

💡 데이터가 불균형하면(클래스 A가 90%, 클래스 B가 10%) class_weight 파라미터를 사용하세요. 적은 클래스에 더 큰 가중치를 줍니다.

💡 학습 중 모델 저장: ModelCheckpoint 콜백으로 최고 성능 모델을 자동 저장할 수 있습니다. 나중에 load_model()로 불러옵니다.

💡 TensorBoard로 학습 과정을 실시간 모니터링할 수 있습니다. keras.callbacks.TensorBoard()를 추가하고 'tensorboard --logdir=logs'로 실행하세요.


#Python#CNN#Convolution#Pooling#DeepLearning#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

데이터 증강과 정규화 완벽 가이드

머신러닝 모델의 성능을 극대화하는 핵심 기법인 데이터 증강과 정규화에 대해 알아봅니다. 실무에서 바로 활용할 수 있는 다양한 기법과 실전 예제를 통해 과적합을 방지하고 모델 성능을 향상시키는 방법을 배웁니다.

ResNet과 Skip Connection 완벽 가이드

딥러닝 모델이 깊어질수록 성능이 떨어지는 문제를 해결한 혁신적인 기법, ResNet과 Skip Connection을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실제 구현 코드와 함께 배워보세요.

CNN 아키텍처 완벽 가이드 LeNet AlexNet VGGNet

컴퓨터 비전의 기초가 되는 세 가지 핵심 CNN 아키텍처를 배웁니다. 손글씨 인식부터 이미지 분류까지, 딥러닝의 발전 과정을 따라가며 각 모델의 구조와 특징을 실습 코드와 함께 이해합니다.

TensorFlow와 Keras 완벽 입문 가이드

머신러닝과 딥러닝의 세계로 들어가는 첫걸음! TensorFlow와 Keras 프레임워크를 처음 접하는 분들을 위한 친절한 가이드입니다. 실무에서 바로 활용할 수 있는 핵심 개념과 예제를 통해 AI 모델 개발의 기초를 탄탄히 다져보세요.

PyTorch Dataset과 DataLoader 완벽 가이드

딥러닝 모델을 학습시킬 때 데이터를 효율적으로 다루는 방법을 배웁니다. PyTorch의 Dataset과 DataLoader를 사용하여 대용량 데이터를 메모리 효율적으로 처리하고, 배치 처리와 셔플링을 자동화하는 방법을 실무 예제와 함께 알아봅니다.