悟得笑 2018-03-09
代码即诗歌。这是WordPress软件的哲学。
作为一位程序员和诗人,我一直很喜欢这句话。我决定换种方式思考这句话。
我想问,我能用代码写诗吗?我可不可以做一个可以写出原创诗歌的机器人?为了找出方法,我做了一个实验。
首先,我知道如果我的机器人想写诗,首先要让它读诗。2017年,许多作者用WordPress发表了超过50万个标签为诗歌的帖子。我联系了一些写了许多诗的诗人,并问他们是否愿意和我一起进行一个有趣的实验:他们是否允许我的机器人读他们的作品,让它可以从中学习出诗歌的形式和结构,从而可能学会自己写诗?特别感谢这些为了科学而合作的很棒的作家们!
什么是LSTM,它如何生成文本?
我使用一种名为LSTM的神经网络创建我的机器人,它也叫作长短期记忆网络。神经网络使用“层”将问题分解为许多小问题。
举个例子,假设你正在训练一个可以识别正方形的神经网络。可能一个层负责识别直角,另一个层负责识别平行边。它们同时存在时图像才是正方形。神经网络通过训练数百万张正方形图像,从中学习到这些层。它可以学习到图像中的哪些方面对于识别正方形是重要的,哪些是不重要的。
现在假设你用一个神经网络预测这个序列的下一个字母:
作为一个普通人,这个任务真是太简单了。你会猜e,我打赌,如果你会说英文,你肯定不会猜q。因为你知道在英文中th后面不会接q。前面的字母和预测的接下来的字母关联性非常强。LSTM可以“记住”以前的状态并以此作出当前的决定。如果想深入了解LSTM是如何工作的,可以查看这个很赞的帖子,作者是谷歌大脑的Chris Olah。
与许多用LSTM生成文本的例子相同,机器人生成文本时每一时刻生成一个字符。想要把单词聚集成有意义的诗句,首先它要学会如何产生单词。为了达到这个目标,它需要数百万个包含有效单词的序列例子。有一件好事就是:WordPress有很多诗歌!
准备数据集
我从上面的链接中获得了所有的诗歌。我使用一个很简单的规则,通过判断每个字符\n对应多少个单词判断文本是否是诗歌。如果文本有许多单词但字符\n很少,它可能是一段或多段文字的集合。相反地,如果同样的文本有许多行,那么它是诗歌的可能性更大。
当然,这是一个很简单的方法,我能想出来很多好诗并不符合这样的测试方法!但为了这个实验的目的,我特别感兴趣的是LSTM是否可以学习出如断行和节奏等结构特点,以及诗歌中包含的押韵、谐音、头韵等特点。所以,把训练数据限制在有结构的诗歌中是有道理的。
如果一段文本被判断为一首诗,我把它写到一个文件中,用++++\n作为前缀,表示一首新诗歌的开始。最终获得了500KB的训练数据。通常,我尝试训练LSTM时都要用至少1MB的数据集,因此我需要寻找更多的诗歌!我在去年发表的标签为诗歌的公共帖子中随机选择样本作为特色诗人的补充。如果你在WordPress中点击过诗歌这一标签,你可能会发现你对它们很熟悉。我在每一个作者中选择一个帖子作为诗歌。
训练LSTM网络
当我获得1MB的诗歌时,我开始建立LSTM网络。我使用Python中的keras工具建立神经网络,keras的GitHub中有许多例子,可以帮助你学习使用几种不同类型的神经网络,其中有一个例子就是使用LSTM生成文本。我在这个例子后贴出了我的代码,并开始尝试不同的模型配置。模型的目标是生成原始的诗歌。在这个例子中,过拟合--将训练数据学习得太好以至于模型无法概括数据的特点--会使生成的文本和输入文本非常相似。(这就像剽窃一样,没有诗人喜欢这样!)避免过拟合的一种方式是在网络中添加dropout。它迫使在每一步骤中有随机权重下降到0。这有点像强迫网络“忘记”一些它刚刚学到的东西。(为了防止诗人的作品没有被机器人复制,我增加了额外的后期检查。)
我使用FloydHub的GPU完成训练我的网络这一繁重工作。这使我可以用比我的笔记本电脑快10倍的速度训练我的网络。我的第一个网络使用一个LSTM层,后面接入一个dropout层。这真的产生了像诗歌的文本!它有断行和阕,几乎所有的字符组合都是真正的单词。有时整句话略微一致,事实上,第一个迭代后产生了这句如宝石般精彩的话:
添加LSTM层,在每一层中试验dropout的参数,直到最终获得了下面的模型。最终选择使用3个LSTM层,因为再增加层数会让训练时间变得不合理,而且3层的结果已经很不错了。
model = Sequential() model.add(LSTM(300, input_shape=(maxlen, len(chars)), return_sequences=True, dropout= 20, recurrent_dropout=.20)) model.add(LSTM(300, return_sequences=True, dropout=.20, recurrent_dropout=.20)) model.add(LSTM(300, dropout=.20, recurrent_dropout=.20)) model.add(Dropout(.20)) model.add(Dense(len(chars))) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam')
这是一张图表,对比了增加不同LSTM层的模型对应的损失曲线。
哎呀!尖峰!表明这种情况通常发生在使用adam作为优化器时。注意到在网络中增加LSTM层数时,模型的验证损失下降得更多,而且下降的速率也更快。这意味着遍历更少的epoch就可以收敛,但增加LSTM层也增加了每一个epoch的训练时间。网络中有一层LSTM时,训练一个epoch需要大约600秒,全部训练结束需要一夜。然而,3个LSTM层的网络训练一个epoch需要7000秒,完成训练需要几天。因此,验证损失下降得更快并不意味着更快得到结果。但在我看来,即使训练时间很长,3个LSTM层的网络得到了最好的诗歌。
生成诗歌
为了产生完全原创的文本,也需要改变生成文本的方式。在keras的例子中,这个例子从训练数据中选择一个随机的字符序列作为种子,输入到训练好的网络中。我要的是一个可以自己写诗的机器人,而不是完成其他诗人的提示!因此,我在文本生成步骤中尝试了不同的种子。因为我之前在训练集中使用++++\n作为每首诗歌的开始,我认为它可以创作出原始的诗歌。然而结果是\n,_,.和&的无意义的组合。
经过一些尝试和失败之后,我发现种子序列需要与训练序列具有相同数量的字符,这在事后看起来是显而易见的!最终,我使用300个字符的序列,我将++++\n重复到300个字符作为种子,机器人可以通过偶尔将++++\n分开以此生成每一轮诗歌。
剧本产生新一轮诗歌后,进行了最后的抄袭检查。因此,首先在训练集中创建了所有独特的4-grams(包含4个词的短语)的集合,且对机器人诗歌创建相同集。计算两集之间的交集。为验证实验目的,手动检查了4-grams,以确保在两个集合中出现的短语为inane。通常情况下,这个交点包含如下内容:
然后重复这个过程,使用5-grams和6-grams进行良好测量。若要使该过程自动化,可能会采取一种基频法,排除在多个著作中常见的n-gram,认为是剽窃的情况。
诗歌!
每个时期输出模型权重意味着我们可以在训练期间的几个点上加载模型快照。回顾最后模型的早期时代,明显机器人掉线很快。期望可以将其用于设计上,训练数据最显著的特征是每行几个字符。下面是一个例子,训练结束后生成的诗歌:
它已经学到了一些实际的词语,并且模仿了每行之间空行的惯例。从远处看,如果你不仔细看,看起来的确像是一首诗!在单个LSTM模型的损失收敛之后,模型学习了断节和断行,甚至展示一些常见的诗歌性重复。
单一的LSTM模型的强大套装毫无疑问是个别性的。除了标题行之外,我喜欢的另一个是:
在Inspirobot热烈的精神之下,Demet从她最喜欢的一行诗创造了宝石:
单一的LSTM模型不能够精准地掌握诗歌主题,似乎在所有工作中一个共同的线索。即由单一LSTM模型生成的整个诗集词汇云。
迷人!机器人沉迷于太阳和星星。
若太阳成为训练数据中最普遍的话题,并不令人惊讶,但事实并非如此!这里有由训练数据生成的文字云。
诗人喜欢写爱情。
艾米莉狄金森写了关于自然和死亡的诗歌。机器人给个人写有关天体的诗!
添加第二个LSTM图层后,可以开始看到其他诗歌技巧,如头韵和韵律。
它也开始产生一些非常有诗意的诗歌。与之前模型训练的单行诗歌类似,有时不知一行。例如,
哇,那很深刻!
目前为止,已经看到了行,节,韵(内部和行结尾),重复和头韵。不错!但是,偶尔戏剧性的天赋,这时机器人模仿的诗歌通常是不连贯的词汇集。废话绝大部分都没有语法结构。
然而发生了一些变化即增加了第三个LSTM层。这种模式更有可能产生在语法上合理的单行诗,即使仍是荒谬的。例如:
这句诗没有任何意义,但却正确地放置了词性。具有一致性,名词从句具有一般的诗意。三层LSTM模型同样创作了这些,我认为是很稳定,诗情画意地讲:
但三层LSTM模式的最高成就正是这首完整的诗。
这不是大段文字的摘录。这些单行诗被牢牢地定位在两个++++\n分隔符之间。