LSTM

写在前面

本文记录笔者在学习LSTM时的记录,相信读者已经在网上看过许多的LSTM博客与视频,与其他博客不同的是,本文会从数学公式的角度,剖析LSTM模型中各个部分的模型输入输出等维度信息,帮助初学者在公式层面理解LSTM模型,并且给出了相关计算的例子代入股票预测场景,并给出参考代码。

模型结构

LSTM的模型结构如下图所示。它由若干个重复的LSTM单元组成,每个单元内部包含遗忘门、输入门和输出门,以及当前时刻的单元状态和输出状态。

LSTM模型结构图

模型输入

LSTM模型,通常是处理一个序列(比如文本序列或时间序列)X=(x1,x2,…,xt,… )TX = (x_1,x_2,\dots,x_t,\dots)^TX=(x1,x2,,xt,)T ,每个时间步的输入可以表示为xtx_txt,我们使用滑动窗口将序列分为若干个窗口大小为LLL的窗口,步长为stepstepstep,当数据划分到最后,若不足为LLL不能构成窗口时,缺少的数据使用pad填充,通常为0填充或使用最近数据填充。例如,假设我们有292929个时间步骤的输入,即x⃗=(x0,x1,…,x28)T\vec{x} = (x_0,x_1,\dots,x_{28})^Tx =(x0,x1,,x28)T,且假设窗口大小为101010,步长stepstepstep也为101010我们将数据分成三个窗口,即分为
x1⃗=(x0,x1,…,x9)T\vec{x_1} = (x_0,x_1,\dots,x_{9})^T x1 =(x0,x1,,x9)T
x2⃗=(x10,x11,…,x19)T\vec{x_2} = (x_{10},x_{11},\dots,x_{19})^T x2 =(x10,x11,,x19)T
x3⃗=(x20,x21,…,x28,x29)T\vec{x_3} = (x_{20},x_{21},\dots,x_{28},x_{29})^T x3 =(x20,x21,,x28,x29)T
由于x29x_{29}x29的值不存在,我们将其值设为000或者x28x_{28}x28的值,即x3⃗=(x20,x21,…,x28,0)T\vec{x_3} = (x_{20},x_{21},\dots,x_{28}, 0)^Tx3 =(x20,x21,,x28,0)T或者x3⃗=(x20,x21,…,x28,x28)T\vec{x_3} = (x_{20},x_{21},\dots,x_{28},x_{28})^Tx3 =(x20,x21,,x28,x28)T

当步长stepstepstep111时,通常不会出现上面的情况,这也是我们使用的最多的一种滑动窗口划分方案。
例如,对于一个时序序列 X={x1,x2,…,x10}X = \{x_1, x_2, \ldots, x_{10}\}X={x1,x2,,x10},窗口大小 L=3L = 3L=3,滑动步长 step=1step = 1step=1,滑动窗口划分结果为:
x1⃗=(x1,x2,x3)x2⃗=(x2,x3,x4)x3⃗=(x3,x4,x5)x4⃗=(x4,x5,x6)x5⃗=(x5,x6,x7)x6⃗=(x6,x7,x8)x7⃗=(x7,x8,x9)x8⃗=(x8,x9,x10) \begin{aligned} \vec{x_1} & = (x_1, x_2, x_3) \\ \vec{x_2} & = (x_2, x_3, x_4) \\ \vec{x_3} & = (x_3, x_4, x_5) \\ \vec{x_4} & = (x_4, x_5, x_6) \\ \vec{x_5} & = (x_5, x_6, x_7) \\ \vec{x_6} & = (x_6, x_7, x_8) \\ \vec{x_7} & = (x_7, x_8, x_9) \\ \vec{x_8} & = (x_8, x_9, x_{10}) \end{aligned} x1 x2 x3 x4 x5 x6 x7 x8 =(x1,x2,x3)=(x2,x3,x4)=(x3,x4,x5)=(x4,x5,x6)=(x5,x6,x7)=(x6,x7,x8)=(x7,x8,x9)=(x8,x9,x10)

LSTM 单元的输入包含当前时刻的输入xt⃗\vec{x_t}xt 、上一时刻的输出状态ht−1h_{t-1}ht1以及上一时刻的单元状态ct−1c_{t-1}ct1。在进行运算第一层LSTM单元时,我们会手动初始化h0h_0h0c0c_0c0,而在后面的LSTM的单元中ht−1h_{t-1}ht1ct−1c_{t-1}ct1,都可以由上一次的LSTM单元获得。xt⃗\vec{x_t}xt ht−1h_{t-1}ht1ct−1c_{t-1}ct1分别代表当前时刻的输入信息、上一时刻的输出信息以及上一时刻的记忆信息。其中,xt⃗∈Rm×1\vec{x_t} \in \mathbb{R}^{m \times 1}xt Rm×1mmm是输入序列处理后的窗口大小(长度),ht−1h_{t-1}ht1上一时刻的输出状态,形状为ht−1∈Rd×1h_{t-1} \in \mathbb{R}^{d \times 1}ht1Rd×1ddd是LSTM单元的隐藏状态大小,ct−1c_{t-1}ct1是上一时刻的单元状态,形状为ct−1∈Rd×1c_{t-1} \in \mathbb{R}^{d \times 1}ct1Rd×1,与ht−1h_{t-1}ht1具有相同的形状。

我们通常会把ht−1h_{t-1}ht1xt⃗\vec{x_t}xt 拼在一起形成更长的向量yt⃗\vec{y_t}yt ,我们通常竖着拼,即 yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1}yt R(d+m)×1 ,如公式下所示,然后yt⃗\vec{y_t}yt 会传入各个门。当采用多批次时,yt⃗∈R(d+m)×n\vec{y_t} \in \mathbb{R}^{(d + m) \times n}yt R(d+m)×n

