Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

2 P1. 기본 신경망과 훈련

Updated: 23 aug 2026

P1에서는 손글씨 숫자 이미지인 MNIST를 이용해 첫 신경망을 구성하고, 훈련하고, 평가하는 전체 과정을 이해한다.

세 번의 강의에서 다음 질문을 차례로 다룬다.

신경망은 어떻게 예측하는가?
데이터는 신경망 안에서 어떤 모양으로 전달되는가?
신경망은 어떻게 더 좋은 예측을 하도록 훈련되는가?

Keras를 기본 흐름으로 사용하고, 같은 원리가 PyTorch에서는 어떻게 표현되는지 함께 확인한다.

2.1강의 1 — 신경망은 어떻게 예측하는가?

먼저 하나의 실제 문제에서 시작하자.

MNIST에는 28 × 28 크기의 손글씨 숫자 이미지가 들어 있다.

  • 입력: 손글씨 숫자 이미지

  • 타깃: 0부터 9까지의 숫자

  • 목표: 새로운 이미지가 어떤 숫자인지 예측

따라서 이 문제는 10개의 범주형 값 가운데 하나를 예측하는 다중분류 문제다.

이 문제를 해결하기 위해 먼저 아주 간단한 신경망을 하나 만들어보자.

2.1.1Keras로 첫 신경망 구성

기존 dlp2의 MNIST 예제는 두 개의 Dense 층을 순서대로 연결한다.

지금은 코드의 세부 문법을 외우기보다 모델이 어떤 구조인지를 먼저 본다.

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Dense(512, activation="relu"),
    layers.Dense(10, activation="softmax")
])

2.1.2이 신경망은 어떤 구조일까?

MNIST 이미지 한 장은 28 × 28 = 784개의 픽셀값으로 이루어진다.

기본 완전연결 신경망에서는 이 이미지를 길이가 784인 벡터로 펼쳐서 사용한다.

따라서 위 모델의 구조는 다음과 같이 읽을 수 있다.

784개의 입력값 → 512개의 은닉 유닛 → 10개의 출력값

신경망에서 입력을 받는 부분을 입력층(input layer), 입력과 출력 사이의 층을 은닉층(hidden layer), 마지막 예측을 만드는 층을 출력층(output layer)이라고 한다.

각 층을 구성하는 계산 단위를 흔히 뉴런(neuron) 또는 유닛(unit)이라고 한다.

이 예제에서는 784개의 입력값이 512개의 은닉 유닛을 거쳐 10개의 출력값으로 변환된다.

2.1.3신경망 구조를 읽어보자

다음 구조를 보고 각 숫자가 무엇을 의미하는지 설명해보자.

784 → 512 → 10

  • 784: 입력 이미지 한 장을 펼쳤을 때의 픽셀 수

  • 512: 은닉층의 유닛 수

  • 10: 예측하려는 범주의 수

다음 질문에 답해보자.

  1. 입력 이미지가 32 × 32라면 입력값은 몇 개인가?

  2. 개와 고양이를 구분하는 문제라면 출력층은 어떻게 달라져야 할까?

  3. 하나의 수치형 값을 예측하는 회귀 문제라면 출력층은 어떻게 달라질까?

해설 보기
  1. 이미지를 펼치면 32 × 32 = 1024개의 입력값이 된다.

  2. 이진분류에서는 보통 출력 1개를 사용한다.

  3. 하나의 수치형 값을 예측하는 회귀에서는 보통 출력 1개를 사용한다.

2.1.4완전연결층

완전연결층에서는 모든 입력값이 모든 출력 유닛과 연결된다.

한 층의 계산을 단순화하면 다음과 같이 생각할 수 있다.

z=xW+bz = xW + b
  • xx: 층에 들어오는 입력

  • WW: 가중치(weight)

  • bb: 편향(bias)

  • zz: 계산된 출력

가중치와 편향은 모델의 파라미터(parameter)다.

가중치와 편향은 사람이 미리 정답을 입력하는 값이 아니다.

모델 훈련은 훈련 데이터를 이용하여 모델의 파라미터를 결정하는 과정이다.

2.1.5파라미터는 몇 개일까?

784 → 512 완전연결층에서는 784개의 입력값이 각각 512개의 출력 유닛과 연결된다.

따라서 가중치는

784×512784 \times 512

개이고, 512개의 출력 유닛마다 편향이 하나씩 있으므로 전체 파라미터 수는

784×512+512784 \times 512 + 512

개다.

마찬가지로 512 → 10 층의 파라미터 수는

