Python 深度学习架构实用指南:第三、四、五部分(1)

简介: Python 深度学习架构实用指南:第三、四、五部分(1)

第 3 节:序列建模

在本节中,我们将学习两个重要的 DL 模型以及这些模型的演化路径。 我们将通过一些示例探索它们的架构和各种工程最佳实践。

本节将涵盖以下章节:

  • “第 6 章”,“循环神经网络”

六、循环神经网络

在本章中,我们将解释最重要的深度学习模型之一,即循环神经网络RNNs)。 我们将首先回顾什么是 RNN,以及为什么它们非常适合处理顺序数据。 在简要介绍了 RNN 模型的发展路径之后,我们将说明根据不同形式的输入和输出数据以及工业示例进行分类的各种 RNN 架构。 我们将找出问题的答案,例如“我们如何仅生成一个输出?”,“输出可以是序列吗?”,和“仅对一个输入单元有效吗?”。

接下来,我们将讨论按循环层分类的几种架构。 首先,我们将应用基本的 RNN 架构来编写我们自己的《战争与和平》。 具有原始架构的 RNN 不能很好地保存长期依赖的信息。 为了解决这个问题,我们将学习内存增强型架构,包括长短期内存和门控循环单元。 我们还将在股票价格预测中采用门控架构。 最后,由于对捕获过去的信息不满意,我们将引入一种双向架构,该架构允许该模型从序列的过去和将来上下文中保留信息。 具有 LSTM 的双向模型将用于对电影评论的情感进行分类。

在本章中,我们将介绍以下主题:

  • 什么是 RNN?
  • RNN 的演进路径
  • RNN 架构按输入和输出(一对多,多对一,同步和不同步的多对多)
  • 原始 RNN 架构
  • 用于文本生成的原始 RNN
  • 长期记忆
  • 用于文本生成的 LSTM RNN
  • 门控循环单元
  • 用于股价预测的 GRU RNN
  • 双向 RNN
  • 用于情感分类的 BRNN

什么是 RNN?

回想一下,在前几章中讨论的深度前馈网络,自编码器神经网络和 CNN 中,数据从输入层到输出层是单向流动的。 但是,深度学习模型允许数据沿任何方向进行,甚至可以循环回到输入层,并且不仅限于前馈架构。 从上一个输出循环返回的数据成为下一个输入数据的一部分。 RNN 就是很好的例子。 下图描述了 RNN 的一般形式,在本章中,我们将研究 RNN 的几种变体:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GTz3E73E-1681704851864)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/3e30c40c-554c-4e73-9cb5-8b5ca1e60c13.png)]

如上图所示,来自先前时间点的数据进入了当前时间点的训练。 循环架构使模型可以很好地与时间序列(例如产品销售,股票价格)或顺序输入(例如文章中的单词-DNA 序列)配合使用。

假设我们在传统的神经网络中有一些输入x[t](其中t代表时间步长或顺序顺序),如下图所示。 假设不同t处的输入彼此独立。 可以将任何时间的网络输出写为h[t] = f(x[t]),如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mk50n74-1681704851866)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/b84d3a24-c102-4c42-ad72-7ad5a000340f.png)]

在 RNN 中,反馈回路将当前状态的信息传递到下一个状态,如下图的网络展开版本所示。 RNN 网络在任何时间的输出都可以写成h[t] = f(h[t-1], x[t])。 对序列的每个元素执行相同的任f,并且输出h[t]取决于先前计算的输出h[t-1]。 得益于这种类似链的架构,或到目前为止所计算出的额外的存储器捕获,在将 RNN 应用于时间序列和序列数据方面取得了巨大的成功。 在将 RNN 的变体应用于各种问题之前,首先,我们将了解 RNN 的历史及其演变路径:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ePCCOhhF-1681704851866)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/1c4993f9-b13d-411a-a330-9c7a332bf4e3.png)]

RNN 的演进路径

RNN 实际上具有悠久的历史,最早是在 1980 年代开发的。 霍普菲尔德网络是第一个具有循环链接的神经网络,它是约翰·霍普菲尔德(John Hopfield)在《Neurons with graded response have collective computational properties like those of two-state neurons》中发明的。

受 Hopfield 网络的启发,在《及时发现结构》中引入了全连接神经网络 – Elman 网络。 Elman 网络具有一个隐藏层和一组连接到该隐藏层的上下文单元。 在每个时间步,上下文单元都会跟踪隐藏单元的先前值。

1992 年,Schmidhuber 由于记住了长期依赖性而发现了梯度消失的问题。 五年后,Schmidhuber 和 Hochreiter 在《LSTM》中提出了长短期记忆LSTM)。 遗忘门单元增强了 LSTM 的功能,它可以删除旧的和无关的内存,从而避免梯度消失。

在 1997 年,RNN 扩展为双向版本(在《双向循环神经网络》中发表,该模型在正向(从头到尾)和负向(从头到尾)时间方向上训练。

《分层控制如何在人工自适应系统中进行自组织》中介绍的分层 RNN 同时具有水平和垂直循环连接,从而分解复杂和自适应信息。

自 2007 年以来,LSTM 开始盛行:在《循环神经网络的判别性关键词发现应用》中,它们在某些语音识别任务中的表现优于传统模型。 在 2009 年,通过连通性时间分类CTC)训练的 LSTM 模型用于语音音频中的连接手写识别和音素识别等任务。 LSTM 模型也成为机器翻译和语言建模的最新解决方案。 如《Show and Tell:神经图像字幕生成器》中所述,LSTM 甚至与 CNN 结合以自动进行图像字幕。

早在 2014 年,GRU RNN 被引入,是对常规 RNN 的另一项改进,类似于 LSTM。 GRU RNN 在许多任务上的表现与 LSTM 相似。 但是,它们在较小的数据集上表现出更好的表现,部分原因是它们需要调整的参数较少,而架构中的门却较少。

按照承诺,我们将详细研究 RNN 的变体,然后将其应用于实际问题。

RNN 架构和应用

RNN 可以分为多对一一对多多对多(同步)和多对多(基于它们的输入和输出)。 从隐藏层的角度来看,最常用的 RNN 架构包括基本的原始 RNN 和双向的 LSTM 和 GRU。 我们将专注于 RNN 的这四种架构,并将首先通过输入和输出简要提及这四个类别。

不同输入和输出的架构

多对一多对一架构可能是最直观的。 我们可以在序列中输入尽可能多的元素或时间步长,但是模型在经历整个序列后仅产生一个输出。 下图显示了其一般结构,其中f表示一个或多个循环层:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xOuv1b6Y-1681704851866)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/17d757e5-e067-48df-b29b-69d452b485cc.png)]

多对一架构可用于情感分析,其中该模型读取整个客户评论(例如)并输出情感分数。 类似地,它可以用于在遍历整个音频流之后识别歌曲的流派。

一对多:与多对一 RNN 相反,一对多架构仅接受一个输入并产生一个输出序列。 一对多架构可以表示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qCJrLUiA-1681704851867)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/250fb990-a7e3-437e-acad-390f227aa524.png)]

像这样的 RNN 通常用于生成序列。 例如,我们可以使用该模型来生成带有起始音符或流派作为唯一输入的音乐。 以类似的方式,我们甚至可以用指定的起始词以莎士比亚风格写诗。

多对多(同步):第三种架构**多对多(同步)**使每个输入需要一个输出。 正如我们在以下网络流中看到的那样,每个输出都取决于所有先前的输出和当前输入:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9XaTV1a-1681704851867)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/23bd5749-a42f-49f5-aa72-a9ec7d3fb457.png)]

多对多(同步)架构的常见用例之一是时间序列预测,在这种情况下,我们希望在给定当前和所有先前输入的情况下,在每个时间步长执行滚动预测。 此架构还广泛用于自然语言处理NLP)问题,例如命名实体识别NER),词性PoS)标记和语音识别。

