使用Pytorch从零开始构建LSTM

长短期记忆(LSTM)网络已被广泛用于解决各种顺序任务。让我们了解这些网络如何工作以及如何实施它们。

就像我们一样,循环神经网络(RNN)也可能很健忘。这种与短期记忆的斗争导致 RNN 在大多数任务中失去有效性。不过,不用担心,长短期记忆网络 (LSTM) 具有出色的记忆力,可以记住普通 RNN 无法记住的信息!

LSTM 是 RNN 的一种特殊变体,因此掌握 RNN 相关的概念将极大地帮助您理解本文中的 LSTM。我在上一篇文章中介绍了 RNN 的机制。

RNN 快速回顾

RNN 以顺序方式处理输入,其中在计算当前步骤的输出时考虑先前输入的上下文。这允许神经网络在不同的时间步长上携带信息,而不是保持所有输入彼此独立。
在这里插入图片描述
然而,困扰典型 RNN 的一个重大缺点是梯度消失/爆炸问题。在训练过程中通过 RNN 反向传播时会出现此问题,特别是对于具有更深层的网络。由于链式法则,梯度在反向传播过程中必须经过连续的矩阵乘法,导致梯度要么呈指数收缩(消失),要么呈指数爆炸(爆炸)。梯度太小会阻碍权重的更新和学习,而梯度太大会导致模型不稳定。

由于这些问题,RNN 无法处理较长的序列并保持长期依赖性,从而使它们遭受“短期记忆”的困扰。

什么是 LSTM

虽然 LSTM 是一种 RNN,其功能与传统 RNN 类似,但它的门控机制使其与众不同。该功能解决了 RNN 的“短期记忆”问题。
在这里插入图片描述
从图中我们可以看出,差异主要在于 LSTM 保存长期记忆的能力。这在大多数自然语言处理 (NLP) 或时间序列和顺序任务中尤其重要。例如,假设我们有一个网络根据给我们的一些输入生成文本。文章开头提到作者有一只“名叫克里夫的狗”。在其他几个没有提到宠物或狗的句子之后,作者再次提到了他的宠物,模型必须生成下一个单词“但是,克里夫,我的宠物____”。由于单词 pet 出现在空白之前,RNN 可以推断出下一个单词可能是可以作为宠物饲养的动物。
hello
然而,由于短期记忆的原因,典型的 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(Ht1,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}) i2=tanh(Wi2(Ht1,xt)+biasi2)
然后将这两层的输出相乘,最终结果表示要保存在长期记忆中并用作输出的信息。
i i n p u t = i 1 ∗ i 2 i_{input} = i_1 * i_2 iinput=i1i2

遗忘门

遗忘门决定应保留或丢弃长期记忆中的哪些信息。这是通过将传入的长期记忆乘以当前输入和传入的短期记忆生成的遗忘向量来完成的。
在这里插入图片描述
就像输入门中的第一层一样,遗忘向量也是一个选择性过滤层。为了获得遗忘向量,短期记忆和当前输入通过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(Ht1,xt)+biasforget)
输入门和遗忘门的输出将进行逐点加法,以给出新版本的长期记忆,并将其传递到下一个单元。这个新的长期记忆也将用于最后一个门,即输出门。
C t = C t − 1 ∗ f + i i n p u t C_t = C_{t-1} * f + i_{input} Ct=Ct1f+iinput

输出门

输出门将采用当前输入、先前的短期记忆和新计算的长期记忆来产生新的短期记忆/隐藏状态 ,该状态将在下一个时间步骤中传递到单元。当前时间步的输出也可以从这个隐藏状态中得出。
在这里插入图片描述
首先,之前的短期记忆和当前输入将再次以不同的权重传递到 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(Ht1,xt)+biasoutput1)O2=tanh(Woutput2Ct+biasoutput2)Ht,Ot=O1O2
这些门产生的短期和长期记忆将被转移到下一个单元,以重复该过程。每个时间步的输出可以从短期记忆中获得,也称为隐藏状态。

这就是典型 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 = 1

lstm_layer = nn.LSTM(input_dim, hidden_dim, n_layers, batch_first=True)

让我们创建一些虚拟数据来查看该层如何接收输入。由于我们的输入维度是5,我们必须创建一个形状为 ( 1, 1, 5 ) 的张量,它表示(批量大小、序列长度、输入维度)。

