从零开始学keras之使用 LSTM 生成文本

下面用 Keras 来实现这些想法。首先需要可用于学习语言模型的大量文本数据。我们可以使用任意足够大的一个或多个文本文件——*、《指环王》等。本例将使用尼采的一些作品,他是 19 世纪末期的德国哲学家,这些作品已经被翻译成英文。因此,我们要学习的语言模型将是针对于尼采的写作风格和主题的模型,而不是关于英语的通用模型。

首先下载语料,并将其转换为小写。

import keras
import numpy as np
 path = keras.utils.get_file(
     'nietzsche.txt',
     origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt')
text = open(path).read().lower()
print('Corpus length:', len(text))

输出为Corpus length: 600893

接下来,我们要提取长度为 maxlen 的序列(这些序列之间存在部分重叠),对它们进行 one-hot 编码,然后将其打包成形状为 (sequences, maxlen,unique_characters) 的三维 Numpy 数组。与此同时,我们还需要准备一个数组 y,其中包含对应的目标,即在每一个所提取的序列之后出现的字符(已进行 one-hot 编码)。

# Length of extracted character sequences
# 提取 60 个字符组成的序列
maxlen = 60

# We sample a new sequence every `step` characters
# 每 3 个字符采样一个新序列 
step = 3

# This holds our extracted sequences(保存所提取的序列)
sentences = []

# This holds the targets (the follow-up characters)
# 保存目标(即下一个字符)
next_chars = []

for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_chars.append(text[i + maxlen])
print('Number of sequences:', len(sentences))

# List of unique characters in the corpus(语料中唯一字符组成的列表)
chars = sorted(list(set(text)))
print('Unique characters:', len(chars))
# Dictionary mapping unique characters to their index in `chars`
# 一个字典,将唯一字符映射为它在列表 chars 中的索引
char_indices = dict((char, chars.index(char)) for char in chars)

# Next, one-hot encode the characters into binary arrays.
# 将字符 one-hot 编码为 二进制数组
print('Vectorization...')
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence): 
        x[i, t, char_indices[char]] = 1
    y[i, char_indices[next_chars[i]]] = 1

输出为 Number of sequences: 200278
      Unique characters: 58
      Vectorization..

构建神经网络

这个网络是一个单层 LSTM,然后是一个 Dense 分类器和对所有可能字符的 softmax。但要注意,循环神经网络并不是序列数据生成的唯一方法,最近已经证明一维卷积神经网络也可以成功用于序列数据生成。

from keras import layers

model = keras.models.Sequential()
model.add(layers.LSTM(128, input_shape=(maxlen, len(chars))))
model.add(layers.Dense(len(chars), activation='softmax'))

目标是经过 one-hot 编码的,所以训练模型需要使用 categorical_crossentropy 作为损失。

optimizer = keras.optimizers.RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

训练语言模型并从中采样

给定一个训练好的模型和一个种子文本片段,我们可以通过重复以下操作来生成新的文本。

  • (1) 给定目前已生成的文本,从模型中得到下一个字符的概率分布。
  • (2) 根据某个温度对分布进行重新加权。
  • (3) 根据重新加权后的分布对下一个字符进行随机采样。
  • (4) 将新字符添加到文本末尾。

下列代码将对模型得到的原始概率分布进行重新加权,并从中抽取一个字符索引[采样函数(sampling function)]。

def sample(preds, temperature=1.0):
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / temperature
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)

最后,下面这个循环将反复训练并生成文本。在每轮过后都使用一系列不同的温度值来生成文本。这样我们可以看到,随着模型收敛,生成的文本如何变化,以及温度对采样策略的影响。

import random
import sys

for epoch in range(1, 60):
    print('epoch', epoch)
    # Fit the model for 1 epoch on the available training data
    model.fit(x, y,
              batch_size=128,
              epochs=1)

    # Select a text seed at random
    start_index = random.randint(0, len(text) - maxlen - 1)
    generated_text = text[start_index: start_index + maxlen]
    print('--- Generating with seed: "' + generated_text + '"')

    for temperature in [0.2, 0.5, 1.0, 1.2]:
        print('------ temperature:', temperature)
        sys.stdout.write(generated_text)

        # We generate 400 characters
        for i in range(400):
            sampled = np.zeros((1, maxlen, len(chars)))
            for t, char in enumerate(generated_text):
                sampled[0, t, char_indices[char]] = 1.

            preds = model.predict(sampled, verbose=0)[0]
            next_index = sample(preds, temperature)
            next_char = chars[next_index]

            generated_text += next_char
            generated_text = generated_text[1:]

            sys.stdout.write(next_char)
            sys.stdout.flush()
        print()

输出就不写出来了,生成许多段随机生成的对话。

由结果可见,较小的温度值会得到极端重复和可预测的文本,但局部结构是非常真实的,特别是 所有单词都是真正的英文单词(单词就是字符的局部模式)。随着温度值越来越大,生成的文本 也变得更有趣、更出人意料,甚至更有创造性,它有时会创造出全新的单词,听起来有几分可信(比 如 eterned 和 troveration)。对于较大的温度值,局部模式开始分解,大部分单词看起来像是半随 机的字符串。毫无疑问,在这个特定的设置下,0.5 的温度值生成的文本最为有趣。一定要尝试 多种采样策略!在学到的结构与随机性之间,巧妙的平衡能够让生成的序列非常有趣。

注意,利用更多的数据训练一个更大的模型,并且训练时间更长,生成的样本会比上面的结果看起来更连贯、更真实。但是,不要期待能够生成任何有意义的文本,除非是很偶然的情况。 你所做的只是从一个统计模型中对数据进行采样,这个模型是关于字符先后顺序的模型。语言是一种信息沟通渠道,信息的内容与信息编码的统计结构是有区别的。为了展示这种区别,我们来看一个思想实验:如果人类语言能够更好地压缩通信,就像计算机对大部分数字通信所做的那样,那么会发生什么?语言仍然很有意义,但不会具有任何内在的统计结构,所以不可能像刚才那样学习一个语言模型。

上一篇:1282. 翻转字符串中的元音字母


下一篇:day6流程控制