本文是个人对《Deep Learning with Python》一书的学习笔记。使用 VSCode 下的 ipynb (python notebook, python 3.8.4 64-bit).
由于原书的代码使用的tensorflow,keras,scipy的版本较为古老,在新版本(tensorflow 2.x 等)条件下已无法直接运行。经过不断调整后代码能成功在新的版本下运行。
库的导入
先来看一下使用的库及版本。
import tensorflow as tf
import tensorflow.keras as keras
import numpy as np
import cv2 as cv
print("Tf version =",tf.__version__)
print("Keras version =",keras.__version__)
print("Numpy version =",np.__version__)
print("Opencv version =",cv.__version__)
输出:
Tf version = 2.3.1
Keras version = 2.4.0
Numpy version = 1.19.3
Opencv version = 4.2.0
模型建立
直接导入现成的 InceptionV3 模型,并设置 trainable = False
注意在导入前需要如下禁止 eager_execution 模式,这是 tensorflow 2.x 所需要的。
tf.compat.v1.disable_eager_execution()
model = keras.applications.inception_v3.InceptionV3(weights='imagenet',include_top=False)
model.trainable = False
构建Loss函数
注意此处用 loss = 0 初始化,这是与原书不同的地方。
如果查看 loss 的类型,会发现不是普通的float,而是Tensor:
<class ‘tensorflow.python.framework.ops.Tensor’>.
所以 K.gradients可以根据计算图求得 model.input 到 loss 的函数的梯度。
修改 layers_contibution 参数可以调整不同层的贡献从而得到不同的效果。
import tensorflow.keras.backend as K
layers_contribution = {'mixed2':3.0,'mixed3':1.0,'mixed4':0.2,'mixed5':0.5}
layer_dict = {layer.name : layer for layer in model.layers}
loss = 0 # 不要用 K.variable(0.)
for layer_name, contribution in layers_contribution.items():
activation = layer_dict[layer_name].output
scaling = K.prod(K.cast(K.shape(activation),'float32'))
loss += ( contribution * K.sum(K.square(activation[:, 2:-2, 2:-2, :]))/scaling )
grads = K.gradients(loss, model.get_layer('input_1').input)[0]
#grads = K.gradients(loss,model.input)[0]
grads /= K.maximum(K.mean(K.abs(grads)), 1e-7)
fetch_loss_and_grads = K.function([model.input], [loss, grads])
辅助函数
定义一些辅助函数。原书使用的 scipy.misc.imsave 已经被移除,这里替换成了 opencv.imwrite.
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))
# 将 [-1,1] 的值线性映射到 [0,255] 的整数
x /= 2.
x += 0.5
x *= 255.
x = np.clip(x, 0, 255).astype('uint8')
return x[:,:,[2,1,0]] # 需要把 RGB 通道转化为cv.imwrite需要的 BGR 通道
def resize_img(img, size):
# 注意 opencv的resize的形状参数是 (width, height)
return np.expand_dims(cv.resize(img[0], (size[1],size[0]) ) , axis = 0)
def save_img(img, fname):
cv.imwrite(fname , deprocess_image(np.copy(img)))
def preprocess_image(image_path):
img = keras.preprocessing.image.load_img(image_path)
img = keras.preprocessing.image.img_to_array(img)
img = np.expand_dims(img, axis=0)
# 返回值的 shape 是 (1,height,width,3)
return keras.applications.inception_v3.preprocess_input(img)
def gradient_ascent(x, iterations, step, max_loss=None):
for i in range(iterations):
loss_value, grad_values = fetch_loss_and_grads([x])
print('...Loss value at', i, ':', loss_value)
if max_loss is not None and loss_value > max_loss:
break
x += step * grad_values
return x
梯度上升(gradien_ascent)与梯度下降相反,可以认为是将结果加上梯度*系数,让图片产生了变化。
实验测试
选择图像的路径后,运行该段代码即可。
程序会先把原图缩小,从缩小后的图片开始处理。再逐步放大图片处理,直至大小和输入图像相同。
因为不涉及神经网络训练,所以运行时间不长。我的运行时间:98.1s.
step = 1e-2 # 系数
num_octave = 3 # 放大 num_octave 次
octave_scale = 1.4 # 每次放大的比例
iterations = 20 # 每次图片处理的最大次数
max_loss = 10. # 最大允许损失值
# 修改为自己的路径
origin_dir = 'D:\\Python Projects\\Neural Network\\GAN\\DeepDream'
# img 的 shape 是 (1,height,width,3)
img = preprocess_image(origin_dir + '\\base_image.png')
original_shape = img.shape[1:3]
successive_shapes = [original_shape]
for i in range(1, num_octave):
shape = tuple([int(dim / (octave_scale ** i)) for dim in original_shape])
successive_shapes.append(shape)
successive_shapes = successive_shapes[::-1]
original_img = np.copy(img)
shrunk_original_img = resize_img(img, successive_shapes[0])
for shape in successive_shapes:
print('Processing image shape', shape)
img = resize_img(img, shape)
img = gradient_ascent(img, iterations=iterations, step=step, max_loss=max_loss)
# 向 img 加入因为放大而失真的部分
upscaled_shrunk_original_img = resize_img(shrunk_original_img, shape)
same_size_original = resize_img(original_img, shape)
lost_detail = same_size_original - upscaled_shrunk_original_img
img += lost_detail
shrunk_original_img = resize_img(original_img, shape)
save_img(img, fname=origin_dir + '\\dream_at_scale_' + str(shape) + '.png')
测试用原图:
最终结果:出现了很多奇幻的纹理。