此外,我们必须初始化 LSTM 的隐藏状态和单元状态,因为这是第一个单元。隐藏状态和单元状态存储在格式为 ( hidden_​​state , cell_state ) 的元组中。

batch_size = 1
seq_len = 1

inp = 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_fn=<StackBackward>), tensor([[[ 0.4167,  0.0385, -0.4982,  0.6955, -0.9213, -1.0072,  0.4426,
          -0.3691,  0.2020, -0.2242]]], grad_fn=<StackBackward>))

在上面的过程中,我们看到了 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  # We're training on the first 800,000 reviews in the dataset
num_test = 200000  # Using 200,000 reviews from test set

train_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/tokens
    train_sentences[i] = []
    for word in nltk.word_tokenize(sentence):  # Tokenizing the words
        words.update([word.lower()])  # Converting all the words to lowercase
        train_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 v>1}
# Sorting the words according to the number of appearances, with the most common word being first
words = sorted(words, key=words.get, reverse=True)
# 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 words
    train_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 well
    test_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),dtype=int)
    for ii, review in enumerate(sentences):
        if len(review) != 0:
            features[ii, -len(review):] = np.array(review)[:seq_len]
    return features

seq_len = 200  # The length that the sentences will be padded/shortened to

train_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 nn

train_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 = 400

train_loader = DataLoader(train_data, shuffle=True, batch_size=batch_size)
val_loader = DataLoader(val_data, shuffle=True, batch_size=batch_size)
test_loader = DataLoader(test_data, shuffle=True, batch_size=batch_size)

我们还可以检查是否有 GPU 可以将训练时间加快很多倍。如果您使用带有 GPU 的 FloydHub 来运行此代码,训练时间将显着减少。

# torch.cuda.is_available() checks and returns a Boolean True if a GPU is available, else it'll return False
is_cuda = torch.cuda.is_available()

# If we have a GPU available, we'll set our device to GPU. We'll 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_prob=0.5):
        super(SentimentNet, self).__init__()
        self.output_size = output_size
        self.n_layers = n_layers
        self.hidden_dim = hidden_dim
        
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, hidden_dim, n_layers, dropout=drop_prob, batch_first=True)
        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, hidden
    
    def init_hidden(self, batch_size):
        weight = next(self.parameters()).data
        hidden = (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 = 2

model = SentimentNet(vocab_size, output_size, embedding_dim, hidden_dim, n_layers)
model.to(device)

lr=0.005
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)

最后,我们可以开始训练模型。每 1000 个步骤,我们将根据验证数据集检查模型的输出,如果模型的表现比前一次更好,则保存模型。

state_dict是 PyTorch 中模型的权重,可以在单独的时间或脚本中加载到具有相同架构的模型中。

epochs = 2
counter = 0
print_every = 1000
clip = 5
valid_loss_min = np.Inf

