如何开发一个词语级的神经语言模型并使用它生成文本?

出品:贪心科技(公众号:贪心科技)

作者:Artem Oppermann(贪心科技编译)

字数:2300

阅读时长:5分钟

前言

在本教程中您将发现如何使用 Python 中的深层学习来开发统计语言模型。神经网络模型是开发统计语言模型的首选方法,因为这种模型可以使用分布表示的形式,在该形式中具有相似含义的不同词语具有相似的表示形式,并且它们可以使用大量的上下文进行训练预测。


完成本教程后您将知道:

  • 如何为基于单词的语言模型来编写文本

  • 如何设计并拟合一个具有嵌入学习以及有 LSTM 隐藏层的神经语言模型

  • 如何使用学习的语言模型生成具有相似统计属性的新文本作为源文本

我们开始吧!

教程概

本教程分为4部分它们是:

1. 柏拉图的The Republic

2. 数据准

3. 训练语言模

4. 使用语言模


柏拉图的The Republic

The Republic是希腊古典哲学家柏拉图最著名的著作

它的结构是一长段关于一个国家秩序和正义的对话。


文本从以下内容开始:

BOOK I.

I went down yesterday to the Piraeus with Glaucon the son of Ariston,

并以此作为结束:


And it shall be well with us both in this life and in the pilgrimage of a thousand years which we have been describing.

将已整理的版本保存为republic_clean.txt’并保存在您当前的工作目录中。该文件应有15802行文本

现在我们可以从这个文本中开发一个语言模型

数据准

我们将从准备数据建模开始

第一步是查看数据

查看文

在编辑器中打开文本然后查看文本数据

例如下面是第一段对话:

BOOK I 

I went down yesterday to the Piraeus with Glaucon the son of Ariston,
that I might offer up my prayers to the goddess (Bendis,the Thracian
Artemis.);and also because I wanted to see in what manner they would
celebrate the festival,which was a new thing. I was delighted with the
procession of the inhabitants;but that of the Thracians was equally,
if not more,beautiful. When we had finished our prayers and viewed the
spectacle,we turned in the direction of the city;and at that instant
Polemarchus the son of Cephalus chanced to catch sight of us from a
distance as we were starting on our way home,and told his servant to
run and bid us wait for him. The servant took hold of me by the cloak
behind,and said: Polemarchus desires you to wait.

I turned round,and asked him where his master was.

There he is,said the youth,coming after you,if you will only wait.

Certainly we will,said Glaucon;and in a few minutes Polemarchus
appeared,and with him Adeimantus,Glaucon’s brother,Niceratus the son
of Nicias,and several others who had been at the procession.

Polemarchus said to me: I perceive,Socrates,that you and your
companion are already on your way to the city.

You are not far wrong,I said.

在准备数据时我们需要处理哪些内容

以下是我迅速浏览后认为应该处理的:

  • 书籍/章节标题 ( "BOOK I.")

  • 英式英语拼写 ( "honoured")

  • 许多标点 (例如 "-"";-""-" )

  • 奇怪的名字 (例如 "Polemarchus")

  • 一些冗长的独白(持续上百行那种)

  • 某些引用的对话框 (例如 "...")

以上就是我认为我们应该为文本数据准备的内容

我们准备数据的具体方式取决于我们打算如何对其进行建模而这又取决于我们打算如何使用它

语言模型设

在本教程中我们将开发一个文本模型然后我们可以使用它来生成新的文本序列

语言模型将是统计相关的并将预测每个单词的概率。当给定一个输入序列的文本,被预测的词将反过来被送入作为输入进而产生下一个词

关键的设计步骤是输入序列的长度。因为需要很长一段时间来让模型学习上下文以便预测单词。此输入长度还将定义使用模型时用于生成新序列的种子文本的长度

实际上我们没有正确答案。只要有足够的时间和资源我们可以通过输入不同长度的输入序列来探索模型的能力

这次我们会选择长度为50字的输入序列

我们可以处理数据使模型只处理句子并合并截断文本以满足每个输入序列的这一要求。

为了保持示例简短我们将让所有文本一并输入并训练模型来预测文本中的句子、段落甚至书籍或章节中下一个单词

现在我们已经有了一个模型我们可以把原始文本转换成100输入单词的序列并输出1个输出词准备好拟合一个模型

加载文

第一步是将文本加载到内存中

我们可以开发一个小函数将整个文本文件加载到内存中并反馈。该函数称为load_doc () ,即下图所示。给定一个文件名它将返回一个加载完毕的文本序列

使用此函数我们可以在文件 "republic_clean中加载处理后的文本版本如下所示:

运行此代码段将加载文档并将前200个字符输出为健全检查

BOOK I.

I went down yesterday to the Piraeus with Glaucon the son of Ariston,
that I might offer up my prayers to the goddess (Bendis,the Thracian
Artemis.);and also because I wanted to see in what

目前为止,一切都好。接下来我们来清理文本

清理文本

我们需要将原始文本转换成一系列的标记或单词我们可以使用它作为训练模型的来源