yt⃗=[ht−1;xt⃗]=[ht−1xt⃗] \vec{y_t} = [h_{t-1}; \vec{x_t}] = \left[{\begin{matrix} h_{t-1} \\ \vec{x_t} \end{matrix}}\right] yt =[ht1;xt ]=[ht1xt ]

遗忘门

遗忘门的输入为我们在模型输入中处理得到的Xt′X_t'Xt。我们将Xt′X_t'Xt与遗忘门中的权重矩阵WfW_fWf相乘再加上置偏值bfb_fbf,得到结果MfM_fMf。然后对MfM_fMf取Sigmoid,得到遗忘门的输出ftf_tft,其形状与单元状态ctc_tct相同,即 ft∈Rd×1f_t \in \mathbb{R}^{d \times 1}ftRd×1,表示遗忘的程度。具体的计算公式如(\ref{LSTME02})所示。
Mf=Wfyt⃗+bf M_f = W_f\vec{y_t} + b_f Mf=Wfyt +bf
ft=σ(Mf)=11+e−(Wfyt⃗+bf) f_t = \sigma(M_f) = \frac{1}{1 + e^{-(W_f\vec{y_t} + b_f)}} ft=σ(Mf)=1+e(Wfyt +bf)1
其中,yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1}yt R(d+m)×1Wf∈Rd×(d+m)W_f \in \mathbb{R}^{d \times (d + m)}WfRd×(d+m)bf∈Rd×1b_f \in \mathbb{R}^{d \times 1}bfRd×1ft∈Rd×1f_t \in \mathbb{R}^{d \times 1}ftRd×1

在LSTM的许多门中,都使用Sigmoid函数,Sigmoid函数的绝大部分的值的取值范围为(0,1)(0, 1)(0,1),这可以很有效的表示在Sigmoid函数的输入中哪些数据需要记忆,哪些数据需要遗忘的过程。当Sigmoid函数只越接近000时表示遗忘,当接近111时表示需要记忆。

输入门

输入门的输入为我们在模型输入中处理得到的yt⃗\vec{y_t}yt ,且yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1 }yt R(d+m)×1。我们将yt⃗\vec{y_t}yt 与输入门中的权重矩阵WiW_iWi相乘再加上置偏值bib_ibi,得到结果MiM_iMi,然后对MiM_iMi取Sigmoid,得到输入门的输出iti_tit,表示输入的重要程度。具体的计算公式如下所示。
Mi=Wiyt⃗+bi M_i = W_i\vec{y_t} + b_i Mi=Wiyt +bi
it=σ(Mi)=11+e−(Wiyt⃗+bi) i_t = \sigma(M_i) = \frac{1}{1 + e^{-(W_i\vec{y_t} + b_i)}} it=σ(Mi)=1+e(Wiyt +bi)1
其中,yt⃗∈R(d+m)×n\vec{y_t} \in \mathbb{R}^{(d + m) \times n}yt R(d+m)×nWi∈Rd×(d+m)W_i \in \mathbb{R}^{d \times (d + m)}WiRd×(d+m)bi∈Rd×1b_i \in \mathbb{R}^{d \times 1}biRd×1it∈Rd×1i_t \in \mathbb{R}^{d \times 1}itRd×1

输出门

输出门的输入为我们在模型输入中处理得到的yt⃗\vec{y_t}yt ,且yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1 }yt R(d+m)×1。我们将yt⃗\vec{y_t}yt 与输出门中的权重矩阵WoW_oWo相乘再加上置偏值bob_obo,得到结果MoM_oMo,然后对MoM_oMo取Sigmoid,得到输出门的输出oto_tot,具体的计算公式如下所示。

Mo=Woyt⃗+bo M_o = W_o\vec{y_t} + b_o Mo=Woyt +bo
ot=σ(Mo)=11+e−(Woyt⃗+bo) o_t = \sigma(M_o) = \frac{1}{1 + e^{-(W_o\vec{y_t} + b_o)}} ot=σ(Mo)=1+e(Woyt +bo)1
其中,yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1}yt R(d+m)×1Wo∈Rd×(d+m)W_o \in \mathbb{R}^{d \times (d + m)}WoRd×(d+m)bo∈Rd×1b_o \in \mathbb{R}^{d \times 1}boRd×1ot∈Rd×1o_t \in \mathbb{R}^{d \times 1}otRd×1

当前输入单元状态

在计算ctc_tct之前,我们需要引入当前输入单元状态,并计算ct~\tilde{c_t}ct~的值。ct~\tilde{c_t}ct~是当前输入的单元状态,表示当前输入要保留多少内容到记忆中。我们将yt⃗\vec{y_t}yt 与当前时刻状态单元的权重矩阵WcW_cWc相乘再加上置偏值bcb_cbc,得到结果McM_cMc,然后对McM_cMc取tanh,得到的输出ct~\tilde{c_t}ct~ct~\tilde{c_t}ct~的计算如公式下所示。
Mc=Wcyt⃗+bc M_c = W_c\vec{y_t} + b_c Mc=Wcyt +bc
ct~=tanh(Mc)=eMc−e−MceMc+e−Mc=(eWcyt⃗+bc)−e−(Wcyt⃗+bc)(eWcyt⃗+bc)+e−(Wcyt⃗+bc) \tilde{c_t} = \text{tanh}(M_c) = \frac{e^{M_c}-e^{-M_c}}{e^{M_c}+e^{-M_c}} = \frac{(e^{W_c\vec{y_t} + b_c)}-e^{-(W_c\vec{y_t} + b_c)}}{(e^{W_c\vec{y_t} + b_c)}+e^{-(W_c\vec{y_t} + b_c)}} ct~=tanh(Mc)=eMc+eMceMceMc=(eWcyt +bc)+e(Wcyt +bc)(eWcyt +bc)e(Wcyt +bc)
其中,yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1}yt R(d+m)×1Wc∈Rd×(d+m)W_c \in \mathbb{R}^{d \times (d + m)}WcRd×(d+m)bc∈Rd×1b_c \in \mathbb{R}^{d \times 1}bcRd×1ct~∈Rd×1\tilde{c_t} \in \mathbb{R}^{d \times 1}ct~Rd×1

