運用TensorFlow處理簡單的NLP問題

當前「人工智能」是繼「大數據」後又一個即將被毀的詞,每家公司都宣稱要發力人工智能,就跟4-5年前大數據同樣,業界叫的都很是響亮,不由想到以前一個老外說過的話:php

Big Data is like teenage sex: Everyone talks about it, nobody really knows how to do it, everyone thinks everyone else is doing it, so everyone claims.html

如今看來,上面的」Big Data」能夠換成」AI」了,在你們還沒搞明白大數據的時候,人工智能就開始引領下一個潮流了。本着跟風的態度,我也嘗試去窺探個究竟。git


引言

當前不管是學術界仍是工業界,深度學習都受到極大的追捧,尤爲是在Google開源深度學習平臺TensorFlow以後,更是給深度學習火上澆油。目前在開源社區Github上全部開源項目中,TensorFlow最爲活躍,從推出到如今,經歷了幾個版本的演進,能夠說可以靈活高效地解決大量實際問題。本文主要嘗試闡述TensorFlow在天然語言處理(NLP)領域的簡單應用,讓你們夥兒更加感性地認識TensorFlow。github

說到NLP,其實我對它並非很熟悉,以前也不曾有過NLP的相關經驗,本文是我最近學習TensorFlow的一些積累,就當拋磚引玉了。當前互聯網天天都在產生大量的文本和音頻數據,經過挖掘這些數據,咱們能夠作一些更加便捷的應用,例如機器翻譯、語音識別、詞性標註以及信息檢索等,這些都屬於NLP範疇。而在NLP領域中,語言模型是最基本的一個環節,本文主要圍繞語言模型展開,首先介紹其基本原理,進而引出詞向量(word2vec)、循環神經網絡(RNN)、長短時記憶網絡(LSTM)等深度學習相關模型,並詳細介紹如何利用 TensorFlow 實現上述模型。算法

語言模型

語言模型是一種機率模型,它是基於一個語料庫建立,獲得每一個句子出現的機率,通俗一點講就是看一句話是否是正常人說出來的,數學上表示爲:api

 

P(W)=P(w1w2wt)=P(w1)P(w2|w1)P(w3|w1w2)P(wt|w1w2wt1)(2-1)(2-1)P(W)=P(w1w2…wt)=P(w1)P(w2|w1)P(w3|w1w2)⋯P(wt|w1w2⋯wt−1)

 

上述公式的意義是:一個句子出現的機率等於給定前面的詞狀況下,緊接着後面的詞出現的機率。它是經過條件機率公式展開獲得。其中條件機率 P(w2|w1),P(w3|w1w2),,P(wt|w1w2wt1)P(w2|w1),P(w3|w1w2),⋯,P(wt|w1w2⋯wt−1) 就是建立語言模型所須要的參數,每一個條件機率的意義解釋爲:根據前面的詞預測下一個詞的機率。有了這些條件機率參數,給定一個句子,就能夠經過以上公式獲得一個句子出現的機率。例若有一句話「php是最好的語言」(我不肯定這是否是天然語言),假設已經分詞爲「php」、「是」、「最好的」、「語言」,那麼它出現的機率爲P(「php」,「是」,「最好的」,「語言」)=P(「php」)P(「是」|「php」)P(「最好的」|「php」,「是」)P(「語言」|「php」,「是」,「最好的」),若是這個機率較大,那麼判斷爲正常的一句話。以上這些條件機率經過以下貝葉斯公式獲得:網絡

 

P(wt|w1w2wt1)=P(w1w2wt)P(w1w2wt1)(2-2)(2-2)P(wt|w1w2⋯wt−1)=P(w1w2⋯wt)P(w1w2⋯wt−1)

 

根據大數定理上述公式又能夠近似爲:session

 

P(wt|w1w2wt1)=count(w1w2wt)count(w1,w2,wt1)(2-3)(2-3)P(wt|w1w2⋯wt−1)=count(w1w2⋯wt)count(w1,w2,⋯wt−1)

 

假如語料庫裏有 NN 個詞,一個句子長度爲 TT ,那麼就有 NTNT 種可能,每一種可能都要計算 TT 個條件機率參數,最後要計算 TNTTNT 個參數並保存,不只計算量大,對於內存要求也是驚人。那麼如何避免這個問題呢,以前窮舉的方法行不通,那麼換個思路,採用一種偷懶的處理方法,就是將上述公式中條件機率作個以下近似:架構

 

