[机器学习笔记#4] Neural Style 算法分析及caffe实验
Cirno2016/08/09软件综合 IP:美国

2015 年德国的几位科研人员基于 google deep dream 的思路,提出了一种用 CNN自动生成带有特定艺术风格的图片的算法,发表了一篇名为A Neural Algorithm of Artistic Style的 tech notes, 可以说是 deep dream的美梦+艺术版本。
inceptionism-neural-network-deep-dream-art-42__605.jpg

由于效果实在是太神奇,从那时起开始出现了很多利用此技术来美化用户图片的App和在线应用,比如手机应用 Prisma 和ostagram,以及deepart

楼主在读过这篇 tech notes后,被其想法的巧妙所折服,再加上目前网络上开源的版本已经有了基于 Torch, Theano 和 Tensorflow 的,但尚无 caffe版本。 于是产生了用 caffe来重复原文实验的想法。


该文的一个基本思路是:一张图片的内容(content)和风格(style)是可分离和重组的。于是我们可以用图片A的内容,加上图片B的风格,组合出一张新的图片,使之同时具有二者的内容和风格。

而这里CNN的作用在于,当用CNN识别一张图片时,当图片数据沿着各 convolution layer前向(forward)传播时,其包含的信息将越来越趋于精简,图片中的细节部分,或者说基础纹理和边缘,将逐渐淡去,而这些正是所谓“艺术风格”的体现之处,大概相当于平常说的“笔触”吧;而代表主干内容的结构信息,则会越来越清晰。这是CNN对人类视觉中“分层理解”的工作方式的一种近似模仿。

所以实际中,CNN中的浅层 convolution layer,主要对细节纹理信息进行提取,而这些 layer经过 activation layer 之后的输出,经过处理后,表征了一幅图片的 style 信息;而较深层次的convolution layer,则表征了图片的 content 信息。


具体的实现思路如下:

1. Find Content

基于上述思路,文中首先定义了用于度量一副图片在某一特定 layer \(l\) 上 content 的特征 \(F^l\) ,这个特征直接是选取了CNN在该 layer 后 activation 的输出的 feature map。 假设我们有一张图片 p,使我们在 layer \(l\) 上得到了一个特征 \(P^l\) , 那么根据内容可分离的观点,这世界上一定存在另一张图片 x,其在同一CNN同一 layer上输出的特征 \(F^l\) 无限接近于 \(P^l\)。也即是说图片 px 拥有相同的 content 。这里我们可以定义如下的 content loss function。
$$ L_{content}(p,x,l)=\frac{1}{2} \sum_{ij} (F^l_{ij}-P^l_{ij})^2 $$

然后的问题就是要如何找到这张图片?考虑到图片的像素数和灰度级,这里的搜索空间是极其极其巨大的。但我们有 gradient descent 这一法宝在手,可助降妖伏魔。

解决的方法也非常鬼畜: 首先对 x 生成一张白噪声图片,作为初始值,然后用 gradient descent 和 backpropagation,在每一次迭代循环中计算 \(L_{content}(p,x,l)\) 对 x每一个像素的导数,作为 x 该次 gradient descent 的 update。经过一定的循环次数后,x便可逐渐收敛为我们想要的目标图片。 由于在 backpropagation 中我们相当于使用了 \(L_{content}(p,x,l)\)作为 output layer,所以相对应的求导过程也需要我们给出。不过这里的 least-square loss 求导非常简单,初中生都会算(分段是因为 VGG使用 ReLu作为 activation layer的关系):
$$ \frac{\partial L_{content} }{\partial F^l_{ij}}= \begin{cases} ( F^l_{ij}- P^l_{ij} ) & if & F^l_{ij}>0\\\\ 0 & if & F^l_{ij}<0 \end{cases} $$ (实际上我本人比较想把这里的 least-square loss改成理论上更科学的 cross-entropy loss看看效果)
基于上述理论,楼主编写了如下基于 caffe的实验代码,待我一行行分析:

这一段是设置 caffe相关的环境,为省事直接copy了之前 deep dream的部分。

<code class="language-python"># imports and basic notebook setup
from cStringIO import StringIO
import numpy as np
import scipy.ndimage as nd
import PIL.Image
from IPython.display import clear_output, Image, display
from google.protobuf import text_format
import matplotlib.pyplot as plt
from numpy import linalg as LA
%matplotlib inline
import caffe

# If your GPU supports CUDA and Caffe was built with CUDA support,
# uncomment the following to run Caffe operations on the GPU.
caffe.set_mode_gpu()
caffe.set_device(0) # select GPU device if multiple devices exist

def showarray(a, fmt='jpeg'):
    a = np.uint8(np.clip(a, 0, 255))
    f = StringIO()
    PIL.Image.fromarray(a).save(f, fmt)
    display(Image(data=f.getvalue()))
</code>

根据原文,导入已经 train好的 VGG19模型

<code class="language-python">model_path = '/home/lzhang57/DeepLearning/caffe/models/bvlc_vgg19/' # substitute your path here
net_fn   = model_path + 'VGG_ILSVRC_19_layers_deploy.prototxt'
param_fn = model_path + 'VGG_ILSVRC_19_layers.caffemodel'

# Patching model to be able to compute gradients.
# Note that you can also manually add "force_backward: true" line to "deploy.prototxt".
model = caffe.io.caffe_pb2.NetParameter()
text_format.Merge(open(net_fn).read(), model)
model.force_backward = True
open('tmp.prototxt', 'w').write(str(model))

net = caffe.Classifier('tmp.prototxt', param_fn,
                       mean = np.float32([104.0, 116.0, 122.0]), # ImageNet mean, training set dependent
                       channel_swap = (2,1,0)) # the reference model has channels in BGR order instead of RGB

# a couple of utility functions for converting to and from Caffe's input image layout
def preprocess(net, img):
    return np.float32(np.rollaxis(img, 2)[::-1]) - net.transformer.mean['data']
def deprocess(net, img):
    return np.dstack((img + net.transformer.mean['data'])[::-1])
</code>

读取 content 图片,一只萌猫。并指定用于计算 content feature的参考 layer,这里先设置成 'conv4_2'
cat.jpg

<code class="language-python">img = np.float32(PIL.Image.open('data/cat.jpg'))
[h,w,c]=img.shape
content_img=preprocess(net, img)
### set stop layer ###
end='conv4_2'   
src = net.blobs['data']
src.reshape(1,3,h,w)
dst = net.blobs[end]
### generate content image ###
src.data[0]=content_img
net.forward(end=end)
Content=np.copy(dst.data[0])
</code>

生成白噪声图片,并进入 gradient descent 主循环。

<code class="language-python">### generate style image 
target=np.float32(np.random.rand(h,w,3)*255)
target_img=preprocess(net, target)
### gradient descent step
step=2
src.data[0]=target_img
### optimize target image
Loss=[];
max_iter=500
for iter in range(max_iter):
    ## compute feature
    net.forward(end=end)
    Feature=dst.data[0]
    ## compute gradient
    tmp=Feature[:]-Content[:];
    Loss.append(0.5*LA.norm(Feature-Content))
    if( iter>0 and np.abs((Loss[-2]-Loss[-1])/Loss[-2])</code>

最后得到的输出图片如下,现在看来还平淡无奇,因为这里只是实现了“生成一张跟参考图片具有相同内容图片”的功能:
cat_content.jpg 迭代过程中的 loss function 收敛情况如下:
curve_content.png

为了更好的演示这个过程中发生了什么,这里我将 gradient descent时的中间结果显示如下(成功规避水印):
change.png

在我研究 machine learning以前,如果有人告诉我可以像这样把一张白噪声图片只靠做减法,一点一点撸出一只猫来,我是打死也不信的。

今天就做这么多吧,实际上这里只实现了全部功能的 1/3,明天继续,睡觉zzz

更新计划:明天七夕,后天再继续

[修改于 7年8个月前 - 2016/08/09 12:19:59]

+1  学术分    虎哥    2016/08/10 高品质原创文献。
来自:计算机科学 / 软件综合
0
 
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
Cirno
专家 进士 老干部 学者 机友 笔友
文章
34
回复
359
学术分
2
2012/09/03注册,9天18时前活动

Machine Learning, computer vision enthusiast

Google

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}