[강화학습 기초 4-2] 텐서플로로 첫 신경망 만들기
최신 텐서플로로 MNIST 손글씨 숫자 분류 신경망을 만들며, 강화학습 코드에도 그대로 쓰이는 구조를 익힙니다.
강화학습 기초 시리즈 · 12편 중 9번째 글
연재 목차 보기
대표가 쓴 『개발자답게 코드로 익히는 강화학습』(프리렉, 2025)의 내용을 블로그에 맞게 정리해 연재합니다.
이번 편에서는 텐서플로로 손글씨 숫자(MNIST)를 맞히는 신경망을 직접 만들어 봅니다. 강화학습 코드도 결국 이 구조 위에 올라가므로, 여기서 흐름을 익혀 두면 6장의 DQN 코드가 훨씬 쉽게 읽힙니다.
실습 환경 준비
가장 쉬운 방법은 구글 코랩(Colab)입니다. 브라우저만 있으면 되고, 텐서플로가 이미 설치되어 있습니다. 내 PC에서 하려면 최신 파이썬을 설치한 뒤 아래 명령으로 필요한 패키지를 설치합니다.
pip install tensorflow gymnasium matplotlib notebook
책과 달라진 점
책은 파이썬 3.7, 텐서플로 2.2, OpenAI Gym 기준으로 쓰였습니다. 지금은 Gym 대신 후속 패키지인 Gymnasium을 쓰고, 텐서플로도 버전이 많이 올라갔습니다. 이 연재의 코드는 2026년 기준 최신 버전(텐서플로 2.21, Gymnasium 1.4)에서 실행을 확인했습니다.
텐서플로 2의 장점
텐서플로 1은 계산 그래프를 먼저 정의하고 따로 실행하는 방식이라 초보자가 이해하기 어려웠습니다. 텐서플로 2는 코드를 쓴 순서대로 바로 실행되고(즉시 실행 모드), Keras라는 고수준 API로 기능이 정리되어 구조가 단순합니다.
전체 코드
import tensorflow as tf
# 1. 데이터 불러오기 (훈련 60,000장, 검증 10,000장)
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0 # 0~255 → 0~1로 맞추기
# 2. 신경망 구성
model = tf.keras.Sequential([
tf.keras.layers.Input(shape=(28, 28)),
tf.keras.layers.Flatten(), # 28×28 → 784
tf.keras.layers.Dense(128, activation="relu"), # 은닉층
tf.keras.layers.Dense(10, activation="softmax"), # 0~9 숫자별 확률
])
# 3. 학습 방법 정하기
model.compile(optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
# 4. 학습과 검증
model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test, verbose=2)
프로그램은 네 단계로 이루어집니다. 앞으로 볼 강화학습 코드도 같은 순서를 따릅니다.
1. 데이터 불러오기
MNIST는 28×28 크기의 손글씨 숫자 이미지 70,000장입니다. 60,000장은 훈련용, 10,000장은 검증용으로 나뉘어 있습니다. 나누는 이유는 공부한 문제집으로 시험을 보면 실력을 제대로 잴 수 없기 때문입니다. 픽셀 값(0~255)은 255로 나눠 0~1로 맞춰 주면 학습이 안정적입니다.
2. 신경망 구성
- Flatten: Dense 층은 1차원 입력을 받으므로 28×28 이미지를 784개 숫자 한 줄로 펼칩니다.
- Dense(128, relu): 노드 128개짜리 은닉층입니다. ReLU는 시그모이드처럼 비선형성을 더하는 활성 함수인데, 깊은 신경망에서 학습이 더 잘 됩니다. 노드 수는 정답이 없어서 바꿔 가며 찾습니다.
- Dense(10, softmax): 숫자 0~9에 해당하는 출력 10개입니다. softmax는 출력값을 0~1로 바꾸고 합이 1이 되게 만들어, "이 이미지가 각 숫자일 확률"을 알려 줍니다.
3. 학습 방법 정하기
- optimizer = "adam": 손실을 줄이는 방법입니다. 기본 경사하강법보다 빠르고 안정적이라 가장 많이 씁니다.
- loss = "sparse_categorical_crossentropy": 정답이 0~9 같은 정수일 때 쓰는 분류용 손실 함수입니다. 회귀에서 쓴 MSE와 역할은 같고 계산 방식만 다릅니다.
- metrics = ["accuracy"]: 학습 중에 정확도를 함께 보여 줍니다.
4. 학습과 검증
fit은 훈련 데이터로 학습합니다. epochs = 5면 60,000장을 다섯 번 반복해 봅니다. 화면에 epoch마다 loss(작을수록 좋음)와 accuracy(1에 가까울수록 좋음)가 출력됩니다. evaluate는 학습에 쓰지 않은 검증 데이터로 최종 정확도를 확인합니다.
코드 구성 요약
| 단계 | MNIST | 강화학습(DQN)에서는 |
|---|---|---|
| 데이터 | 미리 준비된 이미지 | 에이전트가 환경에서 행동하며 직접 모음 |
| 신경망 | 이미지 → 숫자별 확률 | 상태 → 행동별 Q값 |
| 손실 | 크로스엔트로피 | 목표 Q값과 예측 Q값의 MSE |
| 학습 | fit 한 번 | 모은 데이터로 조금씩 계속 |
다음 편에서는 신경망이 어떻게 강화학습의 가치함수를 대신하는지, 그 다리 역할을 하는 함수 근사를 봅니다.