Calculus_심파이를 사용한 함수 미분

JKH·약 6시간 전
0

미적분

목록 보기
2/3

데이터 사이언스 스쿨에서 공부한 내용입니다.

4.2 심파이를 사용한 함수 미분

예측 모형의 성능

데이터 분석에서 미분이 필요한 이유?
✏️
입력값이 변했을 때 함수의 출력값이 어떻게 달라지는지를 미분을 통해 정량적으로 계산하여 목적함수를 최적화 한다.

기울기

수치적 최적화는 가장 적은 횟수로 여러가지 xx값을 시도하여 최적의 값을 찾는 방법이다. 수치적 최적화를 하려면 어떤 x1x_1값을 시도한 후 다음 번에 다른 x2x_2값으로 어떤 값이 더 좋을지 알아야 한다.

목적함수를 최대화하는 문제에서 만약 x1x_1 위치에서 xx를 증가시켰을 때 yy값이 증가한다는 것을 알고 있다면 x2x_2값은 x1x_1보다 더 큰 값을 쓰는 것이 좋다. 반대로 x1x_1 위치에서 xx를 증가시켰을 때 yy값이 감소한다면 x2x_2값은 x1x_1보다 더 작은 값을 쓰는 것이 좋다. 이 정보를 기울기(slope) 혹은 민감도(sensitivity) 라고 한다.

만약 입력변수의 값이 xx에서 x2x_2Δx=x2x\Delta x = x_2 - x만큼 달라졌다고 가정하자. 출력변수는 f(x)f(x)이라는 값에서 f(x2)f(x_2)라는 값으로 Δy=f(x2)f(x)\Delta y = f(x_2) - f(x)만큼 달라질 것이다. 이를 비율로 나타내면 다음과 같다.

ΔyΔx=f(x2)f(x)x2x=f(x+Δx)f(x)Δx(4.2.2)\dfrac{\Delta y}{\Delta x} = \dfrac{f(x_2) - f(x)}{x_2 - x} = \dfrac{f(x + \Delta x) - f(x)}{\Delta x} \tag{4.2.2}

그런데 이 방식으로 계산한 변화율은 x2x_2x1x_1에서 얼마나 멀리 떨어져 있는가 즉, Δx\Delta x의 크기에 따라 달라진다. 이를 해결하기 위해 기울기(slope) 라는 개념을 사용한다. 기울기는 dxdx값이 0으로 근접할 때의 변화율을 말한다. 기호로는 다음처럼 쓴다.

slope=limΔx0f(x+Δx)f(x)Δx(4.2.3)\text{slope} = \lim_{\Delta x \rightarrow 0} \dfrac{f(x + \Delta x) - f(x)}{\Delta x} \tag{4.2.3}

수치미분(numerical differentiation)

scipy.misc 패키지의 derivative() 명령을 사용하면 수치적으로 대략적인 기울기를 계산할 수 있다. 인수로는 기울기를 구하고자 하는 함수 f, 기울기를 구할 위치 x, 기울기를 구하기 위해 이동할 거리 dx를 받는다. 이 숫자는 작을수록 좋지만(예를들면 1e-6) 너무 작을 경우에는 부동소수점 연산의 오버플로우 오류로 인해 오히려 오차를 증폭할 수도 있으므로 조심하여야 한다. 다음 수식으로 대략적인 기울기는 구한다.

slopef(x+12dx)f(x12dx)dx(4.2.7)\text{slope} \approx \dfrac{f\left(x + \dfrac{1}{2}dx\right) - f\left(x - \dfrac{1}{2}dx\right)}{dx} \tag{4.2.7}
from scipy.misc import derivative
derivative(f, 0, dx=1e-6)

미분

미분(differentiation) 이란 어떤 함수로부터 그 함수 기울기를 출력하는 새로운 함수를 만들어내는 작업이다. 미분으로 만들어진 함수를 원래 함수의 도함수(derivative) 라고 한다.

f=ddx(f)=ddxf=dfdx=ddx(y)=ddxy=dydx(4.2.8)f' = \dfrac{d}{dx}(f) = \dfrac{d}{dx}f = \dfrac{df}{dx} = \dfrac{d}{dx}(y) = \dfrac{d}{dx}y = \dfrac{dy}{dx} \tag{4.2.8}

