밑바닥부터 시작하는 딥러닝 1 - 신경망 학습 알고리즘
4장 후반의 코드는 길어 보인다. 하지만 새 개념이 갑자기 쏟아지는 것이 아니다. 앞에서 배운 순전파, 손실 함수, 미니배치, 기울기 계산을 한 클래스 안에 모아둔 것이다.
이 글의 목표는 코드를 한 줄씩 외우는 것이 아니라, 신경망 학습이 어떤 순서로 반복되는지 구조를 잡는 것이다.
이번 글에서 잡을 것
- `params`는 가중치와 편향을 저장한다.
- `predict`는 순전파로 예측값을 만든다.
- `loss`는 예측값과 정답을 비교해 벌점을 계산한다.
- `gradient`는 손실을 줄이기 위한 방향을 계산한다.
- 학습 루프는 미니배치 선택, 기울기 계산, 매개변수 갱신의 반복이다.
클래스가 들고 있는 것
| 구성 | 역할 | 읽는 관점 |
|---|---|---|
| params | W1, b1, W2, b2 저장 | 모델이 조정할 값 |
| predict | 입력에서 출력까지 계산 | 순전파 |
| loss | 현재 예측의 벌점 계산 | 학습 목표 |
| accuracy | 맞힌 비율 계산 | 평가용 지표 |
| gradient | 각 매개변수의 수정 방향 계산 | 학습의 핵심 단서 |
학습 루프의 4단계
이 흐름이 확률적 경사 하강법(SGD)의 기본 구조다. 무작위로 데이터를 뽑고, 그 데이터에 대해 손실을 줄이는 방향을 계산하고, 매개변수를 조금 바꾼 뒤 다시 반복한다.
전체 코드를 읽을 때의 우선순위
먼저 볼 것
데이터가 어디서 들어오고 손실이 어디서 계산되는지 본다.
다음 볼 것
기울기가 params의 어떤 키와 대응되는지 본다.
나중에 볼 것
수치 미분 구현 세부와 속도 문제를 본다.
지금 단계의 기준
코드 전체를 외우는 것보다 `미니배치 -> 기울기 -> 갱신 -> 반복` 구조를 설명할 수 있으면 충분하다.
훈련 데이터와 시험 데이터
학습 중 손실이 줄어드는 것만으로는 충분하지 않다. 훈련 데이터에만 잘 맞고 처음 보는 데이터에는 약한 과대적합이 생길 수 있기 때문이다. 그래서 에포크마다 훈련 데이터와 시험 데이터의 정확도를 함께 확인한다.
| 데이터 | 쓰임 | 주의점 |
|---|---|---|
| 훈련 데이터 | 매개변수 학습 | 너무 잘 맞으면 과대적합 가능 |
| 시험 데이터 | 최종 성능 평가 | 학습에 직접 쓰지 않는다 |
| 검증 데이터 | 하이퍼파라미터 조정 | 6장에서 다시 등장 |
TwoLayerNet의 최소 뼈대
TwoLayerNet을 처음 보면 코드가 길어 보여서 부담스럽다. 하지만 뼈대만 놓고 보면 가중치 보관함과 기능 함수 몇 개로 이루어진 객체다.
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size):
self.params = {}
self.params["W1"] = ...
self.params["b1"] = ...
self.params["W2"] = ...
self.params["b2"] = ...
def predict(self, x):
# X -> W1,b1 -> 활성화 -> W2,b2 -> 출력
pass
def loss(self, x, t):
# 예측값과 정답을 비교해 손실 계산
pass
def accuracy(self, x, t):
# 맞힌 비율 계산
pass
def gradient(self, x, t):
# W1,b1,W2,b2를 어떻게 바꿀지 계산
pass
읽는 기준
이 클래스는 `params`를 보관하고, `predict/loss/gradient`로 학습 루프에 필요한 값을 계산해 돌려주는 구조다. 그래서 먼저 볼 것은 긴 코드가 아니라 어떤 데이터가 어느 함수로 들어가고 나오는지다.
params와 grads는 짝을 이룬다
학습 루프에서 가장 중요한 연결은 `params`와 `grads`의 키가 서로 대응된다는 점이다. `params['W1']`을 바꾸려면 `grads['W1']`이 필요하고, `params['b1']`을 바꾸려면 `grads['b1']`이 필요하다.
| params 키 | 담긴 값 | 대응되는 grads | 학습 때 하는 일 |
|---|---|---|---|
| W1 | 입력층 -> 은닉층 가중치 | grads['W1'] | W1을 손실이 줄어드는 방향으로 갱신 |
| b1 | 은닉층 편향 | grads['b1'] | b1 갱신 |
| W2 | 은닉층 -> 출력층 가중치 | grads['W2'] | W2 갱신 |
| b2 | 출력층 편향 | grads['b2'] | b2 갱신 |
for key in ("W1", "b1", "W2", "b2"):
network.params[key] -= learning_rate * grad[key]
핵심 연결
학습은 결국 `params`에 들어 있는 숫자들을 `grad` 방향으로 조금씩 바꾸는 반복이다. `predict`와 `loss`는 이 숫자를 평가하고, `gradient`는 바꿀 방향을 계산한다.
학습 루프를 코드 모양으로 보면
전체 학습 코드는 길어도 중심 루프는 아래 구조에서 크게 벗어나지 않는다.
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
grad = network.gradient(x_batch, t_batch)
for key in ("W1", "b1", "W2", "b2"):
network.params[key] -= learning_rate * grad[key]
읽는 순서
데이터를 뽑고, 기울기를 구하고, `params`를 갱신한다. 이 세 줄기가 보이면 전체 코드를 훨씬 덜 무섭게 읽을 수 있다.
에포크는 언제 한 바퀴인가
에포크(epoch)는 훈련 데이터를 전체적으로 한 번 다 본 정도의 단위다. 훈련 데이터가 60,000개이고 배치 크기가 100이면, 600번 미니배치를 뽑았을 때 대략 1에포크라고 볼 수 있다.
| 훈련 데이터 수 | 배치 크기 | 1에포크당 반복 수 |
|---|---|---|
| 60,000 | 100 | 600 |
| 60,000 | 200 | 300 |
| 10,000 | 100 | 100 |
정상적인 학습 로그는 어떻게 보여야 하나
실제 수치는 데이터와 초기값에 따라 달라지지만, 학습이 제대로 진행되면 손실은 대체로 내려가고 정확도는 올라가는 방향을 보인다. 반대로 손실이 계속 폭주하면 학습률이 너무 크거나 구현이 잘못됐을 가능성을 의심해야 한다.
| 관찰값 | 좋은 신호 | 의심할 상황 |
|---|---|---|
| loss | 반복할수록 대체로 감소 | 계속 증가하거나 NaN |
| train accuracy | 에포크마다 상승 | 전혀 오르지 않음 |
| test accuracy | train과 함께 상승 | train만 높고 test는 낮음 |
이 표의 용도
학습 루프 코드를 외우기보다, 로그를 보고 학습이 움직이는지 판단하는 기준으로 쓰면 된다.
한 번의 반복을 말로 풀면
미니배치 100장을 뽑았다고 하자. 네트워크는 그 100장을 예측하고, 정답과 비교해 손실을 계산한 뒤, `W1`, `b1`, `W2`, `b2` 각각이 손실에 얼마나 영향을 주는지 구한다. 마지막으로 그 네 값을 조금씩 수정한다.
| 순서 | 코드에서 보이는 값 | 의미 |
|---|---|---|
| 1 | x_batch, t_batch | 이번 반복에서 사용할 작은 데이터 묶음 |
| 2 | loss | 현재 모델이 이 묶음에서 받은 벌점 |
| 3 | grad | 각 매개변수를 어느 방향으로 바꿀지 |
| 4 | params 갱신 | 모델 자체가 조금 바뀜 |
스스로 점검
- `params`와 `grads`의 차이를 설명할 수 있는가?
- 학습 루프 4단계를 순서대로 말할 수 있는가?
- 손실이 줄어도 시험 데이터 평가가 필요한 이유를 설명할 수 있는가?
이번 글에서 기억할 것
- `params`는 가중치와 편향을 저장한다.
- `predict`는 순전파로 예측값을 만든다.
- `loss`는 예측값과 정답을 비교해 벌점을 계산한다.
- `gradient`는 손실을 줄이기 위한 방향을 계산한다.
- 학습 루프는 미니배치 선택, 기울기 계산, 매개변수 갱신의 반복이다.
다음 글로 이어지는 질문
다음 글에서는 느린 수치 미분 대신 빠르게 기울기를 구하는 오차역전파법으로 넘어간다.
한 줄 정리: TwoLayerNet은 새로운 개념 덩어리가 아니라, 지금까지 배운 학습 부품들을 한 루프로 묶은 조립도다.