LSTM生成尼采风格文章
LSTM生成文本
github地址
使用循环神经网络生成序列文本数据。循环神经网络可以用来生成音乐、图像作品、语音、对话系统对话等等。
如何生成序列数据?
深度学习中最常见的方法是训练一个网络模型(RNN或者CNN)通过之前的tokens预测下一个或者之后的几个token序列。通常在处理文本数据时,tokens通常是单词或字符,任何可以对给定前一个tokens时对下一个令牌的概率进行建模的网络模型称为语言模型。语言模型能捕捉语言的潜在空间特性:其统计结构特征。
一旦你有了这样一个训练有素的语言模型,你就可以从中进行采样(生成新的序列):你给它一个初始的文本字符串(称为条件数据),让它生成下一个字符或下一个字(你甚至可以一次生成几个tokens),将生成的输出添加回输入数据,并多次重复该过程(见图8.1)。
此循环允许生成任意长度的序列,这些序列反映了训练模型的数据结构:看起来几乎与人类书写句子相似的序列。
取样策略
生成文本时,选择下一个字符的方式至关重要。一种朴素的方法是贪婪采样--总是选择最可能的下一个字符。但是这种方法导致重复的,可预测的字符串看起来不连贯。一种更有趣的方法会产生更令人惊讶的选择:它通过从下一个字符的概率分布中抽样,在抽样过程中引入随机性。这称为随机抽样。注意,贪心采样也可以作为概率分布的采样:一个特定字符的概率为1而其他概率为0。
从模型的softmax输出中概率地采样是巧妙的:它允许在某些时候对不太可能的字符进行采样,产生更有趣的句子,并且有时通过提出在训练数据中未发生的新的,逼真的单词来显示模型创造力。但是这个策略存在一个问题:它没有提供一种控制采样过程中随机性的方法。
随机性的重要性。考虑一个极端情况:纯随机抽样,从均匀概率分布中绘制下一个字符,并且每个角色都具有相同的可能性。该方案具有最大随机性;换句话说,该概率分布具有最大熵。当然,它不会产生任何有趣的东西。在另一个极端,贪婪的采样也不会产生任何有趣的东西,并且没有随机性:相应的概率分布具有最小的熵。从“真实”概率分布中抽样(由模型的softmax函数输出的分布)构成这两个极端之间的中间点。但是,可能希望探索许多其他更高或更低熵的中间点。较少的熵将使生成的序列具有更可预测的结构(因此它们可能看起来更逼真),而更多的熵将导致更令人惊讶和创造性的序列。
当从生成模型中抽样时,在生成过程中探索不同量的随机性总是好的。因为我们是生成数据有趣程度的终极判断,所以相互作用是高度主观的,并且不可能事先知道最佳熵点在哪里。
为了控制采样过程中的随机性,我们将引入一个名为softmax temperature的参数,该参数表示用于采样的概率分布的熵:它表征下一个字符的选择将会出乎意料或可预测的程度。给定温度值,通过以下列方式对其进行重新加权,从原始概率分布(模型的softmax输出)计算新的概率分布。
import numpy as np
def reweight_distribution(original_distribution, temperature=0.5):
distribution = np.log(original_distribution) / temperature#原始分布为1D,和为1;
distribution = np.exp(distribution)
return distribution / np.sum(distribution)
较高的temperature导致较高熵的采样分布,这将产生更多令人惊讶和非结构化的生成数据,而较低的temperature将导致较少的随机性和更可预测的生成数据。
实现字符级LSTM文本生成
用Keras将这些想法付诸实践。第一件事是准备用来学习语言模型的大量文本数据。可以使用任何足够大的文本文件或一组文本文件---维基百科,指环王等等。在这个例子中,你将使用19世纪晚期德国哲学家尼采(Nietzsche)的一些著作(翻译成英文)。因此,将学习的语言模型将特别是尼采的写作风格和选择主题的模型,而不是更通用的英语模型。
准备数据
下载数据,转换成小写
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))
之后,将提取长度为maxlen的部分重叠序列,对它们进行one-hot编码,打包成3D numpy数组形式,形状(squences,maxlen,unique_characters).同时,将准备一个包含相应目标的数组y:每个提取序列之后的one-hot编码字符。
maxlen = 60#句子最大长度
step = 3#每3个字符对句子进行采样
sentences = []
next_chars = []#targets
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))
chars = sorted(list(set(text)))#字典
print('Unique characters:', len(chars))
char_indices = dict((char, chars.index(char)) for char in chars)#字符-id对应关系
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):#one-hot
for t, char in enumerate(sentence):
x[i, t, char_indices[char]] = 1
y[i, char_indices[next_chars[i]]] = 1
构建模型
LSTM+Dense;但是RNN并不是唯一生成序列文本的模型,1D卷积也可以达到相同的效果。
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)
语言模型训练、采样
给定训练有素的模型和种子文本片段,可以通过重复执行以下操作来生成新文本:
- 给定到目前为止生成的文本,从模型中绘制下一个字符的概率分布;
- 将分布重新调整到某个temperature;
- 根据重新加权的分布随机抽取下一个字符;
- 在可用文本的末尾添加新字符。
根据模型预测对下一个字符采样
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)#返回概率最大的字符下标
最后,以下循环重复训练并生成文本。可以在每个epochs之后使用一系列不同的temperature开始生成文本。这使可以了解生成的文本在模型开始收敛时如何演变,以及temperature对采样策略的影响。
import random
import sys
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 + '"')
for temperature in [0.2, 0.5, 1.0, 1.2]:#不同temperature生成文本对比
print('------ temperature:', temperature)
sys.stdout.write(generated_text)
for i in range(400):#从种子文本开始,生成400个字符
sampled = np.zeros((1, maxlen, len(chars)))#种子文本one-hot编码
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)#预测生成的字符
种子文本‘new faculty, and the jubilation reached its climax when kant.’;
epochs=20,temperature=0.2,生成文本序列:
new faculty, and the jubilation reached its climax when kant and such a man
in the same time the spirit of the surely and the such the such
as a man is the sunligh and subject the present to the superiority of the
special pain the most man and strange the subjection of the
special conscience the special and nature and such men the subjection of the
special men, the most surely the subjection of the special
intellect of the subjection of the same things and
temperature=0.5:
new faculty, and the jubilation reached its climax when kant in the eterned
and such man as it's also become himself the condition of the
experience of off the basis the superiory and the special morty of the
strength, in the langus, as which the same time life and "even who
discless the mankind, with a subject and fact all you have to be the stand
and lave no comes a troveration of the man and surely the
conscience the superiority, and when one must be w
epochs=60,模型开始收敛,生成文本看起来更有意义,temperature=0.2:
cheerfulness, friendliness and kindness of a heart are the sense of the
spirit is a man with the sense of the sense of the world of the
self-end and self-concerning the subjection of the strengthorixes--the
subjection of the subjection of the subjection of the
self-concerning the feelings in the superiority in the subjection of the
subjection of the spirit isn't to be a man of the sense of the
subjection and said to the strength of the sense of the
temperature=0.5:
cheerfulness, friendliness and kindness of a heart are the part of the soul
who have been the art of the philosophers, and which the one
won't say, which is it the higher the and with religion of the frences.
the life of the spirit among the most continuess of the
strengther of the sense the conscience of men of precisely before enough
presumption, and can mankind, and something the conceptions, the
subjection of the sense and suffering and the
正如所看到的,低temperature值导致极其重复且可预测的文本,但局部结构非常逼真:特别是,所有单词都是真正的英语单词。随着temperature的升高,生成的文本变得更有趣,令人惊讶,甚至创造性;它有时会发明一些听起来有些合理的新词(比如说eterned)。在高temperature下,局部结构开始分解,大多数单词看起来像半随机字符串。毫无疑问,0.5是这个特定设置中文本生成最有趣的temperature值。始终尝试多种采样策略!学习结构和随机性之间的巧妙平衡是让生成有趣的原因。
请注意,通过训练更大的模型,更长的数据,可以获得生成的样本,这些样本看起来比这个更连贯和更真实。当然,除了随机机会之外,不要期望生成任何有意义的文本:您所做的只是从统计模型中抽取数据,其中字符来自哪些字符。
语言是一种通信渠道,通信的内容与通信编码的消息的统计结构之间存在区别。
小结
- 可以通过训练模型来生成离散序列数据:预先给定前一个tokens生成下一个tokens;
- 文本生成模型成为语言模型,基于单词或字符;
- 对下一个tokens进行采样需要在遵守模型判断可能性和引入随机性之间取得平衡;
- 解决这个问题的一种方法是softmax temperature的概念。始终尝试不同的temperature,找到合适的temperature。