연습 문제 4.2.2

몇 개의 점을 정하고 그곳에서 y=x33x2+xy=x^3 - 3x^2 + x 의 기울기를 수치미분을 통해 계산하고
이를 바탕으로 도함수의 그래프를 그려본다.

✏️

import numpy as np
from scipy.misc import derivative
import matplotlib.pyplot as plt

def f(x):
    return (x**3 - 3*x**2 + x)

x = np.linspace(-1, 3, 8)
y_prime = derivative(f, x, dx=1e-6)
print(x)
print(y_prime)
plt.plot(x, y_prime)

수치미분 결과

x-1.-0.50.0.51.1.52.2.53.
y'10.4.751.-1.25-2.-1.251.4.7510.

도함수의 그래프(개형)

도함수의 값이 기울기와 일치하는 것을 알 수 있다.

def fprime(x):
    return 3 * x ** 2 - 6 * x + 1

# 계수가 3, -6, 1인 2차 방정식의 근
x1, x2 = np.roots([3, -6, 1]) 

x = np.linspace(-1, 3, 400)

plt.figure(figsize=(10, 7))
plt.subplot(211)
plt.plot(x, f(x))
plt.xlim(-2, 4)
plt.xticks(np.arange(-1, 4))
plt.yticks(np.arange(-5, 4))
plt.xlabel('x')
plt.title('함수 f(x)')
plt.axvline(x1, c="b", ls="--")
plt.axvline(x2, c="b", ls="--")

plt.subplot(212)
plt.plot(x, fprime(x))
plt.xlim(-2, 4)
plt.xticks(np.arange(-1, 4))
plt.yticks(np.arange(-3, 11))
plt.xlabel('x')
plt.title("도함수 f'(x)")
plt.axhline(0, c="r", ls="--")
plt.axvline(x1, c="b", ls="--")
plt.axvline(x2, c="b", ls="--")

plt.tight_layout()
plt.show()

연습 문제 4.2.3

다음 함수를 미분하라. kk, aa, bb 상수)
(1)

f(x)=x31(4.2.34)f(x) = x^3 - 1 \tag{4.2.34}

✏️
f(x)=3x2f'(x) = 3x^2

(2)

f(x)=log(x23k)(4.2.35)f(x) = \log (x^{2}-3k) \tag{4.2.35}

✏️
f(x)=2xx23kf'(x) = \frac{2x}{x^2-3k}

(3)

f(x)=exp(axb)(4.2.36)f(x) =\exp({ax^b}) \tag{4.2.36}

f(x)=abxb1exp(axb)f'(x) = abx^{b-1}\exp(ax^b)

2차 도함수

도함수를 한 번 더 미분하여 만들어진 함수를 2차 도함수(second derivative) 라고 한다.

f=d2dx2(f)=d2dx2f=d2fdx2=d2dx2(y)=d2dx2y=d2ydx2(4.2.37)f'' = \dfrac{d^2}{dx^2}(f) = \dfrac{d^2}{dx^2}f = \dfrac{d^2f}{dx^2} = \dfrac{d^2}{dx^2}(y) = \dfrac{d^2}{dx^2}y = \dfrac{d^2y}{dx^2} \tag{4.2.37}

2차 도함수는 도함수의 기울기를 나타낸다. 즉 도함수 값이 증가하면 2차 도함수 값은 양수이고, 도함수 값이 감소하면 2차 도함수 값은 음수다.

2차 도함수 값이 양수인 경우를 볼록(convex) 하다고 하며 2차 도함수 값이 음수인 경우를 오목(concave) 하다고 한다. 이때 볼록과 오목은 아래에서 바라 본 관점이다. 그래서 2차 도함수값을 볼록도(convexity) 라고도 부른다.

예제

다음 그래프의 함수는 f(x)f''(x)가 음수인 구간(x<1x < 1)에서는 오목하고 f(x)f''(x)가 양수인 구간(x>1x > 1)에서는 볼록하다.

def fprime2(x):
    return 6*x - 6

# 2차 방정식의 근
x1, x2 = np.roots([3, -6, 1]) 

x = np.linspace(-1, 3, 400)

plt.figure(figsize=(10, 10))

