콘텐츠로 이동

9차시: 나만의 CNN 만들기 - MNIST 손글씨 분류 프로젝트

⏰ 80분 · Keras · CNN 설계 · 과적합 · 필터 시각화 · 난이도 ●●●●○

학습목표: Keras로 Conv-Pool-Flatten-Dense 구조의 CNN을 설계해 MNIST를 분류하고, 학습 곡선으로 과적합을 진단하며, 학습된 필터가 무엇을 보는지 해석할 수 있습니다.

오늘의 질문: “우체국 기계는 어떻게 제 악필 우편번호를 읽어낼까요? 그리고 그 기계가 ‘본’ 것을 제가 훔쳐볼 수 있을까요?”


Conv → Pool → Flatten → Dense 4단 구조를 Keras로 조립합니다.

층 수·필터 수·Dropout을 각자 조합해 검증 정확도를 겨룹니다.

학습된 필터와 특징 맵을 시각화해 모델이 숫자의 어디를 보는지 해석합니다.

학습 곡선을 읽고 Dropout·Augmentation으로 처방합니다.


MNIST가 왜 ‘머신러닝의 Hello World’인지 확인하고, Colab에서 Keras와 데이터를 불러옵니다. 이미지 한 장을 눈으로 직접 보며 28×28 숫자 배열의 정체를 확인합니다.

v1(최소 CNN) → v2(층 추가) → v3(Dropout 추가) 순서로 모델을 진화시키며 각 레이어가 왜 필요한지 이해합니다.

5 epoch 학습 후 loss·accuracy 곡선을 그려 과적합 신호를 읽습니다.

4단계: 미니 대회 & Augmentation (20분)

섹션 제목: “4단계: 미니 대회 & Augmentation (20분)”

하이퍼파라미터를 자유 조합해 검증 정확도를 경쟁합니다. 과적합 팀은 Data Augmentation으로 반등을 시도합니다.

5단계: 필터·특징 맵 시각화 & 정리 (15분)

섹션 제목: “5단계: 필터·특징 맵 시각화 & 정리 (15분)”

학습된 첫 Conv 층의 필터와 중간 특징 맵을 그려 모델의 ‘시선’을 해석하고, 성찰·평가로 마무리합니다.


💡 개념 1: CNN의 4단 구조 — 눈, 요약, 펴기, 판단

섹션 제목: “💡 개념 1: CNN의 4단 구조 — 눈, 요약, 펴기, 판단”

친구 얼굴을 알아볼 때 우리 뇌는 한 번에 “전체”를 보지 않습니다. 먼저 눈·코·입 같은 작은 패턴을 감지하고, 그것을 요약해 얼굴 부위로 묶고, 마지막에 “아, 지수구나” 하고 판단합니다. CNN은 이 과정을 네 층으로 모방합니다.

역할비유
Conv2D작은 필터로 이미지를 훑어 국소 패턴(가로선, 곡선 등)을 찾음돋보기로 부분을 확대해 보기
MaxPooling2D영역 안에서 가장 강한 신호만 남겨 크기를 줄임노트 필기 요약
Flatten2D 특징 맵을 1D 벡터로 펼침요약본을 줄글로 풀기
Dense모든 특징을 종합해 0-9 중 하나로 판단최종 결론 내리기
flowchart LR
  A["입력<br/>28×28×1"] --> B["Conv2D<br/>32 filters"]
  B --> C["MaxPool<br/>2×2"]
  C --> D["Conv2D<br/>64 filters"]
  D --> E["MaxPool<br/>2×2"]
  E --> F["Flatten"]
  F --> G["Dense 64"]
  G --> H["Dense 10<br/>(softmax)"]

💡 개념 2: 과적합과 학습 곡선 읽기

섹션 제목: “💡 개념 2: 과적합과 학습 곡선 읽기”

시험공부를 할 때 기출문제 답만 달달 외우면, 새 문제가 나왔을 때 틀립니다. 과적합(overfitting)은 모델이 훈련 데이터의 정답만 외워버려 새 데이터에서 성능이 떨어지는 현상입니다.

학습 곡선에서 훈련 정확도는 계속 올라가는데 검증 정확도가 정체되거나 떨어진다면 과적합 신호입니다. 처방은 두 가지입니다.

  • Dropout: 학습 중 일부 뉴런을 무작위로 꺼서 특정 패턴에 의존하지 못하게 합니다.
  • Data Augmentation: 이미지를 살짝 회전·이동시켜 “새 문제처럼” 만들어 훈련 데이터를 늘립니다.

💡 개념 3: 모델의 시선 — 필터와 특징 맵

섹션 제목: “💡 개념 3: 모델의 시선 — 필터와 특징 맵”

학습이 끝난 CNN의 첫 Conv 층 필터를 이미지로 그려보면, 대부분 가로선·세로선·대각선·원호 같은 단순한 모양이 나타납니다. 사람이 “선 감지 세포”를 진화시킨 것과 같은 패턴입니다.

특징 맵(feature map)은 특정 입력 이미지가 각 필터를 통과한 후의 결과입니다. 숫자 “8”을 넣었을 때 어떤 필터가 강하게 반응하는지 보면, 모델이 “8의 위쪽 곡선”을 보고 있는지 “가운데 교차점”을 보고 있는지 추측할 수 있습니다.


실행 환경은 Google Colab(Python 3.10+, TensorFlow 2.x 사전 설치)을 권장합니다. 로컬이라면 pip install tensorflow matplotlib numpy로 설치하세요.

# MNIST는 Keras에 내장되어 있어 한 줄로 다운로드됩니다
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 데이터가 어떤 모양인지 확인 — 감을 잡는 게 먼저입니다
print("훈련 이미지:", x_train.shape, "레이블:", y_train.shape)
print("픽셀 값 범위:", x_train.min(), "~", x_train.max())
# 첫 이미지 한 장 눈으로 보기
plt.imshow(x_train[0], cmap='gray')
plt.title(f"정답: {y_train[0]}")
plt.show()
훈련 이미지: (60000, 28, 28) 레이블: (60000,)
픽셀 값 범위: 0 ~ 255

해석: 28×28 흑백 이미지가 6만 장, 픽셀 값은 0-255 정수입니다. 모델에 넣기 전에 0-1로 정규화하고 채널 차원(마지막 1)을 추가해야 합니다.

# 왜 255로 나누나요? — 신경망은 작은 값에서 학습이 안정적입니다
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# 왜 차원을 추가하나요? — Conv2D는 (높이, 너비, 채널) 형태를 요구합니다
x_train = x_train[..., np.newaxis] # (60000, 28, 28, 1)
x_test = x_test[..., np.newaxis]
print("전처리 후:", x_train.shape)
전처리 후: (60000, 28, 28, 1)
from tensorflow.keras import layers, models
# v1: 최소한의 구조만 넣은 '뼈대' 모델
model_v1 = models.Sequential([
layers.Conv2D(16, (3, 3), activation='relu', input_shape=(28, 28, 1)), # <- 여기가 Conv층
layers.MaxPooling2D((2, 2)), # <- 여기가 Pool층
layers.Flatten(), # <- 여기가 Flatten
layers.Dense(10, activation='softmax') # <- 여기가 판단층
])
model_v1.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model_v1.summary()

위 코드 3번째 줄 Conv2D(16, (3,3))이 바로 개념 1의 Conv2D입니다. 16개의 3×3 필터로 이미지를 훑습니다. 4번째 줄 MaxPooling2D요약 단계, 5번째 줄 Flatten펴기, 6번째 줄 Dense(10, softmax)0-9 중 선택하는 판단입니다.

v1은 단순해서 정확도 한계가 있습니다. Conv+Pool 블록을 한 번 더 쌓아 더 복잡한 패턴을 잡도록 합니다.

# v2: Conv 블록을 하나 더 추가 + Dense 은닉층 삽입
model_v2 = models.Sequential([
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(64, (3, 3), activation='relu'), # <- v1에서 추가된 Conv 블록
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(64, activation='relu'), # <- 은닉 Dense층 추가
layers.Dense(10, activation='softmax')
])
model_v2.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])

v1과의 차이: 필터 수를 16→32로 늘리고, Conv+Pool 블록을 하나 더 추가했으며, 판단 전에 Dense(64) 은닉층을 끼웠습니다. 일반적으로 깊어질수록 더 추상적인 특징을 잡습니다.