model.train()
for i in range(epochs):
    h = model.init_hidden(batch_size)
    
    for inputs, labels in train_loader:
        counter += 1
        h = 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(i+1, 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/1
    correct_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.161
       Test accuracy: 93.906%

通过这个简单的 LSTM 模型,我们成功实现了93.8%的准确率!这显示了 LSTM 在处理此类顺序任务方面的有效性。

这个结果是通过几个简单的层实现的,并且没有任何超参数调整。可以进行许多其他改进来提高模型的有效性,并且您可以自由地尝试通过实施这些改进来超越此准确性!

一些改进建议如下:

  • 运行超参数搜索来优化您的配置。可以在此处找到技术指南
  • 增加模型复杂性
  • 添加更多层/使用双向 LSTM 使用预先训练的词嵌入,例如GloVe嵌入

超越 LSTM

多年来,LSTM 在 NLP 任务方面一直是最先进的。然而,基于注意力的模型和Transformer 的最新进展产生了更好的结果。随着 Google 的 BERT 和 OpenAI 的 GPT 等预训练 Transformer 模型的发布,LSTM 的使用量一直在下降。尽管如此,理解 RNN 和 LSTM 背后的概念肯定还是有用的,谁知道,也许有一天 LSTM 会卷土重来呢?

本博文译自Gabriel Loye的博客。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/183346.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决mv3版本浏览器插件,不能注入js脚本问题

文章目录 背景引入ifream解决ifream和父页面完全跨域问题参考链接 背景 浏览器插件升级mv3版本后&#xff0c;不能再使用content_script内容脚本向原浏览器&#xff08;top&#xff09;注入script标签达到注入脚本的目的。浏览器认为插入未经审核的脚本是不安全的行为。 引入…

经常喝羊奶,羊大师告诉你会有何不同

经常喝羊奶&#xff0c;羊大师告诉你会有何不同 羊奶&#xff0c;与人们日常饮用的牛奶相比&#xff0c;一直都没有得到足够的关注。然而&#xff0c;羊奶在一些特定方面却具有独特的优势。它不仅具有丰富的营养价值&#xff0c;还有助于提升人体的健康水平。本文小编羊大师将…

【Skynet 入门实战练习】游戏模块划分 | 基础功能模块 | timer 定时器模块 | logger 日志服务模块

文章目录 游戏模块基础功能模块定时器模块日志模块通用模块 游戏模块 游戏从逻辑方面可以分为下面几个模块&#xff1a; 注册和登录网络协议数据库玩法逻辑其他通用模块 除了逻辑划分&#xff0c;还有几个重要的工具类模块&#xff1a; Excel 配置导表工具GM 指令测试机器人…

【CCF-PTA】第03届Scratch第01题 -- 梦醒时分

梦醒时分 【题目描述】 睡眠是人体正常的生理需要&#xff0c;同年龄男女睡眠时间无明显差别&#xff0c;一般是8小时左右。居家的小明作息生活很规律&#xff0c;晚上11点睡觉&#xff0c;早晨7点起床学习。请你编写程序来判断&#xff0c;每周&#xff08;共168小时&#x…

Elasticsearch:ES|QL 函数及操作符

如果你对 ES|QL 还不是很熟悉的话&#xff0c;请阅读之前的文章 “Elasticsearch&#xff1a;ES|QL 查询语言简介​​​​​​​”。ES|QL 提供了一整套用于处理数据的函数和运算符。 功能分为以下几类&#xff1a; 目录 ES|QL 聚合函数 AVG COUNT COUNT_DISTINCT 计数为近…

【计算机网络笔记】数据链路层概述

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

IAR为恩智浦S32M2提供全面支持,提升电机控制能力

IAR Embedded Workbench for Arm已全面支持恩智浦最新的S32系列&#xff0c;可加速软件定义汽车的车身和舒适性应用的开发 瑞典乌普萨拉&#xff0c;2023年11月22日 – 嵌入式开发软件和服务的全球领导者IAR现已全面支持恩智浦半导体&#xff08;NXP Semiconductors&#xff0…

好工具|datamap,一个好用的地图可视化Excel插件,在Excel中实现地理编码、拾取坐标

在做VRP相关研究的时候&#xff0c;需要对地图数据做很多处理&#xff0c;比如地理编码&#xff0c;根据“重庆市沙坪坝区沙正街174号”这样的一个文本地址知道他的经纬度&#xff1b;再比如绘制一些散点图&#xff0c;根据某个位置的经纬度在地图上把它标注出来。还有有的时候…

Redis-Redis高可用集群之水平扩展

Redis3.0以后的版本虽然有了集群功能&#xff0c;提供了比之前版本的哨兵模式更高的性能与可用性&#xff0c;但是集群的水平扩展却比较麻烦&#xff0c;今天就来带大家看看redis高可用集群如何做水平扩展&#xff0c;原始集群(见下图)由6个节点组成&#xff0c;6个节点分布在三…

一致性 Hash 算法 Hash 环发生偏移怎么解决

本篇是对文章《一文彻底读懂一致性哈希算法》的重写&#xff0c;图文并茂&#xff0c;篇幅较长&#xff0c;欢迎阅读完提供宝贵的建议&#xff0c;一起提升文章质量。如果感觉不错不要忘记点赞、关注、转发哦。原文链接&#xff1a; 《一文彻底读懂一致性Hash 算法》 通过阅读本…

界面组件Telerik UI for WinForms中文教程 - 创建明暗模式的桌面应用

黑暗模式现在在很多应用程序中都挺常见的&#xff0c;但如何在桌面应用程序中实现它呢&#xff1f;这很简单&#xff0c;本文将为大家介绍如何使用一个类和命令行调用来实现&#xff01; Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件。所有的UI fo…

绝地求生:PGC 2023 赛事直播期间最高可获:2000万G-Coins,你还不来吗?

今年PGC直播期间将有最高2000万G-Coin掉落&#xff0c;究竟花落谁家咱们拭目以待 公告原文&#xff1a;Watch PGC 2023 Live And Earn G-Coins! 如何赚取高额G-Coin&#xff1f; Throughout the PGC 2023, an astounding 20,000,000 G-Coins will be up for grabs as part of …

关于「光学神经网络」的一切:理论、应用与发展

/目录/ 一、线性运算的光学实现 1.1. 光学矩阵乘法器 1.2. 光的衍射实现线性运行 1.3. 基于Rayleigh-Sommerfeld方程的实现方法 1.4. 基于傅立叶变换的实现 1.5. 通过光干涉实现线性操作 1.6. 光的散射实现线性运行 1.7. 波分复用&#xff08;WDM&#xff09;实现线性运…

OpenCV快速入门:图像分析——傅里叶变换、积分图像

文章目录 前言一、傅里叶变换1.1 离散傅里叶变换1.1.1 离散傅里叶变换原理1.1.2 离散傅里叶变换公式1.1.3 代码实现1.1.4 cv2.dft 函数解析 1.2 傅里叶变换进行卷积1.2.1 傅里叶变换卷积原理1.2.2 傅里叶变换卷积公式1.2.3 代码实现1.2.4 cv2.mulSpectrums 函数解析 1.3 离散余…

Unity机器学习 ML-Agents第一个例子

上一节我们安装了机器学习mlagents的开发环境&#xff0c;本节我们创建第一个例子&#xff0c;了解什么是机器学习。 我们的例子很简单&#xff0c;就是让机器人自主移动到目标位置&#xff0c;不能移动到地板范围外。 首先我们来简单的了解以下机器学习的过程。 机器学习的过…

读像火箭科学家一样思考笔记07_探月思维

1. 挑战“不可能”的科学与企业 1.1. 互联网 1.1.1. 和电网一样具有革命性&#xff0c;一旦你插上电源&#xff0c;就能让自己的生活充满活力 1.1.2. 互联网的接入可以帮助人们摆脱贫困&#xff0c;拯救生命 1.1.3. 互联网还可以提供与天气相关的信息 1.2. 用廉价、可靠的…

搭配:基于OpenCV的边缘检测实战

引言 计算机中的目标检测与人类识别物体的方式相似。作为人类&#xff0c;我们可以分辨出狗的形象&#xff0c;因为狗的特征是独特的。尾巴、形状、鼻子、舌头等特征综合在一起&#xff0c;帮助我们把狗和牛区分开来。 同样&#xff0c;计算机能够通过检测与估计物体的结构和性…

【Django使用】10大章31模块md文档,第5篇:Django模板和数据库使用

当你考虑开发现代化、高效且可扩展的网站和Web应用时&#xff0c;Django是一个强大的选择。Django是一个流行的开源Python Web框架&#xff0c;它提供了一个坚实的基础&#xff0c;帮助开发者快速构建功能丰富且高度定制的Web应用 全套Django笔记直接地址&#xff1a; 请移步这…

【Docker】从零开始:8.Docker命令:Commit提交命令

【Docker】从零开始&#xff1a;8.Docker命令:Commit命令 基本概念镜像镜像分层什么是镜像分层为什么 Docker 镜像要采用这种分层结构 本章要点commit 命令命令格式docker commit 操作参数实例演示1.下载一个新的ubuntu镜像2.运行容器3.查看并安装vim4.退出容器5提交自己的镜像…

51单片机应用从零开始(八)·循环语句(for循环、while 语句、do‐while 语句)

51单片机应用从零开始&#xff08;七&#xff09;循环语句&#xff08;if语句&#xff0c;swtich语句&#xff09;-CSDN博客 目录 1. 用for 语句控制蜂鸣器鸣笛次数 2. 用while 语句控制 LED 3. 用 do‐while 语句控制 P0 口 8 位 LED 流水点亮 1. 用for 语句控制蜂鸣器鸣笛…