본문 바로가기
딥러닝

순환신경망(RNN) 구현, Time RNN

by 데이널 2024. 4. 9.

이전 글에서 RNN의 학습에 대해 알아봤다면, 이번에는 RNN의 구현에 대해 이야기해 보겠습니다. '밑바닥부터 시작하는 딥러닝 2'가 잘 설명하고 있어서 책의 내용을 기반으로 작성되었습니다. 

 

 

순환신경망(RNN) 구현

RNN은 순환하는 신경망이라고 했습니다. 그리고 몇번 순환할 것인지가 횟수가 t입니다. RNN은 순환하는 길이가 t인 시계열 데이터를 받을 경우 각 시각의 은닉 상태를 t개 출력합니다. 이를 모듈화 해보면 신경망을 '하나의 계층'으로 구현할 수 있습니다. 


일반적인 신경망은 가중의 종류가 하나지만, 은닉층의 메모리 셀은 $ ℎ_{𝑡} $를 계산하기 위해서 총 두 개의 가중치를 가집니다. 

  • 입력층을 위한 가중치 $ 𝑊_{𝑥} $
  • 이전 시점 t-1의 은닉 상태값인 $ ℎ_{𝑡−1} $을 위한 가중치  $ W_{ℎ} $

은닉층의 수식은  $ ℎ_{𝑡}=tanh⁡(𝑊_{𝑥} 𝑥_{𝑡} + 𝑊_{ℎ} ℎ_{𝑡−1} + 𝑏) $입니다. 데이터의 모양을 나타내면 아래 그림과 같은 형태입니다.  

 

RNN의 은닉층 차원 계산
RNN의 은닉층 차원 계산

 


출력층은 $ 𝑦_{𝑡}=𝑓⁡(𝑊_{𝑦} ℎ_{𝑡} + 𝑏) $ 로 표현할 수 있습니다. 단, 여기서 𝑓는 활성화 함수입니다. 출력층에서는 일반적인 가중치와 편향이 있는 방정식에서 활성화 함수를 적용한다고 할 수 있습니다.  


순환신경망(RNN) shape

RNN의 입출력 데이터는 어떻게 생겼을까요? 우선 입력 데이터부터 살펴 보겠습니다. 입력 데이터는 그림에서 확인할 수 있듯이 shape(3, 5, 4)입니다. 그리면 이 shape이 어떻게 나오지 그 구성은 아래와 같습니다.

  • 미니 batch size(N) : 배치 크기이며 그림에서는 3으로 표시되며, 배치 처리를 3 row 씩 처리한다는 이야기 입니다. 
  • 시퀀스 길이(S) : 얼마나 반복해서 수행하는지를 나타내며 그림에서 5개가 반복됩니다. 그래서 5번 넣어줘야 합니다. 
  • 입력 벡터 차원수(D) : 입력 벡터 크기이며 그림에서는 4 입니다, 4개씩 입력 벡터가 들어갑니다. 

RNN의 입출력 데이터 shape
RNN의 입출력 데이터 shape

 

 

그렇다면 출력(Output) 데이터는 어떻게 될까요? 히든 상태 벡터 차원수(H)에 따라 달라지는 데요. 보라색 원의 결괏값의 벡터 size를 정하기 나름입니다. 그림에서는 히든 벡터 차원수를 2로 정해서 출력 데이터는 shape(3, 5, 2)가 됩니다. 

 

이렇게 입력과 출력 데이터에 영향을 주는 요소들을 확인해 보았습니다. 그러면 위쪽에서 설명했던 차원 계산 그림은 sequence length가 1이라고 가정한 식이라는 것을 알 수 있습니다. 


순환신경망(RNN) 구현 코드

__init__

  • params : 변수에 2개의 가중치와 1개의 편향을 저장
  • grads : 변수에 각 매개변수에 대응하는 형태로 기울기를 초기화 한 후 저장
  • cache는 역전파 계산 시 사용하는 중간 데이터를 담아야 하기 때문에 생성

forward

  • 입력 x와 이전의 h_prev 값을 받아옴
  • 식 그대로 순전파를 구현
  • cache에 저장

Time RNN 구현

Time RNN 계층은 RNN 계층 t개를 연결한 신경망입니다. 이 신경망으로 Time RNN 클래스로 구현할 수 있습니다. RNN 계층의 은닉 상태 h를 인스턴스 변수로 유지합니다. RNN 계층의 은닉 상태를 Time RNN 계층에서 관리하는 것이죠. Time RNN 사용자는 RNN 계층 사이에서 은닉 상태를 '인계하는 작업'을 생각하지 않아도 된다는 장점이 있습니다. 

 

가중치와 편향을 stateful이라는 boolean 값을 인수로 받습니다. stateful은 은닉 상태를 인계받을지를 조정하기 위한 변수입니다. 예를 들어, True는 '유지'를 나타내고, '유지한다'는 말은 Time RNN 계층의 순전파를 끊지 않고 전파한다는 의미입니다. 


만약, Flase인 경우는 은닉 상태를 '영행렬'(모든 요소가 0인 행렬)로 초기화합니다. 이것이 상태가 없는 모드이며, '무상태'라고 합니다. h는 forward() 시 마지막 RNN 계층의 은닉 상태를 저장합니다. dh는 backward()를 불렀을 때 하나 앞 블록의 은닉 상태의 기울기를 저장합니다. 

 

Time RNN 계층의 역전파
Time RNN 계층의 역전파


forward

  • for문 안에서 T번 반복하여 RNN 계층을 생성
  • 계층 생성과 동시에 forward()로 h를 계산하고 이를 hs에 해당 인덱스(시각)의 값으로 설정
  • h에는 마지막 RNN 계층의 은닉 상태가 저장
  • 다음번 forward() 메서드 호출 시 stateful이 True면 먼저 저장된 h값이 이용되고, False면 다시 영행렬로 초기화

 

t번째 RNN 계층의 역전파
t번째 RNN 계층의 역전파

 

 

backward

  • 순전파에서 출력이 2개로 분기 되어, 역전파에서는 각 기울기가 합산되어 전해짐
  • 따라서 역전파 시 RNN 계층에는 합산된 기울기($ d_{ht} + dh_{next} $)가 입력
  • 각 RNN 계층의 가중치 기울기를 합산하여 최종 결과를 멤버 변수 grads에 덮어씀