我們?cè)?6.4 節(jié)介紹了自然語(yǔ)言推理任務(wù)和 SNLI 數(shù)據(jù)集。鑒于許多基于復(fù)雜和深層架構(gòu)的模型, Parikh等人。( 2016 )提出用注意力機(jī)制解決自然語(yǔ)言推理,并將其稱為“可分解注意力模型”。這導(dǎo)致模型沒(méi)有循環(huán)層或卷積層,在 SNLI 數(shù)據(jù)集上以更少的參數(shù)獲得了當(dāng)時(shí)最好的結(jié)果。在本節(jié)中,我們將描述和實(shí)現(xiàn)這種用于自然語(yǔ)言推理的基于注意力的方法(使用 MLP),如圖 16.5.1所示。
圖 16.5.1本節(jié)將預(yù)訓(xùn)練的 GloVe 提供給基于注意力和 MLP 的架構(gòu)以進(jìn)行自然語(yǔ)言推理。
16.5.1。該模型
比保留前提和假設(shè)中標(biāo)記的順序更簡(jiǎn)單的是,我們可以將一個(gè)文本序列中的標(biāo)記與另一個(gè)文本序列中的每個(gè)標(biāo)記對(duì)齊,反之亦然,然后比較和聚合這些信息以預(yù)測(cè)前提和假設(shè)之間的邏輯關(guān)系。類似于機(jī)器翻譯中源句和目標(biāo)句之間的 token 對(duì)齊,前提和假設(shè)之間的 token 對(duì)齊可以通過(guò)注意力機(jī)制巧妙地完成。
圖 16.5.2使用注意機(jī)制的自然語(yǔ)言推理。
圖 16.5.2描述了使用注意機(jī)制的自然語(yǔ)言推理方法。在高層次上,它由三個(gè)聯(lián)合訓(xùn)練的步驟組成:參與、比較和聚合。我們將在下面逐步說(shuō)明它們。
import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l
from mxnet import gluon, init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np()
16.5.1.1。出席
第一步是將一個(gè)文本序列中的標(biāo)記與另一個(gè)序列中的每個(gè)標(biāo)記對(duì)齊。假設(shè)前提是“我確實(shí)需要睡覺(jué)”,假設(shè)是“我累了”。由于語(yǔ)義相似,我們可能希望將假設(shè)中的“i”與前提中的“i”對(duì)齊,并將假設(shè)中的“tired”與前提中的“sleep”對(duì)齊。同樣,我們可能希望將前提中的“i”與假設(shè)中的“i”對(duì)齊,并將前提中的“需要”和“睡眠”與假設(shè)中的“疲倦”對(duì)齊。請(qǐng)注意,使用加權(quán)平均的這種對(duì)齊是軟的,其中理想情況下較大的權(quán)重與要對(duì)齊的標(biāo)記相關(guān)聯(lián)。為了便于演示,圖 16.5.2以硬方式顯示了這種對(duì)齊方式 。
現(xiàn)在我們更詳細(xì)地描述使用注意機(jī)制的軟對(duì)齊。表示為 A=(a1,…,am)和 B=(b1,…,bn)前提和假設(shè),其標(biāo)記數(shù)為m和n,分別在哪里 ai,bj∈Rd (i=1,…,m,j=1,…,n) 是一個(gè)d維詞向量。對(duì)于軟對(duì)齊,我們計(jì)算注意力權(quán)重 eij∈R作為
(16.5.1)eij=f(ai)?f(bj),
哪里的功能f是在以下函數(shù)中定義的 MLP mlp 。的輸出維度f(wàn)num_hiddens由的參數(shù)指定 mlp。
def mlp(num_inputs, num_hiddens, flatten): net = [] net.append(nn.Dropout(0.2)) net.append(nn.Linear(num_inputs, num_hiddens)) net.append(nn.ReLU()) if flatten: net.append(nn.Flatten(start_dim=1)) net.append(nn.Dropout(0.2)) net.append(nn.Linear(num_hiddens, num_hiddens)) net.append(nn.ReLU()) if flatten: net.append(nn.Flatten(start_dim=1)) return nn.Sequential(*net)
def mlp(num_hiddens, flatten): net = nn.Sequential() net.add(nn.Dropout(0.2)) net.add(nn.Dense(num_hiddens, activation='relu', flatten=flatten)) net.add(nn.Dropout(0.2)) net.add(nn.Dense(num_hiddens, activation='relu', flatten=flatten)) return net
需要強(qiáng)調(diào)的是,在(16.5.1) f接受輸入ai和bj分開(kāi)而不是將它們中的一對(duì)一起作為輸入。這種分解技巧只會(huì)導(dǎo)致m+n的應(yīng)用(線性復(fù)雜度) f而不是mn應(yīng)用程序(二次復(fù)雜度)。
對(duì)(16.5.1)中的注意力權(quán)重進(jìn)行歸一化,我們計(jì)算假設(shè)中所有標(biāo)記向量的加權(quán)平均值,以獲得與由索引的標(biāo)記軟對(duì)齊的假設(shè)表示i在前提下:
(16.5.2)βi=∑j=1nexp?(eij)∑k=1nexp?(eik)bj.
同樣,我們?yōu)橛伤饕拿總€(gè)標(biāo)記計(jì)算前提標(biāo)記的軟對(duì)齊j在假設(shè)中:
(16.5.3)αj=∑i=1mexp?(eij)∑k=1mexp?(ekj)ai.
下面我們定義Attend類來(lái)計(jì)算假設(shè) ( beta) 與輸入前提的軟對(duì)齊A以及前提 ( alpha) 與輸入假設(shè)的軟對(duì)齊B。
class Attend(nn.Module): def __init__(self, num_inputs, num_hiddens, **kwargs): super(Attend, self).__init__(**kwargs) self.f = mlp(num_inputs, num_hiddens, flatten=False) def forward(self, A, B): # Shape of `A`/`B`: (`batch_size`, no. of tokens in sequence A/B, # `embed_size`) # Shape of `f_A`/`f_B`: (`batch_size`, no. of tokens in sequence A/B, # `num_hiddens`) f_A = self.f(A) f_B = self.f(B) # Shape of `e`: (`batch_size`, no. of tokens in sequence A, # no. of tokens in sequence B) e = torch.bmm(f_A, f_B.permute(0, 2, 1)) # Shape of `beta`: (`batch_size`, no. of tokens in sequence A, # `embed_size`), where sequence B is softly aligned with each token # (axis 1 of `beta`) in sequence A beta = torch.bmm(F.softmax(e, dim=-1), B) # Shape of `alpha`: (`batch_size`, no. of tokens in sequence B, # `embed_size`), where sequence A is softly aligned with each token # (axis 1 of `alpha`) in sequence B alpha = torch.bmm(F.softmax(e.permute(0, 2, 1), dim=-1), A) return beta, alpha
class Attend(nn.Block): def __init__(self, num_hiddens, **kwargs): super(Attend, self).__init__(**kwargs) self.f = mlp(num_hiddens=num_hiddens, flatten=False) def forward(self, A, B): # Shape of `A`/`B`: (b`atch_size`, no. of tokens in sequence A/B, # `embed_size`) # Shape of `f_A`/`f_B`: (`batch_size`, no. of tokens in sequence A/B, # `num_hiddens`) f_A = self.f(A) f_B = self.f(B) # Shape of `e`: (`batch_size`, no. of tokens in sequence A, # no. of tokens in sequence B) e = npx.batch_dot(f_A, f_B, transpose_b=True) # Shape of `beta`: (`batch_size`, no. of tokens in sequence A, # `embed_size`), where sequence B is softly aligned with each token # (axis 1 of `beta`) in sequence A beta = npx.batch_dot(npx.softmax(e), B) # Shape of `alpha`: (`batch_size`, no. of tokens in sequence B, # `embed_size`), where sequence A is softly aligned with each token # (axis 1 of `alpha`) in sequence B alpha = npx.batch_dot(npx.softmax(e.transpose(0, 2, 1)), A) return beta, alpha
16.5.1.2。比較
在下一步中,我們將一個(gè)序列中的標(biāo)記與與該標(biāo)記軟對(duì)齊的另一個(gè)序列進(jìn)行比較。請(qǐng)注意,在軟對(duì)齊中,來(lái)自一個(gè)序列的所有標(biāo)記,盡管可能具有不同的注意力權(quán)重,但將與另一個(gè)序列中的標(biāo)記進(jìn)行比較。為了便于演示,圖 16.5.2以硬方式將令牌與對(duì)齊令牌配對(duì)。例如,假設(shè)參與步驟確定前提中的“need”和“sleep”都與假設(shè)中的“tired”對(duì)齊,則將比較“tired-need sleep”對(duì)。
在比較步驟中,我們提供連接(運(yùn)算符 [?,?]) 來(lái)自一個(gè)序列的標(biāo)記和來(lái)自另一個(gè)序列的對(duì)齊標(biāo)記到一個(gè)函數(shù)中g(shù)(一個(gè) MLP):
(16.5.4)vA,i=g([ai,βi]),i=1,…,mvB,j=g([bj,αj]),j=1,…,n.
在(16.5.4)中,vA,i是token之間的比較i在前提和所有與 token 軟對(duì)齊的假設(shè) tokeni; 盡管 vB,j是token之間的比較j在假設(shè)和所有與標(biāo)記軟對(duì)齊的前提標(biāo)記中j. 下面的Compare類定義了比較步驟。
class Compare(nn.Module): def __init__(self, num_inputs, num_hiddens, **kwargs): super(Compare, self).__init__(**kwargs) self.g = mlp(num_inputs, num_hiddens, flatten=False) def forward(self, A, B, beta, alpha): V_A = self.g(torch.cat([A, beta], dim=2)) V_B = self.g(torch.cat([B, alpha], dim=2)) return V_A, V_B
class Compare(nn.Block): def __init__(self, num_hiddens, **kwargs): super(Compare, self).__init__(**kwargs) self.g = mlp(num_hiddens=num_hiddens, flatten=False) def forward(self, A, B, beta, alpha): V_A = self.g(np.concatenate([A, beta], axis=2)) V_B = self.g(np.concatenate([B, alpha], axis=2)) return V_A, V_B
16.5.1.3。聚合
有兩組比較向量vA,i (i=1,…,m) 和vB,j (j=1,…,n) 手頭,在最后一步中,我們將匯總此類信息以推斷邏輯關(guān)系。我們首先總結(jié)兩組:
(16.5.5)vA=∑i=1mvA,i,vB=∑j=1nvB,j.
接下來(lái),我們將兩個(gè)匯總結(jié)果的串聯(lián)提供給函數(shù)h(一個(gè)MLP)得到邏輯關(guān)系的分類結(jié)果:
(16.5.6)y^=h([vA,vB]).
聚合步驟在以下類中定義Aggregate。
class Aggregate(nn.Module): def __init__(self, num_inputs, num_hiddens, num_outputs, **kwargs): super(Aggregate, self).__init__(**kwargs) self.h = mlp(num_inputs, num_hiddens, flatten=True) self.linear = nn.Linear(num_hiddens, num_outputs) def forward(self, V_A, V_B): # Sum up both sets of comparison vectors V_A = V_A.sum(dim=1) V_B = V_B.sum(dim=1) # Feed the concatenation of both summarization results into an MLP Y_hat = self.linear(self.h(torch.cat([V_A, V_B], dim=1))) return Y_hat
class Aggregate(nn.Block): def __init__(self, num_hiddens, num_outputs, **kwargs): super(Aggregate, self).__init__(**kwargs) self.h = mlp(num_hiddens=num_hiddens, flatten=True) self.h.add(nn.Dense(num_outputs)) def forward(self, V_A, V_B): # Sum up both sets of comparison vectors V_A = V_A.sum(axis=1) V_B = V_B.sum(axis=1) # Feed the concatenation of both summarization results into an MLP Y_hat = self.h(np.concatenate([V_A, V_B], axis=1)) return Y_hat
16.5.1.4。把它們放在一起
通過(guò)將參與、比較和聚合步驟放在一起,我們定義了可分解的注意力模型來(lái)聯(lián)合訓(xùn)練這三個(gè)步驟。
class DecomposableAttention(nn.Module): def __init__(self, vocab, embed_size, num_hiddens, num_inputs_attend=100, num_inputs_compare=200, num_inputs_agg=400, **kwargs): super(DecomposableAttention, self).__init__(**kwargs) self.embedding = nn.Embedding(len(vocab), embed_size) self.attend = Attend(num_inputs_attend, num_hiddens) self.compare = Compare(num_inputs_compare, num_hiddens) # There are 3 possible outputs: entailment, contradiction, and neutral self.aggregate = Aggregate(num_inputs_agg, num_hiddens, num_outputs=3) def forward(self, X): premises, hypotheses = X A = self.embedding(premises) B = self.embedding(hypotheses) beta, alpha = self.attend(A, B) V_A, V_B = self.compare(A, B, beta, alpha) Y_hat = self.aggregate(V_A, V_B) return Y_hat
class DecomposableAttention(nn.Block): def __init__(self, vocab, embed_size, num_hiddens, **kwargs): super(DecomposableAttention, self).__init__(**kwargs) self.embedding = nn.Embedding(len(vocab), embed_size) self.attend = Attend(num_hiddens) self.compare = Compare(num_hiddens) # There are 3 possible outputs: entailment, contradiction, and neutral self.aggregate = Aggregate(num_hiddens, 3) def forward(self, X): premises, hypotheses = X A = self.embedding(premises) B = self.embedding(hypotheses) beta, alpha = self.attend(A, B) V_A, V_B = self.compare(A, B, beta, alpha) Y_hat = self.aggregate(V_A, V_B) return Y_hat
16.5.2。訓(xùn)練和評(píng)估模型
現(xiàn)在我們將在 SNLI 數(shù)據(jù)集上訓(xùn)練和評(píng)估定義的可分解注意力模型。我們從讀取數(shù)據(jù)集開(kāi)始。
16.5.2.1。讀取數(shù)據(jù)集
我們使用16.4 節(jié)中定義的函數(shù)下載并讀取 SNLI 數(shù)據(jù)集 。批量大小和序列長(zhǎng)度設(shè)置為256和50, 分別。
batch_size, num_steps = 256, 50 train_iter, test_iter, vocab = d2l.load_data_snli(batch_size, num_steps)
read 549367 examples read 9824 examples
batch_size, num_steps = 256, 50 train_iter, test_iter, vocab = d2l.load_data_snli(batch_size, num_steps)
Downloading ../data/snli_1.0.zip from https://nlp.stanford.edu/projects/snli/snli_1.0.zip... read 549367 examples read 9824 examples
16.5.2.2。創(chuàng)建模型
我們使用預(yù)訓(xùn)練的 100 維 GloVe 嵌入來(lái)表示輸入標(biāo)記。因此,我們預(yù)先定義向量的維數(shù) ai和bj在(16.5.1)中作為 100. 函數(shù)的輸出維度f(wàn)在(16.5.1) 和g在(16.5.4)中設(shè)置為 200。然后我們創(chuàng)建一個(gè)模型實(shí)例,初始化其參數(shù),并加載 GloVe 嵌入以初始化輸入標(biāo)記的向量。
embed_size, num_hiddens, devices = 100, 200, d2l.try_all_gpus() net = DecomposableAttention(vocab, embed_size, num_hiddens) glove_embedding = d2l.TokenEmbedding('glove.6b.100d') embeds = glove_embedding[vocab.idx_to_token] net.embedding.weight.data.copy_(embeds);
embed_size, num_hiddens, devices = 100, 200, d2l.try_all_gpus() net = DecomposableAttention(vocab, embed_size, num_hiddens) net.initialize(init.Xavier(), ctx=devices) glove_embedding = d2l.TokenEmbedding('glove.6b.100d') embeds = glove_embedding[vocab.idx_to_token] net.embedding.weight.set_data(embeds)
Downloading ../data/glove.6B.100d.zip from http://d2l-data.s3-accelerate.amazonaws.com/glove.6B.100d.zip...
16.5.2.3。訓(xùn)練和評(píng)估模型
與第 13.5 節(jié)split_batch中 采用單一輸入(如文本序列(或圖像))的 函數(shù)相反,我們定義了一個(gè)函數(shù)來(lái)采用多個(gè)輸入,如小批量中的前提和假設(shè)。split_batch_multi_inputs
#@save def split_batch_multi_inputs(X, y, devices): """Split multi-input `X` and `y` into multiple devices.""" X = list(zip(*[gluon.utils.split_and_load( feature, devices, even_split=False) for feature in X])) return (X, gluon.utils.split_and_load(y, devices, even_split=False))
現(xiàn)在我們可以在 SNLI 數(shù)據(jù)集上訓(xùn)練和評(píng)估模型。
lr, num_epochs = 0.001, 4 trainer = torch.optim.Adam(net.parameters(), lr=lr) loss = nn.CrossEntropyLoss(reduction="none") d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
loss 0.498, train acc 0.805, test acc 0.819 14389.2 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]
lr, num_epochs = 0.001, 4 trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr}) loss = gluon.loss.SoftmaxCrossEntropyLoss() d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices, split_batch_multi_inputs)
loss 0.521, train acc 0.793, test acc 0.821 4398.4 examples/sec on [gpu(0), gpu(1)]
16.5.2.4。使用模型
最后,定義預(yù)測(cè)函數(shù)輸出一對(duì)前提和假設(shè)之間的邏輯關(guān)系。
#@save def predict_snli(net, vocab, premise, hypothesis): """Predict the logical relationship between the premise and hypothesis.""" net.eval() premise = torch.tensor(vocab[premise], device=d2l.try_gpu()) hypothesis = torch.tensor(vocab[hypothesis], device=d2l.try_gpu()) label = torch.argmax(net([premise.reshape((1, -1)), hypothesis.reshape((1, -1))]), dim=1) return 'entailment' if label == 0 else 'contradiction' if label == 1 else 'neutral'
#@save def predict_snli(net, vocab, premise, hypothesis): """Predict the logical relationship between the premise and hypothesis.""" premise = np.array(vocab[premise], ctx=d2l.try_gpu()) hypothesis = np.array(vocab[hypothesis], ctx=d2l.try_gpu()) label = np.argmax(net([premise.reshape((1, -1)), hypothesis.reshape((1, -1))]), axis=1) return 'entailment' if label == 0 else 'contradiction' if label == 1 else 'neutral'
我們可以使用訓(xùn)練好的模型來(lái)獲得樣本對(duì)句子的自然語(yǔ)言推理結(jié)果。
predict_snli(net, vocab, ['he', 'is', 'good', '.'], ['he', 'is', 'bad', '.'])
'contradiction'
predict_snli(net, vocab, ['he', 'is', 'good', '.'], ['he', 'is', 'bad', '.'])
'contradiction'
16.5.3。概括
可分解注意力模型由三個(gè)步驟組成,用于預(yù)測(cè)前提和假設(shè)之間的邏輯關(guān)系:參與、比較和聚合。
通過(guò)注意機(jī)制,我們可以將一個(gè)文本序列中的標(biāo)記與另一個(gè)文本序列中的每個(gè)標(biāo)記對(duì)齊,反之亦然。這種對(duì)齊是軟的,使用加權(quán)平均,理想情況下,大權(quán)重與要對(duì)齊的標(biāo)記相關(guān)聯(lián)。
在計(jì)算注意力權(quán)重時(shí),分解技巧導(dǎo)致比二次復(fù)雜度更理想的線性復(fù)雜度。
我們可以使用預(yù)訓(xùn)練的詞向量作為下游自然語(yǔ)言處理任務(wù)(如自然語(yǔ)言推理)的輸入表示。
16.5.4。練習(xí)
使用其他超參數(shù)組合訓(xùn)練模型。你能在測(cè)試集上獲得更好的準(zhǔn)確性嗎?
用于自然語(yǔ)言推理的可分解注意力模型的主要缺點(diǎn)是什么?
假設(shè)我們想要獲得任何一對(duì)句子的語(yǔ)義相似度(例如,0 和 1 之間的連續(xù)值)。我們應(yīng)該如何收集和標(biāo)記數(shù)據(jù)集?你能設(shè)計(jì)一個(gè)帶有注意力機(jī)制的模型嗎?
-
自然語(yǔ)言
+關(guān)注
關(guān)注
1文章
287瀏覽量
13346 -
pytorch
+關(guān)注
關(guān)注
2文章
807瀏覽量
13200
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論