# validation_split=0.1로 훈련 데이터 10%를 검증용으로 떼어 냅니다
history = model_v2.fit(x_train, y_train,
epochs=5,
batch_size=128,
validation_split=0.1,
verbose=1)
# 학습 곡선 그리기 — 과적합 진단의 핵심 도구입니다
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='train')
plt.plot(history.history['val_accuracy'], label='val')
plt.xlabel('epoch'); plt.ylabel('accuracy'); plt.legend()
plt.title('Accuracy')
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='val')
plt.xlabel('epoch'); plt.ylabel('loss'); plt.legend()
plt.title('Loss')
plt.show()
test_loss, test_acc = model_v2.evaluate(x_test, y_test, verbose=0)
print(f"테스트 정확도: {test_acc:.4f}")
Epoch 1/5 ... accuracy: 0.9312 - val_accuracy: 0.9805
Epoch 2/5 ... accuracy: 0.9832 - val_accuracy: 0.9852
Epoch 3/5 ... accuracy: 0.9887 - val_accuracy: 0.9880
Epoch 4/5 ... accuracy: 0.9915 - val_accuracy: 0.9888
Epoch 5/5 ... accuracy: 0.9938 - val_accuracy: 0.9890
테스트 정확도: 0.9901

곡선 읽기: 훈련·검증 정확도가 함께 올라가면 건강합니다. 훈련만 오르고 검증은 떨어지면 과적합, 둘 다 낮으면 과소적합입니다.

⚠️ 에러 체험: 자주 만나는 실수

섹션 제목: “⚠️ 에러 체험: 자주 만나는 실수”

아래 코드는 의도적으로 틀린 코드입니다. 어떤 에러가 날까요?

# 일부러 틀린 코드 — 차원을 추가하지 않고 Conv에 넣음
x_wrong = x_train.squeeze() # (60000, 28, 28)로 되돌림
model_v2.fit(x_wrong, y_train, epochs=1)
ValueError: Input 0 of layer "conv2d" is incompatible with the layer:
expected min_ndim=4, found ndim=3. Full shape received: (None, 28, 28)

원인: Conv2D는 (배치, 높이, 너비, 채널) 4차원을 요구합니다. 흑백이라도 채널=1을 명시해야 합니다.

수정: x_train[..., np.newaxis] 또는 x_train.reshape(-1, 28, 28, 1)로 채널 차원을 추가합니다.

Step 6: CNN v3 — Dropout으로 과적합 방어

섹션 제목: “Step 6: CNN v3 — Dropout으로 과적합 방어”
# v3: Flatten 뒤와 Dense 사이에 Dropout 추가
model_v3 = models.Sequential([
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(64, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dropout(0.5), # <- 50% 뉴런을 학습 중 무작위로 끔
layers.Dense(64, activation='relu'),
layers.Dropout(0.3),
layers.Dense(10, activation='softmax')
])
model_v3.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])

Dropout(0.5)가 바로 개념 2의 과적합 처방입니다. 학습 중에만 작동하고 평가 시에는 자동으로 꺼집니다.

Step 7: Data Augmentation — 데이터를 ‘늘리기’

섹션 제목: “Step 7: Data Augmentation — 데이터를 ‘늘리기’”

과적합이 심하면 이미지를 살짝 회전·이동시켜 새 훈련 데이터를 만들어냅니다.

from tensorflow.keras.preprocessing.image import ImageDataGenerator
# 숫자는 좌우 뒤집으면 안 됩니다(6과 9 문제) — 회전·이동만 허용
datagen = ImageDataGenerator(
rotation_range=10, # ±10도 회전
width_shift_range=0.1, # 가로 10% 이동
height_shift_range=0.1, # 세로 10% 이동
zoom_range=0.1
)
history_aug = model_v3.fit(
datagen.flow(x_train, y_train, batch_size=128),
epochs=5,
validation_data=(x_test, y_test),
verbose=1
)

학습된 모델이 “무엇을 보는지” 들여다보는 단계입니다.

