《動手學深度學習》-學習筆記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編碼範圍,是需要去除的特殊字符。再數據預處理的過程中,我們首先需要對數據進行清洗。

  • 分詞: 將字符串轉成單詞組成的列表
  • 建立字典
    1. 統計詞頻並去重
    2. 處理特殊tokens, 比如:padding, begin of sentence, end of sentence, unknown
    3. 構建一個list列表,建立index to token,用列表的下標表示index
    4. 將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分別由各自的單詞序列構成:
Source=[x1,x2,...,xm]Target=[y1,y2,...,yn] Source=[x_1,x_2,...,x_m] \\ Target=[y_1,y_2,...,y_n]
Encoder-Decoder框架的基本思想就是利用兩個RNN,一個RNN作爲encoder,另一個RNN作爲decoder。 其中 encoder是輸入到隱藏狀態 ; decoder是隱藏狀態到輸出。

Image Name
Encoder的最後一個時刻的hidden state 轉爲語義編碼,並用於初始化decode的前一個hidden state。

下面我們舉個英語翻譯爲法語的例子

模型訓練時
Image Name
在此模型中,訓練時 輸入的是 翻譯文本pair對,比如 “hello world” 對應 “bonjour le monde”。同時輸入英語和法語,並根據預測的結果去計算loss, 訓練好該模型。

模型預測時:

Image Name
在預測時 輸入的是隻有英文,hello world,然後預測的第一個前一個單詞,將作爲下一個預測的輸入,比如由 encoder的 hidden state 和 <bos><bos> 預測了bonjour,則bonjour 作爲下一個預測的輸入,由 前一次的hidden state 和 bonjour 去預測 le,一直下去,直到預測到<eos><eos> 爲止。

seq2seq 的整體結構:

Image Name
如前面所述,Source和Target分別由各自的單詞序列構成:
Source=[x1,x2,...,xm]Target=[y1,y2,...,yn] Source=[x_1,x_2,...,x_m] \\ Target=[y_1,y_2,...,y_n]
訓練時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模型中,解碼器只能隱式地從編碼器的最終狀態中選擇相應的信息。然而,注意力機制可以將這種選擇過程顯式地建模。

Image Name

注意力機制框架

Attention 是一種通用的帶權池化方法,輸入由兩部分構成:詢問(query)和鍵值對(key-value pairs)。

attention layer得到輸出與value的維度一致

. 對於一個query來說,attention layer 會與每一個key計算注意力分數並進行權重的歸一化,輸出的向量oo則是value的加權求和,而每個key計算的權重與value一一對應。

爲了計算輸出,我們首先假設有一個函數α\alpha 用於計算query和key的相似性,然後可以計算所有的 attention scores a1,,ana_1, \ldots, a_n by

ai=α(q,ki). a_i = \alpha(\mathbf q, \mathbf k_i).

我們使用 softmax函數 獲得注意力權重:

b1,,bn=softmax(a1,,an). b_1, \ldots, b_n = \textrm{softmax}(a_1, \ldots, a_n).
最終的輸出就是value的加權求和:
o=i=1nbivi. \mathbf o = \sum_{i=1}^n b_i \mathbf v_i.

Image Name

不同的attetion layer的區別在於score函數的選擇,在本節的其餘部分,我們將討論兩個常用的注意層 Dot-product Attention 和 Multilayer Perceptron Attention;隨後我們將實現一個引入attention的seq2seq模型並在英法翻譯語料上進行訓練與測試。

3. Transformer

4. 參考鏈接

[1] https://www.jianshu.com/p/b2b95f945a98

發佈了50 篇原創文章 · 獲贊 13 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章