当前输入单元状态中,使用了tanh函数,tanh函数的取值范围为(−1,1)(-1,1)(1,1),当函数的值接近−1-11时代表着当前输入信息要被修正,当但函数值接近111时,代码当前输入信息要被加强。

当前时刻单元状态

接下来我们进行当前时刻单元状态ctc_tct的计算。我们使用遗忘门和输入门得到的结果 ftf_tftiti_tit和上一时刻单元状态ct−1c_{t-1}ct1来计算当前时刻单元状态ctc_tct。我们分别将ftf_tftct−1c_{t-1}ct1按元素相乘,iti_titct~\tilde{c_t}ct~按元素相乘,然后再将两者相加得到我们的当前时刻单元状态ctc_tct。具体计算如公式下所示。
ct=ft∘ct−1+it∘ct~ c_t = f_t \circ c_{t-1} + i_t \circ \tilde{c_t} ct=ftct1+itct~
其中,ft∈Rd×1f_t \in \mathbb{R}^{d \times 1}ftRd×1时遗忘门输出,it∈Rd×1i_t \in \mathbb{R}^{d \times 1}itRd×1是输入门输出,ct~∈Rd×1\tilde{c_{t}} \in \mathbb{R}^{d \times 1}ct~Rd×1是当前输入状态单元,ct−1∈Rd×1c_{t-1} \in \mathbb{R}^{d \times 1}ct1Rd×1 是上一时刻状态单元,∘\circ表示 按元素乘

模型输出

模型的输出是hth_tht和当前时刻的单元状态ctc_tct,而hth_tht由当前时刻的单元状态ctc_tct​和输出门的输出oto_tot确定。我们将当前时刻的单元状态ctc_tct取 tanh得到dtd_tdt,然后将 dtd_tdtoto_tot按元素相乘得到最后的hth_tht,计算公式如下所示。通常,hth_tht会进一步传递给模型的上层或者作为最终的预测结果。
dt=tanh(ct)=ect−e−ctect+e−ct d_t = \text{tanh}(c_t) = \frac{e^{c_t}-e^{-c_t}}{e^{c_t}+e^{-c_t}} dt=tanh(ct)=ect+ectectect
ht=ot∘dt h_t = o_t \circ d_t ht=otdt
其中 ht∈Rd×1h_t \in \mathbb{R}^{d \times 1}htRd×1 为当前层隐藏状态,ot∈Rd×1o_t \in \mathbb{R}^{d \times 1}otRd×1为输出门的输出,ct∈Rd×1c_t \in \mathbb{R}^{d \times 1}ctRd×1为当前时刻状态单元。

日期 开盘价 收盘价 最高价 最低价
4月23日 3038.6118 3021.9775 3044.9438 3016.5168
4月24日 3029.4028 3044.8223 3045.6399 3019.1238
4月25日 3037.9272 3052.8999 3060.2634 3034.6499
4月26日 3054.9793 3088.6357 3092.4300 3054.9793

Table: SH000001

简单的LSTM例子

接下来我们根据上面的模型结构中的计算方法来简单计算一个LSTM的例子。

我们以取中国A股上证指数(SH000001)2024年4月23日-25日共3个交易日的数据为例,取开盘价、收盘价、最高价、最低价作为特征,具体数据如表格所示。使用LSTM模型计算预测2024年4月26日的开盘价、收盘价、最高价、最低价,损失函数使用MSE。我们取隐藏层状态ddd的大小为444,然后进行计算,预测下一天的数据。

我们把表格数据处理成xtx_txt的形式,也就是把每天的444个特征,转换成m×1m \times 1m×1(4×1)(4 \times 1)(4×1)的向量,然后我们得到以XXX的结果。

X=(x1⃗,x2⃗,x3⃗)=[3038.61183029.40283037.92723021.97753044.82233052.89993044.94383045.63993060.26343016.51683019.12383034.6499] X = (\vec{x_1}, \vec{x_2}, \vec{x_3}) = \begin{bmatrix} 3038.6118 & 3029.4028 & 3037.9272 \\ 3021.9775 & 3044.8223 & 3052.8999 \\ 3044.9438 & 3045.6399 & 3060.2634 \\ 3016.5168 & 3019.1238 & 3034.6499 \\ \end{bmatrix} X=(x1 ,x2 ,x3 )= 3038.61183021.97753044.94383016.51683029.40283044.82233045.63993019.12383037.92723052.89993060.26343034.6499

由于隐藏层大小为 d=4d = 4d=4,所以 h0h_0h0c0c_0c0的维度都是 4×14 \times 14×1,我们将 h0h_0h0c0c_0c0进行初始化为0⃗\vec{0}0 向量,即

h0=[0,0,0,0]T,c0=[0,0,0,0]T h_0 = [0, 0, 0, 0]^T, c_0 = [0, 0, 0, 0]^T h0=[0,0,0,0]T,c0=[0,0,0,0]T

随后我们初始化 WfW_fWfWiW_iWiWcW_cWcWoW_oWo(维度为d×(d+m)d \times (d + m)d×(d+m),即 4×84 \times 84×8以及bfb_fbfbib_ibibcb_cbcbob_oboWWW的元素值 ∈[−0.0001,0.0001]\in [-0.0001, 0.0001][0.0001,0.0001],W是随机矩阵,如下所示。
Wf=[−0.0005−0.0010−0.0010−0.0004−0.0008−0.0006−0.0006−0.00070.0004−0.0009−0.00060.00090.00010.00040.00090.0003−0.0005−0.00060.0007−0.0003−0.00030.00010.00040.0006−0.0007−0.00080.0007−0.00060.0005−0.0003−0.0010−0.0002] W_f = \begin{bmatrix} -0.0005 & -0.0010 & -0.0010 & -0.0004 & -0.0008 & -0.0006 & -0.0006 & -0.0007 \\ 0.0004 & -0.0009 & -0.0006 & 0.0009 & 0.0001 & 0.0004 & 0.0009 & 0.0003 \\ -0.0005 & -0.0006 & 0.0007 & -0.0003 & -0.0003 & 0.0001 & 0.0004 & 0.0006 \\ -0.0007 & -0.0008 & 0.0007 & -0.0006 & 0.0005 & -0.0003 & -0.0010 & -0.0002 \\ \end{bmatrix} Wf= 0.00050.00040.00050.00070.00100.00090.00060.00080.00100.00060.00070.00070.00040.00090.00030.00060.00080.00010.00030.00050.00060.00040.00010.00030.00060.00090.00040.00100.00070.00030.00060.0002

Wi=[−0.0006−0.0001−0.00030.00020.00080.0000−0.0003−0.00030.0007−0.00020.00060.0001−0.0009−0.0005−0.0007−0.0005−0.00080.00040.0007−0.0008−0.00080.0010−0.0006−0.0009−0.00050.0010−0.0006−0.0002−0.00020.0006−0.00070.0002] W_i = \begin{bmatrix} -0.0006 & -0.0001 & -0.0003 & 0.0002 & 0.0008 & 0.0000 & -0.0003 & -0.0003 \\ 0.0007 & -0.0002 & 0.0006 & 0.0001 & -0.0009 & -0.0005 & -0.0007 & -0.0005 \\ -0.0008 & 0.0004 & 0.0007 & -0.0008 & -0.0008 & 0.0010 & -0.0006 & -0.0009 \\ -0.0005 & 0.0010 & -0.0006 & -0.0002 & -0.0002 & 0.0006 & -0.0007 & 0.0002 \\ \end{bmatrix} Wi= 0.00060.00070.00080.00050.00010.00020.00040.00100.00030.00060.00070.00060.00020.00010.00080.00020.00080.00090.00080.00020.00000.00050.00100.00060.00030.00070.00060.00070.00030.00050.00090.0002

Wc=[0.00010.00040.0000−0.0006−0.0006−0.00020.00030.0005−0.0002−0.00060.0005−0.00090.0002−0.0008−0.0003−0.00090.00020.00040.00000.00090.00030.00030.0006−0.0008−0.0007−0.00080.0009−0.00070.0002−0.0010−0.0006−0.0003] W_c = \begin{bmatrix} 0.0001 & 0.0004 & 0.0000 & -0.0006 & -0.0006 & -0.0002 & 0.0003 & 0.0005 \\ -0.0002 & -0.0006 & 0.0005 & -0.0009 & 0.0002 & -0.0008 & -0.0003 & -0.0009 \\ 0.0002 & 0.0004 & 0.0000 & 0.0009 & 0.0003 & 0.0003 & 0.0006 & -0.0008 \\ -0.0007 & -0.0008 & 0.0009 & -0.0007 & 0.0002 & -0.0010 & -0.0006 & -0.0003 \\ \end{bmatrix} Wc= 0.00010.00020.00020.00070.00040.00060.00040.00080.00000.00050.00000.00090.00060.00090.00090.00070.00060.00020.00030.00020.00020.00080.00030.00100.00030.00030.00060.00060.00050.00090.00080.0003

Wo=[−0.0009−0.00050.00000.0001−0.0001−0.0004−0.0005−0.00070.0009−0.00050.0008−0.00090.00010.0004−0.00020.0004−0.0005−0.00040.0007−0.0008−0.00060.00080.00060.0010−0.00020.00080.0008−0.00020.0008−0.00040.0008−0.0002] W_o = \begin{bmatrix} -0.0009 & -0.0005 & 0.0000 & 0.0001 & -0.0001 & -0.0004 & -0.0005 & -0.0007 \\ 0.0009 & -0.0005 & 0.0008 & -0.0009 & 0.0001 & 0.0004 & -0.0002 & 0.0004 \\ -0.0005 & -0.0004 & 0.0007 & -0.0008 & -0.0006 & 0.0008 & 0.0006 & 0.0010 \\ -0.0002 & 0.0008 & 0.0008 & -0.0002 & 0.0008 & -0.0004 & 0.0008 & -0.0002 \\ \end{bmatrix} Wo= 0.00090.00090.00050.00020.00050.00050.00040.00080.00000.00080.00070.00080.00010.00090.00080.00020.00010.00010.00060.00080.00040.00040.00080.00040.00050.00020.00060.00080.00070.00040.00100.0002

bbb全部初始化为单位列向量即

bf=bi=bc=bo=[1111]T b_f = b_i = b_c = b_o = \begin{bmatrix} 1 \\ 1 \\ 1 \\ 1 \end{bmatrix}^T bf=bi=bc=bo= 1111 T

然后我们将h0h_0h0x1x_1x1拼在一起作为 y1⃗\vec{y_1}y1 ,即
y1⃗=[h0;x1⃗]=[00003038.61183021.97753044.94383016.5168]T \vec{y_1} = [h_0; \vec{x_1}] = \begin{bmatrix} 0 & 0 & 0 & 0 & 3038.6118 & 3021.9775 & 3044.9438 & 3016.5168 \end{bmatrix}^T y1 =[h0;x1 ]=[00003038.61183021.97753044.94383016.5168]T

我们依次计算遗忘门f1f_1f1,输入门i1i_1i1,输出门o1o_1o1,即

f1=σ(Wfy1⃗+bf)=[0.00080.99850.97130.1164],i1=σ(Wiy1⃗+bi)=[0.85140.00100.05680.6491],o1=σ(Woy1⃗+bo)=[0.01980.95770.99810.9842] f_1 = \sigma(W_f\vec{y_1} + b_f) = \begin{bmatrix} 0.0008 \\ 0.9985 \\ 0.9713 \\ 0.1164 \end{bmatrix}, i_1 = \sigma(W_i\vec{y_1} + b_i) = \begin{bmatrix} 0.8514 \\ 0.0010 \\ 0.0568 \\ 0.6491 \end{bmatrix}, o_1 = \sigma(W_o\vec{y_1} + b_o) = \begin{bmatrix} 0.0198 \\ 0.9577 \\ 0.9981 \\ 0.9842 \end{bmatrix} f1=σ(Wfy1 +bf)= 0.00080.99850.97130.1164 ,i1=σ(Wiy1 +bi)= 0.85140.00100.05680.6491 ,o1=σ(Woy1 +bo)= 0.01980.95770.99810.9842

随后我们进行计算当前输入单元状态c1~\tilde{c_1}c1~,即

c1~=tanh(Wcy1⃗+bc)=[0.7923−0.99970.9805−0.9994]T \tilde{c_1} = \text{tanh}(W_c\vec{y_1} + b_c) = \begin{bmatrix} 0.7923 & -0.9997 & 0.9805 & -0.9994 \end{bmatrix}^T c1~=tanh(Wcy1 +bc)=[0.79230.99970.98050.9994]T

接着我们计算当前时刻单元状态c1c_1c1,即

c1=f1∘c0+i1∘c1~=[0.00080.99850.97130.1164]∘[0000]+[0.85140.00100.05680.6491]∘[0.7923−0.99970.9805−0.9994]=[0.6746−0.0010.0557−0.6488] c_1 = f_1 \circ c_{0} + i_1 \circ \tilde{c_1} = \begin{bmatrix} 0.0008 \\ 0.9985 \\ 0.9713 \\ 0.1164 \end{bmatrix} \circ \begin{bmatrix} 0 \\ 0 \\ 0 \\ 0 \end{bmatrix} + \begin{bmatrix} 0.8514 \\ 0.0010 \\ 0.0568 \\ 0.6491 \end{bmatrix} \circ \begin{bmatrix} 0.7923 \\ -0.9997 \\ 0.9805 \\ -0.9994 \end{bmatrix} = \begin{bmatrix} 0.6746 \\ -0.001 \\ 0.0557 \\ -0.6488 \end{bmatrix} c1=f1c0+i1c1~= 0.00080.99850.97130.1164 0000 + 0.85140.00100.05680.6491 0.79230.99970.98050.9994 = 0.67460.0010.05570.6488

最后我们计算当前层隐藏层输出 h1h_1h1,即

h1=o1∘d1=o1∘tanh(c1)=[0.0116−0.0010.0556−0.5618]T h_1 = o_1 \circ d_1 = o_1 \circ \text{tanh}(c_1) = \begin{bmatrix} 0.0116 & -0.001 & 0.0556 & -0.5618 \end{bmatrix}^T h1=o1d1=o1tanh(c1)=[0.01160.0010.05560.5618]T

这样我们就完成了一次LSTM单元的正向传播计算,我们得到了 h1h_1h1c1c_1c1,我们将其传入下一层。

同理我们可以进行接下来 222个交易日 的计算。
我们将h1h_1h1x2⃗\vec{x_2}x2 拼在一起作为 y2⃗\vec{y_2}y2 ,即

y2⃗=[h1;x2⃗]=[0.0116−0.0010.0556−0.56183029.40283044.82233045.63993019.1238]T \vec{y_2} = [h_1; \vec{x_2}] = \begin{bmatrix} 0.0116 & -0.001 & 0.0556 & -0.5618 & 3029.4028 & 3044.8223 & 3045.6399 & 3019.1238 \end{bmatrix}^T y2 =[h1;x2 ]=[0.01160.0010.05560.56183029.40283044.82233045.63993019.1238]T

我们依次计算遗忘门f2f_2f2,输入门i2i_2i2,输出门o2o_2o2,即

f2=σ(Wfy2⃗+bf)=[0.00080.99850.97150.1151],i2=σ(Wiy2⃗+bi)=[0.85030.00100.05830.6527],o2=σ(Woy2⃗+bo)=[0.01960.95810.9981.9839] f_2 = \sigma(W_f\vec{y_2} + b_f) = \begin{bmatrix} 0.0008 \\ 0.9985 \\ 0.9715 \\ 0.1151 \end{bmatrix}, i_2 = \sigma(W_i\vec{y_2} + b_i) = \begin{bmatrix} 0.8503 \\ 0.0010 \\ 0.0583 \\ 0.6527 \end{bmatrix}, o_2 = \sigma(W_o\vec{y_2} + b_o) = \begin{bmatrix} 0.0196 \\ 0.9581 \\ 0.9981 \\.9839 \end{bmatrix} f2=σ(Wfy2 +bf)= 0.00080.99850.97150.1151 ,i2=σ(Wiy2 +bi)= 0.85030.00100.05830.6527 ,o2=σ(Woy2 +bo)= 0.01960.95810.9981.9839

随后我们进行计算当前输入单元状态c2~\tilde{c_2}c2~,即

c2~=tanh(Wcy2⃗+bc)=[0.7935−0.99980.9806−0.9994]T \tilde{c_2} = \text{tanh}(W_c\vec{y_2} + b_c) = \begin{bmatrix} 0.7935 & -0.9998 & 0.9806 & -0.9994 \end{bmatrix}^T c2~=tanh(Wcy2 +bc)=[0.79350.99980.98060.9994]T

接着我们计算当前时刻单元状态c2c_2c2,即

c2=f2∘c1+i2∘c2~=[0.6747−0.00100.0571−0.6524]T c_2 = f_2 \circ c_{1} + i_2 \circ \tilde{c_2} = \begin{bmatrix} 0.6747 & -0.0010 & 0.0571 & -0.6524 \end{bmatrix}^T c2=f2c1+i2c2~=[0.67470.00100.05710.6524]T

最后我们计算当前层隐藏层输出 h2h_2h2,即

h2=o2∘d2=o2∘tanh(c2)=[0.0115−0.00100.0570−0.5640]T h_2 = o_2 \circ d_2 = o_2 \circ \text{tanh}(c_2) = \begin{bmatrix} 0.0115 & -0.0010 & 0.0570 & -0.5640 \end{bmatrix}^T h2=o2d2=o2tanh(c2)=[0.01150.00100.05700.5640]T

同理我们可以进行接下来 333个交易日 的计算。
我们将h2h_2h2x3⃗\vec{x_3}x3 拼在一起作为 y3⃗\vec{y_3}y3 ,即

y3⃗=[h2;x3⃗]=[0.0115−0.00100.0570−0.56403037.92723052.89993060.26343034.6499]T \vec{y_3} = [h_2; \vec{x_3}] = \begin{bmatrix} 0.0115 & -0.0010 & 0.0570 & -0.5640 & 3037.9272 & 3052.8999 & 3060.2634 & 3034.6499 \end{bmatrix}^T y3 =[h2;x3 ]=[0.01150.00100.05700.56403037.92723052.89993060.26343034.6499]T

我们依次计算遗忘门f3f_3f3,输入门i3i_3i3,输出门o3o_3o3

f3=σ(Wfy3⃗+bf)=[0.00080.99850.97190.1135],i3=σ(Wiy3⃗+bi)=[0.85010.00100.05720.6518],o3=σ(Woy3⃗+bo)=[0.01920.95840.99820.9841] f_3 = \sigma(W_f\vec{y_3} + b_f) = \begin{bmatrix} 0.0008 \\ 0.9985 \\ 0.9719 \\ 0.1135 \end{bmatrix}, i_3 = \sigma(W_i\vec{y_3} + b_i) = \begin{bmatrix} 0.8501 \\ 0.0010 \\ 0.0572 \\ 0.6518 \end{bmatrix}, o_3 = \sigma(W_o\vec{y_3} + b_o) = \begin{bmatrix} 0.0192 \\ 0.9584 \\ 0.9982 \\ 0.9841 \end{bmatrix} f3=σ(Wfy3 +bf)= 0.00080.99850.97190.1135 ,i3=σ(Wiy3 +bi)= 0.85010.00100.05720.6518 ,o3=σ(Woy3 +bo)= 0.01920.95840.99820.9841

随后我们进行计算当前输入单元状态c3~\tilde{c_3}c3~,即

c3~=tanh(Wcy3⃗+bc)=[0.7956−0.99980.9807−0.9994]T \tilde{c_3} = \text{tanh}(W_c\vec{y_3} + b_c) = \begin{bmatrix} 0.7956 & -0.9998 & 0.9807 & -0.9994 \end{bmatrix}^T c3~=tanh(Wcy3 +bc)=[0.79560.99980.98070.9994]T

接着我们计算当前时刻单元状态c3c_3c3,即

c3=f3∘c2+i3∘c3~=[0.6763−0.00100.0561−0.6515]T c_3 = f_3 \circ c_{2} + i_3 \circ \tilde{c_3} = \begin{bmatrix} 0.6763 & -0.0010 & 0.0561 & -0.6515 \end{bmatrix}^T c3=f3c2+i3c3~=[0.67630.00100.05610.6515]T

最后我们计算当前层隐藏层输出 h3h_3h3,即

h3=o3∘d3=o3∘tanh(c3)=[0.0113−0.00100.0559−0.5636]T h_3 = o_3 \circ d_3 = o_3 \circ \text{tanh}(c_3) = \begin{bmatrix} 0.0113 & -0.0010 & 0.0559 & -0.5636 \end{bmatrix}^T h3=o3d3=o3tanh(c3)=[0.01130.00100.05590.5636]T

得到了 h3h_3h3之后,我们可以简单将h3h_3h3的结果作为预测的结果,然后使用MSE进行计算损失,MSE的计算公式如下所示。
MSE=1n∑i=1n(yi^−yi)2 \text{MSE} = \frac{1}{n} \sum_{i = 1}^{n} (\hat{y_i} - y_i )^2 MSE=n1i=1n(yi^yi)2

MSE=14[(3054.9793−0.0113)2+(3088.6357+0.0010)2+(3092.43−0.0559)2+(3054.9793+0.5636)2]=9437756.3022 \text{MSE} = \frac{1}{4} [(3054.9793 - 0.0113)^2 + (3088.6357 + 0.0010)^2 + ( 3092.43 - 0.0559)^2 + (3054.9793 + 0.5636)^2 ] \\ = 9437756.3022 MSE=41[(3054.97930.0113)2+(3088.6357+0.0010)2+(3092.430.0559)2+(3054.9793+0.5636)2]=9437756.3022
然后我们就得到我们的损失为 9437756.30229437756.30229437756.3022

以上就完成了一次将LSTM用于预测的计算。可以看到误差很大,实际应用中会先将数据输入到LSTM前,会进行一次归一化,在LSTM的输出后,会将隐藏层的结果进行一层线性映射,然后使用逆归一化,这样得到结果会比较接近我们的指数。

小结

LSTM模型的具体训练步骤如下:

1.LSTM 单元的输入包含当前时刻的输入vecxtvec{x_t}vecxt、上一时刻的输出状态ht−1h_{t-1}ht1以及上一时刻的单元状态ct−1c_{t-1}ct1。在进行运算第一层LSTM单元时,我们会手动初始化h0h_0h0c0c_0c0,而在后面的LSTM的单元中ht−1h_{t-1}ht1ct−1c_{t-1}ct1,都可以由上一次的LSTM单元获得。其中,xt⃗∈Rm×1\vec{x_t} \in \mathbb{R}^{m \times 1}xt Rm×1mmm是输入特征的维度,ht−1h_{t-1}ht1上一时刻的输出状态,形状为ht−1∈Rd×1h_{t-1} \in \mathbb{R}^{d \times 1}ht1Rd×1ddd是LSTM单元的隐藏状态大小,ct−1c_{t-1}ct1是上一时刻的单元状态,形状为ct−1∈Rd×1c_{t-1} \in \mathbb{R}^{d \times 1}ct1Rd×1

我们通常会把ht−1h_{t-1}ht1xt⃗\vec{x_t}xt 拼在一起形成更长的向量yt⃗\vec{y_t}yt ,我们通常竖着拼,即 yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1}yt R(d+m)×1 ,然后yt⃗\vec{y_t}yt 会传入各个门。
yt⃗=[ht−1;xt⃗]=[ht−1xt⃗] \vec{y_t} = [h_{t-1};\vec{x_t}] = \left[{\begin{matrix}h_{t-1} \\ \vec{x_t} \end{matrix}}\right] yt =[ht1;xt ]=[ht1xt ]

