LSTM详解
轉載自:https://blog.csdn.net/m0_37917271/article/details/82350571?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-11.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-11.control
侵權聯(lián)系刪除
?
LSTM規(guī)避了標準RNN中梯度爆炸和梯度消失的問題,所以會顯得更好用,學習速度更快
下圖是最基本的LSTM單元連接起來的樣子
上圖為一層LSTM單元連接起來的樣子,在工業(yè)上,LSTM是可以像一個很大的方陣的,其中除了輸入層和輸出層分別對應著Xt和ht的值以外,中間的部分都是一層層的LSTM單元,拓撲結構如下:
LSTM內部結構
LSTM看上去就是這樣一種效果,一個一個首尾相接,
同一層的會把前面單元的輸出作為后面單元的輸入;
前一層的輸出會作為后一層的輸入
?
細胞狀態(tài)
LSTM 的關鍵就是細胞狀態(tài),水平線在圖上方從左到右貫穿運行。
細胞狀態(tài)類似于傳送帶。直接在整個鏈上運行,只有一些少量的線性交互。信息在上面流傳保持不變會很容易
左面的乘號是一個乘法操作,右面的加號就是普通的線性疊加、
這個操作相當于左側的Ct-1進入單元后,先被一個乘法器乘以一個系數后,再線性疊加一個數值然后從右側輸出去
?
?
(一)忘記門
在我們 LSTM 中的第一步是決定我們會從細胞狀態(tài)中丟棄什么信息。這個決定通過一個稱為忘記門層完成
忘記門:
作用對象:細胞狀態(tài)
作用:將細胞狀態(tài)中的信息選擇性的遺忘, 即丟掉老的不用的信息
讓我們回到語言模型的例子中來基于已經看到的預測下一個詞。在這個問題中,細胞狀態(tài)可能包含當前主語的類別,因此正確的代詞可以被選擇出來。當我們看到新的主語,我們希望忘記舊的主語。
例如,他今天有事,所以我。。。當處理到‘’我‘’的時候選擇性的忘記前面的’他’,或者說減小這個詞對后面詞的作用。
左側的ht-1和下面輸入的xt經過了連接操作,再通過一個線性單元,經過一個σ也就是sigmoid函數生成一個0到1之間的數字作為系數輸出,表達式如左:
Wf和bf作為待定系數是要進行訓練學習的
?
(二)產生要更新的新信息
包含兩個小的神經網絡層
一個是熟悉的sigmoid部分
另一個tanh標識(映射到-1到1)也是一個神經網絡層,表達式為:
?
其中的W和b都是要通過訓練得到的
(三)
更新細胞狀態(tài),
新細胞狀態(tài) = 舊細胞狀態(tài) × 忘記門結果 + 要更新的新信息
?
(四)
基于細胞狀態(tài),確定輸出什么值
一個輸出到同層下一個單元,一個輸出到下一層的單元上
首先,我們運行一個 sigmoid 層來確定細胞狀態(tài)的哪個部分將輸出出去。
接著,我們把細胞狀態(tài)通過 tanh 進行處理(得到一個在 -1 到 1 之間的值)并將它和 sigmoid 門的輸出相乘,最終我們僅僅會輸出我們確定輸出的那部分。
在語言模型中,這種影響是可以影響前后詞之間詞形的相關性的,例如前面輸入的是一個代詞或名詞,后面跟隨的動詞會學到是否使用“三單形式”或根據前面輸入的名詞數量來決定輸出的是單數形式還是復數形式。
以下為本文實現LSTM的代碼,使用了perplexity(即平均cost的自然常數指數,是語言模型中用來比較模型性能的重要指標,越低表示模型輸出的概率分布在預測樣本上越好)來測評模型,代碼及詳細注釋如下:
import time import numpy as np import tensorflow as tf from tensorflow.models.tutorials.rnn.ptb import reader'''處理輸入數據的類''' class PTBInput(object):def __init__(self, config, data, name=None):self.batch_size = batch_size = config.batch_size #讀取config中的batch_size,num_steps到本地變量self.num_steps = num_steps = config.num_stepsself.epoch_size = ((len(data) // batch_size) - 1) // num_steps #全部樣本被訓練的次數self.input_data, self.targets = reader.ptb_producer(data, batch_size, num_steps, name=name)'''語言模型的類''' class PTBModel(object):def __init__(self, is_training, #訓練標記config, #配置參數input_): #PTBInput類的實例input_self._input = input_batch_size = input_.batch_sizenum_steps = input_.num_stepssize = config.hidden_size #LSTM的節(jié)點數vocab_size = config.vocab_size #詞匯表的大小def lstm_cell(): #定義默認的LSTM單元return tf.contrib.rnn.BasicLSTMCell(size, #隱含節(jié)點數forget_bias=0.0, #忘記門的biasstate_is_tuple=True) #代表接受和返回的state將是2-tuple的形式attn_cell = lstm_cell #不加括號表示調用的是函數,加括號表示調用的是函數的結果if is_training and config.keep_prob < 1: #如果在訓練狀態(tài)且Dropout的keep_prob小于1#在前面的lstm_cell后接一個Dropout層def attn_cell():return tf.contrib.rnn.DropoutWrapper(lstm_cell(), output_keep_prob=config.keep_prob)#使用RNN堆疊函數tf.contrib.rnn.MultiRNNCell將前面構造的lstm_cell多層堆疊得到cell,堆疊次數為config中num_layerscell = tf.contrib.rnn.MultiRNNCell([attn_cell() for _ in range(config.num_layers)],state_is_tuple=True)self._initial_state = cell.zero_state(batch_size, tf.float32) #設置LSTM單元的初始化狀態(tài)為0'''詞嵌入部分'''with tf.device("/cpu:0"):embedding = tf.get_variable( #初始化詞向量embedding矩陣"embedding", [vocab_size, size], dtype=tf.float32) #行數詞匯表數vocab_size,列數(每個單詞的向量位數)設為hidden_sizeinputs = tf.nn.embedding_lookup(embedding, input_.input_data) #查詢單詞的向量表達獲得inputsif is_training and config.keep_prob < 1: #如果為向量狀態(tài)則再添加一層Dropout層inputs = tf.nn.dropout(inputs, config.keep_prob)'''定義輸出outputs'''outputs = []state = self._initial_state#為了控制訓練過程,我們會限制梯度在反向傳播時可以展開的步數為一個固定的值,而這個步數也就是num_stepswith tf.variable_scope("RNN"):for time_step in range(num_steps):if time_step > 0: #第二次循環(huán)開始,使用tf.get_variable_scope().reuse_variables()設置復用變量tf.get_variable_scope().reuse_variables()#每次循環(huán),傳入inputs和state到堆疊的LSTM單元,得到輸出的cell_output和更新后的state#inputs有三個維度,1:batch中第幾個樣本,2:樣本中第幾個單詞,3:單詞的向量維度(cell_output, state) = cell(inputs[:, time_step, :], state) #inputs[:, time_step, :]表示所有樣本的第time_step個單詞outputs.append(cell_output) #添加到輸出列表outputs#將output內容用tf.concat串接到一起,并使用tf.reshape將其轉為一個很長的一維向量output = tf.reshape(tf.concat(outputs, 1), [-1, size])#定義權重softmax_w = tf.get_variable("softmax_w", [size, vocab_size], dtype=tf.float32)#定義偏置softmax_b = tf.get_variable("softmax_b", [vocab_size], dtype=tf.float32)#輸出乘上權重并加上偏置得到logits,即網絡最后的輸出logits = tf.matmul(output, softmax_w) + softmax_b#使用如下函數計算輸出logits和targets的偏差loss = tf.contrib.legacy_seq2seq.sequence_loss_by_example([logits],[tf.reshape(input_.targets, [-1])],[tf.ones([batch_size * num_steps], dtype=tf.float32)])self._cost = cost = tf.reduce_sum(loss) / batch_size #匯總batch的總誤差,再計算到平均每個樣本的誤差costself._final_state = state #保留最終的狀態(tài)為final_stateif not is_training:returnself._lr = tf.Variable(0.0, trainable=False) #定義學習速率并設為不可訓練的tvars = tf.trainable_variables() #獲取全部可訓練的參數#針對前面得到的cost,計算tvars的梯度,并用tf.clip_by_global_norm設置梯度的最大范數max_grad_norm#這就是Gradient Clipping的方法,控制梯度的最大范數,防止梯度爆炸,某種程度上起到正則化的效果grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),config.max_grad_norm)optimizer = tf.train.GradientDescentOptimizer(self._lr) #定義優(yōu)化器#創(chuàng)建訓練操作_train_op,用optimizer.apply_gradients將clip過的梯度應用到所有可訓練參數tvars上self._train_op = optimizer.apply_gradients(zip(grads, tvars),global_step=tf.contrib.framework.get_or_create_global_step()) #生成全局統(tǒng)一的訓練步數#設置一個名為_new_lr的placeholder用以控制學習速率self._new_lr = tf.placeholder(tf.float32, shape=[], name="new_learning_rate")self._lr_update = tf.assign(self._lr, self._new_lr) #將_new_lr的值賦給當前的學習速率_lr#用以外部控制學習速率def assign_lr(self, session, lr_value):session.run(self._lr_update, feed_dict={self._new_lr: lr_value}) #執(zhí)行_lr_update操作完成對學習速率的修改''' @property裝飾器可以將返回變量設為只讀,防止修改變量引發(fā)問題這里定義input,initial_state,cost,final_state,lr,train_op為只讀,方便外部訪問'''@propertydef input(self):return self._input@propertydef initial_state(self):return self._initial_state@propertydef cost(self):return self._cost@propertydef final_state(self):return self._final_state@propertydef lr(self):return self._lr@propertydef train_op(self):return self._train_op'''小模型的設置''' class SmallConfig(object):init_scale = 0.1 #網絡中權重的初始scalelearning_rate = 1.0 #學習速率的初始值max_grad_norm = 5 #前面提到的梯度的最大范數num_layers = 2 #LSTM可以堆疊的層數num_steps = 20 #LSTM梯度反向傳播的展開步數hidden_size = 200 #LSTM內隱含節(jié)點數max_epoch = 4 #初始學習速率可訓練的epoch數max_max_epoch = 13 #總共可訓練的epoch數keep_prob = 1.0 #dropout保留節(jié)點的比例lr_decay = 0.5 #學習速率的衰減速度batch_size = 20 #每個batch中樣本數vocab_size = 10000 #詞匯表大小'''中等大小模型的設置''' class MediumConfig(object):init_scale = 0.1 #減小,小一些有利于溫和的訓練learning_rate = 1.0max_grad_norm = 5num_layers = 2num_steps = 35 #增大hidden_size = 650 #增大max_epoch = 6 #增大max_max_epoch = 39 #增大keep_prob = 0.5 #減小,之前設為1即沒有dropoutlr_decay = 0.8 #學習迭代次數增大,因此學習速率的衰減次數減小batch_size = 20vocab_size = 10000'''大型模型設置''' class MediumConfig(object):init_scale = 0.04 #減小learning_rate = 1.0max_grad_norm = 10 #增大num_layers = 2num_steps = 35hidden_size = 1500 #增大max_epoch = 14 #增大max_max_epoch = 55 #增大keep_prob = 0.35 #減小,之前設為1即沒有dropoutlr_decay = 1 / 1.15 #學習迭代次數增大,因此學習速率的衰減次數減小batch_size = 20vocab_size = 10000'''測試用設置,參數盡量使用最小值,只為測試可以完整運行模型''' class TestConfig(object):init_scale = 0.1learning_rate = 1.0max_grad_norm = 1num_layers = 1num_steps = 2hidden_size = 2max_epoch = 1max_max_epoch = 1keep_prob = 1.0lr_decay = 0.5batch_size = 20vocab_size = 10000'''訓練一個epoch數據''' def run_epoch(session, model, eval_op=None, verbose=False):start_time = time.time() #記錄當前時間costs = 0.0 #初始化損失costsiters = 0 #初始化迭代數state = session.run(model.initial_state) #執(zhí)行初始化狀態(tài)并獲得初始狀態(tài)feches = { #輸出結果的字典表"cost":model.cost,"final_state":model.final_state}if eval_op is not None: #如果有評測操作,也加入fechesfeches["eval_op"] = eval_opfor step in range(model.input.epoch_size): #訓練循環(huán),次數為epoch_sizefeed_dict = {}for i, (c, h) in enumerate(model.initial_state): #生成訓練用的feed_dict,將全部LSTM單元的state加入feed_dictfeed_dict[c] = state[i].cfeed_dict[h] = state[i].h# 執(zhí)行feches對網絡進行一次訓練,得到cost和statevals = session.run(feches, feed_dict)cost = vals["cost"]state = vals["final_state"]costs += cost #累加cost到costsiters += model.input.num_steps #累加num_steps到itersif verbose and step % (model.input.epoch_size // 10) == 10: #每完成約10%的epoch,就進行一次結果的展示#一次展示當前epoch的進度,perplexity和訓練速度#perplexity(即平均cost的自然常數指數,是語言模型中用來比較模型性能的重要指標,越低表示模型輸出的概率分布在預測樣本上越好)print("%.3f perplexity: %.3f speed: %.0f wps" %(step * 1.0 / model.input.epoch_size, np.exp(costs / iters),iters * model.input.batch_size / (time.time() - start_time)))return np.exp(costs / iters) #返回perplexity作為函數結果raw_data = reader.ptb_raw_data('F://研究生資料/data/simple-examples/data/') #直接讀取解壓后的數據 train_data, valid_data, test_data, _ = raw_data #得到訓練數據,驗證數據和測試數據config = SmallConfig() #定義訓練模型的配置為SmallConfig eval_config = SmallConfig() #測試配置需和訓練配置一致 eval_config.batch_size = 1 #將測試配置的batch_size和num_steps修改為1 eval_config.num_steps = 1#創(chuàng)建默認的Graph with tf.Graph().as_default():# 設置參數的初始化器,令參數范圍在[-init_scale,init_scale]之間initializer = tf.random_uniform_initializer(-config.init_scale, config.init_scale)'''使用PTBInput和PTBModel創(chuàng)建一個用來訓練的模型m,以及用來驗證的模型mvalid和測試的模型mtest,其中訓練和驗證模型直接使用前面的config,測試模型使用前面的測試配置eval_config'''#訓練模型mwith tf.name_scope("Train"):train_input = PTBInput(config=config, data=train_data, name="TrainInput")with tf.variable_scope("Model", reuse=None, initializer=initializer):m = PTBModel(is_training=True, config=config, input_=train_input)#驗證模型mvalidwith tf.name_scope("Valid"):valid_input = PTBInput(config=config, data=valid_data, name="ValidInput")with tf.variable_scope("Model", reuse=True, initializer=initializer):mvalid = PTBModel(is_training=False, config=config, input_=valid_input)#測試模型mtestwith tf.name_scope("Test"):test_input = PTBInput(config=eval_config, data=test_data, name="TestInput")with tf.variable_scope("Model", reuse=True, initializer=initializer):mtest = PTBModel(is_training=False, config=eval_config, input_=test_input)sv = tf.train.Supervisor() #創(chuàng)建訓練的管理器with sv.managed_session() as session: #使用sv.managed_session()創(chuàng)建默認sessionfor i in range(config.max_max_epoch): #執(zhí)行多個epoch數據的循環(huán)lr_decay = config.lr_decay ** max(i + 1 - config.max_max_epoch, 0.0)#計算累計的學習速率衰減值(只計算超過max_epoch的輪數)m.assign_lr(session, config.learning_rate * lr_decay) #將初始學習速率呈上累計的衰減,并更新學習速率'''循環(huán)內執(zhí)行一個epoch的訓練和驗證并輸出當前的學習速率,訓練和驗證集上的perplexity'''print("Epoch: %d Learning rate: %.3f" % (i + 1, session.run(m.lr)))train_perplexity = run_epoch(session, m, eval_op=m.train_op, verbose=True)print("Epoch: %d Train Perplexity: %.3f" % (i + 1, train_perplexity))valid_perplexity = run_epoch(session, mvalid)print("Epoch: %d Valid Perplexity: %.3f" % (i + 1, valid_perplexity))'''完成全部訓練后,計算并輸出模型在測試集上的perplexity'''test_perplexity = run_epoch(session, mtest)print("Test Perplexity: %.3f" % test_perplexity)結果如下:
Epoch: 13 Learning rate: 1.000
0.004 perplexity: 60.833 speed: 11641 wps
0.104 perplexity: 46.656 speed: 11364 wps
0.204 perplexity: 51.109 speed: 11372 wps
0.304 perplexity: 50.082 speed: 11419 wps
0.404 perplexity: 50.203 speed: 11437 wps
0.504 perplexity: 50.504 speed: 11413 wps
0.604 perplexity: 49.936 speed: 11394 wps
0.703 perplexity: 50.170 speed: 11379 wps
0.803 perplexity: 50.362 speed: 11379 wps
0.903 perplexity: 49.721 speed: 11376 wps
Epoch: 13 Train Perplexity: 49.700
Epoch: 13 Valid Perplexity: 138.165
Test Perplexity: 132.954?
總結
- 上一篇: Adam公式+参数解析
- 下一篇: 元学习Meta learning深入理解