AI 깊이 이해하기 — 자율 시스템 설계까지 · 03
모델의 답이 틀렸다는 사실만으로는 어느 가중치를 얼마나 바꿀지 알 수 없다. 마지막 출력 앞에는 여러 계산이 이어져 있다. 맨 앞의 가중치가 최종 손실에 미친 영향을 알려면 그 사이의 경로를 따라가야 한다. 이번 글에서는 두 층으로 구성한 작은 신경망에서 이 계산을 직접 확인한다.
1강은 숫자 하나를 갱신하며 학습과 추론을 구분했다. 2강에서는 토큰을 입력 벡터로 바꾸는 과정을 보았다. 이제 질문을 좁혀 보자. 출력에서 계산한 오차가 앞쪽 가중치의 변화 방향으로 어떻게 연결되는가?
순전파: 입력부터 손실까지 계산하기
가상의 공개 문서 변경 보고 시스템에, 검토 우선순위 점수를 만드는 작은 모형이 있다고 하자. 입력 x=0.8과 목표 점수 y=0.7은 계산을 설명하려고 정한 합성 값이다. 실제 문서에서 측정한 값도, 운영에 쓸 판정 기준도 아니다. 출력은 확률로 해석하지 않는다.
학습할 숫자는 첫 층의 가중치와 편향 w1, b1, 둘째 층의 w2, b2 네 개다. 편향은 곱셈 결과에 더하는 값이다. 계산은 아래 순서로 진행한다.
z = w1 × x + b1
h = tanh(z)
pred = w2 × h + b2
L = 0.5 × (pred − y)²
tanh는 입력을 −1과 1 사이의 값으로 바꾸는 매끄러운 비선형 함수다. 단순한 곱셈과 덧셈 사이에 이런 함수를 넣으면 여러 층의 계산을 하나의 직선 식으로는 일반적으로 나타낼 수 없게 된다. 정의는 PyTorch의 Tanh 문서에서 확인할 수 있다. 여기서는 미분식 dh/dz = 1 − h²를 사용한다.
초기값을 w1=0.5, b1=0.1, w2=−0.4, b2=0.2로 놓으면 z=0.5, h≈0.462117이다. 이어서 예측값은 약 0.015153, 손실은 약 0.234508이 된다. 입력에서 출력을 거쳐 손실까지 계산하는 이 방향이 순전파다. 손실 앞의 0.5는 미분할 때 제곱에서 나오는 2를 상쇄해 식을 단순하게 만든다.
역전파: 각 구간의 변화율을 곱하기
먼저 예측값을 조금 바꿨을 때 손실이 얼마나 달라지는지 계산한다. 위 손실식의 미분은 dL/dpred = pred − y다. 이를 e라고 쓰면 이번 값은 약 −0.684847이다. 지금 위치에서는 예측값을 조금 높이는 쪽이 손실을 줄인다.
둘째 층은 가까이에 있다. pred = w2 × h + b2이므로 w2를 바꾸는 영향에는 h가 곱해지고, b2를 바꾸는 영향에는 1이 곱해진다. 따라서 dL/dw2 = e × h, dL/db2 = e다.
첫 층의 w1에 도달하려면 두 구간을 더 지나야 한다. w1의 변화는 z를 바꾸고, z는 h를, h는 예측값을 바꾼다. 그 경로의 변화율을 곱한다.
dL/dw1 = e × w2 × (1 − h²) × x
dL/db1 = e × w2 × (1 − h²)
이것이 이 예제에서의 연쇄법칙이다. 출력의 오차를 모든 가중치에 똑같이 나누어 주는 것이 아니다. 각 가중치에서 손실까지 이어지는 계산 경로가 다르므로 미분값도 다르다. PyTorch Autograd 안내는 계산 그래프를 따라 연쇄법칙으로 기울기를 구하고, 이후 옵티마이저가 파라미터를 갱신하는 과정을 구분한다.
이번 w1의 기울기는 양수인 약 0.172351이다. 예측값이 목표보다 낮다고 해서 모든 가중치를 높여야 하는 것은 아니다. 현재 w2가 음수이므로 w1을 낮추면 h가 줄고, 음수인 w2 × h가 덜 음수가 되어 예측값이 높아진다. 이 부호를 설명할 수 있으면 기울기를 단순한 벌점과 구분한 것이다.
수치 미분으로 계산식을 교차 확인하기
손으로 만든 미분식에도 실수가 있을 수 있다. 각 파라미터를 아주 조금 늘렸을 때와 줄였을 때의 손실을 비교해 검산하자. 다른 파라미터는 그대로 두고, 검사할 값만 ε=0.000001씩 바꾼다.
수치 기울기 ≈ [L(p + ε) − L(p − ε)] / (2ε)
이 중앙차분은 미분의 근삿값이다. PyTorch Gradcheck 문서도 해석적으로 구한 기울기와 유한차분으로 구한 기울기를 비교한다. 아래 코드는 PyTorch를 설치하지 않고 같은 검산 원리를 Python 표준 라이브러리만으로 구현한다. gradient_demo.py로 저장해 python gradient_demo.py를 실행하면 된다.
import math
x, target = 0.8, 0.7
p = [0.5, 0.1, -0.4, 0.2] # w1, b1, w2, b2
def forward(p):
w1, b1, w2, b2 = p
z = w1 * x + b1
h = math.tanh(z)
pred = w2 * h + b2
loss = 0.5 * (pred - target) ** 2
return z, h, pred, loss
z, h, pred, loss = forward(p)
error = pred - target
back = error * p[2] * (1 - h * h)
grad = [back * x, back, error * h, error]
print(f'z={z:.6f}, h={h:.6f}')
print(f'prediction={pred:.6f}, loss={loss:.6f}')
eps = 1e-6
for i, name in enumerate(['w1', 'b1', 'w2', 'b2']):
plus, minus = p.copy(), p.copy()
plus[i] += eps
minus[i] -= eps
numeric = (forward(plus)[3] - forward(minus)[3]) / (2 * eps)
assert abs(grad[i] - numeric) < 1e-8
print(f'{name}: analytic={grad[i]:.6f}, numeric={numeric:.6f}')
for lr in [0.2, 3.0]:
updated = [v - lr * g for v, g in zip(p, grad)]
print(f'lr={lr}: loss={forward(updated)[3]:.6f}')
실행 결과는 다음과 같다. 네 파라미터 모두 두 방식의 차이가 코드에 정한 허용 오차 1e-8보다 작아 검사를 통과했다.
z=0.500000, h=0.462117
prediction=0.015153, loss=0.234508
w1: analytic=0.172351, numeric=0.172351
b1: analytic=0.215439, numeric=0.215439
w2: analytic=-0.316479, numeric=-0.316479
b2: analytic=-0.684847, numeric=-0.684847
lr=0.2: loss=0.124659
lr=3.0: loss=0.813393
이 검산이 보장하는 범위는 선택한 입력과 파라미터 지점에서 두 계산이 일치한다는 것까지다. 모든 가능한 값에서 구현이 옳다는 증명은 아니다. ε를 무조건 작게 잡으면 더 정확해지는 것도 아니다. 컴퓨터의 반올림 오차와 차분 근사의 오차를 함께 고려해야 한다.
기울기를 구하는 일과 가중치를 바꾸는 일
코드의 grad를 계산한 시점에는 원래 파라미터 p가 바뀌지 않았다. 이후 새 값 = 현재 값 − 학습률 × 기울기를 적용할 때 갱신이 일어난다. 이번 코드에서는 두 학습률 모두 동일한 초기값에서 출발하며, 첫 갱신 결과에 두 번째 갱신을 이어 붙이지 않는다.
학습률 0.2에서는 손실이 0.234508 → 0.124659로 줄었다. 그러나 3.0에서는 0.813393으로 커졌다. 기울기는 현재 위치 근처의 변화율이다. 그 방향으로 너무 멀리 이동해도 손실이 줄어든다는 보장은 없다. 이는 여기서 실제 계산한 반례이며, 모든 모형에서 0.2가 좋은 학습률이라는 뜻은 아니다.
활성화 함수도 전달되는 크기에 영향을 준다. 이 식에서 h가 −1이나 1에 가까워지면 1−h²가 작아져 첫 층으로 이어지는 이 경로의 기울기가 작아질 수 있다. 다만 실제 전체 기울기는 다른 곱셈 항에도 좌우된다. 이번 예제는 신경망 전체의 학습 안정성을 평가한 실험이 아니다.
문서 보고 시스템에 적용할 때 남는 질문
검토 점수의 손실이 줄었다고 변경 보고서가 정확해졌다고 말할 수는 없다. 날짜를 잘못 추출했거나 목표 점수 자체가 부적절하면, 학습은 잘못 정한 목표에 더 가까워질 수도 있다. 실제 설계에서는 원문 확인과 목표 정의, 학습에 쓰지 않은 문서로 하는 평가가 따로 필요하다. 이번 실습이 입증한 것은 작은 수학 모형의 기울기와 한 번의 갱신 결과다.
확인 질문. 네 기울기를 정확히 계산했지만 갱신 코드를 실행하지 않았다면, 같은 입력에서 예측값이 달라질까? 답. 이처럼 무작위 요소가 없는 모형에서는 달라지지 않는다. 다음에는 w2의 부호만 바꾸어 w1의 기울기를 다시 계산해 보자. 무엇이 바뀌는지 식의 각 항으로 설명하는 것이 목표다.
다음 4강 「Attention은 무엇을 계산하는가」에서는 토큰 표현들이 서로의 정보를 어떻게 참조하는지 작은 Q·K·V 계산으로 살펴본다.