多对多(不同步):对于多对多架构的不同步版本,该模型在完成读取整个输入序列之前不会生成输出。 结果,输出的数量可以与输入的数量不同。 如下图所示,输出序列Ty的长度不必等于输入序列Tx的长度:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b1Z9tujW-1681704851867)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/db3ebe85-a04a-41a7-94c8-1916d6257e5f.png)]

在机器翻译中最常见的是不同步的多对多架构。 例如,模型读取英语的整个句子,然后开始生成法语的翻译句子。 另一个流行的用例是提前进行多步预测,要求我们根据所有先前的输入来预测提前几个时间步。

到目前为止,我们已经通过模型输入和输出了解了四种 RNN 架构,我们将在本章的其余部分的实际示例中结合其中的一些,我们将主要讨论隐藏层中的架构,更具体地说,是循环层。

让我们从原始 RNN 开始,这是循环架构的最基本形式。

原始神经网络

标注了权重且展开后的版本的基本 RNN 模型如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CZXrta1M-1681704851867)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/0c393218-eae9-42f3-a09e-bf404ddf0682.png)]

这里,U是连接输入层和隐藏层的权重,V是隐藏层和输出层之间的权重,W是循环层,即反馈层的权重; s[t]是时间步t的隐藏状态,x[t]h[t]分别是时间步t的输入和输出。

请注意,为简便起见,我们从现在开始仅使用一个循环层,但是我们可以将多个循环层堆叠在一起,我们将很快看到。

层之间的关系可以描述如下:

  • 基于当前输入x[t]和通过s[t] = a(U x[t] + W s[t-1])的先前隐藏状态s[t-1]计算时间步长ts[t]的隐藏状态,其中a是激活函数。 RNN 中隐藏层的激活函数的典型选择包括 tanh 和 ReLU。
  • 同样,s[t-1]取决于s[t-2]: s[t-1] = a(U x[t] + W s[t-2]),依此类推。 s[1]也依赖于s[0],按照惯例,该s[0]设置为全零。
  • 由于对时间步长具有这种依赖性,因此可以将隐藏状态视为网络的内存,从以前的时间步长中捕获信息。
  • 将时间步长t的输出计算为h[t] = g(V s[t]),其中g是激活函数。 根据执行的任务,它可以是用于二分类的 Sigmoid 函数,用于多类分类的 softmax 函数以及用于回归的简单线性函数。

与传统的神经网络类似,所有的权重UVW均使用反向传播算法进行训练。 但是不同之处在于,在当前时间t上,我们需要计算除当前时间之外的所有先前t-1个时间步的损失。 这是因为权重由所有时间步共享,并且一个时间步的输出间接依赖于所有先前的时间步,例如权重的梯度。 例如,如果要计算时间步t = 5的梯度,则需要向后传播前四个时间步,并对五个时间步的梯度求和。 这种特殊的反向传播算法称为时间上的反向传播(BPTT)

从理论上讲,RNN 可以从输入序列的开头捕获信息,从而增强了时间序列或序列建模的预测能力。 但是,由于梯度梯度消失,原始 RNN 并非如此。 我们将在后面对此进行详细说明,并将了解专门设计用于解决此问题的其他架构,例如 LSTM 和 GRU。 但是现在,让我们假设原始 RNN 在许多情况下都能正常工作,并且获得了一些实践经验,因为它们是任何 RNN 的基本组成部分。

用于文本生成的原始 RNN

如前所述,RNN 通常在 NLP 域中用作语言模型,它在单词序列上分配概率分布,例如机器翻译,PoS 标记和语音识别。 我们将使用一种相当有趣的语言来对问题文本生成进行建模,其中 RNN 模型用于学习指定域的文本序列,然后在所需域中生成全新且合理的文本序列。

基于 RNN 的文本生成器可以接受任何输入文本,例如《哈利波特》等小说,莎士比亚的诗歌以及《星球大战》的电影剧本,并可以生成自己的《哈利波特》,莎士比亚的诗歌和《星球大战》电影剧本。 如果对模型进行了很好的训练,那么人工文本应该是合理的,并且阅读起来应与原始文本相似。 在本部分中,我们将以《战争与和平》和俄罗斯作家列夫·托尔斯泰的小说为例。 随意使用您喜欢的任何书籍进行训练。 我们建议从没有版权保护的书中下载文本数据。 古腾堡计划是一个很好的资源,拥有超过 5.7 万本版权已过期的免费优秀书籍。

