前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >有了LSTM网络,我再也不怕老师让我写作文了

有了LSTM网络,我再也不怕老师让我写作文了

作者头像
望月从良
发布2019-03-04 15:13:53
7280
发布2019-03-04 15:13:53
举报
文章被收录于专栏:Coding迪斯尼Coding迪斯尼

随着深度学习的迅猛发展,人工智能的强大能力已经超出了模仿人类的简单动作,例如识别物体,如今已经能发展到自动驾驶,而且车开的比人都好的地步。目前深度学习进化出的一大功能是能够进行艺术创作,前几年google开发的DeepDream算法能够自己绘制出犹如毕加索抽象画般的艺术作品,而现在使用LSTM网络甚至可以开发出自动作曲程序,据说现在很多曲调都是由深度学习网络创作的。

很多艺术创作其实是通过序列号数据构成的,例如文章其实是一个个单词前后相邻构成,音乐是一个个音符前后相邻构成,甚至绘画也是笔触前后相邻构成,因此艺术创作从数学上看其实是时间序列数据,而LSTM忘了是最擅长处理时间序列数据的,因此只要我们训练网络识别相应艺术创作的时间序列中的数据规律,我们就可以利用网络进行相应的创作。

我们要创建的网络具有的功能是自动写作。我们把含有N个单词的句子输入网络,让网络预测第N+1个单词,然后把预测结果重新输入网络,让网络预测第N+2个单词,这种自我循环能让网络创作出跟人写出来几乎一模一样的句子。例如我们有句子”hello Tom, how are you”,我们把”hello Tom, how”输入网络后网络预测下个单词是”are”,然后我们继续把”hello Tom, how are”输入网络,网络预测下一个单词是”you”,网络运行的基本流程如下图:

上图中数据采样很重要,通常我们会从下一个可能单词的概率分布中,选择概率最大的那个单词,但是这么做会导致生成的句子不流畅,看起来不像人写得。通用做法是在可能性最高的若干个单词集合中进行一定随机选择。例如网络预测某个词的概率是30%,那么我们引入一种随机方法,使得该词被选中的概率是30%。

我们引入的随机方法,它的随机性必须要有所控制。如果随机性为0,那么最终网络创作的句子就没有一点创意,如果随机性太高,那么得到的句子在逻辑上可能就比较离谱,因此我们要把随机性控制在某个程度。于是我们引入一个控制随机性的参数叫temperature,也就是温度的意思。

在前面章节我们多次看到,当网络要给出概率时,最后输出层时softmax,它会输出一个向量,向量中每个分量的值是0到1间的小数,所有分量加总得1.我们假设这个向量用original_distributin表示,那么我们用下面的方法引入新的随机性:

代码语言:javascript
复制
def  reweight_distribution(original_distribution, temperature=0.5):
  distribution = np.log(original_distribution) / temperature
  distribution = np.exp(distribution)
  return distribution / np.sum(distribution)

上面代码会把网络softmax层输出的结果重新打乱,打乱的程度由tenperature来控制,它的值越大,打乱的程度就越高。接下来我们做一个LSTM网络,它预测的下一个元素是字符而不是我们前面所说的单词。

深度学习网络进行文章创作时,与用于输入它的文本数据相关。如果你用莎士比亚的作品作为训练数据,网络创作的文章与莎士比亚就很像,如果我们在上面函数中引入随机性,那么网络创作结果就会有一部分像莎士比亚,有一部分又不像,而不像的那部分就是网络创作的艺术性所在,下面我们用德国超人哲学创始人尼采的文章训练网络,让我们通过深度学习再造一个新的哲学家,首先我们要加载训练数据:

代码语言:javascript
复制
import  keras
import  numpy as np

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

上面代码运行后,我们会下载单词量为600893的文本数据。接着我们以60个字符为一个句子,第61个字符作为预测字符,也就是告诉网络看到这60个字符后你应该预测第61个字符,同时前后两个采样句子之间的间隔是3个字符:

代码语言:javascript
复制
maxlen = 60
step = 3
setences = []
#next_chars 对应下一个字符,以便用于训练网
next_chars = []

for i in range(0, len(text) - maxlen, step):
  setences.append(text[i : i + maxlen])
  next_chars.append(text[i + maxlen])

print('Number of sequentence: ', len(setences))

chars = sorted(list(set(text)))
print('Unique characters: ', len(chars))
#为每个字符做编号
char_indices = dict((char, chars.index(char)) for char in chars)
print('Vectorization....')
'''
整个文本中不同字符的个数为chars, 对于当个字符我们对他进行one-hot编码,
也就是构造一个含有chars个元素的向量,根据字符对于的编号,我们把向量的对应元素设置为1,
一个句子含有60个字符,因此一行句子对应一个二维句子(maxlen, chars),矩阵的行数是maxlen,列数
是chars
'''
x = np.zeros((len(setences), maxlen, len(chars)), dtype = np.bool)
y = np.zeros((len(setences), len(chars)), dtype = np.bool)

for i, setence in enumerate(setences):
  for t, char in enumerate(setence):
    x[i, t, char_indices[char]] = 1
  y[i, char_indices[next_chars[i]]] = 1

上面代码中构造的x就是输入数据,当输入句子是x时,我们要调教网络去预测下一个字符是y。代码先统计文本资料总共有多少个不同的字符,这些字符包含标点符号,根据运行结果显示,文本总共有57个不同字符,同时我们将不同字符进行编号。

然后构造含有57个元素的向量,当句子中某个字符出现时,我们就把向量中下标对应字符编号的元素设置为1,我们这些向量输入到网络进行训练:

代码语言:javascript
复制
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 = leras.optimizers.RMSprop(lr = 0.01)
model.compile(loss = 'categorical_crossentropy', optimizer = optimizer)

网络输出结果对应一个含有57个元素的向量,每个元素对应相应编号的字符,元素的值表示下一个字符是对应字符的概率。我们按照前面说过的方法对网络给出的概率分布引入随机性,然后选出下一个字符,把选出的字符添加到输入句子中形成新的输入句子传入到网络,让网络以同样的方法判断下一个字符:

代码语言:javascript
复制
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)
  '''
  由于preds含有57个元素,每个元素表示对应字符出现的概率,我们可以把这57个元素看成一个含有57面的骰子,
  骰子第i面出现的概率由preds[i]决定,然后我们模拟丢一次这个57面骰子,看看出现哪一面,这一面对应的字符作为
  网络预测的下一个字符
  '''
  probas = np.random.multinomial(1, preds, 1)
  return np.argmax(probas)

接着我们启动训练流程:

代码语言:javascript
复制
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]:
    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)
      next_char = chars[next_index]

      generated_text += next_char
      generated_text = generated_text[1:]

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

    print()

上面代码将尼采的作品输入到网络进行训练,训练后网络生成的段子就会带上明显的尼采风格,代码最好通过访问外国网站的方式,通过谷歌的colab,运行到GPU上,如果在CPU上运行,它训练的速度会非常慢。

我们看看经过20多次循环训练后,网络生成文章的效果如下:

输出中,Generating with seed 后面的语句是我们从原文任意位置摘出的60个字符。接下来的文字是网络自动生成的段子。当temperature值越小,网络生成的段子与原文就越相似,值越大,网络生成的段子与原文差异就越大,随着epoch数量越大,也就是网络训练次数越多,它生成的段子就越通顺,而且表达的内容也越有创意。

注意到随着temperature值越大,网络合成的词语错误也越多,有些单词甚至是几个字符的随机组合。从观察上来看,temperature取值0.5的效果是最好的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档