注意:对于中文汉字和日文汉字我根据具体情况交替使用它们。
更新:
宾夕法尼亚大学语言学院关于这项工作的讨论。
这是记录我的实验的博客文章系列中的第三篇文章,实验是基于TensorFlow的。为了能更好地理解,可以阅读第一篇关于混合密度网络(MDNs)的文章,以及第二篇结合MDN生成伪造手写体的例子来写的关于长短期记忆网络的文章,关于伪造手写体,Alex Graves有一篇经典论文《Generating Sequences With Recurrent Neural Networks》。
我们将修改并扩展Graves的方法,使 LSTM + MDN 能够生成矢量格式的伪造汉字。方法的主要区别在于除了现有的笔划结束状态之外,还添加了对汉字内容结束状态的建模,并对这些小概率的笔的状态应用梯度提升来改进。
作为一个在英语国家长大的小孩,小时候我会被父母逼着去上周六早上的课程,并在那里学习汉语。在课堂上会有听写测试,就是学生要凭记忆写出教科书上完整的课文。这使得我们间接的接触到了儒家的道德价值观念。在周末的晚上,我们不得不花费很多的时间记忆课文,来为周六的测试做准备。拿不到完美的分数会让我很不高兴。这样的事情持续了很多年。我直到现在还会做噩梦,梦到自己正在进行听写测试。我认为世界上大多数孩子都在通过这种死记硬背的方式学习中文。也许从某种意义上讲,汉语教育就像LSTM从训练集中训练造句一样。
从个人的经验来看,书写汉字与阅读汉字有很大的不同。几十年来,人们的汉字写作能力一直在下降。从日本开始,近几十年已经成为了普遍现象,现在可能就是亚洲其他地区,在这些地区,大多数人通过基于发音的输入法输入亚洲文本(日文使用假名,中文使用拼音),然后从屏幕上一堆可能的候选中选择所需的汉字。但是,当人们坐下来写一张新年贺卡邮寄给老师,却突然意识到他们忘了怎么写汉字的时候,问题就出现了。我也有这样的感觉 - 尽管我在日常生活中阅读了很多中文和日文的内容,但我还是很努力地写汉字。我注意到,虽然我们可以阅读和认识我们能够写的字符,但是反过来就不一定了。
同样在机器学习中,许多问题都从分类任务开始。这个数字是多少?这张照片是狗还是房子?这次交易是否存在欺诈?我们应该借钱给这个申请抵押贷款的家伙吗?男人还是女人?她几岁了?
虽然这些任务非常有用,但我认为一个更有趣的任务是生成数据,我认为这是数据分类的扩展。相比能够阅读某个汉字,能够书写某个汉字表明我们对这个汉字有更多的了解。同理,我认为能够生成内容也是理解这个内容的关键。能够生成一个22岁的漂亮女士的照片比仅仅能够估计这个女人可能在22岁左右更令人印象深刻。
一个生成任务的例子是能实时把英语翻译为另一种语言的翻译机器的开发。生成艺术和音乐越来越受人们的欢迎。最近,出现了一些新技术,如生成对抗网络(GANs)生成看起来像真的一样的伪造的位图图像,有猫的图片、人脸、卧室、甚至动漫人物,对我来说,这样的问题更加令人兴奋,可以说是分类问题的延伸。
然而,我更感兴趣的是生成矢量化内容的能力。我认为很多有用的内容最好的表示是用矢量格式表示,而不是栅格化的位图图像。例如,数字化素描,CAD设计,地理位置标记数据(如我们在Strava上骑自行车的地方),科学实验数据,当然还有手写体,用矢量化数据表达可能会更好。
我认为各种字体和手写体最好用矢量表示,而不是位图。作为一个对设计感兴趣的人,我有点像一个font geek(热衷于各种字体的怪胎),我非常喜欢精心设计的漂亮的TrueType字体,无论显示的尺寸如何,它们都很漂亮。我认为,通过数字化书写汉字记录下来的笔划更适合作为矢量数据来表示,而且在线手写汉字数据与离线数据相比,我更喜欢前者。
在这篇博客文章中,我将介绍如何训练一个循环神经网络,生成伪造的、但似是而非的svg格式的矢量中文汉字。为了训练神经网络,我们汉字笔顺数据库中的汉字的实例输入给神经网络,这样神经网络也需要写出一个有着合理笔划顺序的汉字。
对于日本文化而言,笔划顺序是非常重要的,在这个社会中,过程和结果一样重要。一些书法家对待笔划顺序很认真,如果看到有人用不正确的笔划顺序写汉字,可能会很生气。日本公司甚至创造了电子游戏,让人们学习汉字的正确笔划顺序,就像上图所示。只有矢量化的数据才能真正地模拟这个笔顺并且捕捉到汉字的纯粹本质,或许这是亚洲文化的一部分。栅格化的位图汉字数据相当于在东京发行两年后以低质量的RealMedia流文件格式观看热门动漫的英语配音。因此,我们希望我们的生成循环神经网络学习书写汉字,并尊重正确的笔顺。
我们的生成预测模型将使用和Graves的论文中描述的相同的框架,Graves在论文中展示了生成文本和生成手写体。Karpathy的博客和char-rnn
的实现有一些很好的例子来说明如何使用这个框架来生成以文本表示的数据。
在文本生成这个例子中,假设我们已经有一个预先训练好的模型,我们将一个初始的随机字符输入到初始状态为空的模型中。模型将使用状态信息和当前输入,为下一个字符生成一个概率分布。随机取样该分布(可能我们通过应用温度来扭曲采样过程),来获取下一个字符的预测信息。采样的字符和当前的内部状态一起作为下一个输入。
符合这个框架的一个简单模型是基本的N-GRAM字符建模方法。在N-GRAM中,我们所做的只是记录前N个字符的频率,并使用前边字符的频率表作为生成下一个字符的概率分布。
这个框架也可以用循环神经网络表示,其中状态是循环LSTM节点的隐藏状态,网络的输出值可以通过应用softmax层而被转换成离散概率分布。为了训练神经网络的权重,我们需要一种方法来比较预测分布与训练数据的实际分布。通常所做的是应用交叉熵(cross-entropy)损失函数来比较softmax层之后的模型预测概率与生成的整个序列的实际数据。
Karpathy的char-rnn
项目实现了Graves的论文中提到的上述网络。char-rnn
不仅可以用来生成莎士比亚的剧本,还可以用来生成一些特别的文本,包括Linux源代码,LaTex文档,维基百科的xml文档,以及音乐评分。
我想创建一个char-rnn
类似的工具,但是为了学习绘制素描,而不是学习生成字符序列。SVG数据很容易在网上找到,虽然不像文本数据那样容易获得。最后,我创建了一个名为的工具sketch-rnn
,试图从大量相关的.svg文件中学习到某种结构,并且能够生成和创建与训练集类似的新矢量化绘图。就像char-rnn
如何将特朗普的话生成特朗普的智慧一样,我想能够传入大量猫的.svg图片,并有一个算法来创建新的矢量化的猫的图片。要获得足够数量的猫的svg图片是很困难的,而获取.svg汉字文件则相当容易,所以我最后把这做成了一个生成伪造汉字手写体的实验。
sketch-rnn
和以前手写生成示例的博客帖子遵循相同的原理。每个图形都用类似笔划的数据建模,其中每一步数据都包含x和y轴的偏移量,以及笔是否落在纸上,并且如果笔落在纸上,还包含当前步骤和前一步骤的连线。神经网络会输出下一步的概率分布。不同于字符生成的例子,其中pdf对于每个可能的字符只是一堆离散的概率,而sketch-rnn
则需要用连续的概率分布统计横、纵方向的位移,以及在下一步中笔从纸上被提起的概率(这被称为笔划结束概率)。sketch-rnn
使用混合高斯分布(Mixture Gaussian Distribution)来估算下一笔的位移。这是一种被称为混合密度网络(Mixture Density Networks)的方法,在以前的博客文章中用来生成伪造手写体。
上面是一个使用混合高斯密度描绘汉字笔划的例子。连接起来的黑点代表笔划,LSTM和MDN算法将不断估算下一个点的位置的混合高斯概率分布。这意味着下一个位置取决于许多不同位置的混合(用不同浓度的红色椭圆表示)。每个位置本身都是x和y偏移的二维联合高斯分布,每个偏移都有一个2×2协方差矩阵。
除了笔划位置的概率分布和笔划结束概率外,我们还需要对完成这个汉字的概率建模(我们将这个概念称为字符结束概率,或内容结束概率,也记为“EOC”概率)。本来,我只是增加了另一个和笔划结束概率类似的变量,作为一个独立的随机变量去建模,但我发现实践效果不是很好。我们知道,因为每个字符结束事件(汉字写完)同时也是一个笔划结束事件(最后一个笔划写完),所以其中有一些共享信息被我们忽略了。
我花了一段时间想出了一个更好的方法来对上边说的两个信号(笔划结束概率、字符结束概率)进行建模,最后我所做的是通过神经网络中的softmax层将笔的状态建模为一组离散的状态。笔的状态分为三种:笔划完结,字符完结,落笔。并且对于每一步,模型都会计算这三个状态的概率。对比使用混合分布来模拟x和y的偏移,这似乎是一个非常优雅的方法,类似于使用char-rnn
来模拟三种笔的状态。
我使用了KanjiVG数据库的数据,该数据库源于开源日语学习工具Tagaini Jisho。正如前面提到的那样,写汉字时笔划的顺序很重要,即,使用不正确的笔顺顺序写汉字,到达最后一个字符的时候,它仍然是一个不正确的汉字。KanjiVG包含大约11000汉字的SVG文件,每个SVG文件的路径元素遵循日文汉字的官方笔顺。
在sketch-rnn
中的SketchLoader
类会读取数据子目录内的所有SVG文件,并将线条和路径元素转化为更小的线条,这些更小的线条更适合训练数据。关于粒度和路径到线条转换的具体细节,请参考代码。
下面是从KanjiVG中提取的训练字符的一些训练例子:
然后,SketchLoader
对象将会把从SVG文件中抽取的所有线条转存为一个由笔划数组构成的数组,并保存为一个cPickle二进制文件供以后训练使用。同时也会生成minibatch集合供以后训练使用。
最初的结果有点令人失望,尽管生成的每一笔看上去很真实,但笔会一直“写”下去而不会移动到下一个汉字上。我发现,在处理对数似然损失函数最小化的过程中,算法经常低估或者简单地忽略了字符结束的概率。我认为这是因为字符结束事件不经常发生,大约100步才发生1次。
在Graves的手写生成问题中,这个问题没有发生。因为英语笔迹训练数据不需要对结束训练的信号进行建模,可以让计算机一直“写”下去。但是在这个问题中,我们实际上需要对算法进行训练,需要确切地知道何时停止书写,来形成一个完整的汉字。
如果本节中介绍的方法未被使用,那么计算机运行的训练结果会是如下的状态:
为了解决这个问题,我设计了一种增加误差的方法。因此,在发生字符结束信号时,数据点的梯度也提升了,并相应地修改损失函数以包括这些增加的权重。
生成模型:
其中 x, y, m表示下一步的横、纵偏移量和笔的状态的随机值。x、y用MDN输出的混合二维高斯分布来建模,m用softmax的one-hot输出z_m来建模。
改进后的每一步梯度提升损失函数:
笔划结束点选择了10倍的权重因子,字符结束点选择了100倍的权重因子,以补偿这些事件的低概率。我发现这个方法是非常有效的,结合下一节提到的取样多样化和乱序技巧,最终的效果获得了显著地提升。
我很确信这之前已经有人在小概率事件上想到了这种加权提升的方法,因为这是一个非常明显的方法。我在网上查了一下,却找不到任何东西(最接近的是这个)。如果你找不到,那么你首先在这里听到了。
在每个训练阶段开始时,我们将训练数据中字符的顺序进行乱序处理。笔顺数据库将字符按照相似性分组,而我希望每一批字符能更有代表性。我想增加每个Minibatch的多样性,否则算法会花较长的时间学习鱼类汉字的书写(鱼,鲷,鲔),对世界的感知会因梯度而扭曲,认为所有的东西都是鱼,然后突然重新学习一下鸟这个汉字(鸟),就很抓狂。当它学习到一个终极角色: 鬱 的时候,它可能会觉得自己搞砸了并且很困惑。
还有一个要考虑的是,笔顺数据集只有大约10000个汉字的样本,我觉得我们需要制造更多自己造的汉字数据来训练。所以我从扭曲MNIST训练实例中学到了一些技巧,以创建更多的训练数据。每个Minibatch都随机缩放至【原始大小-30%,原始大小+30%】之间的任何位置。数据的结构(所有东西都是空间的偏移量)使得这个拉伸很容易实施,只需将整个矩阵乘上一个系数。我没有预先计算额外的例子并存储它们,所以只是增加训练阶段的次数来做这些事。我所能做的就是用不同的缩放因子来扭曲X轴和Y轴,这是我在编写时还没有完成的,但是用一个额外的代码行来修改这个工具是很容易的。
最后,每个Minibatch都从一个字符的开始处开始,而不是从中间开始,因为我希望这个算法能够学习完整的结构,而不是从基本模式(口,木,山之类)开始训练。所以下一个batch将跳到下一个字符样本的开始。因此,由于这种跳跃,每个训练阶段的Minibatch数可能不是完全相同的,但是都很接近的。
刚开始的实验相当令人沮丧,因为我只是乱七八糟的一直写着算法,像一个疯狂的疯子一样编织了一张大网。我花了一些时间才想到了使用softmax对笔画结束和字符结尾进行建模的想法,我也喝了很多咖啡才提出了梯度提升的想法。
我们提高小概率事件的梯度以及多样化的训练实例所用到的技巧,改进了实验结果的质量。尽管如此,最终的结果偶尔还是会有一些不好的结果。最后我加了一个过滤器,丢弃了那些超出书写区域大小的结果,并让它们重新开始,直到结果位于指定区域内。
我在sketch-rnn
的github库中加入了一个较小的已经预先训练好的神经网络,如果你愿意的话,可以在自己的机器上通过python sample.py
运行sketch-rnn
。
这个较小的预先训练好的神经网络每步产生24个高斯混合分布,并使用2层256个LSTM节点,在每层的输出处的dropout保持80%的概率。
我将数据缩小了15倍。这是一个有趣的问题,因为典型的训练示例在每个轴上的尺寸大约为80到160个单位。我发现一个很管用的经验,就是缩小数据,使数据的平均维数约为10×10,而且通常对于汉字而言,每个成功的步骤的偏移大约为1×1尺寸。
使用50-100个样本的Minibatch似乎效果很好。我设置了一个初始相对较大的学习率,并在每个后续的训练阶段,学习率按1%的比例下降。有时学习率太高会导致训练冲突,而冲突的那部分训练与估计字符结束的似然有关。当我们需要使用上述梯度提升方法来估计小概率事件的概率,并且这可能导致数值不稳定时,这就有点棘手了。
我对结果很满意。sketch-rnn
能够生成各种各样的不存在的汉字,但却有那么点像书写汉字应该有的方式。汉字的许多基本部分被配置在形成汉字结构方面有意义的位置。这就像一个小孩在努力通过汉语听写测试,并试图通过拼命填写答案来完成这个测试。
下面的其他样本我无法描述,你可以吗?
一些样本让我想起一些广东话里的亵渎用语变成了新的汉字(如𨳊或撚)。
我了解了CASIA在线手写数据库。这个算法可以很容易地应用到这个数据集上,并且可能训练出用语生成伪造的中文草书手写体的循环神经网络。就我个人而言,我不觉得这个基于笔画的数据集有趣(可能觉得草书的连笔和英文手写体有相似的地方),因为我想知道这个算法是否能够生成字和字比较分明的和现有汉字不同的结构,而不是像以前的手写示例那样的手写字符。
廣 -> 広 -> 广
作为一个设计师,我不太喜欢1956年“ 简化中文”,因为我觉得中华人民共和国将汉字简化过头了,有点奥威尔新语的味道。我比较喜欢原始的美丽的繁体中文字符。
看到它从数据中学习后所绘制的,我想将sketch-rnn
应用到TU Berlin素描数据应该会很有趣。但是我觉得这样做不会那么顺利,因为素描的种类是相当多样的,而且在统计学上并不具有相似结构。如果我们有10000个房子的素描样本,并且这些样本所有的笔画,复杂度和维度相似,那么它可能效果不错。但是,如果我们有一个桌子、兔、鱼、苹果和建筑物的数据库,这样我觉得这个模型可能应付不了。采取现有的算法,将光栅化的位图图像转换为矢量化的.svg格式并在sketch-rnn
上运行,这也是个有趣的主意。
LSTM+MDN基本上是LSTM+Softmax的扩展,我之后想尝试更强大的方法。最近关于变分自动编码器、生成式矩匹配网络或者BPL的工作可能会更加富有表现力、拥有更强大的功能。我也考虑到GAN(生成对抗网络)方法或许可以应用在循环神经网络上,不过训练LSTM GAN估计将是非常困难的。
我也思考过使用更小的网络来获得更具生成力的效果。最终,我希望能够在浏览器中使用这些训练好的神经网络,并让客户端的JS来运行demo,这些demo可以实时与用户进行交互,我认为这会非常酷。如果有人对如何将LSTM神经网络有效地压缩成小的JSON文件有任何想法,我们可以来交流一下。