PyTorch实战(7)——循环神经网络
PyTorch实战(7)——循环神经网络
- 0. 前言
- 1. 循环神经网络发展历程
- 1.1 循环神经网络的类型
- 1.2 循环神经网络
- 2. RNN 变体
- 2.1 双向 RNN (Bidirectional RNN)
- 2.2 长短期记忆网络
- 2.3 扩展和双向 LSTM
- 2.4 多维 RNN
- 2.5 堆叠 LSTM
- 2.6 GRU
- 2.7 网格 LSTM
- 2.8 正交门控循环单元
- 3. 训练 RNN 实现情感分析
- 3.1 文本数据集预处理
- 3.2 模型训练
- 4. GRU 和注意力机制
- 4.1 GRU
- 4.2 基于注意力的模型
- 小结
- 系列链接
0. 前言
神经网络作为强大的机器学习工具,能够学习数据集中输入 XXX 与输出 yyy 之间的复杂映射关系。卷积神经网络 (Convolutional Neural Network, CNN) 建立的是 XXX 与 yyy 间的一对一映射关系,即每个输入 XXX 相互独立,每个输出 yyy 也彼此无关。在本节中,我们将探讨循环模型,其处理的数据不再是独立的数据点,而是具有时间关联性的序列数据 [X1,X2,...,Xk][X_1, X_2, ..., X_k][X1,X2,...,Xk] 和 [y1,y2,...,yk][y_1, y_2, ..., y_k][y1,y2,...,yk]。例如 X2X_2X2 依赖于 X1X_1X1,X3X_3X3 同时依赖于 X2X_2X2 和 X1X_1X1,以此类推。这类网络称为循环神经网络 (Recurrent Neural Network
, RNN
)。通过引入循环连接权重,能够有效建模数据的时间特性。这有助于保持状态,如下图所示:
网络能够将时间步 ttt 的中间输出作为时间步 t+1t+1t+1 的输入,同时维护隐藏的内部状态。跨时间步的连接称为循环连接。本节将重点介绍各种循环神经网络架构的发展历程,包括基础 RNN
及其变体、长短期记忆网络 (Long Short-Term Memory
, LSTM
) 和门控循环单元 ( Gated Recurrent Unit
, GRU
)。我们将使用 PyTorch
实现这些架构,并在实际序列建模任务中进行训练和测试。除了模型训练和测试,还将学习如何高效地使用 PyTorch
加载和预处理序列数据。通过本节学习,将能够使用 PyTorch
解决序列数据集的机器学习问题。
1. 循环神经网络发展历程
在本节中,我们将探索循环神经网络的发展历程,讨论和分析架构的演变过程,回顾循环神经网络 (Recurrent Neural Network
, RNN
) 发展的关键节点。在展开介绍之前,我们首先简要回顾不同类型的 RNN
及其与普通前馈神经网络 (Feedforward Neural Network, FFNN) 的关系。
1.1 循环神经网络的类型
与传统监督学习建模一对一关系不同,RNN
能够建模多种类型的输入-输出关系,包括:
- 多对多(即时型,
Instantaneous
),例如命名实体识别,给定一段句子/文本,为其中的词语标注命名实体类别,如人名、机构名、地名等 - 多对多(编码器-解码器型,
Encoder-Decoder
),例如机器翻译(如将英文翻译为中文),输入一句话或一段文本,将其编码为一个固定大小的表示,然后解码该表示,生成另一种语言中等效的句子或文本 - 多对一,例如情感分析,给定一句话或一段文本,将其分类为正面、负面、中性等情感类别
- 一对多,如图像描述生成,给定一张图片,生成描述其内容的句子或文本
- 一对一(实用性较低),如图像分类,通过顺序处理图像像素。
下图展示了这些 RNN
类型与经典前馈神经网络的对比:
可以看到,循环神经网络架构中包含常规神经网络中没有的循环连接,循环连接在时间维度上展开后,能清晰展示其处理序列数据的特性。下图展示了 RNN
在时间折叠和时间展开两种形式下的结构:
在后续学习中,我们将采用时间展开的形式来演示 RNN
架构。在上图中,用粉色标注的 RNN
层实质上是网络的隐藏层。虽然表面看只有一个隐藏层,但当其沿时间维度展开后,实际会形成 TTT 个隐藏层( TTT 代表序列数据的时间步总数)。RNN
的核心优势在于能处理可变长度的序列数据。对于不同长度的序列,常用的处理方法是对较短的序列进行填充,对较长的序列进行截断。
接下来,我们将从基础 RNN
开始,介绍 RNN
架构的发展历程。
1.2 循环神经网络
循环神经网络 (Recurrent Neural Network
, RNN
) 的雏形可追溯至 1982
年的 Hopfield
网络,Hopfield
网络是一种特殊类型的 RNN
,试图模拟人类记忆的工作方式。随后,David Rumelhart
等人正式确立了 RNN
的基础架构,使其具备处理序列和记忆信息的能力。之后的改进历程如下图所示:
虽然该时间轴未涵盖全部演进过程,但标明了关键节点。接下来,我们按时间顺序讨论 RNN
的重要变体,从双向 RNN
开始。
2. RNN 变体
2.1 双向 RNN (Bidirectional RNN)
尽管 RNN
擅长处理序列数据,但研究者发现某些任务(如语言翻译)需要同时考虑前后文信息。例如英语 “I see you” 翻译为法语 “Je te vois” 时,必须完整理解三个英语单词后才能确定法语词序。其中,“te” 表示 “you”,“vois” 表示 “see”。因此,为了正确地将英语翻译成法语,我们需要先知道英语中的三个单词,才能翻译出法语中的第二个和第三个单词。
为了克服这一限制,双向 RNN
在 1997
年被提出。它们与传统 RNN
非常相似,不同之处在于,双向 RNN
内部有两个并行的 RNN
在工作:一个正向处理序列(从开始到结束),另一个逆向处理序列(从结束到开始),其结构如下图所示:
接下来,我们将了解长短期记忆网络 (Long Short-Term Memory
, LSTM
)。
2.2 长短期记忆网络
虽然传统 RNN
能够处理序列数据并具备记忆能力,但它们面临着梯度爆炸和梯度消失的问题。这是由于将循环网络在时间维度展开后,网络变得极深。为了克服这个问题,长短期记忆网络 (Long Short-Term Memory
, LSTM
) 通过引入精巧设计的记忆单元取代传统 RNN
单元。传统的 RNN
单元通常使用 sigmoid
或 tanh
激活函数。这些激活函数能够控制输出值的范围,在 sigmoid
激活函数的情况下,从 0
(无信息流)到 1
(完全信息流),在 tanh
激活函数的情况下,从 -1
到 1
。
Tanh
激活函数的另一个优点是,它能够提供均值为 0
的输出值,并且通常能够产生更大的梯度——这两个因素都有助于加速学习(收敛)。当前时间步的输入会与前一时间步的隐藏状态进行拼接,并应用这些激活函数,如下图所示:
在反向传播过程中,由于梯度在时间展开的RNN单元间连续相乘,会出现梯度持续衰减或膨胀的现象。这使得传统RNN虽然能记忆短序列信息,却难以处理长序列数据——随着序列增长,梯度连乘次数增加,问题会愈发严重。
LSTM
通过使用门控机制来控制输入和输出,从而解决了这个问题。一个 LSTM
层本质上由多个随时间展开的 LSTM
单元组成。信息以单元状态的形式在单元间传递。这些单元状态通过三种门控结构(输入门、遗忘门、输出门)进行调控,其核心机制如下图所示。
门控机制控制信息流向下一个单元,同时保留或忘记来自前一个单元的信息:
- 遗忘门:决定保留多少历史记忆(
0
表示完全遗忘,1
表示完整保留) - 输入门:控制新信息的写入比例
- 输出门:调节当前状态的输出强度
LSTM
的出现标志着循环网络的重大突破,因为它们能够有效地处理更长的序列。接下来,我们将讨论一些 LSTM
的改进版本。
2.3 扩展和双向 LSTM
原始 LSTM
仅由输入门和输出门组成。随后,提出了带有遗忘门的扩展 LSTM
,这种 LSTM
是目前最常用的 LSTM
,之后又提出了双向 LSTM
,其概念与双向 RNN
类似。
2.4 多维 RNN
多维 RNN
(multi-dimensional RNN
, MDRNN
) 的创新之处在于,RNN
单元之间的单一循环连接被替换为与数据维度相等数量的连接。例如,在视频处理中,连续的二维图像序列就需要建立二维连接,这显著提升了时空数据的建模能力。
2.5 堆叠 LSTM
尽管单层 LSTM
网络能够克服梯度消失和梯度爆炸的问题,但实践表明,堆叠更多的 LSTM
层在学习复杂模式时更为有效,尤其是在各种序列处理任务中,如语音识别。下图展示了一个具有两个 LSTM
层的堆叠 LSTM
模型:
时间维度自然堆叠的 LSTM
单元,通过空间维度叠加获得深度增强。但这些模型的缺点在于:
- 训练速度显著降低(因深度增加和循环连接)
- 每次迭代需展开时间维度
- 无法并行化训练
2.6 GRU
LSTM
单元有两个状态——内部状态和外部状态——以及三个不同的门控——输入门、遗忘门和输出门。门控循环单元 (Gated Recurrent Unit
, GRU
) 是 LSTM
的轻量级变体,目的是在有效处理梯度爆炸和梯度消失问题的同时,学习长远的依赖关系。GRU
的创新设计包括:
- 状态精简:合并内外状态为单一状态
- 门控简化:
- 重置门(融合输入门和遗忘门功能)
- 更新门(控制信息流动)
下图展示了一个 GRU
网络:
2.7 网格 LSTM
网格 LSTM
模型是 MDLSTM
模型的改进,成为多维 RNN
的 LSTM
等效模型。在网格 LSTM
模型中,将 LSTM
单元排布为多维网格,这些单元沿着数据的时空维度以及网络层之间进行连接。
2.8 正交门控循环单元
正交门控循环单元将 GRU
和单位 RNN
(Unitary RNN
) 的思想结合起来。单位 RNN
基于使用单位矩阵(即正交矩阵)作为 RNN
的隐藏状态循环矩阵来解决梯度爆炸和梯度消失问题。这种方法的有效性在于,梯度的偏差归因于隐藏层到隐藏层的权重矩阵的特征值偏离 1
。为了解决这一问题,这些矩阵替换为正交矩阵。
我们已经简要介绍了循环神经网络架构的演变。接下来,我们将通过一个基于 RNN
模型的文本分类实战来深入探讨 RNN
。我们还将探索 PyTorch
处理序列数据的核心方法、RNN
模型的构建与评估、以及循环网络在实际任务中的表现分析。
3. 训练 RNN 实现情感分析
本节将使用 PyTorch
框架训练 RNN
模型完成文本分类任务——情感分析。在这个任务中,接收文本序列作为输入,输出 1
(积极情感)或 0
(消极情感)。为了实现文本到数值的转换,我们需要借助分词和词嵌入 (embedding
) 技术。
分词是将单词转换为数字索引的过程。经过处理后,每个句子可表示为数字序列,数组中的每个数字代表一个单词。虽然分词提供了每个单词的整数索引,但我们仍然希望将每个单词表示为一个数字向量,作为单词特征空间中的一个特征。这是由于单个数字无法完整表达单词语义信息,我们需要通过可训练的嵌入矩阵将每个单词映射为多维向量,将单词表示为向量的过程称为嵌入。嵌入矩阵可以在模型训练过程中学习,作为单词向量的查找表。例如索引为 123
的单词,其向量表示就是嵌入矩阵第 123
行的向量值。
本节中,将采用单层单向 RNN
结构处理这个二分类任务。在训练模型之前,需要先将原始文本数据转换为数值形式。在训练模型后,对示例文本进行测试。
3.1 文本数据集预处理
(1) 首先,导入所需库:
import os
import time
import numpy as np
from tqdm import tqdm
from string import punctuation
from collections import Counter
import matplotlib.pyplot as pltimport torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
除了导入 torch
,还导入了用于文本处理的 punctuation
和 Counter
,matplotlib
用于显示图像,numpy
用于数组操作,以及 tqdm
用于可视化进度条。
(2) 接下来,从文本文件中读取数据。对在本节,我们将使用 IMDb
情感分析数据集,IMDb
数据集包含若干电影评论文本以及相应的情感标签(积极或消极)。首先,下载该数据集,读取并存储文本列表和相应的情感标签:
review_list = []
label_list = []
for label in ['pos', 'neg']:for fname in tqdm(os.listdir(f'./aclImdb/train/{label}/')):if 'txt' not in fname:continuewith open(os.path.join(f'./aclImdb/train/{label}/', fname), encoding="utf8") as f:review_list += [f.read()]label_list += [label]
print ('Number of reviews :', len(review_list))
输出结果如下所示:
Number of reviews : 25000
(3) 数据加载完成,开始处理文本数据:
review_list = [review.lower() for review in review_list]
review_list = [''.join([letter for letter in review if letter not in punctuation]) for review in tqdm(review_list)]reviews_blob = ' '.join(review_list)
review_words = reviews_blob.split()
count_words = Counter(review_words)total_review_words = len(review_words)
sorted_review_words = count_words.most_common(total_review_words)print(sorted_review_words[:10])
输出结果如下所示:
[('the', 334691), ('and', 162228), ('a', 161940), ('of', 145326), ('to', 135042), ('is', 106855), ('in', 93028), ('it', 77099), ('i', 75719), ('this', 75190)]
可以看到,首先我们将整个文本语料库转换为小写字母,并从评论文本中删除所有标点符号。然后,统计词频生成词汇表。
(4) 继续处理数据,建立单词到索引的映射关系。这一步骤至关重要,因为机器学习模型只理解数字,而不理解单词:
vocab_to_token = {word:idx+1 for idx, (word, count) in enumerate(sorted_review_words)}
print(list(vocab_to_token.items())[:10])
输出结果如下所示:
[('the', 1), ('and', 2), ('a', 3), ('of', 4), ('to', 5), ('is', 6), ('in', 7), ('it', 8), ('i', 9), ('this', 10)]
(5) 我们已获得单词到整数的映射关系(即词汇表),接下来,使用该词汇表,将数据集中的电影评论转换为数字序列:
reviews_tokenized = []
for review in review_list:word_to_token = [vocab_to_token[word] for word in review.split()]reviews_tokenized.append(word_to_token)
print(review_list[0])
print()
print (reviews_tokenized[0])
输出结果如下所示:
(6) 将情感标签(积极和消极)转换为数值形式 ,1
和 0
分别表示积极和消极:
encoded_label_list = [1 if label =='pos' else 0 for label in label_list]reviews_len = [len(review) for review in reviews_tokenized]reviews_tokenized = [reviews_tokenized[i] for i, l in enumerate(reviews_len) if l>0 ]
encoded_label_list = np.array([encoded_label_list[i] for i, l in enumerate(reviews_len) if l> 0 ], dtype='float32')
(7) 在训练模型之前,我们需要进行序列长度标准化。由于评论长度不一,而 RNN
模型需要固定长度输入。因此,对不同长度的评论进行标准化,使它们都具有相同的长度。定义一个序列长度 L
(在本节中为 512
),然后对长度小于 L
的序列末尾补零填充,对长度大于 L
的序列截断保留前 512
个词:
def pad_sequence(reviews_tokenized, sequence_length):padded_reviews = np.zeros((len(reviews_tokenized), sequence_length), dtype = int)for idx, review in enumerate(reviews_tokenized):review_len = len(review)if review_len <= sequence_length:zeroes = list(np.zeros(sequence_length-review_len))new_sequence = zeroes+reviewelif review_len > sequence_length:new_sequence = review[0:sequence_length]padded_reviews[idx,:] = np.array(new_sequence)return padded_reviewssequence_length = 512
padded_reviews = pad_sequence(reviews_tokenized=reviews_tokenized, sequence_length=sequence_length)plt.hist(reviews_len)
输出结果如下所示:
(8) 将数据集分成训练集和验证集,比例为 75:25
:
train_val_split = 0.75
train_X = padded_reviews[:int(train_val_split*len(padded_reviews))]
train_y = encoded_label_list[:int(train_val_split*len(padded_reviews))]
validation_X = padded_reviews[int(train_val_split*len(padded_reviews)):]
validation_y = encoded_label_list[int(train_val_split*len(padded_reviews)):]
(9) 使用 PyTorch
根据处理后的数据创建数据加载器对象:
train_dataset = TensorDataset(torch.from_numpy(train_X).to(device), torch.from_numpy(train_y).to(device))
validation_dataset = TensorDataset(torch.from_numpy(validation_X).to(device), torch.from_numpy(validation_y).to(device))batch_size = 32
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
validation_dataloader = DataLoader(validation_dataset, batch_size=batch_size, shuffle=True)
(10) 在模型训练前,检查一个批次的数据( 32
条评论及对应标签):
train_data_iter = iter(train_dataloader)
X_example, y_example = next(train_data_iter)
print('Example Input size: ', X_example.size()) # batch_size, seq_length
print('Example Input:\n', X_example)
print()
print('Example Output size: ', y_example.size()) # batch_size
print('Example Output:\n', y_example)
输出结果如下所示:
将文本数据集加载并处理成数字索引序列之后,接下来使用 PyTorch
创建 RNN
模型对象并进行训练。
3.2 模型训练
PyTorch
的 nn.RNN
模块可以非常简洁地实现 RNN
层,只需指定输入维度、隐藏层维度和层数即可。
(1) 首先,自定义 RNN
模型类,模型由嵌入层、RNN
层以及最后的全连接层组成:
class RNN(nn.Module):def __init__(self, input_dimension, embedding_dimension, hidden_dimension, output_dimension):super().__init__()self.embedding_layer = nn.Embedding(input_dimension, embedding_dimension) self.rnn_layer = nn.RNN(embedding_dimension, hidden_dimension, num_layers=1)self.fc_layer = nn.Linear(hidden_dimension, output_dimension)def forward(self, sequence):embedding = self.embedding_layer(sequence) output, hidden_state = self.rnn_layer(embedding)final_output = self.fc_layer(hidden_state[-1,:,:].squeeze(0)) return final_output
嵌入层使用 nn.Embedding
构建可训练的查找表,存储词嵌入,并通过索引进行检索。在本节中,将嵌入维度设置为 100
。这意味着如果词汇表中有 1000
个单词,那么嵌入查找表的大小为 1000x100
。例如,单词 “it” 在词汇表中索引 8
,则其对应 100
维向量存储在嵌入矩阵第 8
行。也可以使用预训练的嵌入来初始化嵌入查找表,以提高性能,但在本节中,我们从零开始训练嵌入层。
(2) 实例化 RNN
模型:
input_dimension = len(vocab_to_token)+1 # +1 to account for padding
embedding_dimension = 100
hidden_dimension = 32
output_dimension = 1rnn_model = RNN(input_dimension, embedding_dimension, hidden_dimension, output_dimension)optim = torch.optim.Adam(rnn_model.parameters())
loss_func = nn.BCEWithLogitsLoss()rnn_model = rnn_model.to(device)
loss_func = loss_func.to(device)
使用 nn.BCEWithLogitsLoss
模块计算损失。BCEWithLogitsLoss
提供了一个数值稳定的计算过程,整合了 Sigmoid
激活和二元交叉熵损失,二元交叉熵是二分类问题中常用的损失函数。隐藏维度 32
表示 RNN
每个时间步输出的隐藏状态为 32
维向量。
(3) 定义 accuracy_metric()
函数衡量训练模型在验证集上的表现:
def accuracy_metric(predictions, ground_truth):rounded_predictions = torch.round(torch.sigmoid(predictions))success = (rounded_predictions == ground_truth).float() #convert into float for division accuracy = success.sum() / len(success)return accuracy
(4) 定义训练和验证过程:
def train(model, dataloader, optim, loss_func):loss = 0accuracy = 0model.train()for sequence, sentiment in dataloader:optim.zero_grad() preds = model(sequence.T).squeeze()loss_curr = loss_func(preds, sentiment)accuracy_curr = accuracy_metric(preds, sentiment)loss_curr.backward()optim.step()loss += loss_curr.item()accuracy += accuracy_curr.item()return loss/len(dataloader), accuracy/len(dataloader)def validate(model, dataloader, loss_func):loss = 0accuracy = 0model.eval()with torch.no_grad():for sequence, sentiment in dataloader:preds = model(sequence.T).squeeze()loss_curr = loss_func(preds, sentiment) accuracy_curr = accuracy_metric(preds, sentiment)loss += loss_curr.item()accuracy += accuracy_curr.item()return loss/len(dataloader), accuracy/len(dataloader)
(5) 开始训练模型:
num_epochs = 10
best_validation_loss = float('inf')for ep in range(num_epochs):time_start = time.time()training_loss, train_accuracy = train(rnn_model, train_dataloader, optim, loss_func)validation_loss, validation_accuracy = validate(rnn_model, validation_dataloader, loss_func)time_end = time.time()time_delta = time_end - time_start if validation_loss < best_validation_loss:best_validation_loss = validation_losstorch.save(rnn_model.state_dict(), 'rnn_model.pt')print(f'epoch number: {ep+1} | time elapsed: {time_delta}s')print(f'training loss: {training_loss:.3f} | training accuracy: {train_accuracy*100:.2f}%')print(f'validation loss: {validation_loss:.3f} | validation accuracy: {validation_accuracy*100:.2f}%')print()
输出结果如下所示:
(6) 定义辅助函数 sentiment_inference()
来对训练好的模型进行实时推理:
def sentiment_inference(model, sentence):model.eval()sentence = sentence.lower()sentence = ''.join([c for c in sentence if c not in punctuation])tokenized = [vocab_to_token.get(token, 0) for token in sentence.split()]tokenized = np.pad(tokenized, (512-len(tokenized), 0), 'constant')# model inferencemodel_input = torch.LongTensor(tokenized).to(device)model_input = model_input.unsqueeze(1)pred = torch.sigmoid(model(model_input))return pred.item()
(7) 测试该模型在自定义评论文本上的表现:
print(sentiment_inference(rnn_model, "This film is horrible"))
print(sentiment_inference(rnn_model, "Director tried too hard but this film is bad"))
print(sentiment_inference(rnn_model, "This film will be houseful for weeks"))
print(sentiment_inference(rnn_model, "I just really loved the movie"))
输出结果如下所示:
0.028724979609251022
0.3060310184955597
0.9979052543640137
0.9279760122299194
可以看到模型确实捕捉到了正面和负面情感的概念。此外,它能够处理不同长度的序列,即使这些序列都远远短于 512
个单词。
4. GRU 和注意力机制
接下来,我们将简要介绍门控循环单元 (Gated Recurrent Unit
, GRU
) ,分析其与 LSTM
的异同点,展示如何使用 PyTorch
初始化 GRU
模型,并介绍基于注意力机制的 RNN
。最后,我们将说明在序列建模任务中,纯注意力模型(不含循环或卷积结构)如何全面超越各类循环神经网络。
4.1 GRU
GRU
是一种带有两个门控机制(重置门和更新门)以及一个隐藏状态向量的记忆单元。其结构配置比 LSTM
更为简洁,但同样有效地解决了梯度爆炸和梯度消失问题。大量研究已经对 LSTM
和 GRU
的性能进行了比较。大量研究表明:在处理序列任务时,LSTM
和 GRU
都显著优于普通 RNN
,但两者在不同任务中各具优势。
GRU
的训练速度通常快于 LSTM
。在语言建模等任务中,GRU
仅需少量训练数据即可达到与 LSTM
相当的性能。然而,从理论上讲,LSTM
应能比 GRU
保持更长的序列记忆。PyTorch
提供了 nn.GRU
模块用于初始化 GRU
层,以下代码创建了包含双向 GRU
层的神经网络:
self.gru_layer = nn.GRU(input_size, hidden_size, num_layers, bias, dropout, bidirectional)
4.2 基于注意力的模型
循环神经网络曾在序列数据处理领域具有最优异的性能,但 2017
年出现的纯注意力模型使这些循环网络相形见绌。注意力机制源于人类处理序列(如文本)时的认知特性——我们会动态调整对序列不同部分的关注程度。
例如补全句子 “Martha歌声优美,我迷上了____嗓音” 时,“Martha” 会成为关键注意力点,以推测空缺的词可能是“她”;而补全 "Martha歌声优美,我迷上了她的____"时,"歌声"则成为主要关注对象,以推测空缺的词可能是“声音”、“歌曲”、“歌唱”等。
传统循环架构缺乏这种动态聚焦机制,无法通过专注于序列中的特定部分来预测当前时间步的输出,仅能通过压缩的隐藏状态向量来概括历史序列信息。
注意力循环网络通过在常规循环层之上加入了一个额外的注意力层来引入注意力机制。注意力层学习序列中各历史词的注意力权重。通过计算历史隐藏状态的注意力加权平均得到上下文向量,该向量与当前隐藏状态共同参与输出预测。基于注意力的 RNN
架构如下图所示:
该架构在每个时间步计算全局上下文向量,后续改进版则采用局部上下文向量(仅关注前 kkk 个词)。基于注意力的循环神经网络在机器翻译等任务上超越了最先进的循环神经网络模型。
2017
年 <Attention Is All You Need>
论文展示了仅靠注意力机制无需循环层即可解决序列任务。近年来,使用注意力机制的模型在自然语言处理等领域全面超越循环网络,推动深度学习取得重大突破。循环神经网络需要在时间上展开,无法并行计算。而 Transformer
模型完全摒弃循环和卷积结构,兼具并行计算优势和轻量级计算特性。
小结
本节介绍了循环神经网络 (Recurrent Neural Network
, RNN
) 及其在序列数据处理中的应用。首先对比了卷积神经网络 (Convolutional Neural Network, CNN) 与 RNN
的差异,指出 RNN
通过循环连接建模时序依赖关系。随后详细梳理了 RNN
的发展历程,包括基础 RNN
、双向 RNN
、LSTM
、GRU
等变体的结构特点及改进动机,其中 LSTM
通过门控机制解决长程依赖问题,GRU
则进一步简化结构。接着通过 IMDb
情感分析实战,演示了如何使用 PyTorch
实现文本预处理、RNN
模型构建及训练评估流程,最后探讨了注意力机制如何提升 RNN
性能。
系列链接
PyTorch实战(1)——深度学习概述
PyTorch实战(2)——使用PyTorch构建神经网络
PyTorch实战(3)——PyTorch vs. TensorFlow详解
PyTorch实战(4)——卷积神经网络(Convolutional Neural Network,CNN)
PyTorch实战(5)——深度卷积神经网络
PyTorch实战(6)——模型微调详解