TensorFlow筆記(六)循環神經網絡

第六章 循環神經網絡

本節主要內容:講解卷積神經網絡,利用基礎CNN、LeNet、AlexNet、VGGNet、InceptionNet和ResNet實現圖像識別。

本節目標:學習循環神經網絡,用 RNN、LSTM、GRU實現連續數據的預測(以股票預測爲例)

1 卷積神經網絡與循環神經網絡簡單對比

CNN: 藉助卷積核(kernel)提取特徵後,送入後續網絡(如全連接網絡 Dense)進行分類、目標檢測等操作。CNN 藉助卷積核從空間維度提取信息,卷積核參數

空間共享。

卷積是什麼? 卷積就是特徵提取器,就是CBAPD

RNN: 藉助循環核(cell)提取特徵後,送入後續網絡(如全連接網絡 Dense)進行預測等操作。RNN 藉助循環核從時間維度提取信息,循環核參數時間共享。

2 詳解 RNN

2.1 RNN 循環核

image-20220424195203824

圖2.1 RNN循環核

循環核具有記憶力,通過不同時刻的參數共享,實現了對時間序列的信息提取。每個循環核有多個記憶體,對應圖 2.1 中的多個小圓柱。記憶體內存儲着每個時刻的狀態信息\(h_t\),這裏\(h_t = tanh(x_t w_{xh} + h_{t-1}w_{hh} + bh)\)。其中,\(w_{xh}\)\(w_{hh}\)爲權重矩陣,\(bh\)爲偏置,\(x_t\)爲當前時刻的輸入特徵,\(h_{t-1}\)爲記憶體上一時刻存儲的狀態信息,tanh 爲激活函數。

當前時刻循環核的輸出特徵$y_t =softmax(h_tw_{hy} + by) \(,其中\)w_{hy}\(爲權重矩陣、\)by\(爲偏置、softmax 爲激活函數,其實就相當於一層全連接層。我們可以設定記憶體的個數從而改變記憶容量,當記憶體個數被指定、輸入\)x_t\(輸出\)y_t\(維度被指定,周圍這些待訓練參數的維度也就被限定了。在前向傳播時,記憶體內存儲的狀態信息\)h_t\(在每個時刻都被刷新,而三個參數矩陣\)w_{xh}、w_{hh}、w_{hy}$ 和兩個偏置項\(bh、by\)自始至終都是固定不變的。在反向傳播時,三個參數矩陣和兩個偏置項由梯度下降法更新。

2.2 循環核按時間步展開

將循環核按時間步展開,就是把循環核按照時間軸方向展開,可以得到如圖2.2的形式。每個時刻記憶體狀態信息\(h_t\)被刷新,記憶體周圍的參數矩陣和兩個偏置項是固定不變的,我們訓練優化的就是這些參數矩陣。訓練完成後,使用效果最好的參數矩陣執行前向傳播,然後輸出預測結果。其實這和我們人類的預測是一致的:我們腦中的記憶體每個時刻都根據當前的輸入而更新;當前的預測推理是根據我們以往的知識積累用固化下來的“參數矩陣”進行的推理判斷。

可以看出,循環神經網絡就是藉助循環覈實現時間特徵提取後把提取到的信息送入全連接網絡,從而實現連續數據的預測。

image-20220424200532560

圖2.2 RNN循環核按時間步展開

2.3 循環計算層:向輸出方向生長

在RNN中,每個循環核構成一層循環計算層,循環計算層的層數是向輸出方向增長的。如圖2.3所示,左圖的網絡有一個循環核,構成了一層循環計算層;中圖的網絡有兩個循環核,構成了兩層循環計算層;右圖的網絡有三個循環核,構成了三層循環計算層。其中,三個網絡中每個循環核中記憶體的個數可以根據我們的需求任意指定。

image-20220424200749399

圖2.3 循環計算層

2.4 RNN訓練

得到RNN的前向傳播結果之後,和其他神經網絡類似,我們會定義損失函數,使用反向傳播梯度下降算法訓練模型。RNN唯一的區別在於:由於它每個時刻的節點都可能有一個輸出,所以RNN的總損失爲所有時刻(或部分時刻)上的損失和。

2.5 Tensorflow2描述循環計算層

tf.keras.layers.SimpleRNN(神經元個數,activation=‘激活函數’,return_sequences=是否每個時刻輸出ℎ𝑡到下一層)

(1)神經元個數:即循環核中記憶體的個數
(2) return_sequences:在輸出序列中,返回最後時間步的輸出值\(h_t\)還是返回全部時間步的輸出。False返回最後時刻(圖2.5),True返回全部時刻(圖2.4)。當下一層依然是RNN層,通常爲True,反之如果後面是Dense層,通常爲Fasle。

image-20220424201216404

圖2.4 return_sequences = True

image-20220424201246277

圖2.5 return_sequences = False

(3)輸入維度:三維張量(輸入樣本數, 循環核時間展開步數, 每個時間步輸入特徵個數)。

image-20220424201435118

圖2.6 RNN層輸入維度

如圖2.6所示,左圖一共要送入RNN層兩組數據,每組數據經過一個時間步就會得到輸出結果,每個時間步送入三個數值,則輸入循環層的數據維度就是[2, 1, 3];右圖輸入只有一組數據,分四個時間步送入循環層,每個時間步送入兩個數值 ,則輸入循環層的數據維度就是 [1,4, 2]。

(4)輸出維度:當return_sequenc=True,三維張量(輸入樣本數, 循環核時間展開步數,本層的神經元個數);當return_sequenc=False,二維張量(輸入樣本數,本層的神經元個數)

(5) activation:‘激活函數’(不寫默認使用tanh)

例:SimpleRNN(3, return_sequences=True),定義了一個具有三個記憶體的循環核,這個循環核會在每個時間步輸出\(h_t\)

3 循環計算過程

3.1 1pre1

RNN最典型的應用就是利用歷史數據預測下一時刻將發生什麼,即根據以前見過的歷史規律做預測。舉一個簡單的字母預測例子體會一下循環網絡的計算過程:輸入一個字母預測下一個字母---輸入a預測出b、輸入b預測出c、輸入c預測出d、輸入d預測出e、輸入e預測出a。計算機不認識字母,只能處理數字。所以需要我們對字母進行編碼。這裏假設使用獨熱編碼(實際中可使用其他編碼方式),編碼結果如圖2.7所示。

image-20220424202432373

圖2.7 字母獨熱編碼

假設使用一層RNN網絡,記憶體的個數選取3,則字母預測的網絡如圖1.2.8所示。假設輸入字母b,即輸入\(x_t\)爲[0,1,0,0,0],這時上一時刻的記憶體狀態信息\(h_{t-1}\)爲0。由上文理論知識不難得到:

\(h_t = tanh(x_t w_{xh} + h_{t-1}w_{hh} + bh)\)=tanh([−2.3 0.8 1.1 ]+ 0 + [ 0.5 0.3 −0.2])= tanh[−1.8 1.1 0.9 ] = [−0.9 0.8 0.7] ,這個過程可以理解爲腦中的記憶因爲當前輸入的事物而更新了。

輸出\(y_t\)是把提取到的時間信息通過全連接進行識別預測的過程,是整個網絡的輸出層。不難知道,\(y_t =softmax(h_tw_{hy} + by)\)= softmax([−0.7 −0.6 2.9 0.7 −0.8] + [ 0.0 0.1 0.4 −0.7 0.1])= softmax([−0.7 −0.5 3.3 0.0 −0.7])=[0.02 0.02 𝟎𝟎.𝟗𝟗𝟗𝟗 0.03 0.02 ]。可見模型認爲有91%的可能性輸出字母c ,所以循環網絡輸出了預測結果 c。

image-20220424203238281

獨熱編碼實踐字母預測

按照六步法八股套路進行編碼:

import numpy as np
import tensorflow as tf
from tensorflow.keras.layers import Dense, SimpleRNN
import matplotlib.pyplot as plt
import os

input_word = "abcde"
w_to_id = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4}  # 單詞映射到數值id的詞典
id_to_onehot = {0: [1., 0., 0., 0., 0.], 1: [0., 1., 0., 0., 0.], 2: [0., 0., 1., 0., 0.], 3: [0., 0., 0., 1., 0.],4: [0., 0., 0., 0., 1.]}  # id編碼爲one-hot

x_train = [id_to_onehot[w_to_id['a']], id_to_onehot[w_to_id['b']], id_to_onehot[w_to_id['c']],
           id_to_onehot[w_to_id['d']], id_to_onehot[w_to_id['e']]]
y_train = [w_to_id['b'], w_to_id['c'], w_to_id['d'], w_to_id['e'], w_to_id['a']]

np.random.seed(7)
np.random.shuffle(x_train)
np.random.seed(7)
np.random.shuffle(y_train)
tf.random.set_seed(7)

# 使x_train符合SimpleRNN輸入要求:[送入樣本數, 循環核時間展開步數, 每個時間步輸入特徵個數]。
# 此處整個數據集送入,送入樣本數爲len(x_train);輸入1個字母出結果,循環核時間展開步數爲1; 表示爲獨熱碼有5個輸入特徵,每個時間步輸入特徵個數爲5
x_train = np.reshape(x_train, (len(x_train), 1, 5))
y_train = np.array(y_train)

import相關模塊->生成訓練用的輸入特徵x_train和標籤y_train(輸入特徵a對應的標籤是b、輸入特徵b對應的標籤是c、依次類推),打亂順序後變形成RNN輸入需要的維度。

model = tf.keras.Sequential([
    SimpleRNN(3),
    Dense(5, activation='softmax')
])

model.compile(optimizer=tf.keras.optimizers.Adam(0.01),
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['sparse_categorical_accuracy'])

checkpoint_save_path = "./checkpoint/rnn_onehot_1pre1.ckpt"

if os.path.exists(checkpoint_save_path + '.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_weights_only=True,
                                                 save_best_only=True,
                                                 monitor='loss') 
# 由於fit沒有給出測試集,不計算測試集準確率,根據loss,保存最優模型

history = model.fit(x_train, y_train, batch_size=32, epochs=100, callbacks=[cp_callback])

model.summary()

構建模型:一個具有3個記憶體的循環層+一層全連接->Compile->fit->summary。

file = open('./weights.txt', 'w')  # 參數提取
for v in model.trainable_variables:
    file.write(str(v.name) + '\n')
    file.write(str(v.shape) + '\n')
    file.write(str(v.numpy()) + '\n')
file.close()

###############################################    show   ###############################################

# 顯示訓練集和驗證集的acc和loss曲線
acc = history.history['sparse_categorical_accuracy']
loss = history.history['loss']

plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.title('Training Accuracy')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.title('Training Loss')
plt.legend()
plt.show()

提取參數和 acc、loss可視化。

############### predict #############

preNum = int(input("input the number of test alphabet:"))
for i in range(preNum):
    alphabet1 = input("input test alphabet:")
    alphabet = [id_to_onehot[w_to_id[alphabet1]]]
    # 使alphabet符合SimpleRNN輸入要求:[送入樣本數, 循環核時間展開步數, 每個時間步輸入特徵個數]。此處驗證效果送入了1個樣本,送入樣本數爲1;輸入1個字母出結果,所以循環核時間展開步數爲1; 表示爲獨熱碼有5個輸入特徵,每個時間步輸入特徵個數爲5
    alphabet = np.reshape(alphabet, (1, 1, 5))
    result = model.predict([alphabet])
    pred = tf.argmax(result, axis=1)
    pred = int(pred)
    tf.print(alphabet1 + '->' + input_word[pred])

所示爲展示預測效果的應用程序,將其寫到了這段代碼的最後:首先輸入要執行幾次預測任務;隨後等待輸入一個字母,將這個字母轉換爲獨熱碼形式後調整爲RNN層希望的形狀;然後通過predict得到預測結果,選出預測結果中最大的一個即爲預測結果。運行結果如圖2.13所示。

image-20220424205929510

Embedding編碼實踐字母預測

爲什麼使用Embedding?

  • 獨熱碼:數據量大、過於稀疏,映射之間是獨立的,沒有表現出關聯性。
  • Embedding:是一種單詞編碼方法,用低維向量實現了編碼。這種編碼通過神經網絡訓練優化,能表達出單詞間的相關性。

Tensorflow2中的詞向量空間編碼層:

tf.keras.layers.Embedding(詞彙表大小,編碼維度)
  • 詞彙表大小:編碼一共要表示多少個單詞;
  • 編碼維度:用幾個數字表達一個單詞;

輸入維度:二維張量[送入樣本數,循環核時間展開步數]
輸出維度:三維張量[送入樣本數,循環核時間展開步數,編碼維度]

例 :tf.keras.layers.Embedding(100, 3)。對數字1-100進行編碼,詞彙表大小就是100 ;每個自然數用三個數字表示,編碼維度就是3; 所以Embedding層的參數是100和3。比如數字[4] embedding爲 [0.25, 0.1, 0.11]。

Embedding實現1pre1

如圖2.14所示,淺藍色框框住的區域爲與獨熱編碼不同的地方。不同是因爲需要把輸入特徵變Embedding層期待的形狀:第一個維度是送入樣本數、第二個維度是循環核時間展開步數。

image-20220424210456797

圖2.14 p27_rnn_embedding_1pre1.py(1)

如圖2.15所示,在模型部分相比於獨熱編碼形式多了一個Embedding層對輸入數據進行編碼,這一層會生成一個五行兩列的可訓練參數矩陣,實現編碼可訓練。

image-20220424210648550

圖2.15 p27_rnn_embedding_1pre1.py(2)

參數提取和acc/loss可視化和p15_rnn_onehot_1pre1.py代碼完全一樣。在結果預測時,如圖2.16所示,只需要將讀到的輸入字母直接查找表示它的ID值,然後調整爲Embedding層希望的形狀輸入網絡進行預測即可。

image-20220424210730033

3.2 4pre1

image-20220424211229869

1pre1是輸入一個字母預測下一個字母的例子,4pre1是連續輸入多(這裏取4)個字母預測下一個字母的例子。這裏仍然使用三個記憶體,初始時刻記憶體內的記憶是0。接下來用一套訓練好的參數矩陣感受循環計算的前向傳播過程,在這個過程中的每個時刻參數矩陣是固定的,記憶體會在每個時刻被更新。下面以輸入bcde預測a爲例:

在第一個時刻,b的獨熱碼[0,1,0,0,0]輸入,記憶體根據更新公式\(h_t = tanh(x_t w_{xh} + h_{t-1}w_{hh} + bh)\)=tanh([−1.5 0.2 0.3] + [0.0 0.0 0.0] + [0.2 0.0 −0.1])= tanh([−1.3 0.2 0.2 ]) = [−0.9 0.2 0.2]刷新。在

第二個時刻,c的獨熱碼[0,0,1,0,0]輸入,記憶體根據更新公式\(h_t = tanh(x_t w_{xh} + h_{t-1}w_{hh} + bh)\)=tanh([−0.3 1.7 0.7] + [1.1 1.1 0.5] + [0.2 0.0 −0.1])= tanh([ 1.0 2.8 1.1]) = [0.8 1.0 0.8]刷新。在

第三個時刻,d的獨熱碼[0,0,0,1,0]輸入,記憶體根據更新公式\(h_t = tanh(x_t w_{xh} + h_{t-1}w_{hh} + bh)\)=tanh([-0.1 0.1 -0.1] + [0.6 0.4 -2.2] + [0.2 0.0 -0.1])= tanh([ 0.7 0.5 -2.4 ] = [0.6 0.5 -1.0]刷新。

在第四個時刻,e的獨熱碼[0,0,0,0,1]輸入,記憶體根據更新公式\(h_t = tanh(x_t w_{xh} + h_{t-1}w_{hh} + bh)\)=tanh([-1.2 -1.5 0.3] + [-1.3 -0.4 0.8] + [0.2 0.0 -0.1])= tanh([-2.3 -1.9 1.0] = [-1.0 -1.0 0.8]刷新。

輸出預測通過全連接完成,由下式求得最終輸出:

$y_t =softmax(h_tw_{hy} + by) $= softmax([3.3 1.2 0.9 0.3 −3.1]+ [−0.3 0.2 0.1 0.1 −0.3])= softmax ([3.0 1.4 1.0 0.4 −3.4]) = [𝟎𝟎.𝟕𝟕𝟕 0.14 0.10 0.05 0.00 ]

說明有71%的可能是字母a。觀察輸出結果,模型不僅成功預測出了下一個字母是a,還可以從神經網絡輸出的概率發現:因爲輸入序列的最後一個字母是e,所以模型理應也確實認爲下一個字母還是e的可能性最小,可能性最大的是a,其次分別是b、c、d 。

獨熱編碼實踐字母預測

image-20220424212209054

圖2.18 p21_rnn_onehot_4pre1.py(1)

如圖2.18所示,淺藍色框框住的區域爲與p15_rnn_onehot_1pre1.py不同的地方,即x_train、y_train變成了四個字母預測一個字母的形式(輸入連續的 abcd對應的標籤是e、輸入連續的bcde對應的標籤是a、依此類推)。

image-20220424212238013

圖2.19 p21_rnn_onehot_4pre1.py(2)

如圖2.19所示,與p15_rnn_onehot_1pre1.py不同,這裏的循環核展開步數爲4。

image-20220424212258955

圖2.20 p21_rnn_onehot_4pre1.py(3)

參數提取和acc/loss可視化和p15_rnn_onehot_1pre1.py代碼完全一樣。在結果預測時,如圖2.20所示,需要等待連續輸入四個字母,然後把這四個字母轉換爲獨熱碼,然後調整爲RNN層希望的形狀輸入網絡進行預測即可。運行結果如圖2.21所示。

image-20220424212345604

圖2.21 p21_rnn_onehot_4pre1.py預測結果

Embedding編碼實踐字母預測

這次將詞彙量擴充到26個(即字母從a到z)。如圖2.22所示,首先建立一個映射表,把字母用數字表示爲0到25;然後建立兩個空列表,一個用於存放訓練用的輸入特徵x_train,另一個用於存放訓練用的標籤y_train;接下來用for循環從數字列表中把連續4個數作爲輸入特徵添加到x_train中,第5個數作爲標籤添加到y_train中,這就構建了訓練用的輸入特徵x_train和標籤y_train。
image-20220424212441076

圖2.22 p31_rnn_embedding_4pre1.py(1)

如圖2.23,把輸入特徵變成Embedding層期待的形狀才能輸入網絡;在sequntial搭建網絡時,相比於one_hot形式增加了一層Embedding層,先對輸入數據進行編碼,這裏的26表示詞彙量是26,這裏的2表示每個單詞用2個數值編碼,這一層會生成一個26行2列的可訓練參數矩陣,實現編碼可訓練。隨後設定具有十個記憶體的循環層和一個全連接層(輸出會是 26個字母之一,所以這裏是26);後邊進行compile、fit、summary、參數提取和acc/loss可視化和p21_rnn_onehot_4pre1.py代碼完全一樣。

image-20220424212521329

圖2.23 p31_rnn_embedding_4pre1.py(2)

在驗證環節,如圖2.24所示,同樣使用了for循環先輸入要執行幾次檢測,隨後等待連續輸入四個字母,待輸入結束後把它們轉換爲Embedding層希望的形狀,然後輸入網絡進行預測,選出預測結果最大的一個。運行結果如圖2.25所示。

image-20220424212555564

圖2.24 p31_rnn_embedding_4pre1.py(3)

image-20220424212608735

圖2.25 p31_rnn_embedding_4pre1.py預測結果

4 RNN實現股票預測

(1) 數據源
SH600519.csv是用tushare模塊下載的SH600519貴州茅臺的日k線數據,本次例子中只用它的C列數據(如圖1.2.26所示):用連續60天的開盤價,預測第61天的開盤價。這個excel表格是)直接下載的真實數據,可以在這裏寫出我們需要的六位股票代碼,下載需要的股票歷史數據。

import tushare as ts
import matplotlib.pyplot as plt

df1 = ts.get_k_data('600519', ktype='D', start='2010-04-26', end='2022-04-26')

datapath1 = "./SH600519.csv"
df1.to_csv(datapath1)

image-20220426191937217

(1) 代碼實現

import numpy as np
import tensorflow as tf
from tensorflow.keras.layers import Dropout, Dense, SimpleRNN
import matplotlib.pyplot as plt
import os
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error
import math

maotai = pd.read_csv('./SH600519.csv')  # 讀取股票文件

training_set = maotai.iloc[0:2426 - 300, 2:3].values  # 前(2426-300=2126)天的開盤價作爲訓練集,表格從0開始計數,2:3 是提取[2:3)列,前閉後開,故提取出C列開盤價
test_set = maotai.iloc[2426 - 300:, 2:3].values  # 後300天的開盤價作爲測試集

# 歸一化
sc = MinMaxScaler(feature_range=(0, 1))  # 定義歸一化:歸一化到(0,1)之間
training_set_scaled = sc.fit_transform(training_set)  # 求得訓練集的最大值,最小值這些訓練集固有的屬性,並在訓練集上進行歸一化
test_set = sc.transform(test_set)  # 利用訓練集的屬性對測試集進行歸一化

x_train = []
y_train = []

x_test = []
y_test = []

按照六步法:

import相關模塊->讀取貴州茅臺日k線數據到變量maotai,把變量maotai中前2126天數據中的開盤價作爲訓練數據,把變量maotai中後300天數據中的開盤價作爲測試數據;

然後對開盤價進行歸一化,使送入神經網絡的數據分佈在0到1之間;

接下來建立空列表分別用於接收訓練集輸入特徵、訓練集標籤、測試集輸入特徵、測試集標籤;

# 測試集:csv表格中前2426-300=2126天數據
# 利用for循環,遍歷整個訓練集,提取訓練集中連續60天的開盤價作爲輸入特徵x_train,第61天的數據作爲標籤,for循環共構建2426-300-60=2066組數據。
for i in range(60, len(training_set_scaled)):
    x_train.append(training_set_scaled[i - 60:i, 0])
    y_train.append(training_set_scaled[i, 0])
# 對訓練集進行打亂
np.random.seed(7)
np.random.shuffle(x_train)
np.random.seed(7)
np.random.shuffle(y_train)
tf.random.set_seed(7)
# 將訓練集由list格式變爲array格式
x_train, y_train = np.array(x_train), np.array(y_train)

# 使x_train符合RNN輸入要求:[送入樣本數, 循環核時間展開步數, 每個時間步輸入特徵個數]。
# 此處整個數據集送入,送入樣本數爲x_train.shape[0]即2066組數據;輸入60個開盤價,預測出第61天的開盤價,循環核時間展開步數爲60; 每個時間步送入的特徵是某一天的開盤價,只有1個數據,故每個時間步輸入特徵個數爲1
x_train = np.reshape(x_train, (x_train.shape[0], 60, 1))
# 測試集:csv表格中後300天數據
# 利用for循環,遍歷整個測試集,提取測試集中連續60天的開盤價作爲輸入特徵x_train,第61天的數據作爲標籤,for循環共構建300-60=240組數據。
for i in range(60, len(test_set)):
    x_test.append(test_set[i - 60:i, 0])
    y_test.append(test_set[i, 0])
# 測試集變array並reshape爲符合RNN輸入要求:[送入樣本數, 循環核時間展開步數, 每個時間步輸入特徵個數]
x_test, y_test = np.array(x_test), np.array(y_test)
x_test = np.reshape(x_test, (x_test.shape[0], 60, 1))

繼續構造數據。用for循環遍歷整個訓練數據,每連續60天數據作爲輸入特徵x_train,第61天數據作爲對應的標籤y_train ,一共生成2066組訓練數據,然後打亂訓練數據的順序並轉變爲array格式繼而轉變爲RNN輸入要求的維度;同理,利用for循環遍歷整個測試數據,一共生成240組測試數據,測試集不需要打亂順序,但需轉變爲array格式繼而轉變爲RNN輸入要求的維度。

model = tf.keras.Sequential([
    SimpleRNN(80, return_sequences=True),
    Dropout(0.2),
    SimpleRNN(100),
    Dropout(0.2),
    Dense(1)
])

model.compile(optimizer=tf.keras.optimizers.Adam(0.001),
              loss='mean_squared_error')  # 損失函數用均方誤差
# 該應用只觀測loss數值,不觀測準確率,所以刪去metrics選項,一會在每個epoch迭代顯示時只顯示loss值

checkpoint_save_path = "./checkpoint/rnn_stock.ckpt"

if os.path.exists(checkpoint_save_path + '.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_weights_only=True,
                                                 save_best_only=True,
                                                 monitor='val_loss')

history = model.fit(x_train, y_train, batch_size=64, epochs=50, validation_data=(x_test, y_test), validation_freq=1,
                    callbacks=[cp_callback])

model.summary()

用sequntial搭建神經網絡:

第一層循環計算層記憶體設定80個,每個時間步推送h𝑡給下一層,使用0.2的Dropout

第二層循環計算層設定記憶體有100個,僅最後的時間步推送h𝑡給下一層,使用0.2的Dropout;

由於輸出值是第61天的開盤價只有一個數,所以全連接Dense是1->compile配置訓練方法使用adam優化器,使用均方誤差損失函數。

在股票預測代碼中,只需觀測loss,訓練迭代打印的時候也只打印loss,所以這裏就無需給metrics賦值->設置斷點續訓,fit執行訓練過程->summary打印出網絡結構和參數統計。

model.summary()

file = open('./weights.txt', 'w')  # 參數提取
for v in model.trainable_variables:
    file.write(str(v.name) + '\n')
    file.write(str(v.shape) + '\n')
    file.write(str(v.numpy()) + '\n')
file.close()

loss = history.history['loss']
val_loss = history.history['val_loss']

plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()
################## predict ######################
# 測試集輸入模型進行預測
predicted_stock_price = model.predict(x_test)
# 對預測數據還原---從(0,1)反歸一化到原始範圍
predicted_stock_price = sc.inverse_transform(predicted_stock_price)
# 對真實數據還原---從(0,1)反歸一化到原始範圍
real_stock_price = sc.inverse_transform(test_set[60:])
# 畫出真實數據和預測數據的對比曲線
plt.plot(real_stock_price, color='red', label='MaoTai Stock Price')
plt.plot(predicted_stock_price, color='blue', label='Predicted MaoTai Stock Price')
plt.title('MaoTai Stock Price Prediction')
plt.xlabel('Time')
plt.ylabel('MaoTai Stock Price')
plt.legend()
plt.show()

進行股票預測。用predict預測測試集數據,然後將預測值和真實值從歸一化的數值變換到真實數值,最後用紅色線畫出真實值曲線 、用藍色線畫出預測值曲線。

##########evaluate##############
# calculate MSE 均方誤差 ---> E[(預測值-真實值)^2] (預測值減真實值求平方後求均值)
mse = mean_squared_error(predicted_stock_price, real_stock_price)
# calculate RMSE 均方根誤差--->sqrt[MSE]    (對均方誤差開方)
rmse = math.sqrt(mean_squared_error(predicted_stock_price, real_stock_price))
# calculate MAE 平均絕對誤差----->E[|預測值-真實值|](預測值減真實值求絕對值後求均值)
mae = mean_absolute_error(predicted_stock_price, real_stock_price)
print('均方誤差: %.6f' % mse)
print('均方根誤差: %.6f' % rmse)
print('平均絕對誤差: %.6f' % mae)

爲了評價模型優劣,給出了三個評判指標:均方誤差、均方根誤差和平均絕對誤差,這些誤差越小說明預測的數值與真實值越接近。
圖2.34爲loss值曲線、圖2.35爲股票預測曲線、圖1.2.36爲三個評價指標值。

image-20220426200015505

圖2.34 RNN股票預測loss曲線

image-20220426195956618

圖2.35 RNN股票預測曲線

image-20220426200343429

圖2.36 RNN股票預測評價指標

5 LSTM、GRU

5.1 原始RNN的問題

image-20220426200507063

RNN面臨的較大問題是無法解決長跨度依賴問題,即後面節點相對於跨度很大的前面時間節點的信息感知能力太弱。如圖2.1.1(圖片來源: https://www.jianshu.com/p/9dc9f41f0b29)中的兩句話:左上角的句子中sky可以由較短跨度的詞預測出來,而右下角句子中的French與較長跨度之前的France有關係,即長跨度依賴,比較難預測。

image-20220426200517664

長跨度依賴的根本問題在於,多階段的反向傳播後會導致梯度消失、梯度爆炸。可以使用梯度截斷去解決梯度爆炸問題,但無法輕易解決梯度消失問題。

下面舉一個例子來解釋RNN梯度消失和爆炸的原因(例子來源:https://zhuanlan.zhihu.com/p/28687529):

假設時間序列有三段,\(h_0\)爲給定值,且爲了簡便假設沒有激活函數和偏置,則RNN的前向傳播過程如下:

image-20220426200621695

假設在t=3時刻,損失函數爲\(loss_3 = \frac{1}{2}(y_3 - y_{true})^2\),其餘時刻類似。則\(total\_loss = \frac{1}{2}[(y_1 - y_{true1})^2+(y_2 - y_{true2})^2+(y_3 - y_{true3})^2]\)。梯度下降法訓練就是對參數分別求偏導,然後按照梯度反方向調整它們使loss值變小的過程。假設只考慮t=3時刻的,這裏考慮\(w_{hh}\)的偏導:

image-20220426201457351

可以看出,只有三個時間點時,\(w_h\)的偏導與\(w_h\)的平方成比例。傳統循環網絡RNN可以通過記憶體實現短期記憶進行連續數據的預測,但是當連續數據的序列變長時會使展開時間步過長。當時間跨度變長時,冪次將變大。所以,如果\(w_h\)爲一個大於0小於1的數,隨着時間跨度的增長,偏導值將會趨於0;同理,當\(w_h\)較大時,偏導值將趨於無窮。這就是梯度消失和爆炸的原因。

5.2 LSTM

LSTM由Hochreiter & Schmidhuber 於1997年提出,通過門控單元很好的解決了RNN長期依賴問題。Sepp Hochreiter,Jurgen Schmidhuber.LONG SHORT-TERM MEMORY.Neural Computation,December 1997.
(1) 原理:
爲了解決長期依賴問題,長短記憶網絡(Long Short Term Memory,LSTM)應運而生。之所以LSTM能解決RNN的長期依賴問題,是因爲LSTM使用門(gate)機制對信息的流通和損失進行控制。

image-20220426201528556

圖2.2.1 LSTM計算過程

如圖2.2.1所示,LSTM引入了三個門限:輸入門\(i_t\)、遺忘門\(f_t\)、輸出門\(O_t\)
引入了表徵長期記憶的細胞態\(C_t\);引入了等待存入長期記憶的候選態$ \widetilde{C_t}$:*

  • 三個門限都是當前時刻的輸入特徵\(X_t\)和上個時刻的短期記憶\(h_{t-1}\)的函數,分別表示爲:

image-20220426202258840

三個公式中\(w_i\)\(w_f\)\(w_o\)是待訓練參數矩陣,\(b_i\)\(b_f\)\(b_o\)是待訓練偏置項。𝜎爲sigmoid激活函數,它可以使門限的範圍在0到1之間。

  • 定義\(h_t\)爲記憶體,它表徵短期記憶,是當前細胞態經過輸出門得到的:

記憶體(短期記憶):$h_t = o_t *tanh(C_t) $

  • 候選態表示歸納出的待存入細胞態的新知識,是當前時刻的輸入特徵\(x_t\)和上個時刻的短期記憶\(h_{t-1}\)的函數:

候選態(歸納出的新知識):$ \widetilde{C_t} = tanh(W_c[h_t-1,x_t] + b_c)$

  • 細胞態\(C_t\)表示長期記憶,它等於上個時刻的長期記憶\(C_{t-1}\)通過遺忘門的值和當前時刻歸納出的新知識\(\widetilde{C_t}\)通過輸入門的值之和:

細胞態(長期記憶):\(C_t = f_t *C_{t-1} + i_t * \widetilde{C_t}\)

當明確了這些概念,這裏舉一個簡單的例子理解一下LSTM:

假設LSTM就是我們聽老師講課的過程,目前老師講到了第45頁PPT。我們的腦袋裏記住的內容,是PPT第1頁到第45頁的長期記憶\(C_t\)。它由兩部分組成:一部分是PPT第1頁到第44頁的內容,也就是上一時刻的長期記憶\(C_{t-1}\)。我們不可能一字不差的記住全部內容,會不自覺地忘記了一些,所以上個時刻的長期記憶\(C_{t-1}\)要乘以遺忘門,這個乘積項就表示留存在我們腦中的對過去的記憶;另一部分是當前我們歸納出的新知識\(\widetilde{C_t}\),它由老師正在講的第45頁PPT(當前時刻的輸入\(x_t\)) 和第44頁PPT的短期記憶留存(上一時刻的短期記憶\(h_{t-1}\))組成。將現在的記憶\(\widetilde{C_t}\)乘以輸入門後與過去的記憶一同存儲爲當前的長期記憶\(C_t\)。接下來,如果我們想把我們學到的知識(當前的長期記憶\(C_t\))複述給朋友,我們不可能一字不落的講出來,所以\(C_t\)需要經過輸出門篩選後才成爲了輸出\(h_t\)。當

有多層循環網絡時,第二層循環網絡的輸入\(x_t\)就是第一層循環網絡的輸出\(h_t\),即輸入第二層網絡的是第一層網絡提取出的精華。可以這麼想,老師現在扮演的就是第一層循環網絡,每一頁PPT都是老師從一篇一篇論文中提取出的精華,輸出給我們。作爲第二層循環網絡的我們,接收到的數據就是老師的長期記憶\(C_t\)過tanh激活函數後乘以輸出門提取出的短期記憶\(h_t\)

(2)Tensorflow2描述LSTM層
tf.keras.layers.LSTM(神經元個數,return_sequences=是否返回輸出)
神經元個數和return_sequences的含義與SimpleRNN相同。
例:LSTM(8, return_sequences=True)

(3)LSTM股票預測
我們只需要將RNN預測股票中的模型更換爲

model = tf.keras.Sequential([
    LSTM(80, return_sequences=True),
    Dropout(0.2),
    LSTM(100),
    Dropout(0.2),
    Dense(1)
])

圖2.2.3爲loss值曲線、圖2.2.4爲股票預測曲線、圖2.2.5爲三個評價指標值。

image-20220426204920886

圖2.2.3 LSTM股票預測loss曲線

image-20220426204928849

圖2.2.4 LSTM股票預測曲線

image-20220426205336734

圖2.2.5 LSTM股票預測評價指標

5.3 GRU

GRU由Cho等人於2014年提出,優化LSTM結構。Kyunghyun Cho,Bart van Merrienboer,Caglar Gulcehre,Dzmitry Bahdanau,Fethi Bougares,Holger Schwenk,Yoshua Bengio.Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation.Computer ence, 2014.
(1)原理:
門控循環單元(Gated Recurrent Unit,GRU)是LSTM的一種變體,將LSTM中遺忘門與輸入門合二爲一爲更新門,模型比LSTM模型更簡單。

image-20220426205515455

圖2.3.1 GRU計算過程

如圖2.3.1所示,GRU使記憶體ℎ𝑡𝑡融合了長期記憶和短期記憶。\(h_t\)包含了過去信息\(h_{t-1}\)和現在信息(候選隱藏層)\(\widetilde{h_t}\),由更新門\(Z_t\)分配重要性:

image-20220426205701015

(2)Tensorflow2描述GRU層
tf.keras.layers.GRU(神經元個數, return_sequences=是否返回輸出)
神經元個數和return_sequences的含義與SimpleRNN相同。
例:GRU(8, return_sequences=True)

(3)GRU股票預測
我們只需要將RNN預測股票中的模型更換爲

model = tf.keras.Sequential([
    GRU(80, return_sequences=True),
    Dropout(0.2),
    GRU(100),
    Dropout(0.2),
    Dense(1)
])

圖2.3.3爲loss值曲線、圖2.3.4爲股票預測曲線、圖2.3.5爲三個評價指標值。

image-20220426210356456

圖2.3.3 GRU股票預測loss曲線

image-20220426210438006

圖2.3.4 GRU股票預測曲線

image-20220426210025848

圖2.3.5 GRU股票預測評價指標

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