본문 바로가기
TYLER SONGBlog
블로그 목록
LLM Research

미적분: 학습이 "미분"인 이유 — gradient는 손실을 줄이는 방향이다

딥러닝 모델이 학습한다는 것은 결국 손실 함수의 기울기를 따라 파라미터를 조금씩 움직이는 과정이다. 오늘은 미분의 정의부터 시작해서 편미분, chain rule, gradient까지 순서대로 쌓아 올리고, 이를 NumPy로 직접 수치 계산해서 손으로 푼 결과와 맞춰본다. 앞으로 나올 역전파(backpropagation)의 수학적 뼈대가 오늘 전부 나온다.

송민성7분 읽기

오늘의 목표

딥러닝에서 "학습한다"는 말이 실제로는 "손실 함수를 미분해서 그 기울기 반대 방향으로 파라미터를 움직인다"는 뜻이라는 걸 몸으로 이해하는 게 오늘의 목표다. 미분을 대학 시험 공식으로 외우는 게 아니라, "지금 여기서 조금 움직이면 함수 값이 어느 방향으로 얼마나 변하는가"를 계산하는 도구로 받아들이는 게 핵심이다.

개념과 직관

미분(derivative)이란 무엇인가

함수 f(x)가 있을 때, x를 아주 조금 바꾸면 f(x)가 얼마나 바뀌는지를 나타내는 게 미분이다. 산길을 걷는다고 생각해보자. 지금 서 있는 지점에서 발밑 경사가 얼마나 가파른지, 오르막인지 내리막인지를 알려주는 게 미분값이다. 경사가 가파르면 미분값이 크고, 평평하면 0에 가깝다.

수식으로는 이렇게 정의한다 (텍스트로):

text
f'(x) = lim(h→0) [f(x+h) - f(x)] / h

말로 풀면: x에서 아주 작은 만큼(h) 옆으로 이동했을 때 함수값이 변한 양을, 이동한 거리로 나눈 것. h를 진짜 0으로 만들 순 없으니 "한없이 0에 가깝게" 보낸 극한(limit)을 취한다.

자주 쓰는 미분 공식 몇 개

  • 다항함수: f(x) = x^n 이면 f'(x) = n·x^(n-1). 예: x^2의 미분은 2x
  • 지수함수: f(x) = e^x 이면 f'(x) = e^x (자기 자신)
  • 로그함수: f(x) = ln(x) 이면 f'(x) = 1/x

이 세 개만 알아도 딥러닝에서 나오는 대부분의 기본 함수(활성화 함수, 손실 함수)를 미분할 수 있다.

편미분(partial derivative) — 변수가 여러 개일 때

딥러닝 모델은 파라미터가 수백만~수십억 개다. 함수 f(x, y)처럼 입력이 여러 개일 때, "다른 변수는 다 고정하고 딱 하나만 살짝 움직이면 어떻게 변하는가"를 보는 게 편미분이다. 표기는 보통 "∂f/∂x" 라고 쓰고 "f를 x로 편미분"이라고 읽는다.

비유하면, 산 위에서 동서 방향으로만 한 발 움직였을 때 고도 변화(∂f/∂x), 남북 방향으로만 한 발 움직였을 때 고도 변화(∂f/∂y)를 따로 재는 것이다.

gradient(그래디언트) — 편미분을 다 모은 벡터

f(x, y)의 모든 편미분을 벡터로 묶은 것이 gradient다.

text
grad f = (∂f/∂x, ∂f/∂y)

이 벡터가 가리키는 방향이 "함수값이 가장 빠르게 증가하는 방향"이다. 그래서 손실을 줄이고 싶으면 gradient의 반대 방향으로 움직인다 — 이게 바로 경사하강(gradient descent, 손실을 줄이는 방향으로 조금씩 파라미터를 옮기는 것)의 원리다.

chain rule (연쇄법칙) — 합성함수의 미분

f(g(x))처럼 함수 안에 함수가 들어있는 합성함수를 미분할 때 쓰는 규칙이다.

text
d/dx f(g(x)) = f'(g(x)) · g'(x)

말로 풀면: 바깥 함수를 안쪽 함수 자리에서 미분한 값과, 안쪽 함수 자체의 미분값을 곱한다. 비유하면 기어(gear) 두 개가 맞물려 돌아가는 것과 같다 — 안쪽 기어가 1도 돌 때 바깥 기어가 몇 도 도는지, 그리고 그 바깥 기어가 다시 최종 결과를 얼마나 바꾸는지를 곱해서 전체 변화율을 구하는 것이다.

딥러닝 모델은 레이어(layer)를 여러 겹 쌓은 거대한 합성함수다. 그래서 chain rule이 없으면 학습 자체가 불가능하다. 나중에 배울 역전파는 chain rule을 층층이 적용해서 각 파라미터의 gradient를 구하는 알고리즘일 뿐이다.

코드로 직접 해보기

1단계: 유한차분(finite difference)으로 미분 흉내내기

극한을 컴퓨터에서 진짜로 계산할 순 없으니, h를 아주 작은 값(예: 0.0001)으로 두고 근사한다. 이걸 유한차분법이라고 한다.

python
import numpy as np def numerical_derivative(f, x, h=1e-5): return (f(x + h) - f(x - h)) / (2 * h) # 중심차분: 더 정확함 # f(x) = x^2 이면 f'(x) = 2x 여야 한다 f = lambda x: x ** 2 x = 3.0 print("수치 미분:", numerical_derivative(f, x)) print("해석 미분(2x):", 2 * x)

