在我们了解驱动Transformer模型的注意力机制之前,了解序列数据传统上是如何处理的是有益的。对于翻译句子或预测文本中下一个词这样的任务,元素的顺序非常重要。前馈神经网络独立处理输入,天生不适合捕获这些序列依赖。这促成了循环神经网络 (RNN) 的出现。RNN的标志性特点是其内部的“记忆”或隐藏状态。与标准前馈网络不同,RNN逐个元素(例如,逐词)处理序列。在每一步,它接收当前输入元素和上一步的隐藏状态,以计算新的隐藏状态。这个隐藏状态就像是目前为止序列中已见信息的持续摘要。可以将其想象成阅读句子:你逐个词处理,任何时候的理解都取决于你已经读过的词。RNN的隐藏状态试图捕捉这种变化的语境。循环结构其核心是,RNN单元在每个时间步执行相同的计算,但其内部状态根据输入序列而变化。如果我们有一个输入序列 $x = (x_1, x_2, ..., x_T)$,RNN在时间步 $t$ 更新其隐藏状态 $h_t$,使用当前输入 $x_t$ 和前一个隐藏状态 $h_{t-1}$。这种更新的常见公式是:$$ h_t = \tanh(W_{hh} h_{t-1} + W_{xh} x_t + b_h) $$在这里:$h_t$ 是时间步 $t$ 的新隐藏状态。$h_{t-1}$ 是前一个时间步的隐藏状态($h_0$ 通常初始化为零)。$x_t$ 是时间步 $t$ 的输入向量。$W_{hh}$ 和 $W_{xh}$ 分别是隐藏到隐藏连接和输入到隐藏连接的权重矩阵。这些权重在所有时间步中是共享的,这是RNN学习序列模式的根本。$b_h$ 是一个偏置向量。$\tanh$ 是一种常见的激活函数(双曲正切),引入非线性。RNN还可以选择在每个时间步生成输出 $y_t$,通常根据隐藏状态计算:$$ y_t = W_{hy} h_t + b_y $$这里 $W_{hy}$ 是隐藏到输出的权重矩阵,$b_y$ 是输出偏置。是否在每一步都需要输出取决于具体的任务(例如,在每一步预测下一个词,或对整个序列进行分类)。过程可视化通过时间“展开”RNN通常更容易理解其运作。想象为每个时间步创建一个独立的网络副本,隐藏状态从一个副本传递到下一个。digraph G { rankdir=LR; node [shape=box, style=rounded, fontname="sans-serif", color="#495057", fillcolor="#dee2e6", style=filled]; edge [fontname="sans-serif", color="#495057"]; subgraph cluster_0 { label = "时间 t-1"; style=dashed; color="#adb5bd"; xt_1 [label="x_{t-1}", shape=ellipse, fillcolor="#a5d8ff"]; ht_1 [label="h_{t-1}", fillcolor="#ffec99"]; RNN_1 [label="RNN 单元", fillcolor="#ced4da"]; yt_1 [label="y_{t-1}", shape=ellipse, fillcolor="#b2f2bb"]; xt_1 -> RNN_1; ht_1 -> RNN_1 [style=dashed]; RNN_1 -> yt_1; } subgraph cluster_1 { label = "时间 t"; style=dashed; color="#adb5bd"; xt [label="x_t", shape=ellipse, fillcolor="#a5d8ff"]; ht [label="h_t", fillcolor="#ffec99"]; RNN_t [label="RNN 单元", fillcolor="#ced4da"]; yt [label="y_t", shape=ellipse, fillcolor="#b2f2bb"]; xt -> RNN_t; RNN_t -> ht; RNN_t -> yt; } subgraph cluster_2 { label = "时间 t+1"; style=dashed; color="#adb5bd"; xt_p1 [label="x_{t+1}", shape=ellipse, fillcolor="#a5d8ff"]; ht_p1 [label="h_{t+1}", fillcolor="#ffec99"]; RNN_p1 [label="RNN 单元", fillcolor="#ced4da"]; yt_p1 [label="y_{t+1}", shape=ellipse, fillcolor="#b2f2bb"]; xt_p1 -> RNN_p1; RNN_p1 -> ht_p1; RNN_p1 -> yt_p1; } RNN_1 -> ht [label="h_{t-1}"]; ht -> RNN_p1 [label="h_t"]; // Invisible edges for alignment if needed {rank=same; xt_1; xt; xt_p1;} {rank=same; RNN_1; RNN_t; RNN_p1;} {rank=same; yt_1; yt; yt_p1;} }一个RNN随时间展开的示意图。相同的RNN单元(权重共享)处理输入 $x_t$ 和前一个隐藏状态 $h_{t-1}$,生成新的隐藏状态 $h_t$ 和输出 $y_t$。隐藏状态从一个时间步流向下一个时间步。这种循环结构使得RNN理论上能够模拟序列中任意长度的依赖关系,因为信息可以通过隐藏状态传播。多年来,RNN(以及其更复杂的变体,如LSTMs和GRUs,它们旨在更好地处理长距离依赖)是序列建模任务的常规选择。然而,正如我们将在下一节中看到,这样纯粹地按顺序处理序列会带来其自身的一些重要挑战,尤其是在处理非常长的序列或复杂依赖关系时。这些局限性促使了替代架构的出现,最终促成了Transformer。