公式代码都有了,速来学LSTM 长短期记忆网络
继续创作,加快成长!这是我参加「日新方案 10 月更文挑战」的第6天,点击检查活动概况
Long Short-Term Memory | MIT Press Journals & Magazine | IEEE Xplore
长短期存储器(long short-term memory, LSTM) 是为了解决RNN梯度爆炸梯度消失问题提出来的,由于RNN每一步都会保存上一步的一些东西,跟着时刻步逐渐变长,离得远的那些信息占比就很小了,所以提出了比如LSTM、GRU等方法来解决这些问题。两者的首要思维是关于前边时刻步的内容有选择地保存,直观可以理解为有用的信息多留一点,没用的恰当丢掉。
先来看一下公式
-
每一步核算都需要三部分内容:
-
上一个时刻步传递过来的回忆单元
-
上一个时刻步步传递过来的隐状况
-
本时刻步的输入
-
-
每一步核算的输出都有两部分内容:
-
本时刻步的回忆单元
-
本时刻步的躲藏状况
-
-
浅蓝色圆圈表明神经网络运用的激活函数
-
深蓝色圆圈表明运算进程
三门
LSTM有三个门,它分别是输入门It\mathbf{I}_t、忘掉门Ft\mathbf{F}_t和输出门Ot\mathbf{O}_t。
假设有 hh 个躲藏单元,批量巨细为 nn,输入数为 dd。
公式如下:
- 其中输入 Xt∈Rnd\mathbf{X}_t \in \mathbb{R}^{n \times d}
- 前一时刻步的躲藏状况为 Ht−1∈Rnh\mathbf{H}_{t-1} \in \mathbb{R}^{n \times h}。
- tt时刻步时, 输入门It∈Rnh\mathbf{I}_t \in \mathbb{R}^{n \times h},忘掉门Ft∈Rnh\mathbf{F}_t \in \mathbb{R}^{n \times h},输出门Ot∈Rnh\mathbf{O}_t \in \mathbb{R}^{n \times h}。
- Wxi,Wxf,Wxo∈Rdh\mathbf{W}_{xi}, \mathbf{W}_{xf}, \mathbf{W}_{xo} \in \mathbb{R}^{d \times h} 和 Whi,Whf,Who∈Rhh\mathbf{W}_{hi}, \mathbf{W}_{hf}, \mathbf{W}_{ho} \in \mathbb{R}^{h \times h} 是权重参数
- bi,bf,bo∈R1h\mathbf{b}_i, \mathbf{b}_f, \mathbf{b}_o \in \mathbb{R}^{1 \times h} 是偏置参数。
- 激活函数仍旧运用sigmoid
当然也可以兼并起来写:
候选回忆单元
长短期回忆网络引入了存储回忆单元(memory cell)。
- Wxc∈Rdh\mathbf{W}_{xc} \in \mathbb{R}^{d \times h} 和 Whc∈Rhh\mathbf{W}_{hc} \in \mathbb{R}^{h \times h} 是权重参数。
- bc∈R1h\mathbf{b}_c \in \mathbb{R}^{1 \times h} 是偏置参数。
- 候选回忆单元运用的激活函数是tanh。
回忆单元
输入门 It\mathbf{I}_t 控制选用多少来自 C~t\tilde{\mathbf{C}}_t 的新数据,而忘掉门 Ft\mathbf{F}_t 控制保存了多少旧回忆单元 Ct−1∈Rnh\mathbf{C}_{t-1} \in \mathbb{R}^{n \times h} 的内容。最终核算成果存储在回忆单元Ct\mathbf{C}_t 中。
- ⊙\odot在这儿的意思是按矩阵元素方位相乘,不是做一般的矩阵运算。
由于输入门、忘掉门他们都运用的sigmoid作为激活函数。因而它们两个的值都是趋近于0或者近于1的。
- 假如忘掉门为 11 且输入门为 00,则曩昔的回忆单元 Ct−1\mathbf{C}_{t-1} 将随时刻被保存并传递到当前时刻步。
- 假如忘掉门为 00 且输入门为 11,则曩昔的回忆单元 Ct−1\mathbf{C}_{t-1} 被丢掉掉,仅运用当前的候选回忆单元C~t\tilde{\mathbf{C}}_t。
引入这种规划是为了缓解梯度消失问题,并更好地捕获序列中的长距离依靠联系。
躲藏单元
输入门忘掉门都介绍了,输出门的作用就在 躲藏单元Ht\mathbf{H}_t 核算这一步。
公式如下:
- 输出门挨近 11,咱们就能够把咱们的回忆单元信息传递下去。
- 输出门挨近 00,咱们只保存存储单元内的所有信息。
代码
之前我还会写手动完成,便是完成以下核算进程,但是实际上其实便是用代码堆出来核算公式,也没什么意思,今后就不搞了,直接写怎么用pytorch完成。
import torch
from torch import nn
from d2l import torch as d2l
from torch.nn import functional as F
导包啊导包,这个不用解说了吧。
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
batch_size, num_steps = 32, 35
这儿直接运用d2l加载数据集。
设定批量巨细batch_size
和时刻步的长度num_steps
,时刻步的长度便是每次LSTM处理的一个序列的长度。
class LSTMModel(nn.Module):
def __init__(self, lstm_layer, vocab_size, **kwargs):
super(LSTMModel, self).__init__(**kwargs)
self.lstm = lstm_layer
self.vocab_size = vocab_size
self.num_hiddens = self.lstm.hidden_size
if not self.lstm.bidirectional:
self.num_directions = 1
self.linear = nn.Linear(self.num_hiddens, self.vocab_size)
else:
self.num_directions = 2
self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)
def forward(self, inputs, state):
X = F.one_hot(inputs.T.long(), self.vocab_size)
X = X.to(torch.float32)
Y, state = self.lstm(X, state)
output = self.linear(Y.reshape((-1, Y.shape[-1])))
return output, state
def begin_state(self, device, batch_size=1):
return (torch.zeros((
self.num_directions * self.lstm.num_layers,
batch_size, self.num_hiddens), device=device),
torch.zeros((
self.num_directions * self.lstm.num_layers,
batch_size, self.num_hiddens), device=device))
-
__init__
初始化这个类,这个类是承继了nn.Module
的。-
self.lstm
设定核算层是LSTM层 -
self.vocab_size
设定字典的巨细,这儿巨细是28,由于为了方便演示,咱们这儿运用的是字母进行分词,所以其中只有a~z26个字母外加和<unk>
(空格和unknown)。 -
self.num_hiddens
设置躲藏层的巨细。或许这儿会导致利诱,我刚开始看的时分也有一会儿的利诱。一般rnn的躲藏层,不是说其他的便是隐状况了吗?
不是这样的,一般rnn是一般躲藏层,现在的GRU。LSTM是含有隐状况的躲藏层。所以仍是躲藏层。 -
if-else语句是设定LSTM单双向的,毕竟还有双向LSTM这种东西的存在。
-
-
forward
定义前向传达网络。也便是描述核算进程。
-
这儿首先是将输入转化为对应的one-hot向量,再将其类型转化为float。
-
Y
和state
是核算隐状况的,这儿Y是输出全部的隐状况,state是输出最终一个时刻步的隐状况。留意 在这儿Y
不是 输出。 -
output
是用于存储输出的。
-
-
begin_state
是进行初始化。这儿是return了好长一个语句。可以拆解开看一会儿。
这儿是初始化为0张量,初始化方位
device=device
由你传入的方位决定是CPU仍是GPU。这儿和一般RNN的区别在于一般RNN和GRU不同,二者只需要返回一个张量即可,但LSTM这儿是一个元组里两个张量。
这段代码看似是写了个LSTM的类,其实是换汤不换药的,便是之前手动简洁完成RNN那个文章里的RNN类改了一会儿。不论是RNN仍是GRU、LSTM,都是在那个类的基础上改的。那个RNN的类写的更完全,具体的可以看→洁完成RNN循环神经网络](简洁完成RNN循环神经网络)。
vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 100, 1
num_inputs = vocab_size
lstm_layer = nn.LSTM(num_inputs, num_hiddens)
model = LSTMModel(lstm_layer, len(vocab))
model = model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)
然后便是咱们的练习和猜测进程。
-
第一句和第二句分别是设置词典长度、躲藏层巨细、运转在CPU仍是GPU上、练习epoch数量、学习率。
-
设置lstm运用pytorch自带的
nn.LSTM
。 -
模型运用咱们的那个类,并且将其放到对应的设备上运转。
-
最终一句便是猜测练习的进程。
下图是100个epoch之后的成果,还没降到底,我这组练习数据要跑400多才能差不多平稳。成果会输出困惑度以及前缀为“time traveler”和“traveler”的猜测成果。