2.随后是计算各个门的输出,各个门的输入是yt⃗\vec{y_t}yt 。我们将yt⃗\vec{y_t}yt 与门中的权重矩阵WWW相乘再加上置偏值bbb,得到中间结果MMM。然后对MMM取Sigmoid,得到门的输出gtg_tgt,其形状与单元状态ctc_tct相同,即 gt∈Rd×1g_t \in \mathbb{R}^{d \times 1}gtRd×1

ft=σ(Wfyt⃗′+bf)=11+e−(Wfyt⃗+bf) f_t = \sigma(W_f\vec{y_t}' + b_f) = \frac{1}{1 + e^{-(W_f\vec{y_t} + b_f)}} ft=σ(Wfyt +bf)=1+e(Wfyt +bf)1
it=σ(Wiyt⃗+bi)=11+e−(Wiyt⃗+bi) i_t = \sigma(W_i\vec{y_t} + b_i) = \frac{1}{1 + e^{-(W_i\vec{y_t} + b_i)}} it=σ(Wiyt +bi)=1+e(Wiyt +bi)1
ot=σ(Woyt⃗+bo)=11+e−(Wfyt⃗+bo) o_t = \sigma(W_o\vec{y_t} + b_o) = \frac{1}{1 + e^{-(W_f\vec{y_t} + b_o)}} ot=σ(Woyt +bo)=1+e(Wfyt +bo)1
其中,yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1}yt R(d+m)×1Wf、Wi、Wo∈Rd×(d+m)W_f、W_i、W_o \in \mathbb{R}^{d \times (d + m)}WfWiWoRd×(d+m)bf、bi、bo∈Rd×1b_f、b_i、b_o \in \mathbb{R}^{d \times 1}bfbiboRd×1ft、it、ot∈Rd×1f_t、i_t、o_t \in \mathbb{R}^{d \times 1}ftitotRd×1

3.计算当前输入单元状态ct~\tilde{c_t}ct~的值,表示当前输入要保留多少内容到记忆中。我们将yt⃗\vec{y_t}yt 与当前时刻状态单元的权重矩阵WcW_cWc相乘再加上置偏值bcb_cbc,得到中间结果McM_cMc,然后对McM_cMc取tanh,得到输出ct~\tilde{c_t}ct~
ct~=tanh(Wcyt⃗+bc)=e(Wcyt⃗+bc)−e−(Wcyt⃗+bc)e(Wcyt⃗+bc)+e−(Wcyt⃗+bc) \tilde{c_t} = \text{tanh}(W_c\vec{y_t} + b_c) = \frac{e^{(W_c\vec{y_t} + b_c)}-e^{-(W_c\vec{y_t} + b_c)}}{e^{(W_c\vec{y_t} + b_c)}+e^{-(W_c\vec{y_t} + b_c)}} ct~=tanh(Wcyt +bc)=e(Wcyt +bc)+e(Wcyt +bc)e(Wcyt +bc)e(Wcyt +bc)

其中,yt⃗∈R(d+m)×1\vec{y_t} \in \mathbb{R}^{(d + m) \times 1}yt R(d+m)×1Wc∈Rd×(d+m)W_c \in \mathbb{R}^{d \times (d + m)}WcRd×(d+m)bc∈Rd×1b_c \in \mathbb{R}^{d \times 1}bcRd×1ct~∈Rd×1\tilde{c_t} \in \mathbb{R}^{d \times 1}ct~Rd×1