P(wt|w1w2wt1)P(wt|wtn+1wt1)(2-4)(2-4)P(wt|w1w2⋯wt−1)≈P(wt|wt−n+1⋯wt−1)

 

這意思就是說一個詞出現的機率只與它前面 n1n−1 個詞有關,而不是與它前面全部的詞有關,這樣極大的減小了統計的可能性,提升了計算效率,這種處理方法稱之爲 n-gram 模型,一般 nn 取2~3就能獲得不錯的效果。總結起來,n-gram 模型就是統計語料庫中詞串出現的次數,一次性計算獲得詞串的機率並將其保存起來,在預測一個句子時,直接經過前面所述的條件機率公式獲得句子出現的機率。app

近年也流行起神經網絡語言模型,從機器學習的角度來看,一開始不所有計算這些詞串的機率值,而是經過一個模型對詞串的機率進行建模,而後構造一個目標函數,不斷優化這個目標,獲得一組優化的參數,當須要哪一個詞串機率時,利用這組優化的參數直接計算獲得對應的詞串機率。將詞串機率 P(w|context(w))P(w|context(w)) 看作是 ww 和 context(w)context(w) 的函數,其中 context(w)context(w) 表示此 ww 的上下文,即至關於前面所述的 n-gram 模型的前 n1n−1 個詞,那麼就有以下數學表示。

 

P(w|context(w))=F(w,context(w),Θ)(2-5)(2-5)P(w|context(w))=F(w,context(w),Θ)

 

目標函數採用對數似然函數,表示以下(其中 NN 表明語料庫中詞典的大小):

 

Obj=1Ni=1NlogP(wi|contexti)(2-6)(2-6)Obj=1N∑i=1NlogP(wi|contexti)

 

經過優化算法不斷最小化目標函數獲得一組優化的參數 ΘΘ ,在神經網絡中參數 ΘΘ 則爲網絡層與層間的權值與偏置。那麼在用神經網絡學習語言模型[1]時,如何表示一個詞呢?一般,在機器學習領域,是將一個樣本對象抽象爲一個向量,因此相似地,神經網絡語言模型中是將詞(或短語)表示爲向量,一般叫作word2vec。那麼神經網絡語言模型就能夠表示以下示意圖。

nlp-nn

上述神經網絡包括輸入層、投影層、隱藏層以及輸出層,其中投影層只是對輸入層作了一個預處理,將輸入的全部詞進行一個鏈接操做,假如一個詞表示爲 mm 維向量,那麼由 n1n−1 個詞鏈接後則爲 (n1)m(n−1)m 維向量,將鏈接後的向量做爲神經網絡的輸入,通過隱藏層再到輸出層,其中 WW 、UU 分別爲投影層到隱藏層、隱藏層到輸出層的權值參數,pp 、qq 分別爲投影層到隱藏層、隱藏層到輸出層的偏置參數,整個過程數學表達以下:

 

ZY=σ(WX+p)=UZ+q(2-7)(2-7)Z=σ(WX+p)Y=UZ+q

 

其中 σσ 爲sigmoid函數,做爲隱藏層的激活函數,輸出層的輸出向量爲 NN 維,對應於語料庫中詞典的大小。通常須要再通過softmax歸一化爲機率形式,獲得預測語料庫中每一個詞的機率。以上神經網絡語言模型看似很簡單,可是詞向量怎麼來呢,如何將一個詞轉化爲向量的形式呢?下面做詳細闡述。

詞向量(word2vec)

