不见春山
骑马倚斜桥,满楼红袖招。
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 笔记
本地部署大语言模型
LVM 管理
HK1 RBOX X4 电视盒子折腾笔记
使用usbip网络转发usb设备到远程主机
热门标签
机器学习
Linux
Router
ROS
Tensorflow
VPN
虚拟组网
ARM
Latex
zerotier
文章目录
残差网络(ResNet)
tensorflow-gpu 安装笔记