不见春山
骑马倚斜桥,满楼红袖招。
Home
Categories
Archives
Tags
About
Home
RNN与LSTM
RNN与LSTM
取消
RNN与LSTM
由
ctaoist
发布于 2021-06-02
·
最后更新:2023-01-03
1
## RNN 一个简单的RNN模型如下: ![RNN模型](https://blog.qiniu.ctaoist.cn/RNN%E6%A8%A1%E5%9E%8B.jpg) 循环神经网络的隐藏层的值 $S$ 不仅仅取决于当前这次的输入 $X$,还取决于上一次隐藏层的值 $S_{t-1}$。权重矩阵 $ W$ 就是隐藏层上一次的值作为这一次的输入的权重。 RNN 按照时间线展开: ![RNN 时间线展开](https://blog.qiniu.ctaoist.cn/RNN%E6%A8%A1%E5%9E%8B%E6%97%B6%E9%97%B4%E7%BA%BF%E5%B1%95%E5%BC%80.jpg) 网络在 $t$ 时刻接收到输入 X_{t} 之后,隐藏层的值是 $S_t$ ,输出值是 $O_t$ 。关键一点是, $O_t$ 的值不仅仅取决于 $X_{t}$ ,还取决于 $S_{t-1}$ 。 ## LSTM LSTM 的梯度由两部分组成:RNN 结构的梯度和线性变换函数的梯度。线性变换函数的梯度就是函数的斜率,是一个常数。由于线性变换函数梯度的存在,当 RNN 的梯度过小趋近于 0 时,LSTM 的梯度趋向于一个常数。因此,**LSTM 通过引入一个梯度常数的方式避免了梯度消失的问题**。 ![](https://blog.qiniu.ctaoist.cn/机器学习/lstm_结构.jpg) LSTM的关键是细胞状态(直译:cell state),表示为 $C_t$ ,用来保存当前LSTM的状态信息并传递到下一时刻的LSTM中,也就是RNN中那根“自循环”的箭头($h^t$)。当前的LSTM接收来自上一个时刻的细胞状态 $C_{t-1}$,并与当前LSTM接收的信号输入 $x_t$ 共同作用产生当前LSTM的细胞状态 ,具体的作用方式下面将详细介绍。 ![](https://blog.qiniu.ctaoist.cn/机器学习/lstm_cell_单.jpg) 在LSTM中,采用专门设计的**门**来引入或者去除细胞状态 $C_t$ 中的信息。这里所采用的门包含一个 $sigmoid$ 神经网络层和一个按位的乘法操作,如下图所示: ![](https://blog.qiniu.ctaoist.cn/机器学习/lstm_cell_gate_sigmoid.png) 其中黄色方块表示 $sigmoid$ 神经网络层,粉色圆圈表示按位乘法操作(对应元素相乘)。神经网络层可以将输入信号转换为 $0$ 到 $1$ 之间的数值,用来描述有多少量的输入信号可以通过。 LSTM主要包括三个不同的门结构:**遗忘门**、**记忆门**和**输出门**,这三个门用来控制LSTM的信息保留和传递。 ### 遗忘门 ![](https://blog.qiniu.ctaoist.cn/机器学习/lstm_gate_forget.jpg) 包含一个 $Sigmoid$ 神经网络层(黄色方框,神经网络参数为 $W_f, b_f$),接收 $t$ 时刻的输入信号 $x_t$ 和 $t-1$ 时刻LSTM的上一个输出信号 $h_{t-1}$,这两个信号进行拼接以后共同输入到神经网络层中,然后输出信号 $f_t$,$f_t$ 是一个 $0$ 到 $1$ 之间的数值,并与 $C_{t-1}$ 相乘来决定 $C_{t-1}$ 中的哪些信息将被保留,哪些信息将被舍弃。 假设 $C_{t-1} = [0.5, 0.6, 0.4], h_{t-1} = [0.3, 0.8, 0.69], x_t = [0.2, 1.3, 0.7]$, 那么遗忘门的输入信号就是 $h_{t-1}$ 和 $x_t$ 的组合,即 $[h_{t-1}, x_t] = [0.3, 0.8, 0.69, 0.2, 1.3, 0.7]$, 然后通过 Sigmoid 神经网络层输出每一个元素都处于 0 到 1 之间的向量 $f_t = [0.5, 0.1, 0.8]$,注意,此时 $f_t$ 是一个与 $C_{t-1}$ 维数相同的向量,此处为3维,因为**这不是 Sigmoid 激活函数,而是 Sigmoid 神经网络层**。 ### 记忆门 ![](https://blog.qiniu.ctaoist.cn/机器学习/lstm_gate_remember.jpg) 如图所示,记忆门包含2个部分。第一个是包含 Sigmoid 神经网络层(输入门,神经网络网络参数为 $W_i, b_i$)和一个 $tanh$ 神经网络层(神经网络参数为 $W_c, b_c$)。 - Sigmoid 神经网络层的作用很明f显,跟遗忘门一样,它接收 $h_{t-1}$ 和 $x_t$ 作为输入,然后输出一个 0 到 1 之间的数值 $i_t$ 来**决定哪些信息需要被更新**; - Tanh 神经网络层的作用是将输入的 $h_{t-1}$ 和 $x_t$ 整合,得到一个新的状态候选向量 $\bar{C_t}$,$\bar{C_t}$ 的值范围在 -1 到 1 之间。 记忆门的输出由上述两个神经网络层的输出决定,$i_t$ 与 $\bar{C_t}$ 相乘来选择哪些信息将被新加入到 $t$ 时刻的细胞状态 $C_t$ 中。 ### 更新细胞状态 有了遗忘门和记忆门,我们就可以更新细胞状态 $C_t$ 了: ![](https://blog.qiniu.ctaoist.cn/机器学习/lstm_gate_update_cell_state.jpg) ### 输出门 ![](https://blog.qiniu.ctaoist.cn/机器学习/lstm_gate_output.jpg) 和前面类似,得到 $t$ 时刻的 $f_t$。 ### Pytorch 实现 #### 官方实现 ![](https://blog.qiniu.ctaoist.cn/机器学习/lstm_pytorch_官方实现.jpg) ``` nn.LSTM(input_size=10, input_size=20, num_layers=2) ``` 其中的 `num_layers` 是指图片中的 `depth`。 #### 自己实现 $$ \begin{array}{ll} \\ f_t = \sigma(W_{if} x_t + b_{if} + W_{hf} h_{t-1} + b_{hf}) , 遗忘门\\ i_t = \sigma(W_{ii} x_t + b_{ii} + W_{hi} h_{t-1} + b_{hi}) \\ \bar{C_t} = \tanh(W_{ig} x_t + b_{ig} + W_{hg} h_{t-1} + b_{hg}) \\ o_t = \sigma(W_{io} x_t + b_{io} + W_{ho} h_{t-1} + b_{ho}), 输出门 \\ C_t = f_t \odot C_{t-1} + i_t \odot \bar{C_t} \\ h_t = o_t \odot \tanh(C_t) \\ \end{array} $$ $W$ 和 $b$ 又分解成 $W_i$ 和 $W_h$ 等,分别对应输入和隐藏层的权重参数。 ```py import torch import torch.nn as nn class LstmCell(nn.Module): def __init__(self, input_sz: int, hidden_sz: int): super().__init__() self.input_size = input_sz self.hidden_size = hidden_sz #f_t self.W_if = nn.Parameter(torch.Tensor(input_sz, hidden_sz)) self.W_hf = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz)) self.b_f = nn.Parameter(torch.Tensor(hidden_sz)) #i_t self.W_ii = nn.Parameter(torch.Tensor(input_sz, hidden_sz)) self.W_hi = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz)) self.b_i = nn.Parameter(torch.Tensor(hidden_sz)) #c_t self.W_ig = nn.Parameter(torch.Tensor(input_sz, hidden_sz)) self.W_hg = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz)) self.b_g = nn.Parameter(torch.Tensor(hidden_sz)) #o_t self.W_io = nn.Parameter(torch.Tensor(input_sz, hidden_sz)) self.W_ho = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz)) self.b_o = nn.Parameter(torch.Tensor(hidden_sz)) self.init_weights() def init_weights(self): stdv = 1.0 / math.sqrt(self.hidden_size) # 初始值使用标准差为 1/sqrt(n) 的分布 for weight in self.parameters(): weight.data.uniform_(-stdv, stdv) def forward(self, x,init_states=None): """ assumes x.shape represents (batch_size, sequence_size, input_size) """ bs, seq_sz, _ = x.size() hidden_seq = [] if init_states is None: h_t, c_t = (torch.zeros(bs, self.hidden_size).to(x.device), torch.zeros(bs, self.hidden_size).to(x.device)) else: h_t, c_t = init_states for t in range(seq_sz): x_t = x[:, t, :] f_t = torch.sigmoid(x_t @ self.W_if + h_t @ self.W_hf + self.b_f) # 遗忘门 @ 表示数学定义的矩阵乘法 i_t = torch.sigmoid(x_t @ self.W_ii + h_t @ self.W_hi + self.b_i) g_t = torch.tanh(x_t @ self.W_ig + h_t @ self.W_hg + self.b_g) o_t = torch.sigmoid(x_t @ self.W_io + h_t @ self.W_ho + self.b_o) c_t = f_t * c_t + i_t * g_t h_t = o_t * torch.tanh(c_t) hidden_seq.append(h_t.unsqueeze(0)) #reshape hidden_seq p/ retornar hidden_seq = torch.cat(hidden_seq, dim=0) hidden_seq = hidden_seq.transpose(0, 1).contiguous() return hidden_seq, (h_t, c_t) ``` ## 参考 - [[干货]深入浅出LSTM及其Python代码实现](https://zhuanlan.zhihu.com/p/104475016)
机器学习
该博客文章由作者通过
CC BY 4.0
进行授权。
分享
最近更新
群晖升级 ARPL 笔记
本地部署大语言模型
WireGuard 搭建组网教程
LVM 管理
HK1 RBOX X4 电视盒子折腾笔记
热门标签
机器学习
Tensorflow
Linux
VPN
虚拟组网
Router
ROS
嵌入式
C++
C
文章目录
残差网络(ResNet)
tensorflow-gpu 安装笔记