RNN入门
雷鋒網(wǎng) AI科技評(píng)論按:本文作者何之源,原文載于知乎專(zhuān)欄AI Insight,雷鋒網(wǎng)(公眾號(hào):雷鋒網(wǎng)) AI科技評(píng)論獲其授權(quán)發(fā)布。
上周寫(xiě)的文章《完全圖解RNN、RNN變體、Seq2Seq、Attention機(jī)制》介紹了一下RNN的幾種結(jié)構(gòu),今天就來(lái)聊一聊如何在TensorFlow中實(shí)現(xiàn)這些結(jié)構(gòu),這篇文章的主要內(nèi)容為:
-
一個(gè)完整的、循序漸進(jìn)的學(xué)習(xí)TensorFlow中RNN實(shí)現(xiàn)的方法。這個(gè)學(xué)習(xí)路徑的曲線較為平緩,應(yīng)該可以減少不少學(xué)習(xí)精力,幫助大家少走彎路。
-
一些可能會(huì)踩的坑
-
TensorFlow源碼分析
-
一個(gè)Char RNN實(shí)現(xiàn)示例,可以用來(lái)寫(xiě)詩(shī),生成歌詞,甚至可以用來(lái)寫(xiě)網(wǎng)絡(luò)小說(shuō)!(項(xiàng)目地址:https://github.com/hzy46/Char-RNN-TensorFlow)
一、學(xué)習(xí)單步的RNN:RNNCell
如果要學(xué)習(xí)TensorFlow中的RNN,第一站應(yīng)該就是去了解“RNNCell”,它是TensorFlow中實(shí)現(xiàn)RNN的基本單元,每個(gè)RNNCell都有一個(gè)call方法,使用方式是:(output, next_state) = call(input, state)。
借助圖片來(lái)說(shuō)可能更容易理解。假設(shè)我們有一個(gè)初始狀態(tài)h0,還有輸入x1,調(diào)用call(x1, h0)后就可以得到(output1, h1):
?
再調(diào)用一次call(x2, h1)就可以得到(output2, h2):
?
也就是說(shuō),每調(diào)用一次RNNCell的call方法,就相當(dāng)于在時(shí)間上“推進(jìn)了一步”,這就是RNNCell的基本功能。
在代碼實(shí)現(xiàn)上,RNNCell只是一個(gè)抽象類(lèi),我們用的時(shí)候都是用的它的兩個(gè)子類(lèi)BasicRNNCell和BasicLSTMCell。顧名思義,前者是RNN的基礎(chǔ)類(lèi),后者是LSTM的基礎(chǔ)類(lèi)。這里推薦大家閱讀其源碼實(shí)現(xiàn)(地址:http://t.cn/RNJrfMl),一開(kāi)始并不需要全部看一遍,只需要看下RNNCell、BasicRNNCell、BasicLSTMCell這三個(gè)類(lèi)的注釋部分,應(yīng)該就可以理解它們的功能了。
除了call方法外,對(duì)于RNNCell,還有兩個(gè)類(lèi)屬性比較重要:
-
state_size
-
output_size
前者是隱層的大小,后者是輸出的大小。比如我們通常是將一個(gè)batch送入模型計(jì)算,設(shè)輸入數(shù)據(jù)的形狀為(batch_size, input_size),那么計(jì)算時(shí)得到的隱層狀態(tài)就是(batch_size, state_size),輸出就是(batch_size, output_size)。
可以用下面的代碼驗(yàn)證一下(注意,以下代碼都基于TensorFlow最新的1.2版本):
import tensorflow as tf
import numpy as np
?
cell = tf.nn.rnn_cell.BasicRNNCell(num_units=128) # state_size = 128
print(cell.state_size) # 128
?
inputs = tf.placeholder(np.float32, shape=(32, 100)) # 32 是 batch_size
h0 = cell.zero_state(32, np.float32) # 通過(guò)zero_state得到一個(gè)全0的初始狀態(tài),形狀為(batch_size, state_size)
output, h1 = cell.call(inputs, h0) #調(diào)用call函數(shù)
?
print(h1.shape) # (32, 128)
對(duì)于BasicLSTMCell,情況有些許不同,因?yàn)長(zhǎng)STM可以看做有兩個(gè)隱狀態(tài)h和c,對(duì)應(yīng)的隱層就是一個(gè)Tuple,每個(gè)都是(batch_size, state_size)的形狀:
import tensorflow as tf
import numpy as np
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=128)
inputs = tf.placeholder(np.float32, shape=(32, 100)) # 32 是 batch_size
h0 = lstm_cell.zero_state(32, np.float32) # 通過(guò)zero_state得到一個(gè)全0的初始狀態(tài)
output, h1 = lstm_cell.call(inputs, h0)
?
print(h1.h) ?# shape=(32, 128)
print(h1.c) ?# shape=(32, 128)
二、學(xué)習(xí)如何一次執(zhí)行多步:tf.nn.dynamic_rnn
基礎(chǔ)的RNNCell有一個(gè)很明顯的問(wèn)題:對(duì)于單個(gè)的RNNCell,我們使用它的call函數(shù)進(jìn)行運(yùn)算時(shí),只是在序列時(shí)間上前進(jìn)了一步。比如使用x1、h0得到h1,通過(guò)x2、h1得到h2等。這樣的h話,如果我們的序列長(zhǎng)度為10,就要調(diào)用10次call函數(shù),比較麻煩。對(duì)此,TensorFlow提供了一個(gè)tf.nn.dynamic_rnn函數(shù),使用該函數(shù)就相當(dāng)于調(diào)用了n次call函數(shù)。即通過(guò){h0,x1, x2, …., xn}直接得{h1,h2…,hn}。
具體來(lái)說(shuō),設(shè)我們輸入數(shù)據(jù)的格式為(batch_size, time_steps, input_size),其中time_steps表示序列本身的長(zhǎng)度,如在Char RNN中,長(zhǎng)度為10的句子對(duì)應(yīng)的time_steps就等于10。最后的input_size就表示輸入數(shù)據(jù)單個(gè)序列單個(gè)時(shí)間維度上固有的長(zhǎng)度。另外我們已經(jīng)定義好了一個(gè)RNNCell,調(diào)用該RNNCell的call函數(shù)time_steps次,對(duì)應(yīng)的代碼就是:
# inputs: shape = (batch_size, time_steps, input_size)
# cell: RNNCell
# initial_state: shape = (batch_size, cell.state_size)。初始狀態(tài)。一般可以取零矩陣
outputs, state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state)
此時(shí),得到的outputs就是time_steps步里所有的輸出。它的形狀為(batch_size, time_steps, cell.output_size)。state是最后一步的隱狀態(tài),它的形狀為(batch_size, cell.state_size)。
此處建議大家閱讀tf.nn.dynamic_rnn的文檔(地址:https://www.tensorflow.org/api_docs/python/tf/nn/dynamic_rnn)做進(jìn)一步了解。
三、學(xué)習(xí)如何堆疊RNNCell:MultiRNNCell
很多時(shí)候,單層RNN的能力有限,我們需要多層的RNN。將x輸入第一層RNN的后得到隱層狀態(tài)h,這個(gè)隱層狀態(tài)就相當(dāng)于第二層RNN的輸入,第二層RNN的隱層狀態(tài)又相當(dāng)于第三層RNN的輸入,以此類(lèi)推。在TensorFlow中,可以使用tf.nn.rnn_cell.MultiRNNCell函數(shù)對(duì)RNNCell進(jìn)行堆疊,相應(yīng)的示例程序如下:
import tensorflow as tf
import numpy as np
?
# 每調(diào)用一次這個(gè)函數(shù)就返回一個(gè)BasicRNNCell
def get_a_cell():
? ?return tf.nn.rnn_cell.BasicRNNCell(num_units=128)
# 用tf.nn.rnn_cell MultiRNNCell創(chuàng)建3層RNN
cell = tf.nn.rnn_cell.MultiRNNCell([get_a_cell() for _ in range(3)]) # 3層RNN
# 得到的cell實(shí)際也是RNNCell的子類(lèi)
# 它的state_size是(128, 128, 128)
# (128, 128, 128)并不是128x128x128的意思
# 而是表示共有3個(gè)隱層狀態(tài),每個(gè)隱層狀態(tài)的大小為128
print(cell.state_size) # (128, 128, 128)
# 使用對(duì)應(yīng)的call函數(shù)
inputs = tf.placeholder(np.float32, shape=(32, 100)) # 32 是 batch_size
h0 = cell.zero_state(32, np.float32) # 通過(guò)zero_state得到一個(gè)全0的初始狀態(tài)
output, h1 = cell.call(inputs, h0)
print(h1) # tuple中含有3個(gè)32x128的向量
通過(guò)MultiRNNCell得到的cell并不是什么新鮮事物,它實(shí)際也是RNNCell的子類(lèi),因此也有call方法、state_size和output_size屬性。同樣可以通過(guò)tf.nn.dynamic_rnn來(lái)一次運(yùn)行多步。
此處建議閱讀MutiRNNCell源碼(地址:http://t.cn/RNJrfMl)中的注釋進(jìn)一步了解其功能。
四、可能遇到的坑1:Output說(shuō)明
在經(jīng)典RNN結(jié)構(gòu)中有這樣的圖:
在上面的代碼中,我們好像有意忽略了調(diào)用call或dynamic_rnn函數(shù)后得到的output的介紹。將上圖與TensorFlow的BasicRNNCell對(duì)照來(lái)看。h就對(duì)應(yīng)了BasicRNNCell的state_size。那么,y是不是就對(duì)應(yīng)了BasicRNNCell的output_size呢?答案是否定的。
找到源碼中BasicRNNCell的call函數(shù)實(shí)現(xiàn):
def call(self, inputs, state):
? ?"""Most basic RNN: output = new_state = act(W * input + U * state + B)."""
? ?output = self._activation(_linear([inputs, state], self._num_units, True))
? ?return output, output
這句“return output, output”說(shuō)明在BasicRNNCell中,output其實(shí)和隱狀態(tài)的值是一樣的。因此,我們還需要額外對(duì)輸出定義新的變換,才能得到圖中真正的輸出y。由于output和隱狀態(tài)是一回事,所以在BasicRNNCell中,state_size永遠(yuǎn)等于output_size。TensorFlow是出于盡量精簡(jiǎn)的目的來(lái)定義BasicRNNCell的,所以省略了輸出參數(shù),我們這里一定要弄清楚它和圖中原始RNN定義的聯(lián)系與區(qū)別。
再來(lái)看一下BasicLSTMCell的call函數(shù)定義(函數(shù)的最后幾行):
new_c = (
? ?c * sigmoid(f + self._forget_bias) + sigmoid(i) * self._activation(j))
new_h = self._activation(new_c) * sigmoid(o)
?
if self._state_is_tuple:
?new_state = LSTMStateTuple(new_c, new_h)
else:
?new_state = array_ops.concat([new_c, new_h], 1)
return new_h, new_state
我們只需要關(guān)注self._state_is_tuple == True的情況,因?yàn)閟elf._state_is_tuple == False的情況將在未來(lái)被棄用。返回的隱狀態(tài)是new_c和new_h的組合,而output就是單獨(dú)的new_h。如果我們處理的是分類(lèi)問(wèn)題,那么我們還需要對(duì)new_h添加單獨(dú)的Softmax層才能得到最后的分類(lèi)概率輸出。
還是建議大家親自看一下源碼實(shí)現(xiàn)(地址:http://t.cn/RNJsJoH)來(lái)搞明白其中的細(xì)節(jié)。
五、可能遇到的坑2:因版本原因引起的錯(cuò)誤
在前面我們講到堆疊RNN時(shí),使用的代碼是:
# 每調(diào)用一次這個(gè)函數(shù)就返回一個(gè)BasicRNNCell
def get_a_cell():
? ?return tf.nn.rnn_cell.BasicRNNCell(num_units=128)
# 用tf.nn.rnn_cell MultiRNNCell創(chuàng)建3層RNN
cell = tf.nn.rnn_cell.MultiRNNCell([get_a_cell() for _ in range(3)]) # 3層RNN
這個(gè)代碼在TensorFlow 1.2中是可以正確使用的。但在之前的版本中(以及網(wǎng)上很多相關(guān)教程),實(shí)現(xiàn)方式是這樣的:
one_cell = ?tf.nn.rnn_cell.BasicRNNCell(num_units=128)
cell = tf.nn.rnn_cell.MultiRNNCell([one_cell] * 3) # 3層RNN
如果在TensorFlow 1.2中還按照原來(lái)的方式定義,就會(huì)引起錯(cuò)誤!
參考自https://www.leiphone.com/news/201709/QJAIUzp0LAgkF45J.html
總結(jié)
- 上一篇: linq结果转换object_你知道Ob
- 下一篇: 买第三便宜帽子