當前位置:成語大全網 - 書法字典 - 如何用PyTorch實現遞歸神經網絡

如何用PyTorch實現遞歸神經網絡

從Siri到谷歌翻譯,深度神經網絡對自然語言的機器理解有了很大突破。這些模型大多將語言視為單調的單詞或字符序列,並使用壹種稱為遞歸神經網絡/RNN的模型來處理該序列。然而,許多語言學家認為,語言最好理解為具有樹狀結構的層次短語。壹種叫做遞歸神經網絡的深度學習模型考慮到了這種結構,在這方面已經做了很多研究。雖然這些模型實現起來非常困難,效率也很低,但壹個全新的深度學習框架PyTorch可以讓它們和其他復雜的自然語言處理模型變得更容易。

雖然遞歸神經網絡很好地展示了PyTorch的靈活性,但它也廣泛支持其他深度學習框架。特別是可以為計算機視覺計算提供強有力的支持。PyTorch是臉書人工智能研究所和其他幾個實驗室的開發者的成果。這個框架結合了Torch7高效靈活的GPU加速後端庫和直觀的Python前端。它的特點是快速原型化、可讀代碼和支持最廣泛的深度學習模型。

開始旋轉

鏈接中的文章(/jekbradbury/examples/tree/SPINN/snli)詳細介紹了遞歸神經網絡的PyTorch實現,它有遞歸跟蹤器和TreeLSTM節點,也稱為SPINN——SPINN是自然語言處理中使用的深度學習模型的壹個例子,很難通過很多流行的框架來構建。這裏的模型實現使用了批處理,所以可以通過GPU加速,使得運行速度明顯快於沒有批處理的版本。

SPINN即stack-augmented parser-interpreter神經網絡(stack-augmented parser-interpreter neural network),是Bowman等人在2016中提出的壹種解決自然語言推理任務的方法。本文使用了斯坦福大學的SNLI數據集。

任務是將句子對分為三類:假設句子1是壹個不可見圖像的確切標題,那麽句子2(a)是肯定的(b)是可能的還是(c)肯定不是壹個準確的標題?(這幾類分別叫蘊涵、中性、矛盾)。比如壹句話是“兩只狗正在跑過壹片田野”,言外之意可能會讓這句話對變成“戶外動物”,中立可能會讓這句話對變成“壹些小狗在跑,想抓住壹根棍子”,矛盾可能會讓這句話對變成“寵物坐在沙發上”。

特別是,研究SPINN的最初目標是在確定句子之間的關系之前,將每個句子編碼成壹個固定長度的向量表示(還有其他方法,比如在註意力模型中用軟聚焦方法比較每個句子的每個部分)。

數據集是機器用句法分析樹的方法生成的,句法分析樹將每個句子中的詞分成具有獨立意義的短語和分句,每個短語由兩個詞或子短語組成。許多語言學家認為,人類通過上面提到的樹的層次方式來結合詞和意義並理解語言,因此值得嘗試以同樣的方式構建神經網絡。以下示例是數據集中的壹個句子,其解析樹由嵌套的括號表示:

( (教堂))((天花板上有裂縫)。) )

對這個句子進行編碼的壹種方法是使用具有解析樹的神經網絡來構造神經網絡層Reduce,它可以組合詞對(通過詞嵌入表示,例如GloVe)和/或短語,然後遞歸地應用這個層(函數)來對句子進行編碼:

X =減少(“the”,“ceiling”)

Y =減少(" in ",X)

...等等。

但是,如果我希望網絡以更像人類的方式工作,從左向右閱讀並保留句子的上下文,同時仍然使用解析樹來組合短語,該怎麽辦?或者,如果我要訓練壹個網絡來建立自己的解析樹,讓它根據看到的單詞來讀句子?這是編寫解析樹的相同但略有不同的方式:

教堂)天花板有裂縫)))))。) )

或者以第三種方式,如下所示:

話說:教堂天花板有裂縫。

語法分析:S S R S S S S S R R R R S R R

我所做的只是刪除了左括號,然後用“s”標記“shift ”,用“r”替換右括號表示“reduce”。但是現在妳可以從左到右讀取信息作為壹組指令來操作壹個棧和壹個類似於棧的緩沖區,妳可以得到和上面遞歸方法完全壹樣的結果:

1.將單詞放入緩沖區。

2.從緩沖區的前面彈出“The”並將其推到堆棧的上層,後面是“church”。

3.彈出前兩個堆棧值,應用它們進行Reduce,然後將結果推回堆棧。

4.從緩沖區彈出“has ”,然後將其推入堆棧,然後“cracks ”,然後“in ”,然後“the ”,然後“ceiling”。

5.重復四次:彈出兩個棧值,應用減少,然後推送結果。

6.砰”並將其推送到堆棧的上層。

7.重復兩次:彈出兩個棧值,應用減少,然後推送結果。

8.彈出剩余的堆棧值,並將其作為句子代碼返回。

我還想保留句子的上下文,以便在對句子的後半部分應用Reduce層時,考慮系統已經讀取的句子部分的信息。所以我將把兩個參數的Reduce函數替換成壹個三個參數的函數,它的輸入值是壹個左子句、壹個右子句和當前句子的上下文狀態。這種狀態是由神經網絡的第二層(稱為環路跟蹤器的單元)創建的。給定當前句子的上下文狀態、緩沖區中的頂部條目B和堆棧中的前兩個條目s1\s2,跟蹤器在堆棧操作的每壹步(即讀取每個單詞或右括號)之後生成新的狀態:

上下文[t+1] = Tracker(上下文[t],b,s1,s2)

很容易想象用自己喜歡的編程語言編寫代碼來做這些事情。對於每壹個要處理的句子,它會從緩沖區加載下壹個單詞,運行跟蹤器,檢查是否將該單詞推送到堆棧上或者執行Reduce函數來執行操作;然後重復,直到整句處理完畢。通過單個句子的應用,這個過程構成了壹個龐大而復雜的深度神經網絡,它的兩個可訓練層通過堆疊操作反復應用。但是,如果妳熟悉TensorFlow或Theano等傳統深度學習框架,妳就知道它們很難實現這樣的動態過程。值得花些時間回顧和探索PyTorch的與眾不同之處。

圖論

圖1:函數的圖形結構表示

深度神經網絡本質上是壹個有大量參數的復雜函數。深度學習的目的是通過計算損失函數測量的偏導數(梯度)來優化這些參數。如果將函數表示為計算圖結構(圖1),這些梯度的計算可以通過向後遍歷圖來實現,不需要多余的工作。每個現代深度學習框架都是基於這種反向傳播的概念,所以每個框架都需要壹種方法來表示計算圖。

在很多流行的框架中,包括TensorFlow,Theano和Keras,以及Torch7的nngraph庫,計算圖都是預先構建的靜態對象。圖形是由類似數學表達式的代碼定義的,但它的變量實際上是沒有保存任何數值的占位符。將圖形中的占位符變量編譯到函數中,然後可以對該批訓練集重復運行該函數,以生成輸出值和梯度值。

這種靜態計算圖的方法對於固定結構的卷積神經網絡非常有效。但是在許多其他應用中,根據數據使神經網絡的圖形結構不同是有用的。在自然語言處理中,研究者通常希望通過每個時間步輸入的單詞來擴展(確定)循環神經網絡。上述SPINN模型中的堆棧操作很大程度上依賴於控制流(如for和if語句)來定義特定句子的計算圖結構。在更復雜的情況下,您可能需要構建壹個模型,其結構取決於模型自身子網的輸出。

這些想法中的壹些(雖然不是全部)可以機械地應用到靜態圖系統中,但幾乎總是以降低透明度和增加代碼混亂為代價。框架必須在其計算圖中添加特殊節點,這些節點表示循環和條件等編程原語,用戶必須學習和使用這些節點,而不僅僅是編程代碼語言中的for和if語句。這是因為程序員使用的任何控制流語句都只會運行壹次,程序員在構建圖時需要硬編碼壹條計算路徑。

