Seq2Seq( Attention )을 구현한 후 몇가지 기록 - beyondnlp/nlp GitHub Wiki

가상 태그의 설정

  • PAD는 항상 0을 부여해야 한다.
  • SOS( Start of Sequence ), EOS( End of Sequence ), PAD( Padding )
PAD = '<PAD>' # 0
SOS = '<SOS>' # 1
EOS = '<EOS>' # 2
UNK = '<UNK>' # 3

LSTM기반 Encoder Decoder를 구현

  • Encoder
self.enc_cell = tf.nn.rnn_cell.BasicLSTMCell(self.n_hidden_size)
self.enc_outputs, self.enc_states = tf.nn.dynamic_rnn( self.enc_cell, self.enc_embed, sequence_length=self.seq_length, dtype=tf.float32, time_major=False )
  • time_major=True

    • False이면 (batch, sequence_len, hidden)으로 output이 출력되지만
    • True이면 (sequence_len, batch, hidden)으로 output이 출력된다.
  • Decoder

    • encoder의 마지막 state를 decoder의 initial_state에 넣는다.
    • initial_state=self.enc_states
self.dec_cell = tf.nn.rnn_cell.BasicLSTMCell(self.n_hidden_size)
self.dec_outputs, self.dec_states = tf.nn.dynamic_rnn( self.dec_cell, self.dec_embed, initial_state=self.enc_states, dtype=tf.float32 )

정확률 계산을 좀더 정밀하게

  • boolean_mask를 사용하면 padding으로 추가된 값을 loss계산시에 제외할수 있다.
self.mask = tf.sequence_mask( self.y_seq_len, n_dec_size );
self.losses = tf.nn.sparse_softmax_cross_entropy_with_logits( logits=self.logits, labels=self.targets )
self.t_loss =  tf.boolean_mask( self.losses, self.mask )
self.loss = tf.reduce_mean( tf.boolean_mask( self.losses, self.mask )  )


32 = y_sequence_length

21 = max_dec_sequence_len

  • self.mask = tf.sequence_mask( self.y_seq_len, n_dec_size );
  • mask (32, 21)
    • mask[0] [ True, True, True, True, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False],
    • mask[1] [ True, True, True, True, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False],

  • self.losses = tf.nn.sparse_softmax_cross_entropy_with_logits( logits=self.logits, labels=self.targets )
  • losses (32, 21)
    • losses[0] [6.8173747, 7.4007316, 6.988785 , 6.3120685, 6.917534 , 6.7973247, 6.84659 , 6.600436 , 6.8238516, 6.9839787, 6.6751084, 6.7640924, 7.0160074, 7.0156994, 6.9426084, 7.3471165, 7.0573063, 6.987302 , 6.7571087, 7.3548436, 6.864047 ],
    • losses[1] [7.169547 , 6.755968 , 7.0384545, 6.77727 , 6.6281867, 6.7585135, 7.415036 , 6.9975333, 7.2876287, 7.229257 , 6.7298517, 6.8102727, 7.026598 , 6.909583 , 6.7665877, 6.881338 , 6.890062 , 6.583917 , 6.9843636, 7.0610375, 6.8578653],

  • self.t_loss = tf.boolean_mask( self.losses, self.mask )
  • t_loss = (159,)
    • [6.8173747, 7.4007316, 6.988785 , 6.3120685, 6.917534 , 7.169547 , 6.755968 , 7.0384545, 6.77727 , 6.6281867, 7.213619 , 6.923578 , 7.0519066, 7.083853 , 7.024545 , 6.8232136, 6.908285 , 6.637931 , 6.819456 , 6.771316 , 7.1317782, 6.921268 , 7.253817 , 6.56936 , 6.913421 , 7.0648794, 7.0934806, 6.751289 , 6.789165 , 6.755739 , 6.727824 , 7.103774 , 7.226494 , 6.9285526, 7.0426645, 6.7115536, 6.845505 , 6.801377 , 6.637927 , 7.0597496, 6.83748 , 6.6499853, 6.817974 , 7.081154 , 6.9284043, 7.1479793, 7.223509 , 6.787448 , 7.13276 , 7.026015 , 7.093263 , 7.112106 , 7.0462995, 6.808757 , 6.607045 , 6.9755125, 6.9699683, 7.0385714, 6.781694 , 7.2062907, 7.023663 , 7.6441007, 6.7315497, 7.1810374, 7.2022085, 7.501133 , 7.127009 , 7.269984 , 7.119417 , 6.7381873, 6.7896004, 6.663507 , 6.841545 , 7.3113337, 6.839011 , 7.105628 , 6.9823346, 6.932164 , 7.1903267, 6.783184 , 7.0292788, 6.996821 , 6.74176 , 7.174912 , 6.998249 , 6.723664 , 7.0585694, 7.1614695, 6.8053875, 6.909032 , 6.994315 , 6.909402 , 7.003597 , 6.9943156, 6.8057246, 6.816606 , 7.447066 , 6.8413815, 6.9115314, 6.99496 , 6.513232 , 7.1596613, 6.8285313, 6.832653 , 6.8153653, 6.761712 , 7.1525936, 7.004413 , 6.5550623, 6.946972 , 6.835298 , 6.909828 , 7.419348 , 6.9219933, 8.050915 , 6.806518 , 6.4410353, 6.822584 , 6.966523 , 7.0852294, 6.6869664, 6.415107 , 6.8192387, 7.243931 , 7.321948 , 6.579841 , 6.7486277, 6.7688236, 7.0509653, 6.9008346, 7.0227046, 6.9946136, 6.8568935, 6.856879 , 7.055225 , 7.064804 , 6.4940605, 7.1065044, 7.088332 , 6.962045 , 7.1206155, 6.991754 , 6.8370647, 7.0655675, 6.8310046, 7.072658 , 6.789998 , 6.4119987, 7.0176735, 7.264865 , 7.104518 , 7.20335 , 6.7106094, 7.034429 , 6.8653703, 6.7118626, 7.238209 , 7.2865467, 7.353095 ])

  • self.loss = tf.reduce_mean( tf.boolean_mask( self.losses, self.mask ) )
  • loss = ('loss : ', 6.9543366)