詞向量要作的事就是將語言數學化表示,以往的作法是採用 One-hot Representation 表示一個詞,即語料庫詞典中有 NN 個詞,那麼向量的維度則爲 NN ,給每一個詞編號,對於第 ii 個詞,其向量表示除了第 ii 個單元爲1,其餘單元都爲0的 NN 維向量,這種詞向量的缺點顯而易見,通常來講語料庫的詞典規模都特別大,那麼詞向量的維數就很是大,而且詞與詞之間沒有關聯性,並不能真實地刻畫語言自己的性質,例如「騰訊」、「小馬哥」這兩個詞經過One-hot向量表示,沒有任何關聯。爲了克服One-hot Representation 的缺點,Mikolov大神提出了一種 Distributed Representation[2],說個題外話,在你們都在如火如荼的用CNN作圖像識別的時候,這哥們卻在研究如何用神經網絡處理NLP問題,最後發了大量關於神經網絡NLP的高水平論文,成爲這一領域的靈魂人物之一。顧名思義,Distributed Representation 就是把詞的信息分佈到向量不一樣的份量上,而不是像 One-hot Representation 那樣全部信息集中在一個份量上,它的作法是將詞映射到 mm 維空間,表示爲 mm 維向量,也稱之爲 Word Embedding,這樣一方面能夠減少詞向量的維度,另外一方面,能夠將有關聯的詞映射爲空間中相鄰的點,詞與詞之間的關聯性經過空間距離來刻畫,以下圖所示。

nlp-word2vec-example

詞被映射到3維空間,每一個詞表示爲一個3維向量,相近的詞離的較近,能夠看到兩組差很少關係的詞,他們之間的詞向量距離也差很少。

要想獲得詞向量,須要藉助語言模型訓練獲得,本質上來講,詞向量是在訓練語言模型過程當中獲得的副產品。解決word2vec問題有兩種模型,即 CBOW 和 Skip-Gram 模型[3],以下圖所示:

nlp-word2vec-model

CBOW 模型是根據詞的上下文預測當前詞,這裏的上下文是由待預測詞的先後 cc 個詞組成。而 Skip-Gram 模型則相反,是經過當前詞去預測上下文。給定一個語料庫做爲訓練集,就能夠經過以上模型訓練出每一個詞的向量表示。從實驗結果來看,CBOW 模型會平滑掉一些分佈信息,由於它將詞的上下文做爲單個樣本,而 Skip-Gram 模型將詞上下文拆分爲多個樣本,訓練獲得的結果更爲精確,爲此,TensorFlow 中 word2vec 採用的是 Skip-Gram 模型,對應於文[2]中所提出的一種更爲優化的 Skip-Gram 模型,下面着重介紹其原理,更多關於 CBOW 和 Skip-Gram 模型細節能夠參閱文[3]。

Skip-Gram 模型

前面也提到, Skip-Gram 模型是根據當前詞去預測上下文,例若有以下語句:

「php 是 世界上 最好的 語言」

假定上下文是由待預測詞的先後2個詞組成,那麼由以上句子能夠獲得以下正樣本:

(世界上, 是), (世界上, php), (世界上, 最好的), (世界上, 語言), (最好的, 世界上), …

訓練目標爲最大化如下對數似然函數:

 

Obj=1Ni=1Ncjc,j0log p(wi+j|wi)(3-1)(3-1)Obj=1N∑i=1N∑−c⩽j⩽c,j≠0log p(wi+j|wi)

 

其中 cc 爲上下文的距離限定,即僅取詞 wtwt 的先後 cc 個詞進行預測。cc 越大,訓練結果更精確,可是計算複雜度加大,訓練成本相應也更大,通常取 cc 爲2~3就能訓練出不錯的結果。基本的 Skip-Gram 模型採用softmax方法將以上目標函數中機率 p(wi+j|wi)p(wi+j|wi) 定義爲:

 

p(wO|wI)=exp(θwOTvwI)wWexp(θwTvwI)(3-2)(3-2)p(wO|wI)=exp(θwOTvwI)∑w∈Wexp(θwTvwI)

 

其中 vwvw 表示輸入詞 ww 的向量,θwθw 表示預測結果爲 ww 的權值參數,兩者都是待訓練的參數。不難發現,經過以上公式,計算每一個詞的損失函數都要用到詞典中的全部詞,而通常詞典的量級都很是大,因此這種方式是不切實際的。對於一個樣本,例如(「世界上」, 「php」),無非是根據詞「世界上」去預測詞「php」,那麼就能夠當作一個二分類問題,對於輸入詞「世界上」,預測「php」爲正,預測其餘則爲負,其餘詞多是除「php」之外的全部詞,爲了簡化計算,能夠經過採樣的方式,每次隨機從全部除「php」之外的詞中取 kk 個詞做爲負樣本對象,那麼訓練目標則能夠轉化爲相似於邏輯迴歸目標函數:

 

