日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

pytorch关系抽取框架OpenNRE源码解读与实践:PCNN ATT

發布時間:2023/12/31 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 pytorch关系抽取框架OpenNRE源码解读与实践:PCNN ATT 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

pytorch關系抽取框架OpenNRE源碼解讀與實踐:PCNN ATT

  • 0 前言
  • 1 OpenNRE整體架構
  • 2 PCNN+ATT 模型架構
    • 2.1 PCNN Encoder
    • 2.2 Bag Attention
  • 結語
  • 參考資料

0 前言

OpenNRE是清華大學推出的開源關系抽取框架,針對命名實體識別,句子級別的關系抽取,遠程監督(包級別的關系抽取),文檔級別的關系抽取以及 few-shot 任務均有實現。其模塊化的設計可以大幅度減少不必要的代碼重寫。

本文將對OpenNRE整體架構進行介紹,并重點解讀OpenNRE中針對遠程監督任務的模型 PCNN + ATT :《Neural Relation Extraction with Selective Attention over Instances》。

ps:OpenNRE不支持 Windows ,在 Windows 環境下需要改很多路徑,非常不方便,建議在 linux 環境下使用。

1 OpenNRE整體架構

OpenNRE在實現時,將關系抽取的框架劃分成不同的模塊,這使得在實現新的模型時,通常秩序修改Model和Encoder部分即可,其他部分不需要太大的改動即可使用,大大的提升了實現模型的效率。

在DataLoader模塊中,針對不同的任務,實現了不同的讀取策略和DataSet類;

在Encoder模塊中,實現了各個模型獲得關系向量表示的步驟。如用 PCNN+最大池化 得到關系向量表示,BERT模型的特殊標記 [CLS] 作為關系向量表示,將兩個實體前面插入特殊標記 [E1start],[E2start][E_{1start}], [E_{2start}][E1start?],[E2start?] ,并將這兩個特殊標記的隱藏向量拼接作為關系向量表示(BERTem模型)等。這里對于BERTem模型具體細節不是很清楚的話可以看一下我之前解讀BERTem論文與源碼的博客。
BERTem:https://blog.csdn.net/xiaowopiaoling/article/details/105931134
我后續也會出解讀OpenNRE中使用BERT進行關系抽取的源碼。

在Model模塊中,實現了在獲取關系向量表示后的分類策略。如普通的全連接加softmax,遠程監督的將一個包中所有的關系向量平均作為包的關系向量表示再過全連接和softmax,以及本文將要講解的對于包中的實例應用attention策略后得到向量表示再進行分類等。

再Train Method 和 Eval Method 中是一些比較套路化的訓練步驟,在實現模型時將其稍加改動即可拿來使用,非常方便。

Module 模塊中實現了一些基礎模塊,如cnn,rnn,lstm,以及處理策略如平均池化,最大池化等,也是可以直接拿來用的。

FrameWork模塊對上述所有模塊進行集成,包括普通句子級別的關系抽取流程 sentence_re ,以及遠程監督包級別的關系抽取 bag_re 等。

2 PCNN+ATT 模型架構

下面,我們將針對遠程監督的 PCNN+ATT 模型,來解讀一下模型實現的細節。

2.1 PCNN Encoder

對于模型中 PCNN 部分,主要流程就是先將文本轉化成詞嵌入與位置嵌入后,再過卷積神經網路,對于得到的結果,按照實體位置分成第一個實體之前,兩個實體之間,第二個實體之后三個部分并分別最大池化。模型圖如下:

這里對于模型細節感興趣的可以看一下我之前的博客。
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

這里的注釋其實也非常清楚了,其中 B 是batch_size,L 是 sequence_len ,H 是輸出通道數,即有多少個卷積核(這里為230)。首先將文本的詞嵌入和位置嵌入連接,這里位置嵌入是根據一個詞與兩個實體之間的相對位置獲得的,所以有兩個,示例如下:


連接后,我們再過一個卷積層,得到 shape 為 (B, H, L) 的矩陣。

之后我們采取利用mask進行分段最大池化的策略,這里也是模型非常巧妙地地方。對于一個句子地某個詞,如果這個詞的位置在第一個實體之前,那么mask相應位置上被置為1,如果在兩個實體之間被置為2,第二個實體之后被置為3,用與補齊句子的0填充被置為0。

通過mask_embedding,mask中文本的位置0,1,2,3分別被映射成[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]。當執行 mask = 1 - self.mask_embedding(mask).transpose(1, 2) 時,四種mask變為 [1, 1, 1], [0, 1, 1], [1, 0, 1], [1, 1, 0]。對于根據實體位置分成的三個段,在每個段的池化過程中,shape 為 (B, H, L) 的 x 將會與 shape 為(B, 1, L) 的 self._minus * mask[:, 0:1, :] 相加,即當前段中元素值不受影響,而其它兩個段所有元素都會因被減掉100而不被計算,這樣就使得最大池化的過程在一個段上進行。重復三次操作,我們便可以得到對三個段分別進行最大池化的結果。將其拼接后我們即可得到關系的向量表示。

2.2 Bag Attention

對于模型中 Attention 的部分,我們先來看一下模型圖:

遠程監督(distant supervision)利用知識圖譜的實體以及對應的關系對未標注文本進行回標,如果未標注文本中包含了一個知識圖譜中具有某種關系的實體對,那么就假定這個文本也描述了相同的關系。通過這種標注策略雖然可以獲得大量數據,但同時也會因為假設性太強而一如很多噪聲數據(因為包含一個實體對的文本不一定描述了對應的關系)。