(f(x+h) - f(x-h)) / (2*h)처럼 양쪽에서 h만큼씩 재는 걸 중심차분(central difference)이라 하는데, 한쪽으로만 재는 것보다 오차가 작아서 실무에서 더 자주 쓴다.

2단계: f(x, y)의 gradient를 수치적으로 계산

python
def numerical_gradient(f, point, h=1e-5): x, y = point df_dx = (f(x + h, y) - f(x - h, y)) / (2 * h) df_dy = (f(x, y + h) - f(x, y - h)) / (2 * h) return np.array([df_dx, df_dy]) # f(x, y) = x^2 + 3*x*y + y^2 f = lambda x, y: x**2 + 3*x*y + y**2 point = (2.0, 1.0) print("수치 gradient:", numerical_gradient(f, point))

3단계: 해석적(analytic)으로 직접 미분해서 비교

f(x, y) = x² + 3xy + y²를 손으로 편미분하면:

  • ∂f/∂x = 2x + 3y
  • ∂f/∂y = 3x + 2y
python
def analytic_gradient(point): x, y = point df_dx = 2*x + 3*y df_dy = 3*x + 2*y return np.array([df_dx, df_dy]) print("해석 gradient:", analytic_gradient(point))

두 결과가 소수점 몇 자리까지 거의 일치하면 성공이다. 완전히 똑같지 않고 아주 작은 오차(예: 1e-6 수준)가 나는 건 정상이다 — h가 0이 아니라 아주 작은 양수이기 때문에 생기는 근사 오차다.

오늘의 실험

  1. g(x) = x² + 1, f(u) = u³ 로 두고 f(g(x))를 chain rule로 손으로 미분해본다.

- g'(x) = 2x - f'(u) = 3u² - 결과: d/dx f(g(x)) = 3(x²+1)² · 2x

  1. 이 결과를 코드로 검증한다.
python
def composite(x): g = x**2 + 1 return g**3 x = 2.0 numeric = numerical_derivative(composite, x) analytic = 3 * (x**2 + 1)**2 * 2 * x print("수치:", numeric, "/ 해석:", analytic)
  • x 값을 여러 개 (예: -2, 0, 0.5, 5) 바꿔가며 두 값이 계속 일치하는지 확인한다.
  • h 값을 1e-2, 1e-5, 1e-8로 바꿔가며 오차가 어떻게 변하는지 기록한다. (너무 작은 h는 부동소수점 반올림 오차 때문에 오히려 부정확해질 수 있다는 것도 관찰 포인트다.)
  • 기록 형식 예시: x값 | h값 | 수치미분 | 해석미분 | 차이를 표로 남기면 나중에 역전파 디버깅할 때 감이 잡힌다.

왜 이걸 배우나 (LLM / Post-training 연결)

지금 손으로 계산한 gradient와 chain rule은 PyTorch에서 loss.backward() 한 줄이 내부적으로 하는 일 그 자체다. LLM을 post-training(SFT, RLHF/DPO 등)할 때도 결국 "손실 함수를 모델 파라미터로 미분해서 그 방향으로 파라미터를 업데이트한다"는 원리는 동일하다. 모델이 수십억 파라미터를 가져도, 수학적으로는 오늘 배운 편미분과 chain rule이 층층이 쌓여있을 뿐이다. 이걸 이해하고 있어야 나중에 학습이 발산하거나 gradient가 사라지는(vanishing gradient) 문제를 겪었을 때 "어디서 왜" 문제가 생겼는지 감을 잡을 수 있다.

초보자가 막히는 지점

  • h를 헷갈려서 부호 실수: (f(x+h) - f(x-h)) / (2*h)에서 분모의 2h를 빼먹고 h로만 나누는 실수가 흔하다. 코드 짤 때 꼭 출력해서 눈으로 확인하자.
  • 편미분을 "다른 변수를 0으로 만드는 것"으로 오해: 편미분은 다른 변수를 0으로 만드는 게 아니라 "상수처럼 고정"하는 것이다. f(x,y)=3xy를 x로 편미분하면 3y가 되지, 0이 되지 않는다.
  • chain rule에서 곱하는 순서/대상 혼동: f'(g(x))는 "f를 미분한 함수에 g(x)를 대입한 것"이지 "f(x)를 미분한 것에 g(x)를 대입"이 아니다. 이 둘을 헷갈리면 답이 완전히 달라진다.
  • 극한(limit) 개념을 어렵게 생각함: "h가 0에 무한히 가까워질 때"라는 말이 추상적으로 느껴질 수 있는데, 코드에서 h=0.00001 정도로 작은 숫자를 그냥 대입해보는 것으로 충분히 감을 잡을 수 있다. 완벽한 수학적 엄밀함보다 "작은 값으로 근사가 잘 맞아떨어진다"는 감각이 더 중요하다.

알고리즘 (병행 1시간)

  • LeetCode Easy 1문제 + Medium 1문제, 수학(Math) 태그로 찾아서 풀기.
  • 풀이 후 기록할 것:

- 접근: 문제를 어떻게 분해했는지, 어떤 아이디어로 접근했는지 (예: 완전탐색 → 규칙 발견 → 수식화) - 시간복잡도: Big-O로 표기하고, 왜 그렇게 나오는지 한 줄 근거 - 틀린 이유: 처음에 틀렸다면 어디서 왜 틀렸는지 (경계 조건? 정수 오버플로? 인덱스 실수?) 반드시 남기기 — 같은 실수를 반복하지 않기 위한 기록이다.

© 2026 Tyler Song