用MXnet实战深度学习之二:Neural art

Published on 2015 - 12 - 18

题注:本来这是第三集的内容,但是Eric Xie 勤劳又机智的修复了mxnet和cuDNN的协作问题,我就把这篇当作一个卷积网络ConvNet(CNN)神奇而有趣的例子,寓教于乐给大家提起学习兴趣,原计划的CNN教学顺延到下一集。

更新:基于Generative Adversarial Network(GAN)生成模型的 MXNet Neural art可以几乎实时生成梵高风格的模型,具体请参见 http://dmlc.ml/mxnet/2016/06/20/end-to-end-neural-style.html

Neural art:用机器模仿梵高

Neural art是个让机器模仿已有画作的绘画风格来把一张照片重新绘画的算法,比如给一张猫的照片和一张梵高的自画像,我们就可以得到用梵高风格画出来的猫,比如这个样子(图二为梵高在1889年的自画像,引用自wikipedia):

Neural art算法来自于这篇论文 “A Neural Algorithm of Artistic Style” by Leon A. Gatys, Alexander S. Ecker, and Matthias Bethge,链接在 http://arxiv.org/abs/1508.06576 有兴趣的观众朋友们可以阅读。它的基本想法是,利用一个多层的卷积网络(CNN)抽象出给定绘画作品里一些高级的隐藏特征用来模仿绘画风格,并把这个绘画风格应用到一个新的图片上。这类的图像生成模型是深度学习的一个方向,比如谷歌的Inception把一个羊的图片和一个云的图片生成羊形状的云之类的“迷幻类”图像也是类似模型的一种。Facebook也有类似的生成模型,他们基于这篇http://arxiv.org/abs/1406.2661由DMLC作者@antinucleon等人合作的文章。
Neural art算法模型有多种实现方式,比如这里这里是两个Lua/Torch版的实现,这片论文的gitxiv下面也包含了五花八门的各种实现,他们都是实现论文中的VGG模型并用caffe描述,MXnet在大家的提议下当然也要实现一下了。按照惯例,mxnet这么有意思的深度学习工具,我们去帮它的github加个星,大家说好不好啊?传送门:https://github.com/dmlc/mxnet

MXnet的Neural art样例

MXnet的neural art范例在mxnet/example/neural-style/目录下。因为这个例子需要大量的计算,推荐安装GPU版的mxnet。安装mxnet GPU版的教程参见前一集 http://phunter.farbox.com/post/mxnet-tutorial1 这里就不再重复。当然了,mxnet的CPU和GPU无缝连接,如果没有GPU可以使用CPU版本,只是需要每张图耐心等待约40-50分钟。
选择安装:mxnet可选使用cuDNN加速。对Neural art的例子,cuDNN v3和v4均可运行,v4在我的GTX 960上比v3稍微快约2-3秒。大家可以到这里 https://developer.nvidia.com/cudnn 申请开发者项目,如果批准通过可以下载安装cuDNN工具包,具体请参照nVidia官方教程或者简单的执行这几步(参考来源 Install Caffe on EC2 from scratch (Ubuntu, CUDA 7, cuDNN))):

