正如我们所知,序列数据不仅包含单个元素的信息,更重要的是其随时间变化的顺序和相互联系。举例来说,像预测句子中的下一个词、基于历史表现预测未来股票价格,或对电影评论进行情感分类等任务。在所有这些情况下,理解由前序元素提供的背景信息十分重要。标准的前馈神经网络,例如多层感知机(MLP),是解决许多机器学习问题的有效工具,如图像分类或表格数据回归。这些网络通过接收一个固定大小的输入向量,然后将其传递通过一层或多层相互连接的神经元,从而生成输出。网络接收的每个输入都独立于其他任何输入进行处理。如果你向一个MLP输入两个不同的数据点,它对第二个数据点的处理不会受到第一个数据点的记忆或影响。这种独立处理方式在处理序列数据时带来明显难题:处理可变长度: 句子可以包含不同数量的词,时间序列可以持续不同时长,音频片段长度各异。标准前馈网络要求输入具有固定、预设的大小。虽然可以使用填充(padding)或截断(truncation)等技术强制序列达到固定长度,但这些方法通常并非最佳。填充可能引入噪声,或者要求模型学会忽略输入的大部分内容,而截断则会丢弃可能重要的数据。捕获时间上的关联: 主要限制是缺少记忆能力。一个前馈网络在处理句子“The cloud stores data”(云存储数据)中的“cloud”(云)一词时,没有内在机制来得知其前面的词是“The”(这个)。它独立处理“cloud”这个词。这使得它非常难以(甚至不可能)捕获序列中相距较远元素之间的关系,即所谓的长期依赖。预测明天的股票价格需要查看过去几天、几周甚至几个月的趋势,而不仅仅是孤立地看昨天的价格。跨时间的参数共享: 想象一下,如果对句子中的每个词都单独应用一个标准MLP。网络将需要独立的参数来学习与第一个词、第二个词等相关的特征。这样做效率低下,并且不允许模型泛化模式。我们希望模型能够识别某个模式或特征(例如识别名词),无论它出现在序列的开头、中间还是末尾。标准前馈网络本身不具备在序列不同位置共享参数的能力。digraph G { rankdir=TB; splines=false; node [shape=box, style=rounded, margin=0.1, fontname="sans-serif", fontsize=10]; edge [fontname="sans-serif", fontsize=9]; subgraph cluster_ffn { label = "前馈网络 (MLP)"; style=dashed; color="#adb5bd"; bgcolor="#f8f9fa"; ffn_x1 [label="输入\nx₁"]; ffn_mlp1 [label="MLP"]; ffn_y1 [label="输出\ny₁"]; ffn_x1 -> ffn_mlp1; ffn_mlp1 -> ffn_y1; ffn_x2 [label="输入\nx₂"]; ffn_mlp2 [label="MLP"]; ffn_y2 [label="输出\ny₂"]; ffn_x2 -> ffn_mlp2; ffn_mlp2 -> ffn_y2 [label="(独立处理,\n无x₁的记忆)"]; ffn_x3 [label="输入\nx₃"]; ffn_mlp3 [label="MLP"]; ffn_y3 [label="输出\ny₃"]; ffn_x3 -> ffn_mlp3; ffn_mlp3 -> ffn_y3 [label="(独立处理,\n无x₁, x₂的记忆)"]; ffn_x1 -> ffn_x2 [style=invis]; ffn_x2 -> ffn_x3 [style=invis]; { rank=same; ffn_mlp1; ffn_mlp2; ffn_mlp3; } { rank=same; ffn_y1; ffn_y2; ffn_y3; } } subgraph cluster_seq { label = "序列模型"; style=dashed; color="#adb5bd"; bgcolor="#f8f9fa"; seq_x1 [label="输入\nx₁"]; seq_m1 [label="模型\n(状态 s₀)"]; seq_y1 [label="输出\ny₁"]; seq_s1 [label="状态 s₁", shape=ellipse, style=filled, fillcolor="#a5d8ff"]; seq_x1 -> seq_m1; seq_m1 -> seq_y1; seq_m1 -> seq_s1 [label="记忆"]; seq_x2 [label="输入\nx₂"]; seq_m2 [label="模型\n(状态 s₁)"]; seq_y2 [label="输出\ny₂"]; seq_s2 [label="状态 s₂", shape=ellipse, style=filled, fillcolor="#74c0fc"]; seq_x2 -> seq_m2; seq_m2 -> seq_y2; seq_m2 -> seq_s2 [label="记忆"]; seq_s1 -> seq_m2 [style=dashed, color="#495057"]; seq_x3 [label="输入\nx₃"]; seq_m3 [label="模型\n(状态 s₂)"]; seq_y3 [label="输出\ny₃"]; seq_s3 [label="状态 s₃", shape=ellipse, style=filled, fillcolor="#4dabf7"]; seq_x3 -> seq_m3; seq_m3 -> seq_y3; seq_m3 -> seq_s3 [label="记忆"]; seq_s2 -> seq_m3 [style=dashed, color="#495057"]; seq_x1 -> seq_x2 [style=invis]; seq_x2 -> seq_x3 [style=invis]; { rank=same; seq_m1; seq_m2; seq_m3; } { rank=same; seq_y1; seq_y2; seq_y3; } { rank=same; seq_s1; seq_s2; seq_s3; } } }处理序列 $x_1, x_2, x_3$ 的比较。前馈网络独立处理每个输入。序列模型保持一个内部状态(记忆,$s_t$),该状态会影响下一个输入的处理。为了有效建模序列,我们需要专门设计用来解决这些不足的架构。序列模型的构建考虑了以下能力:逐步处理元素: 它们一次处理输入序列的一个元素(或一个块)。保持内部状态(记忆): 它们具有一种机制来跟踪前面步骤的信息。这种状态作为迄今为止所见序列的概括,影响着未来元素的处理方式。跨时间步共享参数: 通常使用同一组权重来处理每个时间步的输入。这使得模型能够泛化适用于序列任何位置的模式,并显著减少了参数数量,而无需为每个时间步分配唯一的权重。这些特性使得序列模型能够更自然地处理可变长度输入,并且,重要的是,能够捕获理解序列数据所必需的时间关联。循环神经网络(RNN),我们将在下一章开始详细学习它们,是一类依照这些原理明确设计的神经网络。它们包含循环并保持一个隐藏状态,使得信息能够跨时间步保持,从而直接解决了标准前馈模型在序列任务上的局限性。