在过去的几个月里,我在个人博客上写了100多篇文章。这是相当可观的内容量。我突然想到一个主意:
培养一个像我一样说话的语言生成模型。
或者更具体地说,像我这样写作。这是说明语言生成的主要概念、使用keras实现语言生成以及我的模型的局限性的完美方法。
本文的全部代码都可以在这个存储库中找到:
maelfabien/Machine_Learning_Tutorials
在我们开始之前,我发现这个Kaggle内核是理解语言生成算法结构的有用资源。
语言生成
自然语言生成是一个旨在生成有意义的自然语言的领域。
大多数情况下,内容是作为单个单词的序列生成的。总的来说,它的工作原理如下:
- 你训练一个模型来预测序列中的下一个单词
- 您给经过训练的模型一个输入
- 重复N次,生成下N个单词
- 创建数据集
第一步是构建一个数据集,这个数据集可以被我们稍后将要构建的网络所理解。首先导入以下软件包:
代码语言:javascript复制from keras.preprocessing.sequence import pad_sequences
from keras.layers import Embedding, LSTM, Dense, Dropout
from keras.preprocessing.text import Tokenizer
from keras.callbacks import EarlyStopping
from keras.models import Sequential
import keras.utils as ku
import pandas as pd
import numpy as np
import string, os
a.加载数据
我写的每一篇文章的标题都遵循这个模板:
这是我们通常不希望在最终数据集中拥有的内容类型。我们将转而关注文本本身。
所有文章都在一个单独的标记文件中编写。标题主要包含标题、图片标题等信息。
代码语言:javascript复制import glob, os
os.chdir("/MYFOLDER/maelfabien.github.io/_posts/")
首先,我们需要指向包含文章的文件夹,在我的目录中,名为“maelfabien.github.io”。
b.句子标记
然后,打开每一篇文章,并将每一篇文章的内容附加到列表中。但是,由于我们的目标是生成句子,而不是生成整篇文章,因此我们将把每一篇文章分成一个句子列表,并将每个句子添加到“all_sentences”列表中:
代码语言:javascript复制all_sentences= []
for file in glob.glob("*.md"):
f = open(file,'r')
txt = f.read().replace("n", " ")
try:
sent_text = nltk.sent_tokenize(''.join(txt.split("---")
[2]).strip())
for k in sent_text :
all_sentences.append(k)
except :
pass
总的来说,我们有超过6800个训练句子。目前的过程如下:
c.N-gram创建
然后,这个想法是创建n个一起出现的单词。为此,我们需要:
- 在语料库上安装一个标记赋予器,将一个索引与每个标记相关联
- 把语料库中的每个句子分解成一系列的标记
- 存储一起发生的令牌序列
可通过以下方式进行说明:
让我们来实现这个。我们首先需要安装标记器:
代码语言:javascript复制tokenizer = Tokenizer()
tokenizer.fit_on_texts(all_sentences)
total_words = len(tokenizer.word_index) 1
变量“total_words”包含已使用的不同单词的总数。这里是8976。然后,对于每个句子,获取相应的标记并生成n-grams:
代码语言:javascript复制input_sequences = []
# For each sentence
for sent in all_sentences:
# Get the corresponding token
token_list = tokenizer.texts_to_sequences([sent])[0]
# Create the corresponding n-grams
for i in range(1, len(token_list)):
n_gram_sequence = token_list[:i 1]
input_sequences.append(n_gram_sequence)
“token_list”变量将语句作为标记序列包含:
代码语言:javascript复制[656, 6, 3, 2284, 6, 3, 86, 1283, 640, 1193, 319]
[33, 6, 3345, 1007, 7, 388, 5, 2128, 1194, 62, 2731]
[7, 17, 152, 97, 1165, 1, 762, 1095, 1343, 4, 656]
然后,n-gram序列创建n-grams。它从前两个单词开始,然后逐渐添加单词:
代码语言:javascript复制[656, 6]
[656, 6, 3]
[656, 6, 3, 2284]
[656, 6, 3, 2284, 6]
[656, 6, 3, 2284, 6, 3]
...
d.填充
我们现在面临的问题是:不是所有的序列都有相同的长度!我们如何解决这个问题?
我们将使用填充物。paddings在变量“input_sequences”的每一行之前添加0的序列,这样每一行的长度与最长的行的长度相同。
为了将所有句子填充到句子的最大长度,我们必须首先找到最长的句子:
代码语言:javascript复制max_sequence_len = max([len(x) for x in input_sequences])
对我来说等于792。好吧,对于一个句子来说,这看起来相当大吧!由于我的博客包含了一些代码和教程,我希望这一句话实际上是由python代码编写的。让我们绘制序列长度的直方图:
代码语言:javascript复制import matplotlib.pyplot as plt
plt.figure(figsize=(12,8))
plt.hist([len(x) for x in input_sequences], bins=50)
plt.axvline(max_sequence_len, c="r")
plt.title("Sequence Length")
plt.show()
在一个序列中有200多个单词的例子确实很少。把最大序列长度设为200怎么样?
代码语言:javascript复制max_sequence_len = 200
input_sequences = np.array(pad_sequences(input_sequences,
maxlen=max_sequence_len, padding='pre'))
它返回如下内容:
代码语言:javascript复制array([[ 0, 0, 0, ..., 0, 656, 6],
[ 0, 0, 0, ..., 656, 6, 3],
[ 0, 0, 0, ..., 6, 3, 2284],
...,
e.拆分X和y
现在我们有固定长度的数组,它们中的大多数在实际序列之前都是0。那我们如何把它变成一个训练集?我们需要分开X和y!记住,我们的目标是预测序列的下一个单词。因此,我们必须把除最后一个外的所有标记作为X,并把最后一个作为y。
在Python中,就是这么简单:
代码语言:javascript复制X, y = input_sequences[:,:-1],input_sequences[:,-1]
我们现在将这个问题看作一个多类分类任务。像往常一样,我们必须首先对y进行热编码,以获得一个稀疏矩阵,该矩阵在对应于该标记的列中包含1,在其他位置包含0:
在python中,使用keras utils“to_categorical”:
代码语言:javascript复制y = ku.to_categorical(y, num_classes=total_words)
现在X的形状是(164496,199),y的形状是(164496,8976)。
我们有大约165,000个培训样本。X是199列宽,因为它对应于我们允许的最长序列(200 – 1,标签预测)。Y有8976列,对应于所有单词的稀疏矩阵。数据集现在已经准备好了!其余部分我们明天继续学习。