65.9K
CodeProject 正在变化。 阅读更多。
Home

循环神经网络入门

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2018年3月12日

CPOL

7分钟阅读

viewsIcon

12145

循环神经网络入门。

您是否曾经想过预测文本的算法是如何工作的?语音识别软件究竟是如何识别我们的声音的?对于图像分类,卷积神经网络在幕后默默地发挥着作用。而对于这类问题,我们使用的是循环神经网络(RNN)。这些神经网络非常强大,尤其在所谓的自然语言处理(NLP)领域非常有用。有人可能会好奇是什么让它们如此特别。嗯,我们到目前为止研究过的网络,即标准神经网络和卷积神经网络,接受固定大小的向量作为输入,并产生固定大小的向量作为输出。

然而,人类的思维并非如此。我们不会抛弃一切,从零开始思考。我们会利用上下文和之前接收到的信息。当您阅读这篇文章时,您对每个单词的理解都基于您对前面单词的理解。这意味着我们是以序列的方式思考,也就是说,我们每纳秒都在使用新的输入,将它们与过去的经验结合起来,并在此基础上形成想法。这就是循环神经网络(以某种方式)所做的,它们在输入和输出的序列上进行操作,并返回结果。使用它们,我们可以构建更智能的系统。

架构

循环神经网络的结构与人工神经网络的结构相同,但有一个区别。它们将网络的输出传播回输入。什么?是的,我们使用网络在时间点T的输出,来计算网络在时间点T + 1的输出。看看这个过度简化的RNN表示

这意味着输出将被发送回输入,然后在下一个输入到来时,在下一个时间步长中使用。基本上,“网络”的状态会随着时间向前传播。我们可以“展开”这个结构,并得到另一种表示相同事物的方式,就像这样

我们可以从这个角度来看,RNN具有“记忆”,可以存储到目前为止计算过的信息。无论哪种方式,您都能理解,我们正在使用某种过程来结合前一个时间步长的输出和当前时间步长的输入,以计算当前时间步长的输出。

有人可能会认为输入或输出的序列很少见,然而,重要的是要认识到,即使输入/输出是固定向量,我们也可以以这种方式处理任何数据。例如,在下图,我们可以看到RNN如何通过学习按顺序逐层向画布添加颜色来生成数字图像(Gregor et al.)。

RNN背后的数学

循环神经网络有一个简单的数学表示

本质上,这个方程说明当前时间步长ht的网络状态可以描述为前一个时间步长的状态和当前时间步长的输入的一个函数。函数f通常是非线性函数,例如tanh或ReLU。当前时间步长ht的网络状态成为下一个时间步长的输入值。如果我们采用最简单的RNN形式,也就是使用tanh作为激活函数,我们可以这样表示它

其中Whh是循环神经元的权重,Wxh是输入神经元的权重。在这个例子中,我们只考虑了一个前时间步长,但总的来说,我们可以观察多个时间步长的状态。这样,我们的预测会更精确。然后使用当前的这个状态和输出的权重来计算网络的输出。

简化示例

正如您所见,RNN的数学表示并不那么可怕。如果我们把这些知识转化为代码,就会得到一个简单的RNN类实现。这个实现会公开一个方法——timestep,通过它我们可以模拟时间。它会接受一个输入x,代表该时间步长的网络输入。当然,这个方法会返回一个输出y。在类内部,我们会保存关于先前输入和网络状态的信息。所以,这里是用C#实现的简化RNN

public class RNN
{
    private Matrix<double> _state;
    private Matrix<double> _inputWeights;
    private Matrix<double> _recurrentWeights;
    private Matrix<double> _outputWeights;

    public RNN(Matrix<double> initialInputWeights, 
           Matrix<double> initialReccurentWeights, Matrix<double> initialOutputWeights)
    {
        _inputWeights = initialInputWeights;
        _recurrentWeights = initialReccurentWeights;
        _outputWeights = initialOutputWeights;
    }

    public Matrix<double> TimeStep(Matrix<double> input)
    {
        _state = Matrix<double>.Tanh(_state.Multiply(_recurrentWeights) + input.Multiply(_inputWeights));
        return _state.Multiply(_outputWeights);
    }
}

这里需要说明一点,要使用Matrix类型,您需要安装MathNet.Numerics NuGet包。我们使用tanh函数将激活值压缩到[-1, 1]的范围内。请注意,在该函数调用内部,我们将当前状态与循环权重相乘,并将此结果与输入与相应的输入权重相乘的结果相加。这与上一章的方程一样。然后通过将当前状态与输出权重相乘来计算输出。

由于Python在实现神经网络方面是首选语言,这里也提供了Python实现

import numpy as np

class RNN:
  
  def step(self, x):
    # Update the state
    self.h = np.tanh(np.dot(self.W_hh, self.h) + np.dot(self.W_xh, x))
    # Calculate the output
    y = np.dot(self.W_hy, self.h)
    return y

在这里,我们使用了numpy进行矩阵运算。我们做了同样的事情,即使用存储的状态、输入x以及循环层和输入层的权重来计算当前状态h。我们使用了np.tanh函数作为激活函数,使用np.dot函数进行矩阵乘法。一旦计算出状态,它就被用来计算输出。

当然,这只是循环神经网络的一个简化表示。这个例子的目的是让我们感受一下网络状态是如何随着时间保存的。

时间反向传播(BPTT)

反向传播是神经网络用来更新权重的机制。简而言之,在训练过程中,网络会计算一组输入训练数据的输出。然后,它们将该结果与期望的结果进行比较,并根据比较结果从输出层向输入层更新权重。

然而,在循环神经网络中,情况更加复杂。这是因为我们有一个额外的维度——时间。从比喻上讲,我们必须回到过去来更新权重。这就是为什么在循环神经网络中这个过程被称为时间反向传播(BPTT)。

看看上面的图片。这是展开后的RNN的相同表示,但我们添加了RNN方程的附加信息。展开后的RNN有点像标准神经网络的表示。这就是为什么RNN中的反向传播算法与标准神经网络中的算法相似。唯一的区别是我们汇总了所有时间步长的误差梯度。这样做是因为我们在层之间共享参数。下面是如何实现的!

通常,整个数据序列被视为一个训练样本。这大大简化了这个问题,因为我们可以计算每个时间步长的误差,并计算全局误差(作为所有误差的总和)。我们还可以注意到状态是相互依赖的。我们可以使用随机梯度下降(SGD)来计算梯度,并将此信息传递给前一个时间步长,并用它来计算它们的误差和梯度,依此类推。这就是我们如何压缩时间维度并使用常规的反向传播算法来对齐权重。

结论

循环神经网络是一个非常有用的工具,具有广泛的应用。它们被用于各种语言建模和文本生成器解决方案。此外,它们还用于语音识别。当与卷积神经网络结合时,这种类型的神经网络被用于创建未标记图像的标签。这种组合的效果令人惊叹。

来源

然而,循环神经网络有一个问题。它们在学习长期依赖方面存在困难,这意味着它们无法理解相隔数个时间步长的数据之间的相互作用。例如,有时在预测单词时,我们需要比仅一个前缀单词更多的上下文。这个问题被称为梯度消失问题,它通过一种特殊的循环神经网络——长短期记忆网络(LSTM)来解决,这是一个将在后续文章中介绍的更广泛的主题。

感谢阅读!

© . All rights reserved.