본문 바로가기
machine learning

[TENSORFLOW] seq2seq 기반 챗봇 만들기

by 유주원 2017. 7. 14.

2017/07/12 - [machine learning] - [TENSORFLOW] LSTM Dual encoder 기반 챗봇 만들기


지난 포스팅에 검색 기반 챗봇을 구현했다면 이번에는 generative model의 대표격인 seq2seq를 활용해서 챗봇을 만들어 보자.


seq2seq의 대표적인 구조이다. encoder 부분에서는 입력 응답을 받아 하나의 hidden code 값으로 표현을 해주고 decoder 영역에서는 hidden code 값과 start tag를 받아 가장 적합한 결과 단어들을 추출해 준다. 여기서 train과 test의 모델이 각각 다르게 나타나는데, train의 경우에는 decoder의 output과는 별개로 훈련 셋이 input으로 들어가는데 반해 test 모델의 경우에는 decoder의 output이 다시 input으로 들어가게 된다. 즉 아래의 그림과 같다.


TRAIN 경우에는 올바른 결과 값을 예상할 수 없기 때문에 이전 output을 feed로 받지 않는 게 맞는 설명이며, TEST 시에는 응답 결과 셋이 없기 때문에 이전 output으로 feed를 받아야 한다.


seq2seq 소스를 보자.

  


__graph__() 함수 부분을 보게 되면 우선 tf.reset_default_graph()를 통해 그래프 초기화를 시켜준다. 이 후에 tf 변수 선언을 해준다. 

self.training의 경우 batch-normalization을 위해 선언한 변수이며, test 시에는 해당 변수 값이 false가 되고 training 시에는 true로 설정이 된다.

self.val_ip의 경우 validation에 대한 input을 받기 위한 변수이며, enc_ip는 train input data를 받기 위한 변수이다. self.label은 정답 응답을 받기 위한 변수이고, self.dec_ip는 self.label에 'GO' 라는 decoder 시작 변수가 추가된 decoder input이라고 보면된다. self.keep_prob는 dropout 확률 값을 나타낸다. 해당 확률이 1에 가까워질수록 dropout이 이루어지지 않는다고 생각하면 된다.


변수 선언이 끝났다면 아래와 같이 rnn에 들어갈 기본 cell을 만들어 주자.


basic_cell = tf.contrib.rnn.core_rnn_cell.DropoutWrapper(BN_LSTMCell(emb_dim, self.training), output_keep_prob=0.5)


해당 cell은 dropout과 더불어 batch_normalization도 함께 적용했다. 대부분의 경우 dropout과 batch_normalization을 함께 안쓰지만, 같이 쓸 경우 task에 따라 효율이 더 좋을수도 더 나빠질수도 아니면 비슷할 수도 있다. 

나 같은 경우에는 dropout과 bn을 함께 쓰니 더 수렴이 빨라지게 되어서 함께 사용했다.

dropout과 bn을 함께 쓸 경우 순서에 유의해야 하는데, bn을 먼저 쓰고 그다음에 dropout을 적용해야 한다. activation function이 있을 경우에는 bn -> activation function -> dropout을 적용하면 된다.


https://stackoverflow.com/questions/39691902/ordering-of-batch-normalization-and-dropout-in-tensorflow#answer-40295999


또한 현재 나와 있는 TF의 경우 따로 bn이 적용되어 있는 LSTM Cell이 없기 때문에 아래의 github source를 customizing 해서 BN_LSTM을 구현했다.


https://github.com/tam17aki/recurrent-batchnorm-tensorflow/blob/master/BN_LSTMCell.py


cell 정의 이후에는 cell의 layer를 늘려주자.


stacked_lstm = tf.contrib.rnn.core_rnn_cell.MultiRNNCell([basic_cell]*num_layers, state_is_tuple=True)


이제 seq2seq 모델을 구성해 보자. 아까 위에서 언급했던 바와 같이 train과 test의 모델을 각각 다르게 구성해야 한다.


self.decode_outputs, self.decode_states = tf.contrib.legacy_seq2seq.embedding_rnn_seq2seq(self.enc_ip,                                 self.dec_ip, stacked_lstm, xvocab_size, yvocab_size, emb_dim)


scope.reuse_variables()

self.decode_outputs_test, self.decode_states_test = tf.contrib.legacy_seq2seq.embedding_rnn_seq2seq(                             self.enc_ip, self.dec_ip, stacked_lstm, xvocab_size, yvocab_size, emb_dim,                             feed_previous=True)


TF에서는 feed_previous의 True, False 여부에 따라 decoder output을 입력으로 받는지의 여부를 설정할 수가 있다. (default는 False)


sequence_loss를 통해 cost 함수를 정의하자.


self.loss = tf.contrib.legacy_seq2seq.sequence_loss(self.decode_outputs, self.labels, loss_weights, yvocab_size)


sequence_loss를 TF 공식 홈페이지에서 찾아보면 Weighted cross entropy loss라고 설명 되어 있다. cross entropy를 계산하긴 하는데 weight를 masking으로 줘서 패딩된 값은 반영시키지 않기 위한 것으로 해석하면 될 것 같다. 유효한 timestamp 값의 경우에는 weight를 1로 주고 padding 된 timestamp에는 weight를 0으로 마스킹 해주면 올바른 cross entropy 값을 리턴할 것이다.


마지막으로 gradient optimizer를 adam optimizer를 사용했다.


self.train_op = tf.train.AdamOptimizer(learning_rate=lr).minimize(self.loss)


생성된 모델에 대한 훈련 및 예측과 관련된 code 및 seq2seq의 전체 코드는 아래의 github에 나타나 있다.
(모든 코드를 설명하기엔 너무 벅차서.. neural network model만 설명하는 걸로...)


https://github.com/suriyadeepan/practical_seq2seq