ARTICLE DETAIL

资讯详情

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

深度学习原理,第6章:循环神经网络 (RNN):序列建模的早期探索

深度学习原理,第6章:循环神经网络 (RNN):序列建模的早期探索 我们已经告别了那个很擅长去盯着空间里的某一块儿看的卷积神经网络, 现在, 我们来到了通往这条路上面的第二站这个地方, 在这儿我们把眼睛放到了另外一种特别重要的一种数据形式上边, 这个数据形式就叫序列, 就像说文字, 声音, 还有那些按时间出来的数据之类的都是这种序列数据, 那么处理这种序列数据的关键点到底是什么, 在于要弄明白它在里面藏着的跟时间有关系的那个依赖关系, 这一章里面的大主角, 就是那些家伙为了搞懂这件事儿, 早早就出发去探索的先行者, 这个名字叫作循环神经网络。就像前面的那个章节一样, 这个章节将会把重点放在递归神经网络最核心的设计思想上, 也就是说我们得去弄明白该怎么利用“隐状态”这个玩意儿来处理和传递序列信息。我们将首先要通过展示一段代码的方式, 然后一步一步地从头构建一个最为简单的递归神经元单元, 这样就可以非常直观地去观察信息是在哪些时间节点之间进行循环式传递的。后来, 我们会使用代码实验还有可视化呈现的方式, 把那个简单循环神经网络本身所具有的一个所谓原罪给揭示出来, 这个原罪指的就是梯度消失以及梯度爆炸这两个问题, 并且还会去理解它为什么在捕捉那些很长的距离依赖关系的时候比较困难, 在此之后, 我们会引出对RNN进行重大提升的长短期记忆网络, 也就是英文所说的Long Short-Term , 接下来会借助图示说明加上概念讲解这些方法, 让大家能够弄明白它是如何运用一种设计得非常巧妙的门控机制来帮助缓解前面所说的那些问题的。最后, 这也是最为重要的一个环节, 我们要明确指出所有循环神经网络类模型所共同具备的那个无法从根本上消除的瓶颈问题, 也就是那个由于存在串行依赖从而导致无法进行并行计算的特性, 这将会使我们从内心深处去认同序列处理这一领域确实非常迫切地需要迎来一场在结构架构层面上的革命性的变化。6.面对序列数据时所遭遇的那些令人头疼的挑战, 以及循环神经网络里面所体现出来的那种核心观念与基本思路。在处理像“the cat sat on the mat”这样的一整句序列数据时, 模型不光要弄明白每一个单独的词到底是什么意思, 更重要的是得搞清楚这些词前后出现的顺序, 还有它们彼此之间那种相互依赖的关系。比如, “cat”这个词排在“sat”这个单词的前面, 这两个字连在一起, 就形成了一种主谓之间的对应关系。要是咱们还是用之前学过的全连接神经网络或者是卷积神经网络去处理这种材料, 然后把每一个词都当成独立存在的事物单看的话, 那种非常重要的、跟时间流程有关的信息就会全都丢失掉。RNN的核心思想, 就是引入一个所谓“记忆”的功能单元。当它去处理序列里每一个元素的时候, 不仅仅是考虑当前这个输入是什么样子的。另外一点很关键的是, 它还会去参考一下自己从前一个时间步那儿, “记忆”下来的历史信息。那么这个被叫做“记忆”的东西, 在RNN这个结构里面, 有一个专门的术语叫作隐状态 State。一个RNN单元在时间步t的计算过程, 可以被简化为:hₜ f(Wₓₕ xₜ Wₕₕ hₜ₋₁ b)其中这个步骤需要在全部的顺序数据上不停地进行重复操作, 于是产生了一种被称为“循环圈”的状态具体的样子大家可以参考图6.1里面所展示的内容。在图像的左侧部分, 我们看到的是经过折叠压缩处理后的RNN模型形式, 而在图像的右侧部分, 则是对应着按照时间推进次序展开之后的、结构非常清楚的组成部分。图6.1是RNN的循环结构, 以及它将计算过程按照时间步进行展开的样子。6.第二步, 从零开始构建循环神经网络单元, 这一步骤涉及代码实现中的循环逻辑。理论方面的内容是偏抽象的, 因此我们立刻动手编写代码来直接实现RNN单元里面单步计算部分的逻辑内容, 这样能够让人最直观地去感受到具体的实现过程。import torchimport torch.nn as nn# --- 参数设定 ---input_size 10 # 输入特征维度 (例如词嵌入维度)hidden_size 20 # 隐状态维度# --- 手动创建权重和偏置 ---# Wxh: 输入到隐状态的权重Wxh nn.Parameter(torch.randn(input_size, hidden_size))# Whh: 上一个隐状态到当前隐状态的权重Whh nn.Parameter(torch.randn(hidden_size, hidden_size))# b: 偏置b nn.Parameter(torch.randn(hidden_size))# --- 模拟输入 ---# 一个时间步的输入, 形状为 (batch_size, input_size)# 这里假设批次大小为3xt torch.randn(3, input_size)# 上一个时间步的隐状态, 形状为 (batch_size, hidden_size)ht_minus_1 torch.randn(3, hidden_size)# --- 单步前向传播计算 ---# ht tanh(xt * Wxh ht-1 * Whh b)ht torch.tanh(xt Wxh ht_minus_1 Whh b)print(输入 xt 的形状:, xt.shape)print(隐状态 ht-1 的形状:, ht_minus_1.shape)print(输出新隐状态 ht 的形状:, ht.shape)请提交需要改写的句子。输入 xt 的形状: torch.Size([3, 10])隐状态 ht-1 的形状: torch.Size([3, 20])输出新隐状态 ht 的形状: torch.Size([3, 20])这段代码非常完美地复现了RNN单元在进行计算时所涉及的核心流程。如果把它放到实际使用场景中来思考的话, 我们通常是需要把这个具体的计算过程给封装起来的, 然后还需要让它能够循环着去作用于整个的输入序列之上。而现在呢, 咱们的nn.RNN层其实就已经把这些繁琐的工作全部都给我们给做好了的了。6.长距离的依赖以及关于梯度的问题, 被很多人认为是RNN天生自带的原罪。简单RNN所具备的记忆能力其实是脆弱的。原因在于, 在每一个时间步骤里, 系统都会使用同样的权重矩阵Wₕₕ来进行迭代运算。一旦序列的长度增加, 梯度在向后的传播过程之中, 就会经历多次连乘的作用。我们做一个代码实验, 来直观感知梯度消失。具体操作是计算出最后一个时间步输出的那个值。然后求出这个值对第一个时间步输入的梯度在哪里。import matplotlib.pyplot as plt# 设定一个较长的序列seq_len 20rnn nn.RNN(input_size1, hidden_size5, num_layers1, batch_firstTrue)# 创建一个序列每个时间步都需要计算梯度inputs torch.randn(1, seq_len, 1, requires_gradTrue)outputs, hn rnn(inputs)# 我们只关心最后一个时间步的输出# 对它的第一个元素求和得到一个标量以便反向传播last_output outputs[0, -1, :].sum()# 反向传播last_output.backward()# 提取并可视化每个时间步的梯度大小grad_magnitudes inputs.grad.squeeze().abs().numpy()# --- 可视化 ---plt.figure(figsize(10, 5))plt.bar(range(seq_len), grad_magnitudes)plt.xlabel(时间步 (从远到近))plt.ylabel(梯度大小)plt.title(RNN中梯度随时间步的变化 (梯度消失), fontsize14)plt.show()如图所示, 输入的结果被展示在图6。2中:图6.2代码生成的RNN梯度消失可视化图6.2所展示出来的结果, 大家都能够非常清晰地看到。在最后一个时间步进行的输出操作里。它所使用的梯度数值。绝大部分都集中在了距离现在最近的几个时间步上面。然后对于那些序列开头的输入部分来说也就是指代左侧的那些时间步情况。此时的梯度数值。已经缩小到了非常小的程度。小到基本上可以不予以理会。这种情况看起来就像是有一个人的记忆力很差。他只具备所谓的“短期记忆”能力。这个人能够清楚地记住刚刚才发生过的事情。但是对于很久以前发生的、那些非常重要的信息内容。他却已经完全地遗忘了。正如我们在图6.3中所显示出来的那样。这种状况造成了一个严重的后果。它使得递归神经网络。在试图完成那些任务时遇到了很大的困难。特别是那些需要去深入理解长距离上下文关系的内容。图6.3对于长距离依赖这个问题, 给出了一个形象化的比喻说明。6.关于LSTM, 它进行自我拯救的方式是非常巧妙的, 这主要得力于其内部的门控机制。为了解决在简单的 RNN 里面出现的“遗忘”这个问题, 长短期记忆网络Long Short-Term , LSTM就在这个时候被提了出来。LSTM 最核心的想法, 其实就是引入一个额外的通道, 这个通道是专门用来传递那些长期记忆的, 我们把这个东西叫做细胞状态Cell State。更关键的是, LSTM设计了三个精巧的“门控机制”来控制信息的流动, 如图6.4所示。遗忘门, 也就是Gate, 它的任务是决定了从细胞状态里边, 到底该扔掉哪些老的信息。输入门, 也就是Input Gate, 负责决定说要把哪些新出来的东西, 安放到细胞状态里面去。输出门, 就是那个Gate, 它来定夺细胞状态中的部分信息是不是拿去当当前时间步骤的输出, 也就是隐状态来用。这些门在本质上都是小型神经网络, 而且这些小网络是由激活函数进行控制的, 所以门的输出会在0到1这个范围里面变化, 可以被看作是专门供信息通过的阀门, 这种设计使得信息能够在细胞状态中长时间流动而不出现衰减的情况, 只在必要的时候, 才会通过门控的方式进行修改, 从而极大地缓解了梯度消失问题, 使得模型能够学习到更长时间的序列依赖关系。图6.4是LSTM单元内部的门控机制示意图, 这一幅示意图来源于维基百科。6.我们来看看这五个RNN类模型它们有一个共同存在的瓶颈情况, 那就是没有办法进行并行处理。LSTM这个模型, 加上GRU这种变体, 尽管它们在很多方面都解决了长距离依赖的问题, 也在这个序列建模的领域里统治了很久, 很长一段时间都是这样, 但是它们都存在一个无法去掉的, 也就是根本性的瓶颈, 这个瓶颈是什么? 是计算的过程, 这个计算过程是比较高度串行的, 串行进行。当我们回头翻看那个图6.1的时候就会发现, 如果我们要算出时间步t的隐状态hₜ的话, 那就必须是先得到了上一个时间步的隐状态hₜ₋₁才行。这种对前一个时间步的依赖程度是非常严格的。这就意味着我们是没办法像处理图像的CNN那样去做事情的, 也就是不能把一个序列里的所有的元素放在一起同时进行处理。无论我们的硬件实力有多么强大, 去处理一个长度是N的序列的话, 这个计算所需要的时间复杂度最少也是O(N)。在大家都想要弄出那个更大的模型还有那个更多的数据的那个时代里面, 那种一个接一个算的串行计算的麻烦事儿就变得越来越不能让人忍受了。这个状况直接让模型的训练变得很慢, 也让它的扩展能力受到了很大的影响。正是这个看似无法逾越的障碍, 为下一场序列处理的革命拉开了序幕。这场革命的特点是完全抛弃循环结构, 并拥抱并行计算的注意力机制。6.6 本章小结在这一章之中, 大家深入了对一个东西的了解, 这个东西呢, 被称作早期序列建模的核心部分, 它的名字是循环神经网络。我们把循环神经网络的核心思想给说明白了, 它就是靠那种在循环里头一直传下去的隐藏状态, 去把序列里头的各个元素之间那些个依赖关系给建立起来并且我们是亲手用代码从最最开始的地方实现出了它那一步一步的计算过程。我们不光用代码做了实验, 还弄了可视化的手段, 结果就是把它那简单的循环神经网络的“原罪”给揭开来看了, 因为这个玩意儿存在着梯度消失或者爆炸的情况, 所以它就很难去学会那些离得老远的、长距离的依赖问题。在这一部分, 我们把LSTM这个模型是怎么通过它的那个忘记门、那个输入门、还有输出门这三个很细致的东西来操控信息是怎么流动的, 给说清楚了, 这样一来呢, 那个梯度消失的麻烦事儿就得到了挺有效的缓解。到了最后的地方, 我们把所有RNN这类模型都有的、也是最根本的一个大问题给指出来了, 就是它们那个计算的流程没法子并行地一起搞, 这个情况严重地把它们在大数据这个大时代里能够扩展的劲儿给绊住了, 也就是为了给下一章将要出现的那个算得上是革命性的新技术铺好了路, 埋下了一个引子。我们其实已经看到了那些属于循环神经网络这个大家族里的技术, 它们为了记住东西所做出的各种各样的努力, 当然也连带着暴露出了一些原本就有的问题, 比如速度确实比较慢。到了今天, 大家心里都挺着急的, 因为现在特别需要一种全新的计算方法或者说是模式, 这种新模式得有两样本事才行。第一, 它得能把那些距离很远的信息之间的关联给抓得住第二, 它还必须支持一边算好几件事儿的并行计算功能。等到下一章的时候, 我们要讲的这个东西, 就像是打开新世界大门的那把钥匙一样, 它就是注意力机制。6.关于思考这一部分, 以及从面试的角度来加以思考。问题1: RNN的核心思想是什么? 它同全连接网络来处理序列数据比较起来, 有什么不一样的地方呢?参考答案RNN的核心思想, 是通过在循环中传递的隐藏的, 那个状态的东西, 去处理那些序列的数据。这地方和那种啥都连着的全连接网络不太一样。对于问题2: 长距离依赖问题具体指的是什么, 而且为什么结构简单的RNN很难把这个难题给解决掉?参考答案长距离依赖问题这种说法, 指的是在序列里面, 某一个输出的计算过程, 需要去依靠那些距离它 very 远的输入, 这些输入发生在比较早的时候。举个例子来说, 就比如这个句子, “我在法国长大”, 后面有一些省略号, 然后是“所以我能说流利的法语”。在这个句子里面, 要想弄明白那个“法语”到底是什么意思, 就必须把它和句子开头的“法国”这个地方给联系起来, 因为它们是有关联的。简单的RNN难以解决这个问题, 主要是因为会经常出现梯度消失或者梯度爆炸的情况, 这是因为在反向传播的过程里, 计算梯度时得连着乘好几步同一个权重矩阵, 要是这个矩阵的范数不接近1的话, 梯度要么就会指数级地衰减下去出现梯度消失的问题, 要么就会指数级地增长起来产生梯度爆炸的后果。梯度消失, 这会让来自遥远过去的输入的贡献信号, 没办法有效地去传播回了模型的前端, 因此就导致了模型没办法去学习到这种长距离的关联。关于第三个问题而言, 我们需要弄明白LSTM这个算法到底是用什么办法去缓解那个梯度消失状况的。咱们接下来就简单说一说它的那些门控机制是怎么运作的。参考答案LSTM通过引入细胞状态以及门控机制, 得以缓解梯度消失的问题, 其中细胞状态被比喻为一条信息高速公路, 这条高速公路允许信息在其上进行长期流动, 并且不会发生大的改变, 与此同时门控机制则被比喻为高速公路上的收费站和匝道, 这种设计能够精确地控制信息的流入、流出以及遗忘过程。这是故事的最终结局。一心一意地专注模型与代码。如果大家对这篇文章感到喜爱, 那么非常欢迎大家进行点赞、关注和分享这连串的互动行为。 ^O^。
返回列表