04.3 수치 미분
경사법에서는 기울기(경사) 값을 기준으로 나아갈 방향을 정합니다. 기울기란 무엇인지, 또 어떤 성질이 있는지를 설명하기에 앞서, 이번 절에서는 학생 때 배운 ‘미분’부터 복습해보겠습니다.
4.3.1 미분
여러분이 마라톤 선수고 처음부터 10분에서 2km씩 달렸다고 해봅시다. 이때의 속도는 간단히 $2 / 10 = 0.2 \text{ [km/분]}$이라고 계산할 수 있습니다. 즉, 1분에 0.2km만큼의 속도(변화)로 뛰었다고 해석할 수 있습니다.
이 마라톤 예에서는 ‘달린 거리’가 ‘시간’에 대해서 얼마나 변화했는가를 계산했습니다. 다만 여기에서 10분에 2km를 뛰었다는 것은, 정확하게는 10분 동안의 ‘평균 속도’를 구한 것이죠. 미분은 ‘특정 순간’의 변화량을 뜻합니다. 그래서 10분이라는 시간을 가능한 한 줄여(직전 1분에 달린 거리, 직전 1초에 달린 거리, 직전 0.1초에 달린 거리, … 식으로 갈수록 간격을 줄여) 한 순간의 변화량(어느 순간의 속도)을 얻는 것이죠.
이처럼 미분은 한순간의 변화량을 표시한 것입니다. 수식으로 다음과 같습니다.
\[\frac{df(x)}{dx} = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h} \hfill \text{[식 4.4]}\][식 4.4]는 함수의 미분을 나타낸 식입니다. 좌변은 $f(x)$의 $x$에 대한 미분($x$에 대한 $f(x)$의 변화량)을 나타내는 기호입니다. 결국, $x$의 ‘작은 변화’가 함수 $f(x)$를 얼마나 변화시키느냐를 의미합니다. 이때 시간의 작은 변화, 즉 시간을 뜻하는 $h$를 한없이 0에 가깝게 한다는 의미를 $\lim_{h \to 0}$로 나타냅니다.
지니의 돋보기 미분 마법
- 지니: “도로시, 미분은 아주 미세한 순간의 변화량(접선의 기울기)을 재는 마법이야. 곡선 언덕 위의 어느 한 지점(P)에 서서, 요술 돋보기로 그 지점을 엄청나게 확대해 보면 결국 평평한 직선 언덕처럼 보이지? 그 직선의 기울기를 구하는 게 바로 그 점에서의 미분이단다!”
- 도로시: “아! 먼 미래나 먼 과거를 보며 평균 속도를 구하는 게 아니라, 요술 돋보기로 지금 이 순간(h를 한없이 0에 가깝게 줄인 지점)의 기울기만 딱 낚아채는 거구나!”
- 토토: “왈왈! (돋보기를 들여다보며) 순간 포착 기울기 마법이다 멍!”
지니가 요술 돋보기를 들어 함수 곡선의 한 점 P에서의 접선(미분 기울기)을 크게 확대해 도로시에게 보여주고 있고, 토토가 신기한 듯 꼬리를 흔들고 있어요.
자, [식 4.4]를 참고하여 함수의 미분을 구하는 계산을 파이썬으로 구현해봅시다. [식 4.4]를 곧이곧대로 구현하려면 $h$에 작은 값을 대입해 다음과 같이 계산할 수 있습니다.
# 나쁜 구현 예
def numerical_diff(f, x):
h = 10e-50
return (f(x + h) - f(x)) / h
함수의 이름은 수치 미분 numerical differentiation에서 따온 numerical_diff(f, x)로 했습니다. 이 함수는 ‘함수 f‘와 ‘함수 f에 넘길 인수 x‘라는 두 인수를 받습니다. 얼핏 보면 문제가 없어 보이지만, 실제로는 개선해야 할 점이 2개 있습니다.
앞의 구현에서는 $h$에 가급적 작은 값을 대입하고 싶었기에(가능하다면 $h$를 0으로 무한히 가깝게 하고 싶으니) 10e-50이라는 작은 값을 이용했습니다. 이 값은 0.00…1 형태에서 0이 50개라는 의미죠. 그러나 이 방식은 반올림 오차 rounding error 문제를 일으킵니다. 반올림 오차는 작은 값(가령 소수점 8자리 이하)이 생략되어 최종 계산 결과에 오차가 생기게 합니다. 파이썬에서의 반올림 오차로는 다음과 같은 예가 있습니다.
>>> np.float32(1e-50)
0.0
이와 같이 1e-50을 float32형(32비트 부동소수점)으로 나타내면 0.0이 되어, 올바로 표현할 수 없습니다. 너무 작은 값을 이용하면 컴퓨터로 계산하는 데 문제가 된다는 것이죠. 여기가 첫 번째 개선 포인트입니다. 이 미세한 값 $h$로 $10^{-4}$를 이용해봅시다. $10^{-4}$ 정도의 값을 사용하면 좋은 결과를 얻는다고 알려져 있습니다.
두 번째 개선은 함수 f의 차분*과 관련한 것입니다. 앞의 구현에서는 $x + h$와 $x$ 사이의 함수 f의 차분을 계산하고 있지만, 애당초 이 계산에는 오차가 있다는 사실에 주의해야 합니다. [그림 4-5]와 같이 ‘진정한 미분’은 $x$ 위치의 함수의 기울기(이를 접선이라 함)에 해당하지만, 이번 구현에서의 미분은 $(x + h)$와 $x$ 사이의 기울기에 해당합니다. 그래서 진정한 미분(진정한 접선)과 이번 구현의 값은 엄밀히는 일치하지 않습니다. 이 차이는 $h$를 무한히 0으로 좁히는 것이 불가능해 생기는 한계입니다.
그림 4-5 진정한 미분(진정한 접선)과 수치 미분(근사로 구한 접선)의 값은 다르다.