plt.subplot(311)
plt.plot(x, f(x))
plt.xlim(-2, 4)
plt.xticks(np.arange(-1, 4))
plt.yticks(np.arange(-5, 4))
plt.title('함수 f(x)')
plt.xlabel('x')
plt.axvline(x1, c="b", ls="--")
plt.axvline(x2, c="b", ls="--")
plt.axvline(1, c="g", ls=":")

plt.subplot(312)
plt.plot(x, fprime(x))
plt.xlim(-2, 4)
plt.xticks(np.arange(-1, 4))
plt.yticks(np.arange(-3, 11))
plt.title("도함수 f'(x)")
plt.xlabel('x')
plt.axhline(0, c='r', ls="--")
plt.axvline(x1, c="b", ls="--")
plt.axvline(x2, c="b", ls="--")
plt.axvline(1, c="g", ls=":")

plt.subplot(313)
plt.plot(x, fprime2(x))
plt.xlim(-2, 4)
plt.xticks(np.arange(-1, 4))
plt.title("2차 도함수 f'(x)")
plt.axhline(0, c='r', ls="--")
plt.axvline(1, c="g", ls=":")

plt.tight_layout()
plt.show()

다변수 함수의 연쇄법칙

y1=f1(x1,x2,,xM)y2=f2(x1,x2,,xM)yN=fN(x1,x2,,xM)(4.2.46)\begin{aligned} y_1 &= f_1(x_1, x_2, \ldots, x_M) \\ y_2 &= f_2(x_1, x_2, \ldots, x_M) \\ & \vdots \\ y_N &= f_N(x_1, x_2, \ldots, x_M) \end{aligned} \tag{4.2.46}
z=g(y1,y2,,yN)(4.2.44)z = g(y_1, y_2, \ldots, y_N) \tag{4.2.44}

이때의 변수 x1x_1 값의 변화에 따른 zz 값의 변화는 chain rule로 구할 수 있다.

zx1=zy1y1x1+zy2y2x1++zyNyNx1(4.2.47)\dfrac{\partial z}{\partial x_1} = \dfrac{\partial z}{\partial y_1}\dfrac{\partial y_1}{\partial x_1} + \dfrac{\partial z}{\partial y_2}\dfrac{\partial y_2}{\partial x_1} + \cdots + \dfrac{\partial z}{\partial y_N}\dfrac{\partial y_N}{\partial x_1} \tag{4.2.47}

2차 편미분

편미분에 대해서도 2차 도함수를 정의할 수 있다. 편미분의 2차 도함수를 구할 때는 각각의 미분에 쓰이는 독립 변수를 자유롭게 선택할 수 있다.

첫번째 미분과 두번째 미분에서 모두 xx에 대해 미분하면 다음과 같이 표기한다.

fxx(x,y)=2fx2(4.2.48)f_{xx}(x,y) = \dfrac{\partial^2 f}{\partial x^2} \tag{4.2.48}

첫번째 미분과 두번째 미분에서 모두 yy에 대해 미분하면 다음과 같이 표기한다.

fyy(x,y)=2fy2(4.2.49)f_{yy}(x,y) = \dfrac{\partial^2 f}{\partial y^2} \tag{4.2.49}

첫번째 미분에서는 xx에 대해 미분하고 두번째 미분에서는 yy에 대해 미분하면 다음과 같이 표기한다.

fxy(x,y)=2fyx(4.2.50)f_{xy}(x,y) = \dfrac{\partial^2 f}{\partial y \partial x} \tag{4.2.50}

첫번째 미분에서는 yy에 대해 미분하고 두번째 미분에서는 xx에 대해 미분하면 다음과 같이 표기한다.

fyx(x,y)=2fxy(4.2.51)f_{yx}(x,y) = \dfrac{\partial^2 f}{\partial x \partial y} \tag{4.2.51}

위 결과에서 xx로 먼저 미분하고 나중에 yy로 미분한 2차 도함수 fxyf_{xy}yy로 먼저 미분하고 나중에 xx로 미분한 2차 도함수 fyxf_{yx}와 같다. 만약 함수가 연속이고 미분 가능하면 미분의 순서는 상관없다. 이를 슈와르츠 정리(Schwarz's theorem) 라고 한다.

연습 문제 4.2.4

다음 함수에 대한 1차/2차 편미분 fxf_x, fyf_y, fxxf_{xx}, fxyf_{xy}, fyxf_{yx}, fyyf_{yy}를 구하라.

f(x,y)=exp(x2+2y2)(4.2.56)f(x, y) = \exp{(x^2 + 2y^2)} \tag{4.2.56}

✏️
fx=2xexp(x2+2y2)f_x=2x\exp{(x^2 + 2y^2)}
fy=4yexp(x2+2y2)f_y=4y\exp{(x^2 + 2y^2)}
fxx=2exp(x2+2y2)+4x2exp(x2+2y2)f_{xx}=2\exp{(x^2 + 2y^2)}+4x^2\exp{(x^2 + 2y^2)}
fxy=8xyexp(x2+2y2)f_{xy}=8xy\exp{(x^2 + 2y^2)}
fyx=8xyexp(x2+2y2)f_{yx}=8xy\exp{(x^2 + 2y^2)}
fyy=4exp(x2+2y2)+16y2exp(x2+2y2)f_{yy}=4\exp{(x^2 + 2y^2)}+16y^2\exp{(x^2 + 2y^2)}

테일러 전개

함수의 기울기(1차 미분값)를 알고 있다면 함수의 모양을 다음처럼 근사화할 수 있다. x0x_0는 함수값과 기울기를 구하는 xx 위치이며 사용자가 마음대로 설정할 수 있다.

f(x)f(x0)+df(x0)dx(xx0)(4.2.57)f(x) \approx f(x_0) + \dfrac{df(x_0)}{dx}(x - x_0) \tag{4.2.57}

이를 테일러 전개(Taylor expansion) 라고 한다. 다변수 함수의 경우에는 다음처럼 테일러 전개를 한다.

f(x,y)f(x0,y0)+f(x0,y0)x(xx0)+f(x0,y0)y(yy0)(4.2.58)f(x, y) \approx f(x_0, y_0) + \dfrac{\partial f(x_0, y_0)}{\partial x}(x - x_0) + \dfrac{\partial f(x_0, y_0)}{\partial y}(y - y_0) \tag{4.2.58}

심파이(SymPy)

심파이는 심볼릭 연산(symbolic operation) 을 지원하는 파이썬 패키지다. 심볼릭 연산이란 사람이 연필로 계산하는 미분/적분과 동일한 형태의 연산을 말한다. 즉, x2x^2의 미분 연산을 수행하면 그 결과가 2x2x란 형태로 출력된다. 딥 러닝(deep learning) 등에 많이 사용되는 파이썬의 tensorflow 패키지나 pytorch 패키지도 기울기 함수 계산을 위해 이러한 심볼릭 연산 기능을 갖추고 있다.

import sympy

# Juypter 노트북에서 수학식의 LaTeX 표현을 위해 필요함
sympy.init_printing(use_latex='mathjax')

심볼릭 연산에서 사용하는 심볼릭 변수(symbolic variable) 는 일반 프로그래밍에서 사용하는 변수와 다르다. 일반 프로그래밍에서 사용하는 변수는 이미 메모리에 씌여 있는 어떤 숫자를 기호로 쓴 것에 지나지 않지만 심볼릭 변수는 아무런 숫자도 대입이 되어 있지 않다. 따라서 x2x^2의 미분 연산을 수행하려면 우선 SymPy의 symbols() 명령을 사용하여 xx라는 기호가 단순한 숫자나 벡터 변수가 아닌 심볼(symbol)임을 알려주어야 한다. 이렇게 정의된 심볼 변수는 Symbol 클래스 자료형이 된다.

x = sympy.symbols('x')
x
x
type(x)
sympy.core.symbol.Symbol

일단 심볼 변수를 정의하면 이를 사용하여 다음과 같이 함수를 정의한다. 이때 수학 함수는 심파이 전용 함수를 사용해야 한다.

f = x * sympy.exp(x)
f
   x
x⋅ℯ 

함수가 정의되면 diff() 함수로 미분을 할 수 있다. 또한 simplify() 함수를 써서 소인수분해 등을 통한 수식 정리가 가능하다.

sympy.diff(f)
   x    x
x⋅ℯ  + ℯ 
sympy.simplify(sympy.diff(f))
         x
(x + 1)⋅ℯ 

편미분을 하는 경우에는 어떤 변수로 미분하는지를 diff() 함수에 명시해야 한다. symbols() 명령을 사용할 때는 인수로 주는 문자열에 여러개의 심볼 변수를 동시에 넣을 수도 있다.

x, y = sympy.symbols('x y')
f = x ** 2 + 4 * x * y + 4 * y ** 2
f
 2              2
x  + 4⋅x⋅y + 4⋅y 
sympy.diff(f, x)
2⋅x + 4⋅y
sympy.diff(f, y)
4⋅x + 8⋅y

상수 심볼을 포함하는 함수를 미분하는 경우, 심파이는 어떤 심볼이 상수이고 어떤 심볼이 변수인지 알 수 없기 때문에 편미분인 것처럼 입력 변수를 지정해야 한다.

x, mu, sigma = sympy.symbols('x mu sigma')
f = sympy.exp((x - mu) ** 2 / sigma ** 2)
f
         2
 (-μ + x) 
 ─────────
      2   
     σ    
ℯ         
sympy.diff(f, x)
                      2
              (-μ + x) 
              ─────────
                   2   
                  σ    
(-2⋅μ + 2⋅x)⋅ℯ         
───────────────────────
            2          
           σ           
sympy.simplify(sympy.diff(f, x))
                   2
            (μ - x) 
            ────────
                2   
               σ    
2⋅(-μ + x)⋅ℯ        
────────────────────
          2         
         σ          

이차 도함수는 다음처럼 구한다.

sympy.diff(f, x, x)
                           2
                    (μ - x) 
                    ────────
  ⎛             2⎞      2   
  ⎜    2⋅(μ - x) ⎟     σ    
2⋅⎜1 + ──────────⎟⋅ℯ        
  ⎜         2    ⎟          
  ⎝        σ     ⎠          
────────────────────────────
              2             
             σ              

연습 문제 4.2.5

다음 함수를 미분한 도함수를 심파이를 사용하여 구하라. kk, aa, bb는 상수다.

f(x)=x31(4.2.59)f(x) = x^3 - 1 \tag{4.2.59}
f(x)=log(x23k)(4.2.60)f(x) = \log (x^{2}-3k) \tag{4.2.60}
f(x)=exp(axb)(4.2.61)f(x) =\exp({ax^b}) \tag{4.2.61}

✏️

import sympy
sympy.init_printing(use_latex='mathjax')
x, a, b, k  = sympy.symbols('x a b k')
f1 = x ** 3 - 1
f2 = sympy.log(x ** 2 - 3 * k)
f3 = sympy.exp(a * x ** b)
print(sympy.diff(f1, x))
print(sympy.diff(f2, x))
print(sympy.diff(f3, x))
3*x**2
2*x/(-3*k + x**2)
a*b*x**b*exp(a*x**b)/x

연습 문제 4.2.6

다음 함수에 대한 1차/2차 편미분 fxf_x, fyf_y, fxxf_{xx}, fxyf_{xy}, fyxf_{yx}, fyyf_{yy}를 심파이로 구하라.

f(x,y)=exp(x2+2y2)(4.2.62)f(x, y) = \exp{(x^2 + 2y^2)} \tag{4.2.62}

✏️

import sympy
sympy.init_printing(use_latex='mathjax')
x, y  = sympy.symbols('x y')
f = sympy.exp(x ** 2 + 2 * y ** 2)
print(sympy.diff(f, x))
print(sympy.diff(f, y))
print(sympy.diff(sympy.diff(f, x),x))
print(sympy.diff(sympy.diff(f, x),y))
print(sympy.diff(sympy.diff(f, y),x))
print(sympy.diff(sympy.diff(f, y),y))
2*x*exp(x**2 + 2*y**2)
4*y*exp(x**2 + 2*y**2)
4*x**2*exp(x**2 + 2*y**2) + 2*exp(x**2 + 2*y**2)
8*x*y*exp(x**2 + 2*y**2)
8*x*y*exp(x**2 + 2*y**2)
16*y**2*exp(x**2 + 2*y**2) + 4*exp(x**2 + 2*y**2)
profile
Connecting my favorite things

0개의 댓글