512×10+10512 \times 10 + 10

개다.

은닉층의 유닛 수가 늘어나면 모델의 파라미터 수도 크게 늘어난다.

이 사실은 이후 모델 복잡도와 과대적합을 이해할 때 다시 중요해진다.

2.1.6왜 활성화 함수가 필요한가?

완전연결층의 기본 계산은 선형 변환이다.

선형 변환만 여러 번 이어 붙이면 결국 하나의 선형 변환과 같은 형태로 합쳐질 수 있기 때문에 복잡한 관계를 표현하는 데 한계가 있다.

그래서 은닉층에는 보통 활성화 함수(activation function)를 사용한다.

이 예제의 첫 번째 층에서는 ReLU를 사용한다.

ReLU(x)=max(0,x)\mathrm{ReLU}(x)=\max(0,x)

즉 음수는 0으로 바꾸고 양수는 그대로 둔다.

활성화 함수는 층의 출력에 비선형 변환을 추가해 신경망이 더 복잡한 관계를 표현할 수 있게 한다.

2.1.7ReLU를 직접 확인해보자

입력ReLU 출력
-20
00
33

계산 자체는 간단하지만 역할은 중요하다.

다음 질문을 생각해보자.

ReLU가 없다면 여러 완전연결층을 계속 쌓는 것이 왜 큰 의미가 없을까?

해설 보기

여러 선형 변환만 연속해서 적용하면 결국 하나의 선형 변환으로 합쳐질 수 있다. ReLU와 같은 비선형 활성화 함수가 들어가야 여러 층을 쌓아 더 복잡한 관계를 표현할 수 있다.

2.1.8출력층은 무엇을 나타낼까?

마지막 층의 10개 출력은 숫자 0부터 9까지에 대응한다.

Keras 예제에서는 softmax를 사용하여 10개 출력값을 각 숫자 범주에 대한 확률처럼 해석할 수 있도록 만든다.

이때 중요한 것은 API 이름을 외우는 것이 아니라 출력층이 무엇을 예측하도록 설계되었는지 이해하는 것이다.

입력 하나의 모양: (784,)
마지막 출력의 모양: (10,)

2.1.9문제에 따라 출력층이 달라진다

출력층은 무엇을 예측하려는가에 따라 달라진다.

문제출력층의 기본 형태
이진분류보통 출력 1개두 범주 중 하나
다중분류범주 수만큼 출력MNIST의 10개 숫자
회귀보통 출력 1개하나의 수치형 값 예측

P1에서는 세부적인 출력층 설계보다 다음만 기억한다.

문제 유형에 따라 출력층과 손실함수를 알맞게 선택해야 한다.

분류와 회귀의 출력층과 손실함수는 이후 프로젝트에서 다시 자세히 다룬다.

2.1.10같은 구조를 PyTorch로 표현하면

PyTorch에서도 같은 완전연결 신경망을 만들 수 있다.

import torch
from torch import nn

torch_model = nn.Sequential(
    nn.Linear(28 * 28, 512),
    nn.ReLU(),
    nn.Linear(512, 10)
)

Keras와 PyTorch의 표현 방식은 다르지만 모델의 핵심 구조는 같다.

개념KerasPyTorch
완전연결층Densenn.Linear
ReLUactivation="relu"nn.ReLU()
모델 연결Sequentialnn.Sequential
출력 수1010

PyTorch의 CrossEntropyLoss는 내부에서 필요한 계산을 처리하므로 마지막 층에 Softmax를 따로 넣지 않는 것이 일반적이다.

이것은 모델의 원리가 다른 것이 아니라 손실함수 API의 사용 방식이 다른 것이다.

같은 딥러닝 구조를 Keras와 PyTorch가 서로 다른 방식으로 표현한다.

2.2강의 2 — 데이터의 shape과 배치

딥러닝에서는 데이터를 텐서(tensor), 즉 여러 숫자를 담는 다차원 배열 형태로 다룬다.

P1에서는 텐서의 종류나 연산을 자세히 공부하지 않는다. 실제 코드를 읽는 데 필요한 다음 세 가지만 확인한다.

shape → axis → batch

특히 중요한 것은 데이터의 각 축이 무엇을 의미하는지 읽는 것이다.

2.2.1여러 데이터의 shape을 읽어보자

딥러닝에서는 데이터의 종류가 달라도 먼저 shape이 무엇을 뜻하는지 확인한다.

