github上雖然已經有實現好的Char RNN


  前言
  
  學習RNN的時候很多人應該都有看過Andrej Karpathy寫的The Unreasonable Effectiveness of Recurrent Neural Networks,使用基於字符粒度的RNN讓機器學會自己生成文本,比如令自己訓練的RNN學會寫歌詞、寫代碼、寫小說、寫詩,聽着就很新奇。
  
  github上雖然已經有實現好的Char RNN,比如
  
  但是想要學習,最好的方式就是自己動手實現一遍。自己寫一遍好處還是很多的,比如加深對RNN(LSTM)的理解,可以熟悉深度學習的框架。因為我主要用tensorflow,所以就基於tensorflow實現了一遍Char-RNN。
  
  注:本文使用的tensorflow版本為1.0.0
  
  個人經驗,在實現的過程中最好是拋開別人代碼的影響,只根據基本理論以及所用的框架的API文檔一步步把代碼寫出來跑通,這樣自己的收益才是最大的。
  
  模型選擇
  
  要讓機器生成文本,本質上是需要一個語言模型。語言模型可以用來評估一句話是自然語言的概率,即根據一句話中已觀測到的詞,預測下一個詞出現的概率。也就是要能夠處理序列數據,根據已有的序列數據,推斷接下來可能的數據。如一句話“已經到了午餐時間,我正准備去吃{?}”,根據前面的描述,可以推斷“吃”字背后是要接上可食用的東西,並且是可以作為午餐的,可能是“飯”、“面”等等,通常不可能是“汽車”、“樹木”之類…因此我們需要一個能夠處理序列數據,並且能夠抽象出過去序列與任務相關方面的信息,再根據這些信息預測未來的模型。
  
  神經網絡中,RNN天然適合用於處理序列數據,它可以提取任意長度序列(x(t),x(t−1),...,x(1))的摘要,選擇性地精確保留過去序列的某些方面。而保留這些信息的方式則是通過RNN內部的隱藏狀態。
  
  但是RNN又有很多變體,因為基本RNN只有一個隱藏狀態,對長距離的記憶效果不好,在模型參數迭代優化的時候存在梯度彌散的問題,因此又有了采用LSTM單元的RNN以及其他的變體,如GRU等等。
  
  因此,在Char RNN的實踐當中,就選用LSTM作為基本的模型。
  
  因為tensorflow中已經實現了LSTM的單元,如果不是為了學習LSTM的原理,可以不需要自己去實現它。相應的API為
  
  模型定義
  
  我們需要定義一個class用來定義網絡的結構,以及實現inference的接口。如果初次接觸RNN,剛開始動手寫的時候可能會一頭霧水,我們已經有了LSTM的API,怎么把它拓展成可以接受文本的訓練數據進行訓練,最后再根據輸入的一些文字,輸出接下來文字的模型呢?
  
  我的做法是先明確輸入與輸出,以及我所知道的必備要素,然后再把它們銜接拼湊起來。
  
  基本LSTM單元
  
  首先我們要用到LSTMCell,它的必填參數是num_units,也就是每個LSTM Cell中的單元數,與輸入向量的維度是一致的。我們的輸入是詞向量,維度是我們自己定義的,這里用一個參數rnn_size來表示。定義基本LSTM Cell的代碼如下
  
  # 定義基本lstm單元
  
  lstm_cell_list = [tf.contrib.rnn.LSTMCell(rnn_size) for _ in xrange(layer_size)]
  
  # 使用MultiRNNCell 接口連接多層lstm, 並加上dropout
  
  self.cell = tf.contrib.rnn.DropoutWrapper(tf.contrib.rnn.MultiRNNCell(lstm_cell_list), output_keep_prob=output_keep_prob)
  
  明確輸入
  
  在訓練的過程中,每次都feed進一個batch的數據,batch的大小也是我們定義的,用batch_size表示,因此LSTM模型所接受輸入的shape為(batch_size, rnn_size)。
  
  如果我們使用預訓練好的詞向量作為輸入,那么這里就可以寫成
  
  tf.input_data = tf.placeholder(tf.float32, shape=[batch_size, rnn_size], name='input_data')
  
  但我們希望詞向量可以在train的過程中被改變,更適應我們的訓練數據。那就要用Variable來表示詞向量矩陣。因此我們要定義一個變量來表示,詞向量矩陣的維度應該是 vocab_size * rnn_size。 即每一行代表一個詞,列數就是我們需要自己定義的詞向量維度。定義了詞向量矩陣的變量,每次輸入的時候,還需要為輸入的詞找到對應的詞向量,這些tensorflow都為我們封裝好了,代碼如下
  
  embedding = tf.Variable(tf.truncated_normal([vocab_size, rnn_size], stddev=0.1), name='embedding')
  
  inputs = tf.nn.embedding_lookup(embedding, self.input_data)
  
  tf.nn.embedding_lookup這個函數就是用於返回所查找的詞向量Tensor的。
  
  embedding_lookup(params, ids, partition_strategy=’mod’, name=None, validate_indices=True, max_norm=None)
  
  其中params是詞向量矩陣,ids是需要需要查找的詞的id。舉個簡單的例子如下
  
  # 假設有詞向量空間x
  
  x = [[1.0,2.0,3.0],[4.0,5.0,6.0],[7.0,8.0,9.0]]
  
  vx = tf.Variable(x, name='vx')
  
  ids = tf.placeholder(tf.int32, name='ids')
  
  inputs = tf.nn.embedding_lookup(vx, ids)
  
  # 假如每個batch有3個句子,每個句子有兩個詞,詞的id如下
  
  input_data = [[0,1],[1,2],[0,2]]
  
  with tf.Session() as sess:
  
  sess.run(tf.global_variables_initializer())
  
  sess.run(inputs, feed_dict={ids:input_data})
  
  # 輸出結果如下
  
  >>> array([[[ 1., 2., 3.],
  
  [ 4., 5., 6.]],
  
  [[ 4., 5., 6.],
  
  [ 7., 8., 9.]],
  
  [[ 1., 2., 3.],
  
  [ 7., 8., 9.]http://www.ysbyl.biz/]], dtype=float32)
  
  輸出結果的shape為(3,2,3)
  
  用上述方式就可以查出來一個batch中每個句子的每個詞對應的詞向量。所以我們原始輸入的batch中,每個元素是一個sequence,sequence中的元素又是每個詞對應的id。
  
  這部分的完整代碼如下
  
  self.input_data = tf.placeholder(tf.int32, shape=[batch_size, sequence_length], name='input_data')
  
  # 指定這部分使用CPU進行計算
  
  with tf.device('/cpu:0'):
  
  embedding = tf.Variable(tf.truncated_normal([vocab_size, rnn_size], stddev=0.1), name='embedding')
  
  inputs = tf.nn.embedding_lookup(embedding, self.input_data)
  
  明確輸出
  
  因為在Char RNN中,每一時刻的輸出都是下一時刻的輸入,因此LSTM的輸出ot與輸入xt維度是一樣的。但ot並不是Char RNN模型的輸出,ot之后還需要跟全連接層以及softmax層來判斷每個詞出現的概率。每一時刻都有一個輸出,在訓練的階段,需要收集每一時刻的輸出,以便與targets進行比較來計算loss。因此需要有一個循環來展開整個lstm。展開的這部分tensorflow也有API可以調用,但是為了更好的理解,還是自己實現一遍比較好。代碼如下
  
  # 定義初始狀態
  
  self.initial_state = self.cell.zero_state(batch_size, tf.float32)
  
  with tf.variable_scope('RNN'):
  
  for time_step in xrange(sequence_length):
  
  # 因為LSTM Cell調用__call__(027yeshenghuowang.com)方法時,會使用到get_variable()獲取內部變量
  
  # 如果reuse的flag是False,調用get_variable()后會查找該variable_scope中有沒有重名的變量,如果有就報錯
  
  # 如果reuse的flag是True,調用get_variable()后則是在當前的variable_scope找不到變量時報錯
  
  # 因此在這部分需要reuse的時候要定義一個variable_scope,否則之后想用get_variable()定義新變量都會報錯
  
  if time_step > 0:
  
  tf.get_variable_scope().reuse_variables()
  
  if time_step == 0:
  
  output, state = self.cell(inputs[:, time_step, :], self.initial_state)
  
  else:
  
  output, state = self.cell(inputs[:, time_step, :], state)
  
  outputs.append(output)
  
  self.final_state = state
  
  softmax_w = tf.Variable(tf.truncated_normal([rnn_size, vocab_size], stddev=0.1), name='softmax_w')
  
  softmax_b = tf.Variable(tf.zeros([vocab_size]www.22yigouyule.cn/), name='softmax_b')
  
  # 執行完循環以后,outputs的shape=(sequence_length, batch_size, rnn_size)
  
  # 而matmul接受的矩陣的rank必須是2,因此還需要做一下轉換
  
  # tf.concat()轉換后的outputs的shape為(batch_size * sequence_size, rnn_size)
  
  outputs = tf.concat(outputs, 0)
  
  self.logits = tf.matmul(outputs, softmax_w) + softmax_b
  
  self.prob = tf.nn.softmax(self.logits)
  
  定義loss與train_op
  
  要定義loss函數首先要有正確的輸入,因此先定義targets。在實際feed的時候,要注意targets中的順序必須與outputs中預測結果是對應的。這個之后寫一個輔助函數來對輸入的targets進行轉換。
  
  loss函數的定義使用cross_entropy,tensorflow中有相應的API tf.losses.softmax_cross_entropy, 這個API封裝了softmax步驟,因此應該傳入logits而不是把softmax之后的prob傳進去。
  
  定義完loss之后就需要定義optimizer與train_op。
  
  通常可以直接train_op = tf.train.AdamOptimizer(self.lr).minimize(self.cost)。但是RNN的訓練中很有可能因為梯度過大導致訓練過程不穩定而不收斂,因此需要對計算出的梯度做一步裁剪,再手動更新梯度。
  
  這部分的代碼如下
  
  self.targets = tf.placeholder(tf.int32, shape=[None, vocab_size], name='targets')
  
  self.cost = tf.losses.softmax_cross_entropy(self.targets, self.logits)
  
  self.lr = tf.Variable(0.0, trainable=False)
  
  tvars = tf.trainable_variables(www.yihuanyule.cn)
  
  grads, _ = tf.clip_by_global_norm(tf.gradients(www.xuancayule.com self.cost, tvars), grad_clip)
  
  optimizer = tf.train.AdamOptimizer(self.lr)
  
  self.train_op = optimizer.apply_gradients(zip(grads, tvars))
  
  -
  
  到這里為止Char RNN的主要部分,即模型的結構及其訓練所需的op都定義完成了, 還剩下inference的接口,以及啟動訓練模型的部分未完成。


免責聲明!

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



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