AI-For-Beginners 递归神经网络(RNN)实战:从 RNN 细胞解剖到 LSTM、双向与多层网络的序列建模指南
教程人工智能机器学习深度学习【免费下载链接】AI-For-Beginners12 Weeks, 24 Lessons, AI for All!项目地址https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners点击查看免费下载递归神经网络Recurrent Neural NetworkRNN是自然语言处理中处理序列数据的核心架构。在 AI-For-Beginners 课程的 NLP 模块中第 16 课lessons/5-NLP/16-RNN系统讲解了 RNN 的动机、细胞结构、LSTM 门控机制并提供了 PyTorch 与 TensorFlow 两套可运行的新闻分类实战 Notebook。本文以该课文档为主体结合仓库内的 RNNPyTorch.ipynb、RNNTF.ipynb 与 torchnlp.py 源码完整覆盖从理论到代码的全部内容。读完本文你将掌握RNN 为何能建模词序、RNN 细胞的权重结构与计算公式、LSTM 三门机制如何缓解梯度消失、变长序列的 padding 处理技巧PyTorch packed sequence 与 Keras masking以及双向/多层 RNN 的构造方法。为什么需要 RNN词嵌入无法建模词序在课程前面的单元中我们使用词嵌入Embedding的富语义表示再叠加一个简单的线性分类器来对文本分类。这种架构能捕捉句子中单词的聚合含义但聚合操作如取平均会把词序信息从原始文本中抹掉因此这些模型无法建模单词的顺序。而像文本生成、问答这类更复杂或带有歧义的任务恰恰高度依赖词序——例如 not 对句子语义的否定作用只有在序列上下文中才能被理解。为了捕捉文本序列的含义我们需要另一种神经网络架构——递归神经网络RNN。在 RNN 中我们把句子一个符号一个符号地送入网络网络读取当前符号并产生一个状态state这个状态又连同下一个符号一起再次送入网络。这样状态向量 S₀, …, Sₙ 在整个序列中不断传递网络便能够学习单词之间的顺序依赖关系。例如当序列中出现 not 这个词时RNN 可以学会对状态向量中的某些分量做取反操作从而实现否定语义。✅ 提示由于上图中所有 RNN 块的权重是共享的同一幅图也可以表示成右侧那种带循环反馈回路的单个块——网络的输出状态被重新反馈回输入端。这也是递归recurrent一词的来源。RNN 的端到端训练机制给定输入 token 序列 X₀, …, XₙRNN 会创建一串神经网络块并用**反向传播backpropagation**对这一串网络进行端到端训练每个网络块以(Xᵢ, Sᵢ)为输入产生 Sᵢ₊₁ 作为输出最终状态 Sₙ或输出 Yₙ送入一个线性分类器得到最终结果所有网络块共享同一组权重整个序列只用一次反向传播过程完成训练。TensorFlow 版 Notebook 补充了每一步输出 Yᵢ 的情况对于文本生成、机器翻译等任务我们希望在每一步都拿到输出此时细胞内还存在另一组矩阵 Wₒ输出按Yᵢ f(Wₒ × Sᵢ bₒ)计算见 RNNTF.ipynb。RNN 细胞解剖输入矩阵 W 与状态矩阵 H一个简单 RNN 细胞接收前一个状态 Sᵢ₋₁ 和当前符号 Xᵢ 作为输入并产生输出状态 Sᵢ生成式网络有时还关心额外输出 Yᵢ。细胞内包含两组内部权重矩阵W变换输入符号H变换输入状态。网络的输出按如下公式计算Sᵢ σ(W × Xᵢ H × Sᵢ₋₁ b)其中 σ 是激活函数b 是额外偏置bias。TensorFlow Notebook 中把这两个矩阵记作 W_I 与 W_H并指出激活函数常用 tanh。在多数情况下token 会先经过**嵌入层embedding layer**降维后再进入 RNN。此时若输入向量维度为emb_size、状态向量维度为hid_size则 W 的尺寸为emb_size × hid_sizeH 的尺寸为hid_size × hid_size——这一点直接决定了你在代码里构造nn.RNN/nn.LSTM时各层维度的对应关系。实战一用 PyTorch 构建简单 RNN 新闻分类器PyTorch Notebook 用 AG_NEWS 新闻数据集World / Sports / Business / Sci-Tech 四分类演示了完整流程。数据加载、词汇表构建、编码、训练循环等通用逻辑都封装在 torchnlp.py 中load_dataset()加载 AG_NEWS 训练/测试集用 Counter 统计词频并构建 vocabtorchtext.vocab.vocab(counter, min_freqmin_freq)encode()通过get_stoi()或旧版stoi属性把 token 序列映射为整数索引padify()将 batch 内序列按最大长度补齐为等长张量F.pad(..., value0)返回标签与特征两个张量offsetify()拼接所有序列并计算 offsets供EmbeddingBag使用train_epoch()/train_epoch_emb()标准的 Adam 优化 CrossEntropyLoss 训练循环支持use_pack_sequence参数控制长度张量是否留在 CPU。在 Notebook 中RNNClassifier的结构与前面提到的权重维度一一对应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))要点解读torch.nn.RNNCell表示单个细胞torch.nn.RNN表示由若干细胞组成的完整层self.rnn(x)返回两个输出x是每个时间步的细胞输出序列h是序列最后一个元素的最终隐藏状态本分类器取x.mean(dim1)聚合后过全连接层Notebook 使用未训练的嵌入层并提示可以换用上一单元讲过的 Word2Vec / GloVe 预训练嵌入以获得更好效果训练时使用batch_size16、lr0.001。Notebook 强调RNN 一旦沿序列长度展开反向传播涉及的层数会非常多训练困难、速度慢应使用较小的学习率并在更大数据集上训练且优先使用 GPU。示例训练日志显示经过约 10 万个样本后准确率逐步爬升到 0.8 左右。实战二用 TensorFlow / Keras 构建 SimpleRNN 分类器TensorFlow Notebook 同样以 AG_NEWSag_news_subset通过tfds.load获取为数据集其模型结构如下RNNTF.ipynbvocab_size 20000 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) ])要点解读Keras 中用SimpleRNN层表示简单 RNNEmbedding负责降维embed_size 64Dense(4, softmax)输出四类新闻的概率model.summary()显示参数分布Embedding 层约 128 万参数20000 × 64SimpleRNN 层 1296 参数来自 W、H、bDense 层 68 参数训练时先用vectorizer.adapt(ds_train.take(2000).map(extract_title))在新闻标题上训练向量化器然后compile(losssparse_categorical_crossentropy, metrics[acc], optimizeradam)并fit。由于只用了标题未用正文准确率会偏低示例中约 0.80 的验证准确率Notebook 提示TextVectorization会自动为变长序列补齐 pad token而这些 pad token 也会参与训练、干扰收敛。这引出了下面两个关键优化手段。LSTM用门控显式管理状态经典 RNN 的核心问题是梯度消失vanishing gradientsRNN 在一次反向传播中端到端展开训练误差很难传播到网络最早的那些层导致网络无法学习相距较远的 token 之间的关系。解决办法之一是引入显式状态管理即所谓的门gates。两种最著名的门控架构是LSTMLong Short Term Memory长短期记忆与GRUGated Recurrent Unit门控循环单元本课重点讲解 LSTM。LSTM 的组织方式与 RNN 类似但层与层之间传递两个状态真正的状态 C 与隐藏向量 H。在每个单元中隐藏向量 Hᵢ 与输入 Xᵢ 拼接通过门来控制状态 C 的变化。每个门都是一个带 sigmoid 激活输出范围 [0,1]的神经网络与状态向量相乘时可视为一个位掩码bitwise mask。LSTM 共有三道门对应示意图从左到右遗忘门forget gate取隐藏向量决定状态 C 中哪些分量需要遗忘、哪些需要原样通过输入门input gate从输入向量与隐藏向量中取信息写入状态输出门output gate先用带tanh激活的线性层变换状态再用隐藏向量 Hᵢ 挑选其中部分分量产生新状态 Cᵢ₊₁。状态 C 的分量可以理解为一系列可以开/关的旗标flags。例如当序列中出现名字Alice时我们可以假设它指代女性角色于是在状态中把句子中有阴性名词的旗标打开稍后读到and Tom时再打开名词为复数的旗标。通过这样操作状态网络得以跟踪句子各部分的语法属性。✅ 深入学习 LSTM 内部细节的经典资料是 Christopher Olah 的文章《Understanding LSTM Networks》本课在复习与自学一节中也推荐了它。PyTorch 中的 LSTM 分类器PyTorch 把 LSTM 的内部实现隐藏在LSTMCell/LSTM类中因此代码与简单 RNN 几乎一样只需替换循环层RNNPyTorch.ipynbclass 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])这里self.rnn(x)返回(x, (h, c))——h 是最终隐藏向量c 是最终单元状态。分类时取h[-1]。训练同样使用lr0.001Notebook 提醒LSTM 训练也很慢训练初期准确率提升不明显需要耐心调学习率既能保证合理收敛速度又不至于浪费内存。TensorFlow / Keras 中的 LSTM 层Keras 同样把 LSTM 封装为现成层只需替换循环层RNNTF.ipynbmodel keras.models.Sequential([ vectorizer, keras.layers.Embedding(vocab_size, embed_size), keras.layers.LSTM(8), keras.layers.Dense(4, activationsoftmax) ])处理变长序列PyTorch packed sequence 与 Keras masking真实文本的句子长度各不相同而神经网络要求同一 batch 内的序列等长。之前我们用零向量pad token补齐但这会带来两个问题一是内存浪费二是为填充项额外创建的 RNN 细胞也参与训练却不携带任何有效输入信息。理想做法是让 RNN 只训练到真实序列长度。PyTorch 的 packed sequencePyTorch 为此引入了一种特殊的打包存储格式。假设输入的一个 mini-batch 是[[1,2,3,4,5], [6,7,8,0,0], [9,0,0,0,0]]其中 0 是填充值各序列真实长度为[5,3,1]。为了高效训练我们希望先用大 mini-batch[1,6,9]启动第一批 RNN 细胞然后结束第三条序列的处理继续用缩短的 mini-batch[2,7]、[3,8]训练依此类推。因此 packed sequence 被表示为一个向量[1,6,9,2,7,3,8,4,5]加上长度向量[5,3,1]据此即可重构出原 padded mini-batch。具体实现RNNPyTorch.ipynbdef 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在forward中同时接收补齐后的 mini-batch 与长度向量先算嵌入再打包、过 LSTM、解包class LSTMPackClassifier(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, lengths): batch_size x.size(0) 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])关键 API 与注意事项打包用torch.nn.utils.rnn.pack_padded_sequence解包用torch.nn.utils.rnn.pad_packed_sequenceRNN、LSTM、GRU 等循环层都支持 packed 输入并产出 packed 输出示例中其实并未用到解包结果x后续计算只用隐藏层输出保留解包是为了方便你改成需要网络输出的场景训练时必须传use_pack_sequenceTrue因为pack_padded_sequence要求长度张量位于 CPU训练函数需要避免把长度数据搬到 GPU详见 torchnlp.py 中train_epoch_emb的实现。示例日志显示packed LSTM 最终把验证准确率提升到约 0.81优于简单 RNN。Keras 的两种 masking 方案TensorFlow 侧提供了两种减少 padding 干扰的手段RNNTF.ipynb按长度分桶用tf.data.experimental.bucket_by_sequence_length按序列长度重排数据集、把相近长度的序列分到一组masking掩码Keras 部分层支持额外输入来指明哪些 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) ])开启 masking 后模型就可以放心地使用标题 正文的全量数据训练示例中验证准确率提升到约 0.88。Notebook 还提示此前向量化器只在标题上训练可能忽略正文中的部分 token严格来说应重新训练向量化器但为简单起见保留旧向量化器影响也不大。双向与多层 RNN此前讨论的循环网络都只沿一个方向运行——从序列开头到结尾。这符合我们阅读和听说的自然方式。但在很多实际场景中我们对输入序列是随机访问的因此让循环计算在两个方向上都运行是合理的这类网络称为双向 RNNbidirectional RNN它需要两个隐藏状态向量每个方向各一个。与卷积网络类似我们也可以在第一个循环层之上再堆叠一层循环网络以捕捉更高层次的模式——这就是多层 RNNmulti-layer RNN由两个或更多循环网络组成前一层输出作为后一层输入。PyTorch 实现两个构造参数PyTorch 把双向和多层都抽象成了构造参数RNNPyTorch.ipynb传bidirectionalTrue给 RNN/LSTM/GRU 构造函数即可创建双向网络。此时 PyTorch 把两个方向的隐藏状态编码为一个尺寸翻倍的向量方便直接送入全连接层——只需在创建线性层时把输入维度按翻倍后的尺寸计算传num_layers参数即可自动构建多层循环。注意隐藏/状态向量尺寸会按层数成比例增大处理循环层输出时要相应调整下游层的维度。TensorFlow 实现Bidirectional 包装层与 return_sequencesKeras 用Bidirectional层包装循环层其内部会复制两份循环层并把其中一份的go_backwards属性设为True使其沿序列反向运行RNNTF.ipynb。构造多层网络时除最后一层外所有循环层都必须设置return_sequencesTrue因为我们需要中间各层的完整输出序列而不仅仅是最后一步的状态。一个两层双向 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) ])Notebook 指出这个模型训练耗时很长但能给出到目前为止最高的准确率训练日志中 acc 已超过 0.87值得等待。动手练习与课后作业本课在 README.md 中提供了两个配套 Notebook用于随堂练习标题为Exercises: EmbeddingsRNNs with PyTorchRNNs with TensorFlow作业说明 assignment.md 要求使用本课配套 NotebookPyTorch 或 TensorFlow 任一版本用你自己的数据集重新运行一遍——例如带来源标注的 Kaggle 数据集文中示例是天气推文数据集改写 Notebook 以突出你的发现尝试不同类型的任务并记录结论。此外文档还布置了 挑战阅读关于 LSTM 的文献并思考其应用包括《Grid Long Short-Term Memory》以及《Show, Attend and Tell: Neural Image Caption Generation with Visual Attention》后者展示了 LSTM 在图像描述生成中的应用复习与自学一节则再次推荐 Christopher Olah 的《Understanding LSTM Networks》。结论在本单元中我们看到RNN 不仅可用于序列分类还能处理更多任务——文本生成、机器翻译等这些内容将在下一单元继续展开。核心要点回顾词嵌入的聚合表示丢失词序信息而 RNN 通过逐个符号传递状态向量天然建模顺序依赖简单 RNN 细胞由输入矩阵 W 与状态矩阵 H 组成输出为 σ(W×Xᵢ H×Sᵢ₋₁ b)嵌入层将 token 降维后W 尺寸为 emb_size×hid_size、H 为 hid_size×hid_size经典 RNN 存在梯度消失问题LSTM/GRU 通过遗忘门、输入门、输出门显式管理状态 C把状态分量当作可开关的旗标以跟踪句子语法属性变长序列处理PyTorch 用pack_padded_sequence/pad_packed_sequenceKeras 用mask_zeroTrue或Masking层避免填充项干扰训练双向与多层 RNN 分别通过bidirectionalTrue、num_layersPyTorch与Bidirectional包装层、return_sequencesTrueKeras构建多层双向 LSTM 在本课分类任务上取得了最佳效果。全部代码与数据均可直接在本仓库 lessons/5-NLP/16-RNN 目录下查看与运行配套的通用训练工具函数位于 torchnlp.py。赞分享教程人工智能机器学习深度学习【免费下载链接】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 分类器 本指南以 AI For Beginners 课程第 16 课《教程人工智能机器学习深度学习创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考