Recurrent Neural Network[SRU]



0.背景

對於如機器翻譯、語言模型、觀點挖掘、問答系統等都依賴於RNN模型,而序列的前后依賴導致RNN並行化較為困難,所以其計算速度遠沒有CNN那么快。即使不管訓練的耗時程度,部署時候只要模型稍微大點,實時性也會受到影響。

Tao Lei等人基於對LSTM、GRU等模型的研究,提出了SRU模型。在保證速度的前提下,准確度也是沒有多少損失。

1.SRU

Tao Lei等人通過將每一時間步的主要計算部分,優化為不要去依賴之前時間步的完整計算,從而能夠容易的並行化。其結果示意圖如圖1.1。

圖1.1 普通的RNN結構和SRU結構
大多數RNN模型如LSTM,GRU等都是通過門來控制信息流的傳輸,從而緩解梯度消失和爆炸的問題。

ps:其中所謂的門就是將輸入向量連接到一個門層(向量),然后以sigmoid激活函數來計算當前的可流通量(通俗點說,就是得到一個sigmoid的值向量,去與所需要的其他狀態向量逐點相乘,即每個維度上都有門控制)

在前饋神經網絡中,特別是矩陣相乘是最耗時的部分了,而如果是兩個矩陣逐點相乘,那計算量倒是少了好多。所以SRU的主要設計原理就是:門計算只依賴於當前輸入的循環。這樣就使得模型只有逐點矩陣相乘的計算是依賴於之前的時間步的。從而能夠讓網絡容易的進行並行化。

我們基於參考文獻[1]來進行對應的結構展示:

圖1.2基礎組件

圖1.3 標准RNN結構圖

圖1.4 LSTM結構圖
現在主流的RNN結構都會在當前時間步上用到上一個時間步的隱藏層輸出\(h_{t-1}\)。例如在LSTM中,遺忘門的計算\(f_t=\sigma(W_fx_t+R_fh_{t-1}+b_f)\)。其中涉及到的\(Rh_{t-1}\)就破壞了獨立性和並行性。這樣相似性的設計在GRU和其他RNN變種中都能找到。而SRU的設計是完全丟棄了當前時間步門的計算會依賴之前時間步的\(h_{t-1}\),\(c_{t-1}\)。所以現在的計算瓶頸是圖1.5中式子1-3中的三個矩陣相乘了。在計算完\(\widetilde{x_t},f_t,r_t\)之后,剩下的就是逐點計算了,這時候就很快了。

SRU完整的公式如下:

圖1.5 SRU結構公式

圖1.6 SRU結構圖(自己用visio畫的)

2. SRU工程優化

優化SRU和在cuDNN LSTM中優化LSTM的套路差不多,其中主要涉及到2點:

  • 所有時間步的矩陣相乘可以批次處理,這可以明顯提升計算效率和GPU的使用。如將圖1.5中的式子1-3的三個權重矩陣合並成一個大矩陣。如下:

\[U^T= \begin{pmatrix} W \\ W_f \\ W_r \end{pmatrix} [x_1,x_2,...,x_n ] \]

其中n表示序列的長度,是將n個輸入向量聯合起來,即每個\(x_i\)都是一個向量,\(U\in R^{n \times 3d}\),d表示SRU模塊中的隱藏層維度,當輸入是一個mini-batch為k個序列的時候,U就是一個size為\((n,k,3d)\)的張量;

  • 所有逐元素相乘的操作都可以放入一個kernel函數(cuda中的一個術語)中。如果不這么做。那么加法和sigmoid的激活函數就會分別需要調用各自獨立的函數,並且增加額外的kernel運行延遲和數據移動的開銷(這些都和gpu的計算有關,感興趣的可以學習cuda)。

下面就是kernel函數的偽代碼(CUDA的),其中省略了輸入向量\(x_i\)本身的維度,只涉及到序列的長度,mini-batch的大小和SRU模塊中隱藏層的維度

圖2.1 kernel函數偽代碼

#python形式的cuda偽代碼,因為gpu編程的特性,所以都是基於標量進行具體的操作的
def kernel(xTensor, UTensor, bFVector, bRVector, c0Matrix):
    #xTensor:tensor for input, size is (n, k, d), means sequenceLength by minibatch by hiddenState
    #UTensor:tensor for weight, size is (n, k, 3d) means sequenceLength by minibatch by [W,Wf,Wr],3d
    #bFVector:vector for forget bias, size is (1,d)
    #bRVector:vector for reset bias, size is (1,d)
    #c0Matrix: matrix for SRU state,size is (k,d) means minibatch by hiddenState
    h, c = np.zeros([n,k,d]), np.zeros([n,k,d])
    #one sample in minibatch
    for i in range(1,k):
        #one dimension in 
        for j in range(1,d):
            c = c0Matrix[i,j]
            for l in range(1,n):
                W, Wf, Wr = UTensor[l,i,j], UTensor[l,i,j+d], UTensor[l,i,j+2d]
                f = sigmoid(Wf+bFVector[j])
                r = sigmoid(Wr+bRVector[j])
                c = f*c+(1-f)*W
                h = f*tanh(c)+(1-r)*xTensor[l,i,j]
                c[l,i,j] = c
                h[l,i,j] = h
    return h,c

如上面所示,通過GPU的網格等多線程操作,在外面2個for可以實現並行操作,最內部的for是基於序列順序的,也就是在實現的時候,只有這個維度上是需要前后關聯的,而在minibatch這個維度和hiddenState這個維度都可以分開,也就是都可以並行,只有sequence維度需要前后管理啊,那么這個維度放入寄存器中保持先后關系即可,而minibatch和hiddenState這兩個維度可以看成是網格的x,y軸。
這就是矩陣逐元素相乘比矩陣相乘塊的好處:gpu特喜歡這種逐元素相乘的;對矩陣相乘的,如果矩陣大了,還需要做分塊處理。

通過如圖2.2的結構設計,在實驗上,可以發現速度還是有很可觀的提升的。

圖2.2 SRU與其他模型的結果對比

參考文獻:

  1. 理解LSTM


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM