Stage 03 — 미분 가능한 연산의 경계: exp, log, ReLU, 거듭제곱
거듭제곱,exp,log,ReLU의 국소 미분을 추가하고, 정의역,비미분점,유한성 검사를 통제 실험으로 검증한다.
덧셈과 곱셈만으로도 연쇄 법칙은 만들 수 있지만 GPT의 loss와 layer를 표현하려면 거듭제곱, exp, log, 활성화 함수가 필요하다. 연산을 추가할 때는 미분 공식만 구현해서는 부족하다. 어떤 입력을 허용하고, 비미분점에서 어떤 규칙을 선택하며, 유한하지 않은 값이 어디까지 퍼지기 전에 멈출지도 함께 설계해야 한다.
테스트: Stage 03 테스트
이번 단계가 답할 질문
GPT 계산에 필요한 스칼라 연산을 추가하면서, 각 연산의 수식뿐 아니라 정의역과 실패 경계까지 코드에서 읽을 수 있게 만들 수 있는가?
이번 Stage는 상수 지수의 거듭제곱, 자연지수 함수 exp, 자연로그 log, ReLU를 구현한다. tensor, softmax, 신경망 layer, optimizer는 아직 넣지 않는다. Value 지수는 지수 방향 gradient와 추가 정의역이 필요하므로 상수 지수의 책임과 분리한다.
네 연산의 국소 미분
y = x^n -> dy/dx = n*x^(n-1)
y = exp(x) -> dy/dx = exp(x)
y = log(x) -> dy/dx = 1/x
y = ReLU(x) -> dy/dx = 1 (x>0), 0 (x<=0, 이번 Stage의 규칙)
ReLU는 x=0에서 미분 가능하지 않다. 계산을 계속하려면 한 가지 규칙을 선택해야 하므로 이번 구현은 미분값을 0으로 명시한다. 선택을 코드와 출력에 드러내어 암묵적인 가정이 되지 않게 했다.
실행할 합성 함수
z = log(exp(x)) + ReLU(x)^2
x = 2
양수 구간에서 수학적으로 log(exp(x)) = x이므로 z=x+x^2, dz/dx=1+2x다. 따라서 x=2일 때 z=6, dz/dx=5가 되어야 한다.
실제 그래프에서는 식을 미리 단순화하지 않는다. exp, log, ReLU, 거듭제곱 노드를 모두 보존해 각 국소 미분값이 연쇄 법칙으로 합성되는 과정을 관찰한다.
수식으로 연쇄 법칙을 전개하면 다음과 같다.
에서는 이고 이므로 다음 값을 얻는다.
계산 그래프에서도 같은 값이 두 경로로 나뉜다. exp → log 경로의 기여도는 , ReLU → square 경로의 기여도는 다. 공유 노드 에서 두 값이 합쳐져 최종 gradient 가 된다.
중앙 차분은 어디에서 나오는가
미분의 정의는 를 0으로 보내는 극한이다.
컴퓨터는 을 대입할 수 없으므로 충분히 작은 유한한 로 극한을 근사한다. 전진 차분은 다음과 같다.
중앙 차분은 기준점 양쪽을 대칭으로 평가한다.
중앙 차분이 더 정확한 이유는 Taylor 전개에서 확인할 수 있다.
두 식을 빼면 와 처럼 부호가 같은 짝수 차수 항이 상쇄된다.
따라서 중앙 차분의 주된 절단 오차는 다. 전진 차분의 오차는 이므로 같은 에서는 중앙 차분이 일반적으로 더 정확하다.
예를 들어 라면 다음처럼 오차 항을 직접 볼 수 있다.
h는 작을수록 항상 좋은가
그렇지 않다. 를 줄이면 Taylor 절단 오차는 감소하지만, 거의 같은 두 부동소수점 수 와 를 빼면서 유효 숫자가 사라지는 상쇄 오차는 커진다.
첫 항은 가 작아질수록 감소하고 두 번째 항은 증가한다. double precision에서 값의 크기가 1에 가까운 매끄러운 함수라면 전후가 흔한 출발점이지만 모든 함수에 통하는 고정 정답은 아니다.
실전에서는 입력 스케일에 따라 다음처럼 perturbation을 정하고 여러 값을 비교하는 편이 안전하다.
처음에는 를 줄일수록 결과가 가까워지다가 어느 지점부터 다시 나빠진다면, 절단 오차보다 floating-point 반올림 오차가 지배적인 구간으로 넘어간 것이다.
Stage 03에서 실제로 계산된 값
central_difference()는 , 에서 자동미분을 사용하지 않는scalar_function()을 두 번 평가한다.
이를 으로 나눈다.
자동미분 결과 와 비교한 오차는 다음과 같다.
코드의 허용 오차 보다 훨씬 작으므로 검증을 통과한다. 터미널의 5.00000000은 수치 gradient를 소수점 여덟 자리로 반올림한 결과다.
여기에는 중요한 특수성이 있다. 근방에서는 이므로 이다. 중앙 차분은 정확한 산술에서 이차 다항식의 미분값을 정확히 계산한다. 따라서 이번 측정의 Taylor 절단 오차는 0이며, 약 의 차이는 주로 exp, log, 뺄셈의 floating-point 반올림에서 왔다.
gradient check는 어떻게 사용해야 하는가
- 계산 그래프와 backward()로 를 구한다.
- 자동미분 구현을 사용하지 않는 독립 함수로 같은 수식을 다시 작성한다.
- 한 입력만 , 로 바꾸고 두 순전파 값을 구한다.
- 중앙 차분으로 를 계산한다.
- 절대 오차와 상대 오차가 허용 범위 안인지 확인한다. 수치 미분 함수가 Value나 같은 국소 미분 코드를 재사용하면 두 구현이 같은 버그를 공유할 수 있다. 이번 **scalar_function()**이 Python math와 일반 실수만 사용하는 이유다.
여러 입력을 받는 함수에서는 한 번에 한 입력만 바꾸고 나머지는 고정한다. 개 parameter 전체를 검사하면 각 parameter마다 두 번의 순전파가 필요하므로 대략 회의 함수 평가가 든다. 중앙 차분은 학습 알고리즘이 아니라 작은 그래프나 일부 parameter를 검사하는 느린 진단 도구다.
중앙 차분을 그대로 믿으면 안 되는 경우
- log(0)처럼 perturbation이 정의역을 벗어나는 지점
- ReLU의 처럼 미분값이 존재하지 않는 지점
- exp overflow 또는 underflow가 발생하는 입력
- gradient가 0에 가까워 상대 오차만으로 판단하기 어려운 경우
- 값의 스케일에 비해 가 지나치게 크거나 작은 경우 특히 ReLU 자체를 에서 중앙 차분하면 다음 값이 나온다.
하지만 ReLU는 그 지점에서 미분 가능하지 않고 이번 구현은 subgradient를 0으로 선택했다. 수치 결과 과 구현값 0의 불일치는 자동미분 버그의 증거가 아니다. 좌미분과 우미분을 따로 확인하거나 비미분점에서 떨어진 입력으로 gradient check를 수행해야 한다.
중앙 차분은 미분의 정답을 만드는 도구가 아니라, 미분 가능한 구간에서 서로 독립적인 두 구현을 비교해 잘못된 가설을 파괴하는 통제 실험이다.
직접 실행한 결과
Windows PowerShell에서 다음 명령을 실행했다.
python .\stages\stage_03_scalar_primitives.py
python -m unittest tests.test_stage_03_scalar_primitives -v관찰 결과는 다음과 같다.
exp(2) = 7.38905610log(exp(2)) = 2ReLU(2)^2 = 4- 순전파 결과
z = 6 - 자동미분 결과
dz/dx = 5 - 중앙 차분 결과
5.00000000 - Stage 03 테스트 10개 통과
- 전체 테스트 32개 통과
- sanity check:
PASS
Stage 03 순전파, 역전파, 중앙 차분