# 첫 번째 Conv 층의 필터 가져오기
first_conv = model_v3.layers[0]
filters, biases = first_conv.get_weights()
print("필터 shape:", filters.shape) # (3, 3, 1, 32)
# 32개 필터 중 16개만 시각화
plt.figure(figsize=(8, 8))
for i in range(16):
plt.subplot(4, 4, i + 1)
plt.imshow(filters[:, :, 0, i], cmap='gray')
plt.axis('off')
plt.suptitle('학습된 필터 (첫 Conv층)')
plt.show()
# 특정 이미지의 특징 맵 — 모델의 '시선' 보기
from tensorflow.keras.models import Model
# 첫 Conv층 출력을 꺼내는 보조 모델
activation_model = Model(inputs=model_v3.inputs,
outputs=model_v3.layers[0].output)
sample = x_test[0:1] # 테스트셋 첫 이미지
activations = activation_model.predict(sample)
print("특징 맵 shape:", activations.shape) # (1, 26, 26, 32)
plt.figure(figsize=(10, 6))
plt.subplot(1, 2, 1)
plt.imshow(sample[0, :, :, 0], cmap='gray')
plt.title(f"원본 (정답 {y_test[0]})")
# 32개 필터의 응답 중 8개
for i in range(8):
plt.subplot(2, 8, 8 + i + 1)
plt.imshow(activations[0, :, :, i], cmap='viridis')
plt.axis('off')
plt.show()

해석 포인트: 어떤 특징 맵은 숫자의 수직 획에만 반응하고, 다른 맵은 곡선 부분에만 밝게 빛납니다. 각 필터가 서로 다른 역할을 맡고 있는 것입니다.


🏆 미니 대회: 최고의 검증 정확도 찾기 (짝 활동, 15분)

섹션 제목: “🏆 미니 대회: 최고의 검증 정확도 찾기 (짝 활동, 15분)”

아래 표에서 각 자리를 자유롭게 조합해 자신만의 모델을 만들어 제출하세요. 짝과 결과를 비교합니다.| 선택 항목 | 가능한 값 | |---|---| | Conv 블록 수 | 1개 · 2개 · 3개 | | 첫 Conv 필터 수 | 16 · 32 · 64 | | Dropout 비율 | 0.0 · 0.25 · 0.5 | | Dense 은닉 유닛 | 32 · 64 · 128 | | Augmentation | 사용 · 미사용 | | Epoch | 5 · 10 |

항목내 기록
모델 구성 요약
훈련 정확도 (마지막 epoch)
검증 정확도 (마지막 epoch)
과적합 여부 (Y/N)
다음에 바꿔볼 것

🤔 탐구 활동: 모델의 시선 해석하기 (모둠 4인, 10분)

섹션 제목: “🤔 탐구 활동: 모델의 시선 해석하기 (모둠 4인, 10분)”

Step 8에서 뽑은 특징 맵을 보며 다음 질문에 답을 찾습니다.

  1. 32개 필터 중 거의 반응하지 않는(까만) 필터가 있습니까? 왜 그럴까요?
  2. 숫자 1과 숫자 8의 특징 맵을 비교했을 때, 어느 필터가 두 숫자를 가장 크게 구분합니까?
  3. 학습 전(랜덤 초기화) 모델의 필터와 학습 후 필터를 같이 그려 비교하면, 어떤 차이가 보입니까?

오늘 만든 Conv-Pool-Flatten-Dense 구조는 현업의 출발점입니다.

  • 의료 영상 판독: 흉부 X-ray에서 폐렴을 탐지하는 모델도 기본 뼈대는 동일하고, 입력 해상도와 층 수만 커집니다.
  • QR/바코드 인식기: 카메라 앱이 코드 영역을 찾을 때 작은 CNN이 돌아갑니다. Dropout과 Augmentation은 흔들린 손과 조명 변화를 견디게 해 줍니다.
  • 공장 불량 검출: 라인 카메라 이미지에서 결함을 분류할 때, 오늘 본 필터 시각화는 “모델이 진짜 결함을 보는지, 아니면 조명 반사를 보는지” 검증하는 실무 디버깅 도구로 쓰입니다.
  • Grad-CAM 같은 기법은 오늘의 특징 맵 시각화를 확장한 것으로, AI가 왜 그 판단을 내렸는지 설명해야 하는 XAI(설명 가능한 AI) 영역의 기본기입니다.

