【TensorFlow實戰筆記】通俗詳述RNN理論,LSTM理論,以及LSTM對於PTB數據集進行實戰

一、RNN

顧名思義,循環則神經元有前後的聯繫,而不光是像FCN(全連接)那樣的只有輸入和輸出的同時間的輸入輸出,也可以說這種網絡沒有利用到時間的信息,更準確的說應該是一種序列信息,不管是位置序列、還是時間序列。

1.結構

從網絡結構上,循環神經網絡會記憶之前的信息,並利用之前的信息影響後面結點的輸出。也就是說,循環神經網絡的隱藏層之間的結點是有連接的 ,隱藏層的輸入不僅包括輸入層的輸出 ,還包括上一時刻隱藏層的輸出,當然如果需要後面序列的信息,也需要下一時刻的輸出作爲輸入,所以這也就是單向RNN, 還是雙向RNN。
在這裏插入圖片描述
最經典的一個圖。
左面爲表示出神經網絡循環的感覺,後面是真實狀態的展開。
在每一時刻 t,循環神經網絡會針對該時刻的輸入結合當前模型的狀態給出一個輸出,並更新模型狀態。
理想上,在時刻 t,狀態 ht-1 濃縮了前面序列 Xo,X1->Xt- 1 的信息,用於作爲輸出 o 的參考,可以理解爲輸出Ot有着前面所有的輸入的信息關係。
在這裏插入圖片描述
對於我所畫的所示,可以看出如果別的輸入輸出不理會的話,其實對於Ot和X0來講就相當於一個有t箇中間隱藏層的前饋神經網絡,因此可以直接使用反向傳播算法進行訓練,而不需要任何特別的優化算法。這樣的訓練方法稱爲“沿時間反向傳播”,是訓練循環神經網絡最常見的方法。
在這裏插入圖片描述這個圖和上面那個圖大同小異,我這裏主要是想強調一個東西。
就是這個V,因爲一般狀態(state)的維度以及輸入之後得到的維度都是需要我們在連接一個全連接層才能得到最後我們所想要的維度的,這個全連接層在這個圖裏就是V,狀態就是W也就是上圖中的h,一般用h表示。
一般的神經網絡有兩個輸入,一個是當前時刻的輸入樣本,一部分是上一時刻的狀態。
所以假設 輸入的數據爲x維,狀態的維度爲n,則input的維度爲x+n,如果循環神經元就是全連接層網絡,因爲此時的輸出還是下一次的輸入狀態所以應該還是n維,所以循環體的參數個數爲(n+x)*n+n
下圖爲一個例子講述上述所說的關係,當然這只是神經網絡爲純粹全連接的形式。
在這裏插入圖片描述

2.機器翻譯

當然對於循環神經網絡最多的就是機器翻譯的model,大體形式如下:
在這裏插入圖片描述
來看一個example
在這裏插入圖片描述
EOS爲結束符 LSTM爲較爲流行的rnn網絡
從這兩個圖可以得到以下幾個結論:每個時刻都要有輸入但是不一定要有輸出,當整個時序輸入序列輸入完畢之後,之後每個預測的值都是下一個時刻的輸入。

還有更多比較常見的rnn形式如下,看圖片即可知道其大概形式和結構

3.雙向RNN

在這裏插入圖片描述

4.深層RNN網絡

在這裏插入圖片描述

5.RNN網絡的dropout

在這裏插入圖片描述類似卷積神經網絡只在最後的全連接層中使用 dropout ,循環神 經網絡 一般只在不同層循環
體結構之間使用 dropout ,而不在同一層的循環體結構之間使用。 也就是說從時刻 t-1 傳遞
到時刻 t 時,循環神經網絡不會進行狀態的 dropout ;而在同一個時刻 t 中,不同層循環體
之間會使用 dropout 。
白話來說就是對於深層的rnn來講,每一層之間有dropout,而每一個時刻之間不存在dropout。

二、LSTM

上述的rnn,理論上講是t時刻可以和0->t-1時刻的信息全都有關聯,但是事實上無法做到這一點,可能會引起loss的爆炸或者消散。
所以引出了LSTM(Long short-term memory)長短時記憶網絡。
爲什麼叫這麼名字呢???
其實還是顧名思義,想想看我們去用時序去預測下一個時刻的結果的時候,以時間序列的句子預測爲例,這個時刻的結果可能和前面一小段有關,也可能和很久之前的信息有關。
比如:
①這裏的海很____ 一般來講都是藍對吧,也可以說藍輸出的可能性最大
②最近旁邊的工廠放了很多污水,這裏的海很___ 這回就應該是黑或者污濁了吧
③這裏的海很__,都沒有多少生物生存了。 這裏應該也是渾濁了吧,最大可能性。

①②:所以有選擇的吸取以前的信息還是有必要的,或者說不是什麼時候都需要很久之前的信息,或者很近的信息,也就是說有用的信息有大有小,由遠有近。
③:這裏對應於後面的信息也要關聯,也就是所謂的雙向lstm。

1.先來看一下lstm的結構示意圖

在這裏插入圖片描述
說明

  • Ct-1 上一時刻的狀態值。
  • ht-1 上一時刻的輸出
  • Xt  這個時刻的輸入
  • ht  這個時刻的輸出

有三個核心結構

  • 輸入門:用很通俗的說法就是輸入的信息你想讓進來這個網絡多少,輸入信息由上一時刻的輸入和這個時刻的輸入組成
  • 輸出門:輸出的信息走的門。
  • 遺忘門:最爲關鍵的門,選擇之前的信息狀態想去遺忘多少,最後之前的信息讓進來網絡多少,由上一時刻的狀態值和這個時刻的輸入以及上一時刻的輸出組成。
    輸入門都是對應的組成成分,組成一個新的維度的輸入,之後通過sigmoid的函數得到一個0-1的值,1爲全部信息都通過,0則一點都不要。

2.細節圖

在這裏插入圖片描述

三、實戰

1.下載PTB數據集

如果沒有這個數據集可以按照我以前寫的文章。
《TensorFlow學習筆記》完美解決 pip3 install tensorflow 沒有models庫,讀取PTB數據
也可以直接下載使用
http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz
主要用如圖幾個文件
在這裏插入圖片描述

裏面都是英文句子
在這裏插入圖片描述

2.數據集預處理

①先把txt中的所有出現的word整理成一個按降序排列的詞彙表文件

generate_VOCAB.py

# -*- coding:UTF-8 -*-

"""
@Author:Che_Hongshu
@Modify:2018.12.16
"""
import codecs
import collections
from operator import itemgetter

RAW_DATA = "simple-examples/data/ptb.train.txt"  # 輸入的ptb訓練數據

VOCAB_OUTPUT = "ptb.vocab" #輸出詞彙表文件

counter = collections.Counter() #一個計數器,統計每個單詞出現的次數

with codecs.open(RAW_DATA, "r", "utf-8") as f: #utf-8格式讀取
    for line in f:
        for word in line.strip().split(): #line.strip().split()相當於把每一行的前後空格去掉,再根據空格分詞生成list
            counter[word] += 1 #統計相同單詞出現次數+1
#  Counter 集成於 dict 類,因此也可以使用字典的方法,此類返回一個以元素爲 key 、元素個數爲 value 的 Counter 對象集合
# 依據key排序 itermgetter(1)爲降序
sorted_word_to_cnt = sorted(counter.items(), key=itemgetter(1), reverse=True)

#  轉換成單詞string的list
sorted_words_list = [x[0] for x in sorted_word_to_cnt]

#  加入句子結束符
sorted_words_list = ["<eos>"] + sorted_words_list

with codecs.open(VOCAB_OUTPUT, 'w', 'utf-8') as file_output:
    for word in sorted_words_list:
        file_output.write(word + '\n')

ptb.vocab如下,按行數依次遞減爲出現次數依次減少的word
在這裏插入圖片描述在這裏插入圖片描述
一共爲10000個高頻詞彙,因爲這裏的PTB數據集已經經過前期處理了,所以這邊如果是其他數據集需要對詞彙表的個數進行設定,並且更新,類似於加上下面這樣的代碼

