【閱讀筆記】機器閱讀理解(中)——架構篇

一、MRC模型架構

總體架構

在交互層,模型需要建立文章和問題之間的聯繫。比如問題出現“河流”,而文章中出現關鍵詞“長江”,雖然兩個詞完全不一樣,但是其語義編碼相近,因此文章中“長江”一詞,以及它附近的語句將成爲模型回答問題時的重點關注對象。

大部分閱讀理解的創新集中在交互層,因爲這一層中對文章和問題語義的交叉分析有很多不同的處理方式,而且這也是模型最終能產生正確答案的關鍵步驟。

編碼層

編碼層生成單詞編碼可以包括:詞表向量、NE向量、POS向量、字符編碼、精確匹配編碼和上下文編碼。

詞表向量

有兩種方式獲得詞表中的單詞向量:

  • 保持詞表向量不變,即採用預訓練詞表中的向量,在訓練過程中不進行改變
  • 將詞表中的向量視爲參數,在訓練過程中和其他向量一起求導並優化

第一種選擇的優勢是模型參數少,訓練初期收斂較快;後者可以根據實際數據調整詞向量的值,以達到更好的訓練效果。

在編碼層中,爲了準確表示每個單詞在語句中的語義,除了詞表向量化外,還經常對NE和POS進行向量化處理。如建立命名實體表(其實就是矩陣,N*dN,N是NE種類),詞性表(P * dP),兩個表中的向量均是可訓練的參數。然後用文本分析軟件比如Spacy獲得文章和問題中的每個詞的NE和POS,在將對應向量拼接在詞向量之後。由於一個詞的POS和NE和所在語句有關,因此用這種方式獲得的向量編碼可以更好地表達單詞的雨衣,在許多模型中對性能都有明顯的提升!

另一種在MRC裏常見的單詞編碼是精確匹配編碼,對於文章中的單詞w,檢查w是否出現在問題中,如果是則w精確匹配編碼爲1,否則爲0,然後將這個二進制位拼接在單詞向量後。由於單詞可能有變體,所以也可以用另一個二進制位表示w的詞幹是否和問題中某些詞的詞幹一致。精確匹配編碼可以使模型快速找到文章中出現了問題單詞的部分,而許多問題的答案往往就在這部分內容的附近。

字符編碼

在單詞理解中,字符和子詞具有很強的輔助作用。通過字符組合往往可以識別正確的單詞形式(糾錯)。爲了得到固定長度的字符向量編碼,最常用的模型是字符CNN

設一個單詞有K個字符,對應K個字符向量,每個向量維度爲c。字符CNN利用一個窗口大小爲W且有f個輸出通道的CNN,獲得(K-W+1)個f維向量。然後利用最大池化,求得這些向量每個維度上的最大值,形成一個f維向量作爲結果。

"""
首先需要合併前兩個維度batch和seq_len形成三維數據輸入標準CNN網絡,
然後將輸出中第一維展開回兩維,得到batch, seq_len, out_channels三個維度,
即每個單詞得到個out_channels維字符向量
new_seq_len = word_len - window_size + 1
"""
class Char_CNN_Maxpool(nn.Module):
    def __init__(self, char_num, char_dim, window_size, out_channels):
        super(Char_CNN_Maxpool, self).__init__()
        self.char_embed = nn.Embedding(char_num,  char_dim)
        # 1個輸入通道,out_channels個輸出通道,過濾器大小
        self.cnn = nn.Conv2d(1, out_channels, (window_size, char_dim))

    def forward(self, char_ids):
        """
        輸入:爲每個單詞裏的字符編碼char_ids,共4維,包括batch,seq_len,單詞中字符個數word_len和字符向量長度char_dim
        char_ids: (batch, seq_len, word_len),每個詞含word_len個字符編號(0~char_num-1)
        輸出:(batch*seq_len * out_channels)
        """
        x = self.char_embed(char_ids)
        # x: (batch * seq_len * word_len * char_dim )
        x_unsqueeze = x.view(-1, x.shape[2], x.shape[3]).unsqueeze(1) 
        # x_unsqueeze: ((batch*seq_len) * 1 * word_len * char_dim )
        x_cnn = self.cnn(x_unsqueeze)
        # x_cnn:((batch*seq_len) * out_channels * new_seq_len * 1)
        x_cnn_result = x_cnn.squeeze(3)
        # x_cnn_result : ((batch*seq_len) * out_channels * new_seq_len),刪除最後一維
        res, _ = x_cnn_result.max(2)
        # 最大池化,得到:((batch*seq_len) * out_channels)
        return res.view(x.shape[0], x.shape[1], -1)  # (batch*seq_len * out_channels)

