从原理到实战——序列建模、LSTM 门控与双向多层架构)
教程人工智能机器学习深度学习【免费下载链接】AI-For-Beginners12 Weeks, 24 Lessons, AI for All!项目地址https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners点击查看免费下载本篇技术指南以 AI-For-Beginners 仓库 NLP 单元第 16 课lessons/5-NLP/16-RNN/为核心系统讲解循环神经网络RNN为何是序列建模的必备架构从嵌入聚合丢失词序的痛点出发逐步剖析 RNN 的状态传递机制、单元内部结构以及为解决梯度消失而引入的 LSTM 门控机制并延伸到双向与多层 RNN 的工程实践。读完本文你将掌握 RNN/LSTM 的完整理论脉络并能在 PyTorch 与 TensorFlowKeras两个框架中基于仓库自带 Notebook 实现文本序列分类、处理变长序列并理解打包序列packed sequence与掩码masking等关键工程技巧。图片来源本课程作者绘制。左图为按时间步展开的 RNN 块序列右图为带反馈回路的单块紧凑表示。一、为什么需要 RNN嵌入聚合丢掉了词序在 NLP 单元 的前几课中我们一直在使用嵌入embedding 线性分类器的架构先用预训练或自行训练的嵌入层把词映射为稠密语义向量再在其上做聚合如平均得到整句表示最后送入线性分类器。这种架构能够捕捉句中词语的总体语义但一个致命缺陷是——聚合操作把词的顺序这一信息从原始文本中抹掉了。由于这类模型无法建模词序它们就无法胜任更复杂、更具歧义的任务例如文本生成text generation生成时需要逐个词按顺序推理问答系统question answering答案依赖问题中词语的先后关系。要真正捕捉文本序列的语义就需要一种新的架构——循环神经网络Recurrent Neural Network, RNN。RNN 的核心思想是一次把句子中的一个符号词送入网络网络产生一个状态state这个状态再与下一个符号一起重新送入网络如此往复。这一动机在 RNNPyTorch.ipynb 与 RNNTF.ipynb 的开篇文字中被完整复述是理解本课全部内容的前提。二、RNN 的工作原理展开的块序列与共享权重给定输入 token 序列 X₀, …, XₙRNN 会按时间展开为一串神经网络块并用反向传播端到端训练这串块。其工作机制可以总结为每个网络块接收一对输入(Xᵢ, Sᵢ)其中 Xᵢ 是当前符号Sᵢ 是前一步传来的状态块输出新的状态 Sᵢ₊₁有时还输出 Yᵢ用于生成式网络最终状态 Sₙ或输出 Yₙ被送入线性分类器产生分类结果所有块共享同一套权重并通过一次完整的反向传播端到端训练。因为状态向量 S₀, …, Sₙ 在整个序列中持续传递网络得以学习词与词之间的顺序依赖关系。举个直观的例子当序列中出现否定词not时网络可以学会把状态向量中的某些分量取反从而实现否定语义的传递。✅ 由于上图所有 RNN 块的权重都是共享的这张展开图完全可以等价地表示为右侧的单个块 反馈回路块的输出状态再送回自己的输入。这是理解 RNN 递推本质的关键视角。值得注意的是虽然图中按时间展开会形成很多块但参数数量并不会随序列长度增长——这正是权重共享带来的核心优势。三、RNN 单元解剖两个权重矩阵与一个公式让我们看看一个简单 RNN 单元的内部结构。它接收两个输入前一时刻的状态 Sᵢ₋₁当前符号 Xᵢ。并输出新状态 Sᵢ在生成式网络中我们通常还关注额外输出 Yᵢ。一个简单 RNN 单元内部包含两个权重矩阵W负责变换输入符号H负责变换输入状态。网络的输出按下式计算Sᵢ σ(W × Xᵢ H × Sᵢ₋₁ b)其中 σ 是激活函数常用 tanhb 是额外的偏置项。RNNTF.ipynb 中把同样的公式写成了更通用的形式Sᵢ₊₁ f(W_H × Sᵢ W_I × Xᵢ b)并补充说明——对于文本生成、机器翻译这类需要在每个时间步都输出值的任务还会额外引入输出矩阵 W_O输出计算为 Yᵢ f(W_O × Sᵢ b_O)。图片来源本课程作者绘制。嵌入层与维度关系在多数实际场景中输入 token 会先经过一个**嵌入层Embedding**降维再进入 RNN。此时维度关系如下输入向量维度为emb_size状态向量维度为hid_size矩阵 W 的尺寸为emb_size × hid_size矩阵 H 的尺寸为hid_size × hid_size。这一维度关系可以直接对应到 PyTorch 代码在 RNNPyTorch.ipynb 中torch.nn.Embedding(vocab_size, embed_dim)负责从vocab_size维词表映射到embed_dim而torch.nn.RNN(embed_dim, hidden_dim, batch_firstTrue)的输入维正是embed_dim、隐藏维正是hidden_dim。四、PyTorch 实战基于 AG_NEWS 的简单 RNN 文本分类器4.1 数据准备与词表构建RNNPyTorch.ipynb 使用torchtext加载AG_NEWS新闻数据集四分类World / Sports / Business / Sci-Tech词表构建逻辑封装在仓库的辅助模块 torchnlp.py 中import torch import torchtext from torchnlp import * train_dataset, test_dataset, classes, vocab load_dataset() vocab_size len(vocab)load_dataset()见 torchnlp.py的内部流程是通过torchtext.datasets.AG_NEWS(root./data)下载数据 → 用basic_english分词器配合ngrams_iterator统计词频 → 用torchtext.vocab.vocab(counter, min_freqmin_freq)构建词表。encode()函数则借助stoistring-to-index映射把文本转为索引序列未知词映射为 0。4.2 定义简单 RNN 分类器在 PyTorch 中单个 RNN 单元由torch.nn.RNNCell表示整层 RNN 由torch.nn.RNN表示。Notebook 中的分类器结构如下class RNNClassifier(torch.nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_class): super().__init__() self.hidden_dim hidden_dim self.embedding torch.nn.Embedding(vocab_size, embed_dim) self.rnn torch.nn.RNN(embed_dim, hidden_dim, batch_firstTrue) self.fc torch.nn.Linear(hidden_dim, num_class) def forward(self, x): batch_size x.size(0) x self.embedding(x) x, h self.rnn(x) return self.fc(x.mean(dim1))结构要点Embedding层把 token 索引映射为embed_dim维向量降低输入维度RNN层一次返回两个输出x是每个时间步的单元输出序列h是序列最后一个元素的最终隐藏状态这里对 x 做mean(dim1)聚合后再过全连接线性分类器fc得到类别分数。注Notebook 明确说明这里为了简化使用了未预训练的嵌入层如果需要更好的效果可以替换为上一单元讲过的 Word2Vec 或 GloVe 预训练嵌入。4.3 训练与关键调参经验train_loader torch.utils.data.DataLoader(train_dataset, batch_size16, collate_fnpadify, shuffleTrue) net RNNClassifier(vocab_size, 64, 32, len(classes)).to(device) train_epoch(net, train_loader, lr0.001)这里的关键参数batch_size16、embed_dim64、hidden_dim32、学习率lr0.001。训练循环由 torchnlp.py 中的train_epoch()提供使用Adam 优化器与CrossEntropyLoss 交叉熵损失。从 Notebook 记录的运行输出看简单 RNN 在约 10.88 万样本后准确率升至约0.806。同时 Notebook 给出了两条重要的工程经验RNN 很难训练因为 RNN 单元沿序列长度展开后反向传播涉及的层数非常多序列越长、计算图越深必须选择较小的学习率并在更大的数据集上训练优先使用 GPU训练耗时较长device的选择逻辑cuda优先否则回退cpu实现在 torchnlp.py 中。五、TensorFlowKeras实战SimpleRNN 分类器RNNTF.ipynb 提供了对应的 Keras 实现。它用tfdsTensorFlow Datasets加载ag_news_subset并用keras.layers.experimental.preprocessing.TextVectorization做文本向量化vocab_size 20000 embed_size 64 vectorizer keras.layers.experimental.preprocessing.TextVectorization( max_tokensvocab_size, input_shape(1,)) model keras.models.Sequential([ vectorizer, keras.layers.Embedding(vocab_size, embed_size), keras.layers.SimpleRNN(16), keras.layers.Dense(4, activationsoftmax) ]) model.summary()与 PyTorch 版本形成一一对应SimpleRNN层对应torch.nn.RNNDense(4, activationsoftmax)对应线性分类器。从model.summary()的输出可见参数分布Embedding 层 1,280,000 个参数、SimpleRNN 层 1,296 个、Dense 层 68 个总计 1,281,364 个可训练参数。Notebook 中还给出了两个 Keras 特有细节GPU 显存增长配置tf.config.experimental.set_memory_growth(...)避免训练大模型时显存分配报错只训练标题以加速extract_title/tupelize_title把训练数据简化为新闻标题省略正文描述记录输出显示训练约 82 秒/7500 步得到 train acc ≈ 0.762、val acc ≈ 0.80同时提醒只看标题会拉低精度上限。此外 Notebook 提示某些 NVIDIA 驱动版本在训练结束后不会自动释放显存若在同一 Notebook 中连续运行多个实验遇到怪异报错可以重启内核。六、经典 RNN 的软肋梯度消失问题经典 RNN 面临一个著名难题——梯度消失vanishing gradients。由于 RNN 是在一次反向传播中端到端训练的误差信号需要沿展开的时间步一路传回网络最早最左的块路径越长、梯度被反复相乘稀释得越严重最终导致网络难以把误差传播到序列早期的层无法学习相距很远的 token 之间的关系。一个绕开该问题的经典思路是引入显式状态管理explicit state management即使用门控gates。最具代表性的两种门控架构分别是长短期记忆Long Short-Term Memory, LSTM门控循环单元Gated Recurrent Unit, GRU。门控思想的本质是不再让信息无差别地流过整个序列而是由可学习的门决定每一步该记住什么、该忘掉什么。七、LSTM 架构详解三个门的协同LSTM 的组织方式与普通 RNN 相似但层与层之间传递的是两个状态真正的状态 Ccell state记忆主线隐藏向量 Hhidden vector。在每个单元中隐藏向量 Hᵢ 与输入 Xᵢ 拼接二者通过门共同控制状态 C 的演变。每个门本质上是一个带sigmoid 激活输出范围 [0, 1]的小型神经网络——当它与状态向量逐元素相乘时就相当于一个按位的掩码bitwise mask决定放行多少信息。图片来源本课程原始来源待考。LSTM 包含三个门按上图中从左到右的顺序门作用遗忘门forget gate读取隐藏向量决定状态向量 C 中哪些分量需要被遗忘、哪些要保留传递下去输入门input gate从输入向量与隐藏向量中提取新信息并将其写入状态输出门output gate先用带tanh激活的线性层变换状态再利用隐藏向量 Hᵢ 选择其中的部分分量生成新状态 Cᵢ₊₁状态 C 的直觉理解可拨动的开关标志可以把状态 C 的各分量想象成一组可以打开/关闭的标志位flags。例如在序列中遇到人名Alice可以推测其指代女性角色于是在状态中竖起一面旗表示句子中存在阴性名词之后遇到and Tom再竖起存在复数名词的旗。通过反复操纵这些状态位网络就能粗略地跟踪句子各部分的语法属性并把这些信息一路携带到序列末尾——这正是普通 RNN 难以做到的远距离依赖建模。✅ 想深入理解 LSTM 内部机制的读者推荐 Christopher Olah 的经典文章《Understanding LSTM Networks》它用非常直观的交互式图解拆解了每个门的具体计算。八、LSTM 实战代码把 RNN 层换成 LSTM 层即可8.1 PyTorch 版虽然 LSTM 单元内部结构看起来复杂但 PyTorch 已经把它封装在torch.nn.LSTMCell单单元与torch.nn.LSTM整层中。因此 RNNPyTorch.ipynb 中 LSTM 分类器的实现与简单 RNN 几乎相同只改了两处class LSTMClassifier(torch.nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_class): super().__init__() self.hidden_dim hidden_dim self.embedding torch.nn.Embedding(vocab_size, embed_dim) self.embedding.weight.data torch.randn_like(self.embedding.weight.data) - 0.5 self.rnn torch.nn.LSTM(embed_dim, hidden_dim, batch_firstTrue) self.fc torch.nn.Linear(hidden_dim, num_class) def forward(self, x): batch_size x.size(0) x self.embedding(x) x, (h, c) self.rnn(x) return self.fc(h[-1])两处差异值得注意torch.nn.RNN→torch.nn.LSTM并把 embedding 初始化为均匀分布randn_like - 0.5前向传播时 LSTM 返回二元组(h, c)——h是所有层最后的隐藏状态、c是单元状态。由于这里取最后一个时间步的隐藏状态h[-1]送入分类器不再需要像简单 RNN 那样对输出序列做mean聚合。Notebook 记录的 LSTM 训练结果约为 loss 0.0349 / acc0.773并提醒LSTM 训练同样很慢训练初期精度可能没有明显提升需要耐心同时建议调节lr找到训练速度合理又不浪费显存的学习率。8.2 Keras 版RNNTF.ipynb 中Keras 把 LSTM 实现隐藏在keras.layers.LSTM层内因此只需替换循环层model keras.models.Sequential([ vectorizer, keras.layers.Embedding(vocab_size, embed_size), keras.layers.LSTM(8), keras.layers.Dense(4, activationsoftmax) ]) model.compile(losssparse_categorical_crossentropy, metrics[acc], optimizeradam) model.fit(ds_train.map(tupelize).batch(8), validation_datads_test.map(tupelize).batch(8))记录输出显示train loss ≈ 0.569、train acc ≈ 0.792、val acc ≈ 0.887优于仅看标题的 SimpleRNNval acc ≈ 0.80这说明 LSTM 对更长文本的远距离依赖建模能力更强。九、处理变长序列PyTorch 打包序列与 Keras 掩码真实文本序列长度不一批量训练时通常需要**填充padding**到同一长度。但零填充向量也会参与训练白白增加无意义的 RNN 单元计算。这一节介绍两个框架各自的解法。9.1 PyTorchPacked Sequence打包序列RNNPyTorch.ipynb 用一段非常清晰的例子说明思想。假设一个批次的填充矩阵为[[1,2,3,4,5], [6,7,8,0,0], [9,0,0,0,0]]其中 0 是填充值各序列真实长度为[5,3,1]。理想做法是第一批单元同时处理[1,6,9]处理完第 3 条序列后将其退出第二批只处理[2,7]第三批处理[3,8]……这样训练只发生在真实序列长度之内。打包序列的存储格式就是一个展平向量 长度向量展平向量[1,6,9,2,7,3,8,4,5]长度向量[5,3,1]据此可以无歧义地还原原始批次。PyTorch 提供了两个工具函数torch.nn.utils.rnn.pack_padded_sequence把填充矩阵打包torch.nn.utils.rnn.pad_packed_sequence把打包结果解包回填充矩阵。所有循环层RNN / LSTM / GRU都原生支持打包序列输入。使用打包序列时DataLoader 的collate_fn需要额外返回长度向量def pad_length(b): v [encode(x[1]) for x in b] len_seq list(map(len, v)) l max(len_seq) return ( torch.LongTensor([t[0]-1 for t in b]), torch.stack([torch.nn.functional.pad(torch.tensor(t), (0, l-len(t)), modeconstant, value0) for t in v]), torch.tensor(len_seq) ) train_loader_len torch.utils.data.DataLoader( train_dataset, batch_size16, collate_fnpad_length, shuffleTrue)对应的LSTMPackClassifier前向传播如下class LSTMPackClassifier(torch.nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_class): super().__init__() self.embedding torch.nn.Embedding(vocab_size, embed_dim) self.embedding.weight.data torch.randn_like(self.embedding.weight.data) - 0.5 self.rnn torch.nn.LSTM(embed_dim, hidden_dim, batch_firstTrue) self.fc torch.nn.Linear(hidden_dim, num_class) def forward(self, x, lengths): x self.embedding(x) pad_x torch.nn.utils.rnn.pack_padded_sequence( x, lengths, batch_firstTrue, enforce_sortedFalse) pad_x, (h, c) self.rnn(pad_x) x, _ torch.nn.utils.rnn.pad_packed_sequence(pad_x, batch_firstTrue) return self.fc(h[-1])Notebook 记录显示打包序列版 LSTM 最终达到约 loss 0.0298 /acc 0.814比普通填充版0.773进一步提升同时节省了大量无效计算。注pack_padded_sequence要求长度张量位于CPU因此训练函数需要避免把长度向量搬到 GPU。这一逻辑实现在 torchnlp.py 的train_epoch_emb()中——当use_pack_sequenceTrue时执行off off.to(cpu)。Notebook 里还提到由于实际只用隐藏状态h[-1]可以省略pad_packed_sequence解包步骤保留它是为了方便你在需要逐时间步输出时直接改造。9.2 KerasMasking 掩码与按长度分桶RNNTF.ipynb 指出TextVectorization层会自动用填充 token 对齐批次内变长序列而这些填充 token 同样参与训练、会干扰收敛。该 Notebook 给出两种缓解方案按序列长度分桶用tf.data.experimental.bucket_by_sequence_length把长度相近的样本分到同一批次最小化填充量掩码maskingKeras 中某些层支持额外输入指定哪些 token 应参与训练。实现方式有两种——显式插入Masking层或给Embedding层设置mask_zeroTruemodel keras.models.Sequential([ vectorizer, keras.layers.Embedding(vocab_size, embed_size, mask_zeroTrue), keras.layers.SimpleRNN(16), keras.layers.Dense(4, activationsoftmax) ])开启掩码后即可放心地在标题正文的完整数据上训练此前仅用标题是为了加速。记录输出显示train acc ≈ 0.808、val acc ≈ 0.882明显高于只用标题的 0.80。Notebook 还提醒向量化器此前是在标题上训练的严格来说应重新在全量文本上adapt但影响很小为简化而保留。十、双向与多层 RNN两种加宽加深的架构10.1 双向 RNNBidirectional RNN此前讨论的 RNN 都从序列开头到结尾单向运行这与人类阅读、听语音的方向一致看起来很自然。但很多实际场景对输入序列是随机访问的——例如命名实体识别中一个词的类别往往同时依赖其前后文。此时就有理由让循环计算双向进行这样的网络称为双向 RNN。双向网络需要维护两个隐藏状态向量一个负责正向、一个负责反向PyTorch给RNN/LSTM/GRU构造器传bidirectionalTrue即可。PyTorch 会把两个方向的状态向量编码为一个尺寸加倍的向量——这很方便你只需在创建后续全连接层时把输入维度相应加倍RNNPyTorch.ipynb 中明确指出了这一点Keras用keras.layers.Bidirectional包装循环层。该层内部会复制两份循环层并把其中一份的go_backwards属性设为True使其沿序列反向运行RNNTF.ipynb。10.2 多层 RNNMulti-layer RNN无论单向还是双向循环网络都能在序列中捕捉某些模式并将其存入状态向量或作为输出。与卷积网络逐层抽象类似我们可以在第一层循环网络之上再堆叠一层让高层从低层提取的低级模式中进一步组合出高级模式——这就是多层 RNN的概念由两个或更多循环网络组成前一层的输出作为下一层的输入。图片来源Fernando López 关于从 LSTM 单元到 PyTorch 多层 LSTM 网络的文章配图。两个框架的实现差异体现了各自的设计哲学PyTorch只需给构造器传num_layers参数框架自动构建多层循环同时隐藏/状态向量的尺寸会成比例增大处理循环层输出时需注意RNNPyTorch.ipynbKeras直接在Sequential中追加循环层即可但除最后一层外都必须设置return_sequencesTrue因为中间层需要输出全部时间步的状态而非仅最终状态。Notebook 给出的双层双向 LSTM 示例model keras.models.Sequential([ vectorizer, keras.layers.Embedding(vocab_size, 128, mask_zeroTrue), keras.layers.Bidirectional(keras.layers.LSTM(64, return_sequencesTrue)), keras.layers.Bidirectional(keras.layers.LSTM(64)), keras.layers.Dense(4, activationsoftmax) ]) model.compile(losssparse_categorical_crossentropy, metrics[acc], optimizeradam) model.fit(ds_train.map(tupelize).batch(batch_size), validation_datads_test.map(tupelize).batch(batch_size))该双层双向 LSTM 是 RNNTF.ipynb 中精度最高的模型训练中断前的记录 acc 已达 ≈0.871明显优于单层单向的 SimpleRNN≈0.762与单层 LSTM≈0.792代价是训练时间显著更长——这也印证了双向 多层以算力换精度的权衡。十一、总结RNN 的能力边界与下一站本单元已经展示RNN 不仅能用于序列分类其能力远不止于此还包括文本生成text generation逐时间步输出下一个 token机器翻译machine translation编码器-解码器结构以及问答、命名实体识别等更多序列任务。这些任务将在本课程的下一单元17-GenerativeNetworks中继续探讨。值得注意的是RNN 的逐时间步递归特性也决定了它训练慢、难以并行的短板——这正是后续课程中 Transformer 架构出现的直接动机但理解 RNN/LSTM 的状态传递与门控思想依然是理解一切序列模型的地基。十二、挑战与延伸学习 挑战研读 LSTM 文献并思考应用课程为学有余力的读者布置了文献研读挑战思考 LSTM 在各类场景中的应用Grid Long Short-Term Memory探讨将 LSTM 单元组织为多维网格结构的论文尝试把门控记忆扩展到非序列如图像数据Show, Attend and Tell: Neural Image Caption Generation with Visual Attention把注意力机制引入图像描述生成是序列模型 注意力交叉应用的经典工作。课后作业用你自己的数据集重跑 Notebook课后作业assignment.md建议你选择本课的 PyTorch 或 TensorFlow NotebookRNNPyTorch.ipynb / RNNTF.ipynb之一使用你自己的数据集例如 Kaggle 上的天气推文分类数据集注意注明数据来源重跑训练流程改写 Notebook突出你自己的实验发现尝试不同类型的数据集并用文字记录结论。换数据集时建议重点关注词表规模vocab_size、嵌入维度embed_dim、隐藏维度hidden_dim、批次大小与学习率这几个超参的联动调整——torchnlp.py 中的load_dataset()、padify()、train_epoch()/train_epoch_emb()均以参数形式暴露了这些配置便于直接复用。自学资源Christopher Olah 的《Understanding LSTM Networks》公认的 LSTM 内部机制最佳图解入门适合反复研读。延伸阅读本课属于 NLP 单元 的组成部分其前置基础是 文本表示 与 词嵌入后续可衔接 生成式网络 与 Transformer一路贯通从词向量到现代大模型的完整技术脉络。赞分享教程人工智能机器学习深度学习【免费下载链接】AI-For-Beginners12 Weeks, 24 Lessons, AI for All!项目地址https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners点击查看免费下载相关推荐AI-For-Beginners 循环神经网络RNN实战指南从词序建模、LSTM 门控到双向多层架构AI For Beginners 循环神经网络RNN实战指南从词序建模、LSTM 门控到双向多层架构 导读 本文基于 AI For Beginners 课教程人工智能机器学习深度学习AI-For-Beginners 课程解读循环神经网络RNN与 LSTM 序列建模实战AI For Beginners 课程解读循环神经网络RNN与 LSTM 序列建模实战 导读 本篇文章基于 AI For Beginners 开源课程第教程人工智能机器学习深度学习AI-For-Beginners 课程实战用循环神经网络RNN与 LSTM 建模文本序列顺序AI For Beginners 课程实战用循环神经网络RNN与 LSTM 建模文本序列顺序 循环神经网络RNN解决了词向量嵌入聚合后丢失 词序信息教程人工智能机器学习深度学习上一篇猫抓 Cat-Catch 完整指南网页视频、音频与 M3U8 流媒体怎么下载到本地下一篇告别复杂环境配置LaTeX在线编译服务的终极解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考