일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 융합연구
- 리눅스
- panoptic nerf
- Neural Radiance Field
- 경희대
- linux
- ICCV 2021
- 딥러닝
- Paper
- docker
- Semantic Segmentation
- 파이토치
- IROS
- pytorch
- 2022
- Deep Learning
- ICCV
- GAN
- paper review
- 논문 리뷰
- 논문
- panoptic segmentation
- CVPR
- NeRF paper
- Computer Vision
- NERF
- Python
- Vae
- CVPR2023
- 논문리뷰
- Today
- Total
윤제로의 제로베이스
순환 신경망(Recurrent Neural Network, RNN) 본문
01. 순환 신경망(Recurrent Neural Network, RNN)
RNN(Recurrent Neural Network)은 시퀀스(Sequence) 모델입니다. 입력과 출력을 시퀀스 단위로 처리하는 모델입니다. 번역기를 생각해보면 입력은 번 ...
wikidocs.net
RNN은 시퀀스 모델이다.
입력과 출력을 시퀀스단위로 처리하는 모델이다.
1. 순환 신경망(Recurrent Neural Network, RNN)
RNN은 은닉층의 노드에서 활성화 함수를 통해 나온 결과값을 출력층 방향으로도 보내면서, 다시 은닉층 노드의 다음 계산의 입력으로 보내는 특징을 갖고 있다.
RNN의 은닉층에서 활성화 함수를 통해 결과를 내보내는 역할을 하는 노드를 셀(cell)이라고 한다.
이 셀은 이전의 값을 기억하려고 하는 일종의 메모리 역할을 수행하므로 이를 메모리 셀 또는 RNN 셀이라고 표현한다.
은닉층의 메모리 셀은 각각의 시점(time step)에서 바로 이전 시점에서의 은닉층의 메모리 셀에서 나온 값을 자신의 입력으로 사용하는 재귀적 활동을 하고 있다.
현재 시점 t에서의 메모리 셀이 갖고 있는 값은 과거의 메모리 셀들의 값에 영향을 받은 것임을 의미한다.
메모리 셀이 출력층 방향으로 또는 당므 시점 t+1의 자신에게 보내는 값을 은닉 상태(hidden state)라고 한다.
다시 말해 t시점의 메모리 셀은 t-1 시점의 메모리 셀이 보낸 은닉 상태값을 t시점의 은닉 상태 계산을 위한 입력값으로 사용한다.
RNN을 표현할 때 일반적으로 위의 그림처럼 표현한다.
피드 포워드 신경망에서는 뉴런이라는 단위를 사용했지만, RNN에서는 뉴런이라는 단위 보다는 입력층과 출력층에서는 각각 입력 벡터와 출력 벡터, 은닉층에서는 은닉 상태라는 표현을 주로 사용한다.
그래서 사실 위의 그림에서 회색과 초록색으로 표현한 네모들은 기본적으로 벡터 단위로 가정하고 있다.
위의 그림은 입력 벡터의 차원이 4, 은닉 상태의 크기가 2, 출력층의 출력 벡터의 차원이 2인 RNN이 시점 2일 때의 모습을 보여준다.
다시 말해 뉴런 단위로 해석하면 입력층의 뉴런 수는 4, 은닉층의 뉴런수 2, 출력층의 뉴런수 2이다.
RNN은 입력과 출력의 길이를 다르게 설계할 수 있다.
이를 자연어 처리에서 어떻게 사용할 수 있는지 예를 들어보자.
예를 들어 하나의 입력에 대해서 여러개의 출력(one-to-many)의 모델은 이미지 입력에 대해서 사진의 제목을 출력하는 이미지 캡셔닝(Image Captioning)에 사용할 수 있다.
또한 단어 시퀀스에 대해서 하나의 출력(many-to-one)을 하는 모델은 입력 문서가 긍정적인지 부정적인지를 판별하는 감정 분류(sentiment classification), 또는 메일이 정상인지 스팸인지 판별하는 스팸 메일 분류에서 사용할 수 있다.
many-to-many의 모델의 경우 입력 문장으로 부터 대답 문장을 출력하는 챗봇과 입력 문장으로부터 번역된 문장을 출력하는 번역기, 개체명 인식이나 품사 태깅과 같은 작업이 속한다.
현재 시점 t에서의 은닉 상태를 h_t라고 정의한다.
은닉층의 메모리 셀은 h_t를 계산하기 위해 2개의 가중치를 갖게 된다.
하나는 입력층에서 입력값을 위한 가중치 W_x이고, 하나는 이전 시점 t-1의 은닉상태인 h_t-1을 위한 가중치 W_h이다.
RNN의 은닉층 연산을 벡터와 행렬 연산으로 이해할 수 있다.
자연어 처리에서 RNN의 입력 x_t는 대부분 단어 벡터로 간주할 수 있는데, 단어 벡터의 차원을 d라고 하고, 은닉 상태의 크기를 D_h라고 할 때 벡터와 행렬의 크기는 다음과 같다.
배치 크기가 1이고, d와 D_h 두 값 모두를 4로 가정했을 때 RNN 은닉층 연산을 그림으로 표현하면 아래와 같다.
h_t를 계산하기 위한 활성화 함수로는 주로 하이퍼볼릭탄젠트 함수가 사용되지만, ReLU로 바꿔 사용하는 시도도 있다.
위의 식에서 각각의 가중치 W_x, W_h, W_y의 값은 모든 시점에서 값을 동일하게 공유한다.
만약 은닉층이 2개 이상일 경우 은닉층 2개의 가중치는 서로 다르다.
출력 층은 결과값인 y_t를 계산하기 위한 활성화 함수로는 상황에 따라 다르다.
이진 분류라면 시그모이드, 다중 선택 문제라면 소프트맥스 함수가 사용될 것이다.
2. 파이썬으로 RNN 구현하기
# 아래의 코드는 의사 코드(pseudocode)로 실제 동작하는 코드가 아님.
hidden_state_t = 0 # 초기 은닉 상태를 0(벡터)로 초기화
for input_t in input_length: # 각 시점마다 입력을 받는다.
output_t = tanh(input_t, hidden_state_t) # 각 시점에 대해서 입력과 은닉 상태를 가지고 연산
hidden_state_t = output_t # 계산 결과는 현재 시점의 은닉 상태가 된다.
우선 t 시점의 은닉 상태를 hidden_state_t라는 변수로 선언하였고, 입력 데이터의 길이를 input_length로 선언했다.
이 경우, 입력 데이터의 길이는 곧 총 시점의 수(time steps)가 된다.
그리고 t 시점의 입력값을 input_t로 선언하였다.
각 메모리 셀은 각 시점마다 input_t와 hidden_state_t를 입력으로 활성화 함수인 하이퍼볼릭탄젠트 함수를 통해 현시점의 hidden_state_t를 계산한다.
import numpy as np
timesteps = 10 # 시점의 수. NLP에서는 보통 문장의 길이가 된다.
input_size = 4 # 입력의 차원. NLP에서는 보통 단어 벡터의 차원이 된다.
hidden_size = 8 # 은닉 상태의 크기. 메모리 셀의 용량이다.
inputs = np.random.random((timesteps, input_size)) # 입력에 해당되는 2D 텐서
hidden_state_t = np.zeros((hidden_size,)) # 초기 은닉 상태는 0(벡터)로 초기화
# 은닉 상태의 크기 hidden_size로 은닉 상태를 만듬.
시점, 입력의 차원, 은닉 상태의 크기를 정의한다.
현재 초기 은닉 상태는 0의 값을 가지는 8의 차원을 가지는 벡터로 초기화 되었다.
Wx = np.random.random((hidden_size, input_size)) # (8, 4)크기의 2D 텐서 생성. 입력에 대한 가중치.
Wh = np.random.random((hidden_size, hidden_size)) # (8, 8)크기의 2D 텐서 생성. 은닉 상태에 대한 가중치.
b = np.random.random((hidden_size,)) # (8,)크기의 1D 텐서 생성. 이 값은 편향(bias).
W_x는 (은닉 상태의 크기 * 입력의 차원)
W_h는 (은닉 상태의 크기 * 은닉 상태의 크기)
b는 (은닉 상태의 크기)
total_hidden_states = []
# 메모리 셀 동작
for input_t in inputs: # 각 시점에 따라서 입력값이 입력됨.
output_t = np.tanh(np.dot(Wx,input_t) + np.dot(Wh,hidden_state_t) + b) # Wx * Xt + Wh * Ht-1 + b(bias)
total_hidden_states.append(list(output_t)) # 각 시점의 은닉 상태의 값을 계속해서 축적
print(np.shape(total_hidden_states)) # 각 시점 t별 메모리 셀의 출력의 크기는 (timestep, output_dim)
hidden_state_t = output_t
total_hidden_states = np.stack(total_hidden_states, axis = 0)
# 출력 시 값을 깔끔하게 해준다.
print(total_hidden_states) # (timesteps, output_dim)의 크기. 이 경우 (10, 8)의 크기를 가지는 메모리 셀의 2D 텐서를 출력.
3. 파이토치의 nn.RNN()
import torch
import torch.nn as nn
input_size = 5 # 입력의 크기
hidden_size = 8 # 은닉 상태의 크기
# (batch_size, time_steps, input_size)
inputs = torch.Tensor(1, 10, 5)
입력 텐서의 경우 (배치 크기 * 시점의 수 * 시점마다 들어가는 입력)의 크기를 갖는다.
여기서는 배치크기 1, 10번의 시점동안 5차원의 입력 벡터가 들어가도록 정의되었다.
이제 nn.RNN()을 사용하여 RNN의 셀을 만든다.
인자로 입력의 크기, 은닉 상태의 크기를 정의해주고 batch_first = True를 통해 입력 텐서의 첫번째 차원이 배치 크기임을 알려준다.
cell = nn.RNN(input_size, hidden_size, batch_first=True)
outputs, _status = cell(inputs)
4. 깊은 순환 신경망(Deep Recurrent Neural Network)
은닉층이 2개 이상인 순환 신경망을 깊은 순환 신경망이라고 한다.
깊은 순환 신경망을 파이토치로 구현할 때는 nn.RNN()의 인지안 num_layers에 값을 전달하여 층을 쌓는다.
층이 2개인 깊은 순환 신경망의 경우 어떻게 달라지는지 코드를 확인하자.
# (batch_size, time_steps, input_size)
inputs = torch.Tensor(1, 10, 5)
cell = nn.RNN(input_size = 5, hidden_size = 8, num_layers = 2, batch_first=True)
5. 양방향 순환 신경망(Bidirectional Recurrent Neural Network)
RNN이 과거 시점(time step)의 데이터들을 참고해서 찾고자 하는 정답을 예측하지만 실제 문제에서는 과거 시점의 데이터만 고려하는 것이 아니라 향후 시점의 데이터에 힌트가 있는 경우도 많다.
그래서 이전 시점 뿐 아니라 이후 시점도 힌트로 활용하기 위해 고안된 것이 양방향 RNN이다.
양방향 RNN은 하나의 출력값을 예측하기 위해 기본적으로 두개의 메모리 셀을 사용한다.
첫번째 메모리 셀은 앞에서 배운 것 처럼 앞 시점의 은닉 상태(Forward State)를 전달받아 현재의 은닉 상태를 계산한다.
두번째 메모리 셀은 뒤 시점의 은닉 상태(Backward State)를 전달받아 현재의 은닉 상태를 계산한다.
그리고 이 두개의 값 모두가 출력층에서 출력값을 예측하기 위해 사용된다.
양방향 RNN도 다수의 은닉층을 가질 수 있다.
양방향 순환 신경망을 파이토치로 구현할 때는 nn.RNN()의 인자인 bidirectional에 값을 True로 전달하면 된다.
'Background > Pytorch 기초' 카테고리의 다른 글
문자 단위 RNN(Char RNN) (0) | 2022.01.26 |
---|---|
장단기 메모리(Long Short-Term Memory, LSTM) (0) | 2022.01.26 |
파이토치의 nn.Embedding() (0) | 2022.01.26 |
글로브(GloVe) (0) | 2022.01.26 |
워드투벡터(Word2Vec) (1) | 2022.01.26 |