Obj=logσ(θwOTvwI)+j=1kEwjPn(w)[logσ(θwjTvwI)](3-3)(3-3)Obj=logσ(θwOTvwI)+∑j=1kEwj∼Pn(w)[logσ(−θwjTvwI)]

 

以上表達式稱之爲 NCE(Noise-contrastive estimation)[4]目標函數,其中等號右邊第二項表示經過一個服從 Pn(w)Pn(w)分佈的採樣算法取得 kk 個負樣本的指望損失。文[2]中採用了一個簡單的一元分佈採樣,簡化了計算,稱之爲負採樣(Negative Sampling),下面詳細介紹負採樣算法。

負採樣算法

詞典中的每一個詞在語料庫中出現的頻次有高有低,理論上來講,對於那些高頻詞,被選爲負樣本的機率較大,對於那些低頻詞,被選爲負樣本的機率較小。基於這個基本事實,能夠經過帶權採樣方法來實現,假設每一個詞的詞頻表示爲單位線段上的一小分段,對於詞典大小爲 NN 的語料庫,能夠將詞典中全部的詞表示爲單位線段上的一點,再在單位線段上等距離劃分 MM 個等分, M>>NM>>N , 具體採樣過程就是隨機獲得一個數 i<Mi<M,經過映射找到其對應的詞,以下如所示。

nlp-word2vec-negative-sampling

文[2]中在實際負採樣計算詞頻時,作了一點修正,不是簡單的統計詞的出現次數,而是對詞的出現次數作了 αα 次冪處理,最後詞頻公式爲:

 

freq(w)=[counter(w)]3/4uW[counter(u)]3/4(3-4)(3-4)freq(w)=[counter(w)]3/4∑u∈W[counter(u)]3/4

 

高頻詞二次採樣

在一個大語料庫中,不少常見的詞大量出現,如「的」、「是」等。這些詞雖然詞頻較高,可是能提供的有用信息卻不多。通常來講,這些高頻詞的詞向量在訓練幾百萬樣本後基本不會有太大的變化,爲了提升訓練速度,平衡低頻詞和高頻詞,文[2]中提出一種針對高頻詞二次採樣的技巧,對於每一個詞,按以下機率丟棄而不作訓練。

 

P(wi)=1tf(wi)−−−−−√(3-5)(3-5)P(wi)=1−tf(wi)

 

其中f(wi)f(wi)表示詞頻,從上述公式中不難發現,二次採樣僅針對那些知足 f(wi)>tf(wi)>t 所謂的高頻詞有效,參數 tt 根據語料庫的大小而設置,通常設置爲 10510−5 左右。

TensorFlow實現

根據以上實現原理,下面結合代碼闡述利用TensorFlow實現一個簡易的word2vec模型[5],藉助TensorFlow豐富的api以及強大的計算引擎,咱們能夠很是方便地表達模型。給定語料庫做爲訓練數據,首先掃描語料庫創建字典,爲每一個詞編號,同時將那些詞頻低於min_count的詞過濾掉,即不對那些陌生詞生成詞向量。對於一個樣本(「世界上」, 「php」),利用負採樣獲得若干負實例,分別計算輸入詞爲「世界上」到「php」以及若干負樣本的logit值,最後經過交叉熵公式獲得目標函數(3-3)。

nlp-word2vec-forward

構建計算流圖

首先定義詞向量矩陣,也稱爲 embedding matrix,這個是咱們須要經過訓練獲得的詞向量,其中vocabulary_size表示詞典大小,embedding_size表示詞向量的維度,那麼詞向量矩陣爲 vocabulary_size ×× embedding_size,利用均勻分佈對它進行隨機初始化:

1
2
embeddings = tf.Variable(
tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))

 

定義權值矩陣和偏置向量(對應於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的輸入,從詞向量矩陣中找到對應的向量表示,以及從權值矩陣和偏置向量中找到對應正確輸出的參數,其中examples是輸入詞,labels爲對應的正確輸出,一維向量表示,每一個元素爲詞在字典中編號:

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)

 

負採樣獲得若干非正確的輸出,其中labels_matrix爲正確的輸出詞,採樣的時候會跳過這些詞,num_sampled爲採樣個數,distortion即爲公式(3-4)中的冪指數:

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())

 

找到採樣樣本對應的權值和偏置參數:

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,並經過交叉熵獲得目標函數(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

 

訓練模型

計算流圖構建完畢後,咱們須要去優化目標函數。採用梯度降低逐步更新參數,首先須要肯定學習步長,隨着迭代進行,逐步減小學習步長,其中trained_words爲已訓練的詞數量,words_to_train爲全部待訓練的詞數量:

1
2
lr = init_learning_rate * tf.maximum(
0.0001, 1.0 - tf.cast(trained_words, tf.float32) / words_to_train)

 

定義優化算子,使用梯度降低訓練模型:

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)

 

驗證詞向量

通過以上步驟後,便可獲得詞向量矩陣,即上述代碼中的變量embeddings,那麼如何驗證獲得的詞向量矩陣的好壞呢,Mikolov等人發現[2],若是一對關係差很少的詞,其詞向量在空間中的連線近乎平行,以下圖所示。

nlp-word2vec-analogical

爲此,給定基準測試集,其每行包含4個詞組成一個四元組 (w1,w2,w3,w4)(w1,w2,w3,w4) ,對於一個較好的詞向量結果,每一個四元組大體會有以下關係:

 

Vector(w1)Vector(w2)+Vector(w4)=Vector(w3)Vector(w1)−Vector(w2)+Vector(w4)=Vector(w3)

 

循環神經網絡(RNN)

人類不是從腦子一片空白開始思考,當你讀一篇文章的時候,你會根據前文去理解下文,而不是每次看到一個詞後就忘掉它,理解下一個詞的時候又從頭開始。傳統的神經網絡模型是從輸入層到隱藏層再到輸出層,每層之間的節點是無鏈接的,這種普通的神經網絡不具有記憶功能,而循環神經網絡(Recurrent Neural Network,RNN)就是來解決這類問題,它具有記憶性,一般用於處理時間序列問題,在衆多NLP問題中,RNN取得了巨大成功以及普遍應用。

在RNN網絡中,一個序列當前的輸出除了與當前輸入有關之外,還與前面的輸出也有關,下圖爲RNN中一個單元的結構示意圖,圖片來源於文[7]。

nlp-rnn-cell

上圖理解起來可能還不是很形象,根據時間序列將上圖平鋪展開獲得以下圖,其鏈式的特徵揭示了 RNN 本質上是與序列相關的,因此 RNN 對於這類數據來講是最天然的神經網絡架構。

nlp-rnn-unrolled

然而 RNN 有一個缺點,雖然它能夠將以前的信息鏈接到當前的輸入上,可是若是當前輸入與以前的信息時間跨度很大,因爲梯度衰減等緣由,RNN 學習如此遠的信息的能力會降低,這個問題稱之爲長時間依賴(Long-Term Dependencies)問題。例如預測一句話「飛機在天上」下一個詞,可能不須要太多的上下文就能夠預測到下一個詞爲「飛」,這種狀況下,相關信息與要預測的詞之間的時間跨度很小,RNN 能夠很容易學到以前的信息。再好比預測「他來自法國,…,他會講」的下一個詞,從當前的信息來看,下一個詞多是一種語言,可是要想準確預測哪一種語言,就須要再去前文找信息了,因爲前文的「法國」離當前位置的時間跨度較大,RNN很難學到如此遠的信息。更多長時間依賴細節參考文[8]。幸運的是,有一種 RNN 變種,叫作長短時記憶網絡(Long Short Term Memory networks, LSTM),能夠解決這個問題。

長短時記憶網絡(LSTM)

LSTM 是一種帶有選擇性記憶功能的 RNN,它能夠有效的解決長時間依賴問題,並能學習到以前的關鍵信息。以下圖所示爲 LSTM 展開後的示意圖。

nlp-lstm-unrolled

相對於 RNN , LSTM 只是在每一個單元結構上作了改進,在 RNN 中,每一個單元結構只有單個激活函數,而 LSTM 中每一個單元結構更爲複雜,它增長了一條狀態線(圖中最上面的水平線),以記住從以前的輸入學到的信息,另外增長三個門(gate)來控制其該狀態,分別爲忘記門、輸入門和輸出門。忘記門的做用是選擇性地將以前不重要的信息丟掉,以便存儲新信息;輸入門是根據當前輸入學習到新信息而後更新當前狀態;輸出門則是結合當前輸入和當前狀態獲得一個輸出,該輸出除了做爲基本的輸出外,還會做爲下一個時刻的輸入。下面用數學的方式表達每一個門的意思。

