當前位置:
首頁 > 知識 > 按照這幾個步驟操作,不實現 RNN 都難!

按照這幾個步驟操作,不實現 RNN 都難!

按照這幾個步驟操作,不實現 RNN 都難!



編者按:本文作者劉沖,原文載於作者個人博客,雷鋒網 AI 研習社已獲授權。


最近在看RNN模型,為簡單起見,本篇就以簡單的二進位序列作為訓練數據,而不實現具體的論文模擬,主要目的是理解RNN的原理和如何在TensorFlow中構造一個簡單基礎的模型架構。其中代碼參考了這篇博客。


數據集

首先我們看一下實驗數據的構造:


輸入數據X:在時間t,Xt的值有50%的概率為1,50%的概率為0;


輸出數據Y:在實踐t,Yt的值有50%的概率為1,50%的概率為0,除此之外,如果`Xt-3 == 1`,Yt為1的概率增加50%, 如果`Xt-8 == 1`,則Yt為1的概率減少25%, 如果上述兩個條件同時滿足,則Yt為1的概率為75%。


可知,Y與X有兩個依賴關係,一個是t-3,一個是t-8。我們實驗的目的就是檢驗RNN能否捕捉到Y與X之間的這兩個依賴關係。實驗使用交叉熵作為評價標準,則有下面三條理想的實驗結果:


如果RNN沒有學習到任何一條依賴,那麼Yt為1的概率就是0.625(0.5+0.5*0.5-0.5*0.25),所以所獲得的交叉熵應該是0.66(-(0.625 * np.log(0.625) + 0.375 * np.log(0.375)))。


如果RNN學習到第一條依賴關係,即Xt-3為1時Yt一定為1。那麼,所以最終的交叉熵應該是0.52(-0.5 * (0.875 * np.log(0.875) + 0.125 * np.log(0.125)) -0.5 * (0.625 * np.log(0.625) + 0.375 * np.log(0.375)))。


如果RNN學習到了兩條依賴, 那麼有0.25的概率全對,0.5的概率正確率是75%,還有0.25的概率正確率是0.5。所以其交叉熵為0.45(-0.50 * (0.75 * np.log(0.75) + 0.25 * np.log(0.25)) - 0.25 * (2 * 0.50 * np.log (0.50)) - 0.25 * (0))。


數據預處理


這部分主要是生成實驗數據,並將其按照RNN模型的輸入格式進行切分和batch化。代碼入下:


1,生成實驗數據:

def gen_data(size=100000):


Y = []


for i in range(size):


threshold = 0.5


#判斷X[i-3]和X[i-8]是否為1,修改閾值


if X[i-3] == 1:


threshold += 0.5


if X[i-8] == 1:


threshold -= 0.25


#生成隨機數,以threshold為閾值給Yi賦值

Y.append(0)


else:


Y.append(1)


return X, np.array(Y)


接下來將生成的數據按照模型參數設置進行切分,這裡需要用得到的參數主要包括:batch_size和num_steps,分別是批量數據大小和RNN每層rnn_cell循環的次數,也就是下圖中Sn中n的大小。代碼入下:


def gen_batch(raw_data, batch_size, num_steps):


#raw_data是使用gen_data()函數生成的數據,分別是X和Y


raw_x, raw_y = raw_data


data_length = len(raw_x)


# 首先將數據切分成batch_size份,0-batch_size,batch_size-2*batch_size。。。

batch_partition_length = data_length // batch_size


data_x = np.zeros([batch_size, batch_partition_length], dtype=np.int32)


data_y = np.zeros([batch_size, batch_partition_length], dtype=np.int32)


for i in range(batch_size):


data_x[i] = raw_x[batch_partition_length * i:batch_partition_length * (i + 1)]


data_y[i] = raw_y[batch_partition_length * i:batch_partition_length * (i + 1)]


#因為RNN模型一次只處理num_steps個數據,所以將每個batch_size在進行切分成epoch_size份,每份num_steps個數據。注意這裡的epoch_size和模型訓練過程中的epoch不同。


epoch_size = batch_partition_length // num_steps


#x是0-num_steps, batch_partition_length -batch_partition_length +num_steps。。。共batch_size個


for i in range(epoch_size):

x = data_x[:, i * num_steps:(i + 1) * num_steps]


y = data_y[:, i * num_steps:(i + 1) * num_steps]


yield (x, y)


#這裡的n就是訓練過程中用的epoch,即在樣本規模上循環的次數


def gen_epochs(n, num_steps):


for i in range(n):


yield gen_batch(gen_data(), batch_size, num_steps)


根據上面的代碼我們可以看出來,這裡的數據劃分並沒有將數據完全的按照原先的數據順序,而是每隔一段取num_steps個數據,這樣組成的batch進行訓練==這裡是為了省事還是另有原因還有待後面學習中考證。


模型構建


