an implementation of google's deep dream with pytorch
deep dream 也就是深度做梦,可以通过深度神经网络生成非常奇怪的图片,就像下图这样:
看起来有点奇怪,图片中出现了很多其他的东西,左下角的甚至很清晰的出现了一条狗,这是什么原因呢? 我想从一些背景开始介绍起,其实这样的图片是谷歌的无心之举造成的,产生这样的图片也出乎他们的意料。
我们都知道深度网络以前对我们而言其实是一个黑盒子,我们都知道网络的分类或者其他效果很好, 但是我们不知道为什么这么好,或者说我们只有一个大概的猜想为什么会这么好,这些都不如实际搞清楚 中间的隐藏神经元到底做了什么实在,当然最直观的效果就是可视化。最早的可视化**其实都是可视化 中间层神经元的激活图,我们从激活图中确实发现了很多东西,比如前几层网络提取的都是边缘形状等 简单的信息,随着网络的深入,简单的特征经过各种组合变得复杂,最后提取的都是十分抽象的特征。
但是谷歌想了另一个方法,就是能不能不可视化激活图,而是将神经元的激活回传到输入层,生成图片, 那么图片的内容就是那个神经元或者那组神经元关注的东西。这里其实有图像梯度的**,这里就不多说了。 最后就产生了deepdream。
deepdream的**很简单,就是让原始图片在预训练好的网络里向前传播,传播到特定的层时停止,然后令梯度等于改层的激活, 再反向传播到第一层,得到图像梯度,利用梯度上升,叠加在原始图片上。多次迭代,原始图片上就会多出很多其他的物体, 当然其中动物居多,为什么呢?这是因为我们用的预训练好的网络一般是在Imagenet上训练的,而ImageNet超过一半都是动物, 而动物里一大半是各种各样的狗,所以训练好的网络对狗非常敏感,也就非常关注。这也就是上图中为什么左下角有一只这么明显的狗 的原因,当然那张图其实是经过反复迭代的,所以狗才会这么清晰。
到这里,相信很多人就大概了解了deepdream,毕竟并不复杂,但是有地方可能有疑惑,比如为什么令梯度等于改层的激活呢? 这个很多地方教程都没有解释,这要从整个过程的损失函数谈起,你们应该注意到了,要想反向传播,必须要有一个损失, 而上面我没有提到损失,直接让梯度等于激活值,我们的目标其实是增大改成的激活,也就是说假如这层神经元对狗的激活大, 觉得原图中有一处像狗,那么我们就增加激活,让网络更加相信那就是狗,然后多次迭代,激活越来越大。所以我们的损失就是 激活的L2范数,目标就是最大化这个范数,让激活值增加,方法自然就是梯度上升了,而不是梯度下降,因为我们要增加损失。 而L2损失的梯度就是2倍的激活值,2是常数,省去,也就是激活值,所以前面我们直接让梯度等于激活值,其实是跳过了计算损失这一步。
需要注意的是整个过程中网络的参数是不会改变的,我们梯度上升,改变的是原始图片的像素值,通过不断叠加图像梯度上去, 来增加目标的L2范数损失。
其实网络正过来分类,反过来就是生成,这句话需要好好理解,可以了解了解图像梯度,迷惑图像(fooling image),图像生成等 技术,这样你会更好的理解这句话。deepdream只是其中一个小应用而已。
在介绍一个技巧,就是直接做deepdream生成的图像是无法控制的,也就是说出现小猫小狗是随机的,取决于你的网络和停止的层数, 但是其实我们可以控制做梦的方向,输入另一张图片最为参考,让原始图片朝着参考方向做梦,其实也很简单,就是损失函数变一下而已, 也就是梯度的值不再只是取激活值了,而要考虑参考的特征,由于两个特征的大小尺寸不一样,所以一般组合在一个矩阵里,取出最大值 的下标,然后取出对应的特征,具体参照代码理解。
关于deepdream还有一些技巧,用了这些技巧,做梦的效果更佳。
将原始图片随机抖动在输入网络,相当于起到一个正则化的效果,事实证明效果可以得到提升。
学习速率需要经过一点改动,具体就是计算所有特征的平均值,然后除以这个均值得到学习速率。此处的 所有特征是原始图片的所有特征,不是网络中其他地方的特征。
每次迭代输入同一张图会影响效果,产生过拟合,因此每次都以一定尺度缩小图片,在输入网络, 可以有效改善效果。
基于pytorch,参考谷歌官方。 run.py 初始deepdream,效果如下:
run2.py 控制做梦,参考图和效果如下,可以好好对比一下:
这是参考图:
这是效果图:
当然我们可以把生成的图片,当成输入再次输入网络,这样效果会越来越好,上面的天空图就是反复输入四五次的效果, 谷歌官方甚至给出了上百次的效果,生成的图片看起来已经有点恶心了,汗。