使用循环神经网络生成文本
介绍
文本生成是自然语言处理(NLP)的一个决定性方面,其中计算机算法试图理解以某种语言的自由形式提供的文本或尝试使用训练示例创建类似的文本。文本生成对于浅层学习技术来说一直非常困难,但深度学习算法,尤其是循环神经网络(RNN),在停滞了几十年之后,为该领域注入了新的活力。
在本指南中,我们将学习使用 Python 中的循环神经网络进行基本文本生成,并以约翰·弥尔顿的《失乐园》为例。这本书可以作为古腾堡计划的一部分免费找到,该计划收藏了一些世界文学经典。
循环神经网络 (RNN)
图像分类算法和结构化数据密集网络中使用的深度网络一次性获取所有数据,且不附带任何内存。它们本质上是前馈网络。整个数据集被转换为张量并馈送到网络,网络则调整其权重和偏差以满足训练数据并做出合理预测。
另一方面,RNN 会保留之前出现的序列的记忆,以便猜测序列中接下来会出现什么。原则上,这使得网络更能够适应依赖于先前数据的数据。例如,“正常”一词在不同上下文中可能具有不同含义。如果文本涉及样本和统计数据,则“正常”一词后面很可能跟着“分布”一词。同样,如果该词出现在具有几何参考的句子中,那么它可能意味着“垂直”。同样,如果“正常”一词出现在化学参考中,则后面很可能跟着解决方案。RNN 能够理解这些特定于上下文的细微差别并产生所需的结果。RNN 的变体之一是长短期记忆,我们将在本指南中使用它。
文本预处理
在将数据输入神经网络之前,需要对其进行合理的处理,以便深度学习的数学模型能够接受它。下面的代码块读取《失乐园》这本书并将所有字符转换为小写 ( text_1 )。完成后,它会对数据进行标记,以便为所有字符提供一个唯一的整数 ( char_index )。这些整数最终将以张量的形式输入到网络中。类似地,还会创建一个反向映射 ( index_char ),它将用于从输出层给出的整数创建文本。需要注意的一点是,我们使用的是字符级编码。另一种常用的技术是字编码。
import numpy as np
from tensorflow import keras
from keras_preprocessing.text import Tokenizer
tokenizer = Tokenizer(char_level=True)
filename = "D:\\book\\paradise_lost.txt"
# read the file and covert to lowercase
text_1 = open(filename, 'r').read().lower()
print('total number of characters in book: ', len(text_1))
# create mapping of unique chars to integers and reverse
tokenizer.fit_on_texts(text_1)
char_index = tokenizer.word_index
print('Found %s unique characters. ' % len(char_index))
print('char to integer dictionary: ',char_index)
index_char = dict(enumerate(char_index.keys()))
print('integer to char dictionary: ',index_char)
下面是上述代码的输出。值得注意的是,书中有 48 个独特字符,包括换行符和一些其他特殊字符。
输出
total number of characters in book: 460069
Found 48 unique characters.
char to integer dictionary: {' ': 1, 'e': 2, 't': 3, 'o': 4, 'a': 5, 'n': 6, 'h': 7, 's': 8, 'i': 9, 'r': 10, 'd': 11, 'l': 12, 'u': 13, '\n': 14, ',': 15, 'm': 16, 'w': 17, 'f': 18, 'c': 19, 'g': 20, 'p': 21, 'b': 22, 'y': 23, 'v': 24, ';': 25, 'k': 26, '.': 27, ':': 28, '-': 29, "'": 30, 'x': 31, 'j': 32, '?': 33, '!': 34, 'q': 35, 'z': 36, '"': 37, '(': 38, ')': 39, '0': 40, '1': 41, '9': 42, '6': 43, '8': 44, '4': 45, '5': 46, '7': 47, '3': 48}
integer to char dictionary: {0: ' ', 1: 'e', 2: 't', 3: 'o', 4: 'a', 5: 'n', 6: 'h', 7: 's', 8: 'i', 9: 'r', 10: 'd', 11: 'l', 12: 'u', 13: '\n', 14: ',', 15: 'm', 16: 'w', 17: 'f', 18: 'c', 19: 'g', 20: 'p', 21: 'b', 22: 'y', 23: 'v', 24: ';', 25: 'k', 26: '.', 27: ':', 28: '-', 29: "'", 30: 'x', 31: 'j', 32: '?', 33: '!', 34: 'q', 35: 'z', 36: '"', 37: '(', 38: ')', 39: '0', 40: '1', 41: '9', 42: '6', 43: '8', 44: '4', 45: '5', 46: '7', 47: '3'}
创建输入张量和输出向量
下一步是准备数据作为输入和输出集。我们将取长度为 5 的序列,然后将其用于预测下一个字符。
char_len = len(text_1)
seq_length = 5
data_X = []
data_y = []
for i in range(0, char_len - seq_length, 1):
input_seq = text_1[i:i + seq_length]
output_seq = text_1[i + seq_length]
data_X.append([char_index[char] for char in input_seq])
data_y.append(char_index[output_seq])
n_patterns = len(data_X)
print("Total Patterns: ", n_patterns)
print('###########print first 10 elements of list data_X###########')
print(data_X[:10])
print('###########print first 10 elements of list data_y###########')
print(data_y[:10])
输出
###########print first 10 elements of list data_X###########
[[21, 5, 10, 5, 11], [5, 10, 5, 11, 9], [10, 5, 11, 9, 8], [5, 11, 9, 8, 2], [11, 9, 8, 2, 1], [9, 8, 2, 1, 12], [8, 2, 1, 12, 4], [2, 1, 12, 4, 8], [1, 12, 4, 8, 3], [12, 4, 8, 3, 1]]
###########print first 10 elements of list data_y###########
[9, 8, 2, 1, 12, 4, 8, 3, 1, 22]
准备好输入和输出列表后,我们需要创建所需形状的 numpy 数组以输入到模型中。重塑完成后,需要通过将数组的每个元素除以书中唯一字符的数量(对于我们的示例,该值为 48)来对数组进行规范化。此外,y 向量是独热编码的。您可以在此处阅读有关独热编码的更多信息。
X = np.reshape(data_X, (n_patterns, seq_length, 1))
X = X / len(char_index)
print("###########first 3 elements of X###########")
print(X[:3])
y = keras.utils.to_categorical(data_y)
print("###########first 3 elements of y###########")
print(y[:3])
输出
###########first 3 elements of X###########
[[[0.4375 ]
[0.10416667]
[0.20833333]
[0.10416667]
[0.22916667]]
[[0.10416667]
[0.20833333]
[0.10416667]
[0.22916667]
[0.1875 ]]
[[0.20833333]
[0.10416667]
[0.22916667]
[0.1875 ]
[0.16666667]]]
###########first 3 elements of y###########
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0.]]
定义模型
一旦我们准备好输入和输出数据集,就该开始构建循环神经网络了。在本指南中,我们将使用长短期记忆 (LSTM)。您也可以使用门控循环单元 (GRU) 替代 LSTM。
model = keras.Sequential([
keras.layers.LSTM((256), return_sequences=False, input_shape=(X.shape[1], X.shape[2])),
keras.layers.Dropout(0.2),
keras.layers.Dense(y.shape[1], activation='softmax')
])
model.compile(optimizer='adam', loss='categorical_crossentropy')
print(model.summary())
输出
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
lstm (LSTM) (None, 256) 264192
_________________________________________________________________
dropout (Dropout) (None, 256) 0
_________________________________________________________________
dense (Dense) (None, 49) 12593
=================================================================
Total params: 276,785
Trainable params: 276,785
Non-trainable params: 0
_________________________________________________________________
None
拟合模型并保存
现在模型已准备好进行拟合。在开始拟合过程之前,最好保存每个时期给出的模型。由于 RNN 在 CPU 环境中计算量非常大且速度很慢,我们可以保存每个时期后给出的模型,并调整权重和偏差。保存的模型的名称可以包含损失值,以简化迄今为止获得的最佳模型的分离。
filepath="weights-improvement-{epoch}-{loss:.4f}.hdf5"
checkpoint = keras.callbacks.ModelCheckpoint(filepath, monitor='loss', verbose=1, save_best_only=True, mode='min')
callbacks_list = [checkpoint]
# fit the model
model.fit(X, y, epochs=30, batch_size=128, callbacks=callbacks_list)
我们正在构建的网络将进行 30 个时期的训练过程,批次大小为 128。这些超参数需要进行实验和调整,以根据开发人员掌握的资源获得最佳结果。
输出
Epoch 1/2
2019-11-26 09:04:32.661058: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
460032/460064 [============================>.] - ETA: 0s - loss: 2.9342
Epoch 00001: loss improved from inf to 2.93420, saving model to weights-improvement-01-2.9342.hdf5
460064/460064 [==============================] - 224s 487us/sample - loss: 2.9342
Epoch 2/2
460032/460064 [============================>.] - ETA: 0s - loss: 2.7909
Epoch 00002: loss improved from 2.93420 to 2.79091, saving model to weights-improvement-02-2.7909.hdf5
460064/460064 [==============================] - 215s 468us/sample - loss: 2.7909
加载模型和文本生成
以下程序用于加载已保存的模型,然后使用随机挑选的一百个字符的种子来预测五百个字符的文本。
import numpy as np
from tensorflow import keras
from keras_preprocessing.text import Tokenizer
tokenizer = Tokenizer(char_level=True)
filename = "D:\\book\\paradise_lost.txt"
# read the file and covert to lowercase
text_1 = open(filename, 'r').read().lower()
print('total number of characters in book: ', len(text_1))
# create mapping of unique chars to integers and reverse
tokenizer.fit_on_texts(text_1)
char_index = tokenizer.word_index
print('Found %s unique characters. ' % len(char_index))
print('char to integer dictionary: ',char_index)
index_char = dict(enumerate(char_index.keys()))
print('integer to char dictionary: ',index_char)
char_len = len(text_1)
seq_length = 5
data_X = []
data_y = []
for i in range(0, char_len - seq_length, 1):
input_seq = text_1[i:i + seq_length]
output_seq = text_1[i + seq_length]
data_X.append([char_index[char] for char in input_seq])
data_y.append(char_index[output_seq])
n_patterns = len(data_X)
X = np.reshape(data_X, (n_patterns, seq_length, 1))
X = X / len(char_index)
y = keras.utils.to_categorical(data_y)
model = keras.Sequential([
keras.layers.LSTM((256), return_sequences=False, input_shape=(X.shape[1], X.shape[2])),
keras.layers.Dropout(0.2),
keras.layers.Dense(y.shape[1], activation='softmax')
])
model.compile(optimizer='adam', loss='categorical_crossentropy')
# Loading the weights file
model.load_weights("C:\\guides\\weights-improvement-02-2.7909.hdf5")
txt_fl = []
print(len(data_X))
start = np.random.randint(50,100 )
print(start)
pattern = data_X[start]
print([''.join(index_char[value]) for value in pattern])
# generate characters
# generate characters
for i in range(1000):
x = np.reshape(pattern, (1, len(pattern), 1))
x = x / len(char_index)
prediction = model.predict(x, verbose=0)
index = np.argmax(prediction)
result = index_char[index].rstrip('\n\r')
seq_in = [index_char[value] for value in pattern]
#print(result)
txt_fl.append(result)
pattern.append(index)
pattern = pattern[1:len(pattern)]
print(''.join(txt_fl))
输出
red the saarof the siren of the siahe of heaven,and the siren oi his searen sored the soaeeof the siren of the siahe of heaven,and the siren oi his searen sored the soaeeof the siren of the siahe of heaven,and the siren oi his searen sored the soaeeof the siren of the siahe of heaven,and the siren oi his searen sored the soaeeof the siren of the siahe of heaven,and the siren oi his searen sored the soaeeof the siren of the siahe of heaven,and the siren oi his searen sored the soaeeo
虽然上面的文字看起来荒诞难懂,但它仍然可以推断出一些模式。它能够理解这些线条并创造文字,这本身就是一项成就。经过更高级的训练,它可以创造出更复杂的模式。
结论
应该记住,无论文本生成技术多么先进,它都无法理解人类对单词的含义。机器学习模型试图创建一个从可用训练数据中获取线索的数学模型。然后,它会尝试根据在训练过程中推断出的数学规则生成文本。此外,需要理解的是,RNN 的计算成本非常高。您试图推断的问题可能很快就会变得难以用 CPU 解决。RNN 最适合在图形处理单元 (GPU) 中运行。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~