忘記門,要丟掉的信息以下:

 

ft=σ(Wf[ht1,xt]+bf)(5-1)(5-1)ft=σ(Wf[ht−1,xt]+bf)

 

輸入門,要增長的信息以下:

itCt~=σ(Wi[ht1,xt]+bi)=tanh(WC[ht1,xt]+bC)(5-2)(5-2)it=σ(Wi[ht−1,xt]+bi)Ct~=tanh(WC[ht−1,xt]+bC)

 

那麼根據忘記門和輸入門,狀態更新以下:

 

Ct=ftCt1+itCt~(5-3)(5-3)Ct=ft∗Ct−1+it∗Ct~

 

輸出門,獲得輸出信息以下:

 

otht=σ(Wo[ht1,xt]+bo)=ottanh(Ct)(5-4)(5-4)ot=σ(Wo[ht−1,xt]+bo)ht=ot∗tanh(Ct)

 

LSTM 單元輸入都是上一個時刻的輸出與當前時刻的輸入經過向量concat鏈接而獲得,基於這個輸入,利用sigmoid函數做爲三個門的篩選器,分別獲得 ftft 、itit 、otot,這三個篩選器分別選擇部分份量對狀態進行選擇性忘記、對輸入進行選擇性輸入、對輸出進行選擇性輸出。以上是 LSTM 基本結構原理,在這基礎上,根據不一樣的實際應用場景,演變出不少 LSTM 的變體,更多關於 LSTM 的詳細解釋請參考文[7]。下面介紹一種深層 LSTM 網絡[9],該結構也是 TensorFlow 中 LSTM 所實現的根據[10]。

深層LSTM網絡

深度學習,其特色在於深,前面已經講述單層 LSTM 網絡結構,深層 LSTM 網絡其實就是將多層 LSTM 疊加,造成多個隱藏層,以下圖所示。

nlp-lstm-multilayer

上圖中每一個 LSTM 單元內部結構以下圖所示,對於 ll 層 tt 時刻來講,hlt1ht−1l 爲 ll 層 t1t−1 時刻(即上一個時刻)的輸出,hl1thtl−1 爲 l1l−1 層(即上一層) tt 時刻的輸出,這兩個輸出疊加做爲 ll 層 tt 時刻的輸入。

nlp-lstm-multilayer-cell

根據上面的結構,能夠獲得 ll 層 LSTM 數學表達, hl1t,hlt1,clt1hlt,clthtl−1,ht−1l,ct−1l→htl,ctl:

 

fiog clthlt=σ(Wf[hl1t,hlt1]+bf)=σ(Wi[hl1t,hlt1]+bi)=σ(Wo[hl1t,hlt1]+bo)=tanh(Wg[hl1t,hlt1]+bg)=fclt1+ig=otanh(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)

 

其中 clt1ct−1l 表示上一時刻的狀態,cltctl 表示由當前輸入更新後的狀態。

正則化

然而,實踐證實大規模的 LSTM 網絡很容易過擬合,實際應用中,須要採起正則化方法來避免過擬合,神經網絡中常見的正則化方法是Dropout方法[11],文[12]提出一種簡單高效的Dropout方法運用於 RNN/LTSM 網絡。以下圖所示,Dropout僅應用於虛線方向的輸入,即僅針對於上一層的輸出作Dropout。

nlp-lstm-multilayer-dropout

根據上圖的Dropout策略,公式(5-5)能夠改寫成以下形式:

 

