sheng的学习笔记-AI-序列模型(Sequence Models),RNN,GRU,LSTM
Ai目录:sheng的学习笔记-AI目录-CSDN博客
基础知识
定义:
序列模型是输入输出均为序列数据的模型,它能够将输入序列数据转换为目标序列数据。常见的序列模型类型包括一对一、一对多、多对一、部分多对多和完全多对多。
重要的是需要有顺序,普通的模型对于采样数据不需要有顺序,可以打乱,但序列模型的输入必须是有序的
应用场景
- 在进行语音识别时,给定了一个输入音频片段X ,并要求输出对应的文字记录Y 。这个例子里输入和输出数据都是序列模型,因为X是一个按时播放的音频片段,输出Y是一系列单词
- 音乐生成问题是使用序列数据的另一个例子,在这个例子中,只有输出数据是序列,而输入数据可以是空集,也可以是个单一的整数,这个数可能指代你想要生成的音乐风格,也可能是你想要生成的那首曲子的头几个音符。输入的可以是空的,或者就是个数字,然后输出序列 。
- 在处理情感分类时,输入数据 是序列,你会得到类似这样的输入:“There is nothing to like in this movie.”,你认为这句评论对应几星?
- 在机器翻译过程中,你会得到这样的输入句:“Voulez-vou chante avecmoi?”(法语:要和我一起唱么?),然后要求你输出另一种语言的翻译结果。
- 在进行视频行为识别时,你可能会得到一系列视频帧,然后要求你识别其中的行为。
- 在进行命名实体识别时,可能会给定一个句子要你识别出句中的人名。
循环神经网络模型(Recurrent Neural Network Model)
为啥不用标准的神经网络
如何建立一个神经网络模型,学习x到y的映射
可以尝试的方法之一是使用标准神经网络,在我们之前的例子中,我们有9个输入单词。想象一下,把这9个输入单词,可能是9个one-hot向量(文字转为字典向量,详细见nlp文章),然后将它们输入到一个标准神经网络中,经过一些隐藏层,最终会输出9个值为0或1的项,它表明每个输入单词是否是人名的一部分。
主要有2个问题:
啥是循环神经网络(Recurrent Neural Network)
RNN流程图
RNN公式
RNN简化公式
RNN前向传播示意图
RNN反向传播示意图
在这个反向传播的过程中,最重要的信息传递或者说最重要的递归运算就是这个从右到左的运算,这也就是为什么这个算法有一个很别致的名字,叫做“通过(穿越)时间反向传播(backpropagation through time)”。取这个名字的原因是对于前向传播,你需要从左到右进行计算,在这个过程中,时刻不断增加。而对于反向传播,你需要从右到左进行计算,就像时间倒流。“通过时间反向传播”,就像穿越时光,这种说法听起来就像是你需要一台时光机来实现这个算法一样
RNN前向和反向传播组合
前向传播:上图蓝色箭头所指方向,在神经网络中从左到右地计算这些激活项,直到输出所有地预测结果。
反向传播:反向传播地计算方向(上图红色箭头所指方向)与前向传播基本上是相反的
循环神经网络的梯度消失
基本的RNN算法还有一个很大的问题,就是梯度消失的问题。
看到这个句子,“The cat, which already ate ……, was full.”,前后应该保持一致,因为cat是单数,所以应该用was。“The cats, which ate ……, were full.”,cats是复数,所以用were。这个例子中的句子有长期的依赖,最前面的单词对句子后面的单词有影响。但是我们目前见到的基本的RNN模型,不擅长捕获这种长期依赖效应
在一个很深的神经网络中,会出现梯度爆炸和梯度消失。在反向传播的时候,随着层数的增多,梯度不仅可能指数型的下降,也可能指数型的上升
梯度爆炸
会展示的很明显,指数级大的梯度会让你的参数变得极其大,以至于你的网络参数崩溃。所以梯度爆炸很容易发现,因为参数会大到崩溃,你会看到很多NaN,或者不是数字的情况,这意味着你的网络计算出现了数值溢出。
如果你发现了梯度爆炸的问题,一个解决方法就是用梯度修剪。梯度修剪是观察你的梯度向量,如果它大于某个阈值,缩放梯度向量,保证它不会太大,这就是通过一些最大值来修剪的方法。所以如果你遇到了梯度爆炸,如果导数值很大,或者出现了NaN,就用梯度修剪,这是相对比较鲁棒的,这是梯度爆炸的解决方法
梯度消失
前面计算的结果,随着层数的增多,很难影响后续层的计算,通常用门控循环单元(Gated Recurrent Unit)或者长短期记忆(LSTM(long short term memory)unit)解决
不同类型的循环神经网络
上述的架构,它的输入量Tx等于输出数量Ty。事实上,对于其他一些应用,Tx和Ty并不一定相等。
比如音乐生成这个例子,Tx可以是长度为1甚至为空集。再比如电影情感分类,输出可以是1到5的整数,而输入是一个序列
还有一些情况,输入长度和输出长度不同,他们都是序列但长度不同,比如机器翻译,一个法语句子和一个英语句子不同数量的单词却能表达同一个意思
“多对一”(many-to-one)
你想处理情感分类问题,这里可能是一段文本,比如一个电影的评论,“These is nothing to like in this movie.”(“这部电影没什么还看的。”),所以就是一个序列,而可能是从1到5的一个数字,或者是0或1,这代表正面评价和负面评价,而数字1到5代表电影是1星,2星,3星,4星还是5星
“一对一”(one-to-one)的结构
这就是一个小型的标准的神经网络,输入x然后得到输出y
“一对多”(one-to-many)
音乐生成,你的目标是使用一个神经网络输出一些音符。对应于一段音乐,输入可以是一个整数,表示你想要的音乐类型或者是你想要的音乐的第一个音符,得到RNN的输出,第一个值,然后就没有输入了,再得到第二个输出,接着输出第三个值等等,一直到合成这个音乐作品的最后一个音符
“多对多”
输入和输出长度不同的情况,像机器翻译这样的应用,一个法语的句子,和输出句子的单词数量,翻译成英语,这两个句子的长度可能不同
先读入这个句子,读入这个输入,比如你要将法语翻译成英语,读完之后,这个网络就会输出翻译结果。有了这种结构Tx和Ty就可以是不同的长度了。
情况1:有可能输入所有的数据,然后返回所有的输出
情况2:输入一个x1,就得到一个输出y1,在输入第二个x2,得到输出y1和y2
门控循环单元(GRU Gated Recurrent Unit)
这是普通的流程图,这一段贼复杂,多看几遍
原理
对于 “The cat, which already ate……, was full.” 这句话:
完整公式
注意,上面最后h的公式是错的,应该是 (1-门u)+c. 改成(1-门u)*c,+改成*
辅助理解
理解一下,这里的 c<t>代表一系列相关参数,比如cat,was是相关的,如果是相关的,就更新(值为1),如果是不相关的,就不更新(值为0)。在不更新的时候,会记录相关参数,避免在深层次网络中遗忘。这是从别的地方搞来的图,感受一下,里面的h就是上文提到的c
长短期记忆(LSTM(long short term memory)unit)
原理
“窥视孔连接”(peephole connection)
最常用的版本可能是门值不仅取决于和,有时候也可以偷窥一下的值(上图编号13所示),这叫做“窥视孔连接”(peephole connection)。“偷窥孔连接”其实意思就是门值不仅取决于和,也取决于上一个记忆细胞的值(),然后“偷窥孔连接”就可以结合这三个门来计算了。
如你所见LSTM主要的区别在于一个技术上的细节,比如这(上图编号13所示)有一个100维的向量,你有一个100维的隐藏的记忆细胞单元,然后比如第50个的元素只会影响第50个元素对应的那个门,所以关系是一对一的,于是并不是任意这100维的可以影响所有的门元素。相反的,第一个的元素只能影响门的第一个元素,第二个元素影响对应的第二个元素,如此类推。但如果你读过论文,见人讨论“偷窥孔连接”,那就是在说也能影响门值。
LSTM前向传播图:
LSTM反向传播计算:
门求偏导:
参数求偏导 :
别的地方弄得图,感受一下
GRU和LSTM对比
GRU的优点是这是个更加简单的模型,所以更容易创建一个更大的网络,而且它只有两个门,在计算性上也运行得更快,然后它可以扩大模型的规模。
但是LSTM更加强大和灵活,因为它有三个门而不是两个。如果你想选一个使用,我认为LSTM在历史进程上是个更优先的选择,所以如果你必须选一个,我感觉今天大部分的人还是会把LSTM作为默认的选择来尝试。虽然我认为最近几年GRU获得了很多支持,而且我感觉越来越多的团队也正在使用GRU,因为它更加简单,而且还效果还不错,它更容易适应规模更加大的问题。
双向循环神经网络(Bidirectional RNN)
当需要预测y的时候,不能只看前面的数据,还需要看后面的数据,就需要用BRNN
这就是双向循环神经网络,并且这些基本单元不仅仅是标准RNN单元,也可以是GRU单元或者LSTM单元。事实上,很多的NLP问题,对于大量有自然语言处理问题的文本,有LSTM单元的双向RNN模型是用的最多的。所以如果有NLP问题,并且文本句子都是完整的,首先需要标定这些句子,一个有LSTM单元的双向RNN模型,有前向和反向过程是一个不错的首选。
以上就是双向RNN的内容,这个改进的方法不仅能用于基本的RNN结构,也能用于GRU和LSTM。通过这些改变,你就可以用一个用RNN或GRU或LSTM构建的模型,并且能够预测任意位置,即使在句子的中间,因为模型能够考虑整个句子的信息。这个双向RNN网络模型的缺点就是你需要完整的数据的序列,你才能预测任意位置。比如说你要构建一个语音识别系统,那么双向RNN模型需要你考虑整个语音表达,但是如果直接用这个去实现的话,你需要等待这个人说完,然后获取整个语音表达才能处理这段语音,并进一步做语音识别。对于实际的语音识别的应用通常会有更加复杂的模块,而不是仅仅用我们见过的标准的双向RNN模型。但是对于很多自然语言处理的应用,如果你总是可以获取整个句子,这个标准的双向RNN算法实际上很高效。
深层循环神经网络(Deep RNNs)
seq2seq(sequence to sequence)模型
机器翻译
想通过输入一个法语句子,比如这句 “Jane visite I'Afrique en septembre.”,将它翻译成一个英语句子,“Jane is visiting Africa in September.”。x<1> 到x<5>表示输入的句子的单词,y<1>到y<6>表示输出的句子的单词,需要训练出一个新的网络来输入序列x和输出序列y(注意输入和输出不是一一对应的,输入是5个,输出是6个)
- 建立一个网络,这个网络叫做编码网络(encoder network)(上图编号1所示),它是一个RNN的结构, RNN的单元可以是GRU 也可以是LSTM。每次只向该网络中输入一个法语单词,将输入序列接收完毕后,这个RNN网络会输出一个向量来代表这个输入序列。
- 之后你可以建立一个解码网络(上图编号2所示),它以编码网络的输出作为输入,编码网络是左边的黑色部分(上图编号1所示),之后它可以被训练为每次输出一个翻译后的单词,一直到它输出序列的结尾或者句子结尾标记
在给出足够的法语和英语文本的情况下,如果你训练这个模型,通过输入一个法语句子来输出对应的英语翻译,这个模型将会非常有效
根据图片输出文字描述
给出一张图片,比如这张猫的图片(上图编号1所示),它能自动地输出该图片的描述,一只猫坐在椅子上
需要看下卷积神经网络的部分,sheng的学习笔记-AI-卷积神经网络经典架构-LeNet-5、AlexNet、VGGNet-16_f6layer84-CSDN博客
将图片输入到卷积神经网络中,比如一个预训练的AlexNet结构(上图编号2方框所示),然后让其学习图片的编码,或者学习图片的一系列特征。
去掉最后的softmax单元(上图编号3所示),这个预训练的AlexNet结构会给你一个4096维的特征向量,向量表示的就是这只猫的图片,所以这个预训练网络可以是图像的编码网络。
现在你得到了一个4096维的向量来表示这张图片,接着你可以把这个向量输入到RNN中(上图编号4方框所示),RNN要做的就是生成图像的描述(特征向量),然后让网络生成一个输出序列,或者说一个一个地输出单词序列
集束搜索(Beam Search)
对于机器翻译,将估计一个英文翻译的概率,比如估计这句英语翻译的概率,"Jane is visiting Africa in September.",这句翻译是取决于法语句子,"Jane visite I'Afrique en septembre.",这就是英语句子相对于输入的法语句子的可能性,所以它是一个条件语言模型。
对于输入的法语句子"Jane visite l'Afrique en septembre." ,可以翻译成"Jane is visiting Africa in September.",也可以翻译成一个不一样的,不那么糟糕的,但也不是最好的翻译:"Jane is going to be visiting Africa in September."
所以当你使用这个模型来进行机器翻译时,你并不是从得到的分布中进行随机取样,而是你要找到一个英语句子y(上图编号1所示),使得条件概率最大化。所以在开发机器翻译系统时,你需要做的一件事就是想出一个算法,用来找出合适的值y,使得该项最大化
为什么不是贪心算法
贪心搜索是一种来自计算机科学的算法,生成第一个词的分布以后,它将会根据你的条件语言模型挑选出最有可能的第一个词进入你的机器翻译模型中,在挑选出第一个词之后它将会继续挑选出最有可能的第二个词,然后继续挑选第三个最有可能的词,这种算法就叫做贪心搜索,但是你真正需要的是一次性挑选出整个单词序列,从y<1>、y<2>到y<t>来使得整体的概率最大化。所以这种贪心算法先挑出最好的第一个词,在这之后再挑最好的第二词,然后再挑第三个
这种方法其实并不管用,为了证明这个观点,我们来考虑下面两种翻译
第一串(上图编号1所示)翻译明显比第二个(上图编号2所示)好,所以我们希望机器翻译模型会说第一个句子的概率p比第二个句子要高,第一个句子对于法语原文来说更好更简洁,虽然第二个也不错,但是有些啰嗦,里面有很多不重要的词。
但如果贪心算法挑选出了"Jane is"作为前两个词,因为在英语中going更加常见,于是对于法语句子来说"Jane is going"相比"Jane is visiting"会有更高的概率作为法语的翻译,所以很有可能如果你仅仅根据前两个词来估计第三个词的可能性,得到的就是going,最终你会得到一个欠佳的句子,在模型中这不是一个最好的选择。
集束搜索原理
给定输入,比如法语句子,你不会想要输出一个随机的英语翻译结果,你想要一个最好的,最可能的英语翻译结果。集束搜索就是解决这个最常用的算法
“Jane visite l'Afrique en Septembre.”(法语句子),我们希望翻译成英语,"Jane is visiting Africa in September".(英语句子),集束搜索算法首先做的就是挑选要输出的英语翻译中的第一个单词。这里我列出了10,000个词的词汇表(下图编号1所示)。
在集束搜索的第一步中我用这个网络部分,绿色是编码部分(下图编号2所示),紫色是解码部分(下图编号3所示),来评估第一个单词的概率值,给定输入序列x,即法语作为输入,第一个输出y的概率值是多少
集束宽
集束搜索则会考虑多个选择,集束搜索算法会有一个参数B,叫做集束宽(beam width)。
在这个例子中我把这个集束宽设成3,这样就意味着集束搜索不会只考虑一个可能结果,而是一次会考虑3个,比如对第一个单词有不同选择的可能性,最后找到in、jane、september,是英语输出的第一个单词的最可能的三个选项,然后集束搜索算法会把结果存到计算机内存里以便后面尝试用这三个词。如果集束宽设的不一样,如果集束宽这个参数是10的话,那么我们跟踪的不仅仅3个,而是10个第一个单词的最可能的选择。
算法
对第一个单词有不同选择的可能性,最后找到in、jane、september,是英语输出的第一个单词的最可能的三个选项
针对每个第一个单词考虑第二个单词是什么,单词in后面的第二个单词可能是a或者是aaron,,september后面第二个单词可能的词列表(下图编号1所示)。
为了评估第二个词的概率值,绿色是编码部分(上图编号2所示),而对于解码部分,努力找出第一个单词是in的情况下,第二个单词是什么,这样这个网络就可以用来评估第二个单词的概率了,在给定法语句子和翻译结果的第一个单词in的情况下。要找到最可能的第一个和第二个单词对,所以不仅仅是第二个单词有最大的概率,而是第一个、第二个单词对有最大的概率(上图编号7所示)。
如果集束搜索找到了第一个和第二个单词对最可能的三个选择是“in September”或者“jane is”或者“jane visits”,这就意味着我们去掉了september作为英语翻译结果的第一个单词的选择,所以我们的第一个单词现在减少到了两个可能结果,但是我们的集束宽是3,所以还是有y<1>,y<2>对的三个选择
总结一下:
第一步:找到第一个单词最有可能的三个单词a,b,c
第二步:
- 第一个单词是a的前提下,找到下一个单词列表的概率aa,ab,ac。。。az,
- 在第一个单词是b的前提下,找到下一个单词概率ba,bb,bc。。。bz,
- 在第一个单词是c的前提下,找到下一个单词概率ca,cb,cc。。。。cz
计算出上述所有的概率 aa的概率,ab的概率...ba的概率,bb的概率,ca的概率,cb的概率p(第一个单词) * p(第二个单词) 概率最大的前三个值,比如是 ab,bd,cz
第三步:
重复上面的操作,每次只保留3个概率最高的值,直到输入的最后一个单词结束
改进集束搜索
连乘改为log
乘积概率的每个值,都是小于1的,很多小于1的数乘起来,会得到很小很小的数字,会造成数值下溢(numerical underflow)。数值下溢就是数值太小了,导致电脑的浮点表示不能精确地储存,因此在实践中,我们不会最大化这个乘积,而是取log值。如果在这加上一个,最大化这个log求和的概率值,会得到一个数值上更稳定的算法,不容易出现四舍五入的误差,数值的舍入误差(rounding errors)或者说数值下溢(numerical underflow)
p(x1)*p(x2) 的log函数 log( p(x1) * p(x2)) = log p(x1) + log p(x2)
归一化目标函数
如果有一个很长的句子,那么这个句子的概率会很低,因为乘了很多项小于1的数字来估计句子的概率。所以如果乘起来很多小于1的数字,那么就会得到一个更小的概率值,所以这个目标函数有一个缺点,它可能不自然地倾向于简短的翻译结果,它更偏向短的输出,因为短句子的概率是由更少数量的小于1的数字乘积得到的,所以这个乘积不会那么小
取每个单词的概率对数值的平均了,这样很明显地减少了对输出长的结果的惩罚
有个探索性的方法,相比于直接除Ty,也就是输出句子的单词总数,我们有时会用一个更柔和的方法(a softer approach),在Ty上加上指数a,a可以等于0.7。如果a等于1,就相当于完全用长度来归一化,如果等于0,Ty的0次幂就是1,就相当于完全没有归一化,这就是在完全归一化和没有归一化之间。a就是算法另一个超参数(hyper parameter),需要调整大小来得到最好的结果。不得不承认,这样用实际上是试探性的,它并没有理论验证。
如何选择束宽B
B越大,你考虑的选择越多,你找到的句子可能越好,但是B越大,你的算法的计算代价越大,因为你要把很多的可能选择保存起来。
如果束宽很大,你会考虑很多的可能,你会得到一个更好的结果,因为你要考虑很多的选择,但是算法会运行的慢一些,内存占用也会增大,计算起来会慢一点。
而如果你用小的束宽,结果会没那么好,因为你在算法运行中,保存的选择更少,但是你的算法运行的更快,内存占用也小
我们例子中用了束宽为3,所以会保存3个可能选择,在实践中这个值有点偏小。在产品中,经常可以看到把束宽设到10
集束搜索的误差分析
我咋知道是集束搜索有问题了,还是rnn算法有问题了?
我们来用这个例子说明: “Jane visite l'Afrique en septembre”。
在你的机器翻译的dev集(开发集)中,人工是这样翻译的: Jane visits Africa in September,这是一个十分不错的人工翻译结果,
当你在已经完成学习的RNN模型,也就是已完成学习的翻译模型中运行束搜索算法时,它输出了这个翻译结果:Jane visited Africa last September。这是一个十分糟糕的翻译
你的模型有两个主要部分,一个是神经网络模型,一个是集束搜索算法,将Jane visited Africa 作为输入,根据rnn模型计算人工答案的输出概率p(y*),根据rnn模型计算的模型答案的输出概率是p(y^)
rnn模型人工答案的概率p(y*)>rnn模型答案的概率p(y^) ,代表rnn神经网络部分计算出正确的答案的概率很高,但是集束搜索算法没有把选择这个概率最高的(集束搜索算法的作用应该是找到所有可能性中概率最高的那个),所以集束搜索算法有问题
rnn模型人工答案的概率p(y*)<rnn模型答案的概率p(y^) ,代表rnn神经网络计算出正确答案的概率很低,所以rnn神经网络模型部分出了问题,改这部分逻辑
遍历开发集,然后在其中找出算法产生的错误,假如说p(y*)的值为2 x 10-10,而p(y^) 的值为 1 x10-10,这种情况下我们得知束搜索算法实际上选择了比可能性更低的,因此我会说束搜索算法出错了。我将它缩写为B。
接着你继续遍历第二个错误,再来看这些可能性。也许对于第二个例子来说,你认为是RNN模型出现了问题,我会用缩写R来代表RNN。
再接着你遍历了更多的例子,有时是束搜索算法出现了问题,有时是模型出现了问题,等等。通过这个过程,你就能够执行误差分析,得出束搜索算法和RNN模型出错的比例是多少。有了这样的误差分析过程,你就可以对开发集中每一个错误例子,即算法输出了比人工翻译更差的结果的情况,尝试确定这些错误,是搜索算法出了问题,还是生成目标函数(束搜索算法使之最大化)的RNN模型出了问题
预测未来几个时间步长
通常,时间序列预测描述了预测下一个时间步长的观测值。这被称为“一步预测”,因为仅要预测一个时间步。在一些时间序列问题中,必须预测多个时间步长。与单步预测相比,这些称为多步时间序列预测问题
方案一
选择是使用已经训练好的模型,使其预测下一个值,然后将该值加到输入中(就像这个预测值实际上已经有了),然后再次使用该模型预测后面的值,以此类推
series = generate_time_series(1, n_steps + 10)
X_new, Y_new = series[:, :n_steps], series[:, n_steps:]
X = X_new
for step_ahead in range(10):y_pred_one = model.predict(X[:, step_ahead:])[:, np.newaxis, :]X = np.concatenate([X, y_pred_one], axis=1)Y_pred = X[:, n_steps:]
由于误差可能会累积,因此对下一步长的预测通常会比对未来几个时间步长的预测更为准确
方案二
训练RNN一次预测所有10个值。我们仍然可以使用一个序列到向量的模型,但是它输出10个值而不是1个值
series = generate_time_series(10000, n_steps + 10)
X_train, Y_train = series[:7000, :n_steps], series[:7000, -10:, 0]
X_valid, Y_valid = series[7000:9000, :n_steps], series[7000:9000, -10:, 0]
X_test, Y_test = series[9000:, :n_steps], series[9000:, -10:, 0]model = keras.models.Sequential([keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),keras.layers.SimpleRNN(20),keras.layers.Dense(10)
])Y_pred = model.predict(X_new)
得到的y中有10个变量(代表10个时间步骤的值,比如明天一个值,后天一个值,大后天一个值)
方案三
训练模型在每个时间步长来预测下一个10个值。换句话说,我们可以将这个序列到向量的RNN转换为序列到序列的RNN。这种技术的优势在于,损失将包含每个时间步长的RNN输出项,而不仅仅是最后一个时间步长的输出项。这意味着将有更多的误差梯度流过模型,它们不需要随时间而“流淌”。它们从每个时间步长的输出中流出。这会稳定和加速训练
模型在时间步长0时会输出一个向量,其中包含时间步长1到10的预测,然后在时间步长1时模型会预测时间步长2到11,以此类推。因此,每个目标必须是与输入序列长度相同的序列,在每个步长都必须包含10维向量
要将模型转换为序列到序列的模型,我们必须在所有循环层(甚至最后一层)中设置return_sequen ces=True,必须在每个时间步长都应用输出Dense层。Keras为此提供了一个TimeDistributed层:它包装了任何层(例如Dense层),在输入序列的每个时间步长应用这个层。它通过重构输入的形状来有效地做到这一点,以便每个时间步长都被视为一个单独的实例(将输入的形状从[批量大小,时间步长,输入维度]调整为[批量大小×时间步长,输入维度]。在此示例中,输入维数为20,因为前一个SimpleRNN层有20个单元),然后运行Dense层,最后将输出重构为序列(将输出从[批量大小×时间步长,输出维度]重构为[批量大小,时间步长,输出维度]。在此示例中,由于Dense层有10个单元,因此输出维度的数量为10)[1]。这是更新的模型
Y = np.empty((10000, n_steps, 10)) # each target is a sequence of 10D vectors
for step_ahead in range(1, 10 + 1):Y[:, :, step_ahead - 1] = series[:, step_ahead:step_ahead + n_steps, 0]
Y_train = Y[:7000]
Y_valid = Y[7000:9000]
Y_test = Y[9000:]
model = keras.models.Sequential([keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),keras.layers.SimpleRNN(20, return_sequences=True),keras.layers.TimeDistributed(keras.layers.Dense(10))
])
Dense层实际上支持序列作为输入(甚至是更高维的输入):它像TimeDistributed(Dense(…))一样处理它们,这意味着它仅仅应用于最后一个输入维度(独立于所有的时间步长)。因此,我们可以用Dense(10)代替最后这一层。但是为了清楚起见,我们将继续使用TimeDistributed(Dense(10)),因为它清楚地表明在每个时间步长都独立应用了Dense层,并且模型将输出一个序列,而不仅仅是一个向量
参考文章:
RNN及其变体(LSTM、GRU)的介绍_gru是rnn-CSDN博客
吴恩达深度学习,序列模型的视频