淮北市重点工程建设局网站,对网站建设的描述,wordpress 页 定制,如何做网站竞价排名长短期记忆#xff08;LSTM#xff09;网络已被广泛用于解决各种顺序任务。让我们了解这些网络如何工作以及如何实施它们。
就像我们一样#xff0c;循环神经网络#xff08;RNN#xff09;也可能很健忘。这种与短期记忆的斗争导致 RNN 在大多数任务中失去有效性。不过LSTM网络已被广泛用于解决各种顺序任务。让我们了解这些网络如何工作以及如何实施它们。
就像我们一样循环神经网络RNN也可能很健忘。这种与短期记忆的斗争导致 RNN 在大多数任务中失去有效性。不过不用担心长短期记忆网络 (LSTM) 具有出色的记忆力可以记住普通 RNN 无法记住的信息
LSTM 是 RNN 的一种特殊变体因此掌握 RNN 相关的概念将极大地帮助您理解本文中的 LSTM。我在上一篇文章中介绍了 RNN 的机制。
RNN 快速回顾
RNN 以顺序方式处理输入其中在计算当前步骤的输出时考虑先前输入的上下文。这允许神经网络在不同的时间步长上携带信息而不是保持所有输入彼此独立。 然而困扰典型 RNN 的一个重大缺点是梯度消失/爆炸问题。在训练过程中通过 RNN 反向传播时会出现此问题特别是对于具有更深层的网络。由于链式法则梯度在反向传播过程中必须经过连续的矩阵乘法导致梯度要么呈指数收缩消失要么呈指数爆炸爆炸。梯度太小会阻碍权重的更新和学习而梯度太大会导致模型不稳定。
由于这些问题RNN 无法处理较长的序列并保持长期依赖性从而使它们遭受“短期记忆”的困扰。
什么是 LSTM
虽然 LSTM 是一种 RNN其功能与传统 RNN 类似但它的门控机制使其与众不同。该功能解决了 RNN 的“短期记忆”问题。 从图中我们可以看出差异主要在于 LSTM 保存长期记忆的能力。这在大多数自然语言处理 (NLP) 或时间序列和顺序任务中尤其重要。例如假设我们有一个网络根据给我们的一些输入生成文本。文章开头提到作者有一只“名叫克里夫的狗”。在其他几个没有提到宠物或狗的句子之后作者再次提到了他的宠物模型必须生成下一个单词“但是克里夫我的宠物____”。由于单词 pet 出现在空白之前RNN 可以推断出下一个单词可能是可以作为宠物饲养的动物。 然而由于短期记忆的原因典型的 RNN 只能使用最后几句话中出现的文本中的上下文信息——这根本没有用处。RNN 不知道宠物可能是什么动物因为文本开头的相关信息已经丢失。
另一方面LSTM 可以保留作者有一只宠物狗的早期信息这将有助于模型在生成文本时根据大量上下文信息选择“狗” 。较早的时间步长。
LSTM 的内部工作原理
LSTM 的秘密在于每个 LSTM 单元内的门控机制。在普通 RNN 单元中某个时间步的输入和前一个时间步的隐藏状态通过tanh激活函数来获得新的隐藏状态和输出。 另一方面LSTM 的结构稍微复杂一些。在每个时间步LSTM 单元都会接收 3 条不同的信息当前输入数据、前一个单元的短期记忆类似于 RNN 中的隐藏状态以及最后的长期记忆。 短期记忆通常称为隐藏状态长期记忆通常称为细胞状态。
然后在将长期和短期信息传递到下一个单元之前单元使用门来调节每个时间步要保留或丢弃的信息。
这些门可以看作是水过滤器。理想情况下这些门的作用应该是选择性地去除任何不相关的信息类似于水过滤器如何防止杂质通过。同时只有水和有益的营养物质才能通过这些过滤器就像大门只保留有用的信息一样。当然这些门需要经过训练才能准确过滤有用的内容和无用的内容。
这些门称为输入门、遗忘门和输出门。这些门的名称有多种变体。然而这些门的计算和工作原理大部分是相同的。 让我们一一了解这些门的机制。
输入门
输入门决定哪些新信息将存储在长期记忆中。它仅适用于当前输入的信息和上一时间步的短期记忆。因此它必须从这些变量中过滤掉无用的信息。
从数学上讲这是使用2 层来实现的。第一层可以看作是过滤器它选择哪些信息可以通过它以及哪些信息被丢弃。为了创建这一层我们将短期记忆和当前输入传递给sigmoid函数。sigmoid函数会将值转换为0到1之间0表示部分信息不重要而1表示该信息将被使用。这有助于决定要保留和使用的值以及要丢弃的值。当该层通过反向传播进行训练时sigmoid函数中的权重将被更新以便它学会只让有用的特征通过同时丢弃不太重要的特征。 i 1 σ ( W i 1 ⋅ ( H t − 1 , x t ) b i a s i 1 ) i_1 \sigma(W_{i_1} \cdot (H_{t-1}, x_t) bias_{i_1}) i1σ(Wi1⋅(Ht−1,xt)biasi1) 第二层也采用短期记忆和当前输入并将其传递给激活函数通常是 t a n h tanh tanh函数来调节网络。 i 2 t a n h ( W i 2 ⋅ ( H t − 1 , x t ) b i a s i 2 ) i_2 tanh(W_{i_2} \cdot (H_{t-1}, x_t) bias_{i_2}) i2tanh(Wi2⋅(Ht−1,xt)biasi2) 然后将这两层的输出相乘最终结果表示要保存在长期记忆中并用作输出的信息。 i i n p u t i 1 ∗ i 2 i_{input} i_1 * i_2 iinputi1∗i2
遗忘门
遗忘门决定应保留或丢弃长期记忆中的哪些信息。这是通过将传入的长期记忆乘以当前输入和传入的短期记忆生成的遗忘向量来完成的。 就像输入门中的第一层一样遗忘向量也是一个选择性过滤层。为了获得遗忘向量短期记忆和当前输入通过sigmoid函数传递类似于上面输入门中的第一层但具有不同的权重。该向量将由 0 和 1 组成并将与长期记忆相乘以选择要保留长期记忆的哪些部分。 f σ ( W f o r g e t ⋅ ( H t − 1 , x t ) b i a s f o r g e t ) f \sigma(W_{forget} \cdot (H_{t-1}, x_t) bias_{forget}) fσ(Wforget⋅(Ht−1,xt)biasforget) 输入门和遗忘门的输出将进行逐点加法以给出新版本的长期记忆并将其传递到下一个单元。这个新的长期记忆也将用于最后一个门即输出门。 C t C t − 1 ∗ f i i n p u t C_t C_{t-1} * f i_{input} CtCt−1∗fiinput
输出门
输出门将采用当前输入、先前的短期记忆和新计算的长期记忆来产生新的短期记忆/隐藏状态 该状态将在下一个时间步骤中传递到单元。当前时间步的输出也可以从这个隐藏状态中得出。 首先之前的短期记忆和当前输入将再次以不同的权重传递到 sigmoid 函数是的这是我们第三次这样做以创建第三个也是最后一个过滤器。然后我们将新的长期记忆通过激活 t a n h tanh tanh函数。这两个过程的输出将相乘以产生新的短期记忆。 O 1 σ ( W o u t p u t 1 ⋅ ( H t − 1 , x t ) b i a s o u t p u t 1 ) O 2 t a n h ( W o u t p u t 2 ⋅ C t b i a s o u t p u t 2 ) H t , O t O 1 ∗ O 2 O_1 \sigma (W_{output_1} \cdot (H_{t-1}, x_t) bias_{output_1})\\ O_2 tanh(W_{output_2} \cdot C_t bias_{output_2})\\ H_t, O_t O_1 * O_2 O1σ(Woutput1⋅(Ht−1,xt)biasoutput1)O2tanh(Woutput2⋅Ctbiasoutput2)Ht,OtO1∗O2 这些门产生的短期和长期记忆将被转移到下一个单元以重复该过程。每个时间步的输出可以从短期记忆中获得也称为隐藏状态。
这就是典型 LSTM 结构的全部机制。没那么难吧
代码实现
对 LSTM 有了必要的理论了解后让我们开始在代码中实现它。今天我们将使用 PyTorch 库。
在我们进入具有完整数据集的项目之前让我们通过可视化输出来看看 PyTorch LSTM 层在实践中的实际工作原理。我们不需要实例化模型来查看该层如何工作。您可以使用下面的按钮在 FloydHub 上运行此程序LSTM_starter.ipynb。这部分不需要在 GPU 上运行
import torch
import torch.nn as nn就像其他类型的层一样我们可以实例化 LSTM 层并为其提供必要的参数。可以在此处找到已接受参数的完整文档。在此示例中我们将仅定义输入维度、隐藏维度和层数。
输入维度- 表示每个时间步输入的大小例如维度 5 的输入将如下所示 [1, 3, 8, 2, 3]隐藏维度- 表示每个时间步隐藏状态和细胞状态的大小例如如果隐藏维度为 3则隐藏状态和细胞状态都将具有 [3, 5, 4] 的形状层数 - 彼此堆叠的 LSTM 层数
input_dim 5
hidden_dim 10
n_layers 1lstm_layer nn.LSTM(input_dim, hidden_dim, n_layers, batch_firstTrue)让我们创建一些虚拟数据来查看该层如何接收输入。由于我们的输入维度是5我们必须创建一个形状为 ( 1, 1, 5 ) 的张量它表示批量大小、序列长度、输入维度。
此外我们必须初始化 LSTM 的隐藏状态和单元状态因为这是第一个单元。隐藏状态和单元状态存储在格式为 ( hidden_state , cell_state ) 的元组中。
batch_size 1
seq_len 1inp torch.randn(batch_size, seq_len, input_dim)
hidden_state torch.randn(n_layers, batch_size, hidden_dim)
cell_state torch.randn(n_layers, batch_size, hidden_dim)
hidden (hidden_state, cell_state)[Out]:
Input shape: (1, 1, 5)
Hidden shape: ((1, 1, 10), (1, 1, 10))接下来我们将提供输入和隐藏状态看看我们会从中得到什么。
out, hidden lstm_layer(inp, hidden)
print(Output shape: , out.shape)
print(Hidden: , hidden)[Out]: Output shape: torch.size([1, 1, 10])Hidden: (tensor([[[ 0.1749, 0.0099, -0.3004, 0.2846, -0.2262, -0.5257, 0.2925, -0.1894, 0.1166, -0.1197]]], grad_fnStackBackward), tensor([[[ 0.4167, 0.0385, -0.4982, 0.6955, -0.9213, -1.0072, 0.4426,-0.3691, 0.2020, -0.2242]]], grad_fnStackBackward))在上面的过程中我们看到了 LSTM 单元如何在每个时间步处理输入和隐藏状态。然而在大多数情况下我们将以大序列处理输入数据。LSTM 还可以接收可变长度的序列并在每个时间步产生输出。这次我们尝试改变序列长度。
seq_len 3
inp torch.randn(batch_size, seq_len, input_dim)
out, hidden lstm_layer(inp, hidden)
print(out.shape)[Out]: torch.Size([1, 3, 10])这次输出的第二维是 3表明 LSTM 给出了 3 个输出。这对应于我们输入序列的长度。对于我们需要在每个时间步多对多输出的用例例如文本生成每个时间步的输出可以直接从第二维提取并输入到完全连接的层中。对于文本分类任务多对一例如情感分析可以将最后的输出输入到分类器中。 # Obtaining the last output
out out.squeeze()[-1, :]
print(out.shape)[Out]: torch.Size([10])项目亚马逊评论情绪分析
对于此项目我们将使用 Amazon 客户评论数据集该数据集可以在Kaggle上找到。该数据集总共包含 400 万条评论每条评论都标记为正面或负面情绪, 可以在此处找到 GitHub 存储库的链接。
我们实施此项目时的目标是创建一个 LSTM 模型能够准确分类和区分评论的情绪。为此我们必须从一些数据预处理、定义和训练模型开始然后评估模型。
我们的实现流程如下所示。 我们在此实现中仅使用 100 万条评论来加快速度但是如果您有时间和计算能力请随意使用整个数据集自行运行它。
对于我们的数据预处理步骤我们将使用regex、Numpy和NLTK自然语言工具包库来实现一些简单的 NLP 辅助函数。由于数据以bz2格式压缩因此我们将使用 Python bz2模块来读取数据。
import bz2
from collections import Counter
import re
import nltk
import numpy as np
nltk.download(punkt)train_file bz2.BZ2File(../input/amazon_reviews/train.ft.txt.bz2)
test_file bz2.BZ2File(../input/amazon_reviews/test.ft.txt.bz2)train_file train_file.readlines()
test_file test_file.readlines()Number of training reviews: 3600000 Number of test reviews: 400000
该数据集总共包含 400 万条评论其中 360 万条用于训练40 万条用于测试。我们将仅使用 800k 进行训练200k 进行测试——这仍然是大量数据。
num_train 800000 # Were training on the first 800,000 reviews in the dataset
num_test 200000 # Using 200,000 reviews from test settrain_file [x.decode(utf-8) for x in train_file[:num_train]]
test_file [x.decode(utf-8) for x in test_file[:num_test]]句子的格式如下
__label__2 Stunning even for the non-gamer: This soundtrack was beautiful! It paints the scenery in your mind so well I would recommend it even to people who hate vid. game music! I have played the game Chrono Cross but out of all of the games I have ever played it has the best music! It backs away from crude keyboarding and takes a fresher step with great guitars and soulful orchestras. It would impress anyone who cares to listen! _
我们必须从句子中提取标签。数据是格式__label__1/2 因此我们可以轻松地相应地分割它。积极情绪标签存储为 1消极情绪标签存储为 0。
我们还将所有URL更改为标准url因为在大多数情况下确切的URL与情绪无关。
# Extracting labels from sentences
train_labels [0 if x.split( )[0] __label__1 else 1 for x in train_file]
train_sentences [x.split( , 1)[1][:-1].lower() for x in train_file]test_labels [0 if x.split( )[0] __label__1 else 1 for x in test_file]
test_sentences [x.split( , 1)[1][:-1].lower() for x in test_file]# Some simple cleaning of data
for i in range(len(train_sentences)):train_sentences[i] re.sub(\d,0,train_sentences[i])for i in range(len(test_sentences)):test_sentences[i] re.sub(\d,0,test_sentences[i])# Modify URLs to url
for i in range(len(train_sentences)):if www. in train_sentences[i] or http: in train_sentences[i] or https: in train_sentences[i] or .com in train_sentences[i]:train_sentences[i] re.sub(r([^ ](?\.[a-z]{3})), url, train_sentences[i])for i in range(len(test_sentences)):if www. in test_sentences[i] or http: in test_sentences[i] or https: in test_sentences[i] or .com in test_sentences[i]:test_sentences[i] re.sub(r([^ ](?\.[a-z]{3})), url, test_sentences[i])快速清理数据后我们将对句子进行标记化这是标准的 NLP 任务。
标记化是将句子分割成单个标记的任务这些标记可以是单词或标点符号等。
有许多 NLP 库可以做到这一点例如spaCy或Scikit-learn但我们将在这里使用NLTK因为它具有更快的分词器之一。 然后这些单词将被存储在字典中将单词映射到其出现次数。这些词将成为我们的词汇。
words Counter() # Dictionary that will map a word to the number of times it appeared in all the training sentences
for i, sentence in enumerate(train_sentences):# The sentences will be stored as a list of words/tokenstrain_sentences[i] []for word in nltk.word_tokenize(sentence): # Tokenizing the wordswords.update([word.lower()]) # Converting all the words to lowercasetrain_sentences[i].append(word)if i%20000 0:print(str((i*100)/num_train) % done)
print(100% done)为了删除可能不存在的拼写错误和单词我们将从词汇表中删除仅出现一次的所有单词。为了解决未知单词和填充问题我们还必须将它们添加到我们的词汇表中。然后词汇表中的每个单词将被分配一个整数索引然后映射到该整数。
# Removing the words that only appear once
words {k:v for k,v in words.items() if v1}
# Sorting the words according to the number of appearances, with the most common word being first
words sorted(words, keywords.get, reverseTrue)
# Adding padding and unknown to our vocabulary so that they will be assigned an index
words [_PAD,_UNK] words
# Dictionaries to store the word to index mappings and vice versa
word2idx {o:i for i,o in enumerate(words)}
idx2word {i:o for i,o in enumerate(words)}通过映射我们将句子中的单词转换为其相应的索引。
for i, sentence in enumerate(train_sentences):# Looking up the mapping dictionary and assigning the index to the respective wordstrain_sentences[i] [word2idx[word] if word in word2idx else 0 for word in sentence]for i, sentence in enumerate(test_sentences):# For test sentences, we have to tokenize the sentences as welltest_sentences[i] [word2idx[word.lower()] if word.lower() in word2idx else 0 for word in nltk.word_tokenize(sentence)]在最后的预处理步骤中我们将用 0 填充句子并缩短冗长的句子以便可以批量训练数据以加快速度。
# Defining a function that either shortens sentences or pads sentences with 0 to a fixed length
def pad_input(sentences, seq_len):features np.zeros((len(sentences), seq_len),dtypeint)for ii, review in enumerate(sentences):if len(review) ! 0:features[ii, -len(review):] np.array(review)[:seq_len]return featuresseq_len 200 # The length that the sentences will be padded/shortened totrain_sentences pad_input(train_sentences, seq_len)
test_sentences pad_input(test_sentences, seq_len)# Converting our labels into numpy arrays
train_labels np.array(train_labels)
test_labels np.array(test_labels)填充的句子看起来像这样其中 0 代表填充
array([ 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 44, 125, 13, 28, 1701, 5144, 60,31, 10, 3, 44, 2052, 10, 84, 2131, 2,5, 27, 1336, 8, 11, 125, 17, 153, 6,5, 146, 103, 9, 2, 64, 5, 117, 14,7, 42, 1680, 9, 194, 56, 230, 107, 2,7, 128, 1680, 52, 31073, 41, 3243, 14, 3,3674, 2, 11, 125, 52, 10669, 156, 2, 1103,29, 0, 0, 6, 917, 52, 1366, 2, 31,10, 156, 23, 2071, 3574, 2, 11, 12, 7,2954, 9926, 125, 14, 28, 21, 2, 180, 95,132, 147, 9, 220, 12, 52, 718, 56, 2,2339, 5, 272, 11, 4, 72, 695, 562, 4,722, 4, 425, 4, 163, 4, 1491, 4, 1132,1829, 520, 31, 169, 34, 77, 18, 16, 1107,69, 33])我们的数据集已经分为训练数据和测试数据。然而我们在训练过程中仍然需要一组数据进行验证。因此我们将测试数据分成两半分为验证集和测试集。可以在此处找到数据集拆分的详细说明。
split_frac 0.5 # 50% validation, 50% test
split_id int(split_frac * len(test_sentences))
val_sentences, test_sentences test_sentences[:split_id], test_sentences[split_id:]
val_labels, test_labels test_labels[:split_id], test_labels[split_id:]接下来我们将开始使用 PyTorch 库。我们首先从句子和标签定义数据集然后将它们加载到数据加载器中。我们将批量大小设置为 256。这可以根据您的需要进行调整。
import torch
from torch.utils.data import TensorDataset, DataLoader
import torch.nn as nntrain_data TensorDataset(torch.from_numpy(train_sentences), torch.from_numpy(train_labels))
val_data TensorDataset(torch.from_numpy(val_sentences), torch.from_numpy(val_labels))
test_data TensorDataset(torch.from_numpy(test_sentences), torch.from_numpy(test_labels))batch_size 400train_loader DataLoader(train_data, shuffleTrue, batch_sizebatch_size)
val_loader DataLoader(val_data, shuffleTrue, batch_sizebatch_size)
test_loader DataLoader(test_data, shuffleTrue, batch_sizebatch_size)我们还可以检查是否有 GPU 可以将训练时间加快很多倍。如果您使用带有 GPU 的 FloydHub 来运行此代码训练时间将显着减少。
# torch.cuda.is_available() checks and returns a Boolean True if a GPU is available, else itll return False
is_cuda torch.cuda.is_available()# If we have a GPU available, well set our device to GPU. Well use this device variable later in our code.
if is_cuda:device torch.device(cuda)
else:device torch.device(cpu)此时我们将定义模型的架构。在此阶段我们可以创建具有深层或大量相互堆叠的 LSTM 层的神经网络。然而像下面这样的简单模型只有一个 LSTM 和一个全连接层效果很好并且需要的训练时间要少得多。在将句子输入 LSTM 层之前我们将在第一层训练我们自己的词嵌入。 最后一层是一个全连接层具有 sigmoid 函数用于对评论是否具有积极/消极情绪进行分类。
class SentimentNet(nn.Module):def __init__(self, vocab_size, output_size, embedding_dim, hidden_dim, n_layers, drop_prob0.5):super(SentimentNet, self).__init__()self.output_size output_sizeself.n_layers n_layersself.hidden_dim hidden_dimself.embedding nn.Embedding(vocab_size, embedding_dim)self.lstm nn.LSTM(embedding_dim, hidden_dim, n_layers, dropoutdrop_prob, batch_firstTrue)self.dropout nn.Dropout(drop_prob)self.fc nn.Linear(hidden_dim, output_size)self.sigmoid nn.Sigmoid()def forward(self, x, hidden):batch_size x.size(0)x x.long()embeds self.embedding(x)lstm_out, hidden self.lstm(embeds, hidden)lstm_out lstm_out.contiguous().view(-1, self.hidden_dim)out self.dropout(lstm_out)out self.fc(out)out self.sigmoid(out)out out.view(batch_size, -1)out out[:,-1]return out, hiddendef init_hidden(self, batch_size):weight next(self.parameters()).datahidden (weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().to(device),weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().to(device))return hidden请注意我们实际上可以加载预先训练的词嵌入例如GloVe或fastText这可以提高模型的准确性并减少训练时间。
这样我们就可以在定义参数后实例化我们的模型。输出维度仅为 1因为它只需要输出 1 或 0。还定义了学习率、损失函数和优化器。
vocab_size len(word2idx) 1
output_size 1
embedding_dim 400
hidden_dim 512
n_layers 2model SentimentNet(vocab_size, output_size, embedding_dim, hidden_dim, n_layers)
model.to(device)lr0.005
criterion nn.BCELoss()
optimizer torch.optim.Adam(model.parameters(), lrlr)最后我们可以开始训练模型。每 1000 个步骤我们将根据验证数据集检查模型的输出如果模型的表现比前一次更好则保存模型。
state_dict是 PyTorch 中模型的权重可以在单独的时间或脚本中加载到具有相同架构的模型中。
epochs 2
counter 0
print_every 1000
clip 5
valid_loss_min np.Infmodel.train()
for i in range(epochs):h model.init_hidden(batch_size)for inputs, labels in train_loader:counter 1h tuple([e.data for e in h])inputs, labels inputs.to(device), labels.to(device)model.zero_grad()output, h model(inputs, h)loss criterion(output.squeeze(), labels.float())loss.backward()nn.utils.clip_grad_norm_(model.parameters(), clip)optimizer.step()if counter%print_every 0:val_h model.init_hidden(batch_size)val_losses []model.eval()for inp, lab in val_loader:val_h tuple([each.data for each in val_h])inp, lab inp.to(device), lab.to(device)out, val_h model(inp, val_h)val_loss criterion(out.squeeze(), lab.float())val_losses.append(val_loss.item())model.train()print(Epoch: {}/{}....format(i1, epochs),Step: {}....format(counter),Loss: {:.6f}....format(loss.item()),Val Loss: {:.6f}.format(np.mean(val_losses)))if np.mean(val_losses) valid_loss_min:torch.save(model.state_dict(), ./state_dict.pt)print(Validation loss decreased ({:.6f} -- {:.6f}). Saving model ....format(valid_loss_min,np.mean(val_losses)))valid_loss_min np.mean(val_losses)完成训练后是时候在以前从未见过的数据集我们的测试数据集上测试我们的模型了。我们首先从验证损失最低的点加载模型权重。
我们可以计算模型的准确性看看我们的模型的预测有多准确。
# Loading the best model
model.load_state_dict(torch.load(./state_dict.pt))test_losses []
num_correct 0
h model.init_hidden(batch_size)model.eval()
for inputs, labels in test_loader:h tuple([each.data for each in h])inputs, labels inputs.to(device), labels.to(device)output, h model(inputs, h)test_loss criterion(output.squeeze(), labels.float())test_losses.append(test_loss.item())pred torch.round(output.squeeze()) # Rounds the output to 0/1correct_tensor pred.eq(labels.float().view_as(pred))correct np.squeeze(correct_tensor.cpu().numpy())num_correct np.sum(correct)print(Test loss: {:.3f}.format(np.mean(test_losses)))
test_acc num_correct/len(test_loader.dataset)
print(Test accuracy: {:.3f}%.format(test_acc*100))[Out]: Test loss: 0.161Test accuracy: 93.906%通过这个简单的 LSTM 模型我们成功实现了93.8%的准确率这显示了 LSTM 在处理此类顺序任务方面的有效性。
这个结果是通过几个简单的层实现的并且没有任何超参数调整。可以进行许多其他改进来提高模型的有效性并且您可以自由地尝试通过实施这些改进来超越此准确性
一些改进建议如下
运行超参数搜索来优化您的配置。可以在此处找到技术指南增加模型复杂性添加更多层/使用双向 LSTM 使用预先训练的词嵌入例如GloVe嵌入
超越 LSTM
多年来LSTM 在 NLP 任务方面一直是最先进的。然而基于注意力的模型和Transformer 的最新进展产生了更好的结果。随着 Google 的 BERT 和 OpenAI 的 GPT 等预训练 Transformer 模型的发布LSTM 的使用量一直在下降。尽管如此理解 RNN 和 LSTM 背后的概念肯定还是有用的谁知道也许有一天 LSTM 会卷土重来呢 本博文译自Gabriel Loye的博客。