if len(sorted words ) > 10000:
  sorted words = sorted words [: 10000]

②通過得到的詞彙表,將每個文件轉換爲數字化文件,每個單詞的id爲所在的詞彙錶行數

VOCAB_transfrom_sequence.py

# -*- coding:UTF-8 -*-

"""
@Author:Che_Hongshu
@Modify:2018.12.16
"""
import codecs


RAW_DATA = "simple-examples/data/ptb.valid.txt"  # 輸入的ptb訓練數據

VOCAB = "ptb.vocab" #輸出詞彙表文件

OUTPUT_DATA = 'ptb.valid' #將單詞替換成單詞編號後的輸出文件

with codecs.open(VOCAB, 'r', 'utf-8') as f_vocab:  #打開文件進入讀操作
    vocab = [w.strip() for w in f_vocab.readlines()]  # 先把所有詞轉換成list
    # 把每個詞和所在行數對應起來並且zip打包成(“詞”,行數)格式轉換成dict格式
word_to_id = {k: v for (k, v) in zip(vocab, range(len(vocab)))}

# 返回id 如果在詞彙表文件中則返回對應的id即可,如果沒有則返回'<unk>'
def get_id(word):
    return word_to_id[word] if word in word_to_id else word_to_id['<unk>']

# 打開文件
fin = codecs.open(RAW_DATA, 'r', 'utf-8')
fout = codecs.open(OUTPUT_DATA, 'w', 'utf-8')

for line in fin:
    words = line.strip().split() + ["<eos>"] #每一行的單詞變成sring list格式,每一句話後面加上一個結束符號
    out_line = ' '.join([str(get_id(w)) for w in words]) + '\n' #這一行中的每個單詞取出對應的id之後用空格相連接 
    fout.write(out_line)

# 關閉文件
fin.close()
fout.close()

更改 RAW_DATA 和 OUTPUT_DATA
得到
在這裏插入圖片描述

以ptb.test:
在這裏插入圖片描述

3.自然語言處理建模示意圖和batching方法

①自然語言處理建模示意圖

在這裏插入圖片描述數據經過詞向量層(embedding),經過rnn,最後經過softmax得到每個result的概率。

②batching方法

在這裏插入圖片描述
就是循環神經網絡不能像卷積神經網絡那樣直接圖片reshape,統一規格,還有一點就是需要把序列信息保留下來,解決方法就是把每個句子序列拉成一個很長的序列直接平均截取幾份,之後統一對這幾份一起序列截取採樣,就是一個batch,從圖上解釋我自己理解就是(我個人感覺這裏不是很好理解), 根據下面的代碼來看就是bachsize就是豎列的大小,一個bachsize的數據大小的寬度就爲num_step,而分爲多少份爲num_batches

def make_batches(id_list, batch_size, num_step):
    # 計算總的 batch 數量。每個 batch 包含的單詞數量是 batch_size*num_step
    num_batches = (len(id_list) - 1) // (batch_size*num_step)
    # 從頭開始取正好num_batches*batch_size*num_step
    data = np.array(id_list[: num_batches*batch_size*num_step])
    # 將數據整理成一個維度爲[ batch_size, num_batches*numstep ]
    data = np.reshape(data, [batch_size, num_batches*num_step])
    # 相當於在第二維數據上豎着截取一部分數據
    data_batches = np.split(data, num_batches, axis=1)
    # 因爲相當於一個時刻去預測下一個時刻,所以進行相應的+1,相當於每個時刻的預測真值都在下一時刻。
    label = np.array(id_list[1:num_batches*batch_size*num_step +1])
    label = np.reshape(label, [batch_size, num_batches*num_step])
    label_batches = np.split(label, num_batches, axis=1)

    return list(zip(data_batches, label_batches))

4. 完整的訓練程序

有詳細的代碼詳細說明

# -*- coding:UTF-8 -*-
"""
@Author:Che_Hongshu
@Modify:2018.12.28
@CSDN:http://blog.csdn.net/qq_33431368

"""

import numpy as np
import tensorflow as tf

TRAIN_DATA = "ptb.train"  # 訓練數據
EVAL_DATA = "ptb.valid"   # 驗證數據
TEST_DATA = "ptb.test"    #  測試數據
HIDDEN_SIZE = 300         # 隱藏層

NUM_LAYERS = 2            # LSTM 層數
VOCAB_SIZE = 10000        # 詞典規模(只要這麼大的規模的特徵詞的數字表示)
TRAIN_BATCH_SIZE = 20     # 訓練數據的batchsize
TRAIN_NUM_STEP = 35       # 訓練數據截斷長度

EVAL_BATCH_SIZE = 1       # 驗證數據的batchsize
EVAL_NUM_STEP = 1        # 驗證數據截斷長度
NUM_EPOCH = 5            # 訓練數據的輪數
LSTM_KEEP_PROB = 0.9      # LSTM節點不被dropout的概率
EMBEDDING_KEEP_PROB = 0.9  # 詞向量不被dropout的概率
MAX_GRAB_NORM = 5         # 用於控制梯度膨脹大小的上限
SHARE_EMB_AND_SOFTMAX = True  # Softmax預詞向量層之間共享參數

"""
function: class of LSTM
Parameters:
Returns:
CSDN:
    http://blog.csdn.net/qq_33431368
"""
class PTBModel(object):
    def __init__(self, is_training, batch_size, num_steps):
        # 記錄使用的 batch 大小和截斷長度
        self.batch_size = batch_size
        self.num_steps = num_steps

        # 定義每一步的輸入和預期輸出, 兩者的維度都是[ batch_size ,num_steps ]
        self.input_data = tf.placeholder(tf.int32, [batch_size, num_steps])
        self.targets = tf.placeholder(tf.int32, [batch_size, num_steps])

        # 定義使用 LSTM 結構爲循環體結構且使用 dropout 的深層循環神經網絡。
        dropout_keep_prob = LSTM_KEEP_PROB if is_training else 1.0  #訓練時採用dropout

        #定義lstm
        lstm_cells = [

            #運用Dropout的LSTM,不同時刻不dropout,同一時刻dropout
            tf.nn.rnn_cell.DropoutWrapper(
                tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE),
                output_keep_prob=dropout_keep_prob
            )
            # NUM_LAYERS爲層數,也就是LSTM爲幾層
            for _ in range(NUM_LAYERS)
        ]
        #創建多層深度lstm
        cell = tf.nn.rnn_cell.MultiRNNCell(lstm_cells)
        # 對創建的LSTM進行初始化
        #初始化最初的狀態, 即全0的向量。這個量只在每個epoch初始化第一個batch才使用。
        self.initial_state = cell.zero_state(batch_size, tf.float32)

        # 定義單詞的詞向量矩陣。
        embedding = tf.get_variable("embedding", [VOCAB_SIZE, HIDDEN_SIZE])
        # 將輸入單詞轉化爲詞向量
        inputs = tf.nn.embedding_lookup(embedding, self.input_data)
        #只在訓練時進行dropout,測試和驗證都不要dropout操作
        if is_training:
            inputs = tf.nn.dropout(inputs, EMBEDDING_KEEP_PROB)
        # 定義輸出列表。在這裏先將不同時刻 LSTM 結構的輸出收集起來 , 再一起提供給softmax層
        outputs = []
        # lstm狀態值
        state = self.initial_state
        with tf.variable_scope("RNN"):
            #numsteps爲每次截斷的序列長度
            for time_step in range(num_steps):
                #在第一個時刻聲明LSTM使用的變量,在之後的時刻都需要複用之前定義好的變量
                if time_step > 0:
                    tf.get_variable_scope().reuse_variables()
                # 得到這個時刻lstm輸出以及當前狀態
                cell_output, state = cell(inputs[:, time_step, :], state)
                # 這個時間段的結構,因爲一步一步來append,所以是相當於[[],[],[],[]]需要下面一步concat(,1)來使所有的結果在一個維度上
                outputs.append(cell_output)
        # 把輸出進行調整維度(,HEDDEN_SIZE)
        output = tf.reshape(tf.concat(outputs, 1), [-1, HIDDEN_SIZE])
        # 是否共享參數(softmax層+embedding層)\

        #Softmax擺: 將RNN在每個位置上的輸出轉化爲各個單詞的logits,也就是最後得出的每個單詞是最終預測結果的概率。
        if SHARE_EMB_AND_SOFTMAX:
            weight = tf.transpose(embedding)
        else:
            weight = tf.get_variable("weight", [HIDDEN_SIZE, VOCAB_SIZE])
        bias = tf.get_variable("bias", [VOCAB_SIZE])
        #最後經過全連接層輸出的結果
        logits = tf.matmul(output, weight) + bias
        #交叉熵損失函數,算loss
        loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=tf.reshape(self.targets, [-1]), logits=logits)
        #求出每個batch平均loss
        self.cost = tf.reduce_sum(loss)/batch_size
        #最終的state
        self.final_state = state

        if not is_training:
            return

        # 控制梯度大小,定義優化方法和訓練步驟。
        trainable_variables = tf.trainable_variables()
        # 算出每個需要更新的值的梯度,並對其進行控制
        grads, _ = tf.clip_by_global_norm(tf.gradients(self.cost, trainable_variables), MAX_GRAB_NORM)
        # 利用梯度下降優化算法進行優化.學習率爲1.0
        optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
        #相當於minimize的第二步,正常來講所得到的list[grads,vars]由compute_gradients得到,返回的是執行對應變量的更新梯度操作的op
        self.train_op = optimizer.apply_gradients(zip(grads, trainable_variables))

