DeepDream算法的实现方式是对卷积神经网络的输入做梯度上升,以便将卷积神经网络靠顶部的某一层的某个过滤器激活最大化。有如下特征:
- 使用DeepDream,尝试将所有层(多层)的激活最大化,而不是将某一层的激活最大化。这样可以将大量的可视化混合。
- 输入图像在不同的尺度上(八度octave)进行处理,可以提高可视化的质量。
from keras.applications import inception_v3
from keras import backend as K
import numpy as np
import scipy
import scipy.ndimage
import scipy.misc
from keras.preprocessing import image
# 使用的是Keras内置的Inception V3模型
K.set_learning_phase(0) # 禁用模型用于训练相关的操作
# 载入模型,使用预训练的ImageNet权重;不包括全连接层
model = inception_v3.InceptionV3(weights='imagenet',
include_top=False)
# print(model.summary())
挑出模型中的几个层,并设置上权重
layer_contributions = {
'mixed2': 0.2,
'mixed3': 3.0,
'mixed4': 2.0,
'mixed5': 1.5,
}
#创建一个字典,将层的名称映射为层的实例
layer_dict = dict([(layer.name, layer) for layer in model.layers])
定义损失函数loss
loss = K.variable(0.)
# print(K.eval(loss))
for layer_name in layer_contributions:
coeff = layer_contributions[layer_name]
activation = layer_dict[layer_name].output
# print(activation.shape)
scaling = K.prod(K.cast(K.shape(activation), 'float32')) #其实就是计算层输出有几个元素
loss += coeff * K.sum(K.square(activation[:, 2:-2, 2:-2, :])) / scaling # 2:-2是为了去掉边界像素
梯度上升过程
dream = model.input # 保存生成的图像,或者说是梦境图像
grads = K.gradients(loss, dream)[0] # 计算损失相对于输入图像的梯度,取出具体的梯度,别忘记[0]
grads /= K.maximum(K.mean(K.abs(grads)), 1e-7) # 标准化梯度
fetch_loss_and_grads = K.function([dream], [loss, grads])
def get_loss_grads(x): # x为图像矩阵
ret = fetch_loss_and_grads([x]) # 注意,这里要先把x变成list,所以是[x]而不是x
loss_val = ret[0]
grad_val = ret[1]
return loss_val, grad_val
# max_loss被设置为10
def gradient_ascent(x, iterations, step, max_loss=None):
for i in range(iterations):
loss_val, grad_val = get_loss_grads(x)
if max_loss is not None and loss_val > max_loss:
break
print(f'>>> Loss value at {i}: {loss_val}')
x += step * grad_val
return x
DeepDream需要多个连续的不同尺寸的相同图像,这里设置成后一个图像尺寸为前一个的1.4倍,先处理小图像,然后逐渐放大图像;在每个尺寸的图像上都做20次梯度上升(如果损失超过max_loss10的话就直接进入下一个尺寸)
path = 'img2.jpg' # 自定义图像路径
step = 1e-2 # 0.01
num_octave = 3
octave_scale = 1.4 # 放大倍数
iterations = 20 # 每个octave做得梯度上升次数
max_loss = 10.0
# 设计两次图像八度提升,同时进行图像补充
img = preprocess_img(path)
original_size = img.shape[1:3]
successive_shape = []
for i in range(num_octave): # i: 0 1 2
t = tuple([int(dim / octave_scale ** i) for dim in original_size])
successive_shape.append(t)
successive_shape = successive_shape[::-1] # 反转列表顺序,使其从小到大排列
图像在放大过程中许多细节会损失,下面的代码被称为细节注入,其实就是拿原尺寸的大图缩小到当前尺寸然后减去从更小的尺寸放大到当前尺寸的图像矩阵,获得的差值就是要注入的细节。
# 缩放图片,并注入细节
original_img = np.copy(img)
shrunk_original_img = resize_img(img, successive_shape[0])
for shape in successive_shape:
print('processing image shape:', img.shape)
img = resize_img(img, shape)
print('processed image shape:', img.shape)
img = gradient_ascent(img,
iterations=iterations,
step=step,
max_loss=max_loss) # 做梯度上升
S_original_img = resize_img(original_img, shape)
shrunk_original_img = resize_img(shrunk_original_img, shape)
loss_detail = S_original_img - shrunk_original_img # 计算丢失的细节
img += loss_detail
shrunk_original_img = resize_img(original_img, shape)
save_img(img, f'dream scale at {str(shape)}.png')
save_img(img, 'final_dream.png')
用到的一些自定义的辅助函数
# 通过scipy构建一些辅助函数
def resize_img(img, size):
t = np.copy(img)
arg = (1,
float(size[0])/img.shape[1],
float(size[1])/img.shape[2],
1)
return scipy.ndimage.zoom(t, arg, order=1)
def save_img(img, fname):
pil_img = deprocess_image(np.copy(img))
scipy.misc.imsave(fname, pil_img)
def preprocess_img(path):
img = image.load_img(path)
img = image.img_to_array(img)
img = np.expand_dims(img, axis=0) # img.shape: (1, 1080, 1920, 3)
img = inception_v3.preprocess_input(img) # 预处理图像使之成位Inception V3模型能处理的张量
return img
def deprocess_image(x): # 调整矩阵维度并
if K.image_data_format() == 'channels_first':
x = x.reshape((3, x.shape[2], x.shape[3]))
x = x.transpose((1, 2, 0)) # 交换矩阵维度
else:
x = x.reshape((x.shape[1], x.shape[2], 3))
x /= 2
x += 0.5
x *= 255
x = np.clip(x, 0, 255).astype('uint8')
return x
运行效果
原图是网上随便找了一张风景图
通过DeepDream代码实现的效果图
因为Inception V3模型在训练过程中用到了大量猫、狗图片,所以会映射出一些类似猫·、狗眼睛、嘴巴、毛的图案~