上下文編碼

編碼層需要爲每個單詞生成上下文編碼(contextual embedding),這種編碼會隨着單詞的上下文不同而發生改變,從而反映出單詞在當前語句中的含義。

RNN是最常用的上下文編碼生成結構。

交互層

交互層可以交替使用互注意力、自注意力、上下文編碼。通過這些步驟的反覆使用,可以使模型更好理解單詞、短語、句子、片段以及文章的語義信息,同時融入對問題的理解,從而提高預測答案的準確度。

隨着交互層結構複雜化,容易導致參數過多、模型過深,引起梯度消失、梯度爆炸、難以收斂或過擬合等不利於模型優化的現象。因此,一般建議可以從較少的層數開始,逐漸增加註意力和上下文編碼的模塊,同時配合採用Dropout、梯度裁減等輔助手段加速優化進程

互注意力

爲了對文章的單詞向量、問題的單詞向量兩部分的語義進行交互處理,一般採用注意力機制。

在MRC中,可以用注意力機制計算從文章到問題的注意力向量:基於對文章第i個詞pi的理解、對問題單詞向量組(q1,…,qn)的語義總結,得到一個向量piq是向量組Q的線性組合,即從單詞向量pi的角度對單詞向量組Q=(q1,…,qn)進行總結,從而得到Q所代表的語句與單詞pi相關的部分信息,其中與pi相關的Q中單詞獲得相對大的權重。

從圖可知,交互注意力的結果向量個數是文章單詞的個數m,而維度是問題單詞的編碼長度qi

用類似的方法也可以得到從問題到文章的注意力向量qip,這可以使模型在理解每個問題單詞語義的同時兼顧對文章的理解。上兩種方式的注意力機制統稱 互注意力

注意力機制通過注意力函數對向量組Q中的所有向量打分。一般而言,注意力函數需要反映pi和每個向量qj的相似度。常見的注意力函數有:

  • 內積函數,應用在pi和qj維度相同時。
  • 二次型函數,可以在計算過程中加入參數,便於優化
  • 加法形式函數
  • 雙維度轉換函數,此函數將文章和問題單詞向量轉化到低緯度進行相乘,減少了參數個數。
自注意力

在一些文章中,要獲得答案需要理解文章中若干段相隔較遠的部分,可以使用自注意力機制。這使得信息可以在相隔任意距離的單詞間交互,大大提高信息傳遞效率。每個單詞計算注意力向量的過程都是獨立的,可以用並行計算提高運行速度。

下面算法**,使用參數矩陣W將原向量映射到隱藏層**,然後計算內積得到注意力分數。這樣做的好處是可以在向量維度較大時通過控制隱藏層大小降低時空複雜度。

class SelfAttention(nn.Module):
    def __init__(self, dim, hidden_dim):
        super(SelfAttention, self).__init__()
        self.W = nn.Linear(dim, hidden_dim)

    def forward(self, x):
        hidden = self.W(x)                         # 計算隱藏層,結果爲 batch * n * hidden_dim
        scores = hidden.bmm(hidden.transpose(1,2)) # batch * n * n 
        alpha = F.softmax(scores, dim=-1)          # 對最後一維softmax
        attended = alpha.bmm(x)                    # 注意力向量,結果爲batch x dim
        return attended

但是自注意力機制完全捨棄了單詞的位置信息(單詞的順序和出現位置也會對語義產生影響),因此,自注意力可以和rnn同時使用。此外,還可以配合 位置編碼,爲自注意力加入單詞的位置信息。

上下文編碼

(見書)

輸出層

爲了從文章中生成答案,通常將問題作爲一個整體與文章中的單詞進行匹配運算。因此,需要用一個向量q表示整個問題,以方便後續處理。經過交互層的處理,向量q中已包含問題中所有單詞的上下文信息,也包括了文章的信息。

多項式選擇答案生成

爲了預測正確答案,模型需要在輸出層對每個選型計算一個分數,最後選取分數最大的選項作爲輸出。

設一共有K個選項,可以用類似於處理問題的方法分析每個選項的語義:對選項每個單詞進行編碼,在和問題和文章計算注意力向量,從而得到一個向量ck代表第k個選項的語義。然後,綜合文章、問題與選項計算該選項的得分。這裏可以靈活設計各選項得分的網絡結構,下面給出兩種網路結構:

  • 得到一個表示文章的單一向量p,選項得分爲sk=pTWcck + pTWq q。(也就是分別結合計算選項、問題)
  • 對問題向量q和選項向量ck進行拼接,得到tk=[q; ck] ,然後將ti作爲RNN的初試狀態,計算(p1,p2,…pm)的RNN狀態(h1,h2,…hm),利用最後位置的RNN狀態hm和參數向量b的內積,得到選項得分sk=hTm b

因爲是分類問題,所有使用交叉熵作爲損失函數,fcross_entropy=-log(pk*)。

區間式答案

對於一篇長度爲m的文章,可能的區間式答案有m(m-1)/2種。在SQuAD這種數據集上,標準答案長度一般不會太長,一般參賽隊伍會設置爲15個單詞左右,但即使限定答案長度爲L,可能區間答案扔有L*m種可能。所以模型預測的是始末位置。

可以看出,計算始末位置的過程是獨立的。因此也有模型嘗試在兩個計算過程之間傳遞信息,比如FusionNet模型就讓兩個過程之間有了關聯。

兩者均爲多分類任務,因此也可以用交叉熵損失函數:fcross_entropy=-log(p i* S) -log(p j*E) ,即2個多分類問題的交叉熵之和。模型在預測時需要找到概率最大的一組開始位置和結束位置:iR,jR=argmax PS i PE j

"""
答案區間生成:
輸入:prob_s是大小爲m的開始位置概率,prob_e是大小爲m的結束位置概率,均爲一維pytorch張量
設文本共m個詞,L爲答案區間可以包含的最大單詞數
輸出:概率最高的區間在文本中的開始位置和結束位置
"""
def get_best_interval(prob_s, prob_e, L):
    prob = torch.ger(prob_s, prob_e)
    # 從兩個一維輸入向量生成矩陣,獲得m*m的矩陣,其中prob[i,j]=prob_s[i] x prob_e[j]
    prob.triu_().tril_(L-1)
    # 上三角矩陣保證開始位置不晚於結束位置,tril_函數保證候選區間長度不大於L,對這2個操作取一個交集
    # 即如果i>j 或 j-i+1>L,設置prob[i,j]爲0
    prob = prob.numpy()
    # 轉爲numpy數組
    best_start, best_end = np.unravel_index(np.argmax(prob), prob.shape)
    # 獲得概率最高的答案區間在原數組中的索引,開始位置爲第best_start個詞,結束位置爲第best_end個單詞
    return best_start, best_end

自由式答案生成

模型輸出層基本採用encoder-decoder模型。設詞表大小爲|V|,詞表向量維度是d,建立大小爲d*|V|的全連接層將h1 dec 轉化爲一個|V|維向量,表示模型對詞表每個單詞的打分。這些分數經過softmax可以得到預測概率P1,…Pv。一般來說,編碼器的詞表、解碼器的詞表、最終的全連接層共享其中的參數:大小均爲d * |V| 或|V| * d,這樣做既可以減少參數個數,還能大大提高訓練效率和質量。

傳入到第二個RNN單元的時候(第一個答案單詞向量是文本開始位置標識符),輸入單詞向量取決於是否使用Teacher forcing:如果使用,則使用標準答案的第一個單詞,否則使用分數最高的單詞——即模型預測的第一個詞。

對於單詞,一個提升準確率的技巧是,在模型最終輸出答案時將所有生成答案裏的用文章中隨機選擇的單詞替換,這樣可以去除的同時保證準確率不會下降。

注意力機制的應用

答案生成的各個位置的單詞可能與文章中不同的片段有關,注意力機制也用在解碼器中,在生成單詞的時候提供文章的信息。