[그림 4-5]와 같이 수치 미분에는 오차가 포함됩니다. 이 오차를 줄이기 위해 $(x + h)$와 $(x - h)$일 때의 함수 $f$의 차분을 계산하는 방법을 쓰기도 합니다. 이 차분은 $x$를 중심으로 그 전후의 차분을 계산한다는 의미에서 중심 차분 혹은 중앙 차분이라 합니다(한편, $(x + h)$와 $x$의 차분은 전방 차분이라 합니다).
그럼 이상의 두 개선점을 적용해 수치 미분을 다시 구현해봅시다.
도로시의 오차 요정 퇴치 (중앙 차분)
- 도로시: “지니! 컴퓨터에서 미분을 구하려고 간격 $h$를
10e-50처럼 극도로 작게 설정했더니, 컴퓨터가 미세 소수점 이하를 생략해 버리는 반올림 오차 요정이 나타나서 값이 0이 되어 버려!”- 지니: “후후, 컴퓨터의 표현 한계 때문에 너무 작아도 안 된단다. 가장 좋은 보폭은 $h = 10^{-4}$(0.0001) 정도야. 그리고 전방 차분보다 $x$를 중심으로 앞뒤 간격인 $(x+h)$와 $(x-h)$ 사이의 기울기(중앙 차분)를 재면 오차 요정을 완벽하게 극복할 수 있지!”
- 토토: “왈왈! (중앙 차분 그물로 오차 요정을 낚아채며) $1e-4$와 중앙 차분으로 요정을 잡았다 멍!”
도로시가 미세 간격 $h = 10^{-4}$ 자를 이용해 세밀하게 조정을 진행하고 있고, 지니와 토토가 컴퓨터 계산을 망가뜨리는 ‘반올림 오차 요정’을 그물로 안전하게 포획하고 있어요.
def numerical_diff(f, x):
h = 1e-4 # 0.0001
return (f(x+h) - f(x-h)) / (2*h)
NOTE_ 여기서 하는 것처럼 아주 작은 차분으로 미분을 구하는 것을 수치 미분이라 합니다. 한편, 수식을 전개해 미분을 구하는 것은 해석적 analytic이라는 말을 이용하여 ‘해석적 해’ 혹은 ‘해석적으로 미분을 구하다’ 등으로 표현합니다. 가령 $y = x^2$의 미분은 해석적으로는 $\frac{dy}{dx} = 2x$로 풀어낼 수 있습니다. 그래서 $x = 2$일 때 $y$의 미분은 4가 됩니다. 해석적 미분은 오차를 포함하지 않는 ‘진정한 미분’ 값을 구해줍니다.
4.3.2 수치 미분의 예
앞 절의 수치 미분을 사용하여 간단한 함수를 미분해봅시다. 우선 다음과 같은 2차 함수입니다.
\[y = 0.01x^2 + 0.1x \hfill \text{[식 4.5]}\][식 4.5]를 파이썬으로 구현하면 다음과 같이 됩니다.
def function_1(x):
return 0.01*x**2 + 0.1*x
이어서 이 함수를 그려봅시다.
import numpy as np
import matplotlib.pylab as plt
x = np.arange(0.0, 20.0, 0.1) # 0에서 20까지 0.1 간격의 배열 x를 만든다.
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x, y)
plt.show()
그림 4-6 식 $f(x) = 0.01x^2 + 0.1x$의 그래프