首先,我们需要直接从这里下载《战争与和平》的.txt文件。 或者,我们可以从 Gutenberg 项目下载该文件,但是我们将需要进行一些清理,例如,从文件以及目录中删除开头部分Project Gutenberg EBook,以及结尾的End of the Project

然后,我们读取文件,将文本转换为小写,并通过打印出前 100 个字符来快速查看它:

>>> training_file = 'warpeace_input.txt'
>>> raw_text = open(training_file, 'r').read()
>>> raw_text = raw_text.lower()
>>> raw_text[:100]
'ufeff"well, prince, so genoa and lucca are now just family estates of thenbuonapartes. but i warn you, i'

现在,我们需要计算总共有多少个字符:

>>> n_chars = len(raw_text)
>>> print('Total characters: {}'.format(n_chars))
Total characters: 3196213

然后,我们可以获得唯一的字符和词汇量:

>>> chars = sorted(list(set(raw_text)))
>>> n_vocab = len(chars)
>>> print('Total vocabulary (unique characters): {}'.format(n_vocab))
Total vocabulary (unique characters): 57
>>> print(chars)
['n', ' ', '!', '"', "'", '(', ')', '*', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '=', '?', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '!!CDP!E.agrave!!', '!!CDP!E.auml!!', '!!CDP!E.eacute!!', '!!CDP!E.ecirc!!', 'ufeff']

现在,我们有了一个原始的训练数据集,其中包含超过 300 万个字符和 57 个唯一字符。 但是我们如何将其提供给 RNN 模型呢?

回想一下,在同步多对多架构中,该模型接受序列并同时生成序列。 在我们的例子中,我们可以给模型提供固定长度的字符序列。 输出序列的长度与输入序列的长度相同,并且一个字符从其输入序列偏移。 假设我们从learning的单词设置序列长度为5。 现在,我们可以使用输入learn和输出earni来构造训练样本。 我们可以在网络中对此进行可视化,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FVuasYhw-1681704851868)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/062881a9-6c19-44b0-957a-7b33bcc423e0.png)]

我们只是构造了一个训练样本。 对于整个训练集,我们可以将原始文本数据分成相等长度的序列,例如 100。每个字符序列都是训练样本的输入。

接下来,我们以相同的方式将原始文本数据拆分为序列,但是这次从第二个字符开始。 每个结果序列都是训练样本的输出。 例如,给定原始文本deep learning architectures5作为序列长度,我们可以创建五个训练样本,如下所示:

输入 输出
deep_ eep_l
learn earni
ing_a ng_ar
rchit chite
ectur cture

在此,_表示空间。

请注意,最后一个子序列es不够长,因此我们可以简单地忽略它。

由于神经网络模型仅吸收数字数据,因此字符的输入和输出序列由单热编码的向量表示。 我们通过将 57 个字符映射到从056的索引以及另一个相反的索引来创建字典:

>>> index_to_char = dict((i, c) for i, c in enumerate(chars))
>>> char_to_index = dict((c, i) for i, c in enumerate(chars))
>>> print(char_to_index)
{'n': 0, ' ': 1, '!': 2, '"': 3, "'": 4, '(': 5, ')': 6, '*': 7, ',': 8, '-': 9, '.': 10, '/': 11, '0': 12, '1': 13, '2': 14, '3': 15, '4': 16, '5': 17, '6': 18, '7': 19, '8': 20, '9': 21, ':': 22, ';': 23, '=': 24, '?': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48, 'x': 49, 'y': 50, 'z': 51, '!!CDP!E.agrave!!': 52, '!!CDP!E.auml!!': 53, '!!CDP!E.eacute!!': 54, '!!CDP!E.ecirc!!': 55, 'ufeff': 56}

例如,字符e成为长度为 57 的向量,索引为30的为1,所有其他索引的值为 0s。 准备好角色查找表后,我们可以如下构建训练数据集:

>>> import numpy as np
>>> seq_length = 100
>>> n_seq = int(n_chars / seq_length)

将序列长度设置为 100,我们将获得n_seq训练样本。 接下来,我们初始化训练输入和输出:

请注意,序列长度具有形状(样本数,序列长度,特征维数)。 由于我们将使用 Keras 进行 RNN 模型训练,因此需要这种形式。

>>> X = np.zeros((n_seq, seq_length, n_vocab))
>>> Y = np.zeros((n_seq, seq_length, n_vocab))

组装每个n_seq样本:

>>> for i in range(n_seq):
...     x_sequence = raw_text[i * seq_length : (i + 1) * seq_length]
...     x_sequence_ohe = np.zeros((seq_length, n_vocab))
...     for j in range(seq_length):
...         char = x_sequence[j]
...         index = char_to_index[char]
...         x_sequence_ohe[j][index] = 1.
...     X[i] = x_sequence_ohe
...     y_sequence = 
              raw_text[i * seq_length + 1 : (i + 1) * seq_length + 1]
...     y_sequence_ohe = np.zeros((seq_length, n_vocab))
...     for j in range(seq_length):
...         char = y_sequence[j]
...         index = char_to_index[char]
...         y_sequence_ohe[j][index] = 1.
...     Y[i] = y_sequence_ohe
>>> X.shape
(31962, 100, 57)
>>> Y.shape
(31962, 100, 57)

同样,每个样本都由单热编码字符的100元素组成。 我们终于准备好了训练数据集,现在是时候构建我们的原始 RNN 模型了。 让我们训练一个具有两个循环层的模型,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXiNON2s-1681704851868)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/c2e8a6c3-2717-45e3-80e3-8cf434b83e6a.png)]

每层包含 800 个单元,其中0.3和 ReLU 的丢弃率作为激活函数。 首先,导入所有必需的模块:

>>> from keras.models import Sequential
>>> from keras.layers.core import Dense, Activation, Dropout
>>> from keras.layers.recurrent import SimpleRNN
>>> from keras.layers.wrappers import TimeDistributed
>>> from keras import optimizers

现在,指定其他超参数,包括批量大小和周期数,以及隐藏层和单元数以及丢弃率:

>>> batch_size = 100
>>> n_layer = 2
>>> hidden_units = 800
>>> n_epoch= 300
>>> dropout = 0.3

现在,创建并编译网络:

>>> model = Sequential()
>>> model.add(SimpleRNN(hidden_units, input_shape=
          (None, n_vocab),return_sequences=True, activation='relu'))
>>> model.add(Dropout(dropout))
>>> for i in range(n_layer - 1):
...     model.add(SimpleRNN(hidden_units, return_sequences=True,
                    activation='relu'))
...     model.add(Dropout(dropout))
>>> model.add(TimeDistributed(Dense(n_vocab)))
>>> model.add(Activation('softmax'))

关于我们刚刚建立的模型,需要注意以下几点:

  • return_sequences=True:循环层的输出变成一个序列,从而实现了我们想要的多对多架构。 否则,它将变成多对一,最后一个元素作为输出。
  • TimeDistributed:由于循环层的输出是一个序列,而下一层-密集层-不接受顺序输入,因此TimeDistributed换行用作适配器来解决此问题。
  • Softmax:之所以使用这种激活,是因为该模型生成了一个单编码字符向量。

至于优化器,我们将选择 RMSprop,其学习率为0.001

>>> optimizer = optimizers.RMSprop(lr=0.001, rho=0.9, 
                                   epsilon=1e-08, decay=0.0)
>>> model.compile(loss="categorical_crossentropy",optimizer=optimizer)

添加了多类交叉熵的损失度量之后,我们就完成了模型的构建。 我们可以通过使用以下代码来查看模型的摘要:

>>> print(model.summary()) _________________________________________________________________
 Layer (type) Output Shape Param #
 =================================================================
 simple_rnn_1 (SimpleRNN) (None, None, 800) 686400
 _________________________________________________________________
 dropout_1 (Dropout) (None, None, 800) 0
 _________________________________________________________________
 simple_rnn_2 (SimpleRNN) (None, None, 800) 1280800
 _________________________________________________________________
 dropout_2 (Dropout) (None, None, 800) 0
 _________________________________________________________________
 time_distributed_1 (TimeDist (None, None, 57) 45657
 _________________________________________________________________
 activation_1 (Activation) (None, None, 57) 0
 =================================================================
 Total params: 2,012,857
 Trainable params: 2,012,857
 Non-trainable params: 0
 _________________________________________________________________

我们有超过 200 万个参数需要训练。 但是,在开始漫长的训练过程之前,最好建立一些回调,以便在训练过程中跟踪模型的统计信息和内部状态。 我们将采用的回调函数包括:

  • 模型检查点,它在每个周期后保存模型,以便我们加载最新保存的模型,并在模型意外停止时从那里继续训练。
  • 尽早停止,当失去的表现不再改善时,停止训练。
  • 定期检查文本生成结果。 我们想看看生成的文本是多么合理,并且训练损失不够明显。

这些函数的定义或初始化如下:

>>> from keras.callbacks import Callback, ModelCheckpoint, EarlyStopping
>>> file_path =file_path =
                "weights/weights_epoch_{epoch:03d}_loss_{loss:.4f}.hdf5"
>>> checkpoint = ModelCheckpoint(file_path, monitor='loss',
                            verbose=1, save_best_only=True, mode='min')

模型检查点将与周期号一起保存,而训练损失则保存在文件名中。 我们还同时监视验证损失,并查看其是否在50个连续的周期内停止下降:

>>> early_stop = EarlyStopping(monitor='loss', min_delta=0,         
                                patience=50, verbose=1, mode='min')

接下来,我们有用于质量监控的回调。 首先,我们编写一个辅助函数,该函数在给定 RNN 模型的情况下生成任意长度的文本:

>>> def generate_text(model, gen_length, n_vocab, index_to_char):
...     """
...     Generating text using the RNN model
...     @param model: current RNN model
...     @param gen_length: number of characters we want to generate
...     @param n_vocab: number of unique characters
...     @param index_to_char: index to character mapping
...     @return: string of text generated
...     """
...     # Start with a randomly picked character
...     index = np.random.randint(n_vocab)
...     y_char = [index_to_char[index]]
...     X = np.zeros((1, gen_length, n_vocab))
...     for i in range(gen_length):
...         X[0, i, index] = 1.
...         indices = np.argmax(model.predict(
                        X[:, max(0, i - seq_length -1):i + 1, :])[0], 1)
...         index = indices[-1]
...         y_char.append(index_to_char[index])
...     return ('').join(y_char)

它以随机选择的字符开头。 然后,输入模型根据过去生成的字符来预测剩余的每个gen_length-1字符,这些字符的长度最大为100(序列长度)。

现在,我们可以定义callback类,该类为每个N个周期生成文本:

>>> class ResultChecker(Callback):
...     def __init__(self, model, N, gen_length):
...         self.model = model
...         self.N = N
...         self.gen_length = gen_length
...
...     def on_epoch_end(self, epoch, logs={}):
...         if epoch % self.N == 0:
...             result = generate_text(self.model, self.gen_length, 
                                       n_vocab, index_to_char)
...             print('nMy War and Peace:n' + result)

现在所有组件都准备就绪,让我们开始训练模型:

>>> model.fit(X, Y, batch_size=batch_size, verbose=1, epochs=n_epoch,
 callbacks=[ResultChecker(model, 10, 200), checkpoint, early_stop])

生成器为每个10周期写入200字符。 我们可以看到每个周期的进度条,其详细设置为10是静默模式,2没有进度条)。

以下是周期11151101的结果:

Epoch 1

Epoch 1/300
 8000/31962 [======>.......................] - ETA: 51s - loss: 2.8891 31962/31962 [==============================] - 67s 2ms/step - loss: 2.1955 My War and Peace:
 5 the count of the stord and the stord and the stord and the stord and the stord and the stord and the stord and the stord and the stord and the stord and the stord and the stord and the stord and the
