pytorch关系抽取框架OpenNRE源码解读与实践:PCNN ATT
pytorch關(guān)系抽取框架OpenNRE源碼解讀與實(shí)踐:PCNN ATT
- 0 前言
- 1 OpenNRE整體架構(gòu)
- 2 PCNN+ATT 模型架構(gòu)
- 2.1 PCNN Encoder
- 2.2 Bag Attention
- 結(jié)語
- 參考資料
0 前言
OpenNRE是清華大學(xué)推出的開源關(guān)系抽取框架,針對命名實(shí)體識別,句子級別的關(guān)系抽取,遠(yuǎn)程監(jiān)督(包級別的關(guān)系抽取),文檔級別的關(guān)系抽取以及 few-shot 任務(wù)均有實(shí)現(xiàn)。其模塊化的設(shè)計(jì)可以大幅度減少不必要的代碼重寫。
本文將對OpenNRE整體架構(gòu)進(jìn)行介紹,并重點(diǎn)解讀OpenNRE中針對遠(yuǎn)程監(jiān)督任務(wù)的模型 PCNN + ATT :《Neural Relation Extraction with Selective Attention over Instances》。
ps:OpenNRE不支持 Windows ,在 Windows 環(huán)境下需要改很多路徑,非常不方便,建議在 linux 環(huán)境下使用。
1 OpenNRE整體架構(gòu)
OpenNRE在實(shí)現(xiàn)時,將關(guān)系抽取的框架劃分成不同的模塊,這使得在實(shí)現(xiàn)新的模型時,通常秩序修改Model和Encoder部分即可,其他部分不需要太大的改動即可使用,大大的提升了實(shí)現(xiàn)模型的效率。
在DataLoader模塊中,針對不同的任務(wù),實(shí)現(xiàn)了不同的讀取策略和DataSet類;
在Encoder模塊中,實(shí)現(xiàn)了各個模型獲得關(guān)系向量表示的步驟。如用 PCNN+最大池化 得到關(guān)系向量表示,BERT模型的特殊標(biāo)記 [CLS] 作為關(guān)系向量表示,將兩個實(shí)體前面插入特殊標(biāo)記 [E1start],[E2start][E_{1start}], [E_{2start}][E1start?],[E2start?] ,并將這兩個特殊標(biāo)記的隱藏向量拼接作為關(guān)系向量表示(BERTem模型)等。這里對于BERTem模型具體細(xì)節(jié)不是很清楚的話可以看一下我之前解讀BERTem論文與源碼的博客。
BERTem:https://blog.csdn.net/xiaowopiaoling/article/details/105931134
我后續(xù)也會出解讀OpenNRE中使用BERT進(jìn)行關(guān)系抽取的源碼。
在Model模塊中,實(shí)現(xiàn)了在獲取關(guān)系向量表示后的分類策略。如普通的全連接加softmax,遠(yuǎn)程監(jiān)督的將一個包中所有的關(guān)系向量平均作為包的關(guān)系向量表示再過全連接和softmax,以及本文將要講解的對于包中的實(shí)例應(yīng)用attention策略后得到向量表示再進(jìn)行分類等。
再Train Method 和 Eval Method 中是一些比較套路化的訓(xùn)練步驟,在實(shí)現(xiàn)模型時將其稍加改動即可拿來使用,非常方便。
Module 模塊中實(shí)現(xiàn)了一些基礎(chǔ)模塊,如cnn,rnn,lstm,以及處理策略如平均池化,最大池化等,也是可以直接拿來用的。
FrameWork模塊對上述所有模塊進(jìn)行集成,包括普通句子級別的關(guān)系抽取流程 sentence_re ,以及遠(yuǎn)程監(jiān)督包級別的關(guān)系抽取 bag_re 等。
2 PCNN+ATT 模型架構(gòu)
下面,我們將針對遠(yuǎn)程監(jiān)督的 PCNN+ATT 模型,來解讀一下模型實(shí)現(xiàn)的細(xì)節(jié)。
2.1 PCNN Encoder
對于模型中 PCNN 部分,主要流程就是先將文本轉(zhuǎn)化成詞嵌入與位置嵌入后,再過卷積神經(jīng)網(wǎng)路,對于得到的結(jié)果,按照實(shí)體位置分成第一個實(shí)體之前,兩個實(shí)體之間,第二個實(shí)體之后三個部分并分別最大池化。模型圖如下:
這里對于模型細(xì)節(jié)感興趣的可以看一下我之前的博客。
PCNN:https://blog.csdn.net/xiaowopiaoling/article/details/106120543
下面我們來看一下 PCNN encoder 的代碼:
self.drop = nn.Dropout(dropout)self.kernel_size = kernel_sizeself.padding_size = padding_sizeself.act = activation_functionself.conv = nn.Conv1d(self.input_size, self.hidden_size, self.kernel_size, padding=self.padding_size)self.pool = nn.MaxPool1d(self.max_length)self.mask_embedding = nn.Embedding(4, 3)self.mask_embedding.weight.data.copy_(torch.FloatTensor([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]))self.mask_embedding.weight.requires_grad = Falseself._minus = -100self.hidden_size *= 3def forward(self, token, pos1, pos2, mask):"""Args:token: (B, L), index of tokenspos1: (B, L), relative position to head entitypos2: (B, L), relative position to tail entityReturn:(B, EMBED), representations for sentences"""# Check size of tensorsif len(token.size()) != 2 or token.size() != pos1.size() or token.size() != pos2.size():raise Exception("Size of token, pos1 ans pos2 should be (B, L)")x = torch.cat([self.word_embedding(token), self.pos1_embedding(pos1), self.pos2_embedding(pos2)], 2) # (B, L, EMBED)x = x.transpose(1, 2) # (B, EMBED, L)x = self.conv(x) # (B, H, L)mask = 1 - self.mask_embedding(mask).transpose(1, 2) # (B, L) -> (B, L, 3) -> (B, 3, L)pool1 = self.pool(self.act(x + self._minus * mask[:, 0:1, :])) # (B, H, 1)pool2 = self.pool(self.act(x + self._minus * mask[:, 1:2, :]))pool3 = self.pool(self.act(x + self._minus * mask[:, 2:3, :]))x = torch.cat([pool1, pool2, pool3], 1) # (B, 3H, 1)x = x.squeeze(2) # (B, 3H)x = self.drop(x)return x這里的注釋其實(shí)也非常清楚了,其中 B 是batch_size,L 是 sequence_len ,H 是輸出通道數(shù),即有多少個卷積核(這里為230)。首先將文本的詞嵌入和位置嵌入連接,這里位置嵌入是根據(jù)一個詞與兩個實(shí)體之間的相對位置獲得的,所以有兩個,示例如下:
連接后,我們再過一個卷積層,得到 shape 為 (B, H, L) 的矩陣。
之后我們采取利用mask進(jìn)行分段最大池化的策略,這里也是模型非常巧妙地地方。對于一個句子地某個詞,如果這個詞的位置在第一個實(shí)體之前,那么mask相應(yīng)位置上被置為1,如果在兩個實(shí)體之間被置為2,第二個實(shí)體之后被置為3,用與補(bǔ)齊句子的0填充被置為0。
通過mask_embedding,mask中文本的位置0,1,2,3分別被映射成[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]。當(dāng)執(zhí)行 mask = 1 - self.mask_embedding(mask).transpose(1, 2) 時,四種mask變?yōu)?[1, 1, 1], [0, 1, 1], [1, 0, 1], [1, 1, 0]。對于根據(jù)實(shí)體位置分成的三個段,在每個段的池化過程中,shape 為 (B, H, L) 的 x 將會與 shape 為(B, 1, L) 的 self._minus * mask[:, 0:1, :] 相加,即當(dāng)前段中元素值不受影響,而其它兩個段所有元素都會因被減掉100而不被計(jì)算,這樣就使得最大池化的過程在一個段上進(jìn)行。重復(fù)三次操作,我們便可以得到對三個段分別進(jìn)行最大池化的結(jié)果。將其拼接后我們即可得到關(guān)系的向量表示。
2.2 Bag Attention
對于模型中 Attention 的部分,我們先來看一下模型圖:
遠(yuǎn)程監(jiān)督(distant supervision)利用知識圖譜的實(shí)體以及對應(yīng)的關(guān)系對未標(biāo)注文本進(jìn)行回標(biāo),如果未標(biāo)注文本中包含了一個知識圖譜中具有某種關(guān)系的實(shí)體對,那么就假定這個文本也描述了相同的關(guān)系。通過這種標(biāo)注策略雖然可以獲得大量數(shù)據(jù),但同時也會因?yàn)榧僭O(shè)性太強(qiáng)而一如很多噪聲數(shù)據(jù)(因?yàn)榘粋€實(shí)體對的文本不一定描述了對應(yīng)的關(guān)系)。
解決遠(yuǎn)程監(jiān)督錯誤標(biāo)注所帶來的噪聲問題,我們通常使用多示例學(xué)習(xí)(Multi-instance Learning)的方法,即將多個數(shù)據(jù)打包成一個 bag ,bag 中所有句子都含有相同的實(shí)體對。對于模型圖中,一個 bag 中有 iii 個實(shí)例,句子 m1,m2,...,mim_1,m_2,...,m_im1?,m2?,...,mi? 經(jīng)過上面 PCNN Encoder 后獲得了其對應(yīng)的關(guān)系向量表示 x1,x2,...,xi\mathbf x_1,\mathbf x_2,...,\mathbf x_ix1?,x2?,...,xi? (這里為了防止混淆,將模型圖中的 r1,r2,...rir_1,r_2,...r_ir1?,r2?,...ri? 替換為了 x1,x2,...,xi\mathbf x_1,\mathbf x_2,...,\mathbf x_ix1?,x2?,...,xi? ),對于 bag 的關(guān)系向量表示,我們通過加權(quán)平均獲得,即:
s=∑iαixi\mathbf s=\sum_i {\alpha_i} \mathbf x_i s=i∑?αi?xi?
得到 bag 的向量表示 s\mathbf ss 后,再過全連接加softmax分類即可。
那么這個權(quán)重 α\alphaα 應(yīng)該如何獲得呢?有如下三種比較思路:
1.At Least One。這種思路基于一個假設(shè):對于一個 bag 中的示例,至少有一個示例是標(biāo)注正確的。假設(shè)一個 bag 的標(biāo)簽為 i,即這個包的實(shí)體描述了第 i 個關(guān)系,我們就選擇包中示例預(yù)測關(guān)系為 i 概率最高的那個示例作為 bag 的關(guān)系向量表示。在這種情況下,被選擇的示例權(quán)重 α\alphaα 為1,而其余都為0 。這種思路緩解了數(shù)據(jù)中含有噪聲的問題,但同時也造成了大量的數(shù)據(jù)浪費(fèi)。
2.平均。對于一個包中的關(guān)系向量表示 x1,x2,...,xi\mathbf x_1,\mathbf x_2,...,\mathbf x_ix1?,x2?,...,xi? ,我們將其以一種非常簡單的方式加權(quán),即 bag 種每一個示例的權(quán)重都是 1n1\over nn1? 。這樣雖然可以盡可能地利用包中的信息,但沒有解決遠(yuǎn)程監(jiān)督錯誤標(biāo)注帶來的噪聲問題。
3.Attention機(jī)制。設(shè) bag 標(biāo)簽的關(guān)系向量表示為 r\mathbf rr(注意,這里非常容易混淆,前面的提到的 bag 中示例的關(guān)系表示為 xi\mathbf x_ixi? ,bag 的關(guān)系向量表示為 r\mathbf rr,這兩個向量都是每次計(jì)算得到的,而這里 bag 標(biāo)簽關(guān)系的向量表示 rrr 暫時可以看作從 embedding 中獲得的,在后面我們會詳細(xì)講述),對于 bag 中的示例 iii ,我們計(jì)算其關(guān)系向量表示 xi\mathbf x_ixi? 與 bag 標(biāo)簽的關(guān)系向量表示 rrr 的匹配度 eie_iei? 。在PCNN+ATT 的原文種,eie_iei? 的計(jì)算公式如下:
ei=xiAre_i=\mathbf x_i\mathbf A \mathbf rei?=xi?Ar
其中 A\mathbf AA 是加權(quán)對角矩陣。而 OpenNRE 修改了計(jì)算公式,改為了計(jì)算兩個向量之間的點(diǎn)積,具體如下:
ei=xi?re_i=\mathbf x_i \cdot \mathbf rei?=xi??r
通過當(dāng)前關(guān)系的匹配度 eie_iei? 占全部的比重,我們就可以得到權(quán)重 αi\alpha_iαi?(也就是softmax) :
αi=exp?(ei)∑kexp?(ek)\alpha_i= {\exp(e_i)\over\sum_k \exp(e_k)}αi?=∑k?exp(ek?)exp(ei?)?
看到這里我們可能會有兩個問題,一個是 bag 標(biāo)簽關(guān)系的向量表示 rrr 如何得到,另一個是為什么 OpenNRE 要如此修改計(jì)算公式。
首先,對于 bag 標(biāo)簽關(guān)系的向量表示 rrr ,在得到 bag 的向量表示 s\mathbf ss 后,我們使用全連接加softmax進(jìn)行分類分類。而 bag 標(biāo)簽關(guān)系的向量表示 rrr 就是通過這個全連接層中的權(quán)重矩陣按照類似于 embedding 層以下標(biāo)索引的方式得到的。為什么全連接層的權(quán)重矩陣可以作為關(guān)系向量表示呢?我們來看一下我們使用全連接層分類時矩陣乘法的運(yùn)算過程,其中 S\mathbf SS 是一個 shape 為(batch_size=2, hidden_state=3)的 bag 的關(guān)系向量表示,R\mathbf RR 是 shape 為(hidden_state=3, num_relation=2)的全連接層的權(quán)重,O\mathbf OO 為分類結(jié)果(實(shí)際中還需要加上 bias,這里為了解釋原理暫時不考慮)。
S=[a1,1a1,2a1,3a2,1a2,2a2,3]\mathbf S = \begin{bmatrix} a_{1,1} & a_{1,2} &a_{1,3} \\ a_{2,1} &a_{2,2} &a_{2,3} \end{bmatrix}S=[a1,1?a2,1??a1,2?a2,2??a1,3?a2,3??]
R=[b1,1b1,2b2,1b2,2b3,1b3,2]\mathbf R=\begin{bmatrix} b_{1,1}&b_{1,2}\\b_{2,1}&b_{2,2}\\b_{3,1}&b_{3,2} \end{bmatrix}R=???b1,1?b2,1?b3,1??b1,2?b2,2?b3,2?????
O=S?R=[a1,1b1,1+a1,2b2,1+a1,3b3,1a1,1b1,2+a1,2b2,2+a1,3b3,2a2,1b1,1+a2,2b2,1+a2,3b3,1a2,1b1,2+a2,2b2,2+a2,3b3,2]\mathbf O=\mathbf S\cdot \mathbf R=\begin{bmatrix} a_{1,1}b_{1,1}+a_{1,2}b_{2,1}+a_{1,3}b_{3,1}&a_{1,1}b_{1,2}+a_{1,2}b_{2,2}+a_{1,3}b_{3,2}\\a_{2,1}b_{1,1}+a_{2,2}b_{2,1}+a_{2,3}b_{3,1}&a_{2,1}b_{1,2}+a_{2,2}b_{2,2}+a_{2,3}b_{3,2} \end{bmatrix}O=S?R=[a1,1?b1,1?+a1,2?b2,1?+a1,3?b3,1?a2,1?b1,1?+a2,2?b2,1?+a2,3?b3,1??a1,1?b1,2?+a1,2?b2,2?+a1,3?b3,2?a2,1?b1,2?+a2,2?b2,2?+a2,3?b3,2??]
我們可以看到,在分類的過程中,對于一個 bag 的向量表示 s\mathbf ss ,即 S\mathbf SS 中的一行,我們相當(dāng)于用其與全連接層權(quán)重 R\mathbf RR 的每一列都求了一個點(diǎn)積,將點(diǎn)積得到的值作為當(dāng)前 bag 與相應(yīng)關(guān)系的匹配度,經(jīng)過softmax后作為這個 bag 描述相應(yīng)關(guān)系的概率。這就解釋了 OpenNRE 采用點(diǎn)積的形式來計(jì)算匹配度 eie_iei? 的依據(jù)。也正因如此,我們可以把全連接層權(quán)重 R\mathbf RR 的每一列看作相應(yīng)關(guān)系的向量表示 r\mathbf rr 。在計(jì)算 eie_iei? 時,我們只需使用類似于 embedding 的形式,將對應(yīng)的關(guān)系向量 r\mathbf rr 取出即可與計(jì)算得到的 xi\mathbf x_ixi? 計(jì)算點(diǎn)積,從而得到匹配度 eie_iei? 。
至此,模型的 attention 機(jī)制已經(jīng)非常清晰了,下面我們來看一下這部分的源碼:
if mask is not None:rep = self.sentence_encoder(token, pos1, pos2, mask) # (nsum, H) if train:if bag_size > 0:batch_size = label.size(0)query = label.unsqueeze(1) # (B, 1)att_mat = self.fc.weight.data[query] # (B, 1, H)rep = rep.view(batch_size, bag_size, -1)att_score = (rep * att_mat).sum(-1) # (B, bag)softmax_att_score = self.softmax(att_score) # (B, bag)bag_rep = (softmax_att_score.unsqueeze(-1) * rep).sum(1) # (B, bag, 1) * (B, bag, H) -> (B, bag, H) -> (B, H)bag_rep = self.drop(bag_rep)bag_logits = self.fc(bag_rep) # (B, N)這里的 sentence_encoder 就是 PCNN ,rep 即獲得的 bag 中所有示例的向量表示,shape 為(batch_size * bag_size, hidden_state)。接下來我們從全連接層中取出 label 對應(yīng)的關(guān)系向量 r\mathbf rr ,也就是 att_mat ,shape 為(batch_size, 1, hidden_state),同時將 rep 的 shape 變?yōu)?#xff08;batch_size, bag_size, hidden_state),再將 rep 與 att_mat 中的元素一一對應(yīng)相乘,這時會進(jìn)行廣播運(yùn)算,對于得到的結(jié)果在最后一個維度求和,就相當(dāng)于求得點(diǎn)積。之后再通過 eie_iei? 也就是 softmax 得到對應(yīng)權(quán)重的 αi\alpha_iαi? ,將其與關(guān)系向量表示 rep 按元素意義對應(yīng)相乘并求和,即進(jìn)行了加權(quán)平均運(yùn)算。最后過一個 dropout 和全連接即可得到分類結(jié)果。
PCNN+ATT 模型的損失采用了交叉熵,其訓(xùn)練也是比較套路化的,在此就不過多贅述了,對于更多實(shí)現(xiàn)細(xì)節(jié)有興趣的話可以閱讀 OpenNRE 的源碼。
結(jié)語
OpenNRE 是一個優(yōu)秀的關(guān)系抽取框架,通過將關(guān)系抽取的框架劃分成不同的模塊,大大的提升了實(shí)現(xiàn)模型的效率。同時由于集成了許多關(guān)系抽取模型,使得不同模塊之間可以自由組合,極大方便了日后基于 OpenNRE 的拓展與研究。
參考資料
OpenNRE 論文:
https://www.aclweb.org/anthology/D19-3029.pdf
OpenNRE 源碼:
https://github.com/thunlp/OpenNRE
PCNN 論文:
https://www.aclweb.org/anthology/D15-1203.pdf
PCNN 參考博客:
https://blog.csdn.net/xiaowopiaoling/article/details/106120543
PCNN+ATT 論文:
https://www.researchgate.net/publication/306093646_Neural_Relation_Extraction_with_Selective_Attention_over_Instances
總結(jié)
以上是生活随笔為你收集整理的pytorch关系抽取框架OpenNRE源码解读与实践:PCNN ATT的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android studio 汉化包 美
- 下一篇: 阿里云的这群疯子(转载)