teacher forcing

  • Decoder에서 각 스텝별로 출력되는 값을 argmax를 취해 다음 스텝의 입력으로 사용해야 하지만
  • 학습시에는 다음에 들어갈 단어를 이미 알고 있기 때문에 이 값을 넣어서 학습을 진행할수 있다. 이를 티쳐 포싱이라고 한다.
  • 아래 그림 참고 teacher forcing
  • teacher forcing의 단점 : training code와 inference 코드 두 가지를 작성해야 한다.

bahdanau_attention

  • encoder dynamic_rnn의 출력값 enc_outputs와
  • decoder의 각 스텝별 dynamic_rnn의 출력값 enc_state간의 유사도를 계산다.
    def bahdanau_attention( self, enc_state, enc_outputs ):
        query = enc_state.h             # ( 2,     batch, hidden )
        value = enc_outputs             # ( batch, seq,   hidden )
        query_exp = tf.expand_dims(query, 1) # ( 2,     1,     hidden )
        self.value_weight = tf.layers.dense(value,     self.n_hidden_size, activation=None, reuse=tf.AUTO_REUSE, name='value_weight')
        self.query_weight = tf.layers.dense(query_exp, self.n_hidden_size, activation=None, reuse=tf.AUTO_REUSE, name='query_weight')
        activation = tf.nn.tanh(self.value_weight + self.query_weight)
        attention_score = tf.layers.dense(activation, 1, reuse=tf.AUTO_REUSE, name='attention_score')
        attention_weight= tf.nn.softmax( attention_score )
        self.context = tf.reduce_sum( attention_weight * value, axis=1, name='attention_context' ) # context = [batch, hidden]
        return self.context;

seq2seq에서의 shape 변형

  • 작업을 하다보면 batch, seqlen순서를 seqlen, batch 순으로 바꾸거나 그 반대로 shape을 바꾸는 경우가 종종있다.
  • self.dec_embed = tf.transpose( self.dec_embed, [1, 0, 2] );

gather_nd 사용법

  • idx 번째 column을 가져오는 방법
  • i_dec_embed = tf.gather_nd(self.t_dec_embed, idx)

tf.while_loop 사용법

  • while_loop에서 사용하는 cond와 body는 두개의 입력 파라미터가 동일한다.
  • 그래서 cond에서만 사용한다고 하더라도 body에도 호출시 기술해야 한다.
  • 호출 파라미터는 loop_vars에 리스트 형식으로
  • body함수는 출력이 다음 스탭의 입력으로 사용하기 때문에 입력과 동일한 형태의 변수가 리턴되어야 한다.

변수의 공유

  • 학습시에 사용한 weight를 inference시에 사용하기 위해서는
  • reuse=tf.AUTO_REUSE를 설정해햐 하고
  • 동일한 name을 사용해야 한다.
  • self.logits = tf.layers.dense( self.dec_outputs, self.n_class, activation=None, reuse=tf.AUTO_REUSE, name='output_dense')

seq2seq model

⚠️ **GitHub.com Fallback** ⚠️