TensorFlow實現seq2seq

超人汪小建發表於2019-02-28

前言

前面在《深度學習的seq2seq模型》文章中已經介紹了seq2seq結構及其原理,接下去這篇文章將嘗試使用TensorFlow來實現一個seq2seq網路結構,該例子能通過訓練給定的訓練集實現輸入某個序列輸出某個序列,其中輸入序列和輸出序列相同,這裡選擇使用LSTM模型。

訓練樣本集

為方便起見這裡使用隨機生成的序列作為樣本,序列的長度也是隨機的且在指定的範圍內。

LSTM機制原理

關於LSTM機制原理可看之前的文章《LSTM神經網路》

隨機序列生成器

def random_sequences(length_from, length_to, vocab_lower, vocab_upper, batch_size):
    def random_length():
        if length_from == length_to:
            return length_from
        return np.random.randint(length_from, length_to + 1)

    while True:
        yield [
            np.random.randint(low=vocab_lower, high=vocab_upper, size=random_length()).tolist()
            for _ in range(batch_size)
            ]複製程式碼

構建一個隨機序列生成器方便後面生成序列,其中 length_from 和 length_to表示序列的長度範圍從多少到多少,vocab_lower 和 vocab_upper 表示生成的序列值的範圍從多少到多少,batch_size 即是批的數量。

填充序列

def make_batch(inputs, max_sequence_length=None):
    sequence_lengths = [len(seq) for seq in inputs]
    batch_size = len(inputs)
    if max_sequence_length is None:
        max_sequence_length = max(sequence_lengths)
    inputs_batch_major = np.zeros(shape=[batch_size, max_sequence_length], dtype=np.int32)
    for i, seq in enumerate(inputs):
        for j, element in enumerate(seq):
            inputs_batch_major[i, j] = element
    inputs_time_major = inputs_batch_major.swapaxes(0, 1)
    return inputs_time_major, sequence_lengths複製程式碼

生成的隨機序列的長度是不一樣的,需要對短的序列用來填充,而可設為0,取最長的序列作為每個序列的長度,不足的填充,然後再轉換成time major形式。

構建圖

encoder_inputs = tf.placeholder(shape=(None, None), dtype=tf.int32, name='encoder_inputs')
ecoder_inputs = tf.placeholder(shape=(None, None), dtype=tf.int32, name='decoder_inputs')
decoder_targets = tf.placeholder(shape=(None, None), dtype=tf.int32, name='decoder_targets')複製程式碼

建立三個佔位符,分別為encoder的輸入佔位符、decoder的輸入佔位符和decoder的target佔位符。

embeddings = tf.Variable(tf.random_uniform([vocab_size, input_embedding_size], -1.0, 1.0), dtype=tf.float32)
encoder_inputs_embedded = tf.nn.embedding_lookup(embeddings, encoder_inputs)
decoder_inputs_embedded = tf.nn.embedding_lookup(embeddings, decoder_inputs)複製程式碼

將encoder和decoder的輸入做一個嵌入操作,對於大詞彙量這個能達到降維的效果,嵌入操作也是很常用的方式了。在seq2seq模型中,encoder和decoder都是共用一個嵌入層即可。嵌入層的向量形狀為[vocab_size, input_embedding_size],初始值從-1到1,後面訓練會自動調整。

encoder_cell = tf.contrib.rnn.LSTMCell(encoder_hidden_units)
encoder_outputs, encoder_final_state = tf.nn.dynamic_rnn(
        encoder_cell, encoder_inputs_embedded,
        dtype=tf.float32, time_major=True,
    )
decoder_cell = tf.contrib.rnn.LSTMCell(decoder_hidden_units)
decoder_outputs, decoder_final_state = tf.nn.dynamic_rnn(
        decoder_cell, decoder_inputs_embedded,
        initial_state=encoder_final_state,
        dtype=tf.float32, time_major=True, scope="plain_decoder",
    )複製程式碼

建立encoder和decoder的LSTM神經網路,encoder_hidden_units 為LSTM隱層數量,設定輸入格式為time major格式。這裡我們不關心encoder的迴圈神經網路的輸出,我們要的是它的最終狀態encoder_final_state,將其作為decoder的迴圈神經網路的初始狀態。

decoder_logits = tf.contrib.layers.linear(decoder_outputs, vocab_size)
decoder_prediction = tf.argmax(decoder_logits, 2)
stepwise_cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
        labels=tf.one_hot(decoder_targets, depth=vocab_size, dtype=tf.float32),
        logits=decoder_logits,
    )
loss = tf.reduce_mean(stepwise_cross_entropy)
train_op = tf.train.AdamOptimizer().minimize(loss)複製程式碼

對於decoder的迴圈神經網路的輸出,因為我們要一個分類結果,所以需要一個全連線神經網路,輸出層神經元數量是詞彙的數量。輸出層最大值對應的神經元即為預測的類別。輸出層的啟用函式用softmax,損失函式用交叉熵損失函式。

建立會話

with tf.Session(graph=train_graph) as sess:
    sess.run(tf.global_variables_initializer())
    for epoch in range(epochs):
        batch = next(batches)
        encoder_inputs_, _ = make_batch(batch)
        decoder_targets_, _ = make_batch([(sequence) + [EOS] for sequence in batch])
        decoder_inputs_, _ = make_batch([[EOS] + (sequence) for sequence in batch])
        feed_dict = {encoder_inputs: encoder_inputs_, decoder_inputs: decoder_inputs_,
                     decoder_targets: decoder_targets_,
                     }
        _, l = sess.run([train_op, loss], feed_dict)
        loss_track.append(l)
        if epoch == 0 or epoch % 1000 == 0:
            print('loss: {}'.format(sess.run(loss, feed_dict)))
            predict_ = sess.run(decoder_prediction, feed_dict)
            for i, (inp, pred) in enumerate(zip(feed_dict[encoder_inputs].T, predict_.T)):
                print('input > {}'.format(inp))
                print('predicted > {}'.format(pred))
                if i >= 20:
                    break複製程式碼

建立會話開始執行,每次生成一批數量,用 make_batch 分別建立encoder輸入、decoder的target和decoder的輸入。其中target需要在後面加上[EOS],它表示句子的結尾,同時輸入也加上[EOS]表示編碼開始。每訓練1000詞輸出看看效果。

這裡寫圖片描述
這裡寫圖片描述

github

github.com/sea-boat/De…

========廣告時間========

鄙人的新書《Tomcat核心設計剖析》已經在京東銷售了,有需要的朋友可以到 item.jd.com/12185360.ht… 進行預定。感謝各位朋友。

為什麼寫《Tomcat核心設計剖析》

=========================

歡迎關注:

這裡寫圖片描述
這裡寫圖片描述

相關文章