英文文本的生成方法可以基于字符的粒度,也可以基于词汇的粒度,顾名思义,前者根据历史的字符预测出下一个字符,后者测根据词汇预测出下一个词汇。值得注意的是字符是有限的,单词是无线的,基于词汇粒度生成文本时,输入的数据肯定不能是词汇的onehot向量,最好是词嵌入向量。这里介绍基于字符粒度生成英文文本。
模型结构如上所示,语言模型根据初始文本预测下一个字符的分布,通过采样策略对下一个字符进行采样,采样得到的字符加入到输入中,再预测下一个字符的分布。
这种模型的为LSTM中的 N-1 模型,即输入 N 个字符,预测下一个字符的分布。
生成文本时,如何选择下一个字符至关重要,最简单的方法是贪婪采样,即选择预测概率最大的字符作为下一个输入字符,但这种方法容易得到重复的、 可预测的字符串, 看起来不像是连贯的语言。
还可以采用随机采样的方法,根据下一个字符的概率分布中进行采样,随机选择的缺点容易生成不存在的单词。
更好的办法是在随机采样中引入温度参数,用于表示采样概率分布的熵,温度越大,采样越随机,生成词汇的熵越大,温度越小,越偏向于贪婪采样,生成词汇的熵越小。
# 采样函数,根据温度值进行重新加权,值越大,熵越大
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)
采样函数如上所示,取对数后除于 temperature ,再求指数,温度值为1是完全随机采样,小于1时,概率更大的字符会得到更加大的采样概率。
导入包,下载数据,将字符小写
import keras
import numpy as np
path = keras.utils.get_file(
'nietzsche.txt',
origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt')
#path = './test.txt'
text = open(path,encoding='utf-8').read().lower()
print('Corpus length:', len(text))
max_len 为训练时句子的最大长度,step为文本处理时每个样本起始位置的间隔,sentences 是长度为 max_len 的输入数据,next_char为 sentences 的后一个字符,作为预测值。同时获得字符到索引的dict。
#将字符向量化
maxlen = 60
step = 3
sentences = []
next_chars = []
# 每3个字符重新取一个序列,每个序列60个字符
for i in range(0, len(text) - maxlen, step):
sentences.append(text[i: i+maxlen])
next_chars.append(text[i+maxlen])
print('Number of sentences:', len(sentences))
chars = sorted(list(set(text)))
print('Unique Char:', len(chars))
char_indices = dict((char,chars.index(char)) for char in chars)
将 sentences 和next_char转换成向量,这里转换成one-hot向量
# 对字符做one hot向量化
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
构建模型,模型由1层LSTM加1层Dense层组成,前者有128个神经元,后者激活函数选择 softmax,优化函数为 RMSprop,损失为交叉熵损失。同时定义采样函数
# 构建网络
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'))
# 优化函数
optimizer = keras.optimizers.RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)
# 采样函数,根据温度值进行重新加权,值越大,熵越大
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)
下面开始模型的训练,迭代60次,并查看不同温度抽样的生成效果,需要注意的是,由于输入文本有长度固定在max_len,加入新字符后需要舍去第一个字符,才可以再次作为模型输入数据。
import random
import sys
# 迭代60次
for epoch in range(1, 60):
print('epoch', epoch)
model.fit(x, y, batch_size=128, epochs=1) # 训练样本
start_index = random.randint(0, len(text) - maxlen - 1) # 生成开始文本
generated_text = text[start_index:start_index+maxlen]
print('--- Generating with seed: "' + generated_text + '"')
# 查看4种不同温度值的生成情况
for temperature in [0.2, 0.5, 1.0, 1.2]:
print('---temperature:',temperature)
sys.stdout.write(generated_text)
# 根据开始文本生成后 400个样本
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)
# 把生成的文本加到generated_text后面
next_char = chars[next_index]
generated_text += next_char
generated_text = generated_text[1:]
# 输出结果
sys.stdout.write(next_char)
model.save("./lstm_model.h5")
其实文本设置为 “without partiality for everything most abhorred is closel"”,生成文本如下所示:
—temperature: 0.2
without partiality for everything most abhorred is closely the standard of the standard of the spirit of the sense of the predication of the strength of the spirit of the constantially the more and the world of the standing and such a problem of the spirit of the moral and the more and the moral of the states of the probably and the more things and the more and the moral and the spirit of the experience of the sense of the more according to the
temperature 为 0.2 的生成情况一般,大部分单词拼写正常,除了“constantially”,语法上比较通顺,但是没有标点符号。
–temperature: 1.2
he youncifrig, totjection in liseral worrs, they were and origin–wesa–resperation. with some doys of , an find. are neverth follow therse what permiged thus from truth hard, too self? at frind; “short,” iumbilitubs and interpretateness magnluenism day for the tastes can be immemocious impigia’te vo to agreedeful howedlle midetely something and the wil knows thereforigment enteughaps.
temperature 为 1.2 的生成情况不太好,生成了更多不存在的单词,表达的内容很奇怪,但是生成了标点符号。
本问是一个简单的demo,文本生成的质量效果一般,怪异的单词,不对的语法很常见,与工程应用有很大的差距,但模型的设计流程,训练的方法都是差不多,采样方法的折中也值得学习。目前应该有更好的文本生成模型,这几天查查看,找到了再更新。
本文代码及内容参考《Python深度学习》第八章