鏈接:https://www.zhihu.com/question/37082800/answer/126430702
來源:知乎
著作權歸作者所有,轉載請聯系作者獲得授權。
鏈接: 循環神經網絡RNN打開手冊 - 混沌巡洋艦 - 知乎專欄
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
最近朋友前小伙伴都已經傳播瘋了的谷歌翻譯,實現了令人驚艷的性能。這里的技術核心, 就是RNN- 我們常說的傳說中的循環神經網絡。 RNN可以稱得上是深度學習未來最有前景的工具之一。 你想了解它的威力的根源嗎? 你想知道一些最新的RNN應用?請看下文。
為什么RNN會有如此強大的效力? 讓我們從基礎學起。首先, 要看RNN和對於圖像等靜態類變量處理立下神功的卷積網絡CNN的結構區別來看, “循環”兩個字,已經點出了RNN的核心特征, 即系統的輸出會保留在網絡里, 和系統下一刻的輸入一起共同決定下一刻的輸出。這就把動力學的本質體現了出來, 循環正對應動力學系統的反饋概念,可以刻畫復雜的歷史依賴。另一個角度看也符合著名的圖靈機原理。 即此刻的狀態包含上一刻的歷史,又是下一刻變化的依據。 這其實包含了可編程神經網絡的核心概念,即, 當你有一個未知的過程,但你可以測量到輸入和輸出, 你假設當這個過程通過RNN的時候,它是可以自己學會這樣的輸入輸出規律的, 而且因此具有預測能力。 在這點上說, RNN是圖靈完備的。
圖: 圖1即CNN的架構, 圖2到5是RNN的幾種基本玩法。圖2是把單一輸入轉化為序列輸出,例如把圖像轉化成一行文字。 圖三是把序列輸入轉化為單個輸出, 比如情感測試,測量一段話正面或負面的情緒。 圖四是把序列轉化為序列, 最典型的是機器翻譯, 注意輸入和輸出的“時差”。 圖5是無時差的序列到序列轉化, 比如給一個錄像中的每一幀貼標簽。 圖片來源 The unreasonable effective RNN。
我們用一段小巧的python代碼讓你重新理解下上述的原理:
classRNN:
# ...
def step(self, x):
# update the hidden state
self.h = np.tanh(np.dot(self.W_hh, self.h) + np.dot(self.W_xh, x))
# compute the output vector
y = np.dot(self.W_hy, self.h)
return y
這里的h就是hidden variable 隱變量,即整個網絡每個神經元的狀態,x是輸入, y是輸出, 注意着三者都是高維向量。隱變量h,就是通常說的神經網絡本體,也正是循環得以實現的基礎, 因為它如同一個可以儲存無窮歷史信息(理論上)的水庫,一方面會通過輸入矩陣W_xh吸收輸入序列x的當下值,一方面通過網絡連接W_hh進行內部神經元間的相互作用(網絡效應,信息傳遞),因為其網絡的狀態和輸入的整個過去歷史有關, 最終的輸出又是兩部分加在一起共同通過非線性函數tanh。 整個過程就是一個循環神經網絡“循環”的過程。 W_hh理論上可以可以刻畫輸入的整個歷史對於最終輸出的任何反饋形式, 這是RNN強大的關鍵。
那么CNN似乎也有類似的功能? 那么CNN是不是也可以當做RNN來用呢? 答案是否定的,RNN的重要特性是可以處理不定長的輸入,得到一定的輸出。當你的輸入可長可短, 比如訓練翻譯模型的時候, 你的句子長度都不固定,你是無法像一個訓練固定像素的圖像那樣用CNN搞定的。而利用RNN的循環特性可以輕松搞定。
RNN的本質是一個數據推斷(inference)機器, 它可以尋找兩個時間序列之間的關聯, 只要數據足夠多,就可以得到從x(t)到y(t)的概率分布函數, 從而達到推斷和預測的目的。 這里我們無疑回想到另一個做時間序列推斷的神器- HMM, 隱馬爾科夫模型, 在這個模型里, 也有一個輸入x和輸出y,和一個隱變量h, 而這的h和剛剛的RNN里的h區別是迭代法則, 隱馬通過躍遷矩陣把此刻的h和下一刻的h聯系在一起。躍遷矩陣隨時間變化, 而RNN中沒有躍遷矩陣的概念,取而代之的是神經元之間的連接矩陣。 HMM本質是一個貝葉斯網絡, 因此每個節點都是有實際含義的,而RNN中的神經元只是信息流動的樞紐而已,並無實際對應含義。兩者還是存在千絲萬縷的聯系, 首先隱馬能干的活RNN幾乎也是可以做的,比如語言模型,但是就是RNN的維度會更高。在這些任務上RNN事實上是用它的網絡表達了隱馬的躍遷矩陣。在訓練方法上, 隱馬可以通過類似EM來自最大后驗概率的算法得出隱變量和躍遷矩陣最可能的值。 而RNN可以通過一般的梯度回傳算法訓練。
那么我們看一些RNN處理任務的具體案例吧:
比如說, 學說話! 如何叫計算機說出一段類似人話的東西呢?
此處我們從一個非常具體的程序講起, 看你如何一步步的設計一個程序做最簡單的語言生成任務,這個任務的目標類似是讓神經網絡做一個接龍, 給它一個字母,讓它猜后面的, 比如給它Hell, 它就跟着街上o。 示意圖如下:
data = open('input.txt', 'rw').read() # should be simple plain text file
chars = list(set(data)) # vocabulary
data_size, vocab_size = len(data), len(chars)
print 'data has %d characters, %d unique.' % (data_size, vocab_size)
char_to_ix = { ch:i for i,ch in enumerate(chars) } # vocabulary
ix_to_char = { i:ch for i,ch in enumerate(chars) } # index
首先我們把字母表達成向量,用到一個叫enumerate的函數, 這如同在構建語言的數字化詞典(vocabulary), 在這一步之后, 語言信息就變成了數字化的時間序列
hidden_size = 100 # size of hidden layer of neurons
seq_length = 25 # number of steps to unroll the RNN for
learning_rate = 1e-1
# model parameters
Wxh = np.random.randn(hidden_size, vocab_size)*0.01 # input to hidden
Whh = np.random.randn(hidden_size, hidden_size)*0.01 # hidden to hidden
Why = np.random.randn(vocab_size, hidden_size)*0.01 # hidden to output
bh = np.zeros((hidden_size, 1)) # hidden bias
by = np.zeros((vocab_size, 1)) # output bias
下一步我們要初始化三個矩陣,即W_xh, W_hh,W_hy 分別表示輸入和隱層, 隱層和隱層, 隱層和輸出之間的連接,以及隱層和輸出層的激活函數中的bias( bh和by):
Loss=[ ]
Out=[ ]
while True:
# prepare inputs (we're sweeping from left to right in steps seq_length long)
if p+seq_length+1 >= len(data) or n == 0:
hprev = np.zeros((hidden_size,1)) # reset RNN memory
p = 0 # go from start of data
inputs = [char_to_ix[ch] for ch in data[p:p+seq_length]]
targets = [char_to_ix[ch] for ch in data[p+1:p+seq_length+1]]
下一步是正是開始程序, 首先准備輸入:
# sample from the model now and then
if n % 100 == 0:
sample_ix = sample(hprev, inputs[0], 200)
txt = ' '.join(ix_to_char[ix] for ix in sample_ix)
print '----\n %s \n----' % (txt, )
這一步要做的是每訓練一百步看看效果, 看RNN生成的句子是否更像人話。 Sample的含義就是給他一個首字母,然后神經網絡會輸出下一個字母,然后這兩個字母一起作為再下一個字母的輸入,依次類推:
# forward seq_length characters through the net and fetch gradient
loss, dWxh, dWhh, dWhy, dbh, dby, hprev,y = lossFun(inputs, targets, hprev)
smooth_loss = smooth_loss * 0.999 + loss * 0.001
if n % 100 == 0: print 'iter %d, loss: %f' % (n, smooth_loss) # print progress
這一步是尋找梯度, loss function即計算梯度 , loss function的具體內容關鍵即測量回傳的信息以供學習。函數內容再最后放出
最后一步是根據梯度調整參數的值,即學習的過程。
# perform parameter update with Adagrad
for param, dparam, mem in zip([Wxh, Whh, Why, bh, by],
[dWxh, dWhh, dWhy, dbh, dby],
[mWxh, mWhh, mWhy, mbh, mby]):
mem += dparam * dparam
param += -learning_rate * dparam / np.sqrt(mem + 1e-8) # adagrad update
p += seq_length # move data pointer
n += 1 # iteration counter
Loss.append(loss)
Out.append(txt)
這就是主程序,沒錯, 就是這么簡單, 剛剛省略的loss function 如下:
def lossFun(inputs, targets, hprev):
"""
inputs,targets are both list of integers.
hprev is Hx1 array of initial hidden state
returns the loss, gradients on model parameters, and last hidden state
"""
xs, hs, ys, ps = {}, {}, {}, {}
hs[-1] = np.copy(hprev)
loss = 0
# forward pass
for t in xrange(len(inputs)):
xs[t] = np.zeros((vocab_size,1)) # encode in 1-of-k representation
xs[t][inputs[t]] = 1
hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh, hs[t-1]) + bh) # hidden state
#Whh*hs-->Whh*y_syn*hs; y_syn[t+1]=MishaModel(y_syn[t],tau,U,hs) xe*xg(t)
ys[t] = np.dot(Why, hs[t]) + by # unnormalized log probabilities for next chars
ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t])) # probabilities for next chars
loss += -np.log(ps[t][targets[t],0]) # softmax (cross-entropy loss)
# backward pass: compute gradients going backwards
dWxh, dWhh, dWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why)
dbh, dby = np.zeros_like(bh), np.zeros_like(by)
dhnext = np.zeros_like(hs[0])
for t in reversed(xrange(len(inputs))):
dy = np.copy(ps[t])
dy[targets[t]] -= 1
# backprop into y. see CS231n Convolutional Neural Networks for Visual Recognition if confused here
dWhy += np.dot(dy, hs[t].T)
dby += dy
dh = np.dot(Why.T, dy) + dhnext # backprop into h
dhraw = (1 - hs[t] * hs[t]) * dh # backprop through tanh nonlinearity
dbh += dhraw
dWxh += np.dot(dhraw, xs[t].T)
dWhh += np.dot(dhraw, hs[t-1].T)
dhnext = np.dot(Whh.T, dhraw)
for dparam in [dWxh, dWhh, dWhy, dbh, dby]:
np.clip(dparam, -5, 5, out=dparam) # clip to mitigate exploding gradients
return loss, dWxh, dWhh, dWhy, dbh, dby, hs[len(inputs)-1],ys
讓我們看看RNN得到的一些訓練結果,訓練素材是網上隨便找的一小段莎劇評論文章:
期初一些亂碼:
T. TpsshbokKbpWWcTxnsOAoTn:og?eu l0op,vHH4tag4,y.ciuf?w4SApx? eh:dfokdrlKvKnaTd?bdvabr.0rSuxaurobkbTf,mb,Htl0uma4HHpeas n4ub::wslmpscsWmtm?xbH us:HOug4nvdWS4nil hTkbH Smeu wo0tocvTAfyuvme0vihkpviiHT0:
過一會開始有一些單詞模樣的東西出來, 甚至有Shakespear:
am Shakespeare brovid thiais on an 4iwpes cis oets, primarar Sorld soenth and hathiare orthispeathames ses, An ss porkssork. utles thake be ynlises hed and porith thes, proy ditsor thake provf provrde
最后已經像是人話了,那真的是人模狗樣的句子啊,以至於讓我猜測它是不是開始思考了,也就是訓練了半小時樣子:
of specific events in his life and provide little on the person who experis somewhat a mystery. There are two primary sources that provide historians with a basic outline of his life…
語言結構通過神經網絡可以從一堆亂碼中涌現出來, 這正是目前機器翻譯的state of the art SMT(統計機器翻譯)的基礎, 下面讓我們了解一下大明明鼎鼎的google翻譯又是用了哪些炫技。 首先google翻譯的基礎正是這個游戲般容易, 卻思想內容極為深刻的RNN。 但是這里卻做了若干步變化。 這里要提到RNN的一個變種LSTM。
LSTM(Long short term memory)顧名思義, 是增加了記憶功能的RNN, 首先為什么要給RNN增加記憶呢? 這里就要提到一個有趣的概念叫梯度消失(Vanishing Gradient),剛剛說RNN訓練的關鍵是梯度回傳,梯度信息在時間上傳播是會衰減的, 那么回傳的效果好壞, 取決於這種衰減的快慢, 理論上RNN可以處理很長的信息, 但是由於衰減, 往往事與願違, 如果要信息不衰減, 我們就要給神經網絡加記憶,這就是LSTM的原理了。 這里我們首先再增加一個隱變量作為記憶單元,然后把之前一層的神經網絡再增加三層, 分別是輸入門,輸出門,遺忘門, 這三層門就如同信息的閘門, 控制多少先前網絡內的信息被保留, 多少新的信息進入,而且門的形式都是可微分的sigmoid函數,確保可以通過訓練得到最佳參數。
信息閘門的原理另一個巧妙的理解是某種“慣性” 機制,隱變量的狀態更新不是馬上達到指定的值,而是緩慢達到這個值, 如同讓過去的信息多了一層緩沖,而要多少緩沖則是由一個叫做遺忘門的東西決定的。 如此我們發現其實這幾個新增加的東西最核心的就是信息的閘門遺忘門。 根據這一原理,我們可以抓住本質簡化lstm,如GRU或極小GRU。 其實我們只需要理解這個模型就夠了,而且它們甚至比lstm更快更好。
我們看一下最小GRU的結構:
摘自論文: Minimal Gated Unit for Recurrent Neural Networks
第一個方程f即遺忘門, 第二方程如果你對比先前的RNN會發現它是一樣的結構, 只是讓遺忘門f來控制每個神經元放多少之前信息出去(改變其它神經元的狀態), 第三個方程描述“慣性” ,即最終每個神經元保持多少之前的值,更新多少。
這個結構你理解了就理解了記憶體RNN的精髓。
好了是時候看一下google 翻譯大法是怎么玩的, 首先,翻譯是溝通兩個不同的語言, 而你要這個溝通的本質是因為它們所表達的事物是相同的, 我們自己的大腦做翻譯的時候,也是根據它們所表達的概念相同比如蘋果-vs-apple來溝通兩個語言的。如果漢語是輸入,英語是輸出,神經網絡事實上做的是這樣一件事:
Encoding: 用一個LSTM把漢語變成神經代碼
Decoding:用另一個LSTM把神經代碼轉化為英文。
第一個lstm的輸出是第二個lstm的輸入, 兩個網絡用大量語料訓練好即可。 Google這一次2016寄出的大法, 是在其中加入了attention機制 ,這樣google的翻譯系統就更接近人腦。
運用記憶神經網絡翻譯的核心優勢是我們可以靈活的結合語境,實現句子到句子,段落到段落的過度, 因為記憶特性使得網絡可以結合不同時間尺度的信息而並非只抓住個別單詞, 這就好像你能夠抓住語境而非只是望文生義。也是因為這個RNN有着無窮無盡的應用想象力, 我們將在下一篇繼續講解google翻譯以及rnn的各種應用。
參考文獻 :
The unreasonable effective RNN
Google’s Neural Machine Translation System: Bridging the Gap between Human and Machine Translation
Minimal Gated Unit for Recurrent Neural Networks
