【Tensorflow】使用字符RNN生成莎士比亚文本

使用字符RNN生成莎士比亚文本

从创建数据集开始,逐步研究如何构建Char-RNN。

创建训练数据集

  • 首先下载莎士比亚的所有作品,并从Andrej Karpathy的Char-RNN项目中下载数据:
shakespeare_url = "https://homl.info/shakespeare" # shortcut URL
lepath = keras.utils.get_file("shakespeare.txt", shakespeare_url)
with open(filepath) as f:
    shakespeare_text = f.read()
  • 接下来,将每个字符编码为整数。一种选择是创建自定义的预处理层。首先,我们需要为文本添加一个分词器:它会找到文本中使用的所有字符,并将它们映射到不同的字符ID,从1到不同字符的数量(它不从0开始,所以我们可以使用该值进行屏蔽):
    tokenizer = keras.preprocessing.text.Tokenizer(char_level=True)
    tokenizer.fit_on_texts([shapespeare_text])
  • 现在,分词器可以将一个句子(或句子列表)编码为字符ID列表并返回:
    在这里插入图片描述
  • 对全文进行编码,以便每个字符都由其ID表示(我们减去1即可得到0-38的ID):
[encoded] = np.array(tokenizer.texts_to_sequences([shakespeare_text]))-1

如何拆分顺序数据集

  • 我们使用文本的前90%作为训练集(其余部分保留为验证集和测试集),并创建一个tf.data.Dataset,它将从该集合中逐个返回每个字符:
dataset_size = encoded.shape[0]
train_size = dataset_size * 90 // 100
dataset = tf.data.Dataset.from_tensor_slices(encoded[:train_size])

将顺序数据集切成多个窗口

  • 使用数据集的window()方法将长字符序列转换成许多较小的文本窗口。数据集中的每个实例,将是整个文本的很短的子字符串,这称为时间截断反向传播。调用window()方法来创建短文本窗口的数据集:
n_steps = 100
window_length = n_steps + 1 # target = input shifted 1 character ahead
dataset = dataset.window(window_length, shift=1, drop_remainder=True)

使用shift=1,第一个窗口包含0-100,第二个窗口包含1-101;设置drop_remainder=True,确保所有窗口正好是101个字符长度。

  • Window()方法创建一个包含窗口的数据集,类似于列表的列表。但我们不能直接使用嵌套数据集进行训练,因为模型希望输入张量,而不是数据集。因此,我们调用 flat_map()方法:它将嵌套的数据集转换为一个展平的数据集:
dataset = dataset.flat_map(lambda window: window.batch(window_length))
  • 由于当训练集中的实例独立且分布相同时,梯度下降效果最好,因此我们需要对这些窗口进行混洗。然后,我们可以批处理这些窗口,并将输入(前100个字符)与目标(最后一个字符)分开:
batch_size = 32
dataset = dataset.shuffle(10000).batch(batch_size)
dataset = dataset.map(lambda windows: (windows[:, :-1], windows[:, 1:]))

在这里插入图片描述

  • 使用独热向量对每个字符进行编码:
dataset = dataset.map(lambda X_batch, Y_batch: (tf.one_hot(X_batch, depth=max_id), Y_batch))
  • 添加预取:
dataset = dataset.prefetch(1)

创建和训练Char-RNN模型

  • 使用有2个GRU层的RNN,每个GRU层有128个单元,输入(drop out)和隐藏状态(recurrent_dropout)的dropout率均为 20%。输出层是一个时间分布的Dense层,该层要有39个单元,将softmax应用于Dense层的输出。然后,使用“sparse_categorical_crossentropy”损失和Adam优化器来编译此模型。
model = keras.models.Sequential([
    keras.layers.GRU(128, return_sequences=True, input_shape=[None, max_id],
                    dropout=0.2, recurrent_dropout=0.2),
    keras.layers.GRU(128, return_sequences=True,
                    dropout=0.2, recurrent_dropout=0.2),
    keras.layers.TimeDistributed(keras.layers.Dense(max_id,
                                                   activation="softmax"))
])
model.compile(loss="sparse_categorical_crossentropy", optimizer="adam")
history = model.fit(dataset, epochs=20)

使用Char-RNN模型

X_new = preprocess(["How are yo"])
Y_pred = model.predict_classes(X_new)
tokenizer.sequences_to_texts(Y_pred + 1)[0][-1] # 1st sentence. last char

生成假莎士比亚文本

  • 使用TensorFlow的 tf.random.categorical() 函数估计出来的概率随机选择下一个字符。给定类对数概率(logits),categotical() 函数会对随机类索引进行采样。为了更好地控制生成的文本的多样性,可以把logits除以temperature:接近0的温度倾向于高概率的字符,而非常高的温度会给予所有的字符相同的概率。
def next_char(text, temperature=1):
    X_new = preprocess([text])
    y_proba = model.predict(X_new)[0, -1:, :]
    rescaled_logits = tf.math.log(y_proba) /temperature
    char_id = tf.random.categorical(rescaled_logits, num_samples=1) + 1
    return tokenizer.sequences_to_texts(char_id.numpy())[0]
  • 反复调用next_char() 来获得下一个字符,并将其添加到给定的文本中:
def complete_text(text, n_chars=50, temperature=1):
    for _ in range(n_chars):
        text += next_char(text, temperature)
    return text

有状态RNN

  • 告诉RNN在处理一个训练批次后保留此最终状态,并将其用作下一个训练批次的初始状态。尽管反向传播只是通过短序列,模型仍可以学习长期模式,这称为有状态RNN。
  • 只有当批次中的每个输入序列,均从上一个批次中对应序列中断的确切位置开始时,有状态RNN才有意义。因此,创建有状态RNN需要使用顺序和非重合的输入序列。因此,在调用window()方法时需要使用shift=n_steps。
  • 但是,批处理要比无状态RNN困难得多,第一批包含窗口1-32,第二批包含窗口33-64,考虑每个批次的第一个窗口,是不连续的。解决办法是只使用包含单个窗口的批处理:
dataset = tf.data.Dataset.from_tensor_slices(encoded[:train_size])
dataset = dataset.window(window_length, shift=n_steps, drop_remainder=True)
dataset = dataset.flat_map(lambda window: window.batch(window_length))
dataset = dataset.batch(1)
dataset = dataset.map(lambda windows: (windows[:, :-1], windows[:, 1:]))
dataset = dataset.map(lambda X_batch, Y_batch: (tf.one_hot(X_batch, depth=max_id), Y_batch))
dataset = dataset.prefetch(1)
  • 批处理比较困难,但并非不可能。可以将莎士比亚的文本切成等长的32个文本,为每个文本创建一个连续输入序列的数据集。
    在这里插入图片描述
  • 创建有状态RNN,在创建每个循环层时需要设置stateful=True,在第一层中设置batch_input_shape参数。
model = keras.models.Sequential([
    keras.layers.GRU(128, return_sequences=True, stateful=True,
                    dropout=0.2, recurrent_dropout=0.2,
                    batch_input_shape=[batch_size, None, max_id]),
    keras.layers.GRU(128, return_sequences=True, stateful=True,
                    dropout=0.2, recurrent_dropout=0.2),
    keras.layers.TimeDistributed(keras.layers.Dense(max_id,
                                                   activation="softmax"))
])
  • 在每个轮次结束时,需要先重置状态,然后再返回文本的开头。为此可以使用一个小的回调函数:
class ResetStatesCallback(keras.callbacks.Callback):
    def on_epoch_begin(self, epoch, logs):
        self.model.reset_states()
model.compile(loss="sparse_categorical_crossentropy", optimizer="adam")
model.fit(dataset, epochs=50, callbacks=[ResetStatesCallback])
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值