這個(gè)算是在課程學(xué)習(xí)之外的探索晰筛,不過(guò)希望能盡快用到項(xiàng)目實(shí)踐中谨究。在文章里會(huì)引用較多的博客,文末會(huì)進(jìn)行reference夫晌。
搜索Transformer機(jī)制雕薪,會(huì)發(fā)現(xiàn)高分結(jié)果基本上都源于一篇論文Jay Alammar的《The Illustrated Transformer》(圖解Transformer),提到最多的Attention是Google的《Attention Is All You Need》晓淀。
- 對(duì)于Transformer的運(yùn)行機(jī)制了解即可蹦哼,所以會(huì)基于這篇論文來(lái)學(xué)習(xí)Transformer,結(jié)合《Sklearn+Tensorflow》中Attention注意力機(jī)制一章完成基本的概念學(xué)習(xí)要糊;
- 找一個(gè)基于Transformer的項(xiàng)目練手
0.引言
Transformer由論文《Attention is All You Need》提出纲熏。Transformer和傳統(tǒng)RNN的主要區(qū)別
1.傳統(tǒng)RNN是通過(guò)不斷循環(huán)完成學(xué)習(xí)妆丘,通過(guò)每次迭代后的輸出實(shí)現(xiàn)對(duì)上下文的記憶功能,這樣才有了LSTM和GRU模型局劲,因?yàn)樗鼈兡軌蜉^好處理RNN梯度爆炸和梯度消失問(wèn)題勺拣,通過(guò)對(duì)各類門(mén)的操作實(shí)現(xiàn)“記憶”。但是問(wèn)題是RNN的訓(xùn)練非常緩慢鱼填,之前實(shí)現(xiàn)作詩(shī)軟件的那篇文章药有,一個(gè)2層的LSTM我用I7處理器跑了將近8個(gè)小時(shí)……
2.Transformer不需要循環(huán),通過(guò)構(gòu)建self-attention機(jī)制來(lái)完成對(duì)上下文和距離較遠(yuǎn)詞匯的結(jié)合苹丸,并且通過(guò)并行進(jìn)行處理愤惰,讓每個(gè)單詞在多個(gè)處理步驟中注意到句子中的其他單詞,Transformer 的訓(xùn)練速度比 RNN 快很多赘理,而且其翻譯結(jié)果也比 RNN 好得多宦言。但是在處理小型結(jié)構(gòu)化的語(yǔ)言理解任務(wù)或是簡(jiǎn)單算法任務(wù)時(shí)表現(xiàn)不如傳統(tǒng)模型。
現(xiàn)在Transformer已經(jīng)被擴(kuò)展為一個(gè)通用模型
研究者將該模型建立在 Transformer 的并行結(jié)構(gòu)上商模,以保持其快速的訓(xùn)練速度奠旺。但是他們用單一、時(shí)間并行循環(huán)的變換函數(shù)的多次應(yīng)用代替了 Transformer 中不同變換函數(shù)的固定堆疊(即施流,相同的學(xué)習(xí)變換函數(shù)在多個(gè)處理步驟中被并行應(yīng)用于所有符號(hào)响疚,其中每個(gè)步驟的輸出饋入下一個(gè))。RNN 逐個(gè)符號(hào)(從左到右)處理序列瞪醋,而 Universal Transformer 同時(shí)處理所有符號(hào)(像 Transformer 一樣)忿晕,然后使用自注意力機(jī)制在循環(huán)處理步驟(步驟數(shù)量可變)上,對(duì)每個(gè)符號(hào)的解釋進(jìn)行改進(jìn)银受。這種時(shí)間并行循環(huán)機(jī)制比 RNN 中使用的順序循環(huán)(serial recurrence)更快践盼,也使得 Universal Transformer 比標(biāo)準(zhǔn)前饋 Transformer 更強(qiáng)大。
Transformer模型能做什么蚓土?
目前最火的那個(gè)帖子認(rèn)為T(mén)ransformer已經(jīng)可以完成大多數(shù)的任務(wù)宏侍。這個(gè)也是這幾天我要探究的赖淤。
1.從Encoder到Decoder實(shí)現(xiàn)Seq2Seq模型
本節(jié)主要記錄了seq2seq模型基本概念和encoder和decoder層的構(gòu)建
本文來(lái)源《從Encoder到Decoder實(shí)現(xiàn)Seq2Seq模型》
采用seq2seq框架來(lái)實(shí)現(xiàn)MT(機(jī)器翻譯)現(xiàn)在已經(jīng)是一個(gè)非常熱點(diǎn)的研究方向蜀漆,各種花式設(shè)計(jì)歸根離不開(kāi)RNN、CNN同時(shí)輔以attention咱旱。但是正如上文所說(shuō)的确丢,對(duì)于NLP來(lái)講不論是RNN還是CNN都存在其固有的缺陷,即使attention可以緩解長(zhǎng)距依賴的問(wèn)題吐限。
因此我覺(jué)得可以先從seq2seq開(kāi)始鲜侥,在這里觀察一個(gè)簡(jiǎn)單的Seq2Seq,使用TensorFlow來(lái)實(shí)現(xiàn)一個(gè)基礎(chǔ)版本的Seq2Seq诸典,主要幫助理解Seq2Seq中的基礎(chǔ)架構(gòu)描函。
最基礎(chǔ)的Seq2Seq模型包含三個(gè)部分:Encoder、Decoder以及連接兩者固定大小的State Vector。
Encoder通過(guò)學(xué)習(xí)輸入舀寓,將其編碼成一個(gè)固定大小的狀態(tài)向量S胆数,繼而將S傳給Decoder,Decoder再通過(guò)對(duì)狀態(tài)向量S的學(xué)習(xí)來(lái)進(jìn)行輸出互墓。
下面利用TensorFlow來(lái)構(gòu)建一個(gè)基礎(chǔ)的Seq2Seq模型必尼,通過(guò)向我們的模型輸入一個(gè)單詞(字母序列),例如hello篡撵,模型將按照字母順序排序輸出判莉,即輸出ehllo。
1)數(shù)據(jù)集
數(shù)據(jù)集包括兩個(gè)部分育谬,sorce_data和target_data券盅,放置在datasets/seq2seqdata文件夾下
- sorce_data:每一行是一個(gè)單詞
- target_data:: 每一行是經(jīng)過(guò)字母排序后的“單詞”,它的每一行與source_data中每一行一一對(duì)應(yīng)
2)讀取數(shù)據(jù)和預(yù)處理
在神經(jīng)網(wǎng)絡(luò)中斑司,對(duì)于文本的數(shù)據(jù)預(yù)處理無(wú)非是將文本轉(zhuǎn)化為模型可理解的數(shù)字渗饮。
在這里加入以下四種字符
- <PAD>主要用來(lái)進(jìn)行字符補(bǔ)全
- <EOS>和<GO>都是用在Decoder端的序列中,告訴解碼器句子的起始與結(jié)束
- <UNK>則用來(lái)替代一些未出現(xiàn)過(guò)的詞或者低頻詞宿刮。
import numpy as np
import time
import tensorflow as tf
with open(r'C:\\Users\\01\\Desktop\\機(jī)器學(xué)習(xí)作業(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\\機(jī)器學(xué)習(xí)作業(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的每一個(gè)字母轉(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)建
如上文所述,模型包括兩個(gè)部分僵缺,Encoder和Decoder胡桃。
在Encoder層,首先需要對(duì)定義輸入的tensor磕潮,同時(shí)要對(duì)字母進(jìn)行Embedding翠胰,再輸入到RNN層。使用TensorFlow中的tf.contrib.layers.embed_sequence來(lái)對(duì)輸入進(jìn)行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序列最大長(zhǎng)度(之后target_sequence_length和source_sequence_length會(huì)作為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ù)說(shuō)明:
- input_data: 輸入tensor
- rnn_size: rnn隱層結(jié)點(diǎn)數(shù)量
- num_layers: 堆疊的rnn cell數(shù)量
- source_sequence_length: 源數(shù)據(jù)的序列長(zhǎng)度
- 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端主要要完成以下幾件事情:
- 對(duì)target數(shù)據(jù)進(jìn)行處理
- 構(gòu)造Decoder
- Embedding
- 構(gòu)造Decoder
- 構(gòu)造輸出層之景,輸出層會(huì)告訴我們每個(gè)時(shí)間序列的RNN輸出結(jié)果
- 訓(xùn)練Decoder
- 預(yù)測(cè)Decoder
target數(shù)據(jù)進(jìn)行處理
target數(shù)據(jù)有兩個(gè)作用:
- 在訓(xùn)練過(guò)程中,需要將的target序列作為輸入傳給Decoder端RNN的每個(gè)階段膏潮,而不是使用前一階段預(yù)測(cè)輸出锻狗,這樣會(huì)使得模型更加準(zhǔn)確。(這就是為什么構(gòu)建Training和Predicting兩個(gè)Decoder的原因焕参,下面還會(huì)有對(duì)這部分的解釋)轻纪。
- 需要用target數(shù)據(jù)來(lái)計(jì)算模型的loss。
對(duì)target數(shù)據(jù)的處理時(shí)要將每一行最后一個(gè)標(biāo)記處理掉叠纷,同時(shí)在每一行第一個(gè)元素前添加起始標(biāo)記刻帚。"h,o,w,<EOS>"->"<go>,h,o,w"。
使用tf.strided_slice()來(lái)進(jìn)行這一步處理涩嚣。
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掂僵,這兩個(gè)encoder實(shí)際上是共享參數(shù)的,也就是通過(guò)training decoder學(xué)得的參數(shù)顷歌,predicting會(huì)拿來(lái)進(jìn)行預(yù)測(cè)看峻。那么為什么我們要分兩個(gè)呢,這里主要考慮模型的robust(魯棒性)衙吩。
在training階段互妓,為了能夠讓模型更加準(zhǔn)確,我們并不會(huì)把t-1的預(yù)測(cè)輸出作為t階段的輸入坤塞,而是直接使用target data中序列的元素輸入到Encoder中冯勉。而在predict階段,我們沒(méi)有target data摹芙,有的只是t-1階段的輸出和隱層狀態(tài)灼狰。
在training過(guò)程中,不會(huì)把每個(gè)階段的預(yù)測(cè)輸出作為下一階段的輸入浮禾,下一階段的輸入我們會(huì)直接使用target data交胚,這樣能夠保證模型更加準(zhǔn)確。
代碼中做了一些更新盈电,另外好像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é)點(diǎn)數(shù)量
- target_sequence_length: target數(shù)據(jù)序列長(zhǎng)度
- max_target_sequence_length: target數(shù)據(jù)序列最大長(zhǎng)度
- 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對(duì)象
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)建一個(gè)常量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