自然語言處理N天-從seq2seq到Transformer01

新建 Microsoft PowerPoint 演示文稿 (2).jpg

這個算是在課程學(xué)習之外的探索,不過希望能盡快用到項目實踐中。在文章里會引用較多的博客,文末會進行reference。
搜索Transformer機制,會發(fā)現(xiàn)高分結(jié)果基本上都源于一篇論文Jay Alammar的《The Illustrated Transformer》(圖解Transformer),提到最多的Attention是Google的《Attention Is All You Need》。

  • 對于Transformer的運行機制了解即可,所以會基于這篇論文來學(xué)習Transformer,結(jié)合《Sklearn+Tensorflow》中Attention注意力機制一章完成基本的概念學(xué)習;
  • 找一個基于Transformer的項目練手

0.引言

Transformer由論文《Attention is All You Need》提出。Transformer和傳統(tǒng)RNN的主要區(qū)別
1.傳統(tǒng)RNN是通過不斷循環(huán)完成學(xué)習,通過每次迭代后的輸出實現(xiàn)對上下文的記憶功能,這樣才有了LSTM和GRU模型,因為它們能夠較好處理RNN梯度爆炸和梯度消失問題,通過對各類門的操作實現(xiàn)“記憶”。但是問題是RNN的訓(xùn)練非常緩慢,之前實現(xiàn)作詩軟件的那篇文章,一個2層的LSTM我用I7處理器跑了將近8個小時……
2.Transformer不需要循環(huán),通過構(gòu)建self-attention機制來完成對上下文和距離較遠詞匯的結(jié)合,并且通過并行進行處理,讓每個單詞在多個處理步驟中注意到句子中的其他單詞,Transformer 的訓(xùn)練速度比 RNN 快很多,而且其翻譯結(jié)果也比 RNN 好得多。但是在處理小型結(jié)構(gòu)化的語言理解任務(wù)或是簡單算法任務(wù)時表現(xiàn)不如傳統(tǒng)模型。

現(xiàn)在Transformer已經(jīng)被擴展為一個通用模型

研究者將該模型建立在 Transformer 的并行結(jié)構(gòu)上,以保持其快速的訓(xùn)練速度。但是他們用單一、時間并行循環(huán)的變換函數(shù)的多次應(yīng)用代替了 Transformer 中不同變換函數(shù)的固定堆疊(即,相同的學(xué)習變換函數(shù)在多個處理步驟中被并行應(yīng)用于所有符號,其中每個步驟的輸出饋入下一個)。RNN 逐個符號(從左到右)處理序列,而 Universal Transformer 同時處理所有符號(像 Transformer 一樣),然后使用自注意力機制在循環(huán)處理步驟(步驟數(shù)量可變)上,對每個符號的解釋進行改進。這種時間并行循環(huán)機制比 RNN 中使用的順序循環(huán)(serial recurrence)更快,也使得 Universal Transformer 比標準前饋 Transformer 更強大。

Transformer模型能做什么?
目前最火的那個帖子認為Transformer已經(jīng)可以完成大多數(shù)的任務(wù)。這個也是這幾天我要探究的。

1.從Encoder到Decoder實現(xiàn)Seq2Seq模型

本節(jié)主要記錄了seq2seq模型基本概念和encoder和decoder層的構(gòu)建
本文來源《從Encoder到Decoder實現(xiàn)Seq2Seq模型》
采用seq2seq框架來實現(xiàn)MT(機器翻譯)現(xiàn)在已經(jīng)是一個非常熱點的研究方向,各種花式設(shè)計歸根離不開RNN、CNN同時輔以attention。但是正如上文所說的,對于NLP來講不論是RNN還是CNN都存在其固有的缺陷,即使attention可以緩解長距依賴的問題。
因此我覺得可以先從seq2seq開始,在這里觀察一個簡單的Seq2Seq,使用TensorFlow來實現(xiàn)一個基礎(chǔ)版本的Seq2Seq,主要幫助理解Seq2Seq中的基礎(chǔ)架構(gòu)。

最基礎(chǔ)的Seq2Seq模型包含三個部分:Encoder、Decoder以及連接兩者固定大小的State Vector。
Encoder通過學(xué)習輸入,將其編碼成一個固定大小的狀態(tài)向量S,繼而將S傳給Decoder,Decoder再通過對狀態(tài)向量S的學(xué)習來進行輸出。
下面利用TensorFlow來構(gòu)建一個基礎(chǔ)的Seq2Seq模型,通過向我們的模型輸入一個單詞(字母序列),例如hello,模型將按照字母順序排序輸出,即輸出ehllo。

1)數(shù)據(jù)集

數(shù)據(jù)集包括兩個部分,sorce_data和target_data,放置在datasets/seq2seqdata文件夾下

  • sorce_data:每一行是一個單詞
  • target_data:: 每一行是經(jīng)過字母排序后的“單詞”,它的每一行與source_data中每一行一一對應(yīng)