RNN的具體原理我們就不再進行贅述,主要是隱層狀態和輸入連接後計算新的隱層狀態和輸出。這裡用的是單層的RNN。公式和原理圖如下所示:

St=tanh(W(Xt @ St?1)+bs)


Pt=softmax(USt+bp)

按照這幾個步驟操作,不實現 RNN 都難!



至於使用TensorFlow構建RNN模型,主要就是定義rnn_cell類型,然後將其復用即可。代碼如下所示:


x = tf.placeholder(tf.int32, [batch_size, num_steps], name= input_placeholder )


y = tf.placeholder(tf.int32, [batch_size, num_steps], name= labels_placeholder )


#RNN的初始化狀態,全設為零。注意state是與input保持一致,接下來會有concat操作,所以這裡要有batch的維度。即每個樣本都要有隱層狀態


init_state = tf.zeros([batch_size, state_size])


#將輸入轉化為one-hot編碼,兩個類別。[batch_size, num_steps, num_classes]

x_one_hot = tf.one_hot(x, num_classes)


#將輸入unstack,即在num_steps上解綁,方便給每個循環單元輸入。這裡可以看出RNN每個cell都處理一個batch的輸入(即batch個二進位樣本輸入)


rnn_inputs = tf.unstack(x_one_hot, axis=1)


#定義rnn_cell的權重參數,


with tf.variable_scope( rnn_cell ):


W = tf.get_variable( W , [num_classes + state_size, state_size])


b = tf.get_variable( b , [state_size], initializer=tf.constant_initializer(0.0))


#使之定義為reuse模式,循環使用,保持參數相同


def rnn_cell(rnn_input, state):


with tf.variable_scope( rnn_cell , reuse=True):

W = tf.get_variable( W , [num_classes + state_size, state_size])


b = tf.get_variable( b , [state_size], initializer=tf.constant_initializer(0.0))


#定義rnn_cell具體的操作,這裡使用的是最簡單的rnn,不是LSTM


return tf.tanh(tf.matmul(tf.concat([rnn_input, state], 1), W) + b)


state = init_state


rnn_outputs = []


#循環num_steps次,即將一個序列輸入RNN模型


for rnn_input in rnn_inputs:


state = rnn_cell(rnn_input, state)


rnn_outputs.append(state)

final_state = rnn_outputs[-1]


#定義softmax層


with tf.variable_scope( softmax ):


W = tf.get_variable( W , [state_size, num_classes])


b = tf.get_variable( b , [num_classes], initializer=tf.constant_initializer(0.0))


#注意,這裡要將num_steps個輸出全部分別進行計算其輸出,然後使用softmax預測


logits = [tf.matmul(rnn_output, W) + b for rnn_output in rnn_outputs]


# Turn our y placeholder into a list of labels


y_as_list = tf.unstack(y, num=num_steps, axis=1)


#losses and train_step


logit, label in zip(logits, y_as_list)]


total_loss = tf.reduce_mean(losses)


train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)


模型訓練


定義好我們的模型之後,接下來就是將數據傳入,然後進行訓練,代碼入下:


def train_network(num_epochs, num_steps, state_size=4, verbose=True):


with tf.Session() as sess:


sess.run(tf.global_variables_initializer())


training_losses = []


#得到數據,因為num_epochs==1,所以外循環只執行一次


for idx, epoch in enumerate(gen_epochs(num_epochs, num_steps)):


training_loss = 0


#保存每次執行後的最後狀態,然後賦給下一次執行


training_state = np.zeros((batch_size, state_size))


if verbose:


print("
EPOCH", idx)


#這是具體獲得數據的部分,應該會執行1000000//200//5 = 1000次,即每次執行傳入的數據是batch_size*num_steps個(1000),共1000000個,所以每個num_epochs需要執行1000次。


for step, (X, Y) in enumerate(epoch):


tr_losses, training_loss_, training_state, _ =


sess.run([losses,


total_loss,


final_state,


train_step],


feed_dict=)


training_loss += training_loss_


if step % 100 == 0 and step > 0:


if verbose:


print("Average loss at step", step,


"for last 250 steps:", training_loss/100)


training_losses.append(training_loss/100)


training_loss = 0


return training_losses


training_losses = train_network(1,num_steps)


plt.plot(training_losses)


plt.show()


實驗結果如下所示:

按照這幾個步驟操作,不實現 RNN 都難!



從上圖可以看出交叉熵最終穩定在0。52,按照我們上面的分析可以知道:RNN模型成功的學習到了第一條依賴關係,因為我們的循環步長選擇的是5,所以他只能學習到t-3的第一條依賴關係,而無法學習到t-8的第二條依賴。


接下來可以嘗試num_steps==10,區捕捉第二條依賴關係。最終的結果圖如下所示:

按照這幾個步驟操作,不實現 RNN 都難!



從上圖可以看出,我們的RNN模型成功的學習到了兩條依賴關係。最終的交叉熵未定在0.46附近。


幾點改進


1,首先上面的代碼中,為了儘可能詳細的解釋TensorFlow中RNN模型的構造方法,將rnn_cell的定義寫的很詳細,其實這些工作tf已經封裝好了,我們只需要一行命令就可以實現,所以第一個要改進的地方就是將rnn_cell的定義和循環使用部分的代碼簡化:


#定義rnn_cell的權重參數,


with tf.variable_scope( rnn_cell ):


W = tf.get_variable( W , [num_classes + state_size, state_size])


b = tf.get_variable( b , [state_size], initializer=tf.constant_initializer(0.0))


#使之定義為reuse模式,循環使用,保持參數相同


def rnn_cell(rnn_input, state):


with tf.variable_scope( rnn_cell , reuse=True):


W = tf.get_variable( W , [num_classes + state_size, state_size])


b = tf.get_variable( b , [state_size], initializer=tf.constant_initializer(0.0))


#定義rnn_cell具體的操作,這裡使用的是最簡單的rnn,不是LSTM


return tf.tanh(tf.matmul(tf.concat([rnn_input, state], 1), W) + b)


state = init_state


rnn_outputs = []


#循環num_steps次,即將一個序列輸入RNN模型


for rnn_input in rnn_inputs:


state = rnn_cell(rnn_input, state)


rnn_outputs.append(state)


final_state = rnn_outputs[-1]


#----------------------上面是原始代碼,定義了rnn_cell,然後使用循環的方式對其進行復用,簡化之後我們可以直接調用BasicRNNCell和static_rnn兩個函數實現------------------------


2,使用動態rnn模型,上面的模型中,我們將輸入表示成列表的形式,即rnn_inputs是一個長度為num_steps的列表,其中每個元素是[batch_size, features]的tensor(即每個rnn_cell要處理的數據),這樣做事比較麻煩的,我們還可以使用tf提供的dynamic_rnn函數,這樣做不僅會使編程更加簡單,還可以提高計算效率。使用dynamic_rnn 時,我們直接將輸入表示成[batch_size, num_steps, features]的三維Tensor即可。最終的動態RNN模型代碼如下所示:


x = tf.placeholder(tf.int32, [batch_size, num_steps], name= input_placeholder )


y = tf.placeholder(tf.int32, [batch_size, num_steps], name= labels_placeholder )


init_state = tf.zeros([batch_size, state_size])


rnn_inputs = tf.one_hot(x, num_classes)


#注意這裡去掉了這行代碼,因為我們不需要將其表示成列表的形式在使用循環去做。


#rnn_inputs = tf.unstack(x_one_hot, axis=1)


#使用dynamic_rnn函數,動態構建RNN模型


with tf.variable_scope( softmax ):


W = tf.get_variable( W , [state_size, num_classes])


b = tf.get_variable( b , [num_classes], initializer=tf.constant_initializer(0.0))


logits = tf.reshape(


tf.matmul(tf.reshape(rnn_outputs, [-1, state_size]), W) + b,


[batch_size, num_steps, num_classes])


total_loss = tf.reduce_mean(losses)


train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)


至此,我們就實現了一個很簡單的RNN模型的構造,在這個過程中,我們需要注意的主要有以下三點:


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 唯物 的精彩文章:

TAG:唯物 |

您可能感興趣

吐司這9種做法,步驟簡單,現學現做,早餐再也不用愁
從網戀走到現實的情侶,往往要歷經這四個關鍵步驟,別不懂!
這幾個步驟做錯了,就算每天護膚也拯救不了你的臉!
難怪你做的裡脊肉不好吃,原因在這,關鍵步驟可不能錯!
按照這個步驟操作,你就能親到她
不會化妝的看這裡,掌握這幾個步驟你離明星妝容就不遠了!
能把馬尾扎的美麗其實一點都不難,多幾個步驟就好
幾個步驟教會你怎樣辨別一雙NIKE的真假?
久備不孕,可能這幾個步驟你忘了做吧
護膚丨正確的步驟是這樣的,你可能一直都做錯了!
這幾個護膚步驟,堅持下來會看到不一樣的變化,你做對了嗎?
一個好的櫥櫃,都是這樣子設計出來的,一個步驟都不能少
只要簡單的幾個步驟,你就能做出餐館一樣的一道美食,想學嗎?
包子這樣做才好吃,只需要這幾個步驟,一看就能學會
做五花肉最關鍵的幾個步驟,缺一不可,否則做出來的肉沒人會喜歡
春季果樹養根的幾個關鍵時期!做好這些還不夠,你必須要做對這幾個步驟!
看似很難的熔岩巧克力,其實很簡單,按照這個步驟你也可以做出來
「如果你想我了,就做這五個步驟。」
開車怎樣提速快?不要傻踩油門了,按這個步驟才是正確操作!
如何用PPT做出超可愛乳酪字體?步驟很簡單,你一定能學會!