介绍
2015年Google发布了一个很有意思的东西,叫做Deep Dream,网上瞬间掀起了Deep Dream的热潮,各种各样有着Deep Dream效果的图片漫天飞,下面就直观来看下什么是Deep Dream。



由于这些图片充满着幻觉和梦境,所以这个算法被称为Deep Dream。这个算法的由来非常有意思,因为它是意外得到的效果。 我们知道神经网络在图像分类上取得了显著的进展,但是由于深度学习网络中参数太多了,导致这个算法是一个黑盒子,虽然能够达到良好的效果,但是人们仍然对其内部知之甚少,所以人们希望能够窥探一下网络的内部,看网络到底是怎么判别图像的,图像又是如何呈现在深度学习模型内部的。
算法原理
我们知道一个神经网络读入一张图片,通过多层网络,最后输出一个分类的结果,但是我们仅仅知道一个结果并不够,神经网络的一个挑战是要理解在没一层到底都发生了什么事。我们知道经过训练之后,每一层网络逐步提取越来越高级的图像特征,直到最后一层将这些特征比较做出分类的结果。比如前面几层也许在寻找边缘和拐角的特征,中间几层分析整体的轮廓特征,这样不断的增加层数就可以发展出越来越多的复杂特征,最后几层将这些特征要素组合起来形成完整的解释,这样到最后网络就会对非常复杂的东西,比如房子,小猫等图片有了反应。
为了理解神经网络是如何学习的,我们必须要理解特征是如何被提取和识别的,如果我们分析一些特定层的输出,我们可以发现当它识别到了一些特定的模式,它就会将这些特征显著地增强,而且层数越高,识别的模式就越复杂。当我们分析这些神经元的时候,我们输入很多图片,然后去理解这些神经元到底检测出了什么特征是不现实的,因为很多特征人眼是很难识别的。一个更好的办法是将神经网络颠倒一下,不是输入一些图片去测试神经元提取的特征,而是我们选出一些神经元,看它能够模拟出最可能的图片是什么,将这些信息反向传回网络,每个神经元将会显示出它想增强的模式或者特征。
比如上面这些图片我们能够看出不同的神经元模拟出了不同的增强特征和模式,有一些是狗,有一些是桥,还有一些是房子。
通过上面的过程我们会迫使神经网络在图片中产生一些本来不存在的东西,这也就产生了类似梦境和幻觉,其实上这些梦境强调了网络到底学习到了什么,这种技术给我们提供了一种对抽象层次的定性感受,虽然这和现实中的梦境没有太大的关系,这也就是Deep Dream的最早提出的灵感。
实际上Deep Dream在上面的基础上使用了更多的技术,如果我们将此算法反复地应用在自身的输出上,也就是不断地迭代,并在每次迭代后应用一些缩放,这样我们就能够不断地激活特征,得到无尽的新效果,比如最开始网络的一些神经元模拟出来了一张图中狗的轮廓,通过不断的迭代,网络就会越来越相信这是一只狗,图片中狗的样子也就会越来越明显。
源代码
# coding:utf-8
from __future__ import print_function
import os
from io import BytesIO
import numpy as np
from functools import partial
import PIL.Image
import scipy.misc
import tensorflow as tf
import cv2
graph = tf.Graph()
model_fn = 'tensorflow_inception_graph.pb'
#这是谷歌inception网络模型,已经在imagenet训练好
sess = tf.InteractiveSession(graph=graph)
with tf.gfile.FastGFile(model_fn, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
t_input = tf.placeholder(np.float32, name='input')
imagenet_mean = 117.0
t_preprocessed = tf.expand_dims(t_input - imagenet_mean, 0)
tf.import_graph_def(graph_def, {'input': t_preprocessed})
def savearray(img_array, img_name):
scipy.misc.toimage(img_array).save(img_name)
print('img saved: %s' % img_name)
def resize_ratio(img, ratio):
min = img.min()
max = img.max()
img = (img - min) / (max - min) * 255
img = np.float32(scipy.misc.imresize(img, ratio))
img = img / 255 * (max - min) + min
return img
def calc_grad_tiled(img, t_grad, tile_size=512):
sz = tile_size
h, w = img.shape[:2]
sx, sy = np.random.randint(sz, size=2)
img_shift = np.roll(np.roll(img, sx, 1), sy, 0) # 先在行上做整体移动,再在列上做整体移动
grad = np.zeros_like(img)
for y in range(0, max(h - sz // 2, sz), sz):
for x in range(0, max(w - sz // 2, sz), sz):
sub = img_shift[y:y + sz, x:x + sz]
g = sess.run(t_grad, {t_input: sub})
grad[y:y + sz, x:x + sz] = g
return np.roll(np.roll(grad, -sx, 1), -sy, 0)
k = np.float32([1, 4, 6, 4, 1])
k = np.outer(k, k)
k5x5 = k[:, :, None, None] / k.sum() * np.eye(3, dtype=np.float32)
# 这个函数将图像分为低频和高频成分
def lap_split(img):
with tf.name_scope('split'):
# 做过一次卷积相当于一次“平滑”,因此lo为低频成分
lo = tf.nn.conv2d(img, k5x5, [1, 2, 2, 1], 'SAME')
# 低频成分放缩到原始图像一样大小得到lo2,再用原始图像img减去lo2,就得到高频成分hi
lo2 = tf.nn.conv2d_transpose(lo, k5x5 * 4, tf.shape(img), [1, 2, 2, 1])
hi = img - lo2
return lo, hi
# 这个函数将图像img分成n层拉普拉斯金字塔
def lap_split_n(img, n):
levels = []
for i in range(n):
# 调用lap_split将图像分为低频和高频部分
# 高频部分保存到levels中
# 低频部分再继续分解
img, hi = lap_split(img)
levels.append(hi)
levels.append(img)
return levels[::-1]
# 将拉普拉斯金字塔还原到原始图像
def lap_merge(levels):
img = levels[0]
for hi in levels[1:]:
with tf.name_scope('merge'):
img = tf.nn.conv2d_transpose(img, k5x5 * 4, tf.shape(hi), [1, 2, 2, 1]) + hi
return img
# 对img做标准化。
def normalize_std(img, eps=1e-10):
with tf.name_scope('normalize'):
std = tf.sqrt(tf.reduce_mean(tf.square(img)))
return img / tf.maximum(std, eps)
# 拉普拉斯金字塔标准化
def lap_normalize(img, scale_n=4):
img = tf.expand_dims(img, 0)
tlevels = lap_split_n(img, scale_n)
# 每一层都做一次normalize_std
tlevels = list(map(normalize_std, tlevels))
out = lap_merge(tlevels)
return out[0, :, :, :]
def tffunc(*argtypes):
placeholders = list(map(tf.placeholder, argtypes))
def wrap(f):
out = f(*placeholders)
def wrapper(*args, **kw):
return out.eval(dict(zip(placeholders, args)), session=kw.get('session'))
return wrapper
return wrap
def render_lapnorm(t_obj, img0,
iter_n=10, step=1.0, octave_n=3, octave_scale=1.4, lap_n=4):
# 同样定义目标和梯度
t_score = tf.reduce_mean(t_obj)
t_grad = tf.gradients(t_score, t_input)[0]
# 将lap_normalize转换为正常函数
lap_norm_func = tffunc(np.float32)(partial(lap_normalize, scale_n=lap_n))
img = img0.copy()
for octave in range(octave_n):
if octave > 0:
img = resize_ratio(img, octave_scale)
for i in range(iter_n):
g = calc_grad_tiled(img, t_grad)
# 唯一的区别在于我们使用lap_norm_func来标准化g!
g = lap_norm_func(g)
img += g * step
print('.', end=' ')
savearray(img, 'lapnorm.jpg')
if __name__ == '__main__':
name = 'mixed4d_3x3_bottleneck_pre_relu'
channel = 79
#img_noise = np.random.uniform(size=(224, 224, 3)) + 100.0
#读取自己定义的图像,并设定大小
img_noise= cv2.imread("timg.jpg")
img_noise=cv2.resize(img_noise,(500,400))+100.0
layer_output = graph.get_tensor_by_name("import/%s:0" % name)
render_lapnorm(layer_output[:, :, :, channel], img_noise, iter_n=100)
#这里iter_n为迭代次数,越多效果越明显
实验结果



源代码链接:Deep Dream源代码