循环神经网络(Recurrent Neural Network)

关于RNN

RNN,即循环神经网络,是专门为处理序列数据而设计的一类神经网络。它的核心思想是引入“记忆”的概念,使网络能够利用之前步骤中的信息。

  • 核心思想:具有“记忆”的神经网络
  • 想象一下,当你阅读一个句子时,你并不会孤立地理解每一个单词。你会根据前面已经出现的单词来理解当前单词的含义。

    • 前馈神经网络(如MLP、CNN)的局限:它们假设所有的输入(和输出)是相互独立的。你输入一个数据点,它给出一个结果,这个结果不会影响下一个数据点的处理。这对于图像分类很有效,但对于理解句子、语音等序列数据就力不从心了。
    • 传统RNN的突破:RNN通过在网络中添加“循环”结构,使得信息可以从一个步骤持久化到下一个步骤。这意味着它在处理序列中的第 $t$ 个元素时,会同时考虑当前的输入和来自处理第 $t-1$ 个元素时的“状态”(即记忆)。
  • 核心结构与工作原理
  • 让我们来解读上面这个RNN的结构:

    1. 循环单元展开

    • $x^{(t)}$: 在时间步 $t$ 的输入。例如,在句子中,它可能是一个单词的词向量。
    • $h^{(t)}$:在时间步 $t$ 的隐藏状态。这就是RNN的“记忆”。它包含了之前所有时间步的汇总信息。
    • $o^{(t)}$:在时间步 $t$ 的输出。例如,它可能是预测的下一个单词,或者当前单词的标签。
    • 循环连接: 注意,从 $h^{(t-1)}$ 指向 $h^{(t)}$ 的箭头。这就是“循环”所在,它意味着当前的状态 $h^{(t)}$ 是由当前输入 $x^{(t)}$ 和前一状态 $h^{(t-1)}$ 共同计算得出的。

    2.数学公式

    $$ h^{(t)} = f(W\cdot h^{(t-1)} + U \cdot x^{(t)} + b)$$

    其中:

    • $W$ 是权重矩阵,用于连接上一个隐藏状态 $h^{(t-1)}$。
    • $U$ 是权重矩阵,用于连接当前输入 $x^{(t)}$。
    • $b$ 是偏置项。
    • $f$ 是激活函数,通常是 $\tanh$ 或 $\text{ReLU}$。$\tanh$ 能将输出值压缩到 (-1, 1) 之间,有助于稳定梯度。

    输出$o^{(t)}$ 通常由 $h^{(t)}$ 经过一个简单的变换(比如一个全连接层和Softmax)得到:

    $$ o^{(t)} = g(V \cdot h^{(t)} + c) $$

  • 传统RNN的优势
    • 1. 处理变长序列: 无论输入序列是5个单词还是50个单词,同一个RNN单元都可以处理。
    • 2. 参数共享: 在所有时间步上,模型参数$(U, W, V)$是共享的。这大大减少了需要学习的参数量,也使得模型可以泛化到不同长度的序列。
    • 3. 捕捉时序依赖: 理论上,由于 $h^{(t)}$ 依赖于 $h^{(t-1)}$,而 $h^{(t-1)}$ 又依赖于 $h^{(t-2)}$,如此往复,RNN能够捕捉到序列中任意远距离的依赖关系。
  • 应用场景
  • 由于其简单的结构,传统RNN仍然可以用于一些短期依赖任务:

    • 语言建模: 预测下一个字符或单词。
    • 时间序列预测: 预测股票价格、天气等(短期趋势)。
    • 简单的文本分类。

