[NLP] RNN 예제로 살펴보는 RNN 맛보기
AI, Deep Learning Basics/NLP

[NLP] RNN 예제로 살펴보는 RNN 맛보기

*이 글은 네이버 AI Hackerton의 참가 때 이용하였던 ClovaAI Github를 참고하여 작성되었습니다. 깃허브 링크를 누르시면 더 전반적인 코드를 보실 수 있습니다.

 

이 Encoder, Decoder 부분은 Attention 함수를 기반으로 작성되었기에 좀 어려움을 느끼실 수 있겠지만, RNN 전반적인 코드 작용에 대한 이해를 돕기 위해 작성되었습니다. 너무 깊게 보시는 것보다는 이런 흐름으로 이어진다는 느낌 정도를 받는다는 생각으로 가볍게 보시길 추천드립니다.

 

Encoder 함수 엿보기

#__init__ 함수 정의
def __init__(self, feature_size, hidden_size,
input_dropout_p=0, dropout_p=0, #여기의 파라미터는 알아서 조정해야함.
n_layers=1, bidirectional=False, rnn_cell='gru', variable_lengths=False):
super(EncoderRNN, self).__init__(0, 0, hidden_size,
input_dropout_p, dropout_p, n_layers, rnn_cell)
self.variable_lengths = variable_lengths
self.conv = nn.Sequential( #pytorch의 sequential 모델 이용
nn.Conv2d(1, 32, kernel_size=(41, 11), stride=(2, 2), padding=(20, 5)),
nn.BatchNorm2d(32),
nn.Hardtanh(0, 20, inplace=True),
nn.Conv2d(32, 32, kernel_size=(21, 11), stride=(2, 1), padding=(10, 5)),
nn.BatchNorm2d(32),
nn.Hardtanh(0, 20, inplace=True)
)
feature_size = math.ceil((feature_size - 11 + 1 + (5*2)) / 2)
feature_size = math.ceil(feature_size - 11 + 1 + (5*2))
feature_size *= 32
self.rnn = self.rnn_cell(feature_size, hidden_size, n_layers,
batch_first=True, bidirectional=bidirectional, dropout=dropout_p)
#함수 작동시 정리
def forward(self, input_var, input_lengths=None):
input_var = input_var.unsqueeze(1)
x = self.conv(input_var)
""" 몇번의 x의 행렬 작업"""
if self.training:
self.rnn.flatten_parameters()
output, hidden = self.rnn(x)
return output, hidden

 

1. Conv2d: Convolution 작업(filter를 통해 곱하고, activation function을 거치는 작업)을 하는 레이어

  • input_channel : 1 < 초기 channel 개수
  • output_channel : 32 < convolution에 의해 생성된 Channel의 수
  • kernel_size : (41,11) < filter 사이즈
  • stride : (2,2) < filter 이후 뛰는 걸음
  • padding : (20,5)

 

2. BatchNorm2d: Batch Normalization 작업을 하는 레이어

  • num_features : 32 < 위의 output_channel과 연결되어 batch normalization작업을 수행한다.

 

3. Hardtanh: 여기서는 활성함수 역할을 하는 레이어

hardtanh 정의

  • min_val : 0 < -1 대신 들어가는 값
  • max_val : 20 < 1 대신 들어가는 값
  • inplace : True < 선택적으로 작업할 수 있도록 만들어놓기 기본 : False

 

 

Decoder 함수 엿보기

 

#use_teacher_forcing을 한다면
def forward_step(self, input_var, hidden, encoder_outputs, function):
batch_size = input_var.size(0)
output_size = input_var.size(1)
embedded = self.embedding(input_var)
embedded = self.input_dropout(embedded)
if self.training:
self.rnn.flatten_parameters()
output, hidden = self.rnn(embedded, hidden)
attn = None
if self.use_attention:
output, attn = self.attention(output, encoder_outputs)
predicted_softmax = function(self.out(output.contiguous().view(-1, self.hidden_size)), dim=1).view(batch_size, output_size, -1)
return predicted_softmax, hidden, attn
#decoder 시작 전 초기 실행 부분
def forward(self, inputs=None, encoder_hidden=None, encoder_outputs=None,
function=F.log_softmax, teacher_forcing_ratio=0):
ret_dict = dict() # attention_score
if self.use_attention:
ret_dict[DecoderRNN.KEY_ATTN_SCORE] = list()
inputs, batch_size, max_length = self._validate_args(inputs, encoder_hidden, encoder_outputs,
function, teacher_forcing_ratio)
decoder_hidden = self._init_state(encoder_hidden)
use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
decoder_outputs = []
sequence_symbols = []
lengths = np.array([max_length] * batch_size)
#decode 실행 부분
def decode(step, step_output, step_attn): #decode 실행 전 함수 실행 부분: attention여부에 따른 decoder_output과의
decoder_outputs.append(step_output)
if self.use_attention:
ret_dict[DecoderRNN.KEY_ATTN_SCORE].append(step_attn)
symbols = decoder_outputs[-1].topk(1)[1] # longtensor
sequence_symbols.append(symbols)
eos_batches = symbols.data.eq(self.eos_id)
if eos_batches.dim() > 0:
eos_batches = eos_batches.cpu().view(-1).numpy()
update_idx = ((lengths > step) & eos_batches) != 0
lengths[update_idx] = len(sequence_symbols)
return symbols
# 실제실행부분! = 여기서부터 보시면서 흐름 따라 함수 찾아가시면서 보시면 됩니다.
if use_teacher_forcing:
decoder_input = inputs[:, :-1]
decoder_output, decoder_hidden, attn = self.forward_step(decoder_input, decoder_hidden, encoder_outputs,
function=function)
for di in range(decoder_output.size(1)): #seq_len
step_output = decoder_output[:, di, :]
#attention에 따라서 다르게 설정
if attn is not None:
step_attn = attn[:, di, :]
else:
step_attn = None
decode(di, step_output, step_attn)
else:
decoder_input = inputs[:, 0].unsqueeze(1)
for di in range(max_length):
decoder_output, decoder_hidden, step_attn = self.forward_step(decoder_input, decoder_hidden, encoder_outputs,
function=function)
step_output = decoder_output.squeeze(1)
symbols = decode(di, step_output, step_attn)
decoder_input = symbols
ret_dict[DecoderRNN.KEY_SEQUENCE] = sequence_symbols
ret_dict[DecoderRNN.KEY_LENGTH] = lengths.tolist()
return decoder_outputs, decoder_hidden, ret_dict
view raw decoderRNN.py hosted with ❤ by GitHub

Attention 이론에 따라 정의된 이 DecoderRNN부분은 총 개의 파라미터에 따른 다양한 결과값을 내보일 수 있습니다.

use_teacher_forcing 여부, attention 여부에 따른 실행을 보여주는 것을 볼 수 있는데요,

 

1. use_teacher_forcing을 이용한다면, forward_step 부분이 일어나게 됩니다.(teacher_forcing이 자체적으로 일어나서 output 값과 예상 output 간의 작용이 일어나는 함수, 가볍게 보시면 될 것 같습니다.)

 

2. attention을 이용한다면, step_attn 값에 따라 나뉘게 되는데요 이는 decode부분시에 영향을 미치게 됩니다.

 

 

이와 같이 Encoder와 이의 output에 따라 Decoder와 연결되어서 하나의 output을 창출해냄을 알 수 있습니다.