迴圈神經網路LSTM RNN迴歸:sin曲線預測

華為雲開發者社群 發表於 2021-09-11
神經網路
摘要:本篇文章將分享迴圈神經網路LSTM RNN如何實現迴歸預測。

本文分享自華為雲社群《[Python人工智慧] 十四.迴圈神經網路LSTM RNN迴歸案例之sin曲線預測 丨【百變AI秀】》,作者:eastmount。

一.RNN和LSTM回顧

1.RNN

(1) RNN原理

迴圈神經網路英文是Recurrent Neural Networks,簡稱RNN。假設有一組資料data0、data1、data2、data3,使用同一個神經網路預測它們,得到對應的結果。如果資料之間是有關係的,比如做菜下料的前後步驟,英文單詞的順序,如何讓資料之間的關聯也被神經網路學習呢?這就要用到——RNN。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

假設存在ABCD數字,需要預測下一個數字E,會根據前面ABCD順序進行預測,這就稱為記憶。預測之前,需要回顧以前的記憶有哪些,再加上這一步新的記憶點,最終輸出output,迴圈神經網路(RNN)就利用了這樣的原理。

首先,讓我們想想人類是怎麼分析事物之間的關聯或順序的。人類通常記住之前發生的事情,從而幫助我們後續的行為判斷,那麼是否能讓計算機也記住之前發生的事情呢?

迴圈神經網路LSTM RNN迴歸:sin曲線預測

在分析data0時,我們把分析結果存入記憶Memory中,然後當分析data1時,神經網路(NN)會產生新的記憶,但此時新的記憶和老的記憶沒有關聯,如上圖所示。在RNN中,我們會簡單的把老記憶呼叫過來分析新記憶,如果繼續分析更多的資料時,NN就會把之前的記憶全部累積起來。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

RNN結構如下圖所示,按照時間點t-1、t、t+1,每個時刻有不同的x,每次計算會考慮上一步的state和這一步的x(t),再輸出y值。在該數學形式中,每次RNN執行完之後都會產生s(t),當RNN要分析x(t+1)時,此刻的y(t+1)是由s(t)和s(t+1)共同創造的,s(t)可看作上一步的記憶。多個神經網路NN的累積就轉換成了迴圈神經網路,其簡化圖如下圖的左邊所示。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

總之,只要你的資料是有順序的,就可以使用RNN,比如人類說話的順序,電話號碼的順序,影像畫素排列的順序,ABC字母的順序等。在前面講解CNN原理時,它可以看做是一個濾波器滑動掃描整幅影像,通過卷積加深神經網路對影像的理解。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

而RNN也有同樣的掃描效果,只不過是增加了時間順序和記憶功能。RNN通過隱藏層週期性的連線,從而捕獲序列化資料中的動態資訊,提升預測結果。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

(2) RNN應用

RNN常用於自然語言處理、機器翻譯、語音識別、影像識別等領域,下面簡單分享RNN相關應用所對應的結構。

  • RNN情感分析: 當分析一個人說話情感是積極的還是消極的,就用如下圖所示的RNN結構,它有N個輸入,1個輸出,最後時間點的Y值代表最終的輸出結果。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

  • RNN影像識別: 此時有一張圖片輸入X,N張對應的輸出。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

  • RNN機器翻譯: 輸入和輸出分別兩個,對應的是中文和英文,如下圖所示。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

2.LSTM

接下來我們看一個更強大的結構,稱為LSTM。

(1) 為什麼要引入LSTM呢?

RNN是在有序的資料上進行學習的,RNN會像人一樣對先前的資料發生記憶,但有時候也會像老爺爺一樣忘記先前所說。為了解決RNN的這個弊端,提出了LTSM技術,它的英文全稱是Long short-term memory,長短期記憶,也是當下最流行的RNN之一。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

假設現在有一句話,如下圖所示,RNN判斷這句話是紅燒排骨,這時需要學習,而“紅燒排骨“在句子開頭。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

"紅燒排骨"這個詞需要經過長途跋涉才能抵達,要經過一系列得到誤差,然後經過反向傳遞,它在每一步都會乘以一個權重w引數。如果乘以的權重是小於1的數,比如0.9,0.9會不斷地乘以誤差,最終這個值傳遞到初始值時,誤差就消失了,這稱為梯度消失或梯度離散。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

反之,如果誤差是一個很大的數,比如1.1,則這個RNN得到的值會很大,這稱為梯度爆炸。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

梯度消失或梯度爆炸:在RNN中,如果你的State是一個很長的序列,假設反向傳遞的誤差值是一個小於1的數,每次反向傳遞都會乘以這個數,0.9的n次方趨向於0,1.1的n次方趨向於無窮大,這就會造成梯度消失或梯度爆炸。

這也是RNN沒有恢復記憶的原因,為了解決RNN梯度下降時遇到的梯度消失或梯度爆炸問題,引入了LSTM。

(2) LSTM

LSTM是在普通的RNN上面做了一些改進,LSTM RNN多了三個控制器,即輸入、輸出、忘記控制器。左邊多了個條主線,例如電影的主線劇情,而原本的RNN體系變成了分線劇情,並且三個控制器都在分線上。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

  • 輸入控制器(write gate): 在輸入input時設定一個gate,gate的作用是判斷要不要寫入這個input到我們的記憶體Memory中,它相當於一個引數,也是可以被訓練的,這個引數就是用來控制要不要記住當下這個點。
  • 輸出控制器(read gate): 在輸出位置的gate,判斷要不要讀取現在的Memory。
  • 忘記控制器(forget gate): 處理位置的忘記控制器,判斷要不要忘記之前的Memory。

LSTM工作原理為:如果分線劇情對於最終結果十分重要,輸入控制器會將這個分線劇情按重要程度寫入主線劇情,再進行分析;如果分線劇情改變了我們之前的想法,那麼忘記控制器會將某些主線劇情忘記,然後按比例替換新劇情,所以主線劇情的更新就取決於輸入和忘記控制;最後的輸出會基於主線劇情和分線劇情。

通過這三個gate能夠很好地控制我們的RNN,基於這些控制機制,LSTM是延緩記憶的良藥,從而帶來更好的結果。

二.LSTM RNN迴歸案例說明

前面我們講解了RNN、CNN的分類問題,這篇文章將分享一個迴歸問題。在LSTM RNN迴歸案例中,我們想要用藍色的虛線預測紅色的實線,由於sin曲線是波浪迴圈,所以RNN會用一段序列來預測另一段序列。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

程式碼基本結構包括:

  • (1) 生成資料的函式get_batch()
  • (2) 主體LSTM RNN
  • (3) 三層神經網路,包括input_layer、cell、output_layer,和之前分類RNN的結構一樣。
def add_input_layer(self,):
    pass
def add_cell(self):
    pass     
def add_output_layer(self):
    pass
  • (4) 計算誤差函式 computer_cost
  • (5) 誤差weight和偏置biases
  • (6) 主函式建立LSTM RNN模型
  • (7) TensorBoard視覺化神經網路模型,matplotlib視覺化擬合曲線、

最後再補充下BPTT,就開始我們的程式碼編寫。

(1) 普通RNN

假設我們訓練含有1000000個資料的序列,如果全部訓練的話,整個的序列都feed進RNN中,容易造成梯度消失或爆炸的問題。所以解決的方法就是截斷反向傳播 (Truncated Backpropagation,BPTT) ,我們將序列截斷來進行訓練(num_steps)。

一般截斷的反向傳播是:在當前時間t,往前反向傳播num_steps步即可。如下圖,長度為6的序列,截斷步數是3,Initial State和Final State在RNN Cell中傳遞。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

(2) TensorFlow版本的BPTT

但是Tensorflow中的實現並不是這樣,它是將長度為6的序列分為了兩部分,每一部分長度為3,前一部分計算得到的final state用於下一部分計算的initial state。如下圖所示,每個batch進行單獨的截斷反向傳播。此時的batch會儲存Final State,並作為下一個batch的初始化State。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

參考:深度學習(07)RNN-迴圈神經網路-02-Tensorflow中的實現 - 莫失莫忘Lawlite

三.程式碼實現

第一步,開啟Anaconda,然後選擇已經搭建好的“tensorflow”環境,執行Spyder。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

第二步,匯入擴充套件包。

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

第三步,編寫生成資料的函式get_batch(),它生成了sin曲線的序列。

# 獲取批量資料
def get_batch():
    global BATCH_START, TIME_STEPS
    # xs shape (50batch, 20steps)
    xs = np.arange(BATCH_START, BATCH_START+TIME_STEPS*BATCH_SIZE).reshape((BATCH_SIZE, TIME_STEPS)) / (10*np.pi)
    seq = np.sin(xs)
    res = np.cos(xs)
    BATCH_START += TIME_STEPS    
    # 顯示原始曲線
    plt.plot(xs[0, :], res[0, :], 'r', xs[0, :], seq[0, :], 'b--')
    plt.show()
    # 返回序列seq 結果res 輸入xs
    return [seq[:, :, np.newaxis], res[:, :, np.newaxis], xs]

此時的輸出結果如下圖所示,注意它只是模擬的預期曲線,還不是我們神經網路學習的結構。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

第四步,編寫LSTMRNN類,它用於定義我們的迴圈神經網路結構,初始化操作和所需變數。

初始化init()函式的引數包括:

  • n_steps表示batch中的步驟,共有3步。
  • input_size表示傳入batch data時,每個input的長度,該例項中input_size和output_size均為1。如下圖所示,假設我們batch長度為一個週期(0-6),每個input是線的x值,input size表示每個時間點有多少個值,只有一個點故為1。
  • output_size表示輸出的值,輸出對應input線的y值,其大小值為1。
  • cell_size表示RNN Cell的個數,其值為10。
  • batch_size表示一次性傳給神經網路的batch數量,設定為50。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

該部分程式碼如下,注意xs和ys的形狀。同時,我們需要使用Tensorboard視覺化RNN的結構,所以呼叫tf.name_scope()設定各神經層和變數的名稱空間名稱,詳見第五篇文章

#----------------------------------定義引數----------------------------------
BATCH_START = 0
TIME_STEPS = 20
BATCH_SIZE = 50          # BATCH數量
INPUT_SIZE = 1           # 輸入一個值
OUTPUT_SIZE = 1          # 輸出一個值
CELL_SIZE = 10           # Cell數量
LR = 0.006
BATCH_START_TEST = 0

#----------------------------------LSTM RNN----------------------------------
class LSTMRNN(object):
    # 初始化操作
    def __init__(self, n_steps, input_size, output_size, cell_size, batch_size):
        self.n_steps = n_steps
        self.input_size = input_size
        self.output_size = output_size
        self.cell_size = cell_size
        self.batch_size = batch_size
 
        # TensorBoard視覺化操作使用name_scope
        with tf.name_scope('inputs'):         #輸出變數
            self.xs = tf.placeholder(tf.float32, [None, n_steps, input_size], name='xs')
            self.ys = tf.placeholder(tf.float32, [None, n_steps, output_size], name='ys')
        with tf.variable_scope('in_hidden'):  #輸入層
            self.add_input_layer()
        with tf.variable_scope('LSTM_cell'):  #處理層
            self.add_cell()
        with tf.variable_scope('out_hidden'): #輸出層
            self.add_output_layer()
        with tf.name_scope('cost'):           #誤差
            self.compute_cost()
        with tf.name_scope('train'):          #訓練
            self.train_op = tf.train.AdamOptimizer(LR).minimize(self.cost)

第五步,接著開始編寫三個函式(三層神經網路),它是RNN的核心結構。

# 輸入層
def add_input_layer(self,):
    pass
# cell層
def add_cell(self):
    pass
# 輸出層
def add_output_layer(self):
    pass

這三個函式也是增加在LSTMRNN的Class中,核心程式碼及詳細註釋如下所示:

#--------------------------------定義核心三層結構-----------------------------
# 輸入層
def add_input_layer(self,):
    # 定義輸入層xs變數 將xs三維資料轉換成二維
    # [None, n_steps, input_size] => (batch*n_step, in_size)
    l_in_x = tf.reshape(self.xs, [-1, self.input_size], name='2_2D')
    # 定義輸入權重 (in_size, cell_size)
    Ws_in = self._weight_variable([self.input_size, self.cell_size])
    # 定義輸入偏置 (cell_size, )
    bs_in = self._bias_variable([self.cell_size,])
    # 定義輸出y變數 二維形狀 (batch * n_steps, cell_size)
    with tf.name_scope('Wx_plus_b'):
        l_in_y = tf.matmul(l_in_x, Ws_in) + bs_in
    # 返回結果形狀轉變為三維
    # l_in_y ==> (batch, n_steps, cell_size)
    self.l_in_y = tf.reshape(l_in_y, [-1, self.n_steps, self.cell_size], name='2_3D')
 
# cell層
def add_cell(self):
    # 選擇BasicLSTMCell模型
    # forget初始偏置為1.0(初始時不希望forget) 隨著訓練深入LSTM會選擇性忘記
    lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(self.cell_size, forget_bias=1.0, state_is_tuple=True)
    # 設定initial_state全為0 視覺化操作用name_scope
    with tf.name_scope('initial_state'):
        self.cell_init_state = lstm_cell.zero_state(self.batch_size, dtype=tf.float32)
    # RNN迴圈 每一步的輸出都儲存在cell_outputs序列中 cell_final_state為最終State並傳入下一個batch中
    # 常規RNN只有m_state LSTM包括c_state和m_state
    self.cell_outputs, self.cell_final_state = tf.nn.dynamic_rnn(
        lstm_cell, self.l_in_y, initial_state=self.cell_init_state, time_major=False)
 
# 輸出層 (類似輸入層)
def add_output_layer(self):
    # 轉換成二維 方能使用W*X+B
    # shape => (batch * steps, cell_size) 
    l_out_x = tf.reshape(self.cell_outputs, [-1, self.cell_size], name='2_2D')
    Ws_out = self._weight_variable([self.cell_size, self.output_size])
    bs_out = self._bias_variable([self.output_size, ])
    # 返回預測結果
    # shape => (batch * steps, output_size)
    with tf.name_scope('Wx_plus_b'):
        self.pred = tf.matmul(l_out_x, Ws_out) + bs_out

注意,上面呼叫了reshape()進行形狀更新,為什麼要將三維變數改成二維呢?因為只有變成二維變數之後,才能計算W*X+B。

第六步,定義計算誤差函式。

這裡需要注意:我們使用了seq2seq函式。它求出的loss是整個batch每一步的loss,然後把每一步loss進行sum求和,變成了整個TensorFlow的loss,再除以batch size平均,最終得到這個batch的總cost,它是一個scalar數字。

# 定義誤差計算函式      
def compute_cost(self):
    # 使用seq2seq序列到序列模型
    # tf.nn.seq2seq.sequence_loss_by_example()
    losses = tf.contrib.legacy_seq2seq.sequence_loss_by_example(
        [tf.reshape(self.pred, [-1], name='reshape_pred')],
        [tf.reshape(self.ys, [-1], name='reshape_target')],
        [tf.ones([self.batch_size * self.n_steps], dtype=tf.float32)],
        average_across_timesteps=True,
        softmax_loss_function=self.msr_error,
        name='losses'
    )
    # 最終得到batch的總cost 它是一個數字
    with tf.name_scope('average_cost'):
        # 整個TensorFlow的loss求和 再除以batch size
        self.cost = tf.div(
            tf.reduce_sum(losses, name='losses_sum'),
            self.batch_size,
            name='average_cost')
        tf.summary.scalar('cost', self.cost)

後面的文章我們會詳細寫一篇機器翻譯相關的內容,並使用seq2seq模型。

Seq2Seq模型是輸出的長度不確定時採用的模型,這種情況一般是在機器翻譯的任務中出現,將一句中文翻譯成英文,那麼這句英文的長度有可能會比中文短,也有可能會比中文長,所以輸出的長度就不確定了。如下圖所,輸入的中文長度為4,輸出的英文長度為2。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

在網路結構中,輸入一箇中文序列,然後輸出它對應的中文翻譯,輸出的部分的結果預測後面,根據上面的例子,也就是先輸出“machine”,將"machine"作為下一次的輸入,接著輸出"learning",這樣就能輸出任意長的序列。

機器翻譯、人機對話、聊天機器人等等,這些都是應用在當今社會都或多或少的運用到了我們這裡所說的Seq2Seq。

第七步,定義msr_error計算函式、誤差計算函式和偏置計算函式。

# 該函式用於計算
# 相當於msr_error(self, y_pre, y_target) return tf.square(tf.sub(y_pre, y_target))
def msr_error(self, logits, labels):
    return tf.square(tf.subtract(logits, labels))
# 誤差計算
def _weight_variable(self, shape, name='weights'):
    initializer = tf.random_normal_initializer(mean=0., stddev=1.,)
    return tf.get_variable(shape=shape, initializer=initializer, name=name)
# 偏置計算
def _bias_variable(self, shape, name='biases'):
    initializer = tf.constant_initializer(0.1)
    return tf.get_variable(name=name, shape=shape, initializer=initializer)

寫到這裡,整個Class就定義完成。

第八步,接下來定義主函式,進行訓練和預測操作,這裡先嚐試TensorBoard視覺化展現。

#----------------------------------主函式 訓練和預測----------------------------------     
if __name__ == '__main__':
    # 定義模型並初始化
    model = LSTMRNN(TIME_STEPS, INPUT_SIZE, OUTPUT_SIZE, CELL_SIZE, BATCH_SIZE)
    sess = tf.Session()
    merged = tf.summary.merge_all()
    writer = tf.summary.FileWriter("logs", sess.graph)
    sess.run(tf.initialize_all_variables())

四.完整程式碼及視覺化展示

該階段的完整程式碼如下,我們先嚐試執行下程式碼:

# -*- coding: utf-8 -*-
"""
Created on Thu Jan  9 20:44:56 2020
@author: xiuzhang Eastmount CSDN
"""
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

#----------------------------------定義引數----------------------------------
BATCH_START = 0
TIME_STEPS = 20
BATCH_SIZE = 50          # BATCH數量
INPUT_SIZE = 1           # 輸入一個值
OUTPUT_SIZE = 1          # 輸出一個值
CELL_SIZE = 10           # Cell數量
LR = 0.006
BATCH_START_TEST = 0

# 獲取批量資料
def get_batch():
    global BATCH_START, TIME_STEPS
    # xs shape (50batch, 20steps)
    xs = np.arange(BATCH_START, BATCH_START+TIME_STEPS*BATCH_SIZE).reshape((BATCH_SIZE, TIME_STEPS)) / (10*np.pi)
    seq = np.sin(xs)
    res = np.cos(xs)
    BATCH_START += TIME_STEPS    
    # 返回序列seq 結果res 輸入xs
    return [seq[:, :, np.newaxis], res[:, :, np.newaxis], xs]

#----------------------------------LSTM RNN----------------------------------
class LSTMRNN(object):
    # 初始化操作
    def __init__(self, n_steps, input_size, output_size, cell_size, batch_size):
        self.n_steps = n_steps
        self.input_size = input_size
        self.output_size = output_size
        self.cell_size = cell_size
        self.batch_size = batch_size
 
        # TensorBoard視覺化操作使用name_scope
        with tf.name_scope('inputs'):         #輸出變數
            self.xs = tf.placeholder(tf.float32, [None, n_steps, input_size], name='xs')
            self.ys = tf.placeholder(tf.float32, [None, n_steps, output_size], name='ys')
        with tf.variable_scope('in_hidden'):  #輸入層
            self.add_input_layer()
        with tf.variable_scope('LSTM_cell'):  #處理層
            self.add_cell()
        with tf.variable_scope('out_hidden'): #輸出層
            self.add_output_layer()
        with tf.name_scope('cost'):           #誤差
            self.compute_cost()
        with tf.name_scope('train'):          #訓練
            self.train_op = tf.train.AdamOptimizer(LR).minimize(self.cost)
 
    #--------------------------------定義核心三層結構-----------------------------
    # 輸入層
    def add_input_layer(self,):
        # 定義輸入層xs變數 將xs三維資料轉換成二維
        # [None, n_steps, input_size] => (batch*n_step, in_size)
        l_in_x = tf.reshape(self.xs, [-1, self.input_size], name='2_2D')
        # 定義輸入權重 (in_size, cell_size)
        Ws_in = self._weight_variable([self.input_size, self.cell_size])
        # 定義輸入偏置 (cell_size, )
        bs_in = self._bias_variable([self.cell_size,])
        # 定義輸出y變數 二維形狀 (batch * n_steps, cell_size)
        with tf.name_scope('Wx_plus_b'):
            l_in_y = tf.matmul(l_in_x, Ws_in) + bs_in
        # 返回結果形狀轉變為三維
        # l_in_y ==> (batch, n_steps, cell_size)
        self.l_in_y = tf.reshape(l_in_y, [-1, self.n_steps, self.cell_size], name='2_3D')
 
    # cell層
    def add_cell(self):
        # 選擇BasicLSTMCell模型
        # forget初始偏置為1.0(初始時不希望forget) 隨著訓練深入LSTM會選擇性忘記
        lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(self.cell_size, forget_bias=1.0, state_is_tuple=True)
        # 設定initial_state全為0 視覺化操作用name_scope
        with tf.name_scope('initial_state'):
            self.cell_init_state = lstm_cell.zero_state(self.batch_size, dtype=tf.float32)
        # RNN迴圈 每一步的輸出都儲存在cell_outputs序列中 cell_final_state為最終State並傳入下一個batch中
        # 常規RNN只有m_state LSTM包括c_state和m_state
        self.cell_outputs, self.cell_final_state = tf.nn.dynamic_rnn(
            lstm_cell, self.l_in_y, initial_state=self.cell_init_state, time_major=False)
 
    # 輸出層 (類似輸入層)
    def add_output_layer(self):
        # 轉換成二維 方能使用W*X+B
        # shape => (batch * steps, cell_size) 
        l_out_x = tf.reshape(self.cell_outputs, [-1, self.cell_size], name='2_2D')
        Ws_out = self._weight_variable([self.cell_size, self.output_size])
        bs_out = self._bias_variable([self.output_size, ])
        # 返回預測結果
        # shape => (batch * steps, output_size)
        with tf.name_scope('Wx_plus_b'):
            self.pred = tf.matmul(l_out_x, Ws_out) + bs_out
 
    #--------------------------------定義誤差計算函式-----------------------------     
    def compute_cost(self):
        # 使用seq2seq序列到序列模型
        # tf.nn.seq2seq.sequence_loss_by_example()
        losses = tf.contrib.legacy_seq2seq.sequence_loss_by_example(
            [tf.reshape(self.pred, [-1], name='reshape_pred')],
            [tf.reshape(self.ys, [-1], name='reshape_target')],
            [tf.ones([self.batch_size * self.n_steps], dtype=tf.float32)],
            average_across_timesteps=True,
            softmax_loss_function=self.msr_error,
            name='losses'
        )
        # 最終得到batch的總cost 它是一個數字
        with tf.name_scope('average_cost'):
            # 整個TensorFlow的loss求和 再除以batch size
            self.cost = tf.div(
                tf.reduce_sum(losses, name='losses_sum'),
                self.batch_size,
                name='average_cost')
            tf.summary.scalar('cost', self.cost)
 
    # 該函式用於計算
    # 相當於msr_error(self, y_pre, y_target) return tf.square(tf.sub(y_pre, y_target))
    def msr_error(self, logits, labels):
        return tf.square(tf.subtract(logits, labels))
    # 誤差計算
    def _weight_variable(self, shape, name='weights'):
        initializer = tf.random_normal_initializer(mean=0., stddev=1.,)
        return tf.get_variable(shape=shape, initializer=initializer, name=name)
    # 偏置計算
    def _bias_variable(self, shape, name='biases'):
        initializer = tf.constant_initializer(0.1)
        return tf.get_variable(name=name, shape=shape, initializer=initializer)
 
#----------------------------------主函式 訓練和預測----------------------------------     
if __name__ == '__main__':
    # 定義模型並初始化
    model = LSTMRNN(TIME_STEPS, INPUT_SIZE, OUTPUT_SIZE, CELL_SIZE, BATCH_SIZE)
    sess = tf.Session()
    merged = tf.summary.merge_all()
    writer = tf.summary.FileWriter("logs", sess.graph)
    sess.run(tf.initialize_all_variables())

此時會在Python檔案目錄下新建一個“logs”資料夾和events的檔案,如下圖所示。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

接下來嘗試開啟它。首先調出Anaconda Prompt,並啟用TensorFlow,接著去到events檔案的目錄,呼叫命令“tensorboard --logdir=logs執行即可,如下圖所示。注意,這裡只需要指引到資料夾,它就會自動索引到你的檔案。

activate tensorflow
cd\
cd C:\Users\xiuzhang\Desktop\TensorFlow\blog
tensorboard --logdir=logs

迴圈神經網路LSTM RNN迴歸:sin曲線預測

此時訪問網址“http://localhost:6006/”,選擇“Graphs”,執行之後如下圖所示,我們的神經網路就出現了。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

神經網路結構如下圖所示,包括輸入層、LSTM層、輸出層、cost誤差計算、train訓練等。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

詳細結構如下圖所示:

迴圈神經網路LSTM RNN迴歸:sin曲線預測

通常我們會將train部分放置一邊,選中“train”然後滑鼠右鍵點選“Remove from main graph”。核心結構如下,in_hidden是接受輸入的第一層,之後是LSTM_cell,最後是輸出層out_hidden。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

  • in_hidden: 包括了權重Weights和biases,計算公式Wx_plus_b。同時,它包括了reshape操作,2_2D和2_3D。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

  • out_hidden: 包括了權重weights、偏置biases、計算公式Wx_plus_b、二維資料2_2D,並且輸出結果為cost。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

  • cost: 計算誤差。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

  • 中間是LSTM_cell: 包括RNN迴圈神經網路,初始化initial_state,之後會被state更新替換。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

注意版本問題,讀者可以結合自己的TensorFlow版本進行適當修改執行。作者版本版本資訊為:Python3.6、Anaconda3、Win10、Tensorflow1.15.0。

如果您報錯 AttributeError: module ‘tensorflow._api.v1.nn’ has no attribute ‘seq2seq’,這是TensorFlow 版本升級,方法呼叫更改。解決方式:

迴圈神經網路LSTM RNN迴歸:sin曲線預測

如果您報錯 TypeError: msr_error() got an unexpected keyword argument ‘labels’,msr_error() 函式得到一個意外的關鍵引數 ‘lables’。其解決方式:定義msr_error() 函式時,使用 labels,logits 指定,將

def msr_error(self, y_pre, y_target):
    return tf.square(tf.subtract(y_pre, y_target))

改為:

def msr_error(self, logits, labels):
    return tf.square(tf.subtract(logits, labels))

如果您報錯 ValueError: Variable in_hidden/weights already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? ,則重新啟動kernel即可執行。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

五.預測及曲線擬合

最後,我們在主函式中編寫RNN訓練學習和預測的程式碼。

首先我們來測試cost學習的結果。程式碼如下,if判斷中cell_init_state為前面已初始化的state,之後更新state(model.cell_init_state: state ),其實就是將Final State換成下一個batch的Initial State,從而符合我們定義的結構。

迴圈神經網路LSTM RNN迴歸:sin曲線預測

#----------------------------------主函式 訓練和預測----------------------------------     
if __name__ == '__main__':
    # 定義模型並初始化
    model = LSTMRNN(TIME_STEPS, INPUT_SIZE, OUTPUT_SIZE, CELL_SIZE, BATCH_SIZE)
    sess = tf.Session()
    merged = tf.summary.merge_all()
    writer = tf.summary.FileWriter("logs", sess.graph)
    sess.run(tf.initialize_all_variables())
    # Tensorboard視覺化展現神經網路結果
 
    #------------------------------RNN學習-------------------------------------    
    # 訓練模型 
    for i in range(200):
        # 用seq預測res (序列-seq 結果-res 輸入-xs)
        seq, res, xs = get_batch()
        # 第一步賦值 之後會更新cell_init_state
        if i == 0:
            feed_dict = {
                model.xs: seq,
                model.ys: res,
                # create initial state (前面cell_init_state已初始化state)
            }
        else:
            feed_dict = {
                model.xs: seq,
                model.ys: res,
                model.cell_init_state: state    
                # use last state as the initial state for this run
            }
 
        # state為final_state 
        _, cost, state, pred = sess.run(
                [model.train_op, model.cost, model.cell_final_state, model.pred], 
                feed_dict=feed_dict)
 
        # 每隔20步輸出結果
        if i % 20 == 0:
            print('cost: ', round(cost, 4))

每隔20步輸出結果,如下所示,誤差從最初的33到最後的0.335,神經網路在不斷學習,誤差在不斷減小。

cost:  33.1673
cost:  9.1332
cost:  3.8899
cost:  1.3271
cost:  0.2682
cost:  0.4912
cost:  1.0692
cost:  0.3812
cost:  0.63
cost:  0.335

接下來增加matplotlib視覺化的sin曲線動態擬合過程,最終完整程式碼如下所示:

# -*- coding: utf-8 -*-
"""
Created on Thu Jan  9 20:44:56 2020
@author: xiuzhang Eastmount CSDN
"""
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

#----------------------------------定義引數----------------------------------
BATCH_START = 0
TIME_STEPS = 20
BATCH_SIZE = 50          # BATCH數量
INPUT_SIZE = 1           # 輸入一個值
OUTPUT_SIZE = 1          # 輸出一個值
CELL_SIZE = 10           # Cell數量
LR = 0.006
BATCH_START_TEST = 0

# 獲取批量資料
def get_batch():
    global BATCH_START, TIME_STEPS
    # xs shape (50batch, 20steps)
    xs = np.arange(BATCH_START, BATCH_START+TIME_STEPS*BATCH_SIZE).reshape((BATCH_SIZE, TIME_STEPS)) / (10*np.pi)
    seq = np.sin(xs)
    res = np.cos(xs)
    BATCH_START += TIME_STEPS    
 
    # 顯示原始曲線
    # plt.plot(xs[0, :], res[0, :], 'r', xs[0, :], seq[0, :], 'b--')
    # plt.show()
 
    # 返回序列seq 結果res 輸入xs
    return [seq[:, :, np.newaxis], res[:, :, np.newaxis], xs]

#----------------------------------LSTM RNN----------------------------------
class LSTMRNN(object):
    # 初始化操作
    def __init__(self, n_steps, input_size, output_size, cell_size, batch_size):
        self.n_steps = n_steps
        self.input_size = input_size
        self.output_size = output_size
        self.cell_size = cell_size
        self.batch_size = batch_size
 
        # TensorBoard視覺化操作使用name_scope
        with tf.name_scope('inputs'):         #輸出變數
            self.xs = tf.placeholder(tf.float32, [None, n_steps, input_size], name='xs')
            self.ys = tf.placeholder(tf.float32, [None, n_steps, output_size], name='ys')
        with tf.variable_scope('in_hidden'):  #輸入層
            self.add_input_layer()
        with tf.variable_scope('LSTM_cell'):  #處理層
            self.add_cell()
        with tf.variable_scope('out_hidden'): #輸出層
            self.add_output_layer()
        with tf.name_scope('cost'):           #誤差
            self.compute_cost()
        with tf.name_scope('train'):          #訓練
            self.train_op = tf.train.AdamOptimizer(LR).minimize(self.cost)
 
    #--------------------------------定義核心三層結構-----------------------------
    # 輸入層
    def add_input_layer(self,):
        # 定義輸入層xs變數 將xs三維資料轉換成二維
        # [None, n_steps, input_size] => (batch*n_step, in_size)
        l_in_x = tf.reshape(self.xs, [-1, self.input_size], name='2_2D')
        # 定義輸入權重 (in_size, cell_size)
        Ws_in = self._weight_variable([self.input_size, self.cell_size])
        # 定義輸入偏置 (cell_size, )
        bs_in = self._bias_variable([self.cell_size,])
        # 定義輸出y變數 二維形狀 (batch * n_steps, cell_size)
        with tf.name_scope('Wx_plus_b'):
            l_in_y = tf.matmul(l_in_x, Ws_in) + bs_in
        # 返回結果形狀轉變為三維
        # l_in_y ==> (batch, n_steps, cell_size)
        self.l_in_y = tf.reshape(l_in_y, [-1, self.n_steps, self.cell_size], name='2_3D')
 
    # cell層
    def add_cell(self):
        # 選擇BasicLSTMCell模型
        # forget初始偏置為1.0(初始時不希望forget) 隨著訓練深入LSTM會選擇性忘記
        lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(self.cell_size, forget_bias=1.0, state_is_tuple=True)
        # 設定initial_state全為0 視覺化操作用name_scope
        with tf.name_scope('initial_state'):
            self.cell_init_state = lstm_cell.zero_state(self.batch_size, dtype=tf.float32)
        # RNN迴圈 每一步的輸出都儲存在cell_outputs序列中 cell_final_state為最終State並傳入下一個batch中
        # 常規RNN只有m_state LSTM包括c_state和m_state
        self.cell_outputs, self.cell_final_state = tf.nn.dynamic_rnn(
            lstm_cell, self.l_in_y, initial_state=self.cell_init_state, time_major=False)
 
    # 輸出層 (類似輸入層)
    def add_output_layer(self):
        # 轉換成二維 方能使用W*X+B
        # shape => (batch * steps, cell_size) 
        l_out_x = tf.reshape(self.cell_outputs, [-1, self.cell_size], name='2_2D')
        Ws_out = self._weight_variable([self.cell_size, self.output_size])
        bs_out = self._bias_variable([self.output_size, ])
        # 返回預測結果
        # shape => (batch * steps, output_size)
        with tf.name_scope('Wx_plus_b'):
            self.pred = tf.matmul(l_out_x, Ws_out) + bs_out
 
    #--------------------------------定義誤差計算函式-----------------------------     
    def compute_cost(self):
        # 使用seq2seq序列到序列模型
        # tf.nn.seq2seq.sequence_loss_by_example()
        losses = tf.contrib.legacy_seq2seq.sequence_loss_by_example(
            [tf.reshape(self.pred, [-1], name='reshape_pred')],
            [tf.reshape(self.ys, [-1], name='reshape_target')],
            [tf.ones([self.batch_size * self.n_steps], dtype=tf.float32)],
            average_across_timesteps=True,
            softmax_loss_function=self.msr_error,
            name='losses'
        )
        # 最終得到batch的總cost 它是一個數字
        with tf.name_scope('average_cost'):
            # 整個TensorFlow的loss求和 再除以batch size
            self.cost = tf.div(
                tf.reduce_sum(losses, name='losses_sum'),
                self.batch_size,
                name='average_cost')
            tf.summary.scalar('cost', self.cost)
 
    # 該函式用於計算
    # 相當於msr_error(self, y_pre, y_target) return tf.square(tf.sub(y_pre, y_target))
    def msr_error(self, logits, labels):
        return tf.square(tf.subtract(logits, labels))
    # 誤差計算
    def _weight_variable(self, shape, name='weights'):
        initializer = tf.random_normal_initializer(mean=0., stddev=1.,)
        return tf.get_variable(shape=shape, initializer=initializer, name=name)
    # 偏置計算
    def _bias_variable(self, shape, name='biases'):
        initializer = tf.constant_initializer(0.1)
        return tf.get_variable(name=name, shape=shape, initializer=initializer)
 
