ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

理解RNN:序列模型的核心架构与梯度消失难题

理解RNN:序列模型的核心架构与梯度消失难题 1. 引入序列模型为什么我们绕不开RNN我第一次在Coursera上刷吴恩达老师的《序列模型》第一周时最大的感受不是“RNN好难”而是“原来之前学的神经网络根本不适合处理这类数据”。这一周的核心内容就是循环序列网络Recurrent Neural Network简称RNN课程代码编号5-1属于深度学习专项课程的第五门课。先说清楚一个基本问题我们在图像识别里常用的全连接网络、卷积神经网络CNN处理的是“定长输入、定长输出”的问题。一张图片不管多大最终都会被缩放成固定尺寸喂给网络输出固定的类别概率。但现实世界里有一大类数据天生是变长的、有先后顺序的比如一句话有5个词也有50个词一段语音有1秒也有10秒一段股票收盘价有30天也有300天。这类数据就是序列数据而RNN就是专门为它设计的。吴恩达在第一周花了大量篇幅反复强调一个观点标准神经网络架构无法很好地处理序列数据。他举了三个典型场景语音识别输入音频帧输出文本、情感分类输入一段评论输出正面/负面标签、机器翻译输入英文输出中文。这些场景的共同点是输入和输出的长度都不固定而且元素之间的顺序关系本身就是信息的一部分。在“我喜欢这家餐厅”这句话里“喜欢”这个词的情感权重明显高于“这家”这种位置相关性是CNN这类模型很难编码的。我当年学到这里的时候正好在做一个商品评论情感分析的小项目数据集里全是长短不一的用户评价用TF-IDF加逻辑回归做到了0.78的F1分数再往上提升就非常吃力。看完这一周课程我一下子明白了症结所在我把文本当成了词袋完全丢掉了语序信息。“很不好吃”和“不好吃很”在词袋模型里几乎等价但在RNN里信息的传递是逐字推进的顺序天然被建模进了每个时间步的隐藏状态里。所以这一周课程的定位非常清晰它承接了前面四门课里讲到的神经网络基础知识同时为第二周要讲的LSTM和GRU打下概念基础。不论你是准备入门自然语言处理还是做时间序列预测或者只是想搞清楚生成式AI背后的基本单元第一周的内容都是绕不开的基石。2. RNN要解决的核心问题与整体设计思路2.1 三类序列问题从模型结构看设计动机在动手看RNN的数学公式之前先理解它要解决问题的大类这决定了我们怎么选模型结构。吴恩达把序列问题分为三类第一类是“定长输入、变长输出”比如图像描述Image Captioning输入一张图片输出一句描述文字。第二类是“变长输入、定长输出”比如情感分类输入一段话输出一个标签。第三类是“变长输入、变长输出”比如机器翻译、语音识别输入和输出都是序列而且长度可能不一致。如果只用传统神经网络这三类问题会非常棘手。假设输入是10个词输出是1个标签你当然可以把10个词拼接成一个长向量喂给全连接网络但问题是词的顺序一旦打乱向量就彻底变了模型会认为“我讨厌猫”和“猫讨厌我”是两种完全不同的输入尽管情感极性截然不同但你无法通过共享参数来泛化这种变化。这就是传统网络处理序列数据的第一个痛点缺乏对顺序的建模能力。第二个痛点是参数爆炸。假设词典大小是10000输入一句话平均20个词你用一个巨大的全连接层去编码光输入层就要20乘以10000乘以隐藏层维度这个参数量在当年GPU还很弱的时代根本不敢想。RNN的设计思路就是用“参数共享”来同时解决这两个痛点。它的核心思想是在处理序列的每一个位置时使用完全相同的权重。这个思想在CNN里也有体现——卷积核在图像的各个位置滑动时是共享权重的而RNN则是把一个“循环单元”在时间维度上展开每个时间步都应用同一组参数。这样不管输入序列有多长模型的参数量都是固定的而且天然编码了顺序信息。2.2 RNN的时间步展开从循环到链式结构吴恩达课程里最经典的一张图就是RNN在时间维上展开后的链式结构。我把这张图的核心逻辑拆成人话版本尽量还原他在黑板上的推导过程。首先定义几个符号输入序列 (x^{\langle 1 \rangle}, x^{\langle 2 \rangle}, ..., x^{\langle T_x \rangle})其中 (T_x) 是输入长度每个 (x^{\langle t \rangle}) 是一个词向量通常用Word2Vec或GloVe预训练得到或者用随机初始化的Embedding层学习得到。隐藏状态 (a^{\langle t \rangle}) 是第t步的“记忆”它不但接收当前输入 (x^{\langle t \rangle})还接收上一步的隐藏状态 (a^{\langle t-1 \rangle})。初始隐藏状态 (a^{\langle 0 \rangle}) 通常设为全零向量。输出 (\hat{y}^{\langle t \rangle}) 是第t步的预测有的是每个时间步都输出比如词性标注有的是只在最后一步输出比如情感分类。每个时间步的更新公式是[ a^{\langle t \rangle} g_1(W_{aa} a^{\langle t-1 \rangle} W_{ax} x^{\langle t \rangle} b_a) ][ \hat{y}^{\langle t \rangle} g_2(W_{ya} a^{\langle t \rangle} b_y) ]这里的 (g_1) 通常是tanh双曲正切激活函数取值在-1到1之间(g_2) 根据任务不同而不同——二分类用sigmoid多分类用softmax。这张展开图极度重要因为整个RNN的前向传播、反向传播、梯度消失、LSTM改进全都是围绕这张图展开的。我个人的学习建议是不要背公式而是把这张图亲手画三遍。第一遍照着课程画第二遍自己凭记忆画同时标注每个时间步的数据形状维度第三遍用一个小例子手动跑一遍数值计算比如输入两个词隐藏层维度设为3手动算出每一步的中间结果。做完这三遍RNN的骨架就长在你脑子里了。2.3 为什么用tanh而不是ReLU关于激活函数选择的思考吴恩达在课上其实没有花太多篇幅解释“为什么RNN用tanh”但在实际操作中这个问题特别值得展开。我最早自己写RNN代码时为了“紧跟潮流”把隐藏层激活函数换成了ReLU结果训练到一半loss直接变成了NaN查了很久才意识到问题出在激活函数上。核心原因有两个。第一tanh的输出范围是-1到1是零中心的。在循环结构中上一时刻的隐藏状态会不断和当前输入加权求和传给下一时刻如果激活函数输出没有上下界若干步之后隐藏状态的数值很容易爆炸。ReLU的输出是0到正无穷虽然解决了梯度消失的负半轴问题但正值不断累乘在深层时间展开中特别容易造成数值溢出。第二tanh关于原点对称导数在0附近最大这意味着在训练初期、隐藏状态接近零时梯度能够有效传播。ReLU在正区间导数恒为1看起来很美但一旦某个时间步的加权值落入负区间对应神经元的梯度就是0这个单元就“死”了而且在循环结构里这种死亡是随时间传递的。吴恩达在练习作业里也沿用了tanhNotation符号约定部分明明白白写着activation是tanh。所以我的建议是基础阶段完全照搬课程设计不要自创。等你熟练以后再尝试替换成ReLU或者Leaky ReLU同时配合梯度裁剪gradient clipping那时候你会对“为什么这么选”有更深的体感。3. RNN前向传播与常见的架构变体3.1 从单个时间步到完整前向传播的矩阵写法在深度学习框架里我们很少写for循环逐个时间步去跑而是用矩阵运算一次性处理整个序列。但对于理解而言从循环逐步到矩阵写法这个过程不能跳。假设我们有一个输入序列长度是 (T_x 10)每个时间步的特征维度是 (n_x 100)比如词向量维度100隐藏层神经元个数是 (n_a 64)。那么(W_{ax}) 的形状是 ((n_a, n_x) (64, 100))它负责将输入向量映射到隐藏空间。(W_{aa}) 的形状是 ((n_a, n_a) (64, 64))它负责将上一时刻的隐藏状态映射到当前时刻。(b_a) 的形状是 ((n_a, 1) (64, 1))。前向传播的计算顺序是初始化 (a^{\langle 0 \rangle}) 为全零向量形状 ((64, 1))。对每个时间步 (t 1, 2, ..., 10)计算线性组合 (W_{aa} a^{\langle t-1 \rangle} W_{ax} x^{\langle t \rangle} b_a)再通过tanh激活得到 (a^{\langle t \rangle})。如果需要每个时间步的输出再计算 (\hat{y}^{\langle t \rangle} softmax(W_{ya} a^{\langle t \rangle} b_y))。课程作业里有一个编程练习要求只用numpy实现这个前向传播过程不借助深度学习框架。如果你打算跟着做有一点务必注意在numpy实现里通常会把多个时间步的输入堆叠成一个矩阵形状是 ((T_x, n_x))然后通过np.dot一次性算出所有步的线性组合再逐元素做tanh。这样做既快又简洁但前提是你已经充分理解了逐步计算的原理不然出bug了都不知道在哪。3.2 不同的序列架构一图厘清四种模式吴恩达把常见的RNN按输入输出结构分成几种固定模式每一种对应一类任务。第一种是many-to-many且输入输出等长也就是每个时间步都有输入也有输出。典型任务是视频帧的动作识别、逐词词性标注。对于一个10帧的视频输入 (x^{\langle 1 \rangle}) 到 (x^{\langle 10 \rangle})输出 (\hat{y}^{\langle 1 \rangle}) 到 (\hat{y}^{\langle 10 \rangle})每个y是这帧属于哪个动作类别的概率分布。第二种是many-to-one输入是一整个序列只在最后一步输出。典型任务是情感分类、垃圾邮件检测。比如输入“这部电影太棒了”经过20个时间步后只在最后的 (\hat{y}^{\langle 20 \rangle}) 输出一个二分类概率。第三种是one-to-many输入只有开头一个信号后续每个时间步都产生输出。典型任务是图像描述以图像特征向量作为 (x^{\langle 1 \rangle})之后每个时间步生成一个词或者音乐生成输入一段种子旋律的开头后续逐音生成。第四种是many-to-many且输入输出不等长这是最复杂的架构典型任务是机器翻译。它的核心特点是存在一个“编码器-解码器”结构编码器先读取完整个输入序列把语义压缩到最后一个时间步的隐藏状态里然后解码器从这个状态开始逐字生成目标语言的词。我强烈建议把这四种模式的图亲手画一遍标注清楚每个节点的输入来源是哪里输出去往哪里。你会发现它们之间的差别其实只是“输出连接在哪里”“输入喂给谁”的差别代码层面的改动也不大很多框架里的RNN层只需要调整return_sequences参数就能实现。3.3 语言模型与序列生成RNN最经典的落地场景第一周课程的一大亮点是语言模型Language Model的教学。所谓语言模型就是在给定前面若干个词的情况下预测下一个词是什么。比如你看了“我今天中午吃了”这句话模型应该给出“饭”的概率高于“飞机”。RNN做语言模型的过程是逐词递进的。给定一个语料库先把每个词用one-hot向量或者Embedding向量表示然后逐词输入RNN每个时间步预测下一个词的概率分布用softmax输出。训练时用的损失函数是交叉熵损失跟普通分类任务一致但特殊之处在于每个时间步都有损失总损失是所有时间步损失的平均。这个过程在推理生成阶段特别有意思模型根据当前输入词计算下一个词的概率分布然后从中采样一个词或者取概率最大的词这取决于你想让输出更确定还是更多样把这个词作为下一时间步的输入循环往复。这就是所有“AI续写”工具最底层的原理也是我特别喜欢在课程笔记里强调的部分——看似天花板很高的生成式AI基座思想其实在第一周就已经暴露了。我当年用课程里教的方法写了一个基于RNN的歌词生成器。训练数据是周杰伦的几十首歌词字符级别的模型每个时间步输入一个字符而不是一个词隐藏层维度128训练了不到50轮模型就能生成“怎么让你的微笑在我的心里”这种语法勉强通顺但逻辑还是有点奇怪的句子。虽然效果远不如现在的Transformer但你能够亲眼看着模型从输出完全随机的符号一步步变成能生成有意义词语的“半文明产物”这种成就感是直接调用大模型API完全体验不到的。4. 反向传播与梯度消失RNN训练中绕不开的坎4.1 BPTT前向与反向的时间维度解构训练RNN最常用的算法叫BPTTBackpropagation Through Time随时间反向传播。理解BPTT的关键是把RNN在时间维度上展开后的网络看成一个大前馈网络。展开后的网络有 (T_x) 层对应 (T_x) 个时间步每一层的参数都是共享的都是同一组 (W_{aa}, W_{ax}, W_{ya})。在计算梯度时损失函数对某一时间步的隐藏状态 (a^{\langle t \rangle}) 的误差信号会同时传播到所有比它更早的时间步。举个例子第10步的损失会通过 (a^{\langle 10 \rangle}) 传到 (a^{\langle 9 \rangle})再传到 (a^{\langle 8 \rangle})一路传到 (a^{\langle 1 \rangle})。这就像剥洋葱每一层都要乘一次关于 (W_{aa}) 的导数tanh的导数和权重矩阵的转置。在数学上梯度从后往前传递的过程会不断乘以 (W_{aa}^T) 和 (\tanh(a)) 这两项。tanh导数的最大值是1当输入为0时实际上绝大多数时候都远小于1而权重矩阵的谱半径最大奇异值如果小于1连续相乘几十步之后梯度会指数级衰减到几乎为零。这就是梯度消失。相反如果权重矩阵的谱半径大于1梯度则会指数级膨胀最后变成NaN这就是梯度爆炸。吴恩达在课上用一张图生动展示了这个现象蓝色线表示其他神经网络的梯度从后向前比较平缓红色线表示RNN的梯度最终会剧烈地冲上云霄再掉到谷底。这个视觉冲击力极强的图比你背一百遍公式都管用。4.2 梯度裁剪应对梯度爆炸的实用技巧好消息是梯度爆炸虽然听着吓人但处理起来反而简单梯度裁剪gradient clipping。具体做法是在反向传播得到所有参数梯度之后如果梯度的L2范数超过了一个预设阈值比如5.0就等比例缩放所有梯度使它们的范数等于这个阈值。这样既保证了更新方向不变又防止了一次更新步长过大导致的参数崩溃。深度学习框架一般都内置了这个功能。以TensorFlow/Keras为例optimizer tf.keras.optimizers.Adam(learning_rate0.001, clipnorm5.0)一行代码就解决了梯度爆炸的大部分问题。以PyTorch为例torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0)这个max_norm怎么选我自己的经验是先从5.0起步如果训练过程中loss出现NaN就降到1.0或0.5如果训练正常可以尝试增大学习率来加速收敛。阈值过长惩罚不明显过短会让训练变慢需要按实际loss曲线调。4.3 梯度消失为什么更致命梯度爆炸是“痛苦但好解决”梯度消失则是“致命且难以根治”。当梯度消失发生时靠近序列开始位置的参数几乎收不到任何更新信号。这意味着模型无法学到“长距离依赖”。怎么理解长距离依赖举个语言例子“我出生在法国……我能流利地说____。”这个空应该填“法语”但“法国”这个决定性线索出现在句子开头和空格隔了很长的距离。如果RNN的梯度只能有效传播三五个时间步它就完全无法建立这种跨长距离的关联。课堂上吴恩达反复强调梯度消失是RNN的根本困境也是LSTM和GRU被提出的直接动机。LSTM长短期记忆网络通过引入“门控机制”输入门、遗忘门、输出门和“记忆细胞”来解决这个问题。记忆细胞像一个独立的信息高速公路允许信息在时间维度上畅通无阻地流动而门控结构则决定什么信息该写入、什么该保留、什么该输出。GRU则是LSTM的简化版把三个门精简成两个参数更少、训练更快。第二周课程会深入讲这两种架构但第一周有没有必要提前了解我的建议是理解到“梯度消失导致RNN忘性大LSTM通过门控机制记住长期信息”这个层面就够了。把第一周的基础打牢比囫囵吞枣地往前赶要重要得多。5. 从零实现一个简易RNN框架与代码对照5.1 用numpy实现单步RNN前向传播吴恩达的课后编程作业第一部分要求用numpy手写RNN单元的单步前向传播。这个练习看起来简单但对理解起到了四两拨千斤的作用。课程作业的代码框架大致是这样的我用自己的语言重新组织过import numpy as np def rnn_cell_forward(xt, a_prev, parameters): 单步前向传播 xt: 当前时间步输入, shape (n_x, m) a_prev: 上一时间步隐藏状态, shape (n_a, m) parameters: 包含Wax, Waa, ba, Wya, by Wax parameters[Wax] Waa parameters[Waa] ba parameters[ba] Wya parameters[Wya] by parameters[by] # 计算当前隐藏状态 a_next np.tanh(np.dot(Waa, a_prev) np.dot(Wax, xt) ba) # 计算当前输出 yt_pred softmax(np.dot(Wya, a_next) by) return a_next, yt_pred注意这里吴恩达约定的维度通常是把时间步放在最后即每个输入列向量是 (x^{\langle t \rangle})和PyTorch用户习惯的batch_firstTrue在维度排列上不同。这个细节虽然不影响学习原理但如果后面要用框架实现需要特别留意维度的转换。我建议你拿到这份作业之后不要急着抄答案而是先干一件事把每个矩阵的维度写下来在纸上标清楚哪两个矩阵相乘、结果形状多少、加在哪。只要这一步做通了后面LSTM、GRU的代码理解起来会顺畅很多因为它们的结构复杂度更高如果连基础矩阵运算都没形成本能反应很容易被绕晕。5.2 用Keras搭一个RNN做文本生成如果你只是想快速看到效果不想纠结numpy实现用Keras搭一个字符级RNN是最快的方式。我用电影评论数据集做了个小实验预测下一个字符代码精简后大约30行from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, SimpleRNN, Dense model Sequential([ Embedding(input_dimvocab_size, output_dim256, input_lengthseq_length), SimpleRNN(units128, return_sequencesTrue), SimpleRNN(units128), Dense(vocab_size, activationsoftmax) ]) model.compile(losscategorical_crossentropy, optimizeradam, metrics[accuracy]) model.fit(X, Y, batch_size64, epochs20, validation_split0.1)几个关键参数说明Embedding层的input_dim是词典大小output_dim是词向量维度。正常情况下output_dim取词典维度的平方根附近比较合理比如词典10000取100左右词典100取16左右。太大容易过拟合太小会损失语义表达能力。SimpleRNN层的units是隐藏神经元个数。这个数值决定了模型的记忆容量但不是越大越好128对字符级文本生成足够了增到512可能反而过拟合。return_sequencesTrue表示每个时间步都输出隐藏状态堆叠第二层RNN时必须这样设置最后一个RNN层设为False只输出最后一步然后接Dense层分类。训练结束后生成文本时有一种常见的错误做法每次都取softmax概率最大的那个字符作为下一个输入。这样做出来的文本会陷入重复循环因为概率最大的路径往往是模型最“确定”的路径缺乏多样性。正确做法是引入temperature参数来调节采样分布的“锐度”def sample_from_probs(probs, temperature1.0): probs np.asarray(probs).astype(float64) probs np.log(probs 1e-8) / temperature exp_probs np.exp(probs) probs exp_probs / np.sum(exp_probs) return np.random.choice(len(probs), pprobs)temperature大于1会让分布更平滑生成结果更多样但更混乱小于1会让分布更尖锐生成结果更保守但更连贯。我实测下来字符级模型的temperature在0.5到0.8之间效果比较理想词级模型可以稍微高一点到0.8到1.0之间。5.3 第一个作业的踩坑指南向量化与shape不匹配课后作业里最容易出bug的地方不是前向传播公式写错而是维度匹配问题。我当年做这个作业时踩过一个特别典型的坑在h5py准备数据集时把一个样本的序列长度设得和另一个不一致结果numpy广播自动把长度补齐训练集里混进了大量无意义的填充符。这个问题在单词级语言模型作业里会以“都是符号长度不齐”的方式出现。另外一个常见问题是初始化权重的方式。吴恩达在作业里明确要求初始化使用np.random.randn乘以0.01这种小随机数。这里的逻辑很直接RNN的隐藏状态要通过tanh激活如果初始权重太大输入加权和落在tanh饱和区梯度直接从源头就消失了。0.01这个尺度虽然慢一些但能保证前向传播数值稳定。还有一个容易被忽略的点是损失函数的实现。语言模型的输出是一个词表大小的概率分布用交叉熵损失。在numpy实现里交叉熵的计算涉及对softmax输出取log如果softmax输出某个概率恰好是0取log会得到负无穷导致loss变成NaN。所以在实现时通常会在log里加一个极小值比如1e-8或者直接用框架里封装好的、数值上更稳定的损失函数。6. 常见问题与排查技巧实录6.1 典型问题速查表根据课程讨论区的置顶帖和我自己的踩坑经历我把RNN入门阶段最常见的报错和现象整理成一张速查表。现象可能原因排查步骤loss不下降学习率太大/太小打印梯度范数观察是否过小或过大尝试对数网格调整学习率loss出现NaN梯度爆炸加梯度裁剪检查输入是否含有NaN或未归一化的大数值输出全是重复词采样时取了argmax改用temperature采样或降低temperature到0.6以下长文本后段质量差梯度消失、长期依赖无法建模换LSTM/GRU增加隐藏层维度压缩序列长度训练很久但是准确率低输入序列未做向量化检查token到id的映射是否有bug特殊字符是否被过滤掉生成的句子语法正确但无逻辑字符级模型的天花板升级到词级模型或使用预训练词向量这里面我最想展开的是“输出全是重复词”这个问题。它不是模型bug而是典型的“贪心解码”缺陷。模型在每个时间步都选择概率最高的词但高概率并不等于文本整体连贯。人类在说话的时候也不是每句话都选最“安全”的词而是会有一点随机性。temperature采样的本质就是模拟这种随机性把概率分布“熨平”让低概率的词也有一定机会被选中。6.2 课程作业中我觉得值得反复刷的练习题相比实现代码吴恩达课程里的选择题往往被初学者忽略但它们其实才是理解最深的试金石。第一周有几道题特别值得反复做我强烈建议不要只看答案而是把每个选项为什么对、为什么错都弄清楚。例如有一道题问“为什么RNN不能很好地捕捉长距离依赖”选项里混着“梯度消失”、“梯度爆炸”、“激活函数饱和”和“参数过多”。正确答案是梯度消失但如果你选的是梯度爆炸说明你没有分清这两种问题的本质区别需要回去重新看BPTT的推导。还有一道题关于“语言模型每个时间步的输入”其实是把上一时间步的真实词作为输入与推理时把上一时间步生成的词作为输入不同。这个区别在训练teacher forcing和推理free running之间至关重要。课堂上讲得很快但如果日后做序列生成你会发现这两个模式的效果差别巨大过度使用teacher forcing会导致推理时误差累积这是一个非常重要的问题。6.3 关于“输入是one-hot还是Embedding”的选择问题初学RNN时很多人会纠结输入表示。吴恩达在课里其实两种都提到了对于小词表的作业用one-hot足够对于真实项目用Embedding则是更高效的选择。one-hot的问题在于维度爆炸。假设词表是20000一个batch里64个样本、50个时间步输入矩阵就是 (50 \times 64 \times 20000)其中绝大多数元素是0内存占用巨大。Embedding层则把每个词映射成一个稠密向量比如128维参数量也从 (20000 \times 20000) 变成了 (20000 \times 128)。此外Embedding层还可以加载预训练词向量比如GloVe、Word2Vec这在数据量不大的时候特别有用。吴恩达在课程后半段详细讲了用预训练词向量来处理命名实体识别问题的案例其中有一个非常有意思的细节对于未登录词词典里没有的词他建议直接标记为“UNK”unknown token而不是强行把它映射到一个随机向量。我的经验是如果你处理的语料比较垂直比如全是编程问答预训练词向量不一定比从头训练的Embedding好因为通用词向量里可能根本没有“TensorFlow”“Transformer”这类词的合适语义表示。遇到这种情况干脆用随机初始化Embedding跟着任务数据一起训练效果反而更好。7. 写在最后这一周学完你应该带走什么第一周课程的内容密度很高但核心线索非常清晰从序列数据的特殊性出发引出RNN的设计思想基于RNN的循环结构推演出多种架构变体然后深入到训练算法和梯度问题最后以语言模型为落点展示实际效果。我在实际学习中有两个体会。第一个体会是亲手推导一遍公式比看十遍视频都重要。在纸上用一个小维度的例子手动计算RNN前向传播再手动反推梯度你对参数共享的意义、梯度消失的成因、为什么要用tanh这些问题的理解会彻底扎根。第二个体会是尽早开始动手写代码。看完视频之后哪怕只搭一个最简陋的字符级RNN亲眼看到loss从2.5慢慢降到1.2看到模型输出从乱码变成能读的句子那种“原来原理真的work”的感觉会让你对接下来的LSTM、GRU、Attention充满信心。如果你正在跟着Coursera刷《序列模型》我的建议是第一周笔记不要追求一次看懂而是在你完成课后作业、跑通了最简单的文本生成模型之后再回来看一遍。很多当时觉得抽象的概念比如隐藏状态、门控、时间步展开在真正写过代码后都会变成非常直观的东西。最后再分享一个我用过的方法学完一周后把这三段内容写在一张便利贴上贴在你电脑边框上——“输入输出定长吗”“需要长距离记忆吗”“梯度还能传多远”。等到做任何序列任务时先对这三个问题作答再选模型。这套方法论本身就是吴恩达这门课比RNN公式更值钱的地方。
返回列表