MNIST 손글씨 인식으로 이해하는 신경망의 동작 원리

머신러닝을 처음 배우면 MNIST라는 손글씨 숫자 데이터셋을 거의 무조건 만난다. "Hello World" 같은 존재다. 이 글에서는 MNIST 분류 코드를 한 줄씩 뜯어보면서 신경망이 실제로 어떻게 동작하는지 살펴본다.

전체 코드

먼저 전체 코드를 보자. Google Colab에서 바로 실행할 수 있다.

import tensorflow as tf
from tensorflow.keras import layers, models

# 데이터 불러오기
(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()
train_images = train_images / 255.0
test_images = test_images / 255.0

# 모델 만들기
model = models.Sequential([
    layers.Flatten(input_shape=(28, 28)),
    layers.Dense(128, activation='relu'),
    layers.Dense(10, activation='softmax')
])

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

# 학습
model.fit(train_images, train_labels, epochs=5)

# 평가
model.evaluate(test_images, test_labels)

실행하면 약 97~98% 정확도가 나온다. 이제 하나씩 뜯어보자.

데이터: 28×28 픽셀 이미지

MNIST는 0부터 9까지 손글씨 숫자 이미지 7만 장이다. 각 이미지는 28×28 픽셀이고, 각 픽셀은 0(흰색)~255(검정) 값을 가진다.

train_images = train_images / 255.0

이 줄은 0255를 01 사이로 바꾼다. 숫자가 작아야 학습이 안정적으로 된다.

28×28 = 784개 픽셀이니까, 결국 이미지 하나는 784개 숫자로 표현된다.

Sequential: 층을 순서대로 쌓기

model = models.Sequential([...])

Sequential은 "순차적"이라는 뜻이다. 레고 블록 쌓듯이 층을 아래에서 위로 순서대로 쌓는 가장 간단한 방식이다.

데이터가 위에서 아래로 순서대로 흘러간다.

이미지 입력
    ↓
[Flatten]
    ↓
[Dense + ReLU]
    ↓
[Dense + Softmax]
    ↓
결과 출력

Flatten: 이미지를 1차원으로 펼치기

layers.Flatten(input_shape=(28, 28))

28×28 2차원 이미지를 784개 숫자 1줄로 쭉 펼친다. Dense 층은 1차원 입력만 받을 수 있어서 이 과정이 필요하다.

Dense: 신경망의 Layer

layers.Dense(128, activation='relu')

Dense는 "밀집된"이라는 뜻이다. 모든 입력이 모든 뉴런에 연결되어 있어서 이렇게 부른다. "완전 연결층(Fully Connected Layer)"이라고도 한다.

Dense 하나가 신경망의 층(Layer) 하나다.

입력층        은닉층         출력층
(784개)      (128개)        (10개)

  ●            ●              ●
  ●            ●              ●
  ●     →      ●      →       ●
  ●            ●              ●
  ...          ...            ...

         Dense(128)      Dense(10)
종류뉴런 수학습
Flatten변환층-
Dense(128)은닉층 (Hidden Layer)128개
Dense(10)출력층 (Output Layer)10개

각 뉴런이 하는 일은 간단하다.

(입력1 × 가중치1) + (입력2 × 가중치2) + ... + (입력784 × 가중치784) + 편향

784개 입력에 각각 가중치를 곱하고, 다 더하고, 편향을 더한다. 이게 끝이다.

가중치는 각 입력이 얼마나 중요한지를 나타낸다. 편향은 전체 기준점을 조절하는 값이다.

ReLU: 음수는 0으로

activation='relu'

ReLU(Rectified Linear Unit)는 활성화 함수다. 규칙이 아주 단순하다.

음수 → 0
양수 → 그대로

왜 필요할까? 활성화 함수가 없으면 층을 아무리 깊게 쌓아도 결국 직선 하나로 표현된다. ReLU 같은 활성화 함수가 있어야 복잡한 곡선 패턴을 학습할 수 있다.

128개 뉴런을 통과한 결과는 128개 숫자다. 각 숫자는 해당 뉴런이 "얼마나 활성화됐는지"를 나타낸다. ReLU 때문에 절반 이상이 0이 된다. "이 특징은 없다"는 뜻이다.

두 번째 Dense: 128개를 10개로

layers.Dense(10, activation='softmax')

똑같은 방식이다. 이번엔 뉴런 10개가 각각 128개 입력을 받는다.

뉴런0: 128개 계산 → 1.2
뉴런1: 128개 계산 → 0.5
뉴런2: 128개 계산 → 8.7
...
뉴런9: 128개 계산 → 0.3

결과: [1.2, 0.5, 8.7, 0.1, 0.2, 0.4, 0.1, 0.3, 0.2, 0.3]

Softmax: 확률로 변환

activation='softmax'

위에서 나온 10개 숫자는 아직 확률이 아니다. Softmax가 합이 1이 되도록 변환한다.

[1.2, 0.5, 8.7, ...] → [0.05, 0.02, 0.85, ...]

"0일 확률 5%, 1일 확률 2%, 2일 확률 85%..."가 된다. 가장 높은 걸 선택하면 예측 결과다.

전체 흐름

[784개] → [128개] → [10개]
 픽셀값     특징값     확률값

[0.1, 0.9, 0.2, ...] → [0, 2.5, 8.1, ...] → [0.01, 0.02, 0.85, ...]
      784개                 128개                   10개
                                              "2일 확률 85%"

가중치는 몇 개?

Dense(128): 784 × 128 = 100,352개 가중치 + 128개 편향
Dense(10):  128 × 10  =   1,280개 가중치 +  10개 편향
────────────────────────────────────────────────────────
총합:                    101,770개

10만 개 숫자가 이 모델의 학습 대상이다.

ReLU와 Softmax는 가중치가 없다. 그냥 수식이다. 진짜 학습하는 건 Dense 층의 가중치와 편향이다.

학습: 가중치 최적값 찾기

처음에 모든 가중치는 랜덤이다. 그래서 처음엔 찍는 수준(10%)이다.

학습 과정은 이렇다.

  1. 예측한다: 이미지를 넣고 결과를 본다
  2. 정답과 비교한다: 얼마나 틀렸는지 계산한다 (손실)
  3. 가중치를 조정한다: 덜 틀리는 방향으로 조금씩 바꾼다
  4. 반복한다: 6만 장 전체를 5번 반복 (epochs=5)
model.compile(
    optimizer='adam',           # 가중치 조정 방법
    loss='sparse_categorical_crossentropy',  # 얼마나 틀렸는지 계산
)

model.fit(train_images, train_labels, epochs=5)

실행하면 이렇게 나온다.

Epoch 1/5 - loss: 0.26 - accuracy: 0.92
Epoch 2/5 - loss: 0.11 - accuracy: 0.96
Epoch 3/5 - loss: 0.08 - accuracy: 0.97
Epoch 4/5 - loss: 0.05 - accuracy: 0.98
Epoch 5/5 - loss: 0.04 - accuracy: 0.98

반복할수록 손실은 줄고 정확도는 오른다. 10만 개 가중치가 점점 최적값에 가까워지는 거다.

층을 더 쌓으면? 딥러닝

Dense를 더 쌓으면 더 깊은 신경망이 된다.

model = models.Sequential([
    layers.Flatten(input_shape=(28, 28)),
    layers.Dense(256, activation='relu'),  # 은닉층 1
    layers.Dense(128, activation='relu'),  # 은닉층 2
    layers.Dense(64, activation='relu'),   # 은닉층 3
    layers.Dense(10, activation='softmax') # 출력층
])

이러면 4개 층 신경망이 된다. 이렇게 층을 깊게(deep) 쌓는 게 딥러닝이다.

층이 깊어질수록 더 복잡한 패턴을 학습할 수 있다. 하지만 MNIST 같은 단순한 문제는 2개 층으로도 충분하다.

정리

결국 신경망 학습이란 이거다.

  • 모델 구조: 784개 입력 → 128개 뉴런 → 10개 출력
  • 학습 대상: Dense 층의 가중치와 편향 (약 10만 개)
  • 학습 방법: 예측 → 정답과 비교 → 가중치 조정 → 반복

Sequential, Dense, ReLU, Softmax 같은 용어가 처음엔 어려워 보이지만, 하나씩 뜯어보면 결국 숫자를 곱하고 더하고 변환하는 단순한 연산의 조합이다.

이 구조를 이해하면 더 복잡한 신경망도 같은 원리로 읽을 수 있다. AI 입문: 머신러닝부터 GPT까지 글에서 전체 맥락도 함께 보면 도움이 된다.