?????作者簡介:一位即將上大四,正專攻機器學(xué)習(xí)的保研er
??上期文章:機器學(xué)習(xí)&&深度學(xué)習(xí)——NLP實戰(zhàn)(自然語言推斷——數(shù)據(jù)集)
??訂閱專欄:機器學(xué)習(xí)&&深度學(xué)習(xí)
希望文章對你們有所幫助
引入
在之前已經(jīng)介紹了什么是自然語言推斷,并且下載并處理了SNLI數(shù)據(jù)集。由于許多模型都是基于復(fù)雜而深度的架構(gòu),因此提出用注意力機制解決自然語言推斷問題,并且稱之為“可分解注意力模型”。這使得模型沒有循環(huán)層或卷積層,在SNLI數(shù)據(jù)集上以更少的參數(shù)實現(xiàn)了當時的最佳結(jié)果。下面就實現(xiàn)這種基于注意力的自然語言推斷方法(使用MLP),如下圖所述:
這里的任務(wù)就是要將預(yù)訓(xùn)練GloVe送到注意力和MLP的自然語言推斷架構(gòu)。
模型
與保留前提和假設(shè)中詞元的順序,我們可以將一個文本序列中的詞元與另一個文本序列中的每個詞元對齊,然后比較和聚合這些信息,以預(yù)測前提和假設(shè)之間的邏輯關(guān)系。這和機器翻譯中源句和目標句之間的詞元對齊類似,前提和假設(shè)之間的詞元對齊可以通過注意力機制來靈活完成。如下所示就是使用注意力機制來實現(xiàn)自然語言推斷的模型圖:
上面的i和i相對,前提中的sleep會對應(yīng)tired,假設(shè)中的tired對應(yīng)的是need sleep。
從高層次講,它由三個聯(lián)合訓(xùn)練的步驟組成:對齊、比較和匯總,下面會通過代碼來解釋和實現(xiàn)。
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
注意(Attending)
第一步是將一個文本序列中的詞元與另一個序列中的每個詞元對齊。假設(shè)前提是“我需要睡眠”,假設(shè)是“我累了”。由于語義上的相似性,我們不妨將假設(shè)中的“我”與前提中的“我”對齊,將假設(shè)中的“累”與前提中的“睡眠”對齊。同樣,我們可能希望將前提中的“我”與假設(shè)中的“我”對齊,將前提中“需要睡眠”與假設(shè)中的“累”對齊。
注意,這種對齊是使用的加權(quán)平均的“軟”對齊,其中理想情況下較大的權(quán)重與要對齊的詞元相關(guān)聯(lián)。為了便于演示,上圖是用了“硬”對齊的方式來展示。
現(xiàn)在,我們要詳細描述使用注意力機制的軟對齊。
用
A
=
(
a
1
,
.
.
.
,
a
m
)
和
B
=
(
b
1
,
.
.
.
,
b
n
)
A=(a_1,...,a_m)和B=(b_1,...,b_n)
A=(a1?,...,am?)和B=(b1?,...,bn?)
分別表示前提和假設(shè),其詞元數(shù)量分別為m和n,其中:
a
1
,
b
j
∈
R
d
是
d
維的詞向量
a_1,b_j∈R^d是d維的詞向量
a1?,bj?∈Rd是d維的詞向量
關(guān)于軟對齊,我們將注意力權(quán)重計算為:
e
i
j
=
f
(
a
i
)
T
f
(
b
j
)
e_{ij}=f(a_i)^Tf(b_j)
eij?=f(ai?)Tf(bj?)
其中函數(shù)f是在下面的mlp函數(shù)中定義的多層感知機。輸出維度f由mlp的num_hiddens參數(shù)指定。
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)
值得注意的是,上式中,f分別輸入ai和bi,而不是把它們一對放在一起作為輸入。這種分解技巧導(dǎo)致f只有m+n次計算(線性復(fù)雜度),而不是mn次計算(二次復(fù)雜度)。
對上式中的注意力權(quán)重進行規(guī)范化,我們計算假設(shè)中所有詞元向量的加權(quán)平均值,以獲得假設(shè)的表示,該假設(shè)與前提中索引i的詞元進行軟對齊:
β
i
=
∑
j
=
1
n
e
x
p
(
e
i
j
)
∑
k
=
1
n
e
x
p
(
e
i
k
)
b
j
β_i=\sum_{j=1}^n\frac{exp(e_{ij})}{\sum_{k=1}^nexp(e_{ik})}b_j
βi?=j=1∑n?∑k=1n?exp(eik?)exp(eij?)?bj?
同理,我們計算假設(shè)中索引為j的每個詞元與前提詞元的軟對齊:
α
j
=
∑
i
=
1
m
e
x
p
(
e
i
j
)
∑
k
=
1
m
e
x
p
(
e
k
j
)
a
i
α_j=\sum_{i=1}^m\frac{exp(e_{ij})}{\sum_{k=1}^mexp(e_{kj})}a_i
αj?=i=1∑m?∑k=1m?exp(ekj?)exp(eij?)?ai?
下面,我們定義Attend類來計算假設(shè)(beta)與輸入前提A的軟對齊以及前提(alpha)與輸入假設(shè)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):
# A/B的形狀:(批量大小,序列A/B的詞元數(shù),embed_size)
# f_A/f_B的形狀:(批量大小,序列A/B的詞元數(shù),num_hiddens)
f_A = self.f(A)
f_B = self.f(B)
# e的形狀:(批量大小,序列A的詞元數(shù),序列B的詞元數(shù))
e = torch.bmm(f_A, f_B.permute(0, 2, 1))
# beta的形狀:(批量大小,序列A的詞元數(shù),embed_size),
# 意味著序列B被軟對齊到序列A的每個詞元(beta的第1個維度)
beta = torch.bmm(F.softmax(e, dim=-1), B)
# beta的形狀:(批量大小,序列B的詞元數(shù),embed_size),
# 意味著序列A被軟對齊到序列B的每個詞元(alpha的第1個維度)
alpha = torch.bmm(F.softmax(e.permute(0, 2, 1), dim=-1), A)
return beta, alpha
比較
在下一步中,我們將一個序列中的詞元與和該詞元軟對齊的另一個序列進行比較。注意,軟對齊中,一個序列中的所有詞元(盡管可能具有不同的注意力權(quán)重)將與另一個序列中的詞元進行比較。
在比較步驟中,我們將來自一個序列的詞元的連結(jié)(運算符[·,·])和來自另一個序列的對其的詞元送入函數(shù)g(一個多層感知機):
v
A
,
i
=
g
(
[
a
i
,
β
i
]
)
,
i
=
1
,
.
.
.
,
m
v
B
,
j
=
g
(
[
b
j
,
α
j
]
)
,
j
=
1
,
.
.
.
,
n
其中,
v
A
,
i
指:所有假設(shè)中的詞元與前提中詞元
i
軟對齊,再與詞元
i
的比較;
v
B
,
j
指:所有前提中的詞元與假設(shè)中詞元
j
軟對齊,再與詞元
j
的比較。
v_{A,i}=g([a_i,β_i]),i=1,...,m\\ v_{B,j}=g([b_j,α_j]),j=1,...,n\\ 其中,v_{A,i}指:所有假設(shè)中的詞元與前提中詞元i軟對齊,再與詞元i的比較;\\ v_{B,j}指:所有前提中的詞元與假設(shè)中詞元j軟對齊,再與詞元j的比較。
vA,i?=g([ai?,βi?]),i=1,...,mvB,j?=g([bj?,αj?]),j=1,...,n其中,vA,i?指:所有假設(shè)中的詞元與前提中詞元i軟對齊,再與詞元i的比較;vB,j?指:所有前提中的詞元與假設(shè)中詞元j軟對齊,再與詞元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
聚合
現(xiàn)在我們有兩組比較向量:
v
A
,
i
和
v
B
,
j
v_{A,i}和v_{B,j}
vA,i?和vB,j?
在最后一步中,我們將聚合這些信息以推斷邏輯關(guān)系。我們首先求和這兩組比較向量:
v
A
=
∑
i
=
1
m
v
A
,
i
,
v
B
=
∑
j
=
1
n
v
B
,
j
v_A=\sum_{i=1}^mv_{A,i},v_B=\sum_{j=1}^nv_{B,j}
vA?=i=1∑m?vA,i?,vB?=j=1∑n?vB,j?
接下來,我們將兩個求和結(jié)果的連結(jié)提供給函數(shù)h(一個多層感知機),以獲得邏輯關(guān)系的分類結(jié)果:
y
^
=
h
(
[
v
A
,
v
B
]
)
\hat{y}=h([v_A,v_B])
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):
# 對兩組比較向量分別求和
V_A = V_A.sum(dim=1)
V_B = V_B.sum(dim=1)
# 將兩個求和結(jié)果的連結(jié)送到多層感知機中
Y_hat = self.linear(self.h(torch.cat([V_A, V_B], dim=1)))
return Y_hat
整合代碼
通過將注意步驟、比較步驟和聚合步驟組合在一起,我們定義了可分解注意力模型來聯(lián)合訓(xùn)練這三個步驟:
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)
# 有3種可能的輸出:蘊涵、矛盾和中性
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
訓(xùn)練和評估模型
現(xiàn)在,我們將在SNLI數(shù)據(jù)集上對定義好的可分解注意力模型進行訓(xùn)練和評估。我們從讀取數(shù)據(jù)集開始。
讀取數(shù)據(jù)集
我們使用上節(jié)定義的函數(shù)下載并讀取SNLI數(shù)據(jù)集,批量大小和序列長度分別設(shè)為256和50:
batch_size, num_steps = 256, 50
train_iter, test_iter, vocab = d2l.load_data_snli(batch_size, num_steps)
創(chuàng)建模型
我們將預(yù)訓(xùn)練好的100維GloVe嵌入來表示輸入詞元。我們將向量ai和bj的維數(shù)定義為100。f和g的輸出維度被設(shè)置為200。然后我們創(chuàng)建一個模型實例,初始化參數(shù),并加載GloVe嵌入來初始化輸入詞元的向量。
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)
訓(xùn)練和評估模型
現(xiàn)在我們可以在SNLI數(shù)據(jù)集上訓(xùn)練和評估模型。
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)
d2l.plt.show()
運行結(jié)果:
loss 0.495, train acc 0.805, test acc 0.826
443.5 examples/sec on [device(type=‘cpu’)]
運行圖片:
使用模型
定義預(yù)測函數(shù),輸出一對前提和假設(shè)之間的邏輯關(guān)系。
#@save
def predict_snli(net, vocab, premise, hypothesis):
"""預(yù)測前提和假設(shè)之間的邏輯關(guān)系"""
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'
我們可以使用訓(xùn)練好的模型來獲得對實例句子的自然語言推斷結(jié)果:
print(predict_snli(net, vocab, ['he', 'is', 'good', '.'], ['he', 'is', 'bad', '.']))
預(yù)測結(jié)果:
‘contradiction’文章來源:http://www.zghlxwxcb.cn/news/detail-674828.html
小結(jié)
1、可分解注意模型包括三個步驟來預(yù)測前提和假設(shè)之間的邏輯關(guān)系:注意、比較和聚合。
2、通過注意力機制,我們可以將一個文本序列中的詞元與另一個文本序列中的每個詞元對齊,反之亦然。這種對齊是使用加權(quán)平均的軟對齊,其中理想情況下,較大的權(quán)重與要對齊的詞元相關(guān)聯(lián)。
3、在計算注意力權(quán)重時,分解技巧會帶來比二次復(fù)雜度更理想的線性復(fù)雜度。
4、我們可以使用預(yù)訓(xùn)練好的詞向量作為下游自然語言處理任務(wù)的輸入表示。文章來源地址http://www.zghlxwxcb.cn/news/detail-674828.html
到了這里,關(guān)于機器學(xué)習(xí)&&深度學(xué)習(xí)——NLP實戰(zhàn)(自然語言推斷——注意力機制實現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!