"""
解碼器注意力機制
"""
class Seq2SeqOutputLayer(nn.Module):
    def __init__(self, embed, word_dim, vocab_size):
        super(Seq2SeqOutputLayer, self).__init__()
        self.emb = embed
        self.vocab_size = vocab_size
        self.encoder_rnn = nn.GRU(word_dim, word_dim, batch_first=True)
        self.decoder_rnncell = nn.GRUCell(word_dim, word_dim)
        # 將RNN狀態和注意力向量的拼接結果降維成word_dim維
        self.combine_state_attn = nn.Linear(word_dim + word_dim, word_dim)
        self.linear = nn.Linear(word_dim, vocab_size, bias=False)
        self.linear.weight =embed.weight

    def forward(self, x, q, y_id):
        """
        x:交互層輸出的文章單詞向量,維度爲 batch * seq_len * word_dim
        q: 交互層輸出的問題向量,維度爲 batch * word_dim
        y_id:真值輸出文本的單詞編號,維度爲 batch * seq_len * word_dim
        輸出預測的每個位置每個單詞的得分爲 word_scores,維度是 batch * y_seq_len * word_dim
        """
        y = self.embed(y_id)
        # 文章每個位置的狀態enc_states,結果維度是 batch * x_seq_len * word_dim
        # 最後一個位置的狀態enc_last_states,維度是 1 * batch* word_dim
        enc_states, enc_last_state = self.encoder_rnn(x, q.unsqueeze(0)) # 問題q爲初始狀態
        # 解碼器的初試狀態爲編碼器的最後一個位置的狀態,維度是 batch * word_dim
        prev_dec_state = enc_last_state.squeeze(0)
        # 最終輸出爲每個答案所有位置各種單詞的得分
        scores = torch.zeros(y_id.shape[0], y_id.shape[1], self.vocab_size)

        for t in range(0, y_id.shape[1]):
            # 前一個狀態和真值文本第t個詞的向量表示,輸入解碼器RNN,得到新的狀態,維度是 batch * word_dim
            new_state = self.decoder_rnncell(y[:,t,:].squeeze(1), prev_dec_state)
            context = attention(enc_states, new_state.unsqueeze(1)).squeeze(1) # batch * word_dim
            new_state = self.combine_state_attn(torch.cat((new_state, context), dim=1))
            # 生成這個位置每個詞表中單詞的預測得分
            scores[:,t,:] = self.linear(new_state) 
            # 此狀態傳入下一個GRUCell
            prev_dec_state = new_state 

        return scores
    

vocab_size = 100
word_dim = 20
embed = nn.Embedding(vocab_size, word_dim)
y_id = torch.longTensor(30, 8).random_(0, vocab_size) # 共30個真值輸出文本的詞id,每個文本長度爲8
# 省略編碼層和交互層,交互層最後得到:
# 文章單詞向量x,維度爲30 * word_dim
# 問題向量q,維度爲 30 * word_dim
x = torch.randn(30, 10, word_dim)
q = torch.randn(30, word_dim)

net = Seq2SeqOutputLayer(embed, word_dim, vocab_size)
optimizer = optim.SGD(net.parameters(), lr=0.01)
word_scores = net(x, q, y_id)
loss_func = nn.CrossEntropyLoss()
# 將word_scores變爲二維數組,y_id變爲一維數組,計算損失函數
# word_scores計算出第2、3、4個詞的預測,所以要和y_id錯開一位比較
loss = loss_func(word_scores[:,:-1, :].contiguous().view(-1, vocab_size), y_id[:,1:].contiguous().view(-1))

optimizer.zero_grad()
loss.backward()
optimizer.step()

用Attention計算上下文向量context,並與編碼器rnn狀態合併爲新的狀態。同時,解碼器使用RNN單元GRUCell,因爲每一步只需要預測下一個位置的單詞。解碼過程使用teacher-forcing,即利用標準答案的單詞預測下一個位置的單詞。

拷貝生成機制

如果純用encoder-decoder架構,即完全利用詞表生成單詞,一旦文章中的重要線索單詞,特別是專有名詞不在詞表中,模型就會產生標識符從而降低準確率。爲了解決這一問題,研究者提出了拷貝-生成機制copy-generate mechanism,使得模型也可以生成不在預定詞表中的文章單詞,從而有效提高生成答案的語言豐富性和準確性。該機制在生成單詞的基礎上提供了從文章中複製單詞的選項。需要生成以下內容:

  • 下一個單詞是文章中第i個詞wi的概率Pc(i)
  • 下一個詞使用生成而非拷貝方式的概率Pgen

