在图像识别领域,卷积神经网络(CNN)大放异彩,而在处理序列数据,尤其是自然语言处理(NLP)问题时,则依赖于循环神经网络(RNN)。RNN之所以特别,是因为它们能够处理序列数据,这与人类处理语言的方式更为相似。人类在理解语言时,会利用上下文和之前接收到的信息。例如,当阅读这篇文章时,对每个词的理解是基于对之前词汇的理解。这意味着在序列中思考,即每秒都在接收新的输入,并将它们与之前的经验结合起来,形成基于此的想法。RNN在某种程度上也做到了这一点,它们通过序列的输入和输出操作,并返回结果。使用RNN,可以构建更加智能的系统。
RNN的结构与人工神经网络(ANN)类似,但有一个关键区别:RNN将网络的输出反馈到输入端。换句话说,使用网络在时间点T的输出来计算时间点T+1的输出。这种结构可以展开,得到另一种表示方式,其中网络的状态通过时间向前传播。可以认为RNN具有“记忆”,它们存储了到目前为止计算的信息。无论哪种方式,都能明白,正在使用某种过程将前一个时间步的输出和当前时间步的输入结合起来,以计算当前时间步的输出。
RNN的数学表示相当简单。本质上,这个方程表明网络在当前时间步的状态可以描述为前一个时间步的状态和当前时间步的输入的函数。函数f通常是非线性的,如tanh或ReLU。网络在当前时间步的状态成为下一个时间步的输入值。如果采用最简单的RNN形式,即使用tanh作为激活函数,可以这样表示:
h_t = tanh(W_hh * h_{t-1} + W_xh * x_t)
其中W_hh是递归神经元的权重,W_xh是输入神经元的权重。在这个例子中,只考虑了一个前一个时间步,但通常可以观察多个时间步的状态。这样,预测会更精确。然后,使用当前状态和输出层的权重计算网络的输出。
将RNN的数学表示转化为代码,可以得到一个简单的RNN类实现。这个实现将暴露一个方法——timestep,用于模拟时间。它接受一个输入x,代表网络在该时间步的输入。当然,这个方法将返回一个输出y。在类的内部,会保留关于先前输入和网络状态的信息。以下是C#中RNN的简化实现:
public class RNN {
private Matrix _state;
private Matrix _inputWeights;
private Matrix _recurrentWeights;
private Matrix _outputWeights;
public RNN(Matrix initialInputWeights, Matrix initialReccurentWeights, Matrix initialOutputWeights) {
_inputWeights = initialInputWeights;
_recurrentWeights = initialReccurentWeights;
_outputWeights = initialOutputWeights;
}
public Matrix TimeStep(Matrix input) {
_state = Matrix.Tanh(_state.Multiply(_recurrentWeights) + input.Multiply(_inputWeights));
return _state.Multiply(_outputWeights);
}
}
注意,使用Matrix类型,需要安装MathNet.Numerics NuGet包。使用tanh函数将激活值压缩到[-1, 1]的范围。就像前一章的方程一样,通过将当前状态与递归权重相乘,并将输入与相应的输入权重相乘,然后将它们相加。然后,通过将当前状态与输出权重相乘来计算输出。
反向传播是神经网络用来更新权重的机制。简而言之,在训练过程中,网络会计算某个输入训练数据集的输出。然后,它们将这个结果与期望的结果进行比较,并根据这个更新权重,从输出层回溯到输入层。然而,在RNN中,情况更加复杂,因为有额外的维度——时间。形象地说,必须回到过去来更新权重。这就是为什么在RNN中这个过程被称为反向传播通过时间(BPTT)。
通常,整个数据序列被视为一个训练示例。这简化了这个问题,因为可以在每个时间步计算错误,并计算全局错误(作为所有错误的总和)。还可以注意到状态是相互依赖的。可以使用随机梯度下降计算梯度,并将这些信息传递到前一个时间步,并用它来计算它们的错误和梯度,依此类推。这就是如何压缩时间维度,并使用标准的反向传播算法来对齐权重。
循环神经网络是一个非常有用的工具,应用范围广泛。它们用于各种语言建模和文本生成解决方案。此外,它们还用于语音识别。当与卷积神经网络结合使用时,这种类型的神经网络用于为未标记的图像创建标签。这种组合的运作方式令人惊叹。