학습 루프의 심장은 세 줄입니다.
loss.backward() # 파라미터마다 grad 를 채운다
for p in model.parameters():
p -= lr * p.grad # 그래디언트의 반대 방향으로 조금 간다
p.grad는 p와 똑같은 모양의 텐서입니다. 파라미터가 70억 개면 그래디언트도 70억 개이고, 갱신은 그 둘을 원소별로 빼는 것이 전부입니다.
여기서 물음이 둘 생깁니다. 수십억 개의 미분이 어떻게 텐서 하나로 정리되는가, 그리고 왜 하필 그 방향의 반대인가. 두 번째 물음의 답이 특히 그냥 넘어가기 쉽습니다 — 「기울기의 반대로 내려가니까」는 설명이 아니라 같은 말의 반복입니다. 이 글은 두 물음에 답합니다.
지난 글까지 무엇을 최소화하는지를 봤으니, 이제 어떻게 최소화하는지로 넘어갑니다.
민감도로 읽는 도함수
극한이 아니라 근사로 읽기
초급의 미분 글에서 도함수를 평균변화율의 극한으로 정의했습니다. 계산할 때는 그 정의가 맞지만, 딥러닝에서 실제로 쓰는 것은 같은 값의 다른 읽기입니다.
극한이 존재한다는 것은 가 작을 때 다음이 성립한다는 뜻입니다.
오른쪽은 에 대한 일차식입니다. 즉 도함수는 「 근처에서 를 대신할 수 있는 가장 좋은 직선의 기울기」입니다. 여기서 「가장 좋은」이 무슨 뜻인지가 요점입니다.
정의. 로 적었을 때 오차가 보다 빨리 0으로 가는, 즉 인 이 존재하면 는 에서 미분 가능하고 다.
h보다 빨리 주는 오차
「빨리 0으로 간다」를 수로 확인합니다. , 이면 입니다.
| 실제 변화 | 선형근사 | 오차 | |
|---|---|---|---|
| 0.1 | 0.61 | 0.6 | 0.01 |
| 0.01 | 0.0601 | 0.06 | 0.0001 |
| 0.001 | 0.006001 | 0.006 | 0.000001 |
를 10분의 1로 줄이면 오차는 100분의 1이 됩니다. 오차가 짜리라 에 비하면 없는 것이나 마찬가지가 되고, 그래서 아주 작은 걸음에서는 직선이 곡선을 완전히 대신합니다.
이 읽기가 주는 말이 민감도입니다. 은 「입력을 만큼 흔들면 출력이 대략 만큼 흔들린다」는 뜻입니다. 손실함수라면 「이 파라미터를 조금 키우면 손실이 그 6배만큼 는다」가 됩니다.
미분이 안 되는 자리
정의를 「선형근사」로 읽어 두면 미분이 안 되는 자리도 곧바로 읽힙니다. 오차가 보다 빨리 0으로 가는 일차식이 하나로 정해지지 않는 자리입니다.
의 원점이 그렇습니다. 오른쪽에서 다가가면 기울기가 1이고 왼쪽에서 다가가면 이라, 양쪽을 함께 대신할 직선이 없습니다. 딥러닝에서 매일 쓰는 ReLU 도 같은 자리에 같은 문제를 갖습니다 — 왼쪽 기울기 0, 오른쪽 기울기 1입니다.
이런 점에서는 접선 하나 대신 그래프 아래를 지나는 직선들의 기울기 전체를 씁니다. 그 집합을 서브그래디언트(subgradient)라고 합니다. 의 원점에서는 이고 ReLU의 원점에서는 입니다. 값 하나가 아니라 구간이라는 점만 다르고, 「그 방향으로 가면 값이 적어도 이만큼은 는다」는 쓰임새는 같습니다.
실무에서는 프레임워크가 그 구간에서 하나를 골라 넣습니다. PyTorch는 ReLU의 0에서 도함수를 0으로 둡니다. 입력이 정확히 0이 되는 일이 거의 없어서 대개 문제가 되지 않고, 되더라도 서브그래디언트 중 하나를 쓰는 것은 이론상 허용되는 선택입니다.
그렇다고 없는 문제는 아닙니다. 꺾인 점이 정답인 경우가 있습니다 — L1 정규화는 가중치를 정확히 0으로 몰아붙이는 것이 목적이라, 최적해가 바로 그 미분 불가능한 자리에 놓입니다. 이럴 때는 그래디언트를 대충 하나 골라 쓰는 대신 근접 연산자 같은 다른 도구를 씁니다. 꺾인 점이 지나가는 자리인지 도착하는 자리인지가 갈림길입니다.
손잡이가 여럿일 때 — 그래디언트
편도함수를 벡터로 묶기
초급 45번의 등고선 글에서 편미분을 세웠습니다. 나머지 변수를 상수로 고정하고 하나만 흔들어 미분하는 것이었고, 그 값이 였습니다. 여기서는 그것을 그대로 받아 쓰고, 여러 개를 한꺼번에 흔들면 어떻게 되는가부터 시작합니다.
변수가 둘일 때 를 , 를 만큼 함께 흔들면 변화가 더해집니다.
오른쪽 두 항의 모양이 내적입니다. 내적 글의 그대로입니다. 그러니 편도함수들을 벡터로 묶어 두면 식이 한 덩어리로 접힙니다.
정의. 의 그래디언트(gradient)는 편도함수를 성분으로 갖는 벡터 이고, 이때 선형근사는 로 적힌다.
를 점 에서 봅니다. 편도함수는 , 이므로
입니다. 만큼 움직여 봅니다.
실제 값은 이니 오차가 0.005입니다. 한 변수일 때와 같은 이야기이고, 달라진 것은 곱셈이 내적이 된 것뿐입니다.
방향도함수 — 어느 쪽으로 얼마나
이제 「어느 방향으로 가면 얼마나 변하는가」를 물을 수 있습니다. 길이가 1인 벡터 를 하나 잡고 그 방향으로 만큼 갑니다.
정의. 단위벡터 방향의 방향도함수는 다.
방향마다 미분을 새로 하지 않아도 됩니다. 그래디언트 하나를 구해 두면 어느 방향이든 내적 한 번으로 답이 나옵니다. 편도함수는 그중 축 방향인 특별한 경우입니다 — 이면 , 이면 3입니다. 대각선 방향 로 가면
입니다. 축 방향 둘 중 어느 쪽보다도 가파릅니다.
−∇f가 가장 가파른 내리막인 이유
1을 넘지 못하는 코사인
그러면 가장 가파른 방향은 어디인가. 방향도함수를 내적의 기하적 표현으로 바꿔 적습니다. 는 단위벡터이므로 이고,
입니다. 여기서 는 와 가 이루는 각입니다. 오른쪽에서 우리가 고를 수 있는 것은 하나뿐이고, 그 값은 코시-슈바르츠 부등식이 보장하는 대로 과 1 사이입니다.
- , 즉 가 와 같은 방향일 때 가 최대이고 그 값은 다.
- , 즉 가 반대 방향일 때 최소이고 그 값은 다.
- , 즉 수직일 때 변화율이 0이다.
우리 예에서 이므로, 어떤 방향으로 가도 변화율은 와 사이입니다. 그리고 가장 빨리 내려가는 길은 정확히 방향 하나입니다.
한 줄의 근거가 이것입니다. 「기울기의 반대」가 아니라 「그 점에서 손실을 가장 빨리 줄이는 유일한 방향」이고, 증명은 코사인이 1을 넘지 못한다는 사실 한 줄입니다.
단서를 둘 달아 둡니다. 첫째, «가장 가파른»은 그 점 근처에서만 참입니다. 선형근사가 성립하는 범위를 벗어나면 보장이 사라지고, 그래서 학습률이 필요합니다. 둘째, 이 결론은 길이를 유클리드 노름으로 쟀기 때문에 나왔습니다. 「길이 1인 걸음」의 뜻을 다르게 정하면 최급강하 방향도 달라지는데, 좌표마다 눈금을 달리 잡는 옵티마이저들이 하는 일이 바로 그것입니다.
등고선에 수직인 그래디언트
위의 세 번째 줄이 그림 하나를 곧바로 내놓습니다. 등고선은 값이 같은 점들을 이은 선이므로, 그 위를 걸으면 값이 변하지 않습니다. 즉 등고선을 따라가는 방향 에 대해 이고, 따라서
입니다. 내적이 0이면 수직이니, 그래디언트는 언제나 등고선에 수직입니다.
로 확인해 봅시다. 등고선은 타원이고 점 에서 입니다. 그 점에서 타원의 접선 방향은 인데
이라 정말 수직입니다. 손실 지형을 등고선으로 그린 그림에서 갱신 화살표가 언제나 선을 가로질러 그려지는 이유가 이것입니다.
그래디언트의 크기가 뜻하는 것
여기까지 방향만 이야기했는데, 는 벡터라 길이도 있습니다. 앞의 식이 그 길이의 뜻을 이미 말해 줍니다 — 의 최댓값이 이므로, 길이는 가장 가파른 방향에서의 변화율입니다. 우리 예에서 8.544라는 것은 「가장 좋은 방향으로 걸음 1을 옮기면 가 8.544쯤 는다」는 뜻입니다.
이 길이가 갱신식에서 학습률과 곱해집니다. 걸음의 실제 길이가
이므로 학습률이 걸음을 다 정하는 것이 아닙니다. 같은 라도 그래디언트가 크면 크게 움직이고 작으면 거의 안 움직입니다. 그래서 손실을 배치 합으로 재느냐 평균으로 재느냐만 바꿔도 — 그래디언트가 배치 크기 배만큼 달라지므로 — 학습률을 같이 손봐야 합니다.
여기서 두 가지 실무 장치가 나옵니다.
| 장치 | 크기에 하는 일 |
|---|---|
| 그래디언트 클리핑 | 이 상한을 넘으면 그 길이만 줄인다. 방향은 그대로 두고 걸음만 자른다 |
| Adam 계열 | 좌표마다 최근 크기로 나눠, 크기 정보를 거의 버리고 방향 위주로 걷는다 |
둘 다 길이가 폭주하는 자리에서 걸음이 함께 폭주하는 것을 막는 장치입니다. 반대로 길이가 0에 가까워지면 걸음이 멈추는데, 그 자리가 극값일 수도 있고 평평한 고원일 수도 있습니다 — 길이만 봐서는 구별되지 않고, 그 구별에는 2차 정보가 필요합니다.
수십억 개를 벡터 하나로
θ와 같은 모양의 ∇L
이제 처음의 물음으로 돌아갑니다. 모델의 파라미터를 전부 한 줄로 늘어놓으면 그것은 벡터 하나입니다. 가 70억이어도 벡터는 벡터입니다. 손실은 그 벡터를 받아 수 하나를 내놓는 함수 이고, 그래디언트는
로 와 같은 모양의 벡터입니다. p.grad가 p와 같은 shape인 것이 우연이 아니라 정의 그대로인 셈입니다. 그리고 갱신 한 줄은
입니다. 성분마다 「이 손잡이를 키우면 손실이 얼마나 느는가」를 재어 두고, 그 반대로 배만큼 움직이는 것 — 그것이 전부입니다. 차원이 70억이라는 사실은 이 식 어디에도 나타나지 않습니다.
코드로 확인하기
import numpy as np
f = lambda v: v[0] ** 2 + 3 * v[0] * v[1]
a = np.array([1.0, 2.0])
g = np.array([2 * a[0] + 3 * a[1], 3 * a[0]]) # 손으로 구한 그래디언트
print(g) # [8. 3.]
# ① 유한차분으로 검산 — 정의를 그대로 실행한다
eps = 1e-6
num = np.array([(f(a + eps * e) - f(a - eps * e)) / (2 * eps) for e in np.eye(2)])
print(num.round(6)) # [8. 3.]
# ② 선형근사의 오차는 h보다 빨리 준다
for h in [0.1, 0.01, 0.001]:
d = np.array([h, -h / 2])
print(h, round(float(f(a + d) - f(a) - g @ d), 9))
# 0.1 -0.005 / 0.01 -5e-05 / 0.001 -5e-07
# ③ 어느 방향으로 가도 ‖∇f‖ 를 넘지 못한다
u = g / np.linalg.norm(g)
print(round(float(g @ u), 4), round(float(g @ -u), 4)) # 8.544 -8.544
best = max(float(g @ np.array([np.cos(t), np.sin(t)]))
for t in np.linspace(0, 2 * np.pi, 100000))
print(round(best, 4)) # 8.544 꼭 같다
# ④ 등고선 방향의 방향도함수는 0
gc = np.array([4.0, 4.0]) # x² + 2y² 의 (2,1)에서
print(float(gc @ np.array([1.0, -1.0]))) # 0.0
③이 이 글의 증명을 무식하게 확인한 것입니다. 방향 10만 개를 훑어도 를 넘는 방향은 없습니다.
연습 문제
답은 문항을 눌러 펼칩니다.
연습 1 — 그래디언트 손계산
의 을 구하세요.
, 이므로 입니다.의 을 구하세요.
, 이므로 입니다. 두 성분의 부호가 다른 것은 한 손잡이는 키우면 값이 늘고 다른 손잡이는 줄인다는 뜻입니다.의 을 구하세요.
이므로 입니다. 성분이 셋이어도 하는 일은 같습니다 — 나머지를 고정하고 하나만 흔듭니다.
연습 2 — 방향도함수
1번에서 얻은 에 대해 방향의 방향도함수를 구하세요.
인지부터 봅니다 — 로 단위벡터가 맞습니다. 입니다.같은 점에서 방향도함수가 0이 되는 단위벡터를 모두 구하세요.
이어야 하므로 는 와 평행합니다. 길이를 1로 맞추면 두 개입니다. 이 두 방향이 그 점을 지나는 등고선의 접선 방향입니다.
연습 3 — 등고선과 방향 고르기
의 점 에서 를 구하고, 그 점을 지나는 등고선의 접선 방향을 하나 적으세요.
입니다. 접선 방향은 이것과 수직이어야 하므로 예컨대 이고, 으로 확인됩니다.같은 점에서 방향으로 걸으면 의 변화율이 얼마이고, 그보다 더 빨리 줄이는 방향이 없는 이유는 무엇인가요.
이므로 변화율은 입니다. 어떤 단위벡터를 잡아도 이고 이라 이보다 작아질 수 없습니다.
연습 4 — 코시-슈바르츠로 보이기
인 모든 에 대해 임을 보이고, 등호가 성립하는 를 적으세요.
코시-슈바르츠 부등식 에 , 를 넣고 을 쓰면 곧바로 입니다. 등호는 두 벡터가 평행할 때, 즉 일 때 성립하고 쪽이 최대, 쪽이 최소입니다. 이면 모든 방향에서 변화율이 0이라 고를 방향이 없습니다.
정리
- 도함수는 극한이자 «최선의 선형근사»의 기울기다. 오차가 보다 빨리 0으로 가는 유일한 일차식이 접선이다.
- 그래서 도함수는 민감도로 읽힌다 — 입력을 만큼 흔들면 출력이 만큼 흔들린다.
- 그래디언트는 편도함수를 모은 벡터이고, 다변수 선형근사는 라는 내적 하나로 접힌다.
- 방향도함수 — 그래디언트 하나만 있으면 어느 방향이든 내적 한 번으로 답이 나온다. 편도함수는 축 방향인 특수한 경우다.
- 이므로 최대는 방향, 최소는 방향이다. 코시-슈바르츠가 그 범위를 보장한다.
- 「가장 가파르다」는 그 점 근처에서, 유클리드 노름으로 길이를 쟀을 때 참이다. 둘 다 조건이지 정리의 일부가 아니다.
- 등고선 방향의 변화율은 0이므로 그래디언트는 등고선에 수직이다.
- 꺾인 점에서는 접선이 하나로 안 정해진다. 그래프 아래를 지나는 직선들의 기울기 전체가 서브그래디언트이고, ReLU의 0에서는 이다. 지나가는 자리면 하나 골라 쓰면 되지만 최적해가 그 자리에 놓이면(L1 정규화) 다른 도구가 필요하다.
- 그래디언트의 길이는 가장 가파른 방향의 변화율이고, 걸음의 실제 길이는 이다. 클리핑은 그 길이만 자르고 방향은 남기며, Adam 계열은 길이 정보를 거의 버린다.
- 파라미터가 수십억이어도 와 는 같은 모양의 벡터 하나다. 차원은 식 어디에도 나타나지 않는다.
p -= lr * p.grad로 돌아갑니다. 저 한 줄은 이제 「기울기의 반대로 간다」가 아니라 「그 점에서 손실을 가장 빨리 줄이는 유일한 방향으로, 선형근사를 믿을 만한 만큼만 간다」로 읽힙니다. 두 개의 단서가 붙은 문장이고, 학습률과 옵티마이저를 둘러싼 논의는 대부분 그 단서 둘을 다루는 이야기입니다.
남은 물음은 「그 수십억 개의 편도함수를 실제로 어떻게 구하는가」입니다. 층을 지날 때마다 미분이 어떻게 곱해지는지 — 다음 글이 그 법칙 하나를 세웁니다.
읽어주셔서 감사합니다. 😊