然後將兩個分佈合成得到最後的單詞分佈:

Pc(i)本質上是文章中所有位置的一個分佈,即文章中每個位置的單詞在這一步被拷貝的概率。Pgen是一個實數,代表在這個位置使用生成機制的概率,它可以用上下文向量ci-1、前一個RNN單元輸出狀態、當前輸入詞向量hi-1生成Pgen。

二、常見MRC模型

BiDAF

略。

確定了MRC編碼層-交互層-輸出層的結構。

核心就是,交互層,也就是雙向互注意力層,Q2C, C2Q得到的的是兩個文章表示,它們都在不同程度上融合包含了問題的信息,從而幫助下一層的預測!

R-net

略,不過BiDAF和R-Net的閱讀真的是需要循環漸進。先通過BiDAF來了解Q2C,C2Q的向量表示,從而才能更好的瞭解R-net,畢竟r-net是在向量表示的基礎上,再引入了門控。

創新

  • 門機制的應用,比如vt = GRU(vt-1,gate([ut; ct]))。

    ct是t時刻的文章單詞關於問題的Q2C注意力表示,ut是t時刻的文章單詞向量,vt是t時刻的RNN隱層。

    這裏用門機制把ut和ct結合了起來,相當於用一個門控,R-net可以在每一步動態控制RNN的輸入中有多少信息來自當前單詞ct(融入了問題的單詞向量),有多少信息來自問題ut,

  • 結合了門的注意力機制融入進RNN

融合網絡

融合網絡(FusionNet)是一種改進了閱讀理解網絡中多層上下文編碼注意力機制的模型,的主要貢獻在於提高了深層機器閱讀理解網絡的效率。全關注注意力機制基於單詞歷史使用單詞歷史計算注意力分數,但是輸出是其中某一層或某基層的加權和

因爲由於歷史向量隨網絡深度增加而變長,會導致網絡參數過多。維度壓縮可以有效地解決這一問題,它既融合了單詞的所有歷史信息,也大大降低了注意力向量的維度,達到了化繁爲簡的目的。

推算維度的方式可以見5.3.3 總體架構。

單詞歷史

爲了保留多個層次的信息,FusionNet將每個單詞第1至i-1個網絡層的輸出向量表示拼接起來作爲第i個網絡層的輸入。這個拼接而成的向量稱爲單詞歷史(history of word),因爲其代表了之前每個階段處理這個單詞的所有編碼,如X=(x1,x2,…xn)。一方面,History of Word方法使得深層網絡可以更好理解文章各個層次的語義,但另一方面,隨着計算層數的增加,與每個單詞相關的向量編碼總長度也在增加,會造成網絡參數的個數激增,運算效率下降,優化效果也會受到影響

比如說,每個文章中的單詞的向量是900維,然後經過P2Q後,每個文章單詞的向量獲得了300維的注意力向量,即問題GloVe向量的加權和,經過這一層後,每個文章單詞向量的維度爲900+300=1200。

然而,如果僅僅爲了保證效率而不對每層結果進行拼接,就會損失更低層次的信息

如下圖,每個豎條表示一個單詞對應的向量表示。深色塊代表網絡中更高層的輸出及其代表的語義理解。例如,文章中“小明”一詞在經過第一層後,模型理解爲一個人名;經過第二層後,模型理解爲一個球員;經過第三層後,模型理解爲一個有號碼的球員。

由此可以看出,如果缺少單詞歷史的話,雖然文章和問題中的號碼10與12截然不同,但網絡中的較高計算層會認爲這兩個數字都是球隊中的號碼可以較好地匹配,從而錯誤預測答案“小明”。

因此,就要想一個辦法,將這些單詞歷史融合起來!爲了同時保留多層次信息且不影響計算效率,FusionNet提出了全關注注意力機制的概念。

全關注注意力

也就是說,這不僅可以融合單詞歷史信息,還可以實現維度壓縮。在實際應用中,全關注注意力機制可以靈活使用。例如,可以多次計算注意力,每次對不同的歷史層進行加權和,從而獲得更豐富的語義交互信息。

