\[ \begin{align}\begin{aligned}\newcommand{\ba}{\boldsymbol{a}} \newcommand{\bb}{\boldsymbol{b}} \newcommand{\be}{\boldsymbol{e}} \newcommand{\bq}{\boldsymbol{q}} \newcommand{\bk}{\boldsymbol{k}} \newcommand{\bw}{\boldsymbol{w}} \newcommand{\bx}{\boldsymbol{x}} \newcommand{\by}{\boldsymbol{y}} \newcommand{\bz}{\boldsymbol{z}} \newcommand{\bd}{\boldsymbol{d}} \newcommand{\bv}{\boldsymbol{v}} \newcommand{\bs}{\boldsymbol{s}}\\\newcommand{\btheta}{\boldsymbol{\theta}} \newcommand{\bbeta}{\boldsymbol{\beta}} \newcommand{\bgamma}{\boldsymbol{\gamma}} \newcommand{\bsigma}{\boldsymbol{\sigma}} \newcommand{\md}{\mbox{d}} \newcommand{\bmu}{\boldsymbol{\mu}} \newcommand{\bone}{\boldsymbol{1}} \newcommand{\bzero}{\boldsymbol{0}} \newcommand{\bepsilon}{\boldsymbol{\epsilon}} \newcommand{\bphi}{\boldsymbol{\phi}} \newcommand{\bh}{\boldsymbol{h}} \newcommand{\bc}{\boldsymbol{c}} \newcommand{\br}{\boldsymbol{r}} \newcommand{\bQ}{\boldsymbol{Q}} \newcommand{\bK}{\boldsymbol{K}} \newcommand{\bV}{\boldsymbol{V}} \newcommand{\bSigma}{\boldsymbol{\Sigma}} \newcommand{\bg}{\boldsymbol{g}} \newcommand{\bxi}{\boldsymbol{\xi}} \newcommand{\bvarepsilon}{\boldsymbol{\varepsilon}} \newcommand{\bdelta}{\boldsymbol{\delta}} \newcommand{\bq}{\boldsymbol{q}} \newcommand{\bk}{\boldsymbol{k}} \newcommand{\bJ}{\boldsymbol{J}} \newcommand{\bp}{\boldsymbol{p}} \newcommand{\bi}{\boldsymbol{i}} \newcommand{\bo}{\boldsymbol{o}} \newcommand{\bE}{\boldsymbol{E}} \newcommand{\bH}{\boldsymbol{H}} \newcommand{\bL}{\boldsymbol{L}} \newcommand{\bu}{\boldsymbol{u}} \newcommand{\bLambda}{\boldsymbol{\Lambda}} \newcommand{\trans}{^{\rm\scriptsize T}} \newcommand{\var}{\mathrm{var}}\\\newcommand{\bA}{\boldsymbol{A}} \newcommand{\bB}{\boldsymbol{B}} \newcommand{\bC}{\boldsymbol{C}} \newcommand{\bD}{\boldsymbol{D}} \newcommand{\bG}{\boldsymbol{G}} \newcommand{\bI}{\boldsymbol{I}} \newcommand{\bM}{\boldsymbol{M}} \newcommand{\bP}{\boldsymbol{P}} \newcommand{\bS}{\boldsymbol{S}} \newcommand{\bU}{\boldsymbol{U}} \newcommand{\bW}{\boldsymbol{W}} \newcommand{\bX}{\boldsymbol{X}} \newcommand{\bY}{\boldsymbol{Y}} \newcommand{\bZ}{\boldsymbol{Z}} \newcommand{\cotp}{\textcolor[RGB]{48,209,88}{TP}} \newcommand{\cotn}{\textcolor[RGB]{100,210,255}{TN}} \newcommand{\cofp}{\textcolor[RGB]{94,92,230}{FP}} \newcommand{\cofn}{\textcolor[RGB]{191,90,242}{FN}}\\\newcommand{\numcotp}{\textcolor[RGB]{48,209,88}{50}} \newcommand{\numcotn}{\textcolor[RGB]{100,210,255}{30}} \newcommand{\numcofp}{\textcolor[RGB]{94,92,230}{10}} \newcommand{\numcofn}{\textcolor[RGB]{191,90,242}{10}} \DeclareMathOperator*{\argmin}{arg\,min}\end{aligned}\end{align} \]

前置知识#

学习目标与记号#

  1. 准确解释自回归、词元化与词表;

  2. 根据公式和张量维度分析嵌入,并识别常见实现错误;

  3. Python 实现或验证语言模型;在其他条件相同的情况下,只改变一个需要研究的因素进行比较,并根据实验结果分析该因素可能带来的影响。

  本节沿用统一记号:普通小写字母表示标量,粗体小写字母表示向量,粗体大写字母表示矩阵或高阶张量;层编号写作上标 \([l]\)⁠,样本或时间编号写作下标;转置写作 \(\trans\)⁠。除非另有说明,单个向量均为列向量;需要写成行向量时,必须显式使用转置符号 \(\trans\)⁠。若把 \(n\) 个列向量按行组成矩阵,则统一写为 \(\bU=[\bu_1\trans;\ldots;\bu_n\trans]\)⁠。正文与练习中的程序都应同时检查数值结果和数组维度。本节练习的参考答案见 序列建模预备知识答案⁠。

  这些概念为 RNNTransformer 提供输入表示与概率目标;分类或语言建模损失可回看 交叉熵⁠。

  数值时间序列与文本看似不同,却共享一个核心问题:怎样利用已有上下文形成对下一步或整个序列的表示。本节用自回归模型说明滞后依赖,再梳理文本进入神经网络前的数值化过程。

自回归模型#

  一阶自回归模型 AR(1) 可写为

\[x_t=c+\phi x_{t-1}+\varepsilon_t, \qquad \mathbb{E}(\varepsilon_t)=0, \qquad \operatorname{Var}(\varepsilon_t)=\sigma^2,\]

其中噪声通常还假设在时间上不相关。若 \(|\phi|<1\)⁠,该过程存在平稳解;若包含截距,其无条件均值为 \(c/(1-\phi)\)⁠。一般的 AR( \(P\) ) 模型为

\[x_t=c+\sum_{j=1}^{P}\phi_jx_{t-j}+\varepsilon_t.\]

  普通回归并不要求解释变量彼此独立;自回归的特点是解释变量由同一序列的滞后值构成,因而训练、验证和测试必须尊重时间顺序。

  估计参数后,一步预测使用已观测历史。多步递归预测则把前一步预测当作后一步输入,例如 AR(1) 中

\[\widehat x_{t+h\mid t} =\widehat c+\widehat\phi\widehat x_{t+h-1\mid t}, \qquad h=1,2,\ldots,\]

并令 \(\widehat x_{t\mid t}=x_t\)⁠。递归预测计算简单,但误差会逐步传递;直接多步预测为不同预测跨度分别学习模型,减少递归依赖,却增加参数和训练成本。

备注

  时间序列不能默认随机打散后切分。常见做法是用较早区间训练、随后区间验证、最后区间测试,或采用滚动窗口评估。标准化器也只能用相应训练窗口拟合,否则未来分布信息会泄漏。

从文本到张量#

  文本进入模型通常经历以下步骤:

  1. 语料(corpus)⁠:用于训练或评估的文本集合。数据来源、时间范围、重复样本、标签质量和许可都会影响结论。

  2. 词元化(tokenization)⁠:把字符串拆成词、子词、字符或字节级单元。子词方法可以用有限词表表示罕见词,同时避免纯字符序列过长。

  3. 词表(vocabulary)⁠:把每个词元映射到整数 ID,并定义 [UNK][PAD]序列起止符等特殊词元。[UNK] 是未知词元(unknown token),用于表示词表中没有收录的词元;[PAD] 是填充词元(padding token),用于把同一批次中长度不同的序列补到相同长度。[UNK] 表示一段真实但未被词表收录的文本,通常仍需参与模型计算;[PAD] 只是为了方便批量计算而补入的位置,通常需要通过掩码排除,不让它参与注意力或损失计算。词表通常由训练语料或预训练模型确定,不能根据测试集重新构造。

  4. 批处理⁠:不同长度的序列需要填充或截断,并用注意力掩码标明哪些位置是有效词元。

  5. 嵌入(embedding)⁠:用可训练矩阵把离散 ID 映射为稠密向量。

  设词表大小为 \(V\)⁠、嵌入维度为 \(d_e\)⁠,并把 ID 为 \(k\) 的词元嵌入记为列向量 \(\be_k\in\mathbb{R}^{d_e}\)⁠。嵌入矩阵统一写为 \(\bE=[\be_1\trans;\ldots;\be_V\trans]\in\mathbb{R}^{V\times d_e}\)⁠,所以第 \(k\) 行为 \(\be_k\trans\)⁠,即

\[\be_k\trans=\bE_{k,:}, \qquad \be_k=\bE\trans\cdot\bx_k.\]

其中 \(\bx_k\in\{0,1\}^{V}\) 是词元 \(k\) 的独热编码列向量,只有第 \(k\) 个分量为 1,其余分量均为 0。等价的行向量写法为 \(\be_k\trans=\bx_k\trans\cdot\bE\)⁠。实际进行嵌入查表时,不必显式构造这个高维稀疏向量。

  对第 \(b\) 条长度为 \(T\) 的序列,各位置的嵌入 \(\be_{b,t}\) 都是 \(d_e\) 维列向量。为了组成便于批量计算的矩阵,可以把这些列向量转置后按行堆叠:

\[\bX^{(b)} =[\be_{b,1}\trans;\ldots;\be_{b,T}\trans] \in\mathbb{R}^{T\times d_e}.\]

  再把 \(m\) 条序列的表示矩阵 \(\bX^{(1)},\ldots,\bX^{(m)}\) 沿批量方向堆叠,就得到大小为 \(m\times T\times d_e\) 的张量。这里按行存放只是批量计算的组织方式,并没有改变“单个向量默认是列向量”的规定。

../_images/Figure_6_1_word_embedding.png

图 23 静态词嵌入的示意图#

  Word2Vec、GloVe 等静态嵌入为每个词元提供一个固定向量,因此同一词元在不同语境中的初始表示相同。RNN 或 Transformer 会根据上下文进一步产生动态表示。词向量中的单个坐标通常没有预先规定的“年龄”“性别”等唯一语义;二维示意图只能帮助形成直觉,不能当作严格解释。

语言模型#

  令 \(w_t\in\mathcal{V}\) 表示第 \(t\) 个离散词元。自回归语言模型利用概率链式法则分解联合分布:

\[p(w_{1:T}) =\prod_{t=1}^{T}p(w_t\mid w_{<t}), \qquad w_{<t}=(w_1,\ldots,w_{t-1}).\]

  顺序信息体现在条件事件中。\(p(\text{人},\text{咬},\text{狗})\)\(p(\text{狗},\text{咬},\text{人})\) 是两个不同序列事件,没有任何概率公理要求它们相等。

  对训练序列,常最小化平均负对数似然

\[\mathcal{J} =-\frac{1}{n}\sum_{t\in\mathcal{I}} \log p_{\btheta}(w_t\mid w_{<t}),\]

其中 \(\mathcal{I}\) 只包含非填充、需要预测的位置,\(n=|\mathcal{I}|\)⁠。困惑度(perplexity)是 \(\exp(\mathcal{J})\)⁠;只有在分词方式、词表和评价数据一致时,困惑度才可直接比较。

  训练时可一次提供右移后的真实目标序列,这称为 teacher forcing。生成时未来词元未知,模型按已生成前缀逐步计算概率并选择或采样下一个词元。因此相同提示能否得到不同输出,取决于解码策略:贪心或固定束搜索可以是确定的,随机采样才会引入生成随机性。

  语言模型可以计算一个序列出现的概率,也可以为文本生成包含上下文信息的数值表示。若要进一步完成分类、翻译或问答等具体任务,还需要明确模型应当学习什么,增加把这些数值表示转换成具体预测结果的网络层,并准备相应的训练数据。下一节将用共享的循环状态实现 \(p(w_t\mid w_{<t})\) 或其他序列条件预测。

Shiny 交互演示:变长序列怎样组成批量张量

  交互页面接受两个长度可以不同的数值序列。每个位置的输入 \(\bx_{i,t}\in\mathbb{R}^{d_x}\) 均为列向量;程序将行向量 \(\bx_{i,t}\trans\) 放在批量张量的第 \((i,t)\) 个位置,从而组成大小为 \(m\times T\times d_x\) 的张量,并显示补齐位置和有效位置掩码。页面随后用同一组 RNN 参数处理各时间步,帮助区分序列长度、批量轴和特征维度。

点击打开“序列张量与循环神经网络”交互演示

核心推导与实现核验#

核心关系

\[p(x_{1:T})=\prod_{t=1}^{T}p(x_t\mid x_{<t}).\]

  推导路径。 由概率乘法公式逐项展开联合分布;自回归模型以过去词元 \(x_{<t}\) 为条件估计每一项,训练时通常通过目标右移同时构造所有位置。

关键条件

  对数把乘积变成和,因此序列负对数似然等于各有效位置交叉熵之和;排除 padding 后除以有效词元数得到可比较的平均损失。

数据规模

  若一个批次有 \(m\) 条序列、每条序列有 \(T\) 个词元,则词元编号表的大小为 \(m\times T\)⁠。每个词元查表得到一个 \(d\) 维列向量;程序把该向量的转置沿张量的最后一个方向存放,因此批量表示的大小为 \(m\times T\times d\)⁠。若词表有 \(V\) 个词,模型在每个位置得到的输出也是 \(V\) 维列向量;按同样方式堆叠后,输出张量的大小为 \(m\times T\times V\)⁠。

常见误区

  应先划分训练集、验证集和测试集,再只使用训练集中的文本构造词表。如果先使用全部文本构造词表,测试集中出现的词元及其出现情况就会提前影响数据处理过程。为了让同一批次中的序列长度相同而加入的 padding 不是真实词元,因此计算损失和评价指标时必须用掩码排除这些位置,否则计算结果会失真。