2)讀取數(shù)據(jù)和預(yù)處理

在神經(jīng)網(wǎng)絡(luò)中,對于文本的數(shù)據(jù)預(yù)處理無非是將文本轉(zhuǎn)化為模型可理解的數(shù)字。
在這里加入以下四種字符

  • <PAD>主要用來進行字符補全
  • <EOS>和<GO>都是用在Decoder端的序列中,告訴解碼器句子的起始與結(jié)束
  • <UNK>則用來替代一些未出現(xiàn)過的詞或者低頻詞。
import numpy as np
import time
import tensorflow as tf

with open(r'C:\\Users\\01\\Desktop\\機器學(xué)習作業(yè)\\sklearn+tensorflow\\datasets\\seq2seqdata\\letters_source.txt', 'r',
          encoding='utf-8') as f:
    source_data = f.read()
with open(r'C:\\Users\\01\\Desktop\\機器學(xué)習作業(yè)\\sklearn+tensorflow\\datasets\\seq2seqdata\\letters_target.txt', 'r',
          encoding='utf-8') as f:
    target_data = f.read()

print(source_data.split('\\n')[:10])
print(target_data.split('\\n')[:10])


def extract_character_vocab(data):
    special_words = ['<PAD>', '<UNK>', '<GO>', '<EOS>']
    set_words = list(set([character for line in data.split('\n') for character in line]))
    int_to_vocab = {idx: word for idx, word in enumerate(special_words + set_words)}
    vocab_to_int = {word: idx for idx, word in int_to_vocab.items()}

    return int_to_vocab, vocab_to_int


# 構(gòu)造映射表,就是將source和target的每一個字母轉(zhuǎn)為數(shù)值
source_int_to_letter, source_letter_to_int = extract_character_vocab(source_data)
target_int_to_letter, target_letter_to_int = extract_character_vocab(target_data)

source_int = [[source_letter_to_int.get(letter, source_letter_to_int['<UNK>']) for letter in line] for line in
              source_data.split('\n')]
target_int = [[target_letter_to_int.get(letter, target_letter_to_int['<UNK>']) for letter in line] for line in
              target_data.split('\n')]

print(source_int[:10])
print(target_int[:10])

3)模型構(gòu)建

如上文所述,模型包括兩個部分,Encoder和Decoder。
在Encoder層,首先需要對定義輸入的tensor,同時要對字母進行Embedding,再輸入到RNN層。使用TensorFlow中的tf.contrib.layers.embed_sequence來對輸入進行embedding。

輸入層

def get_inputs():
    '''
    模型輸入tensor
    '''
    inputs = tf.placeholder(tf.int32, [None, None], name='inputs')
    targets = tf.placeholder(tf.int32, [None, None], name='targets')
    learning_rate = tf.placeholder(tf.float32, name='learning_rate')
    
    # 定義target序列最大長度(之后target_sequence_length和source_sequence_length會作為feed_dict的參數(shù))
    target_sequence_length = tf.placeholder(tf.int32, (None,), name='target_sequence_length')
    max_target_sequence_length = tf.reduce_max(target_sequence_length, name='max_target_len')
    source_sequence_length = tf.placeholder(tf.int32, (None,), name='source_sequence_length')
    
    return inputs, targets, learning_rate, target_sequence_length, max_target_sequence_length, source_sequence_length

Encoder
參數(shù)說明:

  • input_data: 輸入tensor
  • rnn_size: rnn隱層結(jié)點數(shù)量
  • num_layers: 堆疊的rnn cell數(shù)量
  • source_sequence_length: 源數(shù)據(jù)的序列長度
  • source_vocab_size: 源數(shù)據(jù)的詞典大小
  • encoding_embedding_size: embedding的大小
def get_encoder_layer(input_data, rnn_size, num_layers,
                      source_sequence_length, source_vocab_size,
                      encoding_embedding_size):
    encoder_embed_input = tf.contrib.layers.embed_sequence(input_data, source_vocab_size, encoding_embedding_size)

    # RNN cell
    def get_lstm_cell(rnn_size):
        lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
        return lstm_cell

    cell = tf.contrib.rnn.MultiRNNCell([get_lstm_cell(rnn_size) for _ in range(num_layers)])
    encoder_output, encoder_state = tf.nn.dynamic_rnn(cell, encoder_embed_input, sequence_length=source_sequence_length,
                                                      dtype=tf.float32)

    return encoder_output, encoder_state

Decoder
在Decoder端主要要完成以下幾件事情:

  • 對target數(shù)據(jù)進行處理
  • 構(gòu)造Decoder
    • Embedding
    • 構(gòu)造Decoder
    • 構(gòu)造輸出層,輸出層會告訴我們每個時間序列的RNN輸出結(jié)果
    • 訓(xùn)練Decoder
    • 預(yù)測Decoder

