본문 바로가기
파이썬 프로그래밍/Numpy 딥러닝

40.[RNN기초] RNN(many to one) 순전파 구현(실습)

by Majestyblue 2023. 9. 13.

이전시간에 아래의 순환 셀과 분류기(fc)에 대하여

 

각 가중치는 아래와 같음을 증명하였다.

  • Wxh = ( sequence length(2), hidden node(3) ) → ( 2, 3 ) 
  • Whh = ( hidden node(3), hidden node(3) ) → ( 3, 3 )
  • Bh = (1, 1)
  • Wy = ( hidden node(3), output feature(1) ) → ( 3, 1 )
  • By = (1, 1)

순전파를 코드로 구현해 보자.

 

 

1. 데이터 전처리

38차시에서 실시했던 전처리 코드이다. 그대로 시행하면 된다.

import numpy as np
from itertools import *
np.random.seed(230907)

dataset = ["as", "soon", "as"]
datalist = list(set(permutations(dataset, 3)))
# permutations는 중복이 허용됨. 중복을 제거하기 위해 set 함수를 이용함

def parallel_prep(x):
  count = 0
  for sentence in x:
    prep = np.array([])
    for word in sentence:
      if word == 'as':
        word = np.array([1, 0])
      elif word == 'soon':
        word = np.array([0, 1])
      if prep.size == 0:
        prep = np.concatenate([prep, word])
      else:
        prep = np.vstack([prep, word])
    if count == 0:
      total_prep = prep.copy()
    else:
      total_prep = np.vstack([total_prep, prep])
    count = count + 1
  total_prep = np.reshape(total_prep, (-1, 3, 2)) # (batch, time steps, sequence length)
  return total_prep

target = np.array([[0],
                   [0],
                   [1]])

 

입력 데이터는 아래와 같다.

 

 

2. 가중치들 설정

설정한 가중치를 참고하여 코드로 가중치를 정의한다. 은닉 상태의 노드는 정하기 나름인데 

여기서는 3개로 정했다.

  • Wxh = ( sequence length(2), hidden node(3) ) → ( 2, 3 ) 
  • Whh = ( hidden node(3), hidden node(3) ) → ( 3, 3 )
  • Bh = (1, 1)
  • Wy = ( hidden node(3), output feature(1) ) → ( 3, 1 )
  • By = (1, 1)
time_steps = input_RNN.shape[1] # t.s(3)
sequence_length = input_RNN.shape[2] # s.l(2)
hidden_node = 3 # 내가 설정해야 하는 것, h.n(3)
output_feature = target.shape[1] # o.f(1)

Wxh = np.random.randn(sequence_length, hidden_node) # (s.l(2), h.n(3))
Whh = np.random.randn(hidden_node, hidden_node) # (h.n(3), h.n(3))
Bh = np.random.randn(1, 1) # (1, 1)

Wy = np.random.randn(hidden_node, output_feature) # (h.n(3), o.f(1))
By = np.random.randn(1, 1) # (1, 1)

 

 

 

3. RNN 순전파 구현

아래의 모식도를 살펴보자. 1개의 sequence가 차례대로 time step에 따라 입력된다.

아래 예제는 "as soon as"의 예시이다.

 [ [ 1, 0 ],

   [ 0, 1 ],

   [ 1, 0 ] ]

 

 

따라서 1개씩 접근하기 위해 for문을 이용해 볼 수 있다.

아래 for문은 input_RNN[0] 요소인 "as as soon"의 예시이다.

data는 (time steps, sequence length)로 구성되어 있다. "as as soon"의 경우

[ [ 1, 0 ]

  [ 1, 0 ]

  [ 0, 1 ] ]

이므로 for sequnce in data로 data를 탐색하면 sequnce 단위인

[ 1, 0 ] → [ 1, 0 ] → [ 0, 1 ] 로 진행한다.

# RNN 셀을 위한 for문을 설정해 보자. 1개씩 요소로 접근해야 한다.
def rnn_cell(data):
  for sequence in data:
    # 1차원 sequence를 2차원으로 변환해야 행렬곱 연산 적용이 쉽다.
    x = np.reshape(sequence, (1, -1))
    print(x)

rnn_cell(input_RNN[0])

출력결과

[[1. 0.]]

[[1. 0.]]

[[0. 1.]]

 

 

표기에 주의할 점이 있는데 하이퍼블릭 탄젠트(tanh)를 통과하기 전 연산을 h`으로 정의하려고 하는데 코드에서 `을 구현하기 힘들어 '를 _i로 표기하고자 한다. 즉 아래와 같다.

 

위 표기법을 잘 기억하여 아래의 순전파 코드를 확인하자.

# RNN 셀을 위한 for문을 설정해 보자. 1개씩 요소로 접근해야 한다.
# 1개씩 접근한 값을 rnn 연산을 해 보자.
# 마지막 출력에 linear 연산을 걸고 출력하자.
def rnn_cell(data):
  h = np.zeros((1, hidden_node)) #(1, 3) h_0일 때 h_-1 상태는 없으므로 0이다.
  for sequence in data:
    x = np.reshape(sequence, (1, -1))
    h_i = np.dot(x, Wxh) + np.dot(h, Whh) + Bh
    h = np.tanh(h_i)
  pred_i = np.dot(h, Wy) + By
  pred = 1/(1+np.exp(-pred_i))
  return pred

predict = rnn_cell(input_RNN[0])
print(predict)

중요한 점을 이야기해 보자.

  • h_0 상태는 존재하지 않지만 역전파 계산 시 규칙성을 적용하기 위해 필요하다. 따라서 np.zeros를 이용하여 0인 h_0상태를 정의한다.
  • x → h_i, → h 를 data의 sequence에 따라 순회한다. 즉 [ 1, 0 ] → [ 1, 0 ] → [ 0, 1 ] 로 진행한다.
  • sequence 진행이 끝났따면 마지막으로 fc 연산을 수행하여 pred_i → pred로 진행한다.

출력결과

[[0.57238818]]

 

 

 

4. 오차함수 구현

오차함수는 Binary Cross Entropy Error인 이진 교차 엔트로피 오차(BCEE)를 사용한다.

# 첫 번째 데이터에 대한 오차 계산
def BCEE_loss(y_hat, y):
  loss = np.sum(-y*np.log(y_hat) - (1-y)*np.log(1-y_hat))
  return loss

losses = BCEE_loss(predict, target[0])
print(losses)

출력결과

0.8495394526507959

 

 

다음 시간에는 역전파를 구현해 보도록 하겠다.