總體架構

談談交互層:單詞注意力層、閱讀層、問題理解層、全關注互注意力層、全關注自注意力層

在交互層中,FusionNet反覆使用全關注注意力機制和循環神經網絡,並控制生成向量的維度,從而減少了模型參數,提高了計算效率

全關注互注意力層

FusionNet將全關注注意力機制應用在文章和問題的單詞歷史上。單詞歷史是一個300+600+250+250=1400維向量,這是一個非常高維的向量表示。因此,FusionNet對維度進行壓縮,融合3個不同層的注意力向量。融合有不同層次的:

  • 低層融合
  • 高層融合
  • 理解層融合

經過融合,都得到一個小維度的向量,遠遠小於單詞歷史。

全關注自注意力層

FusionNet再次使用全關注注意力機制計算文章單詞的自注意力,並通過維度壓縮。

關鍵詞檢索與閱讀模型(ET-RR)

文本庫式閱讀理解任務一般包含一個大型語料庫,因而此類閱讀理解模型必須包含快速而有效的檢索模塊。

關鍵詞檢索與閱讀模型(Essential-Term Retriever Reader Model, ET-RR)是2019年提出的一種文本庫式閱讀理解模型。它專門解決建立在大規模文本庫上的多項選擇式機器閱讀理解任務。ET-RR模型分爲**檢索器(retriever)閱讀器(reader)**兩個模塊。檢索器利用關鍵詞抽取技術大大提高了檢索結果與問題的相關度。而閱讀器中根據檢索結果、問題和選項建立了網絡模型。

ET-RR模型最大的貢獻在於使用深度學習網絡提取問題中的關鍵詞。使用問題關鍵詞作爲查詢可以有效提高檢索結果與問題和選項的相關性,從而提高回答的準確度,這對於文本庫式機器閱讀理解任務至關重要。ET-RR針對多項選擇式答案,模仿人類閱讀解題的方式提出了選項交互的方法,並取得了良好的效果。

檢索器

對於給定的問題Q,將Q作爲查詢輸入檢索器,得到最相關的N個語料庫中的句子,將它們拼接成文章段落P,從而將其轉化成一般的閱讀理解任務。

如果任務是多項選擇形式,除了問題外,還提供N個選項C1, …, CN,一種常用的檢索方法是,將問題Q和每個選項Ci拼接起來作爲一個查詢輸入搜索引擎,得到檢索結果Pi。然後,閱讀器模型計算得出Pi有多大概率證明Ci爲Q的正確答案。最後,模型選擇概率最大的選項輸出

模型的準確性很大程度上取決於檢索器返回的結果的質量,也就是段落與問題及選項的相關度。而搜索引擎對於長查詢的檢索質量遠低於短查詢

ET-RR模型提出了一種解決方案:在問題中選擇若干關鍵詞(essential term),然後將關鍵詞與選項拼接起來作爲查詢。這樣可以有效縮短查詢的長度,而關鍵詞也保留了問題中的重要相關信息。

模型應該如何在一段文本中選取關鍵詞呢?ET-RR將此任務定義爲分類問題:將文本分詞成爲w1w2…|wn後,對每個單詞進行二分類,以預測這個單詞是否爲關鍵詞。爲了提高二分類的準確度,ET-RR設計了分類神經網絡ET-Net

ET-NET

ET-Net的訓練基於公開數據Essentiality Dataset,並使用二分類交叉熵損失函數。如果模型預測到問題中第i個詞作爲關鍵詞的概率Pi大於0.5,則認爲它是關鍵詞,否則認爲它不是關鍵詞。

ET-Net獲得問題Q的關鍵詞之後,將這些關鍵詞與每個選項Ci拼接起來輸入搜索引擎Elastic Search,得到文本庫中與查詢最相關的K個句子。這K個句子就組成了閱讀器的輸入文章Pi。

閱讀器