fiog clthlt=σ(Wf[D(hl1t),hlt1]+bf)=σ(Wi[D(hl1t),hlt1]+bi)=σ(Wo[D(hl1t),hlt1]+bo)=tanh(Wg[D(hl1t),hlt1]+bg)=fclt1+ig=otanh(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操做符,會隨機地將 hl1thtl−1 的中的份量設置爲零。以下圖所示,黑色粗實線表示從 t2t−2 時刻的信息流向 t+2t+2 時刻做爲其預測的參考,它經歷了 L+1L+1 次的Dropout,其中 LL 表示網絡的層數。

nlp-lstm-multilayer-dropout-event-flow

TensorFlow實現

根據前面所述的 LSTM 模型原理,實現以前提到的語言模型,即根據前文預測下一個詞,例如輸入「飛機在天上」預測下一個詞「飛」,使用 TensorFlow 來實現 LSTM 很是的方便,由於 TensorFlow 已經提供了基本的 LSTM 單元結構的Operation,其實現原理就是基於文[12]提出的帶Dropout的 LSTM 模型。完整代碼請參考ptb_word_lm.py

構建LSTM模型

利用TensorFlow提供的Operation,實現 LSTM 網絡很簡單,首先定義一個基本的 LSTM 單元,其中size爲 LSTM 單元的輸出維度,再對其添加Dropout,根據 LSTM 的層數num_layers獲得多層的 RNN 結構單元。

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 網絡的狀態初始化爲0。詞的輸入由詞向量表示,因此先定義一個embedding矩陣,這裏能夠不要關心它一開始有沒有,它會在訓練過程當中的慢慢獲得的,僅做爲訓練的副產品。假設LSTM網絡展開num_steps步,每一步給定一個batch的詞做爲輸入,通過 LSTM 單元處理後,狀態更新並獲得輸出,並經過softmax歸一化後計算損失函數。

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_b

loss = tf.nn.seq2seq.sequence_loss_by_example(
[logits],
[tf.reshape(targets, [-1])],
[tf.ones([batch_size * num_steps])])

訓練模型

簡單採用梯度降低優化上述損失函數,逐步迭代,直至最大迭代次數,獲得final_state,即爲LSTM所要學習的參數。

 
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})

 

驗證測試模型

模型訓練完畢後,咱們已經獲得LSTM網絡的狀態,給定輸入,通過LSTM網絡後便可獲得輸出了。

1
2
(cell_output, _) = cell(inputs, state)
session.run(cell_output)

 

小結

在使用TensorFlow處理深度學習相關問題時,咱們不須要太關注其內部實現細節,只需把精力放到模型的構建上,利用TensorFlow已經提供的抽象單元結構就能夠構建靈活的模型。也偏偏正是由於TensorFlow的高度抽象化,有時讓人理解起來頗費勁。因此在咱們使用TensorFlow的過程當中,不要把問題細化的太深,一切數據當作Tensor便可,利用Tensor的操做符對其進行運算,不要在腦海裏想如何如何的運算細節等等,否則就會身陷囹圄。

參考文獻

[1]. Bengio Y, Schwenk H, Senécal J S, et al. Neural probabilistic language models[M]//Innovations in Machine Learning. Springer Berlin Heidelberg, 2006: 137-186.MLA.
[2]. Mikolov T, Sutskever I, Chen K, et al. Distributed representations of words and phrases and their compositionality[C]//Advances in neural information processing systems. 2013: 3111-3119.
[3]. Mikolov T, Le Q V, Sutskever I. Exploiting similarities among languages for machine translation[J]. arXiv preprint arXiv:1309.4168, 2013.
[4]. Gutmann M U, Hyvärinen A. Noise-contrastive estimation of unnormalized statistical models, with applications to natural image statistics[J]. The Journal of Machine Learning Research, 2012, 13(1): 307-361.
[5]. Vector Representations of Words. https://www.tensorflow.org/versions/r0.8/tutorials/word2vec/index.html#vector-representations-of-words
[6]. word2vec 中的數學原理詳解. http://www.cnblogs.com/peghoty/p/3857839.html
[7]. Understanding LSTM Networks. http://colah.github.io/posts/2015-08-Understanding-LSTMs/
[8]. Bengio Y, Simard P, Frasconi P. Learning long-term dependencies with gradient descent is difficult[J]. Neural Networks, IEEE Transactions on, 1994, 5(2): 157-166.
[9]. Graves A. Generating sequences with recurrent neural networks[J]. arXiv preprint arXiv:1308.0850, 2013.
[10]. Recurrent Neural Networks. https://www.tensorflow.org/versions/r0.8/tutorials/recurrent/index.html#recurrent-neural-networks
[11]. Srivastava N. Improving neural networks with dropout[D]. University of Toronto, 2013.
[12]. Zaremba W, Sutskever I, Vinyals O. Recurrent neural network regularization[J]. arXiv preprint arXiv:1409.2329, 2014.

轉載請註明出處,本文永久連接:http://sharkdtu.com/posts/nn-nlp.html

相關文章
相關標籤/搜索