TensorFlow中實現RNN,徹底弄懂time_step


  這篇博客不是一篇講解原理的博客,這篇博客主要講解tnesorlfow的RNN代碼結構,通過代碼來學習RNN,以及講解time_steps,如果這篇博客沒有讓你明白time_steps,歡迎博客下面評論交流。

  我曾翻閱各大網站,各大博客,他們的對RNN中time_steps的講解,都沒有一個讓人醍醐灌頂的答案,甚至讓人越看模糊。有的博主在博客中講的看似他懂了,一問他自己他答不上來。在這里,我向全中國還迷糊在time_step的學者答疑,立此博文。

學習RNNCell要重點關注三個地方:

  • 類方法 call
  • 類屬性 state_size
  • 類屬性 output_size

RNN_Cell

  想要看懂tensorflow RNN代碼,我們必須要先了解RNNCell,RNNcell 是 tensorlfow中實現RNN的基本單元。我們平時在代碼中用的是RNNcell的子類,BasicRNNCell(RNN的基礎類)和BasicLSTMCell(LSTM的基礎類)

注意:RNNCell是抽象類不能進行實例化,可以使用它的子類BasicRNNCell或BasicLSTMCell進行實例化,得到cell。

  所有的RNNCell的之類都會實現一個__call__函數,利用call函數可以實現RNN的單步計算

使用方式是:(output, next_state) = call(input, state)

舉例:輸入序列是:$x_1、x_2、x_3$,RNN的初始狀態為$h_0$

t=1時刻,$(output_1, h_1) = cell(x_1,h_0)$

t=2時刻,$(output_2, h_2) = cell(x_2,h_1)$

t=3時刻,$(output_3, h_3) = cell(x_3,h_2)$

每調用一次RNNCell的call方法,就相當於在時間上推進了一步

  RNNCell中還有兩個類屬性比較重要,state_size(隱層的大小)output_size(輸出的大小)。output_size一般等於最后一層RNN的state_size。

舉例:設輸入數據的形狀為(batch_size, input_size),那么計算時得到的隱層狀態就是(batch_size, state_size),輸出就是(batch_size, output_size)=(batch_size, 最后一層state_size)。

BaseRNNCell

  在TensorFlow中定義一個基本RNN單元:

import tensorflow as tf

rnn_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=128)  # state_size = 128
# cell = tf.keras.layers.SimpleRNNCell(units=128)
print(rnn_cell.state_size)  # 128

# 32 是 batch_size
inputs = tf.placeholder(tf.float32, shape=(32, 100))

# 通過zero_state得到一個全0的初始狀態,形狀為(batch_size, state_size)
h0 = rnn_cell.zero_state(32, tf.float32)        # h0.shape=(32, 128)

# 調用call函數
output, h1 = rnn_cell.__call__(inputs, h0)

print(output.shape)    # (32, 128)
print(h1.shape)      # (32, 128)

注意:隱藏層的初始化cell.zero_state(batch),shape=(batch_size,state_size)

BaseLSTMCell

  在TensorFlow中定義一個基本LSTM單元:

import tensorflow as tf

lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=128)
print(lstm_cell.state_size)        # LSTMStateTuple(c=128, h=128)


inputs = tf.placeholder(tf.float32, shape=(32, 100)) # 32 是 batch_size
h0 = lstm_cell.zero_state(32, tf.float32) # (32,128)

# 調用call函數
output, h1 = lstm_cell.__call__(inputs, h0)

print(output.shape)    # shape=(32, 128)
print(h1.h.shape)   # shape=(32, 128)
print(h1.c.shape)   # shape=(32, 128)

對於BasicLSTMCell,因為LSTM可以看做有兩個隱狀態h和c,對應的隱層就是一個Tuple,每個都是(batch_size, state_size)的形狀

時間維度靜態展開:static_rnn

  tf.nn.static_rnn——隨時間靜態展開。static_rnn() 返回兩個對象,第一個是每一時刻time_steps RNN輸出的列表,另一個是RNN網絡的最終狀態state。下面代碼舉例time_steps=2的輸入。

X0 = tf.placeholder(tf.float32, [None, n_inputs])
X1 = tf.placeholder(tf.float32, [None, n_inputs])
 
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
output_seqs, states = tf.contrib.rnn.static_rnn(basic_cell, [X0, X1], dtype=tf.float32)
 
Y0, Y1 = output_seqs

如果有50個tiime_steps時刻,操作50個輸入占位符實在太繁瑣了,假如輸入shape=(None, time_steps, imput_size),可以用如下方法一並輸入

X = tf.placeholder(tf.float32, [None, n_steps, n_inputs])
X = tf.transpose(X, perm=[1, 0, 2])     # shape=(n_steps, batchs ,n_inputs)
X_seqs = tf.unstack(X)      # time_steps個(batchs, n_inputs)的列表

basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
output_seqs, states = tf.contrib.rnn.static_rnn(basic_cell, X_seqs, dtype=tf.float32)
 
outputs = tf.transpose(tf.stack(output_seqs), perm=[1, 0, 2])

最終的outputs是一個包含所有實例、任一時刻、所有神經元的輸出的張量。幸運的是,還有更好的解決方案,那就是dynamic_rnn()函數。

時間維度動態展開:dynamic_rnn

  tf.nn.dynamic_rnn——隨時間動態展開。基礎的RNNCell有一個很明顯的問題:對於單個的RNNCell,我們使用它的call函數進行運算時,只是在序列時間上前進了一步。如果我們的序列長度為10,就要調用10次call函數,比較麻煩。對此,TensorFlow提供了一個tf.nn.dynamic_rnn函數,該函數就相當於調用了n次call函數,即通過${h_0,x_1, x_2, …., x_n}$直接得${h_1,h_2…,h_n}$。

  具體來說,設輸入數據的格式為 (batch_size, time_steps, input_size),

  • batch_size:表示batch的大小,即一個batch中序列的個數,
  • time_steps:表示序列本身的長度,如在Char RNN中,長度為10的句子對應的time_steps就等於10。
  • input_size:表示輸入數據單個序列單個時間維度上固有的長度。

舉例:假設輸入數據的格式為(batch_size, time_steps, input_size),其中time_steps表示序列本身的長度,如在NLP中,一句話有25個字,每個字的向量維度為300,那么time_steps就是句子的長度=25,input_size=300。

  假設我們已經定義好了一個RNNCell,調用time_steps該RNNCell的call函數次,對應的代碼是:

outputs, state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state)

參數

  • inputs: 輸入序列 shape = (batch_size, time_steps, input_size)
  • cell: RNNCell
  • initial_state: 初始狀態。一般可以取零矩陣shape = (batch_size, cell.state_size)。

返回

  • outputs:time_steps步里所有輸出,shape=(batch_size, time_steps, cell.output_size)
  • state:最后一步的隱狀態,它的形狀為(batch_size, cell.state_size)
import tensorflow as tf 


rnn_cell = tf.contrib.rnn.BasicRNNCell(num_units=128)    # states_size=128
X = tf.placeholder(tf.float32, [32, 10, 100])   # input_shape=(batch_size, time_steps,input_size)

outputs, states = tf.nn.dynamic_rnn(rnn_cell, X, dtype=tf.float32)

print(outputs.shape)    # (batch_size,time_size, cell.output_size)=(32, 10, 128)
print(states.shape)        # (batch_size, output_size)=(batch_size, states_size)=(32, 128)

變長輸入序列

  前面我們處理的輸入shape=(batch_size, time_step, input_size),輸入序列是定長的,拿我們做自然語言處理為例子,如果數據有1000段時序的句子,每句話有25個字,對每個字進行向量化,每個字的向量維度為300,那么batch_size=1000,time_steps=25,input_size=300。但是每句話的句子長度都是不一樣的,這時候我們就需要在調用dynamic_rnn()(或者static_rnn)時使用sequence_length參數。指明了每一實例輸入序列的長度。例如:

X = tf.placeholder(tf.float32, [None, n_steps, n_inputs])   # (batch_size, time_steps,input_size)
seq_length = tf.placeholder(tf.int32, [None])

basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, sequence_length=seq_length, dtype=tf.float32)

假設我們輸入的第二個實例只有一個時刻的輸入,表示該實例張量的第二維需要補零,如下所示:

X_batch = np.array([
    # step 0     step 1
    [[0, 1, 2], [9, 8, 7]], # instance 0
    [[3, 4, 5], [0, 0, 0]], # instance 1 (padded with a zero vector)
    [[6, 7, 8], [6, 5, 4]], # instance 2
    [[9, 0, 1], [3, 2, 1]], # instance 3
])
seq_length_batch = np.array([2, 1, 2, 2])
 
with tf.Session() as sess:
    init.run()
    outputs_val, states_val = sess.run([outputs, states], feed_dict={X: X_batch, seq_length: seq_length_batch})

堆疊RNN:MultiRNNCell

  單層RNN能力有限,我們需要多層的RNN。將x輸入第一層RNN的后得到隱層狀態h,這個隱層狀態就相當於第二層RNN的一個輸入,第二層RNN的隱層狀態又相當於第三層RNN的一個輸入,以此類推。在TensorFlow中,可以使用tf.nn.rnn_cell.MultiRNNCell函數對RNNCell進行堆疊

import tensorflow as tf

# 每調用一次這個函數就返回一個BasicRNNCell
def get_a_cell():
    return tf.nn.rnn_cell.BasicRNNCell(num_units=128)
# 用tf.nn.rnn_cell MultiRNNCell創建3層RNN
Multi_cell = tf.nn.rnn_cell.MultiRNNCell([get_a_cell() for _ in range(3)]) # 3層RNN
# 得到的cell實際也是RNNCell的子類,它的state_size是(128, 128, 128),
# 這里
並不是128x128x128的意思,而是表示共有3個隱層狀態,每個隱層狀態的大小為128 print(Multi_cell.state_size) # (128, 128, 128) # 使用對應的call函數 inputs = tf.placeholder(tf.float32, shape=(32, 100)) # 32 是 batch_size h0 = Multi_cell.zero_state(32, tf.float32) # 通過zero_state得到一個全0的初始狀態 output, h1 = Multi_cell.__call__(inputs, h0) print(h1) # tuple中含有3個32x128的向量 # (<tf.Tensor 'multi_rnn_cell/cell_0/basic_rnn_cell/Tanh:0' shape=(32, 128) dtype=float32>, # <tf.Tensor 'multi_rnn_cell/cell_1/basic_rnn_cell/Tanh:0' shape=(32, 128) dtype=float32>, # <tf.Tensor 'multi_rnn_cell/cell_2/basic_rnn_cell/Tanh:0' shape=(32, 128) dtype=float32>)

RNN的其他變種

### ------------ LSTM ------------- ###
lstm_cell = tf.contrib.rnn.BasicLSTMCell(num_units=n_neurons)
# peephole connections
# 讓長期記憶也參與控制門的管理可能會更好
lstm_cell = tf.contrib.rnn.LSTMCell(num_units=n_neurons, use_peepholes=True)

### ------------ GRU ------------- ###
gru_cell = tf.contrib.rnn.GRUCell(num_units=n_neurons)

time_steps專欄

有的人學習到RNN的時候,死活都弄不清batch、input_size、time_steps。在這篇博文中,我做一個專欄。

文字數據

  如果數據有1000段時序的句子,每句話有25個字,對每個字進行向量化,每個字的向量維度為300,那么batch_size=1000,time_steps=25,input_size=300。

解析:time_steps一般情況下就是等於句子的長度,input_size等於字量化后向量的長度。

圖片數據

  拿MNIST手寫數字集來說,訓練數據有6000個手寫數字圖像,每個數字圖像大小為28*28,batch_size=6000沒的說,time_steps=28,input_size=28,我們可以理解為把圖片圖片分成28份,每份shape=(1, 28)。

音頻數據

  假如訓練數據有225段語音,每段語音對其進行分幀處理,每段語音有480幀,每一幀數據shape=(8910,),則輸入shape=(225, 480 8910),batch_size=225,time_steps就是,每段語音的幀數,time_steps=480,input_size=8910

我們使用RNN永遠只需要記住,輸入數據一定要是時序的,不能用這一段語音的語音幀,接下一段語音的語音幀,因為這兩段語音之間沒有時域連續性。

  RNN數據一定要是三維的,第一維是batch_size,第二維是time_steps,第三位是數據input_size。

參考文獻

知乎-何之源:TensorFlow中RNN實現的正確打開方式

學習RNN練手項目 Char-RNN

royhoo的博客園——循環神經網絡


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM