tensorflow错别字检测
引言
當(dāng)前無論是學(xué)術(shù)界還是工業(yè)界,深度學(xué)習(xí)都受到極大的追捧,尤其是在Google開源深度學(xué)習(xí)平臺TensorFlow之后,更是給深度學(xué)習(xí)火上澆油。目前在開源社區(qū)Github上所有開源項目中,TensorFlow最為活躍,從推出到現(xiàn)在,經(jīng)歷了幾個版本的演進(jìn),可以說能夠靈活高效地解決大量實際問題。本文主要嘗試闡述TensorFlow在自然語言處理(NLP)領(lǐng)域的簡單應(yīng)用,讓大家伙兒更加感性地認(rèn)識TensorFlow。
說到NLP,其實我對它并不是很熟悉,之前也未曾有過NLP的相關(guān)經(jīng)驗,本文是我最近學(xué)習(xí)TensorFlow的一些積累,就當(dāng)拋磚引玉了。當(dāng)前互聯(lián)網(wǎng)每天都在產(chǎn)生大量的文本和音頻數(shù)據(jù),通過挖掘這些數(shù)據(jù),我們可以做一些更加便捷的應(yīng)用,例如機(jī)器翻譯、語音識別、詞性標(biāo)注以及信息檢索等,這些都屬于NLP范疇。而在NLP領(lǐng)域中,語言模型是最基本的一個環(huán)節(jié),本文主要圍繞語言模型展開,首先介紹其基本原理,進(jìn)而引出詞向量(word2vec)、循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)、長短時記憶網(wǎng)絡(luò)(LSTM)等深度學(xué)習(xí)相關(guān)模型,并詳細(xì)介紹如何利用 TensorFlow 實現(xiàn)上述模型。
語言模型
語言模型是一種概率模型,它是基于一個語料庫創(chuàng)建,得到每個句子出現(xiàn)的概率,通俗一點講就是看一句話是不是正常人說出來的,數(shù)學(xué)上表示為:
?
P(W)=P(w1w2…wt)=P(w1)P(w2|w1)P(w3|w1w2)?P(wt|w1w2?wt?1)(2-1)(2-1)P(W)=P(w1w2…wt)=P(w1)P(w2|w1)P(w3|w1w2)?P(wt|w1w2?wt?1)
?
上述公式的意義是:一個句子出現(xiàn)的概率等于給定前面的詞情況下,緊接著后面的詞出現(xiàn)的概率。它是通過條件概率公式展開得到。其中條件概率?P(w2|w1),P(w3|w1w2),?,P(wt|w1w2?wt?1)P(w2|w1),P(w3|w1w2),?,P(wt|w1w2?wt?1)?就是創(chuàng)建語言模型所需要的參數(shù),每個條件概率的意義解釋為:根據(jù)前面的詞預(yù)測下一個詞的概率。有了這些條件概率參數(shù),給定一個句子,就可以通過以上公式得到一個句子出現(xiàn)的概率。例如有一句話“php是最好的語言”(我不確定這是不是自然語言),假設(shè)已經(jīng)分詞為“php”、“是”、“最好的”、“語言”,那么它出現(xiàn)的概率為P(“php”,“是”,“最好的”,“語言”)=P(“php”)P(“是”|“php”)P(“最好的”|“php”,“是”)P(“語言”|“php”,“是”,“最好的”),如果這個概率較大,那么判斷為正常的一句話。以上這些條件概率通過如下貝葉斯公式得到:
?
P(wt|w1w2?wt?1)=P(w1w2?wt)P(w1w2?wt?1)(2-2)(2-2)P(wt|w1w2?wt?1)=P(w1w2?wt)P(w1w2?wt?1)
?
根據(jù)大數(shù)定理上述公式又可以近似為:
?
P(wt|w1w2?wt?1)=count(w1w2?wt)count(w1,w2,?wt?1)(2-3)(2-3)P(wt|w1w2?wt?1)=count(w1w2?wt)count(w1,w2,?wt?1)
?
假如語料庫里有?NN?個詞,一個句子長度為?TT?,那么就有?NTNT?種可能,每一種可能都要計算?TT?個條件概率參數(shù),最后要計算?TNTTNT?個參數(shù)并保存,不僅計算量大,對于內(nèi)存要求也是驚人。那么如何避免這個問題呢,之前窮舉的方法行不通,那么換個思路,采用一種偷懶的處理方法,就是將上述公式中條件概率做個如下近似:
?
P(wt|w1w2?wt?1)≈P(wt|wt?n+1?wt?1)(2-4)(2-4)P(wt|w1w2?wt?1)≈P(wt|wt?n+1?wt?1)
?
這意思就是說一個詞出現(xiàn)的概率只與它前面?n?1n?1?個詞有關(guān),而不是與它前面所有的詞有關(guān),這樣極大的減少了統(tǒng)計的可能性,提高了計算效率,這種處理方法稱之為 n-gram 模型,通常?nn?取2~3就能得到不錯的效果。總結(jié)起來,n-gram 模型就是統(tǒng)計語料庫中詞串出現(xiàn)的次數(shù),一次性計算得到詞串的概率并將其保存起來,在預(yù)測一個句子時,直接通過前面所述的條件概率公式得到句子出現(xiàn)的概率。
近年也流行起神經(jīng)網(wǎng)絡(luò)語言模型,從機(jī)器學(xué)習(xí)的角度來看,一開始不全部計算這些詞串的概率值,而是通過一個模型對詞串的概率進(jìn)行建模,然后構(gòu)造一個目標(biāo)函數(shù),不斷優(yōu)化這個目標(biāo),得到一組優(yōu)化的參數(shù),當(dāng)需要哪個詞串概率時,利用這組優(yōu)化的參數(shù)直接計算得到對應(yīng)的詞串概率。將詞串概率?P(w|context(w))P(w|context(w))?看做是?ww?和?context(w)context(w)?的函數(shù),其中?context(w)context(w)?表示此?ww?的上下文,即相當(dāng)于前面所述的 n-gram 模型的前?n?1n?1?個詞,那么就有如下數(shù)學(xué)表示。
?
P(w|context(w))=F(w,context(w),Θ)(2-5)(2-5)P(w|context(w))=F(w,context(w),Θ)
?
目標(biāo)函數(shù)采用對數(shù)似然函數(shù),表示如下(其中?NN?代表語料庫中詞典的大小):
?
Obj=1N∑i=1NlogP(wi|contexti)(2-6)(2-6)Obj=1N∑i=1NlogP(wi|contexti)
?
通過優(yōu)化算法不斷最小化目標(biāo)函數(shù)得到一組優(yōu)化的參數(shù)?ΘΘ?,在神經(jīng)網(wǎng)絡(luò)中參數(shù)?ΘΘ?則為網(wǎng)絡(luò)層與層間的權(quán)值與偏置。那么在用神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)語言模型[1]時,如何表示一個詞呢?通常,在機(jī)器學(xué)習(xí)領(lǐng)域,是將一個樣本對象抽象為一個向量,所以類似地,神經(jīng)網(wǎng)絡(luò)語言模型中是將詞(或短語)表示為向量,通常叫做word2vec。那么神經(jīng)網(wǎng)絡(luò)語言模型就可以表示如下示意圖。
上述神經(jīng)網(wǎng)絡(luò)包括輸入層、投影層、隱藏層以及輸出層,其中投影層只是對輸入層做了一個預(yù)處理,將輸入的所有詞進(jìn)行一個連接操作,假如一個詞表示為?mm?維向量,那么由?n?1n?1?個詞連接后則為?(n?1)m(n?1)m?維向量,將連接后的向量作為神經(jīng)網(wǎng)絡(luò)的輸入,經(jīng)過隱藏層再到輸出層,其中?WW?、UU?分別為投影層到隱藏層、隱藏層到輸出層的權(quán)值參數(shù),pp?、qq?分別為投影層到隱藏層、隱藏層到輸出層的偏置參數(shù),整個過程數(shù)學(xué)表達(dá)如下:
?
ZY=σ(WX+p)=UZ+q(2-7)(2-7)Z=σ(WX+p)Y=UZ+q
?
其中?σσ?為sigmoid函數(shù),作為隱藏層的激活函數(shù),輸出層的輸出向量為?NN?維,對應(yīng)于語料庫中詞典的大小。一般需要再經(jīng)過softmax歸一化為概率形式,得到預(yù)測語料庫中每個詞的概率。以上神經(jīng)網(wǎng)絡(luò)語言模型看似很簡單,但是詞向量怎么來呢,如何將一個詞轉(zhuǎn)化為向量的形式呢?下面作詳細(xì)闡述。
詞向量(word2vec)
詞向量要做的事就是將語言數(shù)學(xué)化表示,以往的做法是采用 One-hot Representation 表示一個詞,即語料庫詞典中有?NN?個詞,那么向量的維度則為?NN?,給每個詞編號,對于第?ii?個詞,其向量表示除了第?ii?個單元為1,其他單元都為0的?NN?維向量,這種詞向量的缺點顯而易見,一般來說語料庫的詞典規(guī)模都特別大,那么詞向量的維數(shù)就非常大,并且詞與詞之間沒有關(guān)聯(lián)性,并不能真實地刻畫語言本身的性質(zhì),例如“騰訊”、“小馬哥”這兩個詞通過One-hot向量表示,沒有任何關(guān)聯(lián)。為了克服One-hot Representation 的缺點,Mikolov大神提出了一種 Distributed Representation[2],說個題外話,在大家都在如火如荼的用CNN做圖像識別的時候,這哥們卻在研究如何用神經(jīng)網(wǎng)絡(luò)處理NLP問題,最后發(fā)了大量關(guān)于神經(jīng)網(wǎng)絡(luò)NLP的高水平論文,成為這一領(lǐng)域的靈魂人物之一。顧名思義,Distributed Representation 就是把詞的信息分布到向量不同的分量上,而不是像 One-hot Representation 那樣所有信息集中在一個分量上,它的做法是將詞映射到?mm?維空間,表示為?mm?維向量,也稱之為 Word Embedding,這樣一方面可以減小詞向量的維度,另一方面,可以將有關(guān)聯(lián)的詞映射為空間中相鄰的點,詞與詞之間的關(guān)聯(lián)性通過空間距離來刻畫,如下圖所示。
詞被映射到3維空間,每個詞表示為一個3維向量,相近的詞離的較近,可以看到兩組差不多關(guān)系的詞,他們之間的詞向量距離也差不多。
要想得到詞向量,需要借助語言模型訓(xùn)練得到,本質(zhì)上來說,詞向量是在訓(xùn)練語言模型過程中得到的副產(chǎn)品。解決word2vec問題有兩種模型,即 CBOW 和 Skip-Gram 模型[3],如下圖所示:
CBOW 模型是根據(jù)詞的上下文預(yù)測當(dāng)前詞,這里的上下文是由待預(yù)測詞的前后?cc?個詞組成。而 Skip-Gram 模型則相反,是通過當(dāng)前詞去預(yù)測上下文。給定一個語料庫作為訓(xùn)練集,就可以通過以上模型訓(xùn)練出每個詞的向量表示。從實驗結(jié)果來看,CBOW 模型會平滑掉一些分布信息,因為它將詞的上下文作為單個樣本,而 Skip-Gram 模型將詞上下文拆分為多個樣本,訓(xùn)練得到的結(jié)果更為精確,為此,TensorFlow 中 word2vec 采用的是 Skip-Gram 模型,對應(yīng)于文[2]中所提出的一種更為優(yōu)化的 Skip-Gram 模型,下面著重介紹其原理,更多關(guān)于 CBOW 和 Skip-Gram 模型細(xì)節(jié)可以參閱文[3]。
Skip-Gram 模型
前面也提到, Skip-Gram 模型是根據(jù)當(dāng)前詞去預(yù)測上下文,例如有如下語句:
“php 是 世界上 最好的 語言”
假定上下文是由待預(yù)測詞的前后2個詞組成,那么由以上句子可以得到如下正樣本:
(世界上, 是), (世界上, php), (世界上, 最好的), (世界上, 語言), (最好的, 世界上), …
訓(xùn)練目標(biāo)為最大化以下對數(shù)似然函數(shù):
?
Obj=1N∑i=1N∑?c?j?c,j≠0log?p(wi+j|wi)(3-1)(3-1)Obj=1N∑i=1N∑?c?j?c,j≠0log?p(wi+j|wi)
?
其中?cc?為上下文的距離限定,即僅取詞?wtwt?的前后?cc?個詞進(jìn)行預(yù)測。cc?越大,訓(xùn)練結(jié)果更精確,但是計算復(fù)雜度加大,訓(xùn)練成本相應(yīng)也更大,一般取?cc?為2~3就能訓(xùn)練出不錯的結(jié)果。基本的 Skip-Gram 模型采用softmax方法將以上目標(biāo)函數(shù)中概率?p(wi+j|wi)p(wi+j|wi)?定義為:
?
p(wO|wI)=exp(θwOTvwI)∑w∈Wexp(θwTvwI)(3-2)(3-2)p(wO|wI)=exp(θwOTvwI)∑w∈Wexp(θwTvwI)
?
其中?vwvw?表示輸入詞?ww?的向量,θwθw?表示預(yù)測結(jié)果為?ww?的權(quán)值參數(shù),二者都是待訓(xùn)練的參數(shù)。不難發(fā)現(xiàn),通過以上公式,計算每個詞的損失函數(shù)都要用到詞典中的所有詞,而一般詞典的量級都非常大,所以這種方式是不切實際的。對于一個樣本,例如(“世界上”, “php”),無非是根據(jù)詞“世界上”去預(yù)測詞“php”,那么就可以看成一個二分類問題,對于輸入詞“世界上”,預(yù)測“php”為正,預(yù)測其他則為負(fù),其他詞可能是除“php”以外的所有詞,為了簡化計算,可以通過采樣的方式,每次隨機(jī)從所有除“php”以外的詞中取?kk?個詞作為負(fù)樣本對象,那么訓(xùn)練目標(biāo)則可以轉(zhuǎn)化為類似于邏輯回歸目標(biāo)函數(shù):
?
Obj=logσ(θwOTvwI)+∑j=1kEwj~Pn(w)[logσ(?θwjTvwI)](3-3)(3-3)Obj=logσ(θwOTvwI)+∑j=1kEwj~Pn(w)[logσ(?θwjTvwI)]
?
以上表達(dá)式稱之為 NCE(Noise-contrastive estimation)[4]目標(biāo)函數(shù),其中等號右邊第二項表示通過一個服從?Pn(w)Pn(w)分布的采樣算法取得?kk?個負(fù)樣本的期望損失。文[2]中采用了一個簡單的一元分布采樣,簡化了計算,稱之為負(fù)采樣(Negative Sampling),下面詳細(xì)介紹負(fù)采樣算法。
負(fù)采樣算法
詞典中的每個詞在語料庫中出現(xiàn)的頻次有高有低,理論上來說,對于那些高頻詞,被選為負(fù)樣本的概率較大,對于那些低頻詞,被選為負(fù)樣本的概率較小。基于這個基本事實,可以通過帶權(quán)采樣方法來實現(xiàn),假設(shè)每個詞的詞頻表示為單位線段上的一小分段,對于詞典大小為?NN?的語料庫,可以將詞典中所有的詞表示為單位線段上的一點,再在單位線段上等距離劃分?MM?個等分,?M>>NM>>N?, 具體采樣過程就是隨機(jī)得到一個數(shù)?i<Mi<M,通過映射找到其對應(yīng)的詞,如下如所示。
文[2]中在實際負(fù)采樣計算詞頻時,做了一點修正,不是簡單的統(tǒng)計詞的出現(xiàn)次數(shù),而是對詞的出現(xiàn)次數(shù)做了?αα?次冪處理,最后詞頻公式為:
?
freq(w)=[counter(w)]3/4∑u∈W[counter(u)]3/4(3-4)(3-4)freq(w)=[counter(w)]3/4∑u∈W[counter(u)]3/4
?
高頻詞二次采樣
在一個大語料庫中,很多常見的詞大量出現(xiàn),如“的”、“是”等。這些詞雖然詞頻較高,但是能提供的有用信息卻很少。一般來說,這些高頻詞的詞向量在訓(xùn)練幾百萬樣本后基本不會有太大的變化,為了提高訓(xùn)練速度,平衡低頻詞和高頻詞,文[2]中提出一種針對高頻詞二次采樣的技巧,對于每個詞,按如下概率丟棄而不做訓(xùn)練。
?
P(wi)=1?tf(wi)?????√(3-5)(3-5)P(wi)=1?tf(wi)
?
其中f(wi)f(wi)表示詞頻,從上述公式中不難發(fā)現(xiàn),二次采樣僅針對那些滿足?f(wi)>tf(wi)>t?所謂的高頻詞有效,參數(shù)?tt?根據(jù)語料庫的大小而設(shè)置,一般設(shè)置為?10?510?5?左右。
TensorFlow實現(xiàn)
根據(jù)以上實現(xiàn)原理,下面結(jié)合代碼闡述利用TensorFlow實現(xiàn)一個簡易的word2vec模型[5],借助TensorFlow豐富的api以及強(qiáng)大的計算引擎,我們可以非常方便地表達(dá)模型。給定語料庫作為訓(xùn)練數(shù)據(jù),首先掃描語料庫建立字典,為每個詞編號,同時將那些詞頻低于min_count的詞過濾掉,即不對那些陌生詞生成詞向量。對于一個樣本(“世界上”, “php”),利用負(fù)采樣得到若干負(fù)實例,分別計算輸入詞為“世界上”到“php”以及若干負(fù)樣本的logit值,最后通過交叉熵公式得到目標(biāo)函數(shù)(3-3)。
構(gòu)建計算流圖
首先定義詞向量矩陣,也稱為 embedding matrix,這個是我們需要通過訓(xùn)練得到的詞向量,其中vocabulary_size表示詞典大小,embedding_size表示詞向量的維度,那么詞向量矩陣為 vocabulary_size?××?embedding_size,利用均勻分布對它進(jìn)行隨機(jī)初始化:
| 1 2 | embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0)) |
?
定義權(quán)值矩陣和偏置向量(對應(yīng)于3-3式中的?θθ),并初始化為0:
| 1 2 3 4 | weights = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size],stddev=1.0 / math.sqrt(embedding_size))) biases = tf.Variable(tf.zeros([vocabulary_size])) |
?
給定一個batch的輸入,從詞向量矩陣中找到對應(yīng)的向量表示,以及從權(quán)值矩陣和偏置向量中找到對應(yīng)正確輸出的參數(shù),其中examples是輸入詞,labels為對應(yīng)的正確輸出,一維向量表示,每個元素為詞在字典中編號:
| 1 2 3 4 5 6 | # Embeddings for examples: [batch_size, embedding_size] example_emb = tf.nn.embedding_lookup(embeddings, examples) # Weights for labels: [batch_size, embedding_size] true_w = tf.nn.embedding_lookup(weights, labels) # Biases for labels: [batch_size, 1] true_b = tf.nn.embedding_lookup(biases, labels) |
?
負(fù)采樣得到若干非正確的輸出,其中l(wèi)abels_matrix為正確的輸出詞,采樣的時候會跳過這些詞,num_sampled為采樣個數(shù),distortion即為公式(3-4)中的冪指數(shù):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | labels_matrix = tf.reshape(tf.cast(labels,dtype=tf.int64),[batch_size, 1]) # Negative sampling. sampled_ids, _, _ = tf.nn.fixed_unigram_candidate_sampler(true_classes=labels_matrix,num_true=1,num_sampled=num_samples,unique=True,range_max=vocab_size,distortion=0.75,unigrams=vocab_counts.tolist()) |
?
找到采樣樣本對應(yīng)的權(quán)值和偏置參數(shù):
| 1 2 3 4 | # Weights for sampled ids: [num_sampled, embedding_size] sampled_w = tf.nn.embedding_lookup(weights, sampled_ids) # Biases for sampled ids: [num_sampled, 1] sampled_b = tf.nn.embedding_lookup(biases, sampled_ids) |
?
分別計算正確輸出和非正確輸出的logit值,即計算?WX+bWX+b,并通過交叉熵得到目標(biāo)函數(shù)(3-3):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # True logits: [batch_size, 1] true_logits = tf.reduce_sum(tf.mul(example_emb, true_w), 1) + true_b # Sampled logits: [batch_size, num_sampled] # We replicate sampled noise lables for all examples in the batch # using the matmul. sampled_b_vec = tf.reshape(sampled_b, [num_samples]) sampled_logits = tf.matmul(example_emb,sampled_w,transpose_b=True) + sampled_b_vec # cross-entropy(logits, labels) true_xent = tf.nn.sigmoid_cross_entropy_with_logits(true_logits, tf.ones_like(true_logits)) sampled_xent = tf.nn.sigmoid_cross_entropy_with_logits(sampled_logits, tf.zeros_like(sampled_logits)) # NCE-loss is the sum of the true and noise (sampled words) # contributions, averaged over the batch. loss = (tf.reduce_sum(true_xent) +tf.reduce_sum(sampled_xent)) / batch_size |
?
訓(xùn)練模型
計算流圖構(gòu)建完畢后,我們需要去優(yōu)化目標(biāo)函數(shù)。采用梯度下降逐步更新參數(shù),首先需要確定學(xué)習(xí)步長,隨著迭代進(jìn)行,逐步減少學(xué)習(xí)步長,其中trained_words為已訓(xùn)練的詞數(shù)量,words_to_train為所有待訓(xùn)練的詞數(shù)量:
| 1 2 | lr = init_learning_rate * tf.maximum(0.0001, 1.0 - tf.cast(trained_words, tf.float32) / words_to_train) |
?
定義優(yōu)化算子,使用梯度下降訓(xùn)練模型:
| 1 2 3 4 5 | optimizer = tf.train.GradientDescentOptimizer(lr) train = optimizer.minimize(loss,global_step=global_step,gate_gradients=optimizer.GATE_NONE) session.run(train) |
?
驗證詞向量
經(jīng)過以上步驟后,即可得到詞向量矩陣,即上述代碼中的變量embeddings,那么如何驗證得到的詞向量矩陣的好壞呢,Mikolov等人發(fā)現(xiàn)[2],如果一對關(guān)系差不多的詞,其詞向量在空間中的連線近乎平行,如下圖所示。
為此,給定基準(zhǔn)測試集,其每行包含4個詞組成一個四元組?(w1,w2,w3,w4)(w1,w2,w3,w4)?,對于一個較好的詞向量結(jié)果,每個四元組大致會有如下關(guān)系:
?
Vector(w1)?Vector(w2)+Vector(w4)=Vector(w3)Vector(w1)?Vector(w2)+Vector(w4)=Vector(w3)
?
循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)
人類不是從腦子一片空白開始思考,當(dāng)你讀一篇文章的時候,你會根據(jù)前文去理解下文,而不是每次看到一個詞后就忘掉它,理解下一個詞的時候又從頭開始。傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)模型是從輸入層到隱藏層再到輸出層,每層之間的節(jié)點是無連接的,這種普通的神經(jīng)網(wǎng)絡(luò)不具備記憶功能,而循環(huán)神經(jīng)網(wǎng)絡(luò)(Recurrent Neural Network,RNN)就是來解決這類問題,它具備記憶性,通常用于處理時間序列問題,在眾多NLP問題中,RNN取得了巨大成功以及廣泛應(yīng)用。
在RNN網(wǎng)絡(luò)中,一個序列當(dāng)前的輸出除了與當(dāng)前輸入有關(guān)以外,還與前面的輸出也有關(guān),下圖為RNN中一個單元的結(jié)構(gòu)示意圖,圖片來源于文[7]。
上圖理解起來可能還不是很形象,根據(jù)時間序列將上圖平鋪展開得到如下圖,其鏈?zhǔn)降奶卣鹘沂玖?RNN 本質(zhì)上是與序列相關(guān)的,所以 RNN 對于這類數(shù)據(jù)來說是最自然的神經(jīng)網(wǎng)絡(luò)架構(gòu)。
然而 RNN 有一個缺點,雖然它可以將之前的信息連接到當(dāng)前的輸入上,但是如果當(dāng)前輸入與之前的信息時間跨度很大,由于梯度衰減等原因,RNN 學(xué)習(xí)如此遠(yuǎn)的信息的能力會下降,這個問題稱之為長時間依賴(Long-Term Dependencies)問題。例如預(yù)測一句話“飛機(jī)在天上”下一個詞,可能不需要太多的上下文就可以預(yù)測到下一個詞為“飛”,這種情況下,相關(guān)信息與要預(yù)測的詞之間的時間跨度很小,RNN 可以很容易學(xué)到之前的信息。再比如預(yù)測“他來自法國,…,他會講”的下一個詞,從當(dāng)前的信息來看,下一個詞可能是一種語言,但是要想準(zhǔn)確預(yù)測哪種語言,就需要再去前文找信息了,由于前文的“法國”離當(dāng)前位置的時間跨度較大,RNN很難學(xué)到如此遠(yuǎn)的信息。更多長時間依賴細(xì)節(jié)參考文[8]。幸運的是,有一種 RNN 變種,叫做長短時記憶網(wǎng)絡(luò)(Long Short Term Memory networks, LSTM),可以解決這個問題。
長短時記憶網(wǎng)絡(luò)(LSTM)
LSTM 是一種帶有選擇性記憶功能的 RNN,它可以有效的解決長時間依賴問題,并能學(xué)習(xí)到之前的關(guān)鍵信息。如下圖所示為 LSTM 展開后的示意圖。
相對于 RNN , LSTM 只是在每個單元結(jié)構(gòu)上做了改進(jìn),在 RNN 中,每個單元結(jié)構(gòu)只有單個激活函數(shù),而 LSTM 中每個單元結(jié)構(gòu)更為復(fù)雜,它增加了一條狀態(tài)線(圖中最上面的水平線),以記住從之前的輸入學(xué)到的信息,另外增加三個門(gate)來控制其該狀態(tài),分別為忘記門、輸入門和輸出門。忘記門的作用是選擇性地將之前不重要的信息丟掉,以便存儲新信息;輸入門是根據(jù)當(dāng)前輸入學(xué)習(xí)到新信息然后更新當(dāng)前狀態(tài);輸出門則是結(jié)合當(dāng)前輸入和當(dāng)前狀態(tài)得到一個輸出,該輸出除了作為基本的輸出外,還會作為下一個時刻的輸入。下面用數(shù)學(xué)的方式表達(dá)每個門的意思。
忘記門,要丟掉的信息如下:
?
ft=σ(Wf[ht?1,xt]+bf)(5-1)(5-1)ft=σ(Wf[ht?1,xt]+bf)
?
輸入門,要增加的信息如下:
itCt~=σ(Wi[ht?1,xt]+bi)=tanh(WC[ht?1,xt]+bC)(5-2)(5-2)it=σ(Wi[ht?1,xt]+bi)Ct~=tanh(WC[ht?1,xt]+bC)
?
那么根據(jù)忘記門和輸入門,狀態(tài)更新如下:
?
Ct=ft?Ct?1+it?Ct~(5-3)(5-3)Ct=ft?Ct?1+it?Ct~
?
輸出門,得到輸出信息如下:
?
otht=σ(Wo[ht?1,xt]+bo)=ot?tanh(Ct)(5-4)(5-4)ot=σ(Wo[ht?1,xt]+bo)ht=ot?tanh(Ct)
?
LSTM 單元輸入都是上一個時刻的輸出與當(dāng)前時刻的輸入通過向量concat連接而得到,基于這個輸入,利用sigmoid函數(shù)作為三個門的篩選器,分別得到?ftft?、itit?、otot,這三個篩選器分別選擇部分分量對狀態(tài)進(jìn)行選擇性忘記、對輸入進(jìn)行選擇性輸入、對輸出進(jìn)行選擇性輸出。以上是 LSTM 基本結(jié)構(gòu)原理,在這基礎(chǔ)上,根據(jù)不同的實際應(yīng)用場景,演變出很多 LSTM 的變體,更多關(guān)于 LSTM 的詳細(xì)解釋請參考文[7]。下面介紹一種深層 LSTM 網(wǎng)絡(luò)[9],該結(jié)構(gòu)也是 TensorFlow 中 LSTM 所實現(xiàn)的根據(jù)[10]。
深層LSTM網(wǎng)絡(luò)
深度學(xué)習(xí),其特點在于深,前面已經(jīng)講述單層 LSTM 網(wǎng)絡(luò)結(jié)構(gòu),深層 LSTM 網(wǎng)絡(luò)其實就是將多層 LSTM 疊加,形成多個隱藏層,如下圖所示。
上圖中每個 LSTM 單元內(nèi)部結(jié)構(gòu)如下圖所示,對于?ll?層?tt?時刻來說,hlt?1ht?1l?為?ll?層?t?1t?1?時刻(即上一個時刻)的輸出,hl?1thtl?1?為?l?1l?1?層(即上一層)?tt?時刻的輸出,這兩個輸出疊加作為?ll?層?tt?時刻的輸入。
根據(jù)上面的結(jié)構(gòu),可以得到?ll?層 LSTM 數(shù)學(xué)表達(dá),?hl?1t,hlt?1,clt?1→hlt,clthtl?1,ht?1l,ct?1l→htl,ctl:
?
fiog?clthlt=σ(Wf[hl?1t,hlt?1]+bf)=σ(Wi[hl?1t,hlt?1]+bi)=σ(Wo[hl?1t,hlt?1]+bo)=tanh(Wg[hl?1t,hlt?1]+bg)=f?clt?1+i?g=o?tanh(clt)(5-5)(5-5)f=σ(Wf[htl?1,ht?1l]+bf)i=σ(Wi[htl?1,ht?1l]+bi)o=σ(Wo[htl?1,ht?1l]+bo)g=tanh(Wg[htl?1,ht?1l]+bg)?ctl=f?ct?1l+i?ghtl=o?tanh(ctl)
?
其中?clt?1ct?1l?表示上一時刻的狀態(tài),cltctl?表示由當(dāng)前輸入更新后的狀態(tài)。
正則化
然而,實踐證明大規(guī)模的 LSTM 網(wǎng)絡(luò)很容易過擬合,實際應(yīng)用中,需要采取正則化方法來避免過擬合,神經(jīng)網(wǎng)絡(luò)中常見的正則化方法是Dropout方法[11],文[12]提出一種簡單高效的Dropout方法運用于 RNN/LTSM 網(wǎng)絡(luò)。如下圖所示,Dropout僅應(yīng)用于虛線方向的輸入,即僅針對于上一層的輸出做Dropout。
根據(jù)上圖的Dropout策略,公式(5-5)可以改寫成如下形式:
?
fiog?clthlt=σ(Wf[D(hl?1t),hlt?1]+bf)=σ(Wi[D(hl?1t),hlt?1]+bi)=σ(Wo[D(hl?1t),hlt?1]+bo)=tanh(Wg[D(hl?1t),hlt?1]+bg)=f?clt?1+i?g=o?tanh(clt)(5-6)(5-6)f=σ(Wf[D(htl?1),ht?1l]+bf)i=σ(Wi[D(htl?1),ht?1l]+bi)o=σ(Wo[D(htl?1),ht?1l]+bo)g=tanh(Wg[D(htl?1),ht?1l]+bg)?ctl=f?ct?1l+i?ghtl=o?tanh(ctl)
?
其中?DD?表示Dropout操作符,會隨機(jī)地將?hl?1thtl?1?的中的分量設(shè)置為零。如下圖所示,黑色粗實線表示從?t?2t?2?時刻的信息流向?t+2t+2?時刻作為其預(yù)測的參考,它經(jīng)歷了?L+1L+1?次的Dropout,其中?LL?表示網(wǎng)絡(luò)的層數(shù)。
TensorFlow實現(xiàn)
根據(jù)前面所述的 LSTM 模型原理,實現(xiàn)之前提到的語言模型,即根據(jù)前文預(yù)測下一個詞,例如輸入“飛機(jī)在天上”預(yù)測下一個詞“飛”,使用 TensorFlow 來實現(xiàn) LSTM 非常的方便,因為 TensorFlow 已經(jīng)提供了基本的 LSTM 單元結(jié)構(gòu)的Operation,其實現(xiàn)原理就是基于文[12]提出的帶Dropout的 LSTM 模型。完整代碼請參考ptb_word_lm.py
構(gòu)建LSTM模型
利用TensorFlow提供的Operation,實現(xiàn) LSTM 網(wǎng)絡(luò)很簡單,首先定義一個基本的 LSTM 單元,其中size為 LSTM 單元的輸出維度,再對其添加Dropout,根據(jù) LSTM 的層數(shù)num_layers得到多層的 RNN 結(jié)構(gòu)單元。
| 1 2 3 4 | lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(size, forget_bias=0.0) lstm_cell = tf.nn.rnn_cell.DropoutWrapper(lstm_cell, output_keep_prob=keep_prob) cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell] * num_layers) |
?
每次給定一個batch的輸入,將 LSTM 網(wǎng)絡(luò)的狀態(tài)初始化為0。詞的輸入由詞向量表示,所以先定義一個embedding矩陣,這里可以不要關(guān)心它一開始有沒有,它會在訓(xùn)練過程中的慢慢得到的,僅作為訓(xùn)練的副產(chǎn)品。假設(shè)LSTM網(wǎng)絡(luò)展開num_steps步,每一步給定一個batch的詞作為輸入,經(jīng)過 LSTM 單元處理后,狀態(tài)更新并得到輸出,并通過softmax歸一化后計算損失函數(shù)。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | initial_state = cell.zero_state(batch_size, tf.float32) embedding = tf.get_variable("embedding", [vocab_size, size]) # input_data: [batch_size, num_steps] # targets: [batch_size, num_steps] input_data = tf.placeholder(tf.int32, [batch_size, num_steps]) targets = tf.placeholder(tf.int32, [batch_size, num_steps]) inputs = tf.nn.embedding_lookup(embedding, input_data) outputs = [] for time_step in range(num_steps):(cell_output, state) = cell(inputs[:, time_step, :], state)outputs.append(cell_output)output = tf.reshape(tf.concat(1, outputs), [-1, size]) softmax_w = tf.get_variable("softmax_w", [size, vocab_size]) softmax_b = tf.get_variable("softmax_b", [vocab_size]) logits = tf.matmul(output, softmax_w) + softmax_bloss = tf.nn.seq2seq.sequence_loss_by_example([logits],[tf.reshape(targets, [-1])],[tf.ones([batch_size * num_steps])]) |
訓(xùn)練模型
簡單采用梯度下降優(yōu)化上述損失函數(shù),逐步迭代,直至最大迭代次數(shù),得到final_state,即為LSTM所要學(xué)習(xí)的參數(shù)。
| ? | optimizer = tf.train.GradientDescentOptimizer(lr) train_op = optimizer.minimize(loss) for i in range(max_epoch):_, final_state = session.run([train_op, state],{input_data: x,targets: y}) |
?
驗證測試模型
模型訓(xùn)練完畢后,我們已經(jīng)得到LSTM網(wǎng)絡(luò)的狀態(tài),給定輸入,經(jīng)過LSTM網(wǎng)絡(luò)后即可得到輸出了。
| 1 2 | (cell_output, _) = cell(inputs, state) session.run(cell_output) |
?
小結(jié)
在使用TensorFlow處理深度學(xué)習(xí)相關(guān)問題時,我們不需要太關(guān)注其內(nèi)部實現(xiàn)細(xì)節(jié),只需把精力放到模型的構(gòu)建上,利用TensorFlow已經(jīng)提供的抽象單元結(jié)構(gòu)就可以構(gòu)建靈活的模型。也恰恰正是因為TensorFlow的高度抽象化,有時讓人理解起來頗費勁。所以在我們使用TensorFlow的過程中,不要把問題細(xì)化的太深,一切數(shù)據(jù)看成Tensor即可,利用Tensor的操作符對其進(jìn)行運算,不要在腦海里想如何如何的運算細(xì)節(jié)等等,不然就會身陷囹圄。
總結(jié)
以上是生活随笔為你收集整理的tensorflow错别字检测的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: thinkpad l470安装win10
- 下一篇: CASS实用操作:绘制房子与绿地