一、历史
长短期记忆(LSTM)是一种特殊的循环神经网络(RNN),由Sepp Hochreiter和Jürgen Schmidhuber于1997年首次提出。该模型的目的是解决传统RNN在长序列训练中的梯度消失和梯度爆炸问题。LSTM通过引入门控机制,使得模型能够自主地选择哪些信息需要保留和哪些信息需要遗忘,从而更好地捕捉序列中的长期依赖关系。
二、优点
LSTM相对于传统的RNN有以下优点:
- 解决了梯度消失和梯度爆炸问题,可以处理长序列数据;
- 引入门控机制,能够自主选择哪些信息需要保留和哪些信息需要遗忘;
- 可以处理多层结构,提高了模型的表达能力。
三、与其他方法的不同之处
相对于其他的序列模型,LSTM的不同之处在于其引入了三个门控单元:输入门、遗忘门和输出门。这些门控单元可以自主地选择哪些信息需要保留和哪些信息需要遗忘,从而更好地捕捉序列中的长期依赖关系。除此之外,LSTM还引入了一个细胞状态,用来存储和传递信息。
四、LSTM的结构
LSTM的结构可以用下面的Mermaid代码表示:
其中,输入门、遗忘门和输出门的计算公式如下:
i t = σ ( W x i x t + W h i h t − 1 + b i ) i_t = \sigma(W_{xi}x_t + W_{hi}h_{t-1} + b_i) it=σ(Wxixt+Whiht−1+bi)
f t = σ ( W x f x t + W h f h t − 1 + b f ) f_t = \sigma(W_{xf}x_t + W_{hf}h_{t-1} + b_f) ft=σ(Wxfxt+Whfht−1+bf)
o t = σ ( W x o x t + W h o h t − 1 + b o ) o_t = \sigma(W_{xo}x_t + W_{ho}h_{t-1} + b_o) ot=σ(Wxoxt+Whoht−1+bo)
其中, x t x_t xt表示输入序列的第 t t t个元素, h t − 1 h_{t-1} ht−1表示上一个时刻的隐状态, W x i W_{xi} Wxi、 W h i W_{hi} Whi、 b i b_i bi分别是输入门的权重矩阵、隐状态的权重矩阵和偏置向量, W x f W_{xf} Wxf、 W h f W_{hf} Whf、 b f b_f bf分别是遗忘门的权重矩阵、隐状态的权重矩阵和偏置向量, W x o W_{xo} Wxo、 W h o W_{ho} Who、 b o b_o bo分别是输出门的权重矩阵、隐状态的权重矩阵和偏置向量, σ \sigma σ表示sigmoid函数。
细胞状态的计算公式如下:
c t = f t c t − 1 + i t tanh ( W x c x t + W h c h t − 1 + b c ) c_t = f_tc_{t-1} + i_t\text{tanh}(W_{xc}x_t + W_{hc}h_{t-1} + b_c) ct=ftct−1+ittanh(Wxcxt+Whcht−1+bc)
其中, c t − 1 c_{t-1} ct−1表示上一个时刻的细胞状态, W x c W_{xc} Wxc、 W h c W_{hc} Whc、 b c b_c bc分别是细胞状态的权重矩阵、隐状态的权重矩阵和偏置向量, tanh \text{tanh} tanh表示双曲正切函数。
更新的计算公式如下:
f t = σ ( W x f x t + W h f h t − 1 + b f ) f_t = \sigma(W_{xf}x_t + W_{hf}h_{t-1} + b_f) ft=σ(Wxfxt+Whfht−1+bf)
i t = σ ( W x i x t + W h i h t − 1 + b i ) i_t = \sigma(W_{xi}x_t + W_{hi}h_{t-1} + b_i) it=σ(Wxixt+Whiht−1+bi)
c ~ t = tanh ( W x c x t + W h c h t − 1 + b c ) \tilde{c}_t = \text{tanh}(W_{xc}x_t + W_{hc}h_{t-1} + b_c) c~t=tanh(Wxcxt+Whcht−1+bc)
其中, c ~ t \tilde{c}_t c~t表示当前时刻的候选细胞状态。
最终输出的计算公式如下:
h t = o t tanh ( c t ) h_t = o_t\text{tanh}(c_t) ht=ottanh(ct)
其中, h t h_t ht表示当前时刻的输出, tanh \text{tanh} tanh表示双曲正切函数。
五、LSTM的实现
下面我们用PyTorch实现一个简单的LSTM模型,以MNIST手写数字识别任务为例。
首先,我们需要导入相关的库:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
然后,我们定义一个LSTM模型:
class LSTM(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, num_classes):
super(LSTM, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, num_classes)
def forward(self, x):
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
out, _ = self.lstm(x, (h0, c0))
out = self.fc(out[:, -1, :])
return out
其中,input_size
表示输入序列的特征维度,hidden_size
表示LSTM的隐藏状态维度,num_layers
表示LSTM的层数,num_classes
表示分类的类别数。在__init__
函数中,我们定义了一个LSTM层和一个全连接层,其中LSTM层的参数由输入的input_size
、hidden_size
和num_layers
决定。在forward
函数中,我们将输入数据x
传入LSTM层,得到最后一个时刻的输出,然后通过全连接层进行分类。
接着,我们定义一些超参数:
input_size = 28
sequence_length = 28
hidden_size = 128
num_layers = 2
num_classes = 10
batch_size = 100
num_epochs = 5
learning_rate = 0.001
其中,input_size
和sequence_length
分别为输入序列的特征维度和长度,hidden_size
为LSTM的隐藏状态维度,num_layers
为LSTM的层数,num_classes
为分类的类别数,batch_size
为每个batch的大小,num_epochs
为训练的轮数,learning_rate
为学习率。
接着,我们加载MNIST数据集:
train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor(), download=True)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
然后,我们定义一个设备变量,用来将模型和数据移到GPU上(如果有的话):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
接着,我们实例化一个LSTM模型,并将其移动到设备上:
model = LSTM(input_size, hidden_size, num_layers, num_classes).to(device)
然后,我们定义一个损失函数和一个优化器:
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
接着,我们训练模型:
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.reshape(-1, sequence_length, input_size).to(device)
labels = labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i + 1) % 100 == 0:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch + 1, num_epochs, i + 1, total_step, loss.item()))
其中,我们将每个图像reshape成一个28x28的序列,并将其移动到设备上。然后,我们将序列传入LSTM模型,得到最后一个时刻的输出,并计算损失函数。接着,我们清空梯度、进行反向传播和更新参数。最后,我们每100个batch打印一次损失函数。
最后,我们测试模型的性能:
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.reshape(-1, sequence_length, input_size).to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Test Accuracy of the model on the 10000 test images: {} %'.format(100 * correct / total))
六、总结
LSTM是一种特殊的循环神经网络,通过引入门控机制,使得模型能够自主地选择哪些信息需要保留和哪些信息需要遗忘,从而更好地捕捉序列中的长期依赖关系。相对于传统的RNN,LSTM具有更好的表达能力和更好的序列建模能力。在实际应用中,LSTM被广泛应用于语音识别、机器翻译、自然语言处理等领域。