作者 | Fernando López
编译 | VK
来源 | Towards Data Science
❝“写作没有规定。有时它来得容易而且完美;有时就像在岩石上钻孔,然后用炸药把它炸开一样。”—欧内斯特·海明威
❞
本文的目的是解释如何通过实现基于LSTMs的强大体系结构来构建文本生成的端到端模型。
博客分为以下几个部分:
介绍
文本预处理
序列生成
模型体系结构
训练阶段
文本生成
完整代码请访问:https://github.com/FernandoLpz/Text-Generation-BiLSTM-PyTorch
介绍
多年来,人们提出了各种各样的建议来建模自然语言,但这是怎么回事呢?“建模自然语言”指的是什么?我们可以认为“建模自然语言”是指对构成语言的语义和语法进行推理,本质上是这样,但它更进一步。
目前,自然语言处理(NLP)领域通过不同的方法和技术处理不同的任务,即对语言进行推理、理解和建模。
自然语言处理(NLP)领域在过去的十年里发展非常迅速。许多模型都从不同的角度提出了解决不同NLP任务的方法。同样,最受欢迎的模型中的共同点是实施基于深度学习的模型。
如前所述,NLP领域解决了大量的问题,特别是在本博客中,我们将通过使用基于深度学习的模型来解决文本生成问题,例如循环神经网络LSTM和Bi-LSTM。同样,我们将使用当今最复杂的框架之一来开发深度学习模型,特别是我们将使用PyTorch的LSTMCell类来开发。
问题陈述
给定一个文本,神经网络将通过字符序列来学习给定文本的语义和句法。随后,将随机抽取一系列字符,并预测下一个字符。
文本预处理
首先,我们需要一个我们要处理的文本。有不同的资源可以在纯文本中找到不同的文本,我建议你看看Gutenberg项目(https://www.gutenberg.org/).。
在这个例子中,我将使用George Bird Grinnell的《Jack Among the Indians》这本书,你可以在这里找到:https://www.gutenberg.org/cache/epub/46205/pg46205.txt。所以,第一章的第一行是:
如你所见,文本包含大写、小写、换行符、标点符号等。建议你将文本调整为一种形式,使我们能够以更好的方式处理它,这主要降低我们将要开发的模型的复杂性。
我们要把每个字符转换成它的小写形式。另外,建议将文本作为一个字符列表来处理,也就是说,我们将使用一个字符列表,而不是使用“字符串”。将文本作为字符序列的目的是为了更好地处理生成的序列,这些序列将提供给模型(我们将在下一节中详细介绍)。
代码段1-预处理
如我们所见,在第2行我们定义了要使用的字符,所有其他符号都将被丢弃,我们只保留“空白”符号。
在第6行和第10行中,我们读取原始文件并将其转换为小写形式。
在第14行和第19行的循环中,我们创建了一个代表整本书的字符串,并生成了一个字符列表。在第23行中,我们通过只保留第2行定义的字母来过滤文本列表。
因此,一旦文本被加载和预处理,例如:
可以得到这样的字符列表:
我们已经有了全文作为字符列表。众所周知,我们不能将原始字符直接引入神经网络,我们需要一个数值表示,因此,我们需要将每个字符转换成一个数值表示。为此,我们将创建一个字典来帮助我们保存等价的“字符索引”和“索引字符”。
代码段2-字典创建
我们可以注意到,在第11行和第12行创建了“char-index”和index-char”字典。
到目前为止,我们已经演示了如何加载文本并以字符列表的形式保存它,我们还创建了两个字典来帮助我们对每个字符进行编码和解码。
序列生成
序列生成的方式完全取决于我们要实现的模型类型。如前所述,我们将使用LSTM类型的循环神经网络,它按顺序接收数据(时间步长)。
代码段3-序列生成
太棒了,现在我们知道如何预处理原始文本,如何将其转换为字符列表,以及如何以数字格式生成序列。现在我们来看看最有趣的部分,模型架构。
模型架构
正如你已经在这篇博客的标题中读到的,我们将使用Bi-LSTM循环神经网络和标准LSTM。本质上,我们使用这种类型的神经网络,因为它在处理顺序数据时具有巨大的潜力,例如文本类型的数据。同样,也有大量的文章提到使用基于循环神经网络的体系结构(例如RNN、LSTM、GRU、Bi-LSTM等)进行文本建模,特别是文本生成[1,2]。
❝所提出的神经网络结构由一个嵌入层、一个双LSTM层和一个LSTM层组成。紧接着,后一个LSTM连接到一个线性层。
❞
方法
Bi-LSTM和LSTM
标准LSTM和Bi-LSTM的关键区别在于Bi-LSTM由2个LSTM组成,通常称为“正向LSTM”和“反向LSTM”。基本上,正向LSTM以原始顺序接收序列,而反向LSTM接收序列。随后,根据要执行的操作,两个LSTMs的每个时间步的每个隐藏状态都可以连接起来,或者只对两个LSTMs的最后一个状态进行操作。在所提出的模型中,我们建议在每个时间步加入两个隐藏状态。
那么,首先让我们了解一下如何构造TextGenerator类的构造函数,让我们看看下面的代码片段:
代码段4-文本生成器类的构造函数
如我们所见,从第6行到第10行,我们定义了用于初始化神经网络每一层的参数。需要指出的是,input_size等于词汇表的大小(也就是说,我们的字典在预处理过程中生成的元素的数量)。同样,要预测的类的数量也与词汇表的大小相同,序列长度表示窗口的大小。
另一方面,在第20行和第21行中,我们定义了组成Bi-LSTM的两个「LSTMCells」 (向前和向后)。在第24行中,我们定义了LSTMCell,它将与「Bi-LSTM」的输出一起馈送。值得一提的是,隐藏状态的大小是Bi-LSTM的两倍,这是因为Bi-LSTM的输出是串联的。稍后在第27行定义线性层,稍后将由softmax函数过滤。
一旦定义了构造函数,我们需要为每个LSTM创建包含单元状态和隐藏状态的张量。因此,我们按如下方式进行:
代码片段5-权重初始化
一旦定义了包含隐藏状态和单元状态的张量,是时候展示整个体系结构的组装是如何完成的.
首先,让我们看一下下面的代码片段:
代码片段6-BiLSTM+LSTM+线性层
为了更好地理解,我们将用一些定义的值来解释程序,这样我们就可以理解每个张量是如何从一个层传递到另一个层的。所以假设我们有:
所以x输入张量将有一个形状:
然后,在第2行中,x张量通过嵌入层传递,因此输出将具有一个大小:
需要注意的是,在第5行中,我们正在reshape x_embedded 张量。这是因为我们需要将序列长度作为第一维,本质上是因为在Bi-LSTM中,我们将迭代每个序列,因此重塑后的张量将具有一个形状:
紧接着,在第7行和第8行定义了forward 和backward 列表。在那里我们将存储Bi-LSTM的隐藏状态。
所以是时候给Bi-LSTM输入数据了。首先,在第12行中,我们在向前LSTM上迭代,我们还保存每个时间步的隐藏状态(hs_forward)。在第19行中,我们迭代向后的LSTM,同时保存每个时间步的隐藏状态(hs_backward)。你可以注意到循环是以相同的顺序执行的,不同之处在于它是以相反的形式读取的。每个隐藏状态将具有以下形状:
很好,现在让我们看看如何为最新的LSTM层提供数据。为此,我们使用forward 和backward 列表。在第26行中,我们遍历与第27行级联的forward 和backward 对应的每个隐藏状态。需要注意的是,通过连接两个隐藏状态,张量的维数将增加2倍,即张量将具有以下形状:
最后,LSTM将返回大小为的隐藏状态:
最后,LSTM的最后一个隐藏状态将通过一个线性层,如第31行所示。因此,完整的forward函数显示在下面的代码片段中:
代码片段7-正向函数
到目前为止,我们已经知道如何使用PyTorch中的LSTMCell来组装神经网络。现在是时候看看我们如何进行训练阶段了,所以让我们继续下一节。
训练阶段
太好了,我们来训练了。为了执行训练,我们需要初始化模型和优化器,稍后我们需要为每个epoch 和每个mini-batch,所以让我们开始吧!
代码片段8-训练阶段
一旦模型被训练,我们将需要保存神经网络的权重,以便以后使用它们来生成文本。为此我们有两种选择,第一种是定义一个固定的时间段,然后保存权重,第二个是确定一个停止函数,以获得模型的最佳版本。在这个特殊情况下,我们将选择第一个选项。在对模型进行一定次数的训练后,我们将权重保存如下:
代码段9-权重保存
到目前为止,我们已经看到了如何训练文本生成器和如何保存权重,现在我们将进入这个博客的最后一部分,文本生成!
文本生成
我们已经到了博客的最后一部分,文本生成。为此,我们需要做两件事:第一件事是加载训练好的权重,第二件事是从序列集合中随机抽取一个样本作为模式,开始生成下一个字符。下面我们来看看下面的代码片段:
代码片段10-文本生成器
因此,通过在以下特征下训练模型:
我们可以生成以下内容:
正如我们看到的,生成的文本可能没有任何意义,但是有一些单词和短语似乎形成了一个想法,例如:
恭喜,我们已经到了博客的结尾!
结论
在本博客中,我们展示了如何使用PyTorch的LSTMCell建立一个用于文本生成的端到端模型,并实现了基于循环神经网络LSTM和Bi-LSTM的体系结构。
值得注意的是,建议的文本生成模型可以通过不同的方式进行改进。一些建议的想法是增加要训练的文本语料库的大小,增加epoch以及每个LSTM的隐藏层大小。另一方面,我们可以考虑一个基于卷积LSTM的有趣的架构。
参考引用
[1] LSTM vs. GRU vs. Bidirectional RNN for script generation(https://arxiv.org/pdf/1908.04332.pdf)