動手學深度學習-學習筆記task4
《動手學深度學習》的學習內容鏈接在github上:
Dive-into-DL-PyTorch. 本次學習過程的coding 是用PyTorch實現。
0. 學習任務
機器翻譯及相關技術;注意力機制與Seq2Seq模型;Transformer
1. 機器翻譯
機器翻譯(machine translation) : 將一段文本從一種語言翻譯到另一種語言,輸入和輸出都是一段單詞序列。
主要特徵: 輸入序列和輸出序列長度可能不同
1.1 機器翻譯任務的步驟
數據預處理
讀取數據,並將數據集清洗、轉化爲神經網絡輸入的minbatch
- 讀取數據 ,並進行數據清洗
def preprocess_raw(text):
text = text.replace('\u202f', ' ').replace('\xa0', ' ') # 數據清洗
out = ''
for i, char in enumerate(text.lower()):
if char in (',', '!', '.') and i > 0 and text[i-1] != ' ':
out += ' '
out += char
return out
字符在計算機裏是以編碼的形式存在,我們通常所用的空格是 \x20 ,是在標準ASCII可見字符 0x20~0x7e 範圍內。
而 \xa0 屬於 latin1 (ISO/IEC_8859-1)中的擴展字符集字符,代表不間斷空白符nbsp(non-breaking space),超出gbk編碼範圍,是需要去除的特殊字符。再數據預處理的過程中,我們首先需要對數據進行清洗。
- 分詞: 將字符串轉成單詞組成的列表
- 建立字典
- 統計詞頻並去重
- 處理特殊tokens, 比如:padding, begin of sentence, end of sentence, unknown
- 構建一個list列表,建立index to token,用列表的下標表示index
- 將lists轉爲dict,從而 實現token to index,實現 每個詞映射到一個唯一的索引編號。
- 製作數據生成器
1.2 Seq2Seq 模型
Seq2Seq模型是輸出的長度不確定時採用的模型,常應用於機器翻譯,其輸入是一段序列文本,輸出也是序列文本,值得注意的是:輸入序列和輸出序列長度可能不同。 Seq2Seq模型 通過Encoder-Decoder框架 實現。
Encoder-Decoder
文本處理領域的Encoder-Decoder框架可以這麼直觀地去理解:可以把它看作適合處理由一個句子(或篇章)生成另外一個句子(或篇章)的通用處理模型。對於句子對<Source,Target>,我們的目標是給定輸入句子Source,期待通過Encoder-Decoder框架來生成目標句子Target。Source和Target可以是同一種語言,也可以是兩種不同的語言。而Source和Target分別由各自的單詞序列構成:
Encoder-Decoder框架的基本思想就是利用兩個RNN,一個RNN作爲encoder,另一個RNN作爲decoder。 其中 encoder是輸入到隱藏狀態 ; decoder是隱藏狀態到輸出。
Encoder的最後一個時刻的hidden state 轉爲語義編碼,並用於初始化decode的前一個hidden state。
下面我們舉個英語翻譯爲法語的例子
模型訓練時
在此模型中,訓練時 輸入的是 翻譯文本pair對,比如 “hello world” 對應 “bonjour le monde”。同時輸入英語和法語,並根據預測的結果去計算loss, 訓練好該模型。
模型預測時:
在預測時 輸入的是隻有英文,hello world,然後預測的第一個前一個單詞,將作爲下一個預測的輸入,比如由 encoder的 hidden state 和 預測了bonjour,則bonjour 作爲下一個預測的輸入,由 前一次的hidden state 和 bonjour 去預測 le,一直下去,直到預測到 爲止。
seq2seq 的整體結構:
如前面所述,Source和Target分別由各自的單詞序列構成:
訓練時sources 和 Targets 都要經過數據預處理和 embedding 層得到input vector 再輸入到網絡。
代碼實現
Encoder
class Encoder(nn.Module):
def __init__(self, **kwargs):
super(Encoder, self).__init__(**kwargs)
def forward(self, X, *args):
raise NotImplementedError
Decoder
class Decoder(nn.Module):
def __init__(self, **kwargs):
super(Decoder, self).__init__(**kwargs)
def init_state(self, enc_outputs, *args):
raise NotImplementedError
def forward(self, X, state):
raise NotImplementedError
EncoderDecoder
class EncoderDecoder(nn.Module):
def __init__(self, encoder, decoder, **kwargs):
super(EncoderDecoder, self).__init__(**kwargs)
self.encoder = encoder
self.decoder = decoder
def forward(self, enc_X, dec_X, *args):
enc_outputs = self.encoder(enc_X, *args)
dec_state = self.decoder.init_state(enc_outputs, *args)
return self.decoder(dec_X, dec_state)
class Seq2SeqEncoder(d2l.Encoder):
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqEncoder, self).__init__(**kwargs)
self.num_hiddens=num_hiddens
self.num_layers=num_layers
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = nn.LSTM(embed_size,num_hiddens, num_layers, dropout=dropout)
def begin_state(self, batch_size, device):
return [torch.zeros(size=(self.num_layers, batch_size, self.num_hiddens), device=device),
torch.zeros(size=(self.num_layers, batch_size, self.num_hiddens), device=device)]
def forward(self, X, *args):
X = self.embedding(X) # X shape: (batch_size, seq_len, embed_size)
X = X.transpose(0, 1) # RNN needs first axes to be time
# state = self.begin_state(X.shape[1], device=X.device)
out, state = self.rnn(X)
# The shape of out is (seq_len, batch_size, num_hiddens).
# state contains the hidden state and the memory cell
# of the last time step, the shape is (num_layers, batch_size, num_hiddens)
return out, state
# state 包含兩個部分信息: 記憶細胞的信息和隱藏狀態的信息
class Seq2SeqDecoder(d2l.Decoder):
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqDecoder, self).__init__(**kwargs)
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = nn.LSTM(embed_size,num_hiddens, num_layers, dropout=dropout)
self.dense = nn.Linear(num_hiddens,vocab_size)
def init_state(self, enc_outputs, *args):
return enc_outputs[1]
def forward(self, X, state):
X = self.embedding(X).transpose(0, 1)
out, state = self.rnn(X, state)
# Make the batch to be the first dimension to simplify loss computation.
out = self.dense(out).transpose(0, 1)
return out, state
損失函數
def SequenceMask(X, X_len,value=0):
maxlen = X.size(1)
mask = torch.arange(maxlen)[None, :].to(X_len.device) < X_len[:, None] ### .to(device)!!!1
X[~mask]=value
return X
class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):
# pred shape: (batch_size, seq_len, vocab_size)
# label shape: (batch_size, seq_len)
# valid_length shape: (batch_size, )
def forward(self, pred, label, valid_length):
# the sample weights shape should be (batch_size, seq_len)
weights = torch.ones_like(label)
weights = SequenceMask(weights, valid_length).float()
self.reduction='none'
output=super(MaskedSoftmaxCELoss, self).forward(pred.transpose(1,2), label)
return (output*weights).mean(dim=1)
1.3 Beam Search
集束搜索是維特比算法的貪心形式。
2. Seq2Seq模型+Attention
注意力機制
在“編碼器—解碼器(seq2seq)”⼀節⾥,解碼器在各個時間步依賴相同的背景變量(context vector)來獲取輸⼊序列信息。當編碼器爲循環神經⽹絡時,背景變量來⾃它最終時間步的隱藏狀態。將源序列輸入信息以循環單位狀態編碼,然後將其傳遞給解碼器以生成目標序列。然而這種結構存在着問題,尤其是RNN機制實際中存在長程梯度消失的問題,對於較長的句子,我們很難寄希望於將輸入的序列轉化爲定長的向量而保存所有的有效信息,所以隨着所需翻譯句子的長度的增加,這種結構的效果會顯著下降。
與此同時,解碼的目標詞語可能只與原輸入的部分詞語有關,而並不是與所有的輸入有關。例如,當把“Hello world”翻譯成“Bonjour le monde”時,“Hello”映射成“Bonjour”,“world”映射成“monde”。在seq2seq模型中,解碼器只能隱式地從編碼器的最終狀態中選擇相應的信息。然而,注意力機制可以將這種選擇過程顯式地建模。
注意力機制框架
Attention 是一種通用的帶權池化方法,輸入由兩部分構成:詢問(query)和鍵值對(key-value pairs)。
attention layer得到輸出與value的維度一致
. 對於一個query來說,attention layer 會與每一個key計算注意力分數並進行權重的歸一化,輸出的向量則是value的加權求和,而每個key計算的權重與value一一對應。
爲了計算輸出,我們首先假設有一個函數 用於計算query和key的相似性,然後可以計算所有的 attention scores by
我們使用 softmax函數 獲得注意力權重:
最終的輸出就是value的加權求和:
不同的attetion layer的區別在於score函數的選擇,在本節的其餘部分,我們將討論兩個常用的注意層 Dot-product Attention 和 Multilayer Perceptron Attention;隨後我們將實現一個引入attention的seq2seq模型並在英法翻譯語料上進行訓練與測試。
3. Transformer
4. 參考鏈接
[1] https://www.jianshu.com/p/b2b95f945a98