本文內容:
- 什么是seq2seq模型
- Encoder-Decoder結構
- 常用的四種結構
- 帶attention的seq2seq
- 模型的輸出
- seq2seq簡單序列生成實現代碼
一、什么是seq2seq模型
seq2seq全稱為:sequence to sequence ,是2014年被提出來的一種Encoder-Decoder結構。其中Encoder是一個RNN結構(LSTM、GRU、RNN等)。
主要思想是輸入一個序列,通過encoder編碼成一個語義向量c(context),然后decoder成輸出序列。這個結構重要的地方在於輸入序列和輸出序列的長度是可變的。
應用場景:機器翻譯、聊天機器人、文檔摘要、圖片描述等
二、Encoder-Decoder結構
最初Encoder-Decoder模型由兩個RNN組成
這個結構可以看到,輸入一個句子后,生成語義向量c,編碼過程比較簡單;
解碼時,每個c、上一時刻的yi-1,以及上一時刻的隱藏層狀態si-1都會作用到cell,然后生成解碼向量。
三、常用的四種seq2seq結構
對於上面模型中的編碼模型,是一種比較常用的方式,將編碼模型最后一個時刻的隱層狀態做為整個序列的編碼表示,但是實際應用中這種效果並不太好。
因此,對於常用的模型中,通常直接采用了整個序列隱層編碼進行求和平均的方式得到序列的編碼向量。因此通常有四種模式:
對於解碼模式:
普通作弊模式
如上,編碼時,RNN的每個時刻除了上一時刻的隱層狀態,還有輸入字符,而解碼器沒有這種字符輸入,用context作為輸入,即為一種比較簡單的模式。
學霸模式
如上是一種帶輸出回饋的方式。輸入即為上一時刻的輸出。
學弱模式
學渣作弊模式
學渣作弊模式就是在學弱的基礎上在引入Attention機制,加強對於編碼輸入的特征的影響。
下面主要梳理帶attention機制的seq2seq模型:
四、帶attention的seq2seq
編碼器如上,公式不再贅述。
注意:對於使用雙向的GRU編碼時,得到的兩個方向上的hi,通常進行contact作為輸入。
對於解碼的過程,可以看到,在語義向量C的求解的過程中,添加了attention。
如上,當計算Y4時,上一時刻解碼的隱層狀態會作用於編碼器的輸入,這樣會從新計算context,過程就是這樣的。公式表示:
其中,i對應的是翻譯的第i個字,j對應的是輸入的第j個字。
其中的aij是一個歸一化的值,歸一化的方法為softmax。其中eij為attention計算的輸出,這么做的原因是因為,本質上這個權值是一個概率值,如果直接用eij的話,context縮放變大。
s為解碼器的隱層狀態,h為編碼器的輸出。
五、模型輸出轉化為語句
GRU的輸出已經包含了待生成的詞的信息了,但是要生成具體的詞,還需要進一步操作。
如上圖,output是一個具體的詞向量,這個詞向量的獲取是通過softmax獲得的所有的語料庫的詞向量的概率最大的那一個詞向量。
而softmax的輸入通常是這個詞典的維度,但這個維度的大小往往和GRU輸出的維度並不對應,這時,通過一個全連接層(Dense_Layer)來做一個維度上的映射。
事實上,softmax可以簡單理解為一個歸一化操作,求的是概率。
六、使用seq2seq做序列生成
說白了,seq2seq就是兩個lstm/GRU嘛,做序列生成的化,並不是一個十分復雜的過程,本文在網上流傳的代碼基礎上進行裁剪,保留最簡單的代碼:
import numpy as np import tensorflow as tf import matplotlib.pyplot as plt import copy vocab_size=256 #假設詞典大小為 256 target_vocab_size=vocab_size LR=0.006 inSize = 10 #outSize = 20 假設輸入輸出句子一樣長 buckets=[(inSize, inSize)] #設置一個桶,主要是為了給model_with_buckets函數用 batch_size=1 input_data = np.arange(inSize) target_data = copy.deepcopy(input_data) np.random.shuffle(target_data) target_weights= ([1.0]*inSize + [0.0]*0) class Seq2Seq(object): def __init__(self, source_vocab_size, target_vocab_size, buckets, size): self.encoder_size, self.decoder_size = buckets[0]#因為只有一個桶,索引為0即可 self.source_vocab_size = source_vocab_size self.target_vocab_size = target_vocab_size cell = tf.contrib.rnn.BasicLSTMCell(size) cell = tf.contrib.rnn.MultiRNNCell([cell]) def seq2seq_f(encoder_inputs, decoder_inputs, do_decode): return tf.contrib.legacy_seq2seq.embedding_attention_seq2seq( encoder_inputs, decoder_inputs, cell, num_encoder_symbols=source_vocab_size, num_decoder_symbols=target_vocab_size, embedding_size=size, feed_previous=do_decode) # computational graph self.encoder_inputs = [] self.decoder_inputs = [] self.target_weights = [] for i in range(self.encoder_size): self.encoder_inputs.append(tf.placeholder(tf.int32, shape=[None], name='encoder{0}'.format(i))) for i in range(self.decoder_size): self.decoder_inputs.append(tf.placeholder(tf.int32, shape=[None], name='decoder{0}'.format(i))) self.target_weights.append(tf.placeholder(tf.float32, shape=[None], name='weights{0}'.format(i))) targets = [self.decoder_inputs[i] for i in range(len(self.decoder_inputs))]# - 1 # 使用seq2seq,輸出維度為seq_length x batch_size x dict_size self.outputs, self.losses = tf.contrib.legacy_seq2seq.model_with_buckets( self.encoder_inputs, self.decoder_inputs, targets, self.target_weights, buckets, lambda x, y: seq2seq_f(x, y, False)) self.getPoints = tf.argmax(self.outputs[0],axis=2)#通過argmax,得到字典中具體的值,因為i只有一個批次,所以取0即可 self.trainOp = tf.train.AdamOptimizer(LR).minimize(self.losses[0]) def step(self, session, encoder_inputs, decoder_inputs, target_weights): input_feed = {} for l in range(self.encoder_size): input_feed[self.encoder_inputs[l].name] = [encoder_inputs[l]] for l in range(self.decoder_size): input_feed[self.decoder_inputs[l].name] = [decoder_inputs[l]] input_feed[self.target_weights[l].name] = [target_weights[l]] output_feed = [self.losses[0],self.getPoints,self.trainOp] outputs = session.run(output_feed, input_feed) return outputs[0], outputs[1] # 訓練 LSTMRNN if __name__ == '__main__': # 搭建 LSTMRNN 模型 model= Seq2Seq(vocab_size, target_vocab_size, buckets, size=5) sess = tf.Session() saver=tf.train.Saver(max_to_keep=3) sess.run(tf.global_variables_initializer()) # matplotlib可視化 plt.ion() # 設置連續 plot plt.show() # 訓練多次 for i in range(100): losses, points= model.step(sess, input_data, target_data, target_weights) x = range(inSize) plt.clf() plt.plot(x, target_data, 'r', x, points, 'b--')# plt.draw() plt.pause(0.3) # 每 0.3 s 刷新一次 # 打印 cost 結果 if i % 20 == 0: saver.save(sess, "model/lstem_text.ckpt",global_step=i)# print(losses)
如上,可以很容易實現輸入一個序列,然后訓練生成另一個序列,效果如圖: