鍵.png)
使用這些基本 REST API 最佳實(shí)踐構(gòu)建出色的 API
RNN以輸入數(shù)m對(duì)應(yīng)輸出數(shù)n的不同,可以劃分為5種基礎(chǔ)結(jié)構(gòu)類型:
(1)one to one:其實(shí)和全連接神經(jīng)網(wǎng)絡(luò)并沒有什么區(qū)別,這一類別算不上 RNN。
(2)one to many:輸入不是序列,輸出是序列??捎糜诎粗黝}生成文章或音樂等。
(3)many to one:輸入是序列,輸出不是序列(為單個(gè)值)。常用于文本分類、回歸預(yù)測(cè)。
(4)many to many:輸入和輸出都是不定長的序列。這也就是Encoder-Decoder結(jié)構(gòu),常用于機(jī)器翻譯。
(5)many to many(m==n):輸入和輸出都是等長的序列數(shù)據(jù)。這是 RNN 中最經(jīng)典的結(jié)構(gòu)類型,常用于NLP的命名實(shí)體識(shí)別、序列預(yù)測(cè)。
關(guān)于RNN模型,我們還是從數(shù)據(jù)、模型、學(xué)習(xí)目標(biāo)、優(yōu)化算法這幾個(gè)要素展開解析,使用過程需要重點(diǎn)關(guān)注的是其輸入和輸出的差異(本節(jié)以經(jīng)典的m==n的RNN結(jié)構(gòu)為例)。
不像傳統(tǒng)的機(jī)器學(xué)習(xí)模型假設(shè)輸入是獨(dú)立的,RNN的輸入數(shù)據(jù)元素有順序及相互依賴的,并按時(shí)間步逐一的串行輸入模型的。上一步的輸入對(duì)下一步的預(yù)測(cè)是有影響的(如文字預(yù)測(cè)的任務(wù),以“貓吃魚”這段序列文字,上一步的輸入“貓”–x(0)會(huì)影響下一步的預(yù)測(cè)“吃”–x(1)的概率,也會(huì)繼續(xù)影響下下步的預(yù)測(cè)“魚”–x(2)的概率),我們通過RNN結(jié)構(gòu)就可以將歷史的(上下文)的信息反饋到下一步。
如上圖,RNN模型(如左側(cè)模型,實(shí)際上也只有這一個(gè)物理模型),按各個(gè)時(shí)間步展開后(如右側(cè)模型),可以看作是按時(shí)間步(t)串聯(lián)并共享(、、 )參數(shù)的多個(gè)全連接神經(jīng)網(wǎng)絡(luò)。展開后的立體圖如下:
RNN除了接受每一步的輸入x(t),同時(shí)還會(huì)連接輸入上一步的反饋信息——隱藏狀態(tài)h(t-1),也就是當(dāng)前時(shí)刻的隱藏狀態(tài) ?(t) 由當(dāng)前時(shí)刻的輸入 x(t)和上一時(shí)刻的隱藏狀態(tài)h(t-1)共同決定。另外的,RNN神經(jīng)元在每個(gè)時(shí)間步上是共享權(quán)重參數(shù)矩陣的(不同于CNN是空間上的參數(shù)共享),時(shí)間維度上的參數(shù)共享可以充分利用數(shù)據(jù)之間的時(shí)域關(guān)聯(lián)性,如果我們?cè)诿總€(gè)時(shí)間點(diǎn)都有一個(gè)單獨(dú)的參數(shù),不但不能泛化到訓(xùn)練時(shí)沒有見過序列長度,也不能在時(shí)間上共享不同序列長度和不同位置的統(tǒng)計(jì)強(qiáng)度。
如下各時(shí)間步的前向傳播計(jì)算流程圖,接下來我們會(huì)對(duì)計(jì)算流程逐步分解:
上圖展開了兩個(gè)時(shí)間步t-1及t的計(jì)算過程;
t取值為0~m(序列的長度);
x(t)是t時(shí)間步的 輸入向量;
U是 輸入層到隱藏層的權(quán)重矩陣;
h(t)是t時(shí)間步 隱藏層的輸出狀態(tài)向量,能表征歷史輸入(上下文)的反饋信息;
V是 隱藏層到輸出層的權(quán)重矩陣;b是 偏置項(xiàng);
o(t)是t時(shí)間步 輸出層的輸出向量;
假設(shè)各時(shí)間步的狀態(tài)h的維度為2,h初始值為[0,0],輸入x和輸出o維度為1。
將上一時(shí)刻的狀態(tài)h(t-1),與當(dāng)前時(shí)刻的輸入x(t)拼接成一維向量作為全連接的隱藏層的輸入,對(duì)應(yīng)隱藏層的的輸入維度為3 (如下圖的輸入部分)。
對(duì)應(yīng)到計(jì)算流程圖上,t-1時(shí)刻輸出的狀態(tài)h(t-1)為[0.537, 0.462],t時(shí)刻的輸入為[2.0],拼接之后為[0.537, 0.462, 2.0]輸入全連接的隱藏層,隱藏層的權(quán)重矩陣為[[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]],偏置項(xiàng)b1為[0.1, -0.1],經(jīng)過隱藏層的矩陣運(yùn)算為:h(t-1)拼接x(t) * 權(quán)重參數(shù)W 拼接 權(quán)重矩陣U + 偏置項(xiàng)(b1)再由tanh轉(zhuǎn)換后輸出為狀態(tài)h(t)。接著h(t)與x(t+1)繼續(xù)輸入到下一步(t+1)的隱藏層。
# 隱藏層的矩陣運(yùn)算的對(duì)應(yīng)代碼
np.tanh(np.dot(np.array([[0.537, 0.462, 2.0]]),np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])) + np.array([0.1, -0.1]))
# 輸出h(t)為:array([[0.85972772, 0.88365397]])
隱藏層輸出狀態(tài)h(t)為[0.86, 0.884],輸出層權(quán)重矩陣為[[1.0], [2.0]],偏置項(xiàng)b1為[0.1], h(t)經(jīng)由輸出層的矩陣運(yùn)算為:h(t) * V +偏置項(xiàng)(b2)后,輸出o(t)
# 輸出層的矩陣運(yùn)算的對(duì)應(yīng)代碼
np.dot(np.array([[0.85972772, 0.88365397]]),np.array([[1.0], [2.0]])) + np.array([0.1])
# o(t) 輸出: array([[2.72703566]])
上述過程從初始輸入(t=0)遍歷到序列結(jié)束(t=m),就是一個(gè)完整的前向傳播過程,我們可以看出權(quán)重矩陣、、和偏置項(xiàng)在不同時(shí)刻都是同一組,這也說明RNN在不同時(shí)刻中是共享參數(shù)的。
可以將這RNN計(jì)算過程簡要概述為兩個(gè)公式:
狀態(tài)h(t) = f( U * x(t) + W * h(t-1) + b1), f為激活函數(shù),上圖隱藏層用的是tanh。隱藏層激活函數(shù)常用tanh、relu
輸出o(t) = g( V * h(t) + b2),g為激活函數(shù),上圖輸出層做回歸預(yù)測(cè),沒有用非線性激活函數(shù)。當(dāng)用于分類任務(wù),輸出層一般用softmax激活函數(shù)
RNN模型將輸入 x(t)序列映射到輸出值 o(t)后, 同全連接神經(jīng)網(wǎng)絡(luò)一樣,可以衡量每個(gè) o(t) 與相應(yīng)的訓(xùn)練目標(biāo) y 的誤差(如交叉熵、均方誤差)作為損失函數(shù),以最小化損失函數(shù)L(U,W,V)作為學(xué)習(xí)目標(biāo)(也可以稱為優(yōu)化策略)。
RNN的優(yōu)化過程與全連接神經(jīng)網(wǎng)絡(luò)沒有本質(zhì)區(qū)別,通過誤差反向傳播,多次迭代梯度下降優(yōu)化參數(shù),得到合適的RNN模型參數(shù) (此處忽略偏置項(xiàng)) 。區(qū)別在于RNN是基于時(shí)間反向傳播,所以RNN的反向傳播有時(shí)也叫做BPTT(back-propagation through time),BPTT會(huì)對(duì)不同時(shí)間步的梯度求和,由于所有的參數(shù)在序列的各個(gè)位置是共享的,反向傳播時(shí)我們更新的是相同的參數(shù)組。如下BPTT示意圖及U,W,V求導(dǎo)(梯度)的過程。
優(yōu)化參數(shù) 相對(duì)簡單,求參數(shù) 的偏導(dǎo)數(shù),并對(duì)不同時(shí)間步的梯度求和:
和 的偏導(dǎo)的求解由于需要涉及到歷史數(shù)據(jù),其偏導(dǎo)求起來相對(duì)復(fù)雜,假設(shè)只有三個(gè)時(shí)刻(t==3),那么在第三個(gè)時(shí)刻 對(duì) 的偏導(dǎo)數(shù)為:
相應(yīng)的, 在第三個(gè)時(shí)刻對(duì)U的偏導(dǎo)數(shù)為:
我們根據(jù)上面兩個(gè)式子可以寫出L在 時(shí)刻對(duì) 和 偏導(dǎo)數(shù)的通式:
當(dāng)我們把激活函數(shù)(sigmoid、tanh)代入,分析上述通式的中間累乘的那部分:
sigmoid函數(shù)的導(dǎo)數(shù)范圍是(0,0.25],tanh函數(shù)的導(dǎo)數(shù)范圍是(0,1]。累乘的過程中,如果取sigmoid函數(shù)作為激活函數(shù)的話,隨著時(shí)間步越長,較小導(dǎo)數(shù)累乘就會(huì)導(dǎo)致該時(shí)間步梯度越來越小直到接近于0(歷史時(shí)間步的信息距離當(dāng)前時(shí)間步越長,反饋的梯度信號(hào)就會(huì)越弱),這也就是“梯度消失”。同理,也可能會(huì)導(dǎo)致“梯度爆炸”。
本項(xiàng)目通過創(chuàng)建單層隱藏層的RNN模型,輸入前60個(gè)交易日(時(shí)間步)股票開盤價(jià)的時(shí)間序列數(shù)據(jù),預(yù)測(cè)下一個(gè)(60+1)交易日的股票開盤價(jià)。
導(dǎo)入股票數(shù)據(jù),選取股票開盤價(jià)的時(shí)間序列數(shù)據(jù)
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
#(本公眾號(hào)閱讀原文訪問數(shù)據(jù)集及源碼)
dataset_train = pd.read_csv('./data/NSE-TATAGLOBAL.csv')
dataset_train = dataset_train.sort_values(by='Date').reset_index(drop=True)
training_set = dataset_train.iloc[:, 1:2].values
print(dataset_train.shape)
dataset_train.head()
對(duì)訓(xùn)練數(shù)據(jù)進(jìn)行歸一化,加速網(wǎng)絡(luò)訓(xùn)練收斂。
# 訓(xùn)練數(shù)據(jù)max-min歸一化
from sklearn.preprocessing import MinMaxScaler
sc = MinMaxScaler(feature_range = (0, 1))
training_set_scaled = sc.fit_transform(training_set)
將數(shù)據(jù)整理為樣本及標(biāo)簽:60 timesteps and 1 output
# 每條樣本含60個(gè)時(shí)間步,對(duì)應(yīng)下一時(shí)間步的標(biāo)簽值
X_train = []
y_train = []
for i in range(60, 2035):
X_train.append(training_set_scaled[i-60:i, 0])
y_train.append(training_set_scaled[i, 0])
X_train, y_train = np.array(X_train), np.array(y_train)
print(X_train.shape)
print(y_train.shape)
# Reshaping
X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1))
print(X_train.shape)
利用kera創(chuàng)建單隱藏層的RNN模型,并設(shè)定模型優(yōu)化算法adam, 目標(biāo)函數(shù)均方根MSE
# 利用Keras創(chuàng)建RNN模型
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import SimpleRNN,LSTM
from keras.layers import Dropout
# 初始化順序模型
regressor = Sequential()
# 定義輸入層及帶5個(gè)神經(jīng)元的隱藏層
regressor.add(SimpleRNN(units = 5, input_shape = (X_train.shape[1], 1)))
# 定義線性的輸出層
regressor.add(Dense(units = 1))
# 模型編譯:定義優(yōu)化算法adam, 目標(biāo)函數(shù)均方根MSE
regressor.compile(optimizer = 'adam', loss = 'mean_squared_error')
# 模型訓(xùn)練
history = regressor.fit(X_train, y_train, epochs = 100, batch_size = 100, validation_split=0.1)
regressor.summary()
展示模型擬合的情況:訓(xùn)練集、驗(yàn)證集均有較低的loss
plt.plot(history.history['loss'],c='blue') # 藍(lán)色線訓(xùn)練集損失
plt.plot(history.history['val_loss'],c='red') # 紅色線驗(yàn)證集損失
plt.show()
評(píng)估模型:以新的時(shí)間段的股票交易系列數(shù)據(jù)作為測(cè)試集,評(píng)估模型測(cè)試集的表現(xiàn)。
# 測(cè)試數(shù)據(jù)
dataset_test = pd.read_csv('./data/tatatest.csv')
dataset_test = dataset_test.sort_values(by='Date').reset_index(drop=True)
real_stock_price = dataset_test.iloc[:, 1:2].values
dataset_total = pd.concat((dataset_train['Open'], dataset_test['Open']), axis = 0)
inputs = dataset_total[len(dataset_total) - len(dataset_test) - 60:].values
inputs = inputs.reshape(-1,1)
inputs = sc.transform(inputs)
# 提取測(cè)試集
X_test = []
for i in range(60, 76):
X_test.append(inputs[i-60:i, 0])
X_test = np.array(X_test)
X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1))
# 模型預(yù)測(cè)
predicted_stock_price = regressor.predict(X_test)
# 逆歸一化
predicted_stock_price = sc.inverse_transform(predicted_stock_price)
# 模型評(píng)估
print('預(yù)測(cè)與實(shí)際差異MSE',sum(pow((predicted_stock_price - real_stock_price),2))/predicted_stock_price.shape[0])
print('預(yù)測(cè)與實(shí)際差異MAE',sum(abs(predicted_stock_price - real_stock_price))/predicted_stock_price.shape[0])
通過測(cè)試集評(píng)估,預(yù)測(cè)與實(shí)際差異MSE:53.03141531,預(yù)測(cè)與實(shí)際差異MAE :5.82196445??梢暬A(yù)測(cè)值與實(shí)際值的差異情況,整體比較一致,預(yù)測(cè)有點(diǎn)滯后。可在進(jìn)一步調(diào)參優(yōu)化下(注:本文僅從數(shù)據(jù)規(guī)律維度預(yù)測(cè)股價(jià),僅供參考不構(gòu)成任何投資建議)。
# 預(yù)測(cè)與實(shí)際差異的可視化
plt.plot(real_stock_price, color = 'red', label = 'Real TATA Stock Price')
plt.plot(predicted_stock_price, color = 'blue', label = 'Predicted TAT Stock Price')
plt.title('TATA Stock Price Prediction')
plt.xlabel('samples')
plt.ylabel('TATA Stock Price')
plt.legend()
plt.show()
文章轉(zhuǎn)自微信公眾號(hào)@算法進(jìn)階
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)