target數(shù)據(jù)進行處理
target數(shù)據(jù)有兩個作用:

  • 在訓(xùn)練過程中,需要將的target序列作為輸入傳給Decoder端RNN的每個階段,而不是使用前一階段預(yù)測輸出,這樣會使得模型更加準確。(這就是為什么構(gòu)建Training和Predicting兩個Decoder的原因,下面還會有對這部分的解釋)。
  • 需要用target數(shù)據(jù)來計算模型的loss。

對target數(shù)據(jù)的處理時要將每一行最后一個標記處理掉,同時在每一行第一個元素前添加起始標記。"h,o,w,<EOS>"->"<go>,h,o,w"。
使用tf.strided_slice()來進行這一步處理。

def process_decoder_input(data, vocab_to_int, batch_size):
    ending = tf.strided_slice(data, [0, 0], [batch_size, -1], [1, 1])
    decoder_input = tf.concat([tf.fill([batch_size, 1], vocab_to_int['<GO>']), ending], 1)

    return decoder_input

構(gòu)造Decoder
注意,這里將decoder分為了training和predicting,這兩個encoder實際上是共享參數(shù)的,也就是通過training decoder學(xué)得的參數(shù),predicting會拿來進行預(yù)測。那么為什么我們要分兩個呢,這里主要考慮模型的robust(魯棒性)。
在training階段,為了能夠讓模型更加準確,我們并不會把t-1的預(yù)測輸出作為t階段的輸入,而是直接使用target data中序列的元素輸入到Encoder中。而在predict階段,我們沒有target data,有的只是t-1階段的輸出和隱層狀態(tài)。
在training過程中,不會把每個階段的預(yù)測輸出作為下一階段的輸入,下一階段的輸入我們會直接使用target data,這樣能夠保證模型更加準確。
代碼中做了一些更新,另外好像batch_size參數(shù)丟失了……

def decoding_layer(target_letter_to_int, decoding_embedding_size, num_layers, rnn_size,
                   target_sequence_length, max_target_sequence_length, encoder_state, decoder_input,batch_size):
    '''
    構(gòu)造Decoder層

    參數(shù):
    - target_letter_to_int: target數(shù)據(jù)的映射表
    - decoding_embedding_size: embed向量大小
    - num_layers: 堆疊的RNN單元數(shù)量
    - rnn_size: RNN單元的隱層結(jié)點數(shù)量
    - target_sequence_length: target數(shù)據(jù)序列長度
    - max_target_sequence_length: target數(shù)據(jù)序列最大長度
    - encoder_state: encoder端編碼的狀態(tài)向量
    - decoder_input: decoder端輸入
    '''
    # 1. Embedding
    target_vocab_size = len(target_letter_to_int)
    decoder_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))
    decoder_embed_input = tf.nn.embedding_lookup(decoder_embeddings, decoder_input)

    # 2. 構(gòu)造Decoder中的RNN單元
    def get_decoder_cell(rnn_size):
        decoder_cell = tf.nn.rnn_cell.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
        return decoder_cell

    cell = tf.nn.rnn_cell.MultiRNNCell([get_decoder_cell(rnn_size) for _ in range(num_layers)])

    # 3. Output全連接層
    output_layer = Dense(target_vocab_size, kernel_initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1))

    # 4. Training decoder
    with tf.variable_scope("decode"):
        # 得到help對象
        training_helper = tf.contrib.seq2seq.TrainingHelper(inputs=decoder_embed_input,
                                                            sequence_length=target_sequence_length,
                                                            time_major=False)
        # 構(gòu)造decoder
        training_decoder = tf.contrib.seq2seq.BasicDecoder(cell,
                                                           training_helper,
                                                           encoder_state,
                                                           output_layer)
        training_decoder_output, _ = tf.contrib.seq2seq.dynamic_decode(training_decoder,
                                                                       impute_finished=True,
                                                                       maximum_iterations=max_target_sequence_length)
    # 5. Predicting decoder
    # 與training共享參數(shù)
    with tf.variable_scope("decode", reuse=True):
        # 創(chuàng)建一個常量tensor并復(fù)制為batch_size的大小
        start_tokens = tf.tile(tf.constant([target_letter_to_int['<GO>']], dtype=tf.int32), [batch_size],
                               name='start_tokens')
        predicting_helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(decoder_embeddings,
                                                                     start_tokens,
                                                                     target_letter_to_int['<EOS>'])
        predicting_decoder = tf.contrib.seq2seq.BasicDecoder(cell,
                                                             predicting_helper,
                                                             encoder_state,
                                                             output_layer)
        predicting_decoder_output, _ = tf.contrib.seq2seq.dynamic_decode(predicting_decoder,
                                                                         impute_finished=True,
                                                                         maximum_iterations=max_target_sequence_length)

    return training_decoder_output, predicting_decoder_output

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容