데이터예시 shape의미
표 형식 데이터(1000, 20)1000개 샘플, 20개 특성
흑백 이미지(60000, 28, 28)60000장, 각 이미지 28×28
컬러 이미지(50000, 32, 32, 3)50000장, 32×32 RGB 이미지
MLP 입력 배치(128, 784)128개 샘플, 각 샘플 784개 입력값

shape을 볼 때는 숫자를 그대로 읽는 것이 아니라

각 축이 무엇을 의미하는가?

를 확인해야 한다.

2.2.2MNIST 데이터의 shape

MNIST 훈련 이미지는 다음과 같은 모양을 갖는다.

[ (60000,;28,;28) ]

이를 의미별로 읽으면

(샘플 수, 높이, 너비)

이다.

이미지 하나를 선택하면 (28, 28)이고, 완전연결층에 입력하기 위해 펼치면 (784,)가 된다.

표 형식 데이터도 같은 방식으로 읽을 수 있다. 예를 들어 샘플이 1,000개이고 각 샘플에 특성이 20개라면 입력 데이터의 shape은 보통 (1000, 20)이다.

즉 shape은 단순한 숫자 묶음이 아니라 데이터의 구조를 설명하는 정보다.

2.2.3reshape에서는 무엇이 바뀌고 무엇이 그대로일까?

MNIST 이미지를 완전연결 신경망에 넣기 위해

(60000, 28, 28) → (60000, 784)

로 바꾼다.

여기서

  • 샘플 수 60000은 변하지 않는다.

  • 이미지 하나가 가진 픽셀 수 28 × 28 = 784도 변하지 않는다.

  • 다만 2차원 이미지의 공간적 배열을 1차원으로 펼친다.

reshape은 데이터를 새로 만들거나 버리는 것이 아니라 배열의 모양을 바꾸는 것이다.

이 차이는 나중에 CNN에서 중요하다.

MLP에서는 이미지를 펼쳐 사용하지만, CNN에서는 이미지의 공간적 구조를 유지한 채 처리한다.

Source
train_images.shape, train_images[0].shape, train_images[0].reshape(-1).shape

2.2.4데이터 전처리

기존 MNIST 이미지는 픽셀값이 0부터 255 사이의 정수다.

기존 dlp2 예제에서는

  1. (28, 28) 이미지를 (784,)로 펼치고,

  2. 자료형을 float32로 바꾸고,

  3. 255로 나누어 01 사이의 값으로 만든다.

Source
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255

test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

2.2.5배치는 왜 사용하는가?

훈련 데이터 60,000개를 한꺼번에 처리하지 않고 작은 묶음으로 나누어 처리한다.

이 작은 묶음을 배치(batch)라고 한다.

예를 들어 batch_size=128이면 한 번의 훈련 스텝에서 128개의 이미지를 사용한다.

배치 하나의 입력 shape은

[ (128,;784) ]

처럼 읽을 수 있다.

첫 번째 축: 샘플
두 번째 축: 각 샘플의 784개 입력값

2.2.6한 epoch에서는 몇 번 파라미터가 갱신될까?

훈련 데이터가 60,000개이고 batch_size=128이라면 한 epoch에서 필요한 배치 수는 대략

[ \frac{60000}{128} \approx 469 ]

개다.

즉 한 epoch 동안 대략 469번의 훈련 스텝이 수행된다.

각 스텝에서는

배치 입력 → 예측 → 손실 계산 → 그레이디언트 계산 → 파라미터 갱신

이 일어난다.

이것이 다음 강의의 신경망 훈련 과정으로 바로 이어진다.

2.2.7shape을 읽는 습관

딥러닝 코드에서 오류의 상당수는 데이터의 의미와 shape이 맞지 않을 때 발생한다.

따라서 모델을 보기 전에 항상 다음을 확인한다.

  1. 한 샘플의 shape은 무엇인가?

  2. 배치의 shape은 무엇인가?

  3. 모델의 출력 shape은 무엇인가?

  4. 타깃의 shape과 의미는 무엇인가?

Keras와 PyTorch를 함께 사용할 때도 API보다 먼저 shape을 확인한다.

2.2.8Keras와 PyTorch에서는 이미지 shape이 다를 수 있다

CNN에서는 같은 이미지 배치도 프레임워크에 따라 일반적인 차원 순서가 다르다.

  • Keras: (batch, height, width, channels)

  • PyTorch: (batch, channels, height, width)

예를 들어 32 × 32 RGB 이미지 64장의 배치는

  • Keras: (64, 32, 32, 3)

  • PyTorch: (64, 3, 32, 32)

