Deep Dream是Google公司在2015年公布的一项有趣的技术,在训练好的神经个网络中,只需要设定几个参数就可以通过这项技术生成一张图像,生出的图像不仅令人印象深刻,还能帮助我们更好的理解神经网络的运行机制。
1生成原始的Deep Dream图像
原始的DeepDream模型只需要优化ImageNet模型卷积层某个通道的激活值就可以了,因此应在TensorFlow中导入一个ImageNet图像识别模型,本文以Inception模型进行实验。
代码如下:
#导入要用到的基本模块
from __future__ import print_function
import tensorflow as tf
import os
from io import BytesIO
import numpy as np
from functools import partial
import PIL.Image
import scipy.misc
import tensorflow as tf
#创建图和会话
graph = tf.Graph()
sess = tf.InteractiveSession(graph=graph)
#tensorflow_inception_graph.pb文件中,既存储了inception的网络结构,也存储了对应的数据
#使用下面的语句将其导入
model_fn = 'tensorflow_inception_graph.pb'
with tf.gfile.FastGFile(model_fn,'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
#定义t_input为输入图像
t_input = tf.placeholder(np.float32,name = 'input')
imagenet_mean = 170.0
#输入图像需要经过处理才能送入网络中
#expand_dims是加一维,从[height,width,hannel]变成[1,heigh,width,channel]
#t_input-imagenet_mean是减去一个均值
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 render_naive(t_obj,img0,iter_n=20,step=1.0):
#t_score是优化目标。它是t_obj的平均值
#结合调用处看,实际上就是layer_output[:,:,:,channel]的平均值
t_score=tf.reduce_mean(t_obj)
#计算t_score对t_input的梯度
t_grad=tf.gradients(t_score,t_input)[0]
#创建新图
img=img0.copy()
for i in range(iter_n):
#在sess中计算梯度,以及当前的score
g,score=sess.run([t_grad,t_score],{t_input:img})
#对img应用梯度,step合一看做”学习率“
g/=g.std()+1e-8
img+=g*step
print('score(mean)=%f'%(score))
#保存图片
savearray(img,'chapter4-2.jpg')
#定义卷积层、通道数,并取出对应的Tensor
name = 'mixed4d_3x3_bottleneck_pre_relu'
channel = 139
layer_output = graph.get_tensor_by_name("import/%s:0"%name)
#定义原始图像的通噪声
img_noise = np.random.uniform(size=(224,224,3))+100.0
#调用render_naive函数渲染
render_naive(layer_output[:,:,:,channel],img_noise,iter_n=20)
运行完之后,会得到类似下面的输出:
说明score(也就是卷积层对应通道的平均值)确实是按照期望逐渐增大的。经过20次迭代之后,会把图片保存为chapter4-2.jpg,如下图所示
以上就是通过最大化某一通道的平均值得到的图像,此处图像生成的效果好不太好,下面会生成更加精美的Deep Dream。
2生成更大尺寸的Deep Dream图像
要生成的图片越大,就会占越大的内存,若想生成更大的图片,就会因为内存不够而导致渲染失败。为了避免这个问题,每次不对整张图片做优化,而是把图片分成几个部分,每次只对其中的一部分做优化,这样每次优化时只消耗固定的内存。这个程序中的函数calc_grad_titled可以对任意大小的图像计算梯度。代码如下:
#导入要用到的基本模块
from __future__ import print_function
import tensorflow as tf
import os
from io import BytesIO
import numpy as np
from functools import partial
import PIL.Image
import scipy.misc
import tensorflow as tf
#创建图和会话
graph = tf.Graph()
sess = tf.InteractiveSession(graph=graph)
#tensorflow_inception_graph.pb文件中,既存储了inception的网络结构,也存储了对应的数据
#使用下面的语句将其导入
model_fn = 'tensorflow_inception_graph.pb'
with tf.gfile.FastGFile(model_fn,'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
#定义t_input为输入图像
t_input = tf.placeholder(np.float32,name = 'input')
imagenet_mean = 170.0
#输入图像需要经过处理才能送入网络中
#expand_dims是加一维,从[height,width,hannel]变成[1,heigh,width,channel]
#t_input-imagenet_mean是减去一个均值
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 calc_grad_titled(img,t_grad,title_size=512):
#每次只对title_size×title_size大小的图像计算梯度,避免内存问题
sz=title_size
h,w=img.shape[:2]
#img_shift:先在行上做整体移动,再在列上做整体移动
#防止title的边缘产生边缘效应
sx,sy=np.random.randint(sz,size=2)
img_shift=np.roll(np.roll(img,sx,1),sy,0)
grad=np.zeros_like(img)
#y,x是开始位置的像素
for y in range(0,max(h-sz//2,sz),sz):
for x in range(0,max(w-sz//2,sz),sz):
#每次对sub计算梯度。sub的大小是title_size×title_size
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
#使用np.rool移回去
return np.roll(np.roll(grad,-sx,1),-sy,0)
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 render_multiscale(t_obj,img0,iter_n=10,step=1.0,octave_n=3,octave_scale=1.4):
#同样定义目标和梯度
t_score=tf.reduce_mean(t_obj)
t_gard=tf.gradients(t_score,t_input)[0]
img=img0.copy()
for octave in range(octave_n):
if octave>0:
#每次讲图片放大octave_scale倍
#共放大octane_n-1次
img=resize_ratio(img,octave_scale)
for i in range(iter_n):
#调用calc_grad_tiled计算任意图像大小的梯度
g=calc_grad_titled(img,t_gard)
g/=g.std()+1e-8
img+=g*step
print(',',end='')
savearray(img,'chapter4-3.jpg')
if __name__ == '__main__':
name='mixed4d_3x3_bottleneck_pre_relu'
channel=139
img_noise=np.random.uniform(size=(224,224,3))+100.0
layer_output=graph.get_tensor_by_name("import/%s:0"%name)
render_multiscale(layer_output[:,:,:,channel],img_noise,iter_n=20)
运行完程序后,就会生成如下图片
3生成高质量的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
graph = tf.Graph()
model_fn = 'tensorflow_inception_graph.pb'
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):
# 每次只对tile_size×tile_size大小的图像计算梯度,避免内存问题
sz = tile_size
h, w = img.shape[:2]
# img_shift:先在行上做整体移动,再在列上做整体移动
# 防止在tile的边缘产生边缘效应
sx, sy = np.random.randint(sz, size=2)
img_shift = np.roll(np.roll(img, sx, 1), sy, 0)
grad = np.zeros_like(img)
# y, x是开始位置的像素
for y in range(0, max(h - sz // 2, sz), sz):
for x in range(0, max(w - sz // 2, sz), sz):
# 每次对sub计算梯度。sub的大小是tile_size×tile_size
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
# 使用np.roll移动回去
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, 'chapter4-41.jpg')
if __name__ == '__main__':
name='mixed4d_3x3_bottleneck_pre_relu'
channel=139
img_noise=np.random.uniform(size=(224, 224, 3))+100.0
layer_output=graph.get_tensor_by_name("import/%s:0" % name)
render_lapnorm(layer_output[:, :, :, 139]+layer_output[:,:,:,99], img_noise, iter_n=20)
运行命令之后就可以在当前目录下生成图像
与前面的相比,这次确实在一定程度上提高了生成图像的质量,也可以更清楚的看到这个卷积层中的第139个通道学习到的图像特征,还可以尝试不同的通道,如channel=137
4最终的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
graph = tf.Graph()
model_fn = 'tensorflow_inception_graph.pb'
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 visstd(a,s=0.1):
return (a - a.mean()) / max(a.std(), 1e-4) * s + 0.5
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 resize(img,hw):
min=img.min()
max=img.max()
img=(img-min)/(max-min)*255
img=np.float32(scipy.misc.imresize(img,hw))
img=img/255*(max-min)+min
return img
def calc_grad_tiled(img, t_grad, tile_size=512):
# 每次只对tile_size×tile_size大小的图像计算梯度,避免内存问题
sz = tile_size
h, w = img.shape[:2]
# img_shift:先在行上做整体移动,再在列上做整体移动
# 防止在tile的边缘产生边缘效应
sx, sy = np.random.randint(sz, size=2)
img_shift = np.roll(np.roll(img, sx, 1), sy, 0)
grad = np.zeros_like(img)
# y, x是开始位置的像素
for y in range(0, max(h - sz // 2, sz), sz):
for x in range(0, max(w - sz // 2, sz), sz):
# 每次对sub计算梯度。sub的大小是tile_size×tile_size
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
# 使用np.roll移动回去
return np.roll(np.roll(grad, -sx, 1), -sy, 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_deepdream(t_obj,img0,
iter_n=10,step=1.5,octave_n=4,octave_scale=1.4):
t_score=tf.reduce_mean(t_obj)
t_grad=tf.gradients(t_score,t_input)[0]
img=img0
#同样将图像进行金字塔分解
#此时提取高频、低频的方法比较简单,直接缩放就可以
octaves=[]
for i in range(octave_n-1):
hw=img.shape[:2]
lo=resize(img,np.int32(np.float32(hw)/octave_scale))
hi=img-resize(lo,hw)
img=lo
octaves.append(hi)
#先生成低频的图像,再一次放大并加上高频
for octave in range(octave_n-1):
if octave > 0:
hi=octaves[-octave]
img=resize(img,hi.shape[:2])+hi
for i in range(iter_n):
g=calc_grad_tiled(img,t_grad)
img += g*(step/(np.abs(g).mean()+1e-7))
print('.',end='')
img=img.clip(0,255)
savearray(img,'chapter4- 5.jpg')
if __name__ == '__main__':
img0=PIL.Image.open('test2.jpg')
img0=np.float32(img0)
name='mixed4d_3x3_bottleneck_pre_relu'
channel=139
layer_output=graph.get_tensor_by_name("import/%s:0"%name)
render_deepdream(layer_output[:,:,:,channel],img0,iter_n=150)
背景图 添加背景图之后生成的图片
利用下面代码可以生成著名的含有动物的Deep Dream图片
name = 'mixed4c'
layer_output = graph.get_tensor_by_name("import/%s:0" % name)
render_deepdream(tf.square(layer_output), img0)
背景图 添加背景图之后生成的图片