根据对原始文本的审阅下面是一些执行清除文本特定的操作。您可能需要自己探索更多的清洁操作作为扩展

  • 用空格替换 "-",这样我们可以更好地拆分单词

  • 根据空格分割单词

  • 从单词中删除所有标点符号,以减少词汇量 (e.g. ‘What?’ 变成 ‘What’)

  • 删除不按字母顺序的单词以删除独立标点的重音标记

  • 将所有单词正常化为小写以减少词汇量

词汇量是语言建模的一个重要方面。更小的词汇量可以得到更小的模型以及更快的训练

我们可以在函数中按此顺序实现这些清理操作。下面是函数clean_doc () ,它将加载的文档作为参数并返回一个处理后的标记数组

我们可以在加载的文档上运行此清理操作并打印出一些标记和统计信息以作为详细的检查

首先我们可以看到一个漂亮的标记列表看起来比原始文本更干净。我们可以删除Book I‘ 章节标记或更多内容但这是一个很好的开始

我们也得到一些关于清理文件的统计数据

我们可以看到在清理后的文本的12万字中仅存在7500字的词汇量。这是很小的因此适合这些数据的模型是可操控的适度硬件

接下来我们可以将标记划分为序列并将它们保存到文件中

保存清理后的文本

我们可以将长的标记列表组织成50个输入词和1个输出词的序列

也就是说一个51 词的序列

我们可以通过迭代从标记51开始的标记列表并将前面的50个标记作为序列进行遍历然后将此过程重复到标记列表的末尾

我们将把标记转换为空格分隔的字符串以便以后在文件中存储

下面列出了将清理后的标记列表拆分为长度为51个标记的序列的代码

运行此片断会创建一长串的行

在列表上打印统计数据我们可以看到我们将有完全118633的培训模式以适应我们的模式

接下来我们可以将序列保存到一个新文件中以便以后加载

我们可以定义一个新的函数用于将文本行保存到文件中。此新函数称为save_doc () ,如下所示。它需要输入一组行和一个文件名。行以 ASCII 格式写入每行一次

我们可以调用此函数并将训练序列保存到文件 "republic_sequences

请使用文本编辑器查看该文件

你会发现每条线都沿着一个字移动并且在结尾处会有一个被预测的新词例如下面是截断形式的前3:

book i i … catch sight of
i i went … sight of us
i went down … of us from

完整示

将所有这些绑定在一起下面提供了完整的代码列表

现在您就在当前工作目录中得到了存储在"republic_sequences文件中的训练数据

接下来让我们来看看如何将语言模型应用于这些数据

培训语言模型

我们现在可以从准备好的数据中训练统计语言模型

我们要训练的模型是一种神经语言模型。它有几个独特的特点:

  • 它使用一个词的分布表示法使有相似的意思的不同词以同样的表示法表示

  • 它在学会表示法的同时学习模型

  • 它学会了用上下文中临近的100个单词的来预测下一个单词的概率

具体地说我们将使用嵌入层来学习单词的表示形式以及一个LSTM 递归神经网络来学习如何根据上下文来预测单词

让我们开始加载我们的训练数据

加载序

我们可以使用我们在上一节中开发的load_doc ()函数来加载我们的训练数据

加载后我们可以将数据分成不同的训练序列根据新的行进行拆分

下面的代码段将从当前工作目录中加载 "republic_sequences数据文件

接下来我们可以对训练数据进行编码

编码序

嵌入层要求输入序列由整数组成

我们可以将词汇表中的每个单词映射为一个唯一的整数并对输入序列进行编码。当我们进行预测时我们可以将预测转换为数字并在同一映射中查找它们的关联词

要执行此编码我们将在 Keras API 中使用 Tokenizer class (标记器)

首先标记器必须在整个训练数据集上进行培训这意味着它可以找到数据中的所有的唯一单词并为每个唯一的整数赋值

然后我们可以使用合适的标记器对所有训练序列进行编码将每个序列从单词列表转换为整数列表

我们可以将单词的映射作为标记器对象上称为 word_index 的字典属性来访问

我们需要知道以后定义嵌入层的词汇量的大小。我们可以通过计算映射字典的大小来确定词汇量

单词的值从1到总字数 (例如 7409)。嵌入层需要为这个词汇表中的每个单词分配一个向量表示形式从索引1到最大索引因为数组的索引是零偏移所以在词汇末尾的单词的索引将是 7409;这也意味着数组长度必须为 7409 + 1

因此在将词汇量指定给嵌入层时我们将其指定为比实际词汇量的加1的值

序列的输入和输