解決遠程監督錯誤標注所帶來的噪聲問題,我們通常使用多示例學習(Multi-instance Learning)的方法,即將多個數據打包成一個 bag ,bag 中所有句子都含有相同的實體對。對于模型圖中,一個 bag 中有 iii 個實例,句子 m1,m2,...,mim_1,m_2,...,m_im1?,m2?,...,mi? 經過上面 PCNN Encoder 后獲得了其對應的關系向量表示 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 的關系向量表示,我們通過加權平均獲得,即:

s=∑iαixi\mathbf s=\sum_i {\alpha_i} \mathbf x_i s=i?αi?xi?

得到 bag 的向量表示 s\mathbf ss 后,再過全連接加softmax分類即可。

那么這個權重 α\alphaα 應該如何獲得呢?有如下三種比較思路:

1.At Least One。這種思路基于一個假設:對于一個 bag 中的示例,至少有一個示例是標注正確的。假設一個 bag 的標簽為 i,即這個包的實體描述了第 i 個關系,我們就選擇包中示例預測關系為 i 概率最高的那個示例作為 bag 的關系向量表示。在這種情況下,被選擇的示例權重 α\alphaα 為1,而其余都為0 。這種思路緩解了數據中含有噪聲的問題,但同時也造成了大量的數據浪費。

2.平均。對于一個包中的關系向量表示 x1,x2,...,xi\mathbf x_1,\mathbf x_2,...,\mathbf x_ix1?,x2?,...,xi? ,我們將其以一種非常簡單的方式加權,即 bag 種每一個示例的權重都是 1n1\over nn1? 。這樣雖然可以盡可能地利用包中的信息,但沒有解決遠程監督錯誤標注帶來的噪聲問題。

3.Attention機制。設 bag 標簽的關系向量表示為 r\mathbf rr(注意,這里非常容易混淆,前面的提到的 bag 中示例的關系表示為 xi\mathbf x_ixi? ,bag 的關系向量表示為 r\mathbf rr,這兩個向量都是每次計算得到的,而這里 bag 標簽關系的向量表示 rrr 暫時可以看作從 embedding 中獲得的,在后面我們會詳細講述),對于 bag 中的示例 iii ,我們計算其關系向量表示 xi\mathbf x_ixi? 與 bag 標簽的關系向量表示 rrr 的匹配度 eie_iei? 。在PCNN+ATT 的原文種,eie_iei? 的計算公式如下:

ei=xiAre_i=\mathbf x_i\mathbf A \mathbf rei?=xi?Ar

其中 A\mathbf AA 是加權對角矩陣。而 OpenNRE 修改了計算公式,改為了計算兩個向量之間的點積,具體如下:

ei=xi?re_i=\mathbf x_i \cdot \mathbf rei?=xi??r

通過當前關系的匹配度 eie_iei? 占全部的比重,我們就可以得到權重 α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 標簽關系的向量表示 rrr 如何得到,另一個是為什么 OpenNRE 要如此修改計算公式。

首先,對于 bag 標簽關系的向量表示 rrr ,在得到 bag 的向量表示 s\mathbf ss 后,我們使用全連接加softmax進行分類分類。而 bag 標簽關系的向量表示 rrr 就是通過這個全連接層中的權重矩陣按照類似于 embedding 層以下標索引的方式得到的。為什么全連接層的權重矩陣可以作為關系向量表示呢?我們來看一下我們使用全連接層分類時矩陣乘法的運算過程,其中 S\mathbf SS 是一個 shape 為(batch_size=2, hidden_state=3)的 bag 的關系向量表示,R\mathbf RR 是 shape 為(hidden_state=3, num_relation=2)的全連接層的權重,O\mathbf OO 為分類結果(實際中還需要加上 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 中的一行,我們相當于用其與全連接層權重 R\mathbf RR 的每一列都求了一個點積,將點積得到的值作為當前 bag 與相應關系的匹配度,經過softmax后作為這個 bag 描述相應關系的概率。這就解釋了 OpenNRE 采用點積的形式來計算匹配度 eie_iei? 的依據。也正因如此,我們可以把全連接層權重 R\mathbf RR 的每一列看作相應關系的向量表示 r\mathbf rr 。在計算 eie_iei? 時,我們只需使用類似于 embedding 的形式,將對應的關系向量 r\mathbf rr 取出即可與計算得到的 xi\mathbf x_ixi? 計算點積,從而得到匹配度 eie_iei?

至此,模型的 attention 機制已經非常清晰了,下面我們來看一下這部分的源碼:

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 對應的關系向量 r\mathbf rr ,也就是 att_mat ,shape 為(batch_size, 1, hidden_state),同時將 rep 的 shape 變為(batch_size, bag_size, hidden_state),再將 rep 與 att_mat 中的元素一一對應相乘,這時會進行廣播運算,對于得到的結果在最后一個維度求和,就相當于求得點積。之后再通過 eie_iei? 也就是 softmax 得到對應權重的 αi\alpha_iαi? ,將其與關系向量表示 rep 按元素意義對應相乘并求和,即進行了加權平均運算。最后過一個 dropout 和全連接即可得到分類結果。

PCNN+ATT 模型的損失采用了交叉熵,其訓練也是比較套路化的,在此就不過多贅述了,對于更多實現細節有興趣的話可以閱讀 OpenNRE 的源碼。

結語

OpenNRE 是一個優秀的關系抽取框架,通過將關系抽取的框架劃分成不同的模塊,大大的提升了實現模型的效率。同時由于集成了許多關系抽取模型,使得不同模塊之間可以自由組合,極大方便了日后基于 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

總結

以上是生活随笔為你收集整理的pytorch关系抽取框架OpenNRE源码解读与实践:PCNN ATT的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。