动手检查

  把公式 \(p(x_{1:T})=\prod_{t=1}^{T}p(x_t\mid x_{<t})\) 分解为可独立检查的中间量,使用简单数据手工计算结果,并检查输出是否满足模型必须具备的性质;如果已有可靠的程序库实现,还应将两者的结果进行比较。

数值稳定性与规模

  对长度不同的序列组成批次时,通常需要在较短序列的末尾加入 padding由于这些位置不包含真实词元,因此不应参与损失计算。程序可以使用布尔掩码标记有效词元,将有效位置的交叉熵相加,再除以有效词元的总数。输出层的线性运算结果可以直接传给交叉熵函数,无须事先手动计算 Softmax 函数。这样得到的平均损失不会仅仅因为序列长度不同而改变。

本节小结#

  1. 自回归分解来自联合概率的条件概率分解公式。

  2. 词元化决定序列长度、词表和可表达单位。

  3. 只能使用训练集构造词表,并计算数据处理所需的均值、标准差等数值;处理验证集和测试集时,应直接使用这些已经确定的结果,避免提前利用其中的信息。

综合练习#

  程序题应写出维度断言并报告运行环境。全部参考答案见 序列建模预备知识答案⁠。

  1. AR(1) 的平稳矩。\(x_t=c+\phi x_{t-1}+\varepsilon_t\)⁠,假设 \(|\phi|<1\)⁠、噪声独立同分布且均值为 0、方差为 \(\sigma^2\)⁠。推导平稳均值和方差,并说明 \(|\phi|<1\) 在推导中的作用。

  2. 递归预测计算。 已知 AR(1) 模型的估计结果为 \(\widehat c=1\)⁠、\(\widehat\phi=0.5\)⁠,且 \(x_t=6\)⁠。计算未来三步预测 \(\widehat x_{t+1\mid t}\)⁠、\(\widehat x_{t+2\mid t}\)\(\widehat x_{t+3\mid t}\)⁠,并说明递归预测误差为何会沿序列向后传递。

  3. 序列似然推导。 从概率乘法公式出发,推导 \(p(w_{1:T})=\prod_{t=1}^{T}p(w_t\mid w_{<t})\)⁠,再证明序列负对数似然等于各有效预测位置负对数概率之和。说明 padding 位置为何不能计入求和。

  4. 掩码损失计算。 某批次共有四个预测位置,目标词元的预测概率依次为 \(0.8,0.5,0.25,0.1\)⁠,掩码列向量为 \(\boldsymbol{m}=(1,1,0,1)\trans\)⁠。使用自然对数计算平均负对数似然和困惑度,并说明若错误地把第三个位置计入会怎样改变结果。

  5. 词表与批处理实现。 仅根据训练文本实现 build_vocabencodedecodecollate_batch词表必须包含 [UNK][PAD]批处理函数返回词元编号、有效位置掩码和原始长度,并断言三者维度一致。用验证集中未登录词和不同长度序列展示运行结果。

  6. 稳定的掩码交叉熵。 实现直接接收输出层线性运算结果、目标编号和布尔掩码的 masked_cross_entropy沿词表轴使用稳定的 log-sum-exp,按有效词元数归一化;测试单个有效位置、含 padding 的批次、全部位置无效以及极大绝对值输入,并按照 手算结果与可信实现对照 的原则与可信库函数比较。

  7. 循环版与批量版核验。 分别用逐位置循环和批量数组运算实现第 6 题的损失。固定同一输入,逐项比较每个有效位置的负对数概率、总和和平均值;再用 中心差分自动微分 检查梯度,并测量二者处理相同词元数时的运行时间。

  8. 词元化方法比较。 在同一文本分类或小型语言建模数据集上比较字符、词和子词三种表示。固定训练/验证/测试划分、随机种子集合、模型主体、参数量范围和训练更新次数;分别报告预测质量、训练时间、固定批量预测时间和峰值内存,并同时记录词表大小、平均序列长度与未登录率。

  9. 填充策略效率比较。 比较全局定长填充、批内动态填充和按长度分桶三种批处理方式。固定数据划分、随机种子集合、模型参数、优化器、训练更新次数和批次中的原始样本数;报告测试指标、训练时间、固定批量预测时间、填充比例和峰值内存,并核对三种方式在同一未填充样本上的预测结果。

  10. 上下文长度比较。 在同一序列预测任务上比较二元、三元统计语言模型或等价的不同上下文窗口。固定词表、数据划分、随机种子集合、训练样本数和评价代码,并为各方法规定相同的训练遍历次数或相同词元预算;报告困惑度或预测准确率、训练时间、固定批量预测时间和模型内存,讨论更长上下文带来的收益与稀疏性代价。