파이썬 프로그래밍/딥러닝과 수학

7. 2차원 행렬을 입력받는 합성함수의 도함수(실습)

Majestyblue 2021. 12. 31. 00:43

저번시간에 입력이 2차원인 합성함수의 도함수를 구하였다.

 

1. 다시 복습을 해보자.

 

입력 X, W는 아래와 같다. 

X.shape (3, 3) , W.shape(3, 2)

 

g(X, W)함수를 아래와 같이 정의한다.

g(X, W) = X × W shape (3, 2)
간략하게 표기한 g(X, W)

σ(X) 함수를 아래와 같이 정의한다.

σ 시그마, 시그모이드 함수

 

h(X) 함수를 아래와 같이 정의한다.

각 요소들의 합

 

합성함수 f(X, W) = h(σ(g(X, W)))를 정의한다. 합성함수의 정의와 연산의 결과는 아래와 같다.

합성함수의 정의
합성함수 연산 결과

 

 

이 때 도함수 ∂f/∂X 는 아래와 같이 성립한다고 유도하였다.

 

 

∂f/∂W 는 아래와 같다.

 

 

이번 실습은 ∂f/∂X 으로 진행한다. 

 

 

 

 

2. 합성함수의 도함수 계산이 맞는지 직접 계산하여 확인해보기

위의 식을 numpy를 이용하여 적은 것이다. f(X, W)는 forward 함수, ∂f/∂X는 backward 함수로 정의하였다.

import numpy as np

# 직접 계산하여 합성함수의 도함수 계산이 맞는지 확인해보기
X = np.array([[0.1, 0.2, 0.3],
              [0.4, 0.5, 0.6],
              [0.7, 0.8, 0.9]])
W = np.array([[1, 2],
              [3, 4],
              [5, 6]])
X2 = np.array([[0.1001, 0.2, 0.3],
              [0.4, 0.5, 0.6],
              [0.7, 0.8, 0.9]])

def f_forward(X, W):
    
    #g(X, W) = X × W
    g_XW = np.dot(X, W)
    
    #σ(x) = 1 / (1 + exp^(-x))
    s_XW = 1 / (1 + np.exp(-g_XW))
    
    #h(x) = Σx
    h_XW = np.sum(s_XW)
    
    return h_XW

def deriv(func, input, delta=0.001): 
    return (func(input + delta) - func(input)) / delta

def sigmoid(x):
    return 1 / (1+np.exp(-x))

def f_backward(X, W, sig):
    #g(X, W) = X × W, (3, 2) shape
    g_XW = np.dot(X, W)
    
    #dσ(g(X, W))(/dg(X, W)
    dsdg = deriv(sig, g_XW)
    
    #dg(X, W)/dX
    dgdx = np.transpose(W, (1, 0))
    
    #dh(σ(g(X, W)))/dX
    dfdx = np.dot(dsdg, dgdx)
    
    return dfdx


print('X : ', X)
print('f(X) : ', f_forward(X, W))
print('df(x)/dX : ', f_backward(X, W, sigmoid))
print(' ')
print('X2 : ', X2)
print('f(X2) : ', f_forward(X2, W))


"""
<출력 결과>
f(x) : 5.833329393581208
df(x)/dX :  0.197792792
f(x2) : 5.833349179920838
delta_x = 0.0001

f(x2) - f(x) = 0.000019786
df(x)/dX * delta_x = 0.0000197792
"""

출력결과를 해석하겠다. 

1. x(11)의 변화를 0.0001로 더해준 것을 X2로 정의한다.

2. backward 계산으로 X에서의 기울기를 구한다. 이를 ∂f/∂X 라고 하자.

3. f(X2, W) - f(X, W) = ∂f/∂X * deltaX 와 최대한 같아야 식이 올바른 것이다.

 

4. f(x2) - f(x) = 0.000019786 

5. ∂f/∂X * deltaX = 0.197792792 * 0.0001 = 0.0000197792

 

4번의 결과와 5번의 결과를 비교해 보자. 거의 같다. 그렇다면 식이 올바르다고 볼 수 있다.

 

 

 

 

 

3. pytorch를 이용하여 직접 확인해 보기

pytorch로 식을 계산하여 기울기를 구했을 때

2에서 구한 기울기 ∂f/∂X  = 0.197792792 와 최대한 비슷하거나 같다면 

도함수를 올바르게 유도했다고 볼 수 있을 것이다. 아래는 pytorch로 표현한 코드이다.

(reference : https://wikidocs.net/60754)

import numpy as np
import torch

# 파이토치로 계산해보기
X = np.array([[0.1, 0.2, 0.3],
              [0.4, 0.5, 0.6],
              [0.7, 0.8, 0.9]])
W = np.array([[1., 2.],
              [3., 4.],
              [5., 6.]])

X = torch.tensor(X, requires_grad = True)
W = torch.tensor(W, requires_grad=False)

#g(X, W) = X × W
g_XW = torch.matmul(X, W)

#σ(x) = 1 / (1 + exp^(-x))
s_XW = 1 / (1 + torch.exp(-g_XW))
    
#h(x) = Σx
h_XW = torch.sum(s_XW)

h_XW.backward()


"""
print(X.grad)
tensor([[1.9788e-01, 4.8555e-01, 7.7323e-01],
        [1.0649e-02, 2.8635e-02, 4.6621e-02],
        [5.9074e-04, 1.6814e-03, 2.7721e-03]], dtype=torch.float64)
"""

forward 연산을 하고 최종 출력값인 f(X, W) = h_XW를 구한다. 

여기서 X의 requires_grad = True로 설정하여 기울기를 저장해 놓았다. 즉 변수 X에 대한 변화를 기록하는 것이다!

여기서 W의 변화는 관심없으므로 False로 설정함.

 

최종출력(h_XW)에 대해 backward()를 시행하면 위 함수에 대한 도함수를 구한다.

 

마지막에 X.grad를 호출하면 X에 따른 h_XW의 변화율을 알 수 있다. 

 

1행 1열의 값에 주목하자. 0.197788이다.

위에서 직접 유도하여 구한 ∂f/∂X  = 0.197792792과 비교해 보자. 비슷한가? 비슷한 것 같다.

 

따라서 직접 유도하여 구한 합성함수의 도함수는 타당하다고 볼 수 있다.