import numpy as np
timesteps = 10 # 시점의 수. NLP에서는 보통 문장의 길이가 된다.
input_size = 4 # 입력의 차원. NLP에서는 보통 단어 벡터의 차원이 된다.
hidden_size = 8 # 은닉 상태의 크기. 메모리 셀의 용량이다.
inputs = np.random.random((timesteps, input_size)) # 입력에 해당되는 2D 텐서
# 쉬운 이해를 위해 2D 텐서로 가정했으나 실제 파이토치에선 batch_size가 포함된 3D 텐서의 입력을 받는다.
hidden_state_t = np.zeros((hidden_size,)) # 초기 은닉 상태는 0(벡터)로 초기화
# 은닉 상태의 크기 hidden_size로 은닉 상태를 만듬.
print(hidden_state_t) # 8의 크기를 가지는 은닉 상태. 현재는 초기 은닉 상태로 모든 차원이 0의 값을 가짐.
[0. 0. 0. 0. 0. 0. 0. 0.]
# 가중치와 편향 정의
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).
print(np.shape(Wx))
print(np.shape(Wh))
print(np.shape(b))
(8, 4)
(8, 8)
(8,)
# RNN 층 동작
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 텐서를 출력.
(1, 8)
(2, 8)
(3, 8)
(4, 8)
(5, 8)
(6, 8)
(7, 8)
(8, 8)
(9, 8)
(10, 8)
[[0.99994788 0.9999617 0.99995532 0.99993631 0.99992509 0.99997685
0.9999986 0.9999828 ]
[0.99973439 0.99991707 0.99985339 0.99985522 0.99985771 0.99992208
0.99999709 0.99996431]
[0.99977628 0.99995234 0.99975985 0.99988085 0.99989981 0.99997422
0.99999853 0.99997849]
[0.99975057 0.99995433 0.99989045 0.99975765 0.99991362 0.99989079
0.99999599 0.99995614]
[0.99997016 0.99996681 0.999964 0.99997593 0.99995089 0.99999126
0.99999949 0.99999236]
[0.99970514 0.99994434 0.9997721 0.99988929 0.99991427 0.99996127
0.99999855 0.99997848]
[0.99982007 0.99989741 0.99990778 0.99991067 0.99985279 0.99992823
0.99999761 0.9999706 ]
[0.99982146 0.99989636 0.99990522 0.99989431 0.99982903 0.99992443
0.99999712 0.99996608]
[0.99985846 0.99990951 0.99991321 0.99989168 0.99982538 0.99993841
0.99999708 0.99996645]
[0.99980879 0.99993696 0.99982009 0.9998787 0.99986009 0.99996607
0.99999799 0.99997338]]
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) # 배치크기 x 시점의 수 x 매 시점 둘어가는 입력
# RNN 셀 만들기
cell = nn.RNN(input_size, hidden_size, batch_first=True)
# 입력 텐서를 RNN 셀에 입력
outputs, _status = cell(inputs)
print(outputs.shape) # 모든 time-step의 hidden_state
torch.Size([1, 10, 8])
print(_status.shape) # 최종 time-step의 hidden_state
torch.Size([1, 1, 8])
# (batch_size, time_steps, input_size)
inputs = torch.Tensor(1, 10, 5)
# num_layer = 2 (은닉층이 2개)
cell = nn.RNN(input_size = 5, hidden_size = 8, num_layers = 2, batch_first=True)
print(outputs.shape) # 모든 time-step의 hidden_state
torch.Size([1, 10, 8])
print(_status.shape) # (층의 개수, 배치 크기, 은닉 상태의 크기)
torch.Size([2, 1, 8])
아래 괄호를 채우기 위해선, 앞의 내용 뿐만 아니라 뒤의 내용도 알아야 한다.
- Exercise is very effective a [ ] belly fat.
이처럼 이전 시점 뿐만 아니라 이후 시점의 데이터도 활용하기 위해 고안된 것이 양방향 RNN 이다.
양방향 RNN은 하나의 출력값을 예측하기 위해 기본적으로 두 개의 메모리 셀을 사용한다.
첫 번째 메모리 셀은 앞 시점의 은닉상태(Forward State)를 전달받아 현재의 은닉상태를 계산한다.
두 번째 메모리 셀은 뒤 시점의 은닉상태(Backward State)를 전달받아 현재의 은닉상태를 계산한다
위의 그림에서 주황색은 Forward State를 초록색 메모리 셀은 Backward State에 해당된다.
# (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, bidirectional = True)
outputs, _status = cell(inputs)
print(outputs.shape) # (배치 크기, 시퀀스 길이, 은닉 상태의 크기 x 2)
torch.Size([1, 10, 16])
print(_status.shape) # (층의 개수 x 2, 배치 크기, 은닉 상태의 크기)
torch.Size([4, 1, 8])