如何在Python中使用TensorFlow 2和Keras构建文本生成器?

2021年11月11日17:44:54 发表评论 975 次浏览

使用循环神经网络 (RNN) 和 LSTM 以及 Python 中的 TensorFlow 和 Keras 框架构建深度学习模型以生成人类可读的文本。

Python如何构建文本生成器Recurrent Neural Networks(RNN多个)是分类问题非常强大的序列模型。然而,在本教程中,我们要做一些不同的事情,我们将使用RNN作为生成模型,这意味着它们可以学习问题的序列,然后为问题域生成全新的序列。

阅读本教程后,你将结合Python构建文本生成器示例,学习如何在 Python 中使用TensorFlowKeras构建可以生成文本(逐字符)的LSTM模型。

TensorFlow 2和Keras构建文本生成器:在文本生成中,我们向模型展示了许多训练示例,以便它可以学习输入和输出之间的模式。每个输入是一个字符序列,输出是下一个单个字符。例如,假设我们要训练句子"python is a great language",第一个样本的输入是"python is a great langua",输出将是"g"。第二个样本输入将是“ython is a great languag”,输出是“e”,依此类推,直到我们遍历整个数据集。我们需要向模型展示尽可能多的示例,以便做出合理的预测。

相关: 如何使用 Tensorflow 2 和 Keras 在 Python 中执行文本分类。

入门

让我们安装本教程所需的依赖项:

pip3 install tensorflow==2.0.1 numpy requests tqdm

导入所有内容:

import tensorflow as tf
import numpy as np
import os
import pickle
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM, Dropout
from string import punctuation

准备数据集

Python构建文本生成器示例:我们将使用一本可免费下载的书籍作为本教程的数据集:Lewis Carroll 的 Alice's Adventures in Wonderland。但是你可以使用任何你想要的书籍/语料库。

这些代码行将下载它并将其保存在一个文本文件中:

import requests
content = requests.get("http://www.gutenberg.org/cache/epub/11/pg11.txt").text
open("data/wonderland.txt", "w", encoding="utf-8").write(content)

只需确保你的当前目录中存在一个名为“data”的文件夹。

现在让我们定义我们的参数并尝试清理这个数据集:

sequence_length = 100
BATCH_SIZE = 128
EPOCHS = 30
# dataset file path
FILE_PATH = "data/wonderland.txt"
BASENAME = os.path.basename(FILE_PATH)
# read the data
text = open(FILE_PATH, encoding="utf-8").read()
# remove caps, comment this code if you want uppercase characters as well
text = text.lower()
# remove punctuation
text = text.translate(str.maketrans("", "", punctuation))

Python如何构建文本生成器?上面的代码通过删除大写字符和标点符号以及将两个连续的换行符替换为一个,从而减少了我们的词汇量,以便更好、更快地进行训练。如果你希望保留逗号、句点和冒号,只需定义你自己的punctuation字符串变量。

让我们打印一些关于数据集的统计信息:

# print some stats
n_chars = len(text)
vocab = ''.join(sorted(set(text)))
print("unique_chars:", vocab)
n_unique_chars = len(vocab)
print("Number of characters:", n_chars)
print("Number of unique characters:", n_unique_chars)

输出:

unique_chars:
 0123456789abcdefghijklmnopqrstuvwxyz
Number of characters: 154207
Number of unique characters: 39

复制现在我们成功加载并清理了数据集,我们需要一种将这些字符转换为整数的方法,有很多 Keras 和 Scikit-Learn 实用程序可以做到这一点,但我们将在 Python 中手动进行。

由于我们将vocab作为我们的词汇表,其中包含我们数据集的所有唯一字符,我们可以制作两个字典,将每个字符映射到一个整数,反之亦然:

# dictionary that converts characters to integers
char2int = {c: i for i, c in enumerate(vocab)}
# dictionary that converts integers to characters
int2char = {i: c for i, c in enumerate(vocab)}

让我们将它们保存到一个文件中(以便稍后在文本生成中检索它们):

# save these dictionaries for later generation
pickle.dump(char2int, open(f"{BASENAME}-char2int.pickle", "wb"))
pickle.dump(int2char, open(f"{BASENAME}-int2char.pickle", "wb"))

TensorFlow 2和Keras构建文本生成器:现在让我们对我们的数据集进行编码,换句话说,我们要将每个字符转换为其相应的整数:

# convert all text into integers
encoded_text = np.array([char2int[c] for c in text])

复制由于我们想为更大的数据集扩展我们的代码,我们需要使用tf.dataAPI来有效地处理数据集,因此,让我们tf.data.Dataset在这个encoded_text数组上创建一个对象:

# construct tf.data.Dataset object
char_dataset = tf.data.Dataset.from_tensor_slices(encoded_text)

太棒了,现在这个char_dataset对象有这个数据集的所有字符,让我们尝试打印第一个字符:

# print first 5 characters
for char in char_dataset.take(8):
    print(char.numpy(), int2char[char.numpy()])

这将取前8 个字符并将它们连同它们的整数表示一起打印出来:

38 
27 p
29 r
26 o
21 j
16 e
14 c

太好了,现在我们需要构建我们的序列,正如前面提到的,我们希望每个输入样本都是一个长度的字符序列,sequence_length并且输出是下一个字符的单个字符。幸运的是,我们必须使用tf.data.Dataset的batch()方法将字符收集在一起:

# build sequences by batching
sequences = char_dataset.batch(2*sequence_length + 1, drop_remainder=True)

# print sequences
for sequence in sequences.take(2):
    print(''.join([int2char[i] for i in sequence.numpy()]))

你可能会注意到,我使用了每个样本的2*sequence_length +1大小,你很快就会明白我为什么这样做,检查输出:

project gutenbergs alices adventures in wonderland by lewis carroll

this ebook is for the use of anyone anywhere at no cost and with
almost no restrictions whatsoever  you may copy it give it away or

reuse it under the terms of the project gutenberg license included
with this ebook or online at wwwgutenbergorg
...
<SNIPPED>

你注意到我已经使用int2char之前构建的字典将整数序列转换为普通文本。

现在你知道每个样本是如何表示的,让我们准备输入和目标,我们需要一种方法将单个样本(字符序列)转换为多个(输入,目标)样本。幸运的是,flat_map()方法正是我们所需要的,它需要一个回调函数来循环我们所有的数据样本:

def split_sample(sample):
    # example :
    # sequence_length is 10
    # sample is "python is a great pro" (21 length)
    # ds will equal to ('python is ', 'a') encoded as integers
    ds = tf.data.Dataset.from_tensors((sample[:sequence_length], sample[sequence_length]))
    for i in range(1, (len(sample)-1) // 2):
        # first (input_, target) will be ('ython is a', ' ')
        # second (input_, target) will be ('thon is a ', 'g')
        # third (input_, target) will be ('hon is a g', 'r')
        # and so on
        input_ = sample[i: i+sequence_length]
        target = sample[i+sequence_length]
        # extend the dataset with these samples by concatenate() method
        other_ds = tf.data.Dataset.from_tensors((input_, target))
        ds = ds.concatenate(other_ds)
    return ds

# prepare inputs and targets
dataset = sequences.flat_map(split_sample)

为了更好地理解上述代码的工作原理,让我们举个例子:假设我们有一个序列长度为 10(太小但很好解释),sample参数是一个21 个字符的序列(记住2*sequence_length+ 1 ) 以整数编码,为方便起见,让我们假设它没有编码,说它是“python is a great pro”。

现在我们要生成的第一个数据样本将是以下输入和目标元组('python is ', 'a'),第二个是('ython is a', ' '),第三个是('thon is a ', 'g')等。我们对所有样本都这样做,最后,我们会看到我们显着增加了训练样本的数量。我们使用ds.concatenate()方法将这些样本加在一起。

Python构建文本生成器示例 - 在我们构建了我们的样本之后,让我们对输入和标签(目标)进行单热编码

def one_hot_samples(input_, target):
    # onehot encode the inputs and the targets
    # Example:
    # if character 'd' is encoded as 3 and n_unique_chars = 5
    # result should be the vector: [0, 0, 0, 1, 0], since 'd' is the 4th character
    return tf.one_hot(input_, n_unique_chars), tf.one_hot(target, n_unique_chars)

我们使用方便的map()方法对数据集上的每个样本进行单热编码,tf.one_hot()方法完成了我们的预期。让我们尝试打印前两个数据样本及其形状:

# print first 2 samples
for element in dataset.take(2):
    print("Input:", ''.join([int2char[np.argmax(char_vector)] for char_vector in element[0].numpy()]))
    print("Target:", int2char[np.argmax(element[1].numpy())])
    print("Input shape:", element[0].shape)
    print("Target shape:", element[1].shape)
    print("="*50, "\n")

这是第二个元素的输出:

Input: project gutenbergs alices adventures in wonderland by lewis carroll

this ebook is for the use of an
Target: y
Input shape: (100, 39)
Target shape: (39,)

所以每个输入元素的形状为(序列长度,词汇大小),在这种情况下,有39 个唯一字符,100 个是序列长度。输出的形状是一个单热编码的一维向量。

注意:如果你使用不同的数据集和/或使用其他字符过滤机制,你将看到不同的词汇量,每个问题都有自己的领域。例如,我也用它来生成 Python 代码,它有 92 个唯一字符,那是因为我应该允许一些 Python 代码所需的标点符号。

最后,我们重复、洗牌和批处理我们的数据集:

# repeat, shuffle and batch the dataset
ds = dataset.repeat().shuffle(1024).batch(BATCH_SIZE, drop_remainder=True)

我们将可选设置为drop_remainder True以便我们可以消除大小小于 的剩余样本BATCH_SIZE

构建模型

现在让我们构建模型,它基本上有两个 LSTM 层,具有任意数量的128 个LSTM 单元。尝试尝试不同的模型架构,你可以随心所欲!

输出层是一个全连接层,有39 个单元,其中每个神经元对应一个字符(每个字符出现的概率)。

model = Sequential([
    LSTM(256, input_shape=(sequence_length, n_unique_chars), return_sequences=True),
    Dropout(0.3),
    LSTM(256),
    Dense(n_unique_chars, activation="softmax"),
])

我们在这里使用Adam 优化器,我建议你尝试不同的优化器。

建立模型后,让我们打印摘要并编译它:

# define the model path
model_weights_path = f"results/{BASENAME}-{sequence_length}.h5"
model.summary()
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

训练模型

TensorFlow 2和Keras构建文本生成器:现在让我们训练模型:

# make results folder if does not exist yet
if not os.path.isdir("results"):
    os.mkdir("results")
# train the model
model.fit(ds, steps_per_epoch=(len(encoded_text) - sequence_length) // BATCH_SIZE, epochs=EPOCHS)
# save the model
model.save(model_weights_path)

我们输入了我们之前准备的Dataset对象,由于该model对象不知道数据集中有很多样本,因此我们指定了steps_per_epoch参数,该参数设置为训练样本数除以批量大小。

运行上面的代码后,它应该开始训练,它看起来像这样:

Train for 6473 steps
...
<SNIPPED>

Epoch 29/30
6473/6473 [==============================] - 486s 75ms/step - loss: 0.8728 - accuracy: 0.7509
Epoch 30/30
2576/6473 [==========>...................] - ETA: 4:56 - loss: 0.8063 - accuracy: 0.7678

这将需要几个小时,具体取决于你的硬件,尝试将batch_size增加到256以加快训练速度。

训练结束后,结果文件夹中应该会出现一个新文件,即模型训练的权重。

生成新文本

Python如何构建文本生成器?有趣的部分来了,现在我们已经成功构建并训练了模型,我们如何生成新文本?

让我们导入必要的模块(如果你在单个笔记本上,则不必这样做):

import numpy as np
import pickle
import tqdm
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM, Dropout, Activation
import os

sequence_length = 100
# dataset file path
FILE_PATH = "data/wonderland.txt"
# FILE_PATH = "data/python_code.py"
BASENAME = os.path.basename(FILE_PATH)

我们需要一个示例文本来开始生成,这将取决于你的问题,你可以从训练数据中提取句子以使其表现更好,但我将尝试编写本书的新章节:

seed = "chapter xiii"

让我们加载字典,将每个整数映射到一个字符,反之亦然,我们之前在数据准备阶段保存过:

# load vocab dictionaries
char2int = pickle.load(open(f"{BASENAME}-char2int.pickle", "rb"))
int2char = pickle.load(open(f"{BASENAME}-int2char.pickle", "rb"))
vocab_size = len(char2int)

再次构建模型:

# building the model
model = Sequential([
    LSTM(256, input_shape=(sequence_length, vocab_size), return_sequences=True),
    Dropout(0.3),
    LSTM(256),
    Dense(vocab_size, activation="softmax"),
])

现在我们需要加载最佳模型权重集:

# load the optimal weights
model.load_weights(f"results/{BASENAME}-{sequence_length}.h5")

让我们开始生成:

s = seed
n_chars = 400
# generate 400 characters
generated = ""
for i in tqdm.tqdm(range(n_chars), "Generating text"):
    # make the input sequence
    X = np.zeros((1, sequence_length, vocab_size))
    for t, char in enumerate(seed):
        X[0, (sequence_length - len(seed)) + t, char2int[char]] = 1
    # predict the next character
    predicted = model.predict(X, verbose=0)[0]
    # converting the vector to an integer
    next_index = np.argmax(predicted)
    # converting the integer to a character
    next_char = int2char[next_index]
    # add the character to results
    generated += next_char
    # shift seed and the predicted character
    seed = seed[1:] + next_char

print("Seed:", s)
print("Generated text:")
print(generated)

我们在这里所做的只是从种子文本开始,构建输入序列,然后预测下一个字符。之后,我们通过移除第一个字符并添加预测的最后一个字符来移动输入序列。这给了我们一个稍微改变的输入序列,它的长度仍然等于我们序列长度的大小。

然后我们将这个更新的输入序列输入到模型中以预测另一个字符,重复这个过程N次将生成一个包含N 个字符的文本。

Python构建文本生成器示例 - 这是一个有趣的文本生成:

Seed: chapter xiii
Generated Text:
ded of and alice as it go on and the court
well you wont you wouldncopy thing
there was not a long to growing anxiously any only a low every cant
go on a litter which was proves of any only here and the things and the mort meding and the mort and alice was the things said to herself i cant remeran as if i can repeat eften to alice any of great offf its archive of and alice and a cancur as the mo

Python如何构建文本生成器?明明是英文啊!但正如你可能注意到的,大多数句子没有任何意义,这是由于多种原因。主要原因之一是数据集仅在很少的样本上进行训练。此外,模型架构不是最佳的,其他最先进的架构(例如GPT-2BERT)往往会大大优于这一架构。

但请注意,这不仅限于英文文本,你可以使用任何类型的文本。事实上,一旦拥有足够多的代码行,你甚至可以生成 Python 代码。

TensorFlow 2和Keras构建文本生成器总结

太好了,我们完成了。现在你知道如何:

  • 在 TensorFlow 和 Keras 中制作 RNN 作为生成模型。
  • 使用tf.data API清理文本和构建 TensorFlow 输入管道。
  • 在文本序列上训练 LSTM 网络。
  • 调整模型的性能。

为了进一步改进模型,你可以:

  • 通过删除稀有字符来减少词汇量。
  • 使用更多 LSTM 单元添加更多 LSTM 和Dropout 层,甚至添加双向层
  • 调整一些超参数,例如批量大小、优化器甚至sequence_length,看看哪个效果最好。
  • 训练更多的时代。
  • 使用更大的数据集。

在此处查看完整代码。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: