【编者的话】本文是一系列深度学习的简单实战教程的第二篇,用 MXnet 的 neural art 示例电脑绘画,用来讲解卷积网络 ConvNet(CNN)。第一篇为安装GPU 版mxnet 并跑一个MNIST 手写数字识别。来自Phunter 的博客,Phunter 的职业是数据科学家,DMLC ( http://dmlc.ml/ ) 工具包贡献者之一。
题注:本来这是第三集的内容,但是 Eric Xie 勤劳又机智的修复了 mxnet 和 cuDNN 的协作问题,我就把这篇当作一个卷积网络 ConvNet(CNN) 神奇而有趣的例子,寓教于乐给大家提起学习兴趣,原计划的 CNN 教学顺延到下一集。
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 计算,以下这些免费或收费的实现都需要排队。
- Deepart:网址 https://deepart.io/ 用户可以免费提交,平均等待时间为 1 周左右,如果想插队到 24 小时之内,可以捐款给网站。
- Pikazo App:网址 http://www.pikazoapp.com/ 它相当于把 deepart 这个网站做成 app,需要收费 $2.99,也需要排队。
- 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,适合只有一块显卡的用户(比如我家里的机器)。如果土豪你有 4 块显卡可以并行使用,只需要指定为--gpu 0,1,2,3
就可以了,8 块显卡以此类推,mxnet 支持多块显卡并行而且显存分配效率很高。如果没有 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 显存的机器可以跑起来。这里有两点提醒注意:
- 如果你使用的是 GTX 970 4G 版,它的有效显存使用最高只到 3.5GB,超过这个就会有莫名的错误。具体参考这里。
- 如果显存正好差一点可以关掉系统占用的显存,比如在 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
1440MB
117s
Lua Torch 7
2809MB
225s
850 像素边长的图片
Lua/Torch 7 版对于 850px 边长无法测试,等我的 Titan X 到货再来一战。
内存消耗
运行时间
MXnet
3670MB
350s
Lua Torch 7
显存不足
显存不足
简单来说,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 作物体分类识别,也就是教机器如何识别猫和狗。
编后语
《他山之石》是 InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到 editors@cn.infoq.com。
感谢杜小芳对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。
评论