现在我们已经对输入序列进行了编码我们需要将它们分成输入 (X和输出 (y元素

我们可以使用阵列切片来完成此项

分离后我们需要一个热编码输出单词。这意味着将它从一个整数转换为值为0的向量词汇表中的每个单词都将有一个表示在单词整数值的索引处指定特定的单词

Keras 提供to_categorical () 函数该函数可用于对每个输入输出序列的输出词进行热编码

最后我们需要指定嵌入层输入序列的长度。我们之前指定为100这种方法可以使用第二维 (列数的输入数据的形状。这样如果在准备数据时更改序列的长度就不需要更改此数据的加载代码了,因为它是通用的

拟合模

现在我们可以在训练数据上定义和调整我们的语言模型

学习的嵌入需要知道词汇量的大小和输入序列的长度如前所述。它还有一个参数用于指定使用多少维度来表示每个单词。即嵌入向量空间的大小

一般的值为50100300。我们将在这里使用 50,但你也可以考虑测试较小或更大的值

我们将使用两个 LSTM 隐藏层每个层含有100个内存单元格。更多的内存单元以及更深的网络可以获得更好的结果

一个较为严密的完全连通层中含有100 个连接到 LSTM 隐藏层的神经元以解释从序列中提取的特征。输出层将下一个单词预测为一个向量该词的大小与词汇量的每一个单词的概率相同。一个softmax 激活函数用于保证输出值具有归一化概率的特征

定义的网络摘要输出一个详尽的检查以确保我们已经构建了我们的预期

接下来我们要对模型进行编译以指定适合模型所需分类的交叉熵损失。一般来说模型就是学习一个多种类的分类。采用高效的 Adam 实现小批量的梯度下降并对模型的精度进行评估

最后该模型适用于100个训练纪元的数据其数量适中批量为 128,并且加快了执行速度

没有 GPU的情况下,训练可能需要几个小时。您可以用更大的批次大小和/或更少的训练纪元来加速它

在培训过程中您将看到性能摘要包括每批更新结束时从训练数据中评估的损失和准确性

你会得到不同的结果并且得到的对下一单词的预测准确率为50%,这还算不错。我们的目标不是100% 的准确性而是一个捕捉文本内容的模型

保存模

在运行结束时已培训的模型将保存到文件中

在这里我们使用 Keras 模型 API 将模型保存到当前工作目录中的文件 "model. h5"

稍后当我们加载模型进行预测时我们还需要将单词映射到整数。这是在标记器中完成的如果使用Pickle我们可以省略该步骤

完整示

我们可以把这一切都放在一起下面列出了适合语言模型的完整示例

使用语言模

现在我们有了一个训练有素的语言模型我们可以使用它

在这种情况下我们可以使用它生成与源文本具有相同统计属性的新文本序列

这是不实际的至少对于这个例子来说但它给出了一个具体的例子一个语言模型已经学到的例子

我们将重新加载训练序列

加载数

我们可以使用上一节中的相同的代码来加载文本的训练数据序列

就是 load_doc ()函数

我们需要该文本以便我们可以选择一个源序列作为输入的模型来生成一个新的序列文本

该模型将需要100字作为输入

稍后我们将需要指定输入的预期长度。我们可以通过计算加载数据一行的长度来确定预期长度并在同一行的预期输出词中减去1

加载模

现在我们可以从文件加载模型

Keras 提供了用于加载模型的load_model ()函数可以随时使用

我们还可以使用Pickle API 从文件加载标记器

我们准备使用加载的模型

生成文

生成文本的第一步是准备种子的输入

为此我们将从输入文本中选择一条随机文本行。

接下来我们可以生成新单词一次一个

接下来种子文本必须用我们训练模型时使用的相同标记器编码到整数

模型可以直接通过调用predict_classes () 函数来预测下一个单词该值将返回具有最高概率的单词的索引

然后我们可以在 Tokenizers 映射中查找索引以获取关联的单词

接着我们可以将这个单词追加到种子文本中然后重复该过程

重要的是输入序列将会变得很长。在输入序列编码为整数之后我们可以将其截断为所需的长度。Keras 提供了可用于执行此截断的pad_sequences ()函数

我们可以将所有这些内容封装到一个名为generate_seq () 的函数中它作为输入模型、标记器、输入序列长度、种子文本以及要生成的单词数。然后返回由模型生成的单词序列

我们现在已经准备好生成一个新单词序列给出一些种子文本

把这些都放在一起下面列出了从学习语言模型中生成文本的完整代码列表

运行该示例首先打印种子文本

when he said that a man when he grows old may learn many things for he can no more learn much than he can run much youth is the time for any extraordinary toil of course and therefore calculation and geometry and all the other elements of instruction which are a

然后打印生成文本的50个字

preparation for dialectic should be presented to the name of idle spendthrifts of whom the other is the manifold and the unjust and is the best and the other which delighted to be the opening of the soul of the soul and the embroiderer will have to be said at

你会得到不同的结果。尝试运行这一代作品几次

你可以看到文本似乎是合理的。事实上添加串联将有助于解释种子和生成的文本。然而生成的文本在正确的顺序中得到了正确的单词类型


关于Jason Brownlee

Jason Brownlee博士是一个机器学习专家,他致力于教学开发者如何通过实际动手操作以获得现代机器学习的方法。


如果对本文有自己的见解,欢迎在评论区留言,   或者扫码关注公众号交流。





阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