통제 실험: ReLU의 음수 경로
입력만 x=-2로 바꾸고 그래프 구조는 유지했다.
x = -2
ReLU(x) = 0
z = -2
dz/dx = 1

dz/dx=1을 ReLU가 gradient를 통과시킨 결과로 해석하면 안 된다. ReLU 경로의 국소 미분값은 0이어서 그 경로의 기여도는 차단됐다. 같은 x에서 갈라진 log(exp(x)) 경로의 미분값 1이 전체 gradient에 남은 것이다.
최종 gradient 하나만 보는 것보다 그래프의 경로를 분리해 읽어야 원인을 정확히 이해할 수 있다.
실패해야 하는 실험: log의 정의역
log(0)은 실수 범위에서 정의되지 않는다.
python -c "from stages.stage_03_scalar_primitives import Value; Value(0.0, 'zero').log()"프로그램은 log() 연산 경계에서 즉시 멈췄다.
ValueError: log의 입력은 0보다 커야 합니다

잘못된 log 노드 자체가 만들어지지 않으므로 downstream 그래프와 기존 gradient는 영향을 받지 않는다. NaN을 만든 뒤 역전파에서 발견하는 것보다 실패 범위가 작다.
결과뿐 아니라 국소 미분값도 검사하기
순전파 결과가 유한해도 국소 미분값은 유한하지 않을 수 있다. 예를 들어 x=0에서 0<n<1인 x^n의 함수값은 0이지만 미분값은 발산한다.
결과 노드를 만들기 전에 두 조건을 따로 검사한다.
- 순전파 결과가 유한한가?
- 모든 parent 방향 국소 미분값이 유한한가? 둘 중 하나라도 실패하면 노드를 만들지 않는다. 정상처럼 보이는 순전파 값이 위험한 gradient를 가리는 상황을 차단한다.
exp와 log는 floating-point에서 완전히 상쇄되지 않는다
수학적으로는 log(exp(x)) = x이지만 컴퓨터에서는 모든 입력에 대해 같지 않다. 큰 양수는 exp(x)에서 overflow가 날 수 있고, 큰 음수는 exp(x)가 0으로 underflow한 뒤 log(0) 정의역을 벗어날 수 있다.
이번 Stage는 이를 자동으로 숨기지 않는다. exp overflow와 log 정의역을 각 연산 경계에서 실패시킨다. 이후 softmax에서는 수식 자체를 바꾸는 수치 안정화가 별도 추상화로 필요하다.
비용과 Blast Radius
- unary 연산 하나는 상수 시간에 결과 노드 하나와 parent 간선 하나를 생성
- 노드 수
V, 간선 수E일 때 위상 정렬과 역전파는O(V+E) - 위상 정렬과 임시 gradient는
O(V)보조 메모리 - 정의역 검사는 실패를 해당 연산 노드 하나로 제한
- 국소 미분 유한성 검사는 잘못된 gradient의 전체 그래프 전파를 차단
- 기본 1,000노드 상한으로 CPU·메모리 사용을 조기에 제한
- GPU·NPU는 사용하지 않으며 Python 객체와
math함수 호출 비용이 발생 원자적 역전파도 유지한다. 연산 종류가 늘어나더라도 실패한 계산이 기존 gradient를 부분 변경하지 않는다.
이번 Stage가 증명한 것
Stage 03은 거듭제곱, exp, log, ReLU가 같은 그래프 안에서 국소 미분값을 합성할 수 있음을 증명했다. 중앙 차분과의 일치, ReLU 음수 경로 차단, log(0) 조기 실패를 서로 다른 실험으로 확인했다.
Stage 04에서는 이 방식으로 character tokenizer와 BOS 토큰의 경계를 살펴본다.