처럼 표현될 수 있다.

지금은 외울 필요가 없다.

프레임워크마다 요구하는 shape이 다를 수 있으므로 코드를 실행하기 전에 shape을 확인한다.

는 습관만 기억한다.

2.3강의 3 — 신경망은 어떻게 훈련되는가?

모델을 구성했다고 해서 처음부터 숫자를 잘 맞히는 것은 아니다.

초기의 가중치와 편향은 좋은 예측을 하도록 정해져 있지 않다.

훈련은 반복적으로 다음 과정을 수행한다.

순전파 → 손실 계산 → 그레이디언트 계산 → 파라미터 갱신

2.3.1순전파

입력 데이터가 첫 번째 층에서 마지막 층까지 전달되어 예측값이 만들어지는 과정을 순전파(forward pass)라고 한다.

[ x \rightarrow \text{Dense} \rightarrow \text{ReLU} \rightarrow \text{Dense} \rightarrow \hat{y} ]

여기서 (\hat{y})는 모델의 예측값이고, (y)는 실제값이다.

순전파의 목적은 현재 파라미터를 이용해 현재 모델이 어떤 예측을 하는지 계산하는 것이다.

2.3.2손실함수

모델의 예측값과 실제값의 차이를 하나의 값으로 측정하는 함수가 손실함수(loss function)다.

MNIST처럼 여러 범주 가운데 하나를 예측하는 문제에서는 교차엔트로피 계열 손실함수를 사용한다.

손실값은 훈련 과정에서 파라미터를 어떻게 바꿀지 결정하기 위해 사용된다.

손실값이 작아지는 방향으로 파라미터를 바꾼다.

정확도와 손실함수는 같은 역할을 하지 않는다. 정확도는 결과를 이해하기 쉬운 평가 지표이고, 손실함수는 파라미터 갱신에 직접 사용된다.

2.3.3그레이디언트와 경사하강법

그레이디언트(gradient)는 파라미터를 조금 바꾸었을 때 손실값이 어느 방향으로 얼마나 변하는지 알려준다.

따라서 손실을 줄이려면 대략 그레이디언트의 반대 방향으로 파라미터를 움직이면 된다.

[ W \leftarrow W - \eta \nabla_W L ]

  • (L): 손실

  • (\nabla_W L): 가중치에 대한 손실의 그레이디언트

  • (\eta): 학습률(learning rate)

학습률은 한 번에 파라미터를 얼마나 움직일지 결정한다.

옵티마이저(optimizer)는 계산된 그레이디언트를 이용해 파라미터를 실제로 어떻게 갱신할지 정하는 방법이다. SGD, RMSprop, Adam 등이 대표적인 옵티마이저다.

2.3.4역전파

신경망에는 매우 많은 파라미터가 있다.

각 파라미터가 손실에 미치는 영향을 직접 하나씩 계산하는 대신, 역전파(backpropagation)는 출력 쪽에서 입력 쪽으로 계산을 거슬러 올라가며 필요한 그레이디언트를 효율적으로 계산한다.

직접 미분 공식을 길게 계산하는 것이 P1의 목표는 아니다.

P1에서 이해해야 할 핵심은 다음이다.

역전파는 손실을 줄이기 위해 각 파라미터를 어느 방향으로 바꿔야 하는지 계산하는 핵심 절차다.

2.3.5Keras에서는 훈련 과정이 fit() 안에 들어 있다

기존 dlp2 예제에서는 먼저 훈련 방법을 지정한다.

Source
model.compile(
    optimizer="rmsprop",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"]
)

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

fit()을 호출하면 내부적으로 배치마다 다음 과정이 반복된다.

예측 → 손실 → 그레이디언트 → 파라미터 갱신

Keras의 장점은 이 반복 과정을 매우 간단하게 사용할 수 있다는 것이다.

2.3.6PyTorch에서는 훈련 루프가 더 잘 보인다

PyTorch에서는 같은 과정을 직접 작성하는 경우가 많다.

Source
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.RMSprop(torch_model.parameters())

for x_batch, y_batch in train_loader:
    optimizer.zero_grad()

    pred = torch_model(x_batch)
    loss = loss_fn(pred, y_batch)

    loss.backward()
    optimizer.step()

각 줄을 딥러닝의 공통 원리와 연결하면 된다.

PyTorch 코드의미
pred = model(x_batch)순전파와 예측
loss_fn(pred, y_batch)손실 계산
loss.backward()그레이디언트 계산
optimizer.step()파라미터 갱신
optimizer.zero_grad()이전 그레이디언트 초기화

Keras의 fit()과 PyTorch의 training loop가 하는 핵심 일은 같다.

2.4훈련된 모델은 어떻게 확인할까?

훈련이 끝나면 훈련에 사용하지 않은 데이터에서 모델을 확인한다.

Keras에서는 predict()로 예측하고 evaluate()로 테스트 성능을 확인할 수 있다.

Source
test_digits = test_images[:10]
predictions = model.predict(test_digits)

print(predictions[0].argmax())
print(test_labels[0])

test_loss, test_acc = model.evaluate(test_images, test_labels)
print("test accuracy:", test_acc)

테스트 정확도가 높더라도 숫자 하나만 보고 모델이 좋다고 결론 내리지는 않는다.

P1 실습에서는 다음을 함께 확인한다.

  • 훈련 손실은 어떻게 변했는가?

  • 훈련 정확도는 어떻게 변했는가?

  • 테스트 정확도는 어느 정도인가?

  • 어떤 이미지를 잘못 분류했는가?

  • 잘못 분류한 이미지에는 어떤 특징이 있는가?

이 질문은 이후 모든 프로젝트에서 반복된다.

2.5Keras와 PyTorch: 무엇을 같게 보고 무엇을 다르게 볼까?

두 프레임워크를 비교할 때 API 이름을 일대일로 외우는 것이 목적은 아니다.

공통 질문을 중심으로 코드를 읽는다.

  1. 입력 데이터의 shape은 무엇인가?

  2. 모델은 어떤 층으로 구성되는가?

  3. 마지막 출력은 무엇을 의미하는가?

  4. 손실함수는 무엇인가?

  5. 어떤 옵티마이저가 파라미터를 갱신하는가?

  6. 훈련과 평가가 어떻게 구분되는가?

프레임워크는 달라도 딥러닝의 계산 구조는 같다.

2.6P1 강의 핵심

P1에서 반드시 남겨야 할 개념은 많지 않다.

모델 훈련은 훈련 데이터를 이용하여 모델의 파라미터를 결정하는 과정이다.

그 과정은 다음 흐름으로 요약된다.

입력 → 순전파 → 예측 → 손실 → 역전파 → 파라미터 갱신

그리고 다음을 기억한다.

  • 입력층, 은닉층, 출력층은 서로 다른 역할을 한다.

  • 문제 유형에 따라 출력층과 손실함수가 달라진다.

  • 데이터와 모델을 볼 때 shape을 먼저 확인한다.

  • Keras의 fit()과 PyTorch의 training loop는 같은 훈련 원리를 구현한다.

  • 성능 숫자만 보지 말고 오류가 어디에서 발생하는지 확인한다.

2.7확인 문제

2.7.1문제 1

MNIST 이미지 한 장의 원래 shape은 (28, 28)이다. 완전연결 신경망에 입력하기 위해 (784,)로 바꾸는 이유를 설명하라.

해설 보기

P1의 완전연결층은 하나의 샘플을 1차원 특성 벡터로 입력받는다. 따라서 28×28 픽셀을 784개의 입력값으로 펼친다. 픽셀 자체를 버리는 것이 아니라 배열의 모양을 바꾸는 것이다.

2.7.2문제 2

정확도가 높은데도 손실함수가 필요한 이유는 무엇인가?

해설 보기

훈련에서는 파라미터를 어느 방향으로 바꿔야 하는지 계산할 수 있어야 한다. 손실함수는 파라미터 변화에 따른 성능 변화를 수치적으로 제공하고 그레이디언트를 계산하는 데 사용된다. 정확도는 평가에는 직관적이지만 일반적으로 파라미터 갱신을 위한 손실함수로 직접 사용하지 않는다.

2.7.3문제 3

PyTorch에서 loss.backward()optimizer.step()의 역할을 구분하라.

해설 보기

loss.backward()는 손실에 대한 각 파라미터의 그레이디언트를 계산한다. optimizer.step()은 계산된 그레이디언트를 이용해 실제 파라미터 값을 갱신한다.

2.7.4문제 4

Keras 모델과 PyTorch 모델의 테스트 정확도가 거의 같다면 두 코드가 완전히 같은 모델이라고 결론 내릴 수 있는가?

해설 보기

그렇지 않다. 비슷한 정확도만으로 모델 구조, 초기화, 옵티마이저 설정, 훈련 과정이 모두 같다고 할 수 없다. 모델 구조와 훈련 조건을 함께 확인해야 한다.