比如TensorFlow中需要壹個特殊的控制流節點tf.while_loop,通過字向量(從初始狀態h0開始)運行遞歸神經網絡單元(rnn_unit)。您需要壹個額外的特殊節點來在運行時獲取單詞長度,因為當您運行代碼時,它只是壹個占位符。

#張量流

#(此代碼在模型初始化期間運行壹次)

#“單詞”不是壹個真正的列表(它是壹個占位符變量),所以

#我不會用“len”

cond =λI,h:I & lt;tf.shape(words)[0]

cell = lambda i,h: rnn_unit(words[i],h)

i = 0

_,h = tf.while_loop(cond,cell,(I,h0))

基於動態計算圖的方法與以往的方法有著本質的不同。它有幾十年的學術研究歷史,包括哈佛的Kayak,親筆簽名和以研究為中心的框架Chainer和DyNet。在這樣的框架中(也稱為運行定義),計算圖在運行時被建立和重構,並且相同的代碼被用於執行前向傳遞的計算,同時所需的數據結構也被建立用於反向傳播。此方法可以生成更直接的代碼,因為控制流可以使用標準的for和if編寫。它還使調試更容易,因為運行時斷點或堆棧跟蹤將跟蹤實際編寫的代碼,而不是執行引擎中編譯的函數。壹個簡單的Python for loop可以用在壹個動態框架中,實現壹個相同變長的循環神經網絡。

# PyTorch(也在Chainer中工作)

#(此代碼在模型的每次向前傳遞時運行)

#“單詞”是壹個包含實際值的Python列表

h = h0

逐字逐句:

h = rnn_unit(字,h)

PyTorch是第壹個由運行定義的深度學習框架,它匹配靜態圖框架(如TensorFlow)的功能和性能,使其非常適合從標準卷積神經網絡到最瘋狂的強化學習的想法。所以讓我們看看SPINN的實現。

密碼

在開始構建網絡之前,我需要設置壹個數據加載器。通過深度學習,可以批量處理數據樣本來操作模型,並行化加速訓練,每壹步的梯度變化都更加平滑。我覺得這裏可以這樣做(後面我會解釋上面的棧操作過程是如何批處理的)。下面的Python代碼使用PyTorch的文本庫中內置的系統來加載數據,該系統可以通過連接相似長度的數據樣本來自動生成批處理。運行這段代碼後,train_iter、dev_iter和test_itercontain循環通過訓練集、驗證集和測試集的批處理。

從torchtext導入數據,數據集

TEXT = datasets . snli . parsedtextfield(lower = True)

TRANSITIONS = datasets . snli . shiftreducefield()

標簽=數據。現場(順序=假)訓練、開發、測試=數據集。SNLI.splits(

TEXT,TRANSITIONS,LABELS,wv _ type = ' glove . 42b ')TEXT . build _ vocab(train,dev,test)

train_iter,dev_iter,test_iter = data。BucketIterator.splits(

(培訓、開發、測試),batch_size=64)

您可以在train.py中找到設置訓練周期和精度測量的其余代碼。讓我們繼續。如上所述,SPINN編碼器包括壹個參數化的Reduce層和壹個可選的循環跟蹤器來跟蹤句子上下文,以便在網絡每次讀取壹個單詞或應用Reduce時更新隱藏狀態;下面的代碼表示創建SPINN僅僅意味著創建這兩個子模塊(我們將很快看到它們的代碼)並將它們放在容器中以備後用。

從火炬進口nn進口火炬

#對PyTorch的神經網絡包中的模塊類進行子類化

SPINN類(NN。模塊):

def __init__(self,config):

超級(SPINN,self)。__init__()

self . config = config self . Reduce = Reduce(config . d _ hidden,config.d_tracker)

如果config.d_tracker不為None:

self . Tracker = Tracker(config . d _ hidden,config.d_tracker)