v2 모델의 첫 Conv 필터를 32에서 8로 줄이면 정확도가 어떻게 변할까요? 직접 실행해 비교하세요.

힌트

layers.Conv2D(8, (3, 3), ...)로 바꾸고 동일 조건에서 5 epoch 학습해 검증 정확도를 비교합니다.

정답 코드
model_small = models.Sequential([
layers.Conv2D(8, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(16, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(32, activation='relu'),
layers.Dense(10, activation='softmax')
])
model_small.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model_small.fit(x_train, y_train, epochs=5, batch_size=128, validation_split=0.1)

필터를 줄이면 파라미터 수가 감소해 학습은 빨라지지만, 검증 정확도가 0.5%에서 1.5%p 정도 떨어지는 것이 일반적입니다.

동일 구조를 옷 이미지 데이터셋 Fashion-MNIST에 적용하세요. 정확도는 MNIST보다 낮게 나옵니다. 왜 그럴까요?

힌트

from tensorflow.keras.datasets import fashion_mnist로 불러옵니다. 이미지 크기와 클래스 수(10개)는 같습니다.

정답 코드 & 해설
from tensorflow.keras.datasets import fashion_mnist
(xf_train, yf_train), (xf_test, yf_test) = fashion_mnist.load_data()
xf_train = xf_train.astype('float32') / 255.0
xf_test = xf_test.astype('float32') / 255.0
xf_train = xf_train[..., np.newaxis]
xf_test = xf_test[..., np.newaxis]
# v3 모델과 동일 구조 재사용
model_fashion = models.Sequential([
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(64, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dropout(0.5),
layers.Dense(64, activation='relu'),
layers.Dense(10, activation='softmax')
])
model_fashion.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model_fashion.fit(xf_train, yf_train, epochs=5, batch_size=128, validation_split=0.1)

해설: 옷은 숫자보다 내부 질감(직조, 무늬, 주름)이 다양하고, 셔츠와 풀오버처럼 형태가 비슷한 클래스가 존재합니다. 3×3 작은 필터 몇 개로는 질감과 미세 차이를 구분하기 부족해 정확도가 92% 내외에 머뭅니다.

도전: ‘가장 헷갈리는 숫자’ 찾기

섹션 제목: “도전: ‘가장 헷갈리는 숫자’ 찾기”

테스트셋에서 모델이 틀린 이미지 중 예측 확률이 가장 높은 오답 Top 5를 찾아 출력하세요. 이는 모델이 “자신 있게 틀린” 경우입니다.

힌트

model.predict(x_test)로 확률을 얻고, 예측이 정답과 다른 샘플만 골라 해당 예측 확률 값으로 정렬합니다.

정답 코드
import numpy as np
probs = model_v3.predict(x_test, verbose=0)
preds = probs.argmax(axis=1)
wrong_idx = np.where(preds != y_test)[0]
# 자신 있게 틀린 순서 — 예측 확률 내림차순
confidence_of_wrong = probs[wrong_idx, preds[wrong_idx]]
top5 = wrong_idx[np.argsort(-confidence_of_wrong)[:5]]
plt.figure(figsize=(12, 3))
for i, idx in enumerate(top5):
plt.subplot(1, 5, i + 1)
plt.imshow(x_test[idx, :, :, 0], cmap='gray')
plt.title(f"정답 {y_test[idx]} / 예측 {preds[idx]}\n({probs[idx, preds[idx]]:.2f})")
plt.axis('off')
plt.show()

이 Top 5 이미지를 Augmentation 파이프라인에 우선 포함시키면 실무에서는 ‘어려운 샘플 재학습(hard-example mining)‘이 됩니다.



객관식 1. Conv2D 층이 하는 일로 가장 알맞은 것은?

① 이미지를 1차원으로 펼친다
② 작은 필터를 이미지 위에서 이동시키며 국소 패턴을 감지한다
③ 영역에서 가장 큰 값만 남겨 크기를 줄인다
④ 최종 클래스 확률을 계산한다

정답 확인

정답: ② ①은 Flatten, ③은 MaxPooling, ④는 softmax Dense층의 역할입니다. Conv2D는 필터(커널)로 국소 영역을 훑어 특징을 추출하는 층입니다.

객관식 2. 학습 곡선에서 훈련 정확도는 99%인데 검증 정확도가 92%에 머물고 오히려 내려가기 시작했습니다. 가장 적절한 처방은?

① Conv 블록을 더 많이 쌓는다
② 학습률을 10배 키운다
③ Dropout 비율을 높이거나 Data Augmentation을 추가한다
④ 배치 크기를 1로 줄인다

정답 확인

정답: ③ 훈련 정확도만 높고 검증 정확도가 벌어지는 전형적 과적합 신호입니다. 모델 용량을 더 키우면(①) 더 심해집니다. Dropout과 Augmentation이 표준 처방입니다.

객관식 3. 학습된 첫 Conv 층의 필터를 이미지로 그렸더니 가로선, 세로선, 대각선 모양이 나타났습니다. 이 관찰이 의미하는 바는?

① 모델이 제대로 학습되지 않은 상태이다
② 필터가 저수준의 에지(edge) 패턴을 잡도록 학습되었다
③ 데이터셋이 너무 단순하다는 뜻이다
④ Dropout을 적용하지 않았다는 증거이다

정답 확인

정답: ② 첫 Conv 층은 저수준 특징(에지, 곡선)을 잡도록 수렴하는 것이 일반적이며, 이는 정상적이고 오히려 학습이 잘 되었다는 신호입니다. 더 깊은 층으로 갈수록 부위·형태 같은 고수준 패턴을 잡습니다.

서술형 1. MNIST에서는 Data Augmentation으로 horizontal_flip=True(좌우 뒤집기)를 쓰지 않는 것이 일반적입니다. 그 이유를 설명하고, 반대로 강아지·고양이 사진 분류에서는 왜 좌우 뒤집기를 써도 되는지 비교해 서술하세요.

예시 답안

Augmentation은 “뒤집어도 같은 정답이어야 한다”는 전제 위에서만 정당합니다. 손글씨 숫자에서 2를 좌우로 뒤집으면 2가 아닌 다른 모양이 되고, 특히 6과 9처럼 뒤집으면 다른 숫자로 바뀌는 클래스도 있어 모델에게 잘못된 정답을 가르치게 됩니다. 반면 강아지·고양이 사진은 피사체가 왼쪽을 보든 오른쪽을 보든 여전히 같은 동물이므로 좌우 뒤집기는 안전한 데이터 증강이 되고, 훈련 데이터를 사실상 두 배로 늘리는 효과를 냅니다. 즉 Augmentation 기법 선택은 “그 변환 후에도 레이블이 유지되는가”를 기준으로 결정해야 합니다.

자기점검 체크리스트

  • Conv2D, MaxPooling2D, Flatten, Dense의 역할을 내 말로 설명할 수 있다
  • 학습 곡선을 보고 과적합 여부를 판단하고 Dropout·Augmentation으로 처방할 수 있다
  • 학습된 필터와 특징 맵을 시각화해 모델의 ‘시선’을 해석할 수 있다
  • 우체국 기계가 손글씨를 읽는 원리를 CNN 관점에서 설명할 수 있다

아래 세 질문에 짧게 답을 적어보세요.

  • 오늘 만든 CNN이 내가 기대한 것보다 잘했던 점못했던 점은 각각 무엇이었습니까?
  • 필터 시각화를 처음 봤을 때 가장 의외였던 발견은 무엇이었습니까?
  • 내가 이 모델을 다음 주에 현실 문제에 적용한다면, 어떤 데이터셋을 가져와 어떤 전처리를 추가하겠습니까?

마지막 10차시에서는 지금까지 배운 내용을 모아 자신만의 이미지 데이터셋으로 CNN 프로젝트를 수행합니다. MNIST처럼 “깔끔한 데이터”가 없을 때 어떻게 직접 수집하고, 라벨링하고, 작은 데이터로도 좋은 성능을 내는 전이학습(transfer learning)을 맛봅니다. 여러분이 만든 모델이 스스로 찍은 사진에서도 작동하는 순간을 경험하게 될 것입니다.