*이 글은 네이버 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: 여기서는 활성함수 역할을 하는 레이어

- 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 |
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을 창출해냄을 알 수 있습니다.
'AI, Deep Learning Basics > NLP' 카테고리의 다른 글
[CS182 Sergey Levine] Deep Learning - NLP Basics (0) | 2022.04.19 |
---|---|
[NLP] 4. Modern Recurrent Neural Networks: Seq2Seq (0) | 2022.02.12 |
[NLP] 3. Modern Recurrent Neural Networks: GRU, LSTM (0) | 2022.02.05 |
[NLP] 2. RNN Basics: Language Model (0) | 2022.02.01 |
[NLP] 1. Introduction of NLP, Word2vec (0) | 2022.01.22 |