Search
Duplicate

6-1 순환 신경망 RNN

1. 순환 신경망

RNN의 목적은 시퀀스 텐서를 모델링하는 것입니다. RNN에는 여러 모델이 있는데, 이 장에서는 가장 기본이 되는 엘만 RNN을 다뤄보겠습니다. RNN에서는 시퀀스의 표현 학습을 위해 시퀀스의 현재 상태를 감지하는 은닉 상태 벡터를 관리합니다. 현재 입력 벡터와 이전 은닉 상태 벡터를 이용하여 은닉 상태 벡터를 계산합니다.
출처: 밑바닥부터 시작하는 딥러닝2
현재 타임 스텝의 입력 벡터와 이전 타임 스텝의 은닉 벡터는 현재 타임 스텝의 은닉 상태 벡터에 매핑됩니다. 은닉-은닉 가중치 행렬을 사용해 이전 은닉 상태 벡터를 매핑하고 입력-은닉 가중치 행렬을 사용해 입력 벡터를 매핑하여 새로운 은닉 벡터를 계산합니다. 이때 은닉-은닉 가중치와 입력-은닉 가중치가 연속된 타임 스텝에 걸쳐 공유된다는 점이 중요합니다. 훈련 과정에서 이런 가중치는 수정되며, 현재 입력 정보와 지금까지의 입력을 요약한 상태 표현을 사용하는 방법을 배웁니다. 어느 타임 스텝에 있는지 알 수는 없지만, 한 타임 스텝에서 다른 타임 스텝으로 이동하면서 손실 함수를 최소화하기 위해 상태 표현을 관리하는 방법을 학습하게 됩니다.
동일한 가중치를 사용해 타임 스텝마다 입력을 출력으로 변환하는 것은 파라미터 공유의 한 예입니다. CNN은 커널이라는 파라미터를 사용해 입력 데이터의 부분 영역에서 출력을 계산합니다. 합성곱 커널은 입력을 가로질러 이동하고 가능한 위치마다 출력을 계산함으로써 이동 불변성을 학습합니다. 반면 RNN은 같은 파라미터를 사용해 타임 스텝마다 출력을 계산합니다. 이때 은닉 상태 벡터에 의존해서 시퀀스의 상태를 감지하며, RNN의 목적은 주어진 은닉 상태 벡터와 입력 벡터에 대한 출력을 계산함으로써 시퀀스 불변성을 학습하는 것입니다. RNN은 시간을 따라, CNN은 공간을 따라 파라미터를 공유하는 것입니다.

2. 엘만 RNN

아래 코드의 ElmanRNN은 RNNCell을 사용해 앞서 언급했던 입력-은닉 가중치 행렬과 은닉-은닉 가중치 행렬을 만듭니다. RNNCell은 호출마다 입력 벡터 행렬과 은닉 벡터 행렬을 받아 이 타임 스텝의 은닉 벡터 행렬과 결과를 반환합니다.
class ElmanRNN(nn.Module): """ RNNCell을 사용하여 만든 엘만 RNN """ def __init__(self, input_size, hidden_size, batch_first=False): """ 매개변수: input_size (int): 입력 벡터 크기 hidden_size (int): 은닉 상태 벡터 크기 batch_first (bool): 0번째 차원이 배치인지 여부 """ super(ElmanRNN, self).__init__() self.rnn_cell = nn.RNNCell(input_size, hidden_size) self.batch_first = batch_first self.hidden_size = hidden_size def _initial_hidden(self, batch_size): return torch.zeros((batch_size, self.hidden_size)) def forward(self, x_in, initial_hidden=None): """ ElmanRNN의 정방향 계산 매개변수: x_in (torch.Tensor): 입력 데이터 텐서 If self.batch_first: x_in.shape = (batch_size, seq_size, feat_size) Else: x_in.shape = (seq_size, batch_size, feat_size) initial_hidden (torch.Tensor): RNN의 초기 은닉 상태 반환값: hiddens (torch.Tensor): 각 타임 스텝에서 RNN 출력 If self.batch_first: hiddens.shape = (batch_size, seq_size, hidden_size) Else: hiddens.shape = (seq_size, batch_size, hidden_size) """ if self.batch_first: batch_size, seq_size, feat_size = x_in.size() x_in = x_in.permute(1, 0, 2) else: seq_size, batch_size, feat_size = x_in.size() hiddens = [] if initial_hidden is None: initial_hidden = self._initial_hidden(batch_size) initial_hidden = initial_hidden.to(x_in.device) hidden_t = initial_hidden for t in range(seq_size): hidden_t = self.rnn_cell(x_in[t], hidden_t) hiddens.append(hidden_t) hiddens = torch.stack(hiddens) if self.batch_first: hiddens = hiddens.permute(1, 0, 2) return hiddens
Python
복사
해당 RNN에서 입력과 은닉 상태의 크기를 하이퍼파라미터로 제어하는 것 외에도 배치 차원이 0번째에 있는지 지정하는 boolean매개변수가 있습니다. 이는 모든 파이토치 RNN구현에 있으며, True로 설정 시 RNN이 입력 텐서의 0번째와 1번째 차원을 바꿉니다.
forward() 메서드는 입력 텐서를 순회하면서 타임 스텝마다 은닉 상태 벡터를 계산합니다. 초기 은닉 상태를 따로 지정하지 않는다면 기본 은닉 상태 벡터는 모두 0이 됩니다. ElmanRNN 클래스가 입력 벡터의 길이만큼 반복하면서 새로운 은닉 상태를 계산하고, 이런 은닉 상태를 수집해 쌓아 놓습니다. 은닉 상태를 반환하기 전에 batch_first 플래그를 다시 확인하며, 이 매개변수가 True라면 출력 은닉 상태의 배치 차원을 0번째로 바꿉니다.
위 ElmanRNN 클래스의 출력은 3차원 텐서로, 배치에 있는 각 데이터 포인트와 타임 스텝에 대한 은닉 상태 벡터입니다. 이 은닉 벡터를 주어진 작업에 따라 여러가지 방법으로 사용 가능합니다. 한 가지 방법은 각 타임 스텝을 정해진 범주로 분류하는 것으로, 이는 타임 스텝마다 예측과 관련된 정보를 추적하도록 RNN 가중치가 조정됨을 의미합니다. 다른 방법으로 최종 벡터를 사용해 전체 시퀀스를 분류할 수 있습니다. 이는 최종 분류에 중요한 정보를 추적하도록 RNN 가중치가 조정됨을 의미합니다. 이어지는 두 장에서는 순차 예측을 조금 더 자세히 알아보겠습니다.
다음 글 읽기