그럼 $x = 5$일 때와 10일 때 이 함수의 미분을 계산해봅시다.
>>> numerical_diff(function_1, 5)
0.199999999990898
>>> numerical_diff(function_1, 10)
0.299999999986347
이렇게 계산한 미분 값이 $x$에 대한 $f(x)$의 변화량입니다. 즉, 함수의 기울기에 해당합니다. 또한 $f(x) = 0.01x^2 + 0.1x$의 해석적 해법은 $\frac{df(x)}{dx} = 0.02x + 0.1$입니다. 그래서 $x$가 5와 10일 때의 ‘진정한 미분’은 차례로 0.2와 0.3이 됩니다. 앞의 수치 미분과 결과를 비교하면 그 오차가 매우 작음을 알 수 있습니다. 실제로 거의 같은 값이라고 해도 될 만큼 작은 오차입니다.
이제 앞에서 구한 수치 미분 값을 기울기로 하는 직선을 그려보겠습니다. 결과는 [그림 4-7]과 같이 되어, 함수의 접선에 해당하는 것을 확인할 수 있습니다(ch04/gradient_1d.py).
그림 4-7 $x = 5$, $x = 10$에서의 접선 : 직선의 기울기는 수치 미분에서 구한 값을 사용하였다.

4.3.3 편미분
이어서 [식 4.6]과 같은 함수를 살펴보시죠. 인수들의 제곱 합을 계산하는 단순한 식입니다만, 앞의 예와 달리 변수가 2개라는 점에 주의해야 합니다.
\[f(x_0, x_1) = x_0^2 + x_1^2 \hfill \text{[식 4.6]}\]이 식은 파이썬으로 다음과 같이 구현할 수 있습니다.
def function_2(x):
return x[0]**2 + x[1]**2
# 또는 return np.sum(x**2)
인수 x는 넘파이 배열이라고 가정합니다. 이 코드는 넘파이 배열의 각 원소를 제곱하고 그 합을 구할 뿐인 간단한 구현입니다(또는 np.sum(x**2) 형태로 구현할 수 있습니다). 자, 이 함수를 그래프로 그려볼까요? 결과는 [그림 4-8]처럼 3차원으로 그려집니다.
그림 4-8 $f(x_0, x_1) = x_0^2 + x_1^2$의 그래프

그럼 [식 4.6]의 미분을 구해보죠. 여기서 주의할 점은 [식 4.6]에는 변수가 2개라는 것입니다. 그래서 ‘어느 변수에 대한 미분이냐’, 즉 $x_0$와 $x_1$ 중 어느 변수에 대한 미분이냐를 구별해야 합니다. 덧붙여 이와 같이 변수가 여럿인 함수에 대한 미분을 편미분이라고 합니다. 이 편미분을 수식으로는 $\frac{\partial f}{\partial x_0}$ 나 $\frac{\partial f}{\partial x_1}$ 처럼 씁니다.
기울기는 어떻게 구할까요? 연습 삼아 다음 두 편미분 문제를 풀어보시죠.
문제 1 : $x_0 = 3, x_1 = 4$일 때, $x_0$에 대한 편미분 $\frac{\partial f}{\partial x_0}$를 구하라.
>>> def function_tmp1(x0):
... return x0*x0 + 4.0**2.0
...
>>> numerical_diff(function_tmp1, 3.0)
6.00000000000378
문제 2 : $x_0 = 3, x_1 = 4$일 때, $x_1$에 대한 편미분 $\frac{\partial f}{\partial x_1}$를 구하라.
>>> def function_tmp2(x1):
... return 3.0**2.0 + x1*x1
...
>>> numerical_diff(function_tmp2, 4.0)
7.99999999999119
이들 문제는 변수가 하나인 함수를 정의하고, 그 함수에 대한 미분을 구하는 형태로 구현하여 풀었습니다. 예를 들어 문제 1에서는 $x_1 = 4$로 고정된 새로운 함수를 정의하고, 변수가 $x_0$ 하나뿐인 함수에 대해 수치 미분 함수를 적용하였습니다. 이렇게 구한 문제 1의 결과는 6.00000000000378, 문제 2의 결과는 7.99999999999119입니다. 보다시피 해석적 미분의 결과와 거의 같지요.
이처럼 편미분은 변수가 하나인 미분과 마찬가지로 특정 장소의 기울기를 구합니다. 단, 여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정합니다. 앞의 예에서는 목표 변수를 제외한 나머지를 특정 값에 고정하기 위해서 새로운 함수를 정의했지요. 그리고 그 새로 정의한 함수에 대해 그동안 사용한 수치 미분 함수를 적용하여 편미분을 구한 것입니다.
* 옮긴이_ 임의의 두 점에서의 함수 값들의 차이를 말합니다.