閱讀器輸入:這一層的輸入包括與問題Q、文章Pi和選項對應的向量Ci,閱讀器的作用是,根據查詢結果產生選項i是Q的正確答案的可能性。閱讀器融合選項之間的語義信息(類似人類做多選閱讀理解)來提升模型性能。

  • 關係編碼

    ET-RR利用大規模單詞關係圖ConceptNet來獲得文章中的每個單詞與問題及選項中的每個單詞間的關係。然後,ET-RR建立關係編碼字典,每種關係有一個對應的10維參數向量。

  • 特徵編碼

    根據文章P中的單詞w是否在問題和選項中出現,ET-RR給w加入了一維0/1精確匹配編碼。此外,w在文本中出現的頻率的對數也作爲一維編碼。

  • 注意力層

    模型計算:①文章到問題的注意力向量;②選項到問題的注意力向量;③選項到文章的注意力向量;

  • 序列模型層

  • 融合層

    在融合層中,問題和選項經過單詞向量加權和各自用一個向量q和c來表示,然後,將文章單詞向量HP與問題向量q進行融合,得到文章的最終表示p

  • 選項交互層

    人類在解決多項選擇題時,經常會通過選項文本之間的區別得到有用的信息以輔助解決問題。基於這一思路,ET-RR將選項間的語義信息差異加入模型計算。

    ci inter 代表選項i與其他選項的差異度。最後,將cifinal =[ ciinter ; ci ]作爲選項i的最終表示。

  • 輸出層

    這一層的輸入包括與問題、文章和選項對應的向量。輸出層計算文章Pn支持第n個選項是問題的正確答案的概率,用分數Sn表示。

    三、預訓練模型與遷移學習

使用預訓練模型的方式與使用預訓練詞向量(如GloVe、Word2vec等)有本質上的區別,因爲預訓練詞向量是一個向量字典,而預訓練模型是一個計算模塊,其中既包括網絡架構,也包括網絡中的參數。

基於翻譯的PTM——CoVe

基於語言模型的ELMo

生成式PTM——GPT

transformer

Transformer有個重要的特性——輸入和輸出維度一致。因此,可以將多個Transformer依次疊加形成更復雜的結構。多層Transformer結構可以分析更深層次的語義信息。

多頭注意力

由於多頭注意力包括h次含參注意力計算,因此大大增加了模型的靈活度。將多頭注意力運用於自注意力機制就可以獲得每個單詞的上下文編碼,且完全不受單詞間距離的影響。

位置編碼

位置編碼有兩種形式:函數型表格型

  • 函數型位置編碼以單詞位置和維度作爲參數。

    (就是transformer的位置函數),PEpos,j 代表第pos個位置的單詞的位置編碼中第j維分量的值

    優點:即使測試時出現比訓練數據更長的語句,即pos值超出訓練範圍,依然可以使用函數計算新位置的編碼值。

    缺點:函數型位置編碼中並沒有可以訓練的參數,因而限制了位置編碼的自由度。

  • 表格型位置編碼根據訓練數據中最長語句的長度L建立一個L × dmodel 的編碼表,作爲參數進行訓練

    優點:靈活

    缺點:測試時無法處理長度大於L的語句

殘差網絡

residual network,目的是降低計算導數時鏈式法則路徑的平均長度。

GPT本身

GPT模型第一次做到了替代目標任務模型的主體部分。

模型利用字節對編碼(BPE)對文本進行分詞。然後,GPT使用滑動窗口的概念,即通過文本中第i-1, i-2, …, i-k個詞預測第i個詞。We爲字典單詞向量矩陣,在模型利用最後一層的結果預測下一個詞ui的時候,用了P(u)=softmax(hi-1 WeT),這裏複用了We,這也是 語言模型中的常用技巧

微調的話,在GPT模型後加入簡單的輸出層,繼續訓練3論左右即可

在微調時加入語言模型損失函數能有效提高模型在目標任務上的準確度,即使用如下的損失函數L3:L3 = L2© + λ*L1©,L1是預訓練語言模型損失函數,L2是模型目標損失函數。

劃時代的BERT

輸入是一段文本中每個單詞的詞向量(分詞由WordPiece生成,類似於BPE),輸出是每個單詞的BERT編碼。BERT模型採用了兩個預訓練任務:雙向語言模型和判斷下一段文本。這兩個任務均屬於無監督學習,即只需要文本語料庫,不需要任何人工標註數據。

BERT的創新之處在於:第一,利用掩碼設計實現了雙向語言模型,這更加符合人類理解文本的原理;第二,通過判斷下一段文本增強了BERT分類能力,提高了預訓練任務與目標任務的契合度。

雙向語言模型

只有單層transformer才能在第i個位置的輸出編碼不依賴於單詞i的信息,因爲第二層transformer的時候,zi=f(y1,y2,…,yi-1,yi=1,…,yn),但是這些y向量中已經包含了xi的信息!這樣就會導致zi不能用來預測第i個位置的單詞!爲了解決這個問題,BERT提出了掩碼(masking)機制,就是在一段文本中隨機挑選15%的單詞,以掩碼符號[mask]代替。利用多層Transformer機制預測這些位置的單詞。由於輸入被mask的單詞沒有任何信息,這些位置上的Transformer輸出可以用來預測對應的單詞。因此BERT是一個雙向語言模型。但是爲了預防預訓練任務與之後的目標任務產生不一致,所以用80%-10%-10%的概率策略選擇單詞存留。

NSP

設起始符[CLS]位置的BERT編碼爲xcls,則模型預測B文本是A文本的下一段文本的概率爲σ(W*xcls), W爲參數矩陣

具體任務

BERT遷移到文本分類型任務時,需要將所有信息拼接成一段文本,然後在BERT模型上添加一個前向網絡,將[CLS]位置對應的BERT編碼h0轉化成得分。

在序列標誌型任務,比如區間答案型閱讀理解任務中,定義BERT輸入爲[CLS] Q [SEP] P。然後分別計算答案在文本每個位置開始概率和結束概率,然後將這些概率與標準答案的位置輸入交叉熵損失函數進行訓練。

在以上兩種任務的微調訓練中,BERT自身的參數和添加的前向網絡參數均需要訓練,但一般只需要在目標任務上訓練2~4輪

# 安裝包含在BERT在內的Transformer軟件包
$ pip install pytorch-transformers

import torch
from pytorch_transformers import *

# 使用bert-base模型,不區分大小寫
config = BertConfig.from_pretrained('bert-base-uncased')
# bert使用的分詞工具
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 載入爲區間答案型閱讀理解任務預設的模型,包括前向網絡輸出層
model = BertForQuestionAnswering(config)

# 處理訓練數據
# 獲得文本分詞後的單詞編號,維度爲 batch_size(1) * seg_length(4)
input_ids = torch.tensor(tokenizer.encode('this is an example')).unsqueeze(0)
# 標準答案在文本中的起始位置和終止位置,維度爲batch_size
start_positions = torch.tensor([1])
end_positions = torch.tensor([3])
# 獲得模型的輸出結果
outputs = model(input_ids, start_positions=start_positions, end_positions=end_positions)
# 得到交叉熵損失函數值loss,以及模型預測答案在每個位置開始和結束的打分start_scores與end_scores, 維度均爲batch_size(1) * seq_length
loss, start_scores, end_scores = outputs 

BERT的改進措施【重要】

在BERT出現之後,對於新的模型架構的研究相對減少,研究者更多關注的是如何在給定BERT模型的情況下,改進訓練數據的收集方法、預訓練和微調的模式

  • 更多預訓練任務

    比如改進了掩碼的使用方式,利用掩碼代替短語或句子,而非單個單詞

  • 多任務微調

    在微調階段,可以對多個目標任務同時進行訓練,即將多種類型任務的數據混合起來進行訓練。這些任務共享BERT的基本網絡結構,但各自擁有和自身任務相關的前向網絡輸出層。這些目標任務彼此之間不必相似,可以包括閱讀理解、語句相似度判斷、語句釋義等多種類型。實驗表明,多任務微調可以提高模型在每個子任務上的表現

  • 多階段處理

    可以先在和目標任務相關的語料上繼續預訓練BERT,再針對目標任務(比如醫療、法律)進行微調。

  • 將BERT作爲編碼層

    由於BERT模型的規模巨大,在目標任務上微調也需要大量的計算資源。在資源有限的情況下,可以將BERT作爲編碼層,並固定其中的所有參數。之後,和ELMo模型類似,需要對BERT每一層Transformer的輸出計算含參加權和,以便在後續網絡層中使用。由於BERT的參數不參與訓練,因而大大減少了對計算資源的需求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章