紡紗。創建模型時調用了壹次_ _ init _ _它分配和初始化參數,但不執行任何神經網絡操作或構建任何類型的計算圖。在每個新的批處理數據上運行的代碼由SPINN.forward方法定義,這是標準的PyTorch名稱,用於在用戶實現的方法中定義模型的轉發過程。上面描述的是棧操作算法的有效實現,也就是壹般Python中,它運行在多個緩沖區和棧上,每個例子對應壹個緩沖區和棧。我使用轉換矩陣中包含的壹組“shift”和“reduce”操作進行叠代,運行跟蹤器(如果存在),並遍歷批中的每個樣本,以應用“shift”操作(如果需要)或將其添加到需要“reduce”操作的樣本列表中。然後對列表中的所有樣本運行Reduce層,並將結果推回到各自的堆棧中。

定義轉發(自身、緩沖、轉換):

#輸入作為單詞嵌入的單個張量進入;

#我需要它是壹個堆棧列表,每個例子都有壹個

#我們可以獨立彈出的批次。中的單詞

#每個例子都已經被顛倒了,這樣它們就可以

#從每壹頁的末尾彈出,從左到右閱讀

#列表;它們的前綴也是空值。

buffers =[list(torch . split(b . squeeze(1),1,0))

對於火炬中的b . split(緩沖區,1,1)]

#我們還需要在每個堆棧的底部有兩個空值,

#所以我們可以從輸入中的空值復制;這些空值

#都是必需的,這樣即使

#緩沖區或堆棧為空

stacks = [[buf[0],buf[0]] for buf in buffers]

if hasattr(self,' tracker '):

self.tracker.reset_state()

對於過渡中的trans_batch:

if hasattr(self,' tracker '):

#我之前描述追蹤器取4

# arguments (context_t,b,s1,s2),但這裏我

#將堆棧內容作為單個參數提供

#在跟蹤器中存儲上下文時

#對象本身。

tracker_states,_ = self.tracker(緩沖區,堆棧)

否則:

tracker _ States = ITER tools . repeat(無)

左,右,跟蹤= [],[],[]

batch = zip(trans_batch,buffers,stacks,tracker_states)

對於過渡、緩沖、堆棧、批量跟蹤:

如果transition == SHIFT:

stack.append(buf.pop()

elif轉換==減少:

rights.append(stack.pop())

lefts.append(stack.pop())

trackings.append(跟蹤)

如果權利:

reduced = ITER(self . reduce(left,rights,trackings))

對於轉換,在zip中堆棧(trans_batch,stacks):

如果轉換==減少:

stack.append(next(reduced))

返回[stack.pop() for stack in stacks]

打電話給自己的時候。追蹤者或自我。分別還原、運行跟蹤器的正向方法或還原子模塊。該方法需要對樣本列表應用正向操作。在main函數的forward方法中,對不同的樣本獨立操作是有意義的,即在批處理中為每個樣本提供單獨的緩沖區和堆棧,因為所有大量使用數學和需要受益於批處理執行的GPU加速的操作都在Tracker和Reduce中進行。為了更幹凈地編寫這些函數,我將使用壹些助手(稍後定義)將這些樣本列表轉換為批處理張量,反之亦然。

我想讓Reduce模塊自動批處理它的參數以加快計算速度,然後解除批處理,這樣就可以分別推送和彈出它們了。用於將每對左右子短語的表達組合成母短語的實際組合函數是TreeLSTM,它是通用循環神經網絡單元LSTM的變體。組合函數要求每個子短語的狀態實際上由兩個張量組成,壹個隱藏狀態H和壹個存儲單元狀態C,並且函數是使用在子短語的隱藏狀態中操作的兩個線性層和壹個nn。線性組合函數tree_lstm,其將線性層的結果與子短語的存儲單元狀態相組合。在SPINN中,通過添加在Tracker的隱藏狀態下運行的第三個線性層來擴展該方法。

圖2: Treel STM組合函數添加了第三個輸入(X,在本例中是跟蹤器狀態)。在下面顯示的PyTorch實現中,五組三個線性變換(由藍色、黑色和紅色箭頭的三元組表示)被組合成三個nn。線性模塊,tree_lstm函數執行位於框中的所有計算。圖片來自陳等人(2016)。