虽然简单的循环神经网络(RNN)是为序列数据设计的,但在处理语音中长而变化的序列时,它们会遇到显著的限制。它们的主要缺点是难以在较长时间步中学习和保留信息,这个问题通常被称为处理长距离依赖。这个问题主要源于梯度消失问题,即随着序列变长,过去输入对当前输出的影响呈指数级减小。对于ASR系统而言,这是一个严重不足。模型必须能够将相隔几秒的声音联系起来,以形成连贯的词或短语。例如,为了正确转录句子的末尾,模型可能需要回溯开头的信息。简单的RNN无法很好地完成这项任务。为了应对这些难题,我们转向更精巧的循环架构:长短期记忆(LSTM)和门控循环单元(GRU)网络。长短期记忆 (LSTM)LSTM被明确设计用于长时间记住信息。它们引入了一个单元状态,它就像一条信息传送带,沿着整个序列直线传输,只进行少量线性交互。这种结构使得信息更容易保持不变地流动。LSTM的独特之处在于它们能够使用三个称为门的特殊组件来调节信息流。这些门是小型神经网络,它们会学习哪些信息需要添加到单元状态、哪些需要保留、哪些需要移除。遗忘门: 这个门决定了从前一个单元状态中丢弃哪些信息。它会查看前一个隐藏状态 $h_{t-1}$ 和当前输入 $x_t$,并为前一个单元状态 $C_{t-1}$ 中的每个数值输出一个介于0和1之间的数。1表示“完全保留”,而0表示“完全丢弃”。在语音中,这可能意味着忘记单词之间短暂静音的声学特征。 $$ f_t = \sigma(W_f \cdot [h_{t-1}, x_t] + b_f) $$输入门: 这个门决定了哪些新信息将被存储到单元状态中。它包含两部分:一个sigmoid层,用于决定我们要更新哪些值(即输入门);以及一个tanh层,用于生成可能添加到状态中的新候选值向量 $\tilde{C}t$。模型就是这样加入新的声音的,例如新音素的开始。 $$ i_t = \sigma(W_i \cdot [h{t-1}, x_t] + b_i) $$ $$ \tilde{C}t = \tanh(W_C \cdot [h{t-1}, x_t] + b_C) $$输出门: 这个门决定了网络的输出。输出将是单元状态的过滤版本。首先,我们运行一个sigmoid层来决定单元状态的哪些部分将被输出。然后,我们将单元状态通过tanh(将值推到-1到1之间)并乘以sigmoid门的输出,这样我们只输出我们决定输出的部分。 $$ o_t = \sigma(W_o \cdot [h_{t-1}, x_t] + b_o) $$ $$ h_t = o_t * \tanh(C_t) $$新的单元状态 $C_t$ 是通过结合旧状态(乘以遗忘门)和新候选值(乘以输入门)来计算的。$$ C_t = f_t * C_{t-1} + i_t * \tilde{C}_t $$这种门控机制允许LSTM选择性地记忆或遗忘信息,使其在建模语音复杂的时序动态方面非常有效。digraph G { rankdir=TB; splines=ortho; node [shape=box, style="filled", fillcolor="#a5d8ff", fontname="helvetica", margin="0.2,0.1"]; edge [fontname="helvetica", fontsize=10]; subgraph cluster_lstm { label="LSTM 单元"; style="dashed"; bgcolor="#e9ecef"; // 节点 xt [label="输入 (x_t)", fillcolor="#b2f2bb"]; ht_prev [label="隐藏状态 (h_{t-1})"]; ct_prev [label="单元状态 (C_{t-1})"]; // 门 forget_gate [label="遗忘门 (σ)", shape=ellipse, fillcolor="#ffc9c9"]; input_gate [label="输入门 (σ)", shape=ellipse, fillcolor="#ffc9c9"]; candidate_gate [label="候选 (tanh)", shape=ellipse, fillcolor="#ffc9c9"]; output_gate [label="输出门 (σ)", shape=ellipse, fillcolor="#ffc9c9"]; // 运算符 mul1 [label="×", shape=circle, fillcolor="#ffd8a8"]; mul2 [label="×", shape=circle, fillcolor="#ffd8a8"]; add1 [label="+", shape=circle, fillcolor="#ffd8a8"]; mul3 [label="×", shape=circle, fillcolor="#ffd8a8"]; tanh_out [label="tanh", shape=ellipse, fillcolor="#d0bfff"]; // 输出节点 ct [label="单元状态 (C_t)"]; ht [label="隐藏状态 (h_t)", fillcolor="#b2f2bb"]; // 连接 {xt, ht_prev} -> forget_gate [style=invis]; {xt, ht_prev} -> input_gate [style=invis]; {xt, ht_prev} -> candidate_gate [style=invis]; {xt, ht_prev} -> output_gate [style=invis]; // 路由的虚拟节点 dummy1 [shape=point, width=0]; dummy2 [shape=point, width=0]; xt -> dummy1 [dir=none]; ht_prev -> dummy1 [dir=none]; dummy1 -> forget_gate; dummy1 -> input_gate; dummy1 -> candidate_gate; dummy1 -> output_gate; ct_prev -> mul1; forget_gate -> mul1; input_gate -> mul2; candidate_gate -> mul2; mul1 -> add1; mul2 -> add1; add1 -> ct; add1 -> tanh_out; output_gate -> mul3; tanh_out -> mul3; ct -> ct [style=invis]; // keep on same level mul3 -> ht; // 水平单元状态线 ct_prev -> ct [constraint=false, label=" 遗忘"]; // 水平隐藏状态线 ht_prev -> ht [style=invis]; } }一个LSTM单元。最上面的线表示单元状态,它在时间步之间传递信息。门(红色)控制信息如何添加到或从该状态中移除。门控循环单元 (GRU)门控循环单元,简称GRU,是LSTM的一种较新且略简单的替代方案。它将遗忘门和输入门合并为一个更新门,并融合了单元状态和隐藏状态。这使得模型在计算上更高效。一个GRU单元有两个主要门:更新门 ($z_t$): 这个门的功能与LSTM的遗忘门和输入门相似。它决定了保留多少过去的信息(来自前一个隐藏状态),以及添加多少新信息。重置门 ($r_t$): 这个门决定了如何将新输入与前一个隐藏状态结合。它本质上决定了要遗忘多少过去的信息。GRU的简化结构意味着它们比LSTM拥有更少的参数,这使得它们在训练时更快,并且在较小数据集上更不容易出现过拟合。在许多ASR任务中,GRU已表现出与LSTM相当的性能。digraph G { rankdir=TB; splines=ortho; node [shape=box, style="filled", fillcolor="#a5d8ff", fontname="helvetica", margin="0.2,0.1"]; edge [fontname="helvetica", fontsize=10]; subgraph cluster_gru { label="GRU 单元"; style="dashed"; bgcolor="#e9ecef"; // 节点 xt [label="输入 (x_t)", fillcolor="#b2f2bb"]; ht_prev [label="隐藏状态 (h_{t-1})"]; // 门 update_gate [label="更新门 (z)", shape=ellipse, fillcolor="#ffc9c9"]; reset_gate [label="重置门 (r)", shape=ellipse, fillcolor="#ffc9c9"]; candidate_gate [label="候选 (h̃)", shape=ellipse, fillcolor="#d0bfff"]; // 运算符 mul1 [label="×", shape=circle, fillcolor="#ffd8a8"]; mul2 [label="×", shape=circle, fillcolor="#ffd8a8"]; mul3 [label="×", shape=circle, fillcolor="#ffd8a8"]; one_minus [label="1 - z", shape=ellipse, fillcolor="#ced4da"]; add1 [label="+", shape=circle, fillcolor="#ffd8a8"]; // 输出节点 ht [label="隐藏状态 (h_t)", fillcolor="#b2f2bb"]; // 路由的虚拟节点 dummy1 [shape=point, width=0]; xt -> dummy1 [dir=none]; ht_prev -> dummy1 [dir=none]; dummy1 -> update_gate; dummy1 -> reset_gate; reset_gate -> mul1; ht_prev -> mul1; mul1 -> candidate_gate; xt -> candidate_gate; update_gate -> one_minus; one_minus -> mul2; ht_prev -> mul2; update_gate -> mul3; candidate_gate -> mul3; mul2 -> add1; mul3 -> add1; add1 -> ht; } }一个GRU单元。它通过合并门控和融合单元状态与隐藏状态来简化LSTM设计,从而形成更精简的架构。双向和堆叠架构为了进一步增强模型理解上下文的能力,我们可以采用两种额外的策略:双向处理和堆叠。双向RNN 在语音中,上下文不仅是历史性的,也是前瞻性的。一个词的读音或含义可能受其后续词的影响。例如,为了区分“I read the book”和“I will read the book”中的同音词,模型通过看到整个句子而获得优势。双向RNN(Bi-LSTM或Bi-GRU)以两个方向处理输入序列。一个循环层从头到尾处理序列(前向传播),而第二个独立的层从尾到头处理序列(反向传播)。在每个时间步,两个层的输出会被拼接起来形成最终的表示。这使得模型能够获得序列中每个点的完整周边上下文信息。堆叠RNN 就像其他类型的神经网络一样,我们可以通过将循环层堆叠起来增加模型的深度。在堆叠RNN中,第一层的输出序列成为第二层的输入序列,依此类推。这种方法使得网络能够学习数据的分层表示。第一层可能学习检测基本的声学特征和音素。第二层可以学习将这些音素表示组合成音节或词段,后续层可以学习更高级别的语言结构。典型的ASR声学模型可能会使用2到6个堆叠的双向LSTM或GRU层。通过使用LSTM或GRU,通常采用堆叠、双向的配置,我们创建了一个声学模型,该模型在捕捉人类语音复杂、长距离模式方面非常有效。这种出色的序列到序列架构,是我们下一节构建基于CTC训练流程的立足点。