RNN计算

  • 传统RNN单元
  • 传统RNN单元的核心计算非常简单,只有三个步骤:

    $$ z_t = W_{hh}h_{t-1} + W_{xh}x_t + b_h $$

    $$ h_t = \tanh(z_t) $$

    $$ o_t = \text{Sigmoid}(h_t) (可选) $$

    其中$W_{hh}$表示$h_{(t-1)} \to h_{t}$之间的权重,$W_{xh}$表示$x \to h$之间的权重。

    权重大小和参数数量:如果输入维度是$d_x$(若输入的是词向量,维度表明每个词用$d_x$维向量表示),隐藏层维度是$d_h$,那么:

    $$ W_{xh}:d_h \times d_x $$

    $$ W_{hh}:d_h \times d_h $$

    $$ b_{h}:d_h $$

    注意,权重矩阵的大小采用$d_h \times d_x$还是$d_x \times d_h$取决于在运算时权重矩阵在前还是在后,如果在后,应该是:$W_{xh}:d_x \times d_h$

    在单个时间步的计算上,RNN单元等价于一个特殊的全连接层,其输入是[前一时间步的隐藏状态, 当前输入]的拼接。但在宏观上(整个序列处理、时间维度、状态保持),它是一个完全不同的、更强大的结构。但不能完全理解为一个全连接层,它们还是有很多不同之处:RNN单元:输出会作为下一时间步的输入,形成闭环、时间维度上的参数共享、处理变长序列的能力、状态持续性(记忆)等这些特征是全连接层不具备的。

  • 一个RNN单元可以有多个隐藏层吗?
  • 首先必须澄清一个关键概念:一个RNN单元本身就是一个"层",而不是层中的组件。更准确地说:

    • 一个RNN单元 = 一个隐藏层
    • 多层RNN = 多个RNN单元堆叠
    • 这里的关键是:这个"单元"已经包含了完整的计算逻辑,它本身就是一个隐藏层。当人们问"一个RNN单元可以有多个隐藏层吗?",通常他们想问的是:一个RNN层内部是否可以有多层非线性变换?

      答案:通常不这样设计,但有变体

      • 1. 标准设计:每个RNN单元是单层的
      • 在标准的RNN/LSTM/GRU中,每个单元内部的计算是一次非线性变换:

        • 传统RNN: $h_t = \tanh(W_{hh}·h_{t-1} + W_{xh}·x_t + b_h)$ → 一次tanh
        • LSTM: 多个门控计算,但每个门都是单层计算
      • 2. 为什么通常不设计成多层?设计挑战:
      • 设计挑战:循环连接点不明确:应该从哪一层连接到下一时间步? 参数爆炸:如果每层都有循环连接,参数会急剧增加。训练困难:梯度需要在时间和深度两个维度传播。

    • 实际的多层RNN实现方式:
    • 在实践中,我们通过堆叠多个RNN单元来实现"深度",每个RNN单元都是单层的,但多个单元堆叠形成了深度。

一个简单的RNN计算举例

我们将判断一个长度为 3 的句子序列(用数字表示词向量)的情感是积极(输出 1)还是消极(输出 0)。这是一个多对一的RNN任务。

第一步:定义超参数和初始化参数

为了让计算简单明了,我们做以下假设:

符号说明

  • $W$ 权重矩阵,用于连接上一个隐藏状态 $h_{t-1}$。
  • $U$ 权重矩阵,用于连接当前输入 $x_t$。
  • $V$ 权重矩阵,用于连接当前隐藏层 $h_t$。
  • $b_h$ 隐藏层偏置项。
  • $b_o$ 输出层偏置项。

初始化参数

$$\begin{array}{c}W=\begin{bmatrix}0.5&0.1\\ 0.2&0.8\\ \end{bmatrix}\\U=\begin{bmatrix}0.3&0.6\\ 0.9&0.4\\ \end{bmatrix}\\V=\begin{bmatrix}0.7\\ 0.5\\ \end{bmatrix}\\b_h=\begin{bmatrix}0.1&0.2\\ \end{bmatrix}\\b_o=0.3\end{array}$$

输入序列

$$\begin{bmatrix}1&0.5\\ 0.8&1\\ 0.2&0.9\\ \end{bmatrix}$$

初始隐藏状态

$$\begin{bmatrix}0&0\\ \end{bmatrix}$$

时间步: $t=$1

$$z_{1}=h_{0} \cdot W + x_{1} \cdot U + b_h$$

$$=\begin{bmatrix}0&0\\ \end{bmatrix} \cdot \begin{bmatrix}0.5&0.1\\ 0.2&0.8\\ \end{bmatrix} + \begin{bmatrix}1&0.5\\ \end{bmatrix} \cdot \begin{bmatrix}0.3&0.6\\ 0.9&0.4\\ \end{bmatrix} = \begin{bmatrix}0.85&1\\ \end{bmatrix}$$

$$h_{1}=\tanh(z_{1})=\begin{bmatrix}0.69107&0.76159\\ \end{bmatrix}$$

时间步: $t=$2

$$z_{2}=h_{1} \cdot W + x_{2} \cdot U + b_h$$

$$=\begin{bmatrix}0.69107&0.76159\\ \end{bmatrix} \cdot \begin{bmatrix}0.5&0.1\\ 0.2&0.8\\ \end{bmatrix} + \begin{bmatrix}0.8&1\\ \end{bmatrix} \cdot \begin{bmatrix}0.3&0.6\\ 0.9&0.4\\ \end{bmatrix} = \begin{bmatrix}1.73785&1.75838\\ \end{bmatrix}$$

$$h_{2}=\tanh(z_{2})=\begin{bmatrix}0.93998&0.94232\\ \end{bmatrix}$$

时间步: $t=$3

$$z_{3}=h_{2} \cdot W + x_{3} \cdot U + b_h$$

$$=\begin{bmatrix}0.93998&0.94232\\ \end{bmatrix} \cdot \begin{bmatrix}0.5&0.1\\ 0.2&0.8\\ \end{bmatrix} + \begin{bmatrix}0.2&0.9\\ \end{bmatrix} \cdot \begin{bmatrix}0.3&0.6\\ 0.9&0.4\\ \end{bmatrix} = \begin{bmatrix}1.62845&1.52786\\ \end{bmatrix}$$

$$h_{3}=\tanh(z_{3})=\begin{bmatrix}0.92584&0.91006\\ \end{bmatrix}$$

最终输出

$$o_{3}=V \cdot h_{3} + b_o =\begin{bmatrix}0.7\\ 0.5\\ \end{bmatrix}\cdot \begin{bmatrix}0.92584&0.91006\\ \end{bmatrix}+0.3=1.40312$$

$$ y_3 = \text{sigmoid}(o_3) \approx 0.803 $$

结果解释:

我们的最终输出 $y_3 ≈ 0.803$由于我们使用的是 sigmoid 激活函数,输出值在 0 到 1 之间。我们可以设置一个阈值(比如 0.5): 如果 $y_3 > 0.5$,则预测为积极(1),如果 $y_3 \leq 0.5$,则预测为消极(0),在这个例子中,0.803 > 0.5,所以RNN模型预测这个句子的情感是积极的。

RNN反向传播

一、 问题定义与符号说明

考虑一个简单的单层RNN,做多对一分类任务(如情感分析):

  • 时间步:$t=1,2,\cdots , T$
  • 输入序列:$x_1,x_2,\cdots, x_T$
  • 隐藏状态:$h_t = \tanh(W_{hh}h_{t-1} + W_{xh}x_t + b_h)$
  • 输出层:$o = W_{hy}h_T + b_y$(仅用最后时刻的$h_T$)
  • 预测输出:$\hat{y} = \text{softmax}(o)$
  • 其中,$W_{hy}$是隐藏层→输出层的权重矩阵,$b_y$输出层的偏置。$W_{hh}$是隐藏层的权重,$b_h$是隐藏层的偏置。$W_{xh}$是输入至隐藏层的权重。

    损失函数:交叉熵损失

    $$ L = - \sum_{k=1}^K y_k\log(\hat{y}_k)$$其中$K$是类别数。输出层梯度的计算遵循标准的反向传播思想,但由于RNN只在最后时间步使用输出层,计算相对简单。

    其中$$\hat{y}_k = \frac{e^{o_k}}{ \displaystyle{ \sum_{j=1}^K e^{o_j}}} $$

    计算损失对输出层的权重梯度,与反向传播的算法相似,对于交叉熵损失函数,它的梯度计算如下:

    $$ \frac{\partial L}{\partial o_i} = \hat{y}_i - y_i $$

    $$ \frac{\partial L}{\partial W_{hy}} = (\hat{y} - y)\cdot h_T^T $$

    $$ \frac{\partial L}{\partial b_{y}} = \hat{y} - y $$

二、 时间反向传播(BPTT)

时间反向传播 本质上是标准反向传播在时间维度上的扩展。核心挑战在于:RNN的参数在所有时间步共享,因此损失函数对某个参数的梯度需要累加所有时间步的贡献。在BPTT中,我们将RNN在时间上展开成一个深度前馈网络,然后应用标准反向传播。但有一个重要区别:共享权重意味着梯度要跨时间步累积。

前向传播方程:

  • 1. 隐藏层:
  • $$ h_t = \tanh(W_{hh}h_{t-1} + W_{xh}x_t + b_h) $$

    • $h_t$ ​ : 时间步t的隐藏状态
    • $W_{hh}$:隐藏层到隐藏层权重
    • $W_{xh}$:输入到隐藏层权重
    • $b_h$:隐藏层偏置
  • 2. 输出层
  • $$ o = W_{hy}h_T + b_y $$

    $$ \hat{y} = \text{softmax}(o)$$

  • 3. 损失函数(交叉熵)
  • $$ L = -\sum_{k=1}^Ky_k\log(\hat{y}_k)$$

三、 BPTT 详细推导

我们将计算$L$对$W_{hh}$和$W_{xh}$的梯度。这是BPTT的核心。

步骤1:定义局部导数

隐藏层净输入:$$ z_t=W_{hh}h_{t-1} + W_{xh}x_t + b_h $$

隐藏层激活:$$ h_t=\tanh(z_t) $$

导数关系

$$ \frac{\partial h_t}{\partial z_t} = \text{diag}(1-h_t \odot h_t)$$(tanh的导数)

$$ \frac{\partial z_t}{\partial h_{t-1}} = W_{hh}^T $$

$$ \frac{\partial z_t}{\partial W_{hh}} = h_{t-1}^T $$

步骤2:计算损失对$h_t$的梯度

这是BPTT的关键步骤。损失 $L$通过所有后续的时间步影响$h_t$:

设$\delta_t=\frac{\partial L}{\partial z_t}$为损失对净输入$z_t$的梯度。从最后一个时间步 $T$ 开始反向计算:

情况1:$t=T$(最后一个时间步),损失直接影响$h_T$

$$ \frac{\partial L}{\partial h_T} = W_{hy}^T \cdot \frac{\partial L}{\partial o} $$其中$\frac{\partial L}{\partial o} = \hat{y} - y$(来自输出层梯度)

$$ \delta_T = \frac{\partial L}{\partial z_T} = \frac{\partial L}{\partial h_T} \odot (1-h_T\odot h_T)$$这里$\odot$表示逐元素相乘。

情况2:$t < T$(中间时间步):$h_T$通过两条路径影响损失,对于多对一任务(只有最后时间步有输出):

$$ \frac{\partial L}{\partial h_t} = \frac{\partial z_{t+1}}{\partial h_t} \cdot \frac{\partial L}{\partial z_{t+1}} = W_{hh}^T \cdot \delta_{t+1} $$

$$ \delta_{t} = \frac{\partial L}{\partial z_t} = \frac{\partial L}{\partial h_t} \odot (1-h_t \odot h_t)$$

$$ \delta_{t} = (W_{hh}^T\cdot \delta_{t+1}) \odot (1-h_t \odot h_t)$$

步骤3:计算对权重的梯度

对$W_{hh}$的梯度

由于$W_{hh}$出现在每个时间步,梯度是所有时间步贡献之和:

$$ \frac{\partial L}{\partial W_{hh}} = \sum_{t=1}^T \frac{\partial L}{\partial z_t} \cdot \frac{\partial z_t}{\partial W_{hh}} = \sum_{t=1}^T \delta_t \cdot h_{t-1}^T $$

对$W_{xh}$的梯度

$$ \frac{\partial L}{\partial W_{xh}} = \sum_{t=1}^T \frac{\partial L}{\partial z_t} \cdot \frac{\partial z_t}{\partial W_{xh}} = \sum_{t=1}^T \delta_t \cdot x_{t}^T $$

对$b_{h}$的梯度

$$ \frac{\partial L}{\partial b_{h}} = \sum_{t=1}^T \frac{\partial L}{\partial z_t} = \sum_{t=1}^T \delta_t $$

在QkCalc中设计RNN

在QkCalc中,你可以通过“循环神经网络”来建立有关RNN的模型,在该模块中,你可以建立不同类型的RNN网络,生成样本词嵌入矩阵进行训练,查看训练结果和进行预测。

相关视频

RNN - 循环神经网络