Epoch 00001: loss improved from inf to 2.19552, saving model to weights/weights_epoch_001_loss_2.19552.hdf5

Epoch 11

Epoch 11/300
 100/31962 [..............................] - ETA: 1:26 - loss: 1.2321 31962/31962 [==============================] - 66s 2ms/step - loss: 1.2493 My War and Peace:
 ?" said the countess was a strange the same time the countess was already been and said that he was so strange to the countess was already been and the same time the countess was already been and said Epoch 00011: loss improved from 1.26144 to 1.24933, saving model to weights/weights_epoch_011_loss_1.2493.hdf5

Epoch 51

Epoch 51/300
 31962/31962 [==============================] - 66s 2ms/step - loss: 1.1562 My War and Peace:
 !!CDP!E.agrave!! to see him and the same thing is the same thing to him and the same thing the same thing is the same thing to him and the same thing the same thing is the same thing to him and the same thing the sam Epoch 00051: loss did not improve from 1.14279

Epoch 101

Epoch 101/300
 31962/31962 [==============================] - 67s 2ms/step - loss: 1.1736 My War and Peace:
 = the same thing is to be a soldier in the same way to the soldiers and the same thing is the same to me to see him and the same thing is the same to me to see him and the same thing is the same to me Epoch 00101: loss did not improve from 1.11891

训练在周期203提前停止:

Epoch 00203: loss did not improve from 1.10864
Epoch 00203: early stopping

在 Tesla K80 GPU 上,每个周期大约需要 1 分钟。 经过约 3.5 小时的训练,损失从2.19552减少到1.10864

它在周期151生成以下文本:

which was a strange and serious expression of his face and shouting and said that the countess was standing beside him.
"what a battle is a strange and serious and strange and so that the countess was

我们的《战争与和平》读起来不错,尽管有点荒谬。 通过调整此原始 RNN 模型的超参数,我们可以做得更好吗? 绝对可以,但这是不值得的。 正如我们前面提到的,训练一个普通的 RNN 模型来解决需要学习长期依赖关系的问题非常困难-距离较远的步骤之间的依赖关系通常对于预测至关重要。 但是,由于梯度消失的问题,原始 RNN 仅能够捕获序列中几个早期步骤之间的时间依赖性。 LSTM 和 GRU 之类的架构是专门为解决此问题而设计的。 我们将在以下两个部分中说明它们如何随着时间的推移在内存中维护信息。

LSTM RNN

LSTM 的架构的神奇之处在于:在普通循环单元的顶部,增加了一个存储单元和三个信息门以处理长期依赖关系。 LSTM 的循环单元如下所示(我们还提出了一种原始用于比较):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MbbVIu0q-1681704851868)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/15d95e65-9990-4206-ba91-a6f18891d73f.png)]

在上图中从左到右,主要组成部分说明如下:

  • c[t]存储单元,它从输入序列的最开始就存储上下文。
  • f表示遗忘门,它控制来自前一存储状态c[t-1]的多少信息可以向前传递。 与遗忘门相关的权重包括W[f],它与先前的隐藏状态S[t-1]连接,和u[f],它与当前输入x[t]连接。
  • i代表输入门,它确定当前输入可以通过多少信息。 权重W[i]U[i]分别将其与先前的隐藏状态和当前输入相连。
  • tanh 只是隐藏状态的激活函数,并且基于当前输入x[t]和先前的隐藏状态s[t-1]及其相应的权重W[c]U[c]进行计算。 它与原始 RNN 中的a完全相同。
  • o用作输出门,它定义了将内部存储器中的多少信息用作整个循环单元的输出。 同样,W[o]U[o]是关联的权重。