tar -zxf cudnn-7.0-linux-x64-v3.0-prod.tgz
cd cuda
sudo cp lib64/* /usr/local/cuda/lib64/
sudo cp include/cudnn.h /usr/local/cuda/include/

如果之前没有编译安装cuDNN版的mxnet,请在make/config.mk里把USE_CUDNN = 0修改为1重新编译,并更新安装对应的python包。
如果你没有安装mxnet GPU版本的条件,也可以访问以下这些网站或app玩一下Neural art。这个算法需要大量的GPU计算,以下这些免费或收费的实现都需要排队。

  1. Deepart:网址 https://deepart.io/ 用户可以免费提交,平均等待时间为1周左右,如果想插队到24小时之内,可以捐款给网站。
  2. Pikazo App:网址 http://www.pikazoapp.com/ 它相当于把deepart这个网站做成app,需要收费$2.99,也需要排队。
  3. AI Painter: 网址 https://www.instapainting.com/ai-painter 这是instapainting的一个业务,免费,也是需要排队。

观众朋友们如果正好有一个装了GPU版mxnet的机器的话,那我们就开始用mxnet自己动手丰衣足食,还可以帮朋友们生成有艺术感的微博头像哟。以下例子中我用我妹 @dudulee的浪里格朗 家的美猫“破狗”的照片为例讲解具体生成艺术图的步骤。如果观众朋友们想看更多破狗和她妹妹“泥巴”的日常,可以关注嘟嘟家两只猫“破狗”和“泥巴”的微博 @POGOGO-NIBABA,内有大量图片可供深度学习实验。

简要步骤和参数调整

mxnet使用的是论文中描述的VGG模型,在第一次使用的时候需要执行download.sh下载该模型,mxnet的模型版本占约几十MB的空间。下载模型完毕之后,可以把需要绘画的原始图片和模仿的图片放到input目录里,比如说破狗的照片和梵高的图像,然后执行:

python run.py --content-image input/pogo.jpg --style-image input/vangogh.jpg

耐心等待1-2分钟,就可以看到结果保存在output目录里,比如是这样的:

如果给另外一张现代艺术油画 'Blue Horse' Modern Equine Art Contemporary Horse Daily Oil Painting by Texas Artist Laurie Pace (链接https://www.pinterest.com/pin/407223991276827181/) 替代梵高的画作让机器学习风格,破狗可以画成这样的:

python run.py --content-image input/pogo.jpg --style-image input/blue_horse.jpg

run.py里有一些可以调整的参数,如果想调试输出效果可以按照如下解释调整:

  • --model 指定模型。例子里暂时只有vgg这一个模型,以后可能添加前面提到的inception等其他模型。暂时先不用改。
  • --content-image 内容图片,比如上面的“破狗”的照片
  • --style-image 输入的绘画原作的路径,比如上面的的“梵高自画像”。
  • --stop-eps 模型里用eps值代表两幅图的风格相似度,在训练的过程里会看到这个值逐渐收敛,值越小相似度越高。stop-eps参数指定的是收敛的终止值,一般越小就代表画的越像,但如果太小了会需要很多的计算时间来收敛,默认0.005已经可以得到不错的效果,可适当减小到0.004等。
  • --content-weight --style-weight 内容图片和绘画原作的相对权值,默认是10:1,如果发现绘画风格过于强烈涂抹一片,可适当修改为20:1或者30:1,反之改小。
  • --max-num-epochs 最大收敛步数,默认是1000步。不过一般画作在200步左右就能找到差不多合适的eps风格相似值,这个最大收敛步数不需要修改。
  • --max-long-edge 长边最大边长。程序会自动把输入图片按照这个值等比例缩放,比如上面的图就是缩放到高度为512像素。程序运行时间及内存消耗约和图片面积成正比,因为卷积网络的计算量每个像素相关,700像素的图片差不多比500像素的图片多一倍内存和运行时间。在接下来的对比测试里面可以看到,512像素的图差不多需要1.4GB显存,适合2G显存的显卡比如nvidia显卡的macbook pro等娱乐一下就足够了,4GB的显卡差不多最高可以处理到850-900像素的图片,要想上1080p就得有Titan X的12GB了。同样的,计算时间也会相应拉长,它也和显卡的CUDA核心数约成反比。现在你基本上明白了为什么上面提到的免费版都需要排队几个小时到几周不等了。
  • --lr logistic regression的梯度下降(SGD)学习率,用来寻找既在内容上满足“像破狗”又在风格上“像梵高”的生成图像。较大的eta收敛较快,节省计算时间但会在最小值附近跳跃。默认值0.1,可以调整到0.2和0.3都可以。
  • --gpu 使用第几个GPU,默认是0号GPU,适合只有一块显卡的用户(比如我家里的机器)。如果没有GPU并能忍耐40分钟左右算一张图,--gpu -1也可以指定为纯CPU计算。
  • --output 输出文件名。
  • --save-epochs 是否保存中间结果,默认每50步保存一下结果。
  • -remove-noise 降噪参数,默认0.2,可以降低一些为0.15,这就是高斯降噪的半径。程序在学习模仿画作的过程里会使用两个白噪声图片逼近风格图和内容图,在最终生成的图片里面可能残留一些不必要的噪声点,程序里面可以降噪处理。

可能遇到的问题

内存不足

运行时消耗的显存和图像的面积成正比,如果图像的缩放目标边长太大,很可能会显存不足,mxnet的提示错误信息如下:

terminate called after throwing an instance of 'dmlc::Error' what():  [18:23:33] src/engine/./threaded_engine.h:295: [18:23:33] src/storage/./gpu_device_storage.h:39: Check failed: e == cudaSuccess || e == cudaErrorCudartUnloading CUDA: out of memory

对于512边长的图片,mxnet需要1.4G显存,一般的nVidia版的macbook pro或者其他有2G显存的机器可以跑起来;对于850边长的图片,mxnet需要3.7GB显存,一般4G显存的机器可以跑起来。这里有两点提醒注意:

  1. 如果你使用的是GTX 970 4G版,它的有效显存使用最高只到3.5GB,超过这个就会有莫名的错误。具体参考这里
  2. 如果显存正好差一点可以关掉系统占用的显存,比如在ubuntu下面可以Alt-Ctrl-F1关掉系统图形界面,节省几十MB的显存。

工作空间不足

如果图片边长大于600到700,原始例子里默认的workspace可能不够,会出现如下的错误信息:

terminate called after throwing an instance of 'dmlc::Error' what():  [18:22:39] src/engine/./threaded_engine.h:295: [18:22:39] src/operator/./convolution-inl.h:256: Check failed: (param_.workspace) >= (required_size)
Minimum workspace size: 1386112000 Bytes
Given: 1073741824 Bytes

Mxnet需要工作缓冲空间,这个变量可以在模型的定义里面设置。找到model_vgg19.py文件,把里面的所有workspace=1024改成workspace=2048就可以了。

速度测试

为了体现MXnet在速度和高效的内存使用上的优势,在这里我们选择这这个Lua (Torch 7)的实现 https://github.com/jcjohnson/neural-style,用同一组图“破狗”+“梵高”对比测试mxnet和它的性能。实验条件,单块GTX 960 4GB,4核AMD CPU,16GB内存。值得提醒的是,Lua版使用的原版的VGG模型在第一次运行的时候也需要下载,占用大约1GB多的空间。

512像素边长的图片

内存消耗 运行时间
MXnet(不使用cuDNN) 1440MB 117s
MXnet(使用cuDNN) 1209MB 89s
Lua Torch 7 2809MB 225s

850像素边长的图片

Lua/Torch 7版对于850px边长无法测试,等我的Titan X到货再来一战。

内存消耗 运行时间
MXnet(不使用cuDNN) 3670MB 350s
MXnet(使用cuDNN) 2986MB 320s
Lua Torch 7 显存不足 显存不足

MXnet黑科技(12.21.2015更新)

在reddit上和网友们讨论这篇blog后,Lua版的作者回帖表示不服并成功的把内存消耗降低到1.5GB,具体讨论请参考这一条reddit的讨论。我和Mxnet的各位作者讨论之后,拿出MXnet最新压缩内存使用的黑科技MXNET_BACKWARD_DO_MIRROR迎战,相关技术细节请参看这条github issue。想使用黑科技版MXnet请更新M到github的最新版本并重新编译。在执行代码前加入MXNET_BACKWARD_DO_MIRROR=1即可调用黑科技,比如:

MXNET_BACKWARD_DO_MIRROR=1 python run.py --content-image input/pogo.jpg --style-image input/vangogh.jpg

512像素对比测试

内存消耗 运行时间
MXnet(不使用cuDNN) 1440MB 117s
MXnet(使用cuDNN) 1209MB 89s
MXnet(使用cuDNN和内存压缩) 1116MB 92s

850像素对比测试

内存消耗 运行时间
MXnet(不使用cuDNN) 3670MB 350s
MXnet(使用cuDNN) 2986MB 320s
MXnet(使用cuDNN和内存压缩) 2727MB 332s

我们可以对比看到使用了压缩内存选项之后,MXnet损失了一点运行时间,但是内存得到10%的压缩,现在4GB内存最高可以支持1024px的图片,消耗内存为3855MB。值得指出的是,Mxnet里gram matrix的实现暂时没有用到符号计算的功能(作者告诉我他太忙了以至于偷懒了),等更新了符号计算版本之后会再降低一些内存使用。
简单来说,MXnet节省一倍的显存,速度也接近翻倍。该Lua版本的官方样例使用Titan X(12GB)生成边长512像素的图,需要约一到两分钟,而mxnet只需要一块 GTX 960约两分钟,对比Titan X(1000$)和GTX 960 (200$) 的价格差距,mxnet差不多省了4-5倍的钱。关于运行速度对比值得提醒的是,Lua版本的速度稍慢主要因为它是用L-BFGS来收敛,收敛效果好但是速度较低,而mxnet使用更快的SGD,它有速度优势但是对不同的输入参数可能需要手工微调,在这一点上不能简单的说MXnet一定总是快两倍。
MXnet能达到这样的速度和高效内存使用,得益于他所在的DMLC组件的高效设计和实现。关于DMLC和MXnet的高效内存设计方法,有兴趣的观众朋友们可以前往这里作深入阅读了解DMLC的黑科技。
到这里,观众朋友们应该可以愉快的玩耍起来Neural Art给自己和朋友们生成艺术图片了。接下来的部分讨论一下机器为什么能学习模仿到绘画风格。

机器怎么模仿绘画风格

这个问题的答案在原论文里也语焉不详,作者也没有想解释清楚。以下的讨论均按我个人根据原文以及reddit和知乎上的相关讨论在这里概述一下,更多讨论详情请参阅:reddit 知乎 这里一并感谢上述链接里的作者和评论者。

量化表示“绘画风格”

“绘画风格”是一个抽象定型的词语,它可能和图像的某种高阶统计量相关,但不同的绘画风格有不同的表示,对于一个没有具体定义风格的一般性问题,它很难用人工设计算法去完成。幸运的是,我们知道卷积网络CNN可以通过多层卷积提取物体的抽象特征完成物体识别(请参考Yann Lecun的深度学习教程),这一点“提取抽象特性”的能力被作者借用来描述“风格”。也就是说,经过多层CNN抽象之后的图片丢弃了像素级的特征,而保留了高级的绘画风格。下图引用自原论文图1。在文章里,作者定义了一个5层的CNN网络,梵高的星空在通过第一二三层的时候保留了一些原图的细节,但是在第四第五层的时候,就变成了“看起来是梵高星空的样子”这样的抽象特征:


这时候作者机智的想到了,如果把一张梵高一张其他照片同时都放到这个CNN网络里,经过合适的调整让第二张照片在第四五层接近梵高,而第一二三层保持和原来差不多,那就可以模仿梵高了!细节上,作者为了沿用了CNN的特征抽象能力使用了CNN作物体识别的VGG模型。关于mxnet实现CNN作物体类别识别的相关例子,我在下一集会讲到。

学习风格并生成图像

于是让机器模仿绘画风格并生成图片成了一个优化问题:生成的图像要像原内容图,比如我给一张猫的图片最终还是要像猫;生成的图像要像是由风格图画的,比如我给了个梵高的图,我生成的猫的图片要看起来有梵高的风格。也就是说要找到这样一个中间结果,它的内容表示(第一二三层CNN)接近于破狗,它的风格的表示(第四第五层CNN)接近于梵高。在文章里,作者用一个白噪声图片通过梯度下降生成一个接近内容图的图片,以及另一个白噪声图片生成一个接近绘画图风格的图片,并定义了神奇的描述纹理的gram matrix定义了这两个图的损失函数并加权平均当作优化目标函数,在mxnet的实现里通过梯度下降(SGD)完成收敛找到这样一个内容和风格都搭配中间结果。举例来说,“破狗”和“梵高自画像”的生成过程的200多步的循环里,图像的变化依次如下图所示:

我们可以看到,在刚开始的几十步里,图片更像是原图和绘画的简单纹理的叠加,而随着循环步数增加,程序慢慢学习到了配色和笔触的风格,在150步左右基本成型,最终把破狗的照片绘画成梵高的风格。

模仿风格是不是只有这一个办法?

事实上不是的,很多计算图形学的论文已经针对各种方向做出了一些成果,只是这篇文章利用了深度学习和CNN的方法,其他类似学习风格的论文可以参考相关阅读:

  • "A Parametric Texture Model Based on Joint Statistics of Complex Wavelet Coefficient" http://www.cns.nyu.edu/pub/lcv/portilla99-reprint.pdf 这片文章用小波变换的方式提取了图片纹理“风格”所对应的二阶统计量,和本文提到的论文想法是一致的。
  • “Style Transfer for Headshot Portraits” https://people.csail.mit.edu/yichangshih/portrait_web/ 这篇文章针对头像照片的风格做到了很快的风格学习,并且可以实时转换视频,对于这个有严格限制的问题,它的速度比Neural art高到不知道哪里去了。

结语

作为深度学习和CNN的例子,Neural art确实很好玩,观众朋友们可以自己用MXnet给自己和朋友们生成有意思的艺术图片,记得发到微博上加#mxnet#话题分享。值得提醒的是,如果原图是半身人像类,建议也使用一些人像的画作来学习风格,比如“破狗”+“梵高”的组合;相对应的,风景图片最好用风景画作风格学习。因为风景的表现重点和人像不同,强行把风格画到人像的照片上并不适合,它会看起来像是两幅图简单叠加,这个即使是人类画家也不好画在一起。大家好好玩,下一集会详细讲解卷积网络CNN作物体分类识别,也就是教机器如何识别猫和狗。