序列世界的基本问题:为什么需要“记忆”
人类日常经验中最常见的信息形态之一,并不是一张“静态表格”,而是一段随时间展开的过程:一句话从第一个词逐步说完,一段语音从起音到收音连续变化,一个用户从第一次点击到最终购买经历多次交互,一台设备的传感器在每一秒输出一次读数。此类数据的共同特征是:当前观测与过去观测之间存在依赖,而且这种依赖往往决定了我们能否做出正确判断。序列建模的核心问题因此可以概括为一句话:在不断到来的信息流中,如何保留、选择并利用“相关的过去”,从而更好地理解现在、预测未来或生成合理的后续。
序列数据无处不在:从语言到传感器
语言:同一句话,换一个词就完全不同
语言是典型的序列。读者在阅读时会本能地利用上下文消歧。例如:
- “我把**苹果**放在桌上。”(水果)
- “我用**苹果**发布了一个应用。”(公司/生态)
单看“苹果”这个词并不能决定语义,决定性信息来自其前后的词序列。更强的例子来自长距离依赖:
> “虽然今天下雨,但我还是决定出门,因为我已经和朋友约好了在咖啡馆见面,所以我带了伞。” > 句首的“虽然…但…”结构,需要在较长跨度上保持一致的语法与逻辑关系。
这类现象提示我们:序列任务不能只看“当前一步”,而必须在某种意义上携带一段可用的历史信息。
语音:每一帧都含糊,连续性带来可辨性
语音识别通常把连续语音切成短时间窗(如 10ms 或 20ms)计算特征,每一帧的证据都可能不充分。很多音素在局部上相似,只有结合前后帧的动态变化(协同发音、拖尾、过渡)才能区分。换言之,序列结构本身提供了“平滑约束”和“可辨识性”。
点击流:一个点击不是意图,一串点击才是行为
电商或内容平台常见的点击流(clickstream)会记录用户在一次会话中浏览、搜索、点击、加购、支付等事件。若只拿“最后一次点击”做判断,往往会误读用户意图;而一串行为序列可以揭示:
- 偏好稳定性(长期兴趣)
- 目标驱动性(短期任务)
- 决策路径(从探索到收敛)
实践中,推荐系统与广告系统常把点击序列作为关键输入之一,因为序列承载了因果线索与阶段性目标。
传感器:噪声与缺失让“记忆”变得必要
在工业设备、可穿戴设备、交通系统中,传感器读数常带噪声、漂移、丢包。单点读数不可靠,必须利用时间上的一致性来估计真实状态。例如心率监测中,瞬时异常可能来自运动、接触不良或算法误检;结合前后趋势才能判断是否为真正的生理异常。
上下文依赖与时序相关:序列问题的本质
序列建模的两个关键词是:
- **上下文依赖(context dependency)**:当前的含义/标签/决策依赖于周围或历史信息。
- **时序相关(temporal correlation)**:相邻或相隔若干步的观测在统计上相关,通常不满足相互独立。
为了更精确地表达,我们引入一个常用表述:设输入序列为 $$ \mathbf{x}_{1:T} = (\mathbf{x}_1,\mathbf{x}_2,\dots,\mathbf{x}_T), $$ 其中 $T$ 为序列长度,$\mathbf{x}_t$ 表示第 $t$ 个时间步的观测(可以是一个词、一个向量特征、一条事件记录等)。序列任务的关键在于:目标变量与整个序列(或其某个子序列)有关,而不是只与某个 $\mathbf{x}_t$ 有关。
在概率建模语言里,独立同分布(i.i.d.)假设要求样本之间相互独立且来自同一分布。但对序列而言,典型情况是: $$ p(\mathbf{x}_{1:T}) \neq \prod_{t=1}^T p(\mathbf{x}_t), $$ 因为 $\mathbf{x}_t$ 往往依赖于 $\mathbf{x}_{1:t-1}$。这并不是“序列数据不好”,恰恰相反——依赖结构就是信息本身。问题在于:许多传统机器学习方法默认 i.i.d.,在处理序列时会遇到根本性不适配。
i.i.d. 假设的局限:把序列“打散”会丢掉什么
局限一:顺序信息消失
若把一句话看作“词袋”(bag-of-words),模型只知道哪些词出现过,却不知道词的先后关系。“我打你”和“你打我”词集合相同,但含义完全相反。顺序是结构性信息,打散会直接损失。
局限二:长程依赖被截断
在很多任务中,决定性线索可能出现在很早的时间步。例如阅读理解里,问题的答案可能由开头一个实体决定;在程序日志中,故障起因可能发生在错误提示之前很久。若只做局部窗口特征,容易“看不到关键证据”。
局限三:样本之间并非独立
序列常被切成片段用于训练(例如把长日志切成若干短片段),但这些片段可能高度相关。若训练/测试划分不当,会出现数据泄漏或过高估计性能的问题。对普通读者而言,可以记住一个直观原则:序列的切分方式本身会影响统计结论。
> 侧边栏:一个常见的“假高分”陷阱 > 若把同一个用户的一条长会话拆成多段,并随机分到训练集与测试集,模型可能仅靠“用户习惯”就能在测试集上表现很好,但这并不代表它能泛化到新用户或新会话。实际评估通常需要按用户、按时间或按会话整体划分。
经验数据:序列依赖并非微弱效应
在语言模型研究中,人们早已观察到:随着历史上下文长度增加,模型的困惑度(perplexity)通常会下降,即预测下一个词变得更容易。这意味着更长的历史携带了可利用的信息[^1]。在推荐与点击流任务中,“最近行为”与“长期偏好”的结合也几乎总能带来显著提升,这是工业界反复验证的经验事实。
[^1]: 困惑度是语言模型常用指标,粗略理解为“模型对下一个词有多困惑”。困惑度越低,预测越好。电子版读者可参考综述链接:<https://arxiv.org/abs/1906.08237>(示例链接)。
序列建模要完成什么:四类典型目标
序列学习问题并非只有“预测下一步”。为了让后续章节的讨论有统一坐标系,本书将常见目标归为四类,并给出典型输入/输出形式。
1)序列分类(Sequence Classification)
对整段序列给出一个标签:
- 情感分析:一句话是正面/负面
- 设备诊断:一段振动信号是否异常
- 欺诈识别:一段交易序列是否可疑
形式化为:给定 $\mathbf{x}_{1:T}$,预测 $y \in \{1,\dots,K\}$。
2)序列标注(Sequence Labeling)
对每个时间步给出一个标签:
- 词性标注(POS tagging)
- 命名实体识别(NER)
- 语音帧级别音素分类
形式化为:给定 $\mathbf{x}_{1:T}$,预测 $\mathbf{y}_{1:T}$,其中 $\mathbf{y}_t$ 是第 $t$ 步标签。
3)序列预测/回归(Forecasting / Regression)
预测未来的连续值或事件:
- 负载预测:未来 1 小时的电力需求
- 轨迹预测:行人未来位置
- 传感器预测:未来若干步的温度/压力
常见形式:给定过去 $\mathbf{x}_{1:T}$,预测未来 $\mathbf{x}_{T+1:T+H}$ 或某个目标序列 $\mathbf{y}_{1:H}$。
4)序列生成(Sequence Generation)
生成一段新的序列:
- 机器翻译:输入源语言序列,输出目标语言序列
- 对话系统:输入上下文,生成回复
- 文本续写:给定开头,生成后续
形式化为:学习条件分布 $p(\mathbf{y}_{1:S}\mid \mathbf{x}_{1:T})$,并逐步生成 $\mathbf{y}$。
为了直观地把这些任务放到同一个流程中,可以用如下图示概括:
```mermaid graph TD A[输入序列 x_{1:T}] --> B[序列表示/编码] B --> C{任务类型} C --> D[序列分类: 输出 y] C --> E[序列标注: 输出 y_{1:T}] C --> F[预测: 输出 y_{1:H}] C --> G[生成: 输出 y_{1:S}] ```
序列如何表示:token、embedding 与时间步
token:离散符号的基本单位
在文本中,token 可以是字、词、子词(subword)。在点击流中,token 可以是“搜索”“点击商品 A”“加入购物车”等离散事件;在日志中,token 可以是事件类型或模板 ID。token 的优点是结构清晰,但需要把现实世界映射到离散集合。
embedding:把离散映射为可计算的向量
神经网络通常需要连续向量输入,因此把 token 映射到向量空间: $$ \mathbf{e}_t = \mathrm{Emb}(x_t) \in \mathbb{R}^d, $$ 其中 $d$ 为 embedding 维度,$\mathrm{Emb}$ 可视为一个可学习的查表矩阵。embedding 的直观含义是:相似的 token 在向量空间里更接近,从而让模型能利用“相似性”进行泛化。
下面给出一个极简示意(不依赖具体框架),帮助读者把“序列—索引—向量”对应起来:
```python
假设一句话已分词并映射为id序列
token_ids = [101, 203, 57, 890] # x_{1:T}
embedding矩阵: vocab_size x d
e_t = E[token_ids[t]]
```
时间步:序列的“坐标轴”
时间步 $t$ 是序列的秩序来源。对文本来说它是词序;对传感器来说它是采样点;对点击流来说它是事件发生顺序。后续我们将频繁使用如下记号:
- $\mathbf{x}_t$:第 $t$ 步输入(可为向量或 token)
- $\mathbf{x}_{1:t}$:从 1 到 $t$ 的前缀
- $T$:序列长度(可变)
- $H$:预测步长(forecast horizon)
- $S$:生成序列的长度
变长序列:对齐、填充与掩码
现实数据的 $T$ 往往不一致。工程上常用两种策略:
1. 按批次填充(padding):把短序列补到同一长度,并使用掩码(mask)忽略补齐位置。 2. 按长度分桶(bucketing):将相近长度的样本放到同一批,减少无效计算。
这看似是“实现细节”,但会影响训练效率与数值稳定性,读者在实践时需要有意识地检查 padding/mask 是否正确。
统一问题表述:从“看过去”到“有记忆”
为了把“为什么需要记忆”说清楚,我们先给出一个统一的建模视角:在每个时间步,模型维护一个内部状态,用来概括过去。这个内部状态通常记为 $\mathbf{h}_t$(hidden state)。其核心递推形式是: $$ \mathbf{h}_t = f(\mathbf{h}_{t-1}, \mathbf{x}_t), $$ 其中 $f(\cdot)$ 是某种可学习的更新规则。随后根据任务不同,从 $\mathbf{h}_t$(或整个 $\mathbf{h}_{1:T}$)得到输出。
这一表述的价值在于,它明确提出了“记忆”的两个关键问题:
1. 记什么:哪些历史信息应被保留? 2. 怎么记:内部状态如何在长序列中稳定传播,不被噪声淹没或数值衰减?
如果把 $\mathbf{h}_t$ 想象成读者在阅读句子时的“理解状态”,就能直观理解:读到后面的词时,我们并不会把前面所有词逐字背诵,而是保留与当前理解相关的摘要信息;同时,这个摘要必须足够稳定,才能跨越较长的句子保持一致语义。这就是序列模型中的“记忆”直觉来源。
> 引用:记忆不是“存档”,而是“有用的压缩” > 对多数任务而言,最优策略不是把所有历史原样保存,而是在容量有限的表示中保留与目标最相关的因素,并在新信息到来时动态更新。
对比:没有记忆的模型能走多远?
为了更具体地理解“记忆的必要性”,可以将序列任务的常见方法粗略分为三类(从弱到强):
1. 独立模型(无序/无记忆):把序列打散或只看局部特征,如词袋、固定窗口特征。 2. 有限记忆模型(短上下文):如 $n$-gram、固定长度滑窗、马尔可夫假设,能利用最近若干步。 3. 可学习的动态记忆(长上下文):如 RNN/LSTM 等,通过状态递推在原则上可利用更长历史。
有限记忆模型的代表是 $n$-gram 语言模型:假设下一个词只依赖前 $n-1$ 个词: $$ p(x_t \mid x_{1:t-1}) \approx p(x_t \mid x_{t-n+1:t-1}). $$ 它简单有效,但当关键线索超出窗口时就无能为力。更重要的是,窗口大小增加会带来参数爆炸与稀疏问题。此处读者不必深究细节,只需把握一个要点:固定窗口是一种硬性上限,而自然序列中的依赖跨度并不固定。
因此,真正的挑战是:我们希望模型在需要时记住更久,在不需要时快速遗忘,并能在学习中自动完成这种权衡。这正是从 RNN 走向 LSTM 的动机之一:让“记忆”既有容量又可控。
本书学习路线与实践环境建议(面向普通读者)
学习路线:从直觉到机制,再到实践
本书采用“现象—问题—机制—验证”的路线组织内容:
- 先用大量序列现象建立直觉:为什么 i.i.d. 不够用,为什么需要状态与记忆。
- 再引入最基本的循环结构(RNN):用统一符号把递推和训练目标说清楚。
- 然后讨论 RNN 的困难:长序列训练不稳定、梯度衰减/爆炸等。
- 最后进入 LSTM:通过门控机制解释“选择性记忆”的可学习实现,并配合可运行的实验观察效果。
读者可以把它理解为逐步回答三个问题:能不能做(RNN)—为什么难(训练问题)—怎样更稳(LSTM)。
实践环境:以可复现为第一原则
为保证普通读者能顺利上手,建议采用如下配置(不追求“最强”,追求“少踩坑”):
- **Python**:3.10 或 3.11(生态稳定)
- **深度学习框架**:PyTorch(入门友好,调试直观)或 TensorFlow/Keras(偏工程化)
- **交互环境**:JupyterLab / VS Code
- **硬件**:有 GPU 更快;无 GPU 也可在小数据集上完成核心实验
- **数据与实验**:优先选择公开小数据集(如 IMDB 情感、Penn Treebank 的子集、简单合成序列),先验证概念再扩展规模
电子版读者可在侧栏链接中获取环境安装指南与示例代码仓库地址(后续章节将给出更具体清单与脚本入口)。纸书读者只需记住一个原则:先跑通最小闭环(数据→模型→训练→评估),再逐步加复杂度。
小结:序列建模的“基本矛盾”与下一步
本章从语言、语音、点击流与传感器等例子出发,说明序列任务的本质在于上下文依赖与时序相关;传统 i.i.d. 假设在序列上往往不成立,甚至会因打散顺序而损失关键信息。我们进一步归纳了四类典型目标(分类、标注、预测、生成),并建立了全书通用的表示与符号体系:用 $\mathbf{x}_{1:T}$ 表示输入序列,用内部状态 $\mathbf{h}_t$ 表达“可更新的记忆摘要”。
接下来需要回答的核心问题是:如果我们真的用递推状态 $\mathbf{h}_t$ 来承载历史,那么这个状态如何被定义、如何被训练、如何在长序列中保持有效?下一章将从最基本的循环神经网络(RNN)开始,给出“记忆”的第一种可计算实现,并用具体任务展示它为何既强大又脆弱。