您的位置:首页 > 其它

基于 LSTM 的 Character-Level 文本生成模型

2018-03-06 13:24 423 查看

1. 序列数据的生成步骤

Deep learning 产生序列数据的方法一般是用 RNN 来预测下一个或者多个 token,比如说给定输入 “the cat is on the ma”,那么网络就会训练数据并且产生目标”t”,这里的 token 可以是一个词或者一个字符,任何 Deep Learning 的网络都可以给下一个 token 的概率建模,我们将这个网络训练出来的模型称为 “语言模型”。

语言模型训练完后,可以对它进行采样来生成新的序列数据:

给定一串字符,根据语言模型的分布,生成下一个字符或者单词,然后把生成的数据也作为输入,重复这个过程来生成数据。

使用 LSTM ,每次从语料中取出一串长度为 n 个字符的字符串,训练这串字符来产生第 n+1 个字符。然后将这个长度为 n+1 的字符串继续训练,产生第 n+2 个字符….

由于模型的输出是根据对所有字符的概率分布得到的,这个模型称为字符级神经语言模型 (character-level neural language model)。



2. 采样策略

采样通常使用两种策略,贪婪采样(Greedy sampling)和随机采样(Stochastic sampling)。

贪婪采样:选择最可能的下一个字符

直接从概率分布进行抽样:只有某个特定字符的概率为 1,其他字符概率都为 0

贪婪采样每次采样的值可能总是重复,导致句子不像连续、自然的语言

随机采样:通过概率分布来选择下一个字符

如果字符 e 的概率是0.3,那么 30% 的可能采样 e 字符

随机采样只能根据概率进行采样,无法控制生成的字符,但是可以产生更有意思的句子

如果说每个字符的概率分布相同,使用随机采样会导致熵最大,此时产生的句子易读性很差。因为熵越大,系统的混乱程度越高,生成的句子之中的字符随机性越强,构成的句子或者序列越混乱,比如 “onefsely dion and warkes!”。

如果不是随机抽样的字符,而是固定地采样,即使用贪婪采样,那么系统会有最小熵,也不能构成一个完整的句子。因为熵越小,系统的有序程度越高,那么生成的句子字符随机性越低,虽然句子更接近真实状态,但是重复性也会更高:比如 “the problem of the most power of the superiority of”。

最好的情况就是每次训练采用 softmax ,使用 LSTM 获取每个句子的信息来生成真实数据的概率分布,能够产生一个比较好的句子,兼顾随机性和固定性地进行采样。

在这里使用 Softmax Temperature 用于描述抽样值的概率分布的熵

当 temperatures 很大并且趋近于正无穷时,不同激活值对应的概率分布越接近

当 temperatures 很小且趋近于0时,不同激活值对应的概率分布差异也就越大



### Reweighting a probability distribution to a different "temperature"

import numpy as np

def reweight_distr
d354
ibution(original_distribution, temperature=0.5):
distribution = np.log(original_distribution) / temperature
distribution = np.exp(distribution)

# 通过上面的公式,加起来分布可能不为1
# 通过除以分布的和来重新计算新的分布
return distribution / np.sum(distribution)


也即:

temperatures 越高:概率分布越接近,熵越高,随机性越强,产生更多非结构化、随机的数据

temperatures 越低:概率分布差异性越大,熵越低,有序性越强,产生更多接近真实值的数据

3. Character-level LSTM 文本生成

这里训练集使用的是尼采的文章集合,LSTM 模型对训练集进行学习,并且学习尼采的写作风格。

数据预处理

下载数据并转换为小写格式:

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))


按照特定间隔,抽取训练集中相同长度的句子构建 train data 和 label data,并且将每个句子转换为 one-hot 词向量:

# Vectorizing sequences of characters

# 从文本中每次提取的序列长度
maxlen = 60

# 对新的序列按step进行字符采样
step = 3

# 保存抽取的序列
sentences = []

# 保存targets
next_chars = []

for i in range(0, len(text) - maxlen, step):
# 按照step的间隔,每次取出 maxlen 长度的序列作为 train data
sentences.append(text[i: i + maxlen])
# 取出序列的下一个字符作为 targets
next_chars.append(text[i + maxlen])
print('Number of sequences:', len(sentences))

# 语料的词汇表
chars = sorted(list(set(text)))
print('Unique characters:', len(sentences))
# 使用字典来映射 词汇char和索引index
char_indices = dict((char, chars.index(char)) for char in chars)

# 使用one-hot将句子表示为0/1数组
print('Vectorization...')
# x:训练样本
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
# y:标签样本
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         # 将训练样本转换为one-hot
y[i, char_indices[next_chars[i]]] = 1       # 将标签样本转换为one-hot


构建网络模型

采用 Units 为128 的单层 LSTM 进行训练,Dense 层用于全连接并遍历所有可能的字符,softmax 用于构建概率分布:

# A single-layer LSTM model for next-character prediction

from keras import layers

model = keras.models.Sequential()
# 模型添加输出为 128 个 units的 LSTM,输入为一个序列,每个序列都由词汇表的维度来表示
model.add(layers.LSTM(128, input_shape=(maxlen, len(chars))))
# 最终输出为词汇表维度的 softmax 向量
model.add(layers.Dense(len(chars), activation='softmax'))


与 one-hot 编码相关,损失函数选用 categorical_crossentropy 来训练模型:

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


训练语言模型并进行采样

在给定语言模型参数和文本片段的情况下,就能够不断产生新的字符文本,步骤如下:

从当前文本中提取出基于该句的字符概率分布

使用 Softmax 中的温度参数对字符概率分布重新加权

通过重新加权的概率分布对下一个字符进行采样

将采样到的字符添加到文本的末尾

重新加权字符概率分布,并抽取一个在分布中概率最大的字符返回:

# Function of sampling the next character given the model's predictions
def sample(preds, temperature=1.0):
# 使用 asarray 不会占用新的内存
preds = np.asarray(preds).astype('float64')
# 计算带有温度参数的 softmax
preds = np.log(preds) / temperature
exp_preds = np.exp(preds)
preds = exp_preds / np.sum(exp_preds)

# 返回多项式分布的概率,probas相加为 1,分布为 preds
probas = np.random.multionmial(1, preds, 1)
# 返回带有温度参数的 softmax 概率中的最大值的index
return np.argmax(probas)


重复训练并生成文本,并在每一个 epoch 后,使用不同的 temperature 来生成文本:

# The text generation loop
import random
import sys

for epoch in range(1, 60):
print('epoch', epoch)
# 训练模型
model.fit(x, y, batch_size=128, epochs=1)

# 每次从训练集中选择一段随机的文本,长度为 maxlen
start_index = random.randint(0, len(text) - maxlen - 1)
generated_text = text[start_idex: 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)

for i in range(400):
sampled = np.zeros((1, maxlen, len(chars)))
for t, char in enumerate(generated_text):
# 对每一个字符进行 one-hot 编码
sampled[0, t, char_indices[char]] = 1.

# 得到预测的分布值,是一个维度为词汇表长度的向量,里面的概率相加为 1
preds = model.predict(sampled, verbose=0)[0]
# 输出概率最大的字符相应的索引index
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()


在第 22 个 epoch 下,距离收敛还比较早,选择随机的文本:”, whose daily lives are not empty and colorless, should ber” 可以得到:

temperature = 0.2:

whose daily lives are not empty and colorless, should be
relation is the most have the superiority of the same as a man with the same the most souls of the problem of the contempt of the ascepticism and the most morality of the most contempt the same of the fact that is a complers of the sense of the same as a man with the power, the contempt and souls and souls the superiority of the problem of the most power of the superiority of the same of the proper


temperature = 0.5:

the most power of the superiority of the same of the property the standard and do the sense of a democratic and compless of the ascepticism has also the ancient rearing of the iduaded
for the uninterpretation of the even in the most souls of the allow to the problem of the spiriting and god, the entieral to us, it is so manifeststions and interpretuity, and because of the formulty of filled the spirit of every enough the words, it is not one's absolute an


temperature = 1.0:

pirit of every enough the words, it is not one's absolute and christians--men to met tooutter, as one may always to say to one's time, at
onefsely dion and warkes! the bost--the will from
divines a dreams
the cef,inth comacurid which is reductive anew, the philosophy. a body be
a zater sympathy for a volless in woftice in one is compledes with pelie as had
its idea is
they called
be arunges. every ancient own
proved, undout "good of still ortinged, now and


从上面可以看出:

temperature 低:则会出现重复率比较高的,但是部分结构比较真实的文本。

temperature 高:文本会比较有意思并且跳跃感比较强,可能也会发明一些新的词”cef”等,但是词的部分结构可能不完整,有的词看上去就跟单词的一半差不多

temperature=0.5:文本生成得比较好,在结构和随机性保持平衡的情况下,产生比较好的句子。

4. 总结

如果说数据集更多,epoch越多,网络更深更复杂的话,应该能学习到更好的概率分布,能够生成更好的句子,但是不能够具有语义的句子。语言是一种通信通道,我们产生语言用的编码系统和解码系统跟基于统计的规律并不一样,character-level 的句子生成都是从前一个字符的统计概率中得到的,没有加上语义信息。

后面可以考虑使用 Word2vec 尝试从 “word-level” 来训练模型。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: