程序员想搞机器学习?看看Nodejs之父这一年摸爬滚打的历程
导读:本文是Nodejs之父Ryan Dahl在Google Brain做了一年深度学习后的心得体会,他在那里的目标是用机器学习来卓别林的老电影自动修改到4K画质。他的新项目成果几何?Nodejs之父的机器学习心得又是什么?下面让我们直接来看文章:
去年,在研究TensorFlow做出一番成果后,我开始申请Google Brain的首届见习项目,最后竟然成功了。受邀参加该项目的共有24人,每人都有着不同的机器学习背景,我们需要在Google位于山景城的深度学习研究实验室工作一年,跟Google的科学家和工程师们共同来推进该技术上的前沿研究。
如今,这个为期一年的项目刚刚结束,我就在这里总结一下自己的心得体会。
我最初的目标是改进老电影或电视剧的画面:想象一下,画面粗糙的90年代电视剧,或是60年代黑白电影,要是能被修正为色彩华丽的4K画面,那样的观影体验该有多棒!
而这事看上去完全可行:我们很轻松就能把4K视频转换成满是颗粒感的、低分辨率的、甚至是只有黑白两色的视频,那只要训练出某个监督模型来反转这个过程就可以了。而且,可用的训练数据无穷无尽。咳咳,这个想法简直太彪悍了!
但也别高兴太早——现在的技术还没到这步……但是快了!
带着这样的目标,我再一次从纽约布鲁克林搬到旧金山湾区(上一次是为Node.js项目),以更好地实现这里的深度学习技术。几天后,我生活的日常,就变成了跟Google的深度学习专家进行讨论、在Google庞大的软件库内浏览代码……
如果你不想看接下来的技术细节,可以直接跳到总结部分。
超分辨率的像素递归
众所周知,FBI在《犯罪现场调查》(CSI)中所用的缩放技术是不可能实现的。没人能任意放大照片。然而,在你放大照片图像时把相关像素所构成的合理图形呈现出来,这还是有可能做到的。能够平滑地提升图像分辨率,将是我实现目标的第一步。
该问题在本文中用 超分辨率 一词来描述,很久以前人们就在尝试解决它了。
据此,我们认识到简单使用ConvNet无法彻底解决该问题:它只是把你输入的低分辨率图像的像素间距(L2)最小化来输出高分辨率图像。这类损失函数所学到的,是输出所有可能结果的平均值——所输出的图像看上去就比较模糊了。我们想要的模型是这样的:对于给定的低分辨率图像,它能从所有可能的强化结果中选出那张特定的、效果最好的高分辨率图像。如果是“强化”一张关于树的模糊照片,我们会希望它能给出枝、叶在位置上的一些细节,即便它们的位置并非是枝、叶在树上的实际位置。
某种条件型的GAN(生成式对抗网络)看上去很有希望,但构建起来较难,经过几次失败的尝试后,我们换成了另一种新型的生产式模型:PixelCNN,它也比较有戏。(等我们启动之后,用GAN解决来超分辨率问题的SRGAN就发布了,它生成的结果非常好。)
PixelCNN是一种奇怪的反直觉模型。它将图像生成问题重写成每次选择一个像素序列。像LSTM(长短时记忆网络)这样的门控制递归网络在序列生成方面是非常成功的,它通常会用在单词或字符上。PixelCNN巧妙地构建出一个卷积神经网络(CNN),它能基于先前的像素的概率分布来精确生成像素。这是RNN和CNN的混合功能。
示意图由 van den Oord 等人所绘
意外的是,PixelCNN所生成的图像看起来非常自然。不像艰难平衡两种目标的对抗网络,该模型的目标只有一个,因而面对超参数的变化,它有更好的稳健性。也就是说,它更易于优化。
解决超分辨率问题的首次尝试,我野心过大,选用了ImageNet来训练PixelCNN。(跟CIFAR-10、CelebA或LSUN相比,ImageNet是个较难的数据集,很多生成式模型研究都在用它。)但很显然,按像素来序列生成图像的过程极其缓慢。输出图像的尺寸大于64x64时,耗时将超过数小时!然而,在我把图像的尺寸限制到小尺寸,并使用脸部或卧室类的小型数据集后,得出的结果就开始令人激动了。
图1:用名人脸部图像数据集训练出来的超分辨率像素递归模型所生成的高分辨率图像。左侧为测试数据集所用的8x8低分辨率输入图像。中间为PixelCNN模型所输出的32x32高分辨率图像,右侧是原始的32x32分辨率图像。我们的模型优先整合脸部特征,而后去合成较为逼真的头发与皮肤方面的细节。
由于在Google可以获取到无穷的计算资源,如何扩大训练的规模便成为该项目的另一个目标——因为即便采用这些小型的数据集,在单个CPU上完成训练也要花上数周的时间。
异步随机梯度下降算法(Asynchronous SGD)是最理想的分布式训练方法。使用这种方法,你用N台机器独立来训练同以个模型,但每个时间步长都要共享一次权重参数。权重参数被托管在一台单独的“参数服务器”上,该服务器在每个时间步长内都进行远程过程调用(RPC),以获得最新数值并发送梯度更新。如果数据管道足够好,你就可以线性增加模型每秒内的训练步数,方法是增加机器——因为机器之间互不依赖。然而,当机器增加时,由于老机器更新了权重,新机器的权重会逐步过期或“落伍”。在分类网络中,这里的问题不大,把训练的规模扩增到几十台机器不难。但PixelCNN却对过时的梯度极其敏感,在它的异步随机梯度下降算法内增加机器几乎没有任何收益。
另一个方法,是用同步随机梯度下降算法(Synchronous SGD)。使用这一方法,机器在每个时间步长内都进行同步,且每台机器的梯度都会被平均。它与随机梯度下降算法在数学上是相同的。更多的机器会增大批尺寸。但同步随机梯度下降算法(Sync SGD)允许各机器使用更小、更快的批尺寸,从而来增加每秒的步数(steps/sec)。然而,同步随机梯度下降算法也有它自己的问题。首先,它需要大量的机器经常进行同步,这必然导致停机时间的增加。其次,除非将每台机器的批尺寸设为1,否则它无法通过增加机器来增加每秒训练的步数。最终,我发现简单的设置是用一台机器安装8个GPU来使用同步随机梯度下降算法——但完成训练仍需花上数天的时间。
采用大规模计算的另一个办法,是进行规模更大的超参数搜索。如何来确定所用的批尺寸?把它们全都试一遍。在找到论文中所用的配置前,我尝试过数百种配置。
如何定量评估结果,则是另外一个难题。如何才能证明我们的图像比基准模型好?衡量超分辨率质量的典型方法,是对比强化图像与原始图像的对应像素点之间的距离(峰值信噪比,PSNR)。虽说本模型输出的脸部图像在质量上明显更好,但在像素对比上,平均看来它们还不如基准模型所输出的模糊图像。我们尝试用PixelCNN本身的相似度测量来证明我们的样本比基准版本有着更高的概率值,但同样失败了。最后,我们把这项任务众包给人类评估员——询问他们哪些图像看上去更真实。这倒奏效了。
具体的结果请查看这篇论文:超分辨率的像素递归:https://arxiv.org/abs/1702.00783
PixColor: 关于着色的尝试
PixColor输出的双色模式
Slim的创造者Sergio Guadarrama一直在尝试给图像着色。他跟我说过一个试验:用分量接口(该接口中图像的灰度、颜色相互分离)获取一张224×224×3的图像,将其颜色通道降至28×28×2的超低分辨率,然后用双线性插值法再把颜色通道放大,所得图像与颜色分辨率很高的原始图像相比几乎没有差别。
图3:你需要的只是一些颜色。顶行是原始彩色图像。中间行是降低采样率后的真实色度图像,尺寸缩小至28像素。底行是双线性提高中间行的采样率并结合原始灰度图像的结果。
这表明,把问题变成仅预测低分辨率颜色,我们就可以简化着色问题。我原本已准备好彻底放弃PixelCNN了,因为它显然无法放大小图像,但用来生成28×28×2的图像还是很可行的。通过将颜色数值简化为32个数字而非256,我们进一步简化了着色问题。
Sergio构建了一个“改进的”网络,它能够清理低分辨率颜色的输出,并将溢出边界的颜色推回至正确位置——使用前馈式图像对图像卷积神经网络进行训练,损失仅为L2。我们还用一个预训练好的ResNet作为条件网络,用以消解额外的损耗项需求,毕竟在超分辨率项目中我们已经用过这样的损耗项。
使用这些方法后,无论是众包评估还是用颜色直方图相交评估,我们都能得出ImageNet上最好的结果。事实证明,经过正确训练的PixelCNN可以很好地模拟图像统计数据,不发生任何模式崩溃。
图7:实验室颜色空间中的颜色通道的边缘统计数据。左:每种方法的直方图以蓝色显示,ImageNet的测试数据集直方图以黑色显示。右图:颜色通道的直方图相交。
由于模型为每个灰度输入的可能着色声明了一个概率分布,我们可以对该分布进行多次取样,以获取同一输入的不同着色。下图用结构相似度(SSIM) 很好地展示了分布的多样性:
图8:为证明我们的模型可生成不同的样本,我们用多尺度SSIM对比了同一输入的两种输出。上图显示了ImageNet测试数据集的SSIM距离直方图。图中在多个SSIM间距上分别显示了每对图像。SSIM值为1表示两张图像完全相同。
该模型离远未完美。ImageNet尽管庞大,但不能代表所有的图像。而该模型在处理非ImageNet图像时并不理想。我们发现,真实的黑白照片(不同于彩色转化为灰度的黑白照)会得出不同的统计数据,并能出现很多彩色照片中所没有的物体。比如,Model T汽车的彩色照片不多,ImageNet图像集中可能一张都没有。采用更大的数据集和更好的数据扩增,也许能简化这些问题。
想了解图像质量的话,可以来看看这些图:
作为对比,下面是用其他算法来处理同一ImageNet测试数据集的结果:
最后,完整的细节都在我们的论文中:PixColor: Pixel Recursive Colorization - https://arxiv.org/abs/1705.07208
失败与未报告的实验
这一年期间,我曾短暂着迷过许多业余的小项目,尽管它们都失败了……接下来我会简单来描述其中的几个:
大数的素因数分解
素因数分解一向都是个大难题。但即便是如今,我们仍在不断发现有关素数分解的新问题。如果为深度神经网络提供足够的实例,它能不能找出一些新东西?Mohammad和我尝试过两种方法。他修改了Google机器翻译的seq2seq神经模型,该模型把一个半素大数的整数序列作为输入,并将其素因素预测为输出。我则使用一个较为简单的模型,它将定长整数作为输入,并用几个全连接层来预测输入的分类:素数或合数。这两种方法都只学到了最为明显的规律(如果尾数为0,那它就不是素数!),我们只能抛弃这个想法。
Adversarial Dreaming
受Michael Gygli的项目启发,我想探究一下鉴别器能否充当它自己的生成器。为此,我构建出一个简单的二元分类卷积神经网络来判断输入的真假。为了生成图像,你需要给出噪点并让它使用梯度来更新输入(有时被称为deep dreaming),令该网络把“真实”类别最大化。该模型通过交替生成“假”实例来进行训练,而后跟典型的GAN鉴别器一样,通过升级权重来区分真假实例。
我的想法是,鉴于更少的架构决策,该网络相比一般的GAN可能更容易训练。事实上,它用MNIST确实可以工作。下图中每一栏都在展示:不同的噪音图像被逐渐推向红色MNIST数值的情形。
但我没法让它在CIFAR-10数据集上工作,并且它的实际意义也极为有限。这遗憾了,我相信 "Adversarial Dreaming" 会是一个很酷的论文题目。
使用PixelCNN来训练生成器
PixelCNN生成样本的时间过长,这让我很沮丧。于是,我就想试试能不能用一个预训练的PixelCNN训练出前馈式图像对图像卷积神经网络(8x8至32x32尺寸的LSUN卧室图片集)。我所设置的训练方法是:在前馈式网络的输出上进行自动回归。在PixelCNN下更新权重以便将概率最大化。它用这样的线条生成了非常奇怪的图像:
对异步随机梯度下降算法的修改探索
如前文所述,很多模型都不适用于异步随机梯度下降算法。最近,一篇名为DCASGD论文提出了一种解决过时梯度问题的可能方法:在机器开始步进去应用它们权重的前,在权空间(weight space)使用差分向量。这种方法可大大减少每一个人的训练时间。不幸的是,我没能在TensorFlow上重复他们的结果,也无法实现我所想出的几个类似想法。这里可能有Bug。(如果想获取我的实现方法,请通过内部渠道联系我)
想法和总结
作为软件工程师,我在机器学习方面并没有什么经验。但基于过去一年对深度学习的研究,我来分享一下在该领域的总体看法,及其同范围更广的软件领域之间的关系。
我坚信,机器学习将改变所有行业,并最终改善每个人的生活,许多行业都会因机器学习所提供的智能预测而受益。对于我,我在这个项目中,最初的目标是,在不久的将来,所有人都可以看到查理·卓别林这类老电影的4K版。
不过,我确实发现,这一模型的构建、训练和调试都相当困难。当然,大部分的困难是由于我缺乏经验,这也表明有效训练这些模型是需要相当丰富的经验的。我的工作集中在机器学习最为容易的分支上:监督式学习。但即便有着完美的标注,模型开发仍十分困难。似乎,预测的维度越大,构建模型所花的时间就越长(例如:花大把的时间进行编程、调试和训练)。因此,我推荐所有人在开始时都尽可能简化和限制你的预测。
举一个我们在着色实验中的例子:我们在开始时试图让模型预测整个的RGB图像,而非只预测颜色通道。我们认为,神经网络很容易就能处理好灰度图(intensity image)并输出出来,因为我们使用的是跳跃连接(skip connection)。只预测颜色通道依然能改进性能。
如果我以主观、本能的方式使用“工作”来描述软件:图像分类工作起来似乎很稳健。生成式模型几乎很少能工作,人们也不太了解这种模型。GAN能输出很好地图像,但是构建起来几乎是不可能的——我的经验是,对架构作出任何小改动都有可能使它无法工作。我听说强化学习更加困难。但因经验不足,我对递归神经网络不予置评。
但是,随机梯度下降算法工作起来是太过于稳定,即使是严重的数学错误也不会让它彻底失败,仅稍稍有损于性能。
因为训练模型经常需要花费很多天,这是一个非常缓慢的修改—运行循环。
测试文化尚未完全兴起。训练模型时我们需要更好的评断方法,网络的多个组成部分需要维持特定的均值和变量,不能过度摆动或者留在范围内。机器学习漏洞使系统的heisenbugs能特别轻松地通过测试。
并行计算带来的好处有限。更多的计算机数会让大规模的超参数搜索会变得更容易,但理想情况下,我们会设计出无需仔细调试也可正常工作的模型。(实际上,我的怀疑是,超参数搜索能力有限的研究人员,将不得不设计出更好更稳健的模型。)不幸的是,对很多模型而言,异步随机梯度下降算法并没有什么用处——更加精确的梯度通常用处不大。这就是为什么 DCASGD的研究方向会如此重要。
从软件维护的角度看,大家对如何组织机器学习项目鲜有共识。就像Rails出现之前的网页一样:一群杂乱PHP脚本、商业逻辑和标记符号混在一起。在TensorFlow项目中,数据管道、数学和超参数/配置管理同样豪无组织地混成一团。我认为,精美的结构/组织还有待于我们发掘。(或是说有待于重新发现,就像DHH重新发现并普及MVC那样。)我的项目结构一直在进步,但我不会认为它已足够精美。
框架将继续快速进化。虽说我一开始用的是Caffe,但也不得不感激TensorFlow所带来的好处。而今,PyTorch与Chainer之类的框架都在用动态计算图表来取悦用户。漫长的 修改—运行 循环是开发出更好模型的主要障碍——我猜测那些优先考虑快速启动和快速评估的框架将取得最后的成功。尽管有TensorBoard、iPython这类有用的工具,但检查模型在训练期间的活动仍很困难。
论文中的信噪比都很低,改进的空间依然很大。由于学术会议更看重的是准确度而非透明度,人们往往不会坦率承认他们模型上的失败之处。我希望学术会议能够接受博客作为附加材料,同时要求开源的实现。Distill在这方面的努力值得称赞。
对机器学习而言,这是一个激动人心的时代。在各个层面上都有大量的工作需要完成:从理论端到框架端,还有很多值得改进的空间。这几乎跟互联网的诞生一样令人激动。快动手加入进来吧!
图片说明:
这就是你的机器学习系统吗?
是啊!把数据倒进这堆线性代数中,然后在另外一头接收答案就好了。
如果答案错了怎么办?
只管搅动这堆数据,直到结果开始看上去正确为止。
致谢
非常感谢Jon Shlens和Mohammad Norouzi带给我无数的白板指导和宝贵建议;非常荣幸能跟David Bieber和Sergio Guadarrama这两位顶级黑客共事;最后,特别感谢参与见习项目的其他同事:Martin Stumpe、Kevin Murphy、Doug Eck和Leslie Phillips。