4.接下来我们进行当前时刻单元状态ctc_tct的计算。我们使用遗忘门和输入门得到的结果 ftf_tftiti_tit和上一时刻单元状态ct−1c_{t-1}ct1来计算当前时刻单元状态ctc_tct。我们分别将ftf_tftct−1c_{t-1}ct1按元素相乘,iti_titct~\tilde{c_t}ct~按元素相乘,然后再将两者相加得到我们的但钱时刻单元状态ctc_tct
ct=ft∘ct−1+it∘ct~ c_t = f_t \circ c_{t-1} + i_t \circ \tilde{c_t} ct=ftct1+itct~
其中,ft∈Rd×1f_t \in \mathbb{R}^{d \times 1}ftRd×1时遗忘门输出,it∈Rd×1i_t \in \mathbb{R}^{d \times 1}itRd×1是输入门输出,ct~∈Rd×1\tilde{c_{t}} \in \mathbb{R}^{d \times 1}ct~Rd×1是当前输入状态单元,ct−1∈Rd×1c_{t-1} \in \mathbb{R}^{d \times 1}ct1Rd×1 是上一时刻状态单元,∘\circ表示 按元素乘

5.最后模型的输出是hth_tht和当前时刻的单元状态ctc_tct,而hth_tht由当前时刻的单元状态ctc_tct​和输出门的输出oto_tot确定。我们将当前时刻的单元状态ctc_tct取 tanh得到dtd_tdt,然后将 dtd_tdtoto_tot按元素相乘得到最后的hth_tht
ht=ot∘dt=ot∘tanh(ct)=ect−e−ctect+e−ct h_t = o_t \circ d_t = o_t \circ \text{tanh}(c_t) = \frac{e^{c_t}-e^{-c_t}}{e^{c_t}+e^{-c_t}} ht=otdt=ottanh(ct)=ect+ectectect
其中 ht∈Rd×1h_t \in \mathbb{R}^{d \times 1}htRd×1 为当前层隐藏状态,ot∈Rd×1o_t \in \mathbb{R}^{d \times 1}otRd×1为输出门的输出,ct∈Rd×1c_t \in \mathbb{R}^{d \times 1}ctRd×1为当前时刻状态单元。

	import torch
	import torch.nn as nn
	import numpy as np
	import pandas as pd
	import matplotlib.pyplot as plt
	from sklearn.preprocessing import MinMaxScaler
	
	
	# 读取数据
	df = pd.read_csv('sh_data.csv')
	df = df.iloc[-30:, [2, 5, 3, 4]]
	df1 = df[25:28].reset_index(drop=True)
	df2 = df1.reset_index(drop=True)		
	
	data = df[['open', 'close', 'high', 'low']].values.astype(float)
	
	# 标准化数据
	scaler = MinMaxScaler(feature_range=(0, 1))
	data = scaler.fit_transform(data)
	
	# 创建时间序列数据
	def create_sequences(data, time_step=1):
		X, y = [], []
		for i in range(len(data) - time_step):
			X.append(data[i:(i + time_step)])
			y.append(data[i + time_step])
			return np.array(X), np.array(y)
	
	time_step = 2  # 时间步长设置为2天
	X, y = create_sequences(data, time_step)
	
	# 转换为PyTorch张量
	X = torch.FloatTensor(X)
	y = torch.FloatTensor(y)
	
	class LSTM(nn.Module):
		def __init__(self, input_size, hidden_layer_size, output_size):
			super(LSTM, self).__init__()
			self.hidden_layer_size = hidden_layer_size
			self.lstm = nn.LSTM(input_size, hidden_layer_size)
			self.linear = nn.Linear(hidden_layer_size, output_size)
			self.hidden_cell = (torch.zeros(1, 1, self.hidden_layer_size),
			torch.zeros(1, 1, self.hidden_layer_size))
	
		def forward(self, input_seq):
			lstm_out, self.hidden_cell = self.lstm(input_seq.view(len(input_seq), 1, -1), self.hidden_cell)
			predictions = self.linear(lstm_out.view(len(input_seq), -1))
			return predictions[-1]
	
	
	input_size = 4  # 输入特征数量
	hidden_layer_size = 4
	output_size = 4  # 输出特征数量
	
	model = LSTM(input_size=input_size, hidden_layer_size=hidden_layer_size, output_size=output_size)
	loss_function = nn.MSELoss()
	optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
	
	# epochs = 1
	# for i in range(epochs):
	#     for seq, labels in zip(X, y):
	#         optimizer.zero_grad()
	#         model.hidden_cell = (torch.zeros(1, 1, model.hidden_layer_size),
	#                              torch.zeros(1, 1, model.hidden_layer_size))
	#         y_pred = model(seq)
	
	#         single_loss = loss_function(y_pred, labels)
	#         single_loss.backward()
	#         optimizer.step()
	
	#     if i % 10 == 0:
	#         print(f'epoch: {i:3} loss: {single_loss.item():10.8f}')
	
	# 只进行一次训练
	seq, labels = X[0], y[0]
	optimizer.zero_grad()
	model.hidden_cell = (torch.zeros(1, 1, model.hidden_layer_size),
	torch.zeros(1, 1, model.hidden_layer_size))
	y_pred = model(seq)
	single_loss = loss_function(y_pred, labels)
	single_loss.backward()
	optimizer.step()
	
	print(f'Single training loss: {single_loss.item():10.8f}')
	
	model.eval()
	
	# 预测下一天的四个特征
	with torch.no_grad():
		seq = torch.FloatTensor(data[-time_step:])
		model.hidden_cell = (torch.zeros(1, 1, model.hidden_layer_size),
		torch.zeros(1, 1, model.hidden_layer_size))
		next_day = model(seq).numpy()
	
	# 将预测结果逆归一化
	next_day = scaler.inverse_transform(next_day.reshape(-1, output_size))
	
	print(f'Predicted features for the next day: open={next_day[0][0]}, close={next_day[0][1]}, high={next_day[0][2]}, low={next_day[0][3]}')
	
	
	# 获取训练集的预测值
	train_predict = []
	for seq in X:
		with torch.no_grad():
		model.hidden_cell = (torch.zeros(1, 1, model.hidden_layer_size),
		torch.zeros(1, 1, model.hidden_layer_size))
		train_predict.append(model(seq).numpy())
	
	# 将预测结果逆归一化
	train_predict = scaler.inverse_transform(np.array(train_predict).reshape(-1, output_size))
	actual = scaler.inverse_transform(data)
	
	# 绘制图形
	plt.figure(figsize=(10, 6))
	
	for i, col in enumerate(['open', 'close', 'high', 'low']):
		plt.subplot(2, 2, i+1)
		plt.plot(actual[:, i], label=f'Actual {col}')
		plt.plot(range(time_step, time_step + len(train_predict)), train_predict[:, i], label=f'Train Predict {col}')
		plt.legend()
	
	plt.tight_layout()
	plt.show()		
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