4.建立基于門控循環(huán)單元(GRU)或者長短時記憶(LSTM)的RNN模型
在這個步驟,我們的目的是使用RNN網(wǎng)絡(luò)建立和訓(xùn)練語言模型。語言模型的作用是,當(dāng)我們給出一個由m個單詞組成的句子,它可幫助預(yù)測下一個單詞是什么,包括這一單詞出現(xiàn)的概率,也就是條件概率。
舉個例子,“He went to buy some chocolate”這個句子生成的概率,實際上就是給定了“He”這個條件之后出現(xiàn)“went”的概率,乘以給定了“He went”這個條件之后出現(xiàn)“to”的概率,再乘以給定“He went to”這一條件后出現(xiàn)“buy”的概率……以此類推(嗯,有點繞,大家可以多讀幾遍)。
那么,我們?yōu)槭裁匆柚怕暑A(yù)測句子呢?
首先,該語言模型可作為一個評分模型。例如,在機器翻譯系統(tǒng)中輸入句子,通常會得出多個候選解。在這樣的情況下,就可以使用語言模型,選擇可能性最大的句子作為輸出。
此外,語言模型是一種生成式模型,我們還可以通過給定前面的單詞,預(yù)測下一個詞的概率,并重復(fù)該過程,從而生成新的文本。
需要注意的是,上述概率公式中的每個單詞的概率都是由它前面所有給定的單詞共同決定的。但是由于受計算或內(nèi)存限制,很難長期存儲大量模型。因此,許多模型通常只能關(guān)聯(lián)到前面幾個詞。
下面具體介紹一下基于RNN進行語言建模的代碼實現(xiàn)。
在訓(xùn)練語言模型之前,需要先對數(shù)據(jù)(即特定文本)進行訓(xùn)練。就像小孩學(xué)說話,都是通過大量的詞匯練習(xí),才慢慢形成說話習(xí)慣。
非常方便的是,這一語言模型的訓(xùn)練通過原始文本就可以實現(xiàn),不需要對數(shù)據(jù)做任何人工標(biāo)記。在這里,我們從 Google's BigQuery 的一個數(shù)據(jù)集中下載了15,000條Reddit的長篇評論作為文本數(shù)據(jù)。在此之前,與其它機器學(xué)習(xí)項目一樣,還要先通過預(yù)處理將數(shù)據(jù)轉(zhuǎn)換為正確格式。
如果想要預(yù)測每個詞的概率,就需要先將原始文本中的每一段文字拆分成句子,再將句子拆分成單詞。雖然也可以用空格隔開每段文字,但這種方式無法恰當(dāng)處理標(biāo)點符號。例如,“He left!”應(yīng)分割成3個詞:“He”,“left”,“!”。通過NTLK(http://www.nltk.org/)的分詞系統(tǒng)中的word_tokenize和sent_tokenize兩種方法,可以分別實現(xiàn)對英文的分詞和分句。(ps.NLTK也支持中文接口)
需要注意,在語料庫中,很多單詞在只出現(xiàn)一到兩次,而龐大的詞匯表會降低模型的訓(xùn)練速度,除此之外,對于低頻詞我們也沒有足夠的上下文信息支持訓(xùn)練,因此,在訓(xùn)練前不妨先刪去這些低頻詞。
在代碼中, vocabulary_size代表著詞匯的規(guī)模(此處我們將大小設(shè)置為8000,代表8000個最常見單詞,這個數(shù)值可以修改)。對于詞匯表里沒有的單詞,我們設(shè)置為 UNKNOWN_TOKEN。例如,如果詞匯表中沒有“nonlinearities”,那么句子“nonlineraties are important in neural networks”就會變?yōu)?ldquo;UNKNOWN_TOKEN are important in neural networks”。在這里, UNKNOWN_TOKEN也是詞匯表的一部分,我們會預(yù)測它的概率。在生成新文本時,如果預(yù)測出來的單詞是UNKNOWN_TOKEN,就可以選擇用詞匯表之外的任一單詞來替代它。例如,隨機選取詞匯表以外的詞,或者直接生成句子,直到生成的句子不包含未知詞為止。
我們在每個句首添加了標(biāo)簽 SENTENCE_START ,句末添加了標(biāo)簽 SENTENCE_END ,以此作為標(biāo)記來識別模型文本的開頭和結(jié)尾。
在RNN網(wǎng)絡(luò)中我們的輸入都是向量,而非數(shù)據(jù)集里的字符串。因此,在代碼中,需要通過index_to_word和word_to_index兩種方法在單詞和索引之間創(chuàng)建一個映射,從而把字符串映射成向量。例如,單詞“friendly”的索引可能是2001。訓(xùn)練示例 x 可用向量[0,179,341,416]表示,其中0相當(dāng)于 SENTENCE_START ,相應(yīng)開始符y為[179,341,416,1]。
我們的目標(biāo)是預(yù)測下一個詞,所以 y 是向量 x 的左側(cè)一位,而最后一個詞就是SENTENCE_END。也就是說,單詞179的正確預(yù)測是341,即下一個單詞。
下面是文本中的一個實際訓(xùn)練示例:
關(guān)于RNN的基本介紹,可以參考本教程的第一篇文章《循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)的基本介紹》。
▲RNN的結(jié)構(gòu)圖
在RNN中所輸入的x是一個單詞序列,每個x_t是一個單獨的詞。但根據(jù)矩陣乘法運算原理,我們不能直接使用上述的單詞索引表達方法,需要將單詞表示為vocabulary_size的實數(shù)向量。例如,索引為36的單詞均為0,只有第36位為1。所以, x_t是向量,x為矩陣,行即單詞。我們將在神經(jīng)網(wǎng)絡(luò)代碼中進行轉(zhuǎn)換,而非在預(yù)處理階段。類似的,網(wǎng)絡(luò)的輸出o也有類似格式,o_t是vocabulary_size元素的向量,而元素表示句中該單詞作為下一個單詞出現(xiàn)的概率。
回顧一下教程第一部分中RNN的公式:
另外,我們還發(fā)現(xiàn),把每一層的矩陣和向量的維度記錄下來將非常有助于理解神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)。在這里,我們假設(shè)詞匯量C=8000,記憶單元之間的神經(jīng)元數(shù)量(也稱之為隱藏層)H=100,它的大小相當(dāng)于該網(wǎng)絡(luò)的“記憶容量”,這意味著這個容量越大,學(xué)習(xí)的模式就越復(fù)雜,當(dāng)然也會增加額外計算量。
下面就是整個結(jié)構(gòu)的大小:
這是非常有價值的信息。U, V和W代表的是神經(jīng)網(wǎng)絡(luò)權(quán)重參數(shù)。因此,我們需要掌握的參數(shù)數(shù)量為2HC + H^2,在具體的網(wǎng)絡(luò)中,當(dāng)C=8000,H=100時,參數(shù)值為1,610,000。需要注意的是,實數(shù)向量x_t與U相乘,只是一個選擇U的column的過程,本質(zhì)上沒什么計算量,所以無需進行完整運算。而該神經(jīng)網(wǎng)絡(luò)中最大的矩陣乘積在Vs_t這一步。這也是我們要求詞匯表盡量小的原因。
掌握了這些,我們就可以開始實現(xiàn)以下操作:
先從選擇一個初始化所有權(quán)重參數(shù)的RNN開始,并將其命名為RNNNumpy,后面還會有一個Theano版本。初始化U, V和W有些復(fù)雜,不能簡單將其初始化為0,這將造成所有層級的對稱運算。非常重要的是,因為初始化數(shù)值會對最后的訓(xùn)練結(jié)果產(chǎn)生影響,所以必須進行隨機初始化。
該領(lǐng)域的大量研究表明,初始化的最佳效果由激活函數(shù)(此處指tanh)決定。用等距隨機抽樣的方式,以范圍中上一層的傳入連接數(shù)n為間距來初始化權(quán)重,這是比較好的方法。盡管看似復(fù)雜,但無需過于擔(dān)心,只要將參數(shù)初始化為小的隨機值,通常都可以正常運行。
下面是代碼:
在上面的代碼中,word_dim表示詞匯量的大小,hidden_dim表示隱藏層(可自行選擇)的大小。另外還有bptt_truncate參數(shù)。
接下來,介紹一下前向傳播算法如何實現(xiàn)(用于預(yù)測詞的概率):
值得注意的是,為了避免二次運算,在這個函數(shù)的最后,不僅僅返回了輸出層,還返回了隱藏層,從而來計算梯度。在這里,每一個o_t都表示一個8000維的單詞概率向量,代表每一個單詞的輸出概率。在模型評估過程中,我們想得到的往往只是概率最大的單詞,所以,在下面,我們將用一個predict函數(shù)來實現(xiàn):
來試試我們?nèi)碌膶崿F(xiàn)方法,看一下它的輸出示例:
對于句子中的每一個單詞(圖中的句子有45個單詞),我們的模型輸出了8000個數(shù)值,對應(yīng)詞典中每一個單詞可能作為句子中下一個單詞出現(xiàn)的概率。由于已將U,V,W初始化為隨機值,這些預(yù)測是完全隨機的。下面列出了每個單詞的最高預(yù)測概率的索引:
為訓(xùn)練該神經(jīng)網(wǎng)絡(luò),需要通過一種方法量化誤差,目的是找到能將訓(xùn)練數(shù)據(jù)的誤差最小化的最優(yōu)U,V,W參數(shù),我們將這種方法稱為損失函數(shù)L。通常我們會選擇一個稱為cross-entropy loss(交叉熵損失函數(shù))的損失函數(shù)。假設(shè)有N個訓(xùn)練樣本(文本中的單詞)以及C個類別(詞匯量的大小,8000),預(yù)測輸出o和實際標(biāo)注y的損失函數(shù)如下所示:
這個公式看似復(fù)雜,但其實就是對訓(xùn)練樣本求和后再與預(yù)測損失相加。y(實際標(biāo)注值)與o(預(yù)測輸出值)相距越遠,誤差就越大。因此,我們采用calculate_loss函數(shù)來實現(xiàn):
倒退一步再思考一下隨機預(yù)測的誤差是多少。我們將得到一條底線并確認該實現(xiàn)是正確的。詞匯中有C個單詞,每個單詞的(平均)預(yù)測概率應(yīng)為1/C,由此公式得出誤差值:
結(jié)果很接近了!但需明確,計算整個數(shù)據(jù)集的損失將非常消耗時間,如果數(shù)據(jù)量過于龐大,會花費數(shù)個小時。
別忘了,我們要尋找的是能將訓(xùn)練數(shù)據(jù)的總損失最小化的U,V,W參數(shù)。最常用的做法是SGD(Stochastic gradient descent),即隨機梯度下降算法。SGD的原理非常簡單,通過對所有訓(xùn)練樣本進行迭代,并在每次循環(huán)操作中,不斷調(diào)整參數(shù)從而降低錯誤率,這些方向均由損失的梯度給出:。
在這個過程中,我們需要一個學(xué)習(xí)率( learning rate)來決定SGD在每次迭代中前進多少(學(xué)習(xí)率越大,學(xué)習(xí)速度越快,但容易“穿越“,導(dǎo)致找不到最優(yōu)解;學(xué)習(xí)率越小,學(xué)習(xí)速度越慢)。這個算法不僅僅能夠用于神經(jīng)網(wǎng)絡(luò),在許多其他機器學(xué)習(xí)算法中,SGD也有大量的應(yīng)用。
那么,如何計算上文提到的梯度呢?在傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)中,我們通過反向傳播算法來計算。而在RNN中,在此基礎(chǔ)上我們還會做簡單修正,使用BPTT(Backpropagation Through Time )來實現(xiàn),用中文來解釋,就是跨越時間的反向傳播算法。為什么要用這樣的方法?是由于在這個模型中,所有參數(shù)實時共享,這造成每次輸出的梯度不僅取決于當(dāng)前時刻的數(shù)值,也取決于之前所有時刻的數(shù)值。因此,在這里我們將應(yīng)用鏈?zhǔn)椒▌t。
現(xiàn)在,我們可以先把BPTT算法(在下一篇教程中我們會對此做更詳細的介紹)看做一個黑匣子,下面來看一下代碼實現(xiàn),當(dāng)我們輸入訓(xùn)練示例(x,y)時,就會輸出三個梯度,用于更新權(quán)重:
通常我們會建議同時應(yīng)用反向傳播算法和梯度檢驗,因為這是檢驗正確性的一種方式。梯度檢驗背后所暗含的,是與某一時刻公式斜率相同的參數(shù)的導(dǎo)數(shù),而可以稍微改變參數(shù),也可通過余數(shù)相除獲得。
當(dāng)你在實現(xiàn)一個反向傳播算法的時候,可以同時做一個梯度檢查,來檢驗?zāi)愕乃惴ㄊ欠裾_。檢查的背后原理也很簡單,就是從導(dǎo)數(shù)的定義出發(fā),公式如下:
下面是實現(xiàn)代碼:
有了上面的準(zhǔn)備工作,我們就可以使用SGD算法來更新權(quán)重了??梢杂脙蓚€步驟來實現(xiàn):第一,sdg_step方法,即在一個batch上更新權(quán)重,它可以計算參數(shù),并且每完成一次批處理,便完成一次更新;第二,用外循環(huán)來遍歷所有的訓(xùn)練樣本,并動態(tài)更新學(xué)習(xí)率。下面是實現(xiàn)代碼:
完成!嘗試這樣訓(xùn)練網(wǎng)絡(luò)的耗時如下圖:
可以看到,在我的電腦上,要完成SGD算法的實現(xiàn),大概需要花350毫秒。而在我們的訓(xùn)練樣本大約有80,000個,如果每完成一個周期(整個數(shù)據(jù)集的迭代)要花幾個小時,完成幾個周期可能會花幾天,甚至幾周。
幸運的是,有很多種方法可以提高代碼速度。比如,可以一直使用同一個模型來提高代碼運行速度,或者可以改變模型減少計算消耗時間,當(dāng)然也可以雙管齊下。研究人員已經(jīng)找到許多方法減少模型的計算耗時,比如說他們會使用分層級的softmax函數(shù)或者增加項目層,以避免大規(guī)模的矩陣乘法。
此外,通過使用GPU也可以提高運算速度。但這之前,我們還是需要通過使用小數(shù)據(jù)集嘗試運行SGD算法,從而檢驗誤差是否真的減小了。
我們編寫了一個基于Theano的RNN實現(xiàn)程序代碼,從而代替numpy。同本文其他代碼一樣,這個代碼可以在Github上獲取。
這一次,使用Mac,我們每完成一個SGD只花費了70毫秒,比最開始的速度提高了15倍,這意味著只需要費幾小時或者幾天就可以完成對模型的訓(xùn)練。當(dāng)然,盡管我們的模型現(xiàn)在已經(jīng)足夠好了,但是依然有很多值得改進的地方。
以下是我自己預(yù)訓(xùn)練的Theano模型的使用方法:
在有了模型之后,可以利用它生成新文本:
以下我們挑選了幾個生成的句子:
Anyway, to the city scene you’re an idiot teenager.
What ? ! ! ! ! ignore!
Screw fitness, you’re saying: https
Thanks for the advice to keep my thoughts around girls.
Yep, please disappear with the terrible generation.
值得注意的是,雖然在以上這些句子中,模型非常成功的運用了句法學(xué)習(xí),恰當(dāng)?shù)氖褂昧硕禾枺ㄍǔT赼nd和or中間),以句號作為結(jié)尾。有時,它還模仿了很多網(wǎng)絡(luò)用語,比如使用驚嘆號和笑臉符號。然而,許多生成的句子要么沒有意義,要么有一些語法錯誤(我確實選擇了里面最好的句子)。一部分原因可能是因為我們的神經(jīng)網(wǎng)絡(luò)訓(xùn)練的時間不夠長,或者訓(xùn)練的數(shù)據(jù)不夠多,但是這不是主要原因。
最主要的原因在于,傳統(tǒng)的RNN模型無法學(xué)習(xí)兩個相隔較遠的單詞間的依賴性和相關(guān)性,所以不能生成有意義的文本。這也是為什么RNN模型剛被研發(fā)出來時無法被普遍推廣的原因。理論上它們很完美,但實際上不能被很好的應(yīng)用,并且短時間內(nèi)找不到原因。
當(dāng)然,也不用過分擔(dān)心,現(xiàn)在我們已經(jīng)找到了很多更好的方式來訓(xùn)練RNN模型。
另外,需要說明的是,本文涉及的算法也可以應(yīng)用于LSTM和其他的RNN模型中。
好文章,需要你的鼓勵
新加坡國立大學(xué)研究團隊開發(fā)了SPIRAL框架,通過讓AI與自己對弈零和游戲來提升推理能力。實驗顯示,僅訓(xùn)練AI玩簡單撲克游戲就能讓其數(shù)學(xué)推理能力提升8.6%,通用推理提升8.4%,且無需任何數(shù)學(xué)題目作為訓(xùn)練材料。研究發(fā)現(xiàn)游戲中的三種推理模式能成功轉(zhuǎn)移到數(shù)學(xué)解題中,為AI訓(xùn)練提供了新思路。
同濟大學(xué)團隊開發(fā)的GIGA-ToF技術(shù)通過融合多幀圖像的"圖結(jié)構(gòu)"信息,創(chuàng)新性地解決了3D相機噪聲問題。該技術(shù)利用圖像間的不變幾何關(guān)系,結(jié)合深度學(xué)習(xí)和數(shù)學(xué)優(yōu)化方法,在合成數(shù)據(jù)集上實現(xiàn)37.9%的精度提升,并在真實設(shè)備上展現(xiàn)出色泛化能力,為機器人、AR和自動駕駛等領(lǐng)域提供更可靠的3D視覺解決方案。
伊利諾伊大學(xué)研究團隊通過對比實驗發(fā)現(xiàn),經(jīng)過強化學(xué)習(xí)訓(xùn)練的視覺語言模型雖然表現(xiàn)出"頓悟時刻"現(xiàn)象,但這些自我糾錯行為并不能實際提升推理準(zhǔn)確率。研究揭示了AI模型存在"生成-驗證差距",即生成答案的能力強于驗證答案質(zhì)量的能力,且模型在自我驗證時無法有效利用視覺信息,為AI多模態(tài)推理發(fā)展提供了重要啟示。
MIT等頂尖機構(gòu)聯(lián)合提出SparseLoRA技術(shù),通過動態(tài)稀疏性實現(xiàn)大語言模型訓(xùn)練加速1.6倍,計算成本降低2.2倍。該方法使用SVD稀疏性估計器智能選擇重要計算部分,在保持模型性能的同時顯著提升訓(xùn)練效率,已在多個任務(wù)上驗證有效性。