在前面的章节中,我们学习了如何处理静态数据,如图像分类(CNN)和传统机器学习任务。但现实世界中,很多数据都是有序列性的:时间序列数据(如股票价格、天气)、文本数据(如句子、文档)、语音数据等。这些数据的顺序非常重要,前一个元素往往会影响后一个元素的含义。
例如,理解句子"I love machine learning"和"machine learning I love"时,词的顺序完全改变了句子的含义。传统的神经网络无法捕捉这种序列依赖关系,而循环神经网络(Recurrent Neural Network,RNN)正是为此而生。
什么是RNN?
RNN的基本思想
RNN的核心思想是:在处理序列数据时,不仅考虑当前的输入,还要考虑之前的状态信息。
想象你在阅读这句话:
“小明来到公司,打开电脑,开始写代码。他今天的工作重点是____”
要填空"他今天的工作重点是",你需要记住前面的上下文:小明到了公司、打开了电脑、开始写代码。这些信息帮助你推断出答案可能与编程或软件开发相关。
传统的神经网络(如我们在CNN章节学习的)每次都是独立处理输入的,它们没有"记忆"。而RNN有一个隐藏状态(hidden state),就像人类的短期记忆一样,可以记住之前的信息并传递给下一个时间步。
RNN的架构对比
让我用一个生活中的例子来对比传统神经网络和RNN:
传统神经网络:
- 处理"我爱中国"这句话
- 把三个词"我"、“爱”、"中国"分别输入网络
- 每次处理时,网络都不知道前面处理了什么
- 就像一个人每次看一个词,没有上下文记忆
RNN:
- 同样处理"我爱中国"这句话
- 处理"我"时,记住"我"
- 处理"爱"时,结合"我"和"爱"
- 处理"中国"时,结合"我"、“爱"和"中国”
- 就像一个人从左到右读句子,能够记住前面看到的内容
RNN的工作原理
RNN通过"时间步"(time step)的概念来处理序列数据:
在每个时间步,RNN的隐藏状态会被更新,并将信息传递到下一个时间步。这使得RNN能够捕捉序列中的长期依赖关系。
RNN的数学原理
RNN的前向传播
RNN在每个时间步的计算可以分为三个步骤:
步骤1:计算新的隐藏状态
hₜ = tanh(W_hh · hₜ₋₁ + W_xh · xₜ + b_h)
其中:
- hₜ:当前时间步t的隐藏状态
- hₜ₋₁:上一个时间步的隐藏状态
- xₜ:当前时间步的输入
- W_hh:隐藏状态到隐藏状态的权重矩阵
- W_xh:输入到隐藏状态的权重矩阵
- b_h:隐藏状态的偏置项
- tanh:激活函数,将值压缩到-1到1之间
步骤2:计算输出
yₜ = W_hy · hₜ + b_y
其中:
- yₜ:当前时间步的输出
- W_hy:隐藏状态到输出的权重矩阵
- b_y:输出的偏置项
步骤3:计算损失
Lₜ = Loss(yₜ, ŷₜ)
其中:
- ŷₜ:真实的目标值
- Lₜ:当前时间步的损失
总损失是所有时间步损失的平均值:
L = (1/T) · Σₜ Lₜ
RNN的隐藏状态:信息的载体
隐藏状态hₜ是RNN的核心,它承担着"记忆"的角色。在每个时间步,隐藏状态被更新并传递:
- hₜ不仅包含了当前输入xₜ的信息
- 还包含了之前所有历史信息(通过hₜ₋₁传递)
- 就像一个不断累积信息的容器
假设我们要处理一个文本序列"apple banana orange":
最终,h₂包含了整个序列的信息。
RNN的权重共享机制
与传统神经网络不同,RNN在不同时间步使用相同的权重矩阵:
- W_hh、W_xh、W_hy在整个序列的所有时间步都是相同的
- 这意味着RNN在处理序列的每个位置时,使用的是相同的变换规则
- 这种参数共享大大减少了模型参数数量,使得模型能够处理任意长度的序列
这是RNN的一个关键优势:无论输入序列长度是10还是1000,RNN都可以处理,因为参数数量是固定的。
RNN的类型
根据输入和输出序列的不同,RNN可以分为四种类型:
1. 一对一(One-to-One)
这是最简单的情况,输入和输出都是单一元素。实际上这等价于传统的神经网络,不是真正的序列处理。
例子:图像分类
- 输入:一张图片
- 输出:类别标签
2. 一对多(One-to-Many)
输入是单一元素,输出是一个序列。
例子1:图像描述生成
- 输入:一张图片
- 输出:描述图片的句子(“一只猫坐在桌子上”)
例子2:音乐生成
- 输入:起始音符
- 输出:完整的旋律
3. 多对一(Many-to-One)
输入是一个序列,输出是单一元素。
例子1:情感分析
- 输入:一个句子(“这部电影非常精彩!”)
- 输出:情感类别(正面/负面/中性)
例子2:文本分类
- 输入:一篇文章
- 输出:文章类别(体育/娱乐/科技)
4. 多对多(Many-to-Many)
输入和输出都是序列,又可以细分为两种情况:
情况1:同步多对多 每个输入时间步都有对应的输出。
例子:序列标注
- 输入:[“I”, “love”, “machine”, “learning”]
- 输出:[“名词”, “动词”, “名词”, “名词”]
情况2:异步多对多 输出序列的开始时间可能晚于输入序列。
例子:机器翻译
- 输入:中文句子(“我爱你”)
- 输出:英文句子(“I love you”)
RNN的"展开"表示
为了理解RNN的工作原理,我们可以将RNN按时间"展开":
时间步展开视图:
输入: x₀ x₁ x₂ … xₜ
| | | |
v v v v
┌───┐ ┌───┐ ┌───┐ ┌───┐
│RNN│─│RNN│─│RNN│─…──│RNN│
└───┘ └───┘ └───┘ └───┘
| | | |
v v v v
输出: y₀ y₁ y₂ … yₜ
从这个展开视图可以看出:
- 每个时间步都有一个RNN单元
- 前一个RNN单元的隐藏状态传递给下一个RNN单元
- 所有RNN单元共享相同的权重参数
这种展开表示在训练时特别有用,因为它将RNN转换为一个深度网络,可以使用反向传播算法进行训练。
RNN的训练
反向传播通过时间(BPTT)
训练RNN使用的是**反向传播通过时间(Backpropagation Through Time,BPTT)**算法。它是反向传播算法在序列数据上的扩展。
BPTT的步骤:
- 计算输出层的梯度
- 通过时间反向传播,计算隐藏状态的梯度
- 更新权重参数
BPTT的梯度问题
在BPTT中,梯度需要通过时间反向传播,这会导致两个严重问题:
问题1:梯度消失(Vanishing Gradient)
当序列很长时,梯度在反向传播过程中会不断乘以小于1的数(如tanh的导数),导致梯度变得非常小。
后果:
- 早期的隐藏状态对最终输出的影响几乎为零
- RNN无法学习到长距离的依赖关系
- 网络只能记住最近几步的信息
例子: 考虑句子"The man, who … [很多词] …, turned out to be my father."
如果梯度消失,RNN在处理"father"时,可能已经忘记了"man"的信息,导致无法正确理解句子。
问题2:梯度爆炸(Exploding Gradient)
相反,梯度在反向传播过程中可能不断乘以大于1的数,导致梯度变得非常大。
后果:
- 参数更新幅度过大,导致网络不稳定
- 训练过程中损失函数可能出现NaN(Not a Number)
- 模型无法收敛
解决方法:
- 梯度裁剪(Gradient Clipping):限制梯度的最大值
- 更好的激活函数:使用ReLU等避免梯度消失
- 更好的网络架构:使用LSTM、GRU等改进的RNN架构(下一章详细介绍)
RNN的实际应用
应用1:文本生成
RNN可以用于生成文本,通过学习大量文本数据,然后根据前面的内容预测下一个词。
例子:生成古诗
输入:“床前明月光” 输出:“疑是地上霜”
RNN学会了古诗的规律和风格,能够根据前半句生成合理的后半句。
应用2:语音识别
将语音信号(音频序列)转换为文本。
- 输入:音频信号序列
- 输出:文本序列
RNN(或其变体)能够捕捉语音中的时间依赖关系,准确识别连续的语音。
应用3:机器翻译
将一种语言的文本翻译成另一种语言。
- 输入:中文句子(“人工智能正在改变世界”)
- 输出:英文句子(“AI is changing the world”)
现代机器翻译系统通常使用更复杂的架构(如Transformer),但RNN仍然是理解序列建模的基础。
应用4:时间序列预测
预测未来的数值序列,如股票价格、天气、交通流量等。
- 输入:过去N天的股票价格
- 输出:明天的股票价格
RNN能够捕捉时间序列中的趋势和模式,进行准确的预测。
应用5:音乐生成
根据前面的音符序列生成新的音乐。
- 输入:旋律的开始部分
- 输出:完整的音乐
RNN学习了音乐的结构和模式,能够生成连贯的音乐作品。
RNN的实现(PyTorch)
环境准备
首先,确保已安装必要的库:
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader, TensorDataset
# 检查CUDA
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
# 设置随机种子
torch.manual_seed(42)
np.random.seed(42)
简单RNN示例:序列预测
让我们用RNN来解决一个简单的序列预测问题:给定一个正弦波,预测下一个点。
# 生成正弦波数据
def generate_sine_wave(seq_len=1000, freq=0.1):
x = np.arange(0, seq_len)
y = np.sin(x * freq)
return y
# 生成数据
data = generate_sine_wave(seq_len=1000, freq=0.1)
# 可视化数据
plt.figure(figsize=(12, 4))
plt.plot(data[:200])
plt.title('正弦波数据')
plt.xlabel('时间步')
plt.ylabel('值')
plt.grid(True)
plt.show()
# 准备训练数据
def create_sequences(data, seq_length):
sequences = []
targets = []
for i in range(len(data) – seq_length):
seq = data[i:i+seq_length]
target = data[i+seq_length]
sequences.append(seq)
targets.append(target)
return np.array(sequences), np.array(targets)
# 创建序列
seq_length = 50 # 用前50个点预测下一个点
sequences, targets = create_sequences(data, seq_length)
# 转换为PyTorch张量
sequences = torch.FloatTensor(sequences).unsqueeze(–1) # (N, seq_length, 1)
targets = torch.FloatTensor(targets).unsqueeze(–1) # (N, 1)
# 划分训练集和测试集
train_size = int(0.8 * len(sequences))
train_data = sequences[:train_size]
train_targets = targets[:train_size]
test_data = sequences[train_size:]
test_targets = targets[train_size:]
print(f"训练数据形状: {train_data.shape}")
print(f"训练目标形状: {train_targets.shape}")
print(f"测试数据形状: {test_data.shape}")
print(f"测试目标形状: {test_targets.shape}")
# 定义RNN模型
class SimpleRNN(nn.Module):
def __init__(self, input_size=1, hidden_size=32, num_layers=1, output_size=1):
super(SimpleRNN, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# RNN层
self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
# 全连接输出层
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# x形状: (batch_size, seq_length, input_size)
# 初始化隐藏状态
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
# 前向传播
# out形状: (batch_size, seq_length, hidden_size)
# hn形状: (num_layers, batch_size, hidden_size)
out, hn = self.rnn(x, h0)
# 取最后一个时间步的输出
out = out[:, –1, :] # (batch_size, hidden_size)
# 通过全连接层
out = self.fc(out) # (batch_size, output_size)
return out
# 创建模型
model = SimpleRNN(input_size=1, hidden_size=32, num_layers=1, output_size=1).to(device)
print(model)
print(f"模型参数数量: {sum(p.numel() for p in model.parameters()):,}")
# 训练函数
def train_model(model, train_data, train_targets, test_data, test_targets,
num_epochs=100, batch_size=32, learning_rate=0.01):
# 创建数据加载器
train_dataset = TensorDataset(train_data, train_targets)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 记录训练历史
train_losses = []
test_losses = []
# 训练循环
for epoch in range(num_epochs):
model.train()
epoch_loss = 0.0
for batch_x, batch_y in train_loader:
batch_x, batch_y = batch_x.to(device), batch_y.to(device)
# 前向传播
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
# 计算平均损失
train_loss = epoch_loss / len(train_loader)
train_losses.append(train_loss)
# 在测试集上评估
model.eval()
with torch.no_grad():
test_outputs = model(test_data.to(device))
test_loss = criterion(test_outputs, test_targets.to(device)).item()
test_losses.append(test_loss)
# 打印进度
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], '
f'Train Loss: {train_loss:.6f}, Test Loss: {test_loss:.6f}')
return train_losses, test_losses
# 训练模型
train_losses, test_losses = train_model(
model, train_data, train_targets, test_data, test_targets,
num_epochs=100, batch_size=32, learning_rate=0.01
)
# 可视化训练过程
plt.figure(figsize=(10, 4))
plt.plot(train_losses, label='训练损失')
plt.plot(test_losses, label='测试损失')
plt.xlabel('Epoch')
plt.ylabel('损失')
plt.title('训练过程')
plt.legend()
plt.grid(True)
plt.show()
# 模型预测与可视化
model.eval()
with torch.no_grad():
predictions = model(test_data.to(device)).cpu().numpy()
# 可视化预测结果
plt.figure(figsize=(12, 4))
plt.plot(test_targets.numpy(), label='真实值', linewidth=2)
plt.plot(predictions, label='预测值', linewidth=2, linestyle='–')
plt.title('RNN序列预测结果')
plt.xlabel('时间步')
plt.ylabel('值')
plt.legend()
plt.grid(True)
plt.show()
# 计算准确率
mse = np.mean((predictions – test_targets.numpy())**2)
print(f"均方误差(MSE): {mse:.6f}")
文本处理示例:字符级语言模型
让我们用RNN来学习莎士比亚作品的风格,然后生成新的文本。
# 这里使用简化的示例文本
text = """
To be, or not to be, that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take arms against a sea of troubles,
And by opposing end them.
"""
# 创建字符到索引的映射
chars = sorted(list(set(text)))
char_to_idx = {char: idx for idx, char in enumerate(chars)}
idx_to_char = {idx: char for idx, char in enumerate(chars)}
print(f"字符集大小: {len(chars)}")
print(f"字符集: {''.join(chars)}")
# 创建训练样本
def create_text_samples(text, seq_length):
samples = []
targets = []
for i in range(len(text) – seq_length):
sample = text[i:i+seq_length]
target = text[i+seq_length]
samples.append(sample)
targets.append(target)
return samples, targets
seq_length = 50
samples, targets = create_text_samples(text, seq_length)
print(f"\\n训练样本数量: {len(samples)}")
print(f"\\n示例样本:")
print(f"输入: '{samples[0]}'")
print(f"目标: '{targets[0]}'")
# 将文本转换为索引
samples_indices = [[char_to_idx[char] for char in sample] for sample in samples]
targets_indices = [char_to_idx[target] for target in targets]
# 转换为PyTorch张量
samples_tensor = torch.LongTensor(samples_indices)
targets_tensor = torch.LongTensor(targets_indices)
# 创建数据加载器
dataset = TensorDataset(samples_tensor, targets_tensor)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
# 定义字符级RNN模型
class CharRNN(nn.Module):
def __init__(self, vocab_size, hidden_size=128, num_layers=2):
super(CharRNN, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# 嵌入层
self.embedding = nn.Embedding(vocab_size, hidden_size)
# RNN层
self.rnn = nn.RNN(hidden_size, hidden_size, num_layers, batch_first=True)
# 全连接输出层
self.fc = nn.Linear(hidden_size, vocab_size)
def forward(self, x):
# x形状: (batch_size, seq_length)
# 嵌入
x = self.embedding(x) # (batch_size, seq_length, hidden_size)
# 初始化隐藏状态
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
# RNN前向传播
out, hn = self.rnn(x, h0) # out: (batch_size, seq_length, hidden_size)
# 取最后一个时间步
out = out[:, –1, :] # (batch_size, hidden_size)
# 全连接层
out = self.fc(out) # (batch_size, vocab_size)
return out
# 创建模型
model = CharRNN(vocab_size=len(chars), hidden_size=128, num_layers=2).to(device)
print(model)
print(f"模型参数数量: {sum(p.numel() for p in model.parameters()):,}")
# 训练模型
def train_char_model(model, dataloader, num_epochs=100, learning_rate=0.001):
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
train_losses = []
for epoch in range(num_epochs):
model.train()
epoch_loss = 0.0
for batch_x, batch_y in dataloader:
batch_x, batch_y = batch_x.to(device), batch_y.to(device)
# 前向传播
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
# 计算平均损失
avg_loss = epoch_loss / len(dataloader)
train_losses.append(avg_loss)
# 打印进度
if (epoch + 1) % 20 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}')
return train_losses
# 训练模型
train_losses = train_char_model(model, dataloader, num_epochs=200, learning_rate=0.001)
# 可视化训练过程
plt.figure(figsize=(10, 4))
plt.plot(train_losses)
plt.xlabel('Epoch')
plt.ylabel('损失')
plt.title('训练过程')
plt.grid(True)
plt.show()
# 生成文本
def generate_text(model, seed_text, char_to_idx, idx_to_char, length=100, temperature=1.0):
model.eval()
generated_text = seed_text
with torch.no_grad():
for _ in range(length):
# 准备输入
input_seq = torch.LongTensor([[char_to_idx[char] for char in seed_text]]).to(device)
# 前向传播
output = model(input_seq)
# 应用温度
output = output / temperature
# 转换为概率
probabilities = torch.softmax(output, dim=1)
# 采样
next_idx = torch.multinomial(probabilities, 1).item()
next_char = idx_to_char[next_idx]
# 更新生成文本和种子文本
generated_text += next_char
seed_text = seed_text[1:] + next_char
return generated_text
# 生成文本
seed_text = "To be or not to "
generated = generate_text(model, seed_text, char_to_idx, idx_to_char,
length=200, temperature=0.8)
print("原始文本:")
print(text[:200])
print("\\n生成文本:")
print(generated)
RNN的局限性
虽然RNN在处理序列数据方面很强大,但它也存在一些严重的局限性:
1. 梯度消失问题
问题描述: 当序列很长时,早期的梯度在反向传播过程中会逐渐消失,导致RNN无法学习到长距离的依赖关系。
例子: 考虑句子:“The cat, which … [很长的描述] …, sat on the mat.”
当RNN处理到"mat"时,由于梯度消失,可能已经忘记了"cat"的信息,无法正确理解句子。
影响:
- RNN只能记住最近几个时间步的信息
- 难以处理长文本
- 在长序列任务上表现不佳
2. 梯度爆炸问题
问题描述: 梯度在反向传播过程中可能变得非常大,导致参数更新幅度过大,训练不稳定。
影响:
- 损失函数可能出现NaN
- 模型无法收敛
- 需要小心调节学习率和梯度裁剪
3. 训练效率低
问题描述: RNN在每个时间步都需要计算,无法像CNN那样并行计算。
影响:
- 训练速度慢
- 难以处理超长序列
- 计算资源消耗大
4. 上下文理解有限
问题描述: RNN只能单向处理序列,只能看到前面的信息,无法看到后面的上下文。
影响:
- 某些任务需要双向理解(如序列标注)
- 信息流动受限
5. 固定长度的记忆
问题描述: RNN的隐藏状态大小是固定的,无法根据任务需求动态调整记忆容量。
影响:
- 难以处理需要大量记忆的任务
- 信息可能被过早遗忘
解决方案与改进
针对RNN的局限性,研究者提出了多种解决方案:
1. 改进的RNN架构
LSTM(Long Short-Term Memory):
- 引入了"门控"机制(遗忘门、输入门、输出门)
- 能够长期保存和遗忘信息
- 有效缓解梯度消失问题
- 下一章详细介绍
GRU(Gated Recurrent Unit):
- LSTM的简化版本
- 门控机制更简单(更新门、重置门)
- 参数更少,训练更快
- 下一章详细介绍
2. 双向RNN(Bi-RNN)
原理:
- 同时从前向和后向处理序列
- 在每个时间步结合前向和后向的隐藏状态
- 能够看到完整的上下文信息
应用:
- 序列标注任务
- 机器翻译
- 情感分析
代码示例:
# 双向RNN
class BiRNN(nn.Module):
def __init__(self, input_size, hidden_size):
super(BiRNN, self).__init__()
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True, bidirectional=True)
def forward(self, x):
out, hn = self.rnn(x)
return out
model = BiRNN(input_size=1, hidden_size=32)
3. 梯度裁剪
原理:
- 限制梯度的最大值,防止梯度爆炸
- 通常设置梯度范数的上限
代码示例:
# 在训练循环中添加梯度裁剪
optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 梯度裁剪
optimizer.step()
4. 更好的初始化和正则化
方法:
- 使用Xavier或He初始化
- 添加Dropout防止过拟合
- 使用层归一化(Layer Normalization)
5. 注意力机制(Attention)
原理:
- 在处理序列时,动态地关注不同位置的信息
- 不再需要将所有信息压缩到固定大小的隐藏状态中
- 解决了长距离依赖问题
应用:
- 机器翻译
- 文本摘要
- 图像描述生成
发展:
- 2014年:Bahdanau Attention
- 2015年:Luong Attention
- 2017年:Self-Attention(Transformer的核心)
- 第22章详细介绍
RNN vs 其他序列模型
为了更好地理解RNN的特点,让我们比较一下处理序列数据的不同方法:
RNN vs 1D CNN
RNN的优势:
- 天然适合处理变长序列
- 能够捕捉长期依赖
- 适合处理时间序列
1D CNN的优势:
- 可以并行计算,训练速度快
- 感受野大小固定
- 擅长捕捉局部模式
使用场景:
- RNN:需要捕捉长期依赖的任务(如文本生成)
- 1D CNN:更关注局部模式的任务(如语音识别)
RNN vs Transformer
RNN的特点:
- 顺序计算,无法并行
- 擅长处理短序列
- 理解简单直观
Transformer的特点:
- 并行计算,训练速度快
- 自注意力机制,能够捕捉全局依赖
- 能够处理非常长的序列
- 参数量大,需要大量数据
使用场景:
- RNN:小数据集、简单序列任务
- Transformer:大规模数据、复杂序列任务(如机器翻译、文本生成)
实际应用中的选择
选择RNN的情况:
- 数据量较少
- 序列长度适中
- 任务相对简单
- 需要快速原型开发
选择Transformer的情况:
- 大规模数据集
- 非常长的序列
- 复杂的序列到序列任务
- 需要捕捉全局依赖
选择混合方案的情况:
- 使用CNN提取局部特征
- 使用RNN/Transformer处理时序关系
- 结合各自的优势
本章小结
在本章中,我们深入学习了循环神经网络(RNN)这一重要的序列建模工具:
核心概念
实践技能
应用场景
RNN广泛应用于:
- 自然语言处理(文本生成、机器翻译、情感分析)
- 语音处理(语音识别、语音合成)
- 时间序列预测(股票预测、天气预报)
- 音乐生成
- 视频分析
局限性与改进
RNN的主要局限:
- 梯度消失和梯度爆炸
- 无法并行计算
- 长距离依赖问题
改进方案:
- LSTM和GRU(第20章详细介绍)
- 双向RNN
- 注意力机制(第22章详细介绍)
- Transformer(第22-23章详细介绍)
下一步
在下一章中,我们将学习LSTM和GRU这两种改进的RNN架构,它们通过引入门控机制有效解决了RNN的梯度消失问题,能够更好地处理长距离依赖。
思考一下:
为什么RNN在处理长序列时会出现梯度消失问题?这与我们在第14章学习的激活函数有什么关系?
RNN的隐藏状态和传统神经网络的隐藏层有什么区别?
在什么情况下应该选择RNN,而不是CNN或Transformer?
如何设计一个RNN模型来处理股票价格预测任务?
网硕互联帮助中心





评论前必须登录!
注册