#----------------------------------主函式 訓練和預測----------------------------------     
if __name__ == '__main__':
    # 定義模型並初始化
    model = LSTMRNN(TIME_STEPS, INPUT_SIZE, OUTPUT_SIZE, CELL_SIZE, BATCH_SIZE)
    sess = tf.Session()
    merged = tf.summary.merge_all()
    writer = tf.summary.FileWriter("logs", sess.graph)
    sess.run(tf.initialize_all_variables())
    # Tensorboard視覺化展現神經網路結果
 
    #------------------------------RNN學習-------------------------------------     
    # 互動模式啟動
    plt.ion()
    plt.show()
 
    # 訓練模型 
    for i in range(200):
        # 用seq預測res (序列-seq 結果-res 輸入-xs)
        seq, res, xs = get_batch()
        # 第一步賦值 之後會更新cell_init_state
        if i == 0:
            feed_dict = {
                model.xs: seq,
                model.ys: res,
                # create initial state (前面cell_init_state已初始化state)
            }
        else:
            feed_dict = {
                model.xs: seq,
                model.ys: res,
                model.cell_init_state: state    
                # use last state as the initial state for this run
            }
 
        # state為final_state 
        _, cost, state, pred = sess.run(
                [model.train_op, model.cost, model.cell_final_state, model.pred], 
                feed_dict=feed_dict)

        # plotting
        # 獲取第一批資料xs[0,:] 獲取0到20區間的預測資料pred.flatten()[:TIME_STEPS]
        plt.plot(xs[0, :], res[0].flatten(), 'r', xs[0, :], pred.flatten()[:TIME_STEPS], 'b--')
        plt.ylim((-1.2, 1.2))
        plt.draw()
        plt.pause(0.3)
 
        # 每隔20步輸出結果
        if i % 20 == 0:
            print('cost: ', round(cost, 4))
            # result = sess.run(merged, feed_dict)
            # writer.add_summary(result, i)

寫道這裡,這篇文章終於寫完了。文章非常長,但希望對您有所幫助。LSTM RNN通過一組資料預測另一組資料。預測效果如下圖所示,紅色的實線表示需要預測的線,藍色的虛線表示RNN學習的線,它們在不斷地逼近,藍線學到了紅線的規律,最終將藍線基本擬合到紅線上。

在這裡插入圖片描述

六.總結

本文介紹完了,更多TensorFlow深度學習文章會繼續分享,接下來我們會分享監督學習、GAN、機器翻譯、文字識別、影像識別、語音識別等內容。如果讀者有什麼想學習的,也可以私聊我,我去學習並應用到你的領域。

最後,希望這篇基礎性文章對您有所幫助,如果文章中存在錯誤或不足之處,還請海涵~作為人工智慧的菜鳥,我希望自己能不斷進步並深入,後續將它應用於影像識別、網路安全、對抗樣本等領域,指導大家撰寫簡單的學術論文,一起加油!

程式碼下載地址(歡迎大家關注點贊):

 

點選關注,第一時間瞭解華為雲新鮮技術~