因此,这些组件之间的关系可以概括如下:

  • 在时间步t处的遗忘门f的输出被计算为f = sigmoid(U[f] x[t] + W[f] s[t-1])
  • 将在时间步t处输入门i的输出计算为i = sigmoid(U[i] x[t] + W[i] s[t-1])
  • 将在时间步t处的 tanh 激活c'的输出计算为c' = sigmoid(U[c] x[t] + W[c] s[t-1])
  • 在时间步t处的输出门o的输出被计算为o = sigmoid(U[o] x[t] + W[o] s[t-1])
  • 在时间步t处的存储单元c[t]c[t] = f .* c[t-1] + i .* c'更新,其中.*表示逐元素乘法。 值得注意的是,fi中的 Sigmoid 函数将其输出转换为从01的范围,从而控制分别通过的先前存储器c[t - 1]和当前存储器输入c'的数据比例。
  • 最后,在时间步t的隐藏状态s[t]被更新为s[t] = o .* c[t]。 同样,o确定用作整个单元输出的更新存储单元c[t]的比例。

使用随时间的反向传播训练所有四组权重UW,这与原始 RNN 相同。 通过学习三个信息门的权重,网络显式地对长期依赖关系进行建模。 接下来,我们将利用 LSTM 架构并发明更强大的文本生成器。

Python 深度学习架构实用指南:第三、四、五部分(2)https://developer.aliyun.com/article/1427001

相关文章
|
机器学习/深度学习 数据可视化 定位技术
Python 深度学习第二版(GPT 重译)(四)(4)
Python 深度学习第二版(GPT 重译)(四)
14 3
|
机器学习/深度学习 算法 算法框架/工具
Python 深度学习第二版(GPT 重译)(四)(2)
Python 深度学习第二版(GPT 重译)(四)
26 2
|
机器学习/深度学习 存储 计算机视觉
Python 深度学习第二版(GPT 重译)(四)(1)
Python 深度学习第二版(GPT 重译)(四)
23 3
|
机器学习/深度学习 API 算法框架/工具
Python 深度学习第二版(GPT 重译)(三)(3)
Python 深度学习第二版(GPT 重译)(三)
17 2
|
机器学习/深度学习 监控 算法框架/工具
Python 深度学习第二版(GPT 重译)(三)(2)
Python 深度学习第二版(GPT 重译)(三)
31 1
|
机器学习/深度学习 TensorFlow API
Python 深度学习第二版(GPT 重译)(一)(4)
Python 深度学习第二版(GPT 重译)(一)
26 3
|
8天前
|
机器学习/深度学习 自然语言处理 算法框架/工具
用于NLP的Python:使用Keras进行深度学习文本生成
用于NLP的Python:使用Keras进行深度学习文本生成
20 2
|
2天前
|
机器学习/深度学习 边缘计算 监控
深度学习赋能智能监控:图像识别技术的革新与应用
【4月更文挑战第23天】 随着人工智能的迅猛发展,深度学习技术在图像处理领域取得突破性进展,特别是在智能监控系统中,基于深度学习的图像识别已成为提升系统智能化水平的核心动力。本文旨在探讨深度学习如何优化智能监控系统中的图像识别过程,提高监控效率和准确性,并分析其在不同应用场景下的具体实施策略。通过深入剖析关键技术、挑战及解决方案,本文为读者提供了一个关于深度学习图像识别技术在智能监控领域应用的全面视角。
|
2天前
|
机器学习/深度学习 存储 边缘计算
深度学习在图像识别中的应用与挑战
【4月更文挑战第23天】 随着人工智能技术的飞速发展,深度学习作为其重要分支之一,在图像识别领域取得了显著的成果。本文将探讨深度学习在图像识别中的应用,分析其优势和面临的挑战,并展望未来的发展趋势。
|
3天前
|
机器学习/深度学习 数据采集 自动驾驶
基于深度学习的图像识别技术在自动驾驶系统中的应用
【4月更文挑战第21天】 本文章深入探讨了深度学习技术在自动驾驶车辆图像识别领域的应用。不同于传统的摘要方式,本文将直接点出研究的核心价值和实际应用成果。我们专注于卷积神经网络(CNN)的创新设计,其在复杂道路场景下的行人和障碍物检测中的高效表现,以及这些技术如何整合到自动驾驶系统中以增强安全性和可靠性。通过实验验证,我们的模型在公开数据集上达到了行业领先水平的准确率,并且在真实世界的测试场景中展现了卓越的泛化能力。