"""
function: 使用給定的模型 model 在datasets 上運行 train op 並返回在全部數據上的 perplexity 值
Parameters:
    session-會話
    model-模型
    batches-批量值
    train_op-執行對應變量的更新梯度操作op
    output_log-
    step-訓練步數
Returns:
     return step, np.exp(total_costs / iters)-步數和對應求出的perplexity
CSDN:
    http://blog.csdn.net/qq_33431368
"""
def run_epoch(session, model, batches, train_op, output_log, step):
    # 計算平均 perplexity 的輔助變量
    total_costs = 0.0
    iters = 0
    #得到final_state
    state = session.run(model.initial_state)
    # 訓練一個 epoch
    for x, y in batches:
        # 在當前batch 上運行 train op 並計算損失值, 交叉炳損失函數計算的就是下一個單詞爲給定單詞的概率。
        cost, state, _ = session.run([model.cost, model.final_state, train_op],
                                     {model.input_data: x, model.targets: y, model.final_state: state})
    # 總的loss
    total_costs += cost
    # 總的截斷長度
    iters += model.num_steps

    if output_log and step % 100 == 0:
        print "After %d steps, perplexity is %.3f" % (step, np.exp(total_costs/iters))
    #訓練次數
    step += 1

    return step, np.exp(total_costs / iters)

"""
function: 計算神經網絡的前向傳播的結果
Parameters:
    file_path-文件路徑(文件已經是前面處理好的id文件了)
Returns:
    idlist-對於輸入數據產生對應的轉換爲int的list
CSDN:
    http://blog.csdn.net/qq_33431368
"""
def read_data(file_path):
    with open(file_path, 'r') as fin: #打開文件
        id_string = " ".join([line.strip() for line in fin.readlines()]) #每一行讀取,並用空格相連
    id_list = [int(w) for w in id_string.split()] #轉換成id list
    return id_list

