什么是循环神经网络?
递归神经网络 (RNN) 是一种人工神经网络,旨在处理顺序数据,例如时间序列数据或自然语言文本。与采用固定输入大小的传统前馈神经网络不同,RNN 可以采用可变长度的输入并使用其内部状态来处理顺序数据。
RNN 的关键特征是它们具有循环连接,这使它们能够将信息从一个时间步传递到下一个时间步。在每个时间步,RNN 获取输入并将其与其内部状态组合以产生输出并更新其内部状态。这种内部状态充当先前输入的记忆,并且可以影响未来输入的处理。
可以使用时间反向传播 (BPTT) 来训练 RNN,BPTT 是用于训练前馈神经网络的反向传播算法的变体。BPTT 在每个时间步计算损失函数相对于网络参数的梯度,并随时间累积它们。
有几种类型的 RNN,包括普通 RNN、长短期记忆 (LSTM) 网络和门控循环单元 (GRU)。LSTM 和 GRU 网络旨在解决 vanilla RNN 中的梯度消失问题,这使得学习顺序数据中的长期依赖关系变得困难。
RNN 内部是如何工作的?
下面是一个示例来说明 RNN 的内部工作原理。假设我们想要训练一个 RNN 来预测文本序列中的下一个字符。我们可以将每个字符表示为一个单热向量(一个在与字符对应的位置为 1 而在其他位置为 0 的向量)并将其作为输入提供给 RNN。RNN 将一次一个地处理序列中的每个字符,维护一个内部状态来总结目前看到的信息。
在每个时间步,RNN 获取当前字符 one-hot 向量并将其与其内部状态组合以产生输出向量。输出向量可以解释为可能的下一个字符的概率分布,因此我们可以用它来对预测的下一个字符进行采样。输出向量还用于更新 RNN 的内部状态,成为网络的“记忆”,总结了目前看到的信息。
内部状态的更新是使用一组可学习的参数完成的,这些参数是使用时间反向传播训练的。在训练过程中,我们向 RNN 提供特定点之前的真实字符序列,并要求它预测下一个字符。然后我们计算预测分布与真实下一个字符之间的差异,并使用它来使用梯度下降更新网络参数。
序列中的每个时间步都会重复此过程,从而使 RNN 能够学习捕获输入序列中连续字符之间的依赖关系。RNN 的内部状态作为目前所见信息的总结,可以影响未来输入的处理。通过在大量文本语料库上训练网络,我们可以学习生成与训练数据相似的新文本,甚至可以通过从可能的下一个字符的学习分布中抽样来生成新的创意文本。
RNN 背后的数学
循环神经网络 (RNN) 背后的数学原理涉及一组方程式,这些方程式描述了网络如何随时间处理顺序数据。让我们考虑一个具有单个隐藏层的简单 RNN,其中输入序列由向量序列 x_1、x_2、…、x_T 表示,输出序列由向量序列 y_1、y_2、…、y_T 表示。RNN 在时间步 t 的隐藏层由向量 h_t 表示。
在每个时间步 t,RNN 从前一个时间步获取输入向量 x_t 和隐藏层向量 h_{t-1},并产生输出向量 y_t 和新的隐藏层向量 h_t。控制这个过程的方程式是:
h_t = tanh(W_{xh} x_t + W_{hh} h_{t-1} + b_h) y_t = W_{hy} h_t + b_y
其中 W_{xh} 是输入的权重矩阵,W_{hh} 是隐藏层的权重矩阵,W_{hy} 是输出的权重矩阵,b_h 是隐藏层的偏置向量,b_y是输出的偏置向量。
h_t 等式中的 tanh 函数是隐藏层的激活函数。它将输入值压缩到 [-1, 1] 范围内,这使得网络更容易学习输入序列中的长期依赖关系。
RNN 中的权重和偏差是在训练期间使用时间反向传播 (BPTT) 算法学习的,该算法是用于训练前馈神经网络的反向传播算法的变体。BPTT 在每个时间步计算损失函数相对于网络参数的梯度,并随时间累积它们。
RNN 的优点和缺点
循环神经网络 (RNN) 有几个优点和缺点,我们将在下面讨论。
优点:
- 处理顺序数据的能力:RNN 被设计用来处理顺序数据,使其非常适合自然语言处理、语音识别和时间序列预测等任务。
- 捕获时间依赖性的能力:RNN 能够捕获先前输入和当前输入之间的依赖性,使其适用于对数据中的时间关系进行建模。
- 输入和输出大小的灵活性:RNN 可以处理不同长度的输入序列和不同长度的输出序列,这使得它们可用于机器翻译和语音合成等任务。
- 对噪声数据的稳健性:RNN 能够容忍输入序列中的噪声和缺失数据,因为它们能够使用来自先前输入的信息进行预测。
缺点:
- 计算量大:训练 RNN 的计算量大,因为它们需要按顺序处理序列中的每个输入并在每个时间步更新隐藏状态。
- 训练困难:RNN 可能难以训练,因为它们存在梯度消失问题,其中梯度可能变得非常小并导致网络收敛缓慢或根本不收敛。
- 处理长期依赖性的能力有限:标准 RNN 捕获数据中长期依赖性的能力有限,这可能使它们不适合长期依赖性很重要的任务。
- 处理可变长度序列的能力有限:标准 RNN 具有固定数量的隐藏状态,这使得它们不适合处理可变长度输入序列。
Python 实现
以下是使用 Python 和 TensorFlow 库实现简单 RNN 的示例:
import tensorflow as tf
# 定义RNN参数
n_inputs = 3
n_neurons = 5
# 定义RNN cell
cell = tf.keras.layers.SimpleRNNCell(units=n_neurons)
# 创建RNN层
rnn_layer = tf.keras.layers.RNN(cell , input_shape=( None , n_inputs))
# 创建一个带有RNN层的简单模型
model = tf.keras.models.Sequential([
rnn_layer,
tf.keras.layers.Dense( 1 )
])
# 编译模型
model。compile (loss= 'mse' , optimizer= 'adam' )
# 生成一些样本数据
import numpy as np
X = np.random.randn( 100 , 10 , 3 ) # 100 个长度为 10 的序列,每个序列有 3 个特征
y = np.random.randn( 100 , 1 ) # 每个序列的目标值
# 训练模型
history = model.fit(X, y, epochs= 10 , batch_size= 10 )
在此示例中,我们使用SimpleRNNCell来自 TensorFlow 的 Keras API 的类定义了一个具有 5 个神经元的 RNN。然后我们使用这个单元格创建一个 RNN 层,输入形状为(None, n_inputs),其中None表示输入序列的长度可以变化。
我们通过向模型添加 RNN 层和密集输出层来创建一个简单的模型Sequential。我们使用均方误差损失和 Adam 优化器编译模型。
我们生成一些样本数据,其中包含 100 个长度为 10 的序列,每个序列具有 3 个特征,并使用 10 的批量大小训练模型 10 个时期。
概括
总而言之,RNN 是对顺序数据建模和捕获时间依赖性的强大工具,但它们可能计算量大且难以训练。诸如 LSTM 和 GRU 之类的变体已经被开发出来以解决其中的一些问题,并且已经在许多应用程序中变得流行。