Boostcamp AI Tech

[Boostcamp 선택 과제 - 2] RNN Backpropagation 구현

ju_young 2021. 8. 8. 14:15
728x90

1. Recurrent Neural Networks

RNN은 다음과 같은 점화식으로 정의할 수 있다.
$$S_k = f(S_{k-1} W_{rec} + X_k W_x)$$
구조를 확인하면 다음과 같은 그림으로 표현할 수 있다.

2. Backpropagation 실습

중요한 부분은 여기서부터이므로 이전 설명은 생략하였다.

Import

import numpy as np
np.random.seed(seed=1) #일정한 난수 생성

dataset

# 샘플 개수
n_samples = 100

# 시퀀스 길이
len_sequence = 10

# 시퀀스 생성
X = np.zeros((n_samples, len_sequence))

for row_idx in range(n_samples):
    X[row_idx,:] = np.around(np.random.rand(len_sequence)).astype(int) #0과 1로 구성된 데이터로 생성한다.

# 각 시퀀스의 타겟 생성
t = np.sum(X, axis=1) # 열끼리 더한 값을 반환(100,)

forward

# 점화식 정의
def update_state(xk, sk, wx, wRec):
    return xk * wx + sk * wRec


def forward_states(X, wx, wRec):
    # 모든 input 시퀀스 X 들에 대한 상태를 담고 있는 행렬 S 초기화
    S = np.zeros((X.shape[0], X.shape[1]+1)) #k + 1을 예측하기 위해 1열을 추가
    for k in range(0, X.shape[1]):
        # S[k] = S[k-1] * wRec + X[k] * wx
        S[:,k+1] = update_state(X[:,k], S[:,k], wx, wRec)
    return S

# loss function을 정의
def loss(y, t): 
    return np.mean((t - y)**2)

backward

이 backpropagation에 대한 계산 내용은 따로 작성하겠다. 일단 각 gradient 값의 수식은 다음과 같이 나온다.
$$\frac{\partial L}{\partial W_x} = \sum_{k=0}^n \frac{\partial L}{\partial S_k} X_k$$
$$\frac{\partial L}{\partial W_rec} = \sum_{k=1}^n \frac{\partial L}{\partial S_k} S_{k-1}$$
위 두 개의 식을 그대로 구현하여 작성하면 다음과 같다.

def output_gradient(y, t):
    return 2. * (y - t)

def backward_gradient(X, S, grad_out, wRec):
    """
    X: input
    S: 모든 input 시퀀스에 대한 상태를 담고 있는 행렬
    grad_out: output의 gradient
    wRec: 재귀적으로 사용되는 학습 파라미터
    """
    # grad_over_time: loss의 state 에 대한 gradient 

    # 초기화
    grad_over_time = np.zeros((X.shape[0], X.shape[1]+1))
    grad_over_time[:, -1] = grad_out

    # gradient accumulations 초기화
    wx_grad = 0
    wRec_grad = 0

    # backpropagation이므로 역순으로 반복하는 range(X.shape[1], 0, -1)으로 지정하였다.
    for k in range(X.shape[1], 0, -1):
        wx_grad += np.sum(np.mean(grad_over_time[:,k] * X[:,k-1], axis=0)) # 현재 값과 이전 값을 계산한 값을 모두 더한다. 즉, 두개의 값으로 계산하는 것을 반복하는 것이다.
        wRec_grad += np.sum(np.mean(grad_over_time[:,k] * S[:,k-1]), axis=0)
        grad_over_time[:,k-1] = grad_over_time[:,k] * wRec
    return (wx_grad, wRec_grad), grad_over_time
728x90