"""
function: 數據batching,產生最後輸入數據格式
Parameters:
    id_list-文件的對應id文件,由read_data產生
    batch_size-batch的大小
    num_step-截斷序列數據的長度
Returns:
    list(zip(data_batches, label_batches))-data,label的數據list
CSDN:
    http://blog.csdn.net/qq_33431368
"""
def make_batches(id_list, batch_size, num_step):
    # 計算總的 batch 數量。每個 batch 包含的單詞數量是 batch_size*num_step
    num_batches = (len(id_list) - 1) // (batch_size*num_step)
    # 從頭開始取正好num_batches*batch_size*num_step
    data = np.array(id_list[: num_batches*batch_size*num_step])
    # 將數據整理成一個維度爲[ batch_size, num_batches*numstep ]
    data = np.reshape(data, [batch_size, num_batches*num_step])
    # 相當於在第二維數據上豎着截取一部分數據
    data_batches = np.split(data, num_batches, axis=1)
    # 因爲相當於一個時刻去預測下一個時刻,所以進行相應的+1,相當於每個時刻的預測真值都在下一時刻。
    label = np.array(id_list[1:num_batches*batch_size*num_step +1])
    label = np.reshape(label, [batch_size, num_batches*num_step])
    label_batches = np.split(label, num_batches, axis=1)

    return list(zip(data_batches, label_batches))


def main():
    # 定義初始化函數。
    initializer = tf.random_uniform_initializer(-0.05, 0.05)
    #      initializer: default initializer for variables within this scope.
    # tf.variable_scope(,initializer=initializer)相當於在這個scope中都是這樣的初始化變量情況
    # #定義訓練用的循環神經網絡模型。
    with tf.variable_scope("language_model", reuse=None, initializer=initializer):
        train_model = PTBModel(True, TRAIN_BATCH_SIZE, TRAIN_NUM_STEP)
    # 定義測試用的循環神經網絡模型。它與 train model 共用參數 , 但是測試使用全部的參數,所以沒有dropout 。
    with tf.variable_scope("language_model", reuse=True, initializer=initializer):
        eval_model = PTBModel(False, EVAL_BATCH_SIZE, EVAL_NUM_STEP)
    #train
    with tf.Session() as session:
        tf.global_variables_initializer().run()
        # 生成train,test,eval的batches
        train_batches = make_batches(
            read_data(TRAIN_DATA), TRAIN_BATCH_SIZE, TRAIN_NUM_STEP
        )
        eval_batches = make_batches(
            read_data(EVAL_DATA), EVAL_BATCH_SIZE, EVAL_NUM_STEP
        )
        test_batches = make_batches(
            read_data(TEST_DATA), EVAL_BATCH_SIZE, EVAL_NUM_STEP
        )
        step = 0
        #進行NUM_EPOCH次迭代
        for i in range(NUM_EPOCH):
            print "In iteration: %d" % (i+1)
            step, train_pplx = run_epoch(session, train_model, train_batches, train_model.train_op, True, step)

            print "Epoch : %d train Perplexity: %.3f" % (i + 1, train_pplx)

            _, eval_pplx = run_epoch(session, eval_model, eval_batches, tf.no_op(), False, 0)

            print "Epoch: %d Eval Perplexity: %.3f" % (i + 1, eval_pplx)

        _, test_pplx = run_epoch(session, eval_model, test_batches, tf.no_op(), False, 0)
        print "Test Preplex: %.3f" % test_pplx


if __name__ == '__main__':
    main()


model評價就是簡單的交叉熵,描述現在的值和想要的值之間的距離差距
結果:
在這裏插入圖片描述

5. 需要好好理解的點

四、github

代碼鏈接,希望給予star或者fork
https://github.com/chehongshu/DL-tenserflow/tree/master/RNN_LSTM_PTB

PS: 如果覺得本篇本章對您有所幫助,歡迎關注、評論、點贊!Github給個Star就更完美了_!

特此感謝網上的資源,如果哪裏寫的不對請指正,文章也會在我不斷地加深理解中更改到更好!

Reference

A Critical Review of Recurrent Neural Networks for Sequence Learning

https://blog.csdn.net/xierhacker/article/details/73384760

https://blog.csdn.net/xierhacker/article/details/73480744

《TensorFlow: 實戰Google深度學習框架》 值得一看

http://www.tensorfly.cn/home/

https://blog.csdn.net/songhk0209/article/details/71134698

http://nicodjimenez.github.io/2014/08/08/lstm.html

https://blog.csdn.net/hustqb/article/details/80260002

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章