许多问题和现象都是基于顺序的。常见的例子包括语音、天气模式和时间序列。这些系统的下一个位置取决于之前的状态。
不幸的是,传统的神经网络无法处理或预测此类数据,因为它们单独分析输入。他们不知道数据确实是连续的。
那么,我们如何预测这类数据呢?
好吧,我们转向称为循环神经网络的东西!
注意:存在使传统神经网络能够处理序列数据的技术方法和技术。但这就像试图把大象塞进鞋盒一样——根本行不通!
什么是循环神经网络?
下图展示了循环神经网络(RNN):
RNN 的示例架构。作者绘制的图表。
左侧是循环神经元,右侧是随时间展开的循环神经元。 RNN 看起来类似于普通的前馈神经网络,除了它接收来自先前向后执行的输入的关键区别之外。
这就是为什么它们被称为“循环”,因为每个步骤的输出都会及时传播,以帮助计算下一步的值。系统中有一些固有的“记忆”,可以帮助模型获取历史模式。
例如,在预测Y_1时,它将使用X_1的输入加上Y_0的上一个时间步的输出。由于Y_0影响Y_1,我们可以看到Y_0也会间接影响Y_2,生动地展示了该算法的循环性质。
隐藏状态
在文献中,您通常会看到隐藏状态的概念,通常用通过循环神经元传递的h表示。
显示具有隐藏状态的 RNN 示例架构。作者绘制的图表。
在简单的情况下,隐藏状态只是单元的输出,因此h=Y。然而,正如我们将在后面的文章中看到的,只有在更复杂的单元(例如长期短记忆 (LSTM)和门控循环单元 GRU)中,这种情况有时才成立。
因此,最好明确我们通过并进入每个神经元的内容,这就是为什么它在大多数文献中都像上面那样显示。
理论
循环神经元的每个隐藏状态可以计算如下:
循环神经元隐藏状态方程。由作者在 LaTeX 中编写。
在哪里:
- h_t是时间t 的隐藏状态。
- h_{t-1}是上一个时间步的隐藏状态。
- x_t是时间t 的输入数据。
- W_h是隐藏状态的权重矩阵。
- W_x是 输入数据的权重矩阵。
- b_h是隐藏状态的偏差向量。
- σ是激活函数,通常为 tanh 或 sigmoid。
注意:这些值可以是标量,但在大多数实际应用中通常是向量;因此,它们被这样表示。
然后每个循环神经元的预测输出为:
循环神经元输出方程。由作者在 LaTeX 中编写。
在哪里:
- y_t是时间t 的输出。
- W_y是与输出相关的权重矩阵。
- b_y是输出偏置向量。
正如您所看到的,许多符号和变量与常规前馈神经网络类似。唯一的区别是隐藏状态的传递,它可以被视为模型的另一个输入或特征,用于预测输出。
每个隐藏层可以包含多个循环神经元,因此我们将隐藏状态向量传递给每个后续输入神经元。这使得网络能够捕获并表示数据中更复杂的模式。您可以将其想象为每个时间步内的迷你神经网络。
工作示例
我们可以回顾一个简单的例子来解释RNN 内部到底发生了什么。这将是一个非常简单的场景,但它将说明您需要了解的主要直觉。事实上,现实生活中没有任何问题会这么简单!
设置
假设我们有一个数字 1、2 和 3 的序列,我们想要训练一个 RNN 来预测序列中的下一个数字,即 4。
我们的 RNN 将具有以下架构:
- 1个输入神经元
- 1个隐藏神经元
- 1个输出神经元
我们可以随机初始化权重和偏差:
- W_x (隐藏权重的输入):0.5
- W_h(隐藏到隐藏权重):1.0
- b_h(隐藏偏差):0
- _(输出偏差):0
并使用以下激活函数:
- 隐藏层:tanh
- 输出层:无(恒等/线性)
初始隐藏状态值:
- h_0 = 0
时间步长 1(输入:1)
第一个隐藏状态计算如下:
第一次隐藏状态更新。由作者在 LaTeX 中编写。
然后输出计算如下:
第一个输出状态。由作者在 LaTeX 中编写。
在这个例子中,输出激活函数是恒等的,因此输出值与隐藏状态值相同。但是,请记住,在许多问题中情况并非总是如此。
时间步长 2(输入:2)
现在,我们可以使用最近计算的h_1值在时间步 2 处对下一个输入值重复上述过程:
第二次隐藏状态更新。由作者在 LaTeX 中编写。
我们再次计算第 2 步的输出值:
第二输出状态。由作者在 LaTeX 中编写。
时间步长 3(输入:3)
最后,对于最后一个输入值和第三个时间步长:
第三次隐藏状态更新。由作者在 LaTeX 中编写。
第三输出状态。由作者在 LaTeX 中编写。
因此,当前模型预测接下来的数字为 0.984,这显然与实际值 4 相距甚远。实际上,我们将拥有更广泛的训练集并随着时间执行反向传播来优化我们的参数。这将在我的下一篇文章中介绍!
幸运的是,所有这些计算和优化都是通过 PyTorch 和 TensorFlow 等软件包在 Python 中完成的。我将在本文后面展示如何执行此操作的示例!
RNN 的类型
上面的例子说明了多对一RNN的逻辑过程。我们从多个输入 (1,2,3) 开始,旨在预测序列中的下一个数字,即单个值。
然而,还有其他类型的 RNN 可用于不同的任务,我们现在将介绍它们。
一对一
这只是一个传统的神经网络,具有一组输入,可给出单个预测。它有助于解决一般的监督机器学习问题。
一对一循环神经网络。作者绘制的图表。
一对多
单个输入导致多个输出。这可用于生成图像标题和生成音乐。
一对多循环神经网络。作者绘制的图表。
多对一
多个输入生成一个最终输出;情感分析是用于该架构的一个示例。你给它一个电影评论,如果电影好或坏,它会分别分配+1或-1。
多对一循环神经网络。作者绘制的图表。
多对多
这个在每一步都会得到一个输入,并在每一步产生一个输出。该架构用于机器翻译以及语音标记等问题。
多对多循环神经网络。作者绘制的图表。
编码器-解码器
最后,您可以拥有一个编码器-解码器网络。这是一个多对一网络,然后是一对多网络。它通常用于将句子从一种语言翻译成另一种语言。
编码器-解码器模型是用于创建 LLM 的转换器模型背后的基础。
编码器-解码器递归神经网络。作者绘制的图表。
PyTorch 示例
下面是在 PyTorch 中实现简单 RNN 的简单示例。它经历了我们上面解决的问题,我们输入了 1,2,3 并想要预测序列中的后续数字。
import torch
import torch.nn as nn
import torch.optim as optim
# RNN Model Definition
class SimpleRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleRNN, self).__init__()
self.hidden_size = hidden_size
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
x = x.unsqueeze(-1)
h_0 = torch.zeros(1, x.size(0), self.hidden_size)
rnn_out, _ = self.rnn(x, h_0)
out = self.fc(rnn_out[:, -1, :])
return out
# Dataset
train = torch.tensor([1, 2, 3, 4], dtype=torch.float32)
target = torch.tensor([5], dtype=torch.float32)
# Model Configuration
input_size = 1
hidden_size = 1
output_size = 1
model = SimpleRNN(input_size, hidden_size, output_size)
# Loss and Optimizer
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
for epoch in range(1000):
optimizer.zero_grad()
output = model(train.unsqueeze(0)).squeeze() # Add batch dimension and squeeze to match target shape
loss = criterion(output, target)
loss.backward()
optimizer.step()
# Function to Predict Next Number
def predict(model, input_seq):
with torch.no_grad():
input_seq = torch.tensor(input_seq, dtype=torch.float32).unsqueeze(0)
output = model(input_seq).squeeze().item()
return output
# Example Test Set
test = [2, 3, 4]
predicted = predict(model, test)
print(f'Input: {test}, Predicted Next Number: {predicted:.2f}')
运行这个,1000 个 epoch 后我们的输出是 5!显然,在这种情况下,模型实际上是通过反向传播训练了 1000 次,这就是为什么它的性能比我们上面手工计算的示例要好得多。
如果您有兴趣,可以在我的 GitHub 上找到该代码:
Medium-Articles/Neural Networks/rnn_example.py at main · egorhowell/Medium-Articles
优点与缺点
有了所有这些新获得的信息,让我们来看看 RNN 的主要优点和缺点:
优点
- 它们具有来自先前输入的记忆形式,这使得它们有助于处理基于序列的数据。
- 确切的权重和偏差在所有时间步长之间共享,从而减少参数并获得更好的泛化能力。
- 由于其递归性质,RNN 可以处理可变长度的序列。
缺点
- 他们严重遭受梯度消失问题的困扰,从而导致长期记忆问题。
- 每个时间步长都取决于前一步的输出,这使得 RNN 的计算效率低下,因为它们无法并行化。
概括
RNN 对于序列建模非常有用,因为它们保留先前执行的信息和内存,然后传播到下一个预测。它们的优点是可以处理任意长度的输入,并且模型大小不会随着输入大小的增加而增加。然而,由于它们具有递归性质,因此无法并行化,因此它们的计算效率不高,并且严重遭受梯度消失问题。