Keras深度学习——DeepDream算法生成图像

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

0. 前言

对抗样本生成中,我们通过略微修改了输入图像的像素值以改变图像类别。在本节中,我们同样对输入图像略微进行一些修改,但并不以改变图像的标签为目标,本节的目标是令修改后的图像比原始图像更具艺术感。本节所介绍算法也是我们之后将要介绍的神经风格迁移技术的核心。

1. DreepDream 原理

首先,我们先了解 DeepDream 的技术原理。我们已经知道,根据输入图像,预训练模型中的某些卷积核激活较大,而某些卷积核激活较少。我们通过预训练的模型传递图像,并使用我们希望获得激活的神经网络层。神经网络会调整输入像素值,直到令所选图层的激活值最大。 同时,我们还需要确保激活的最大值不超过设定的阈值,因为我们不希望生成的图像与原始图像有巨大差异。

1.1 模型分析

DeepDream 有了简单的了解后,接下来,我们制定实现 DeepDream 算法的策略:

  • 选择需要最大化激活的神经网络层,并给这些层分配更大的权重以增加它们在总损失中的比重
  • 提取给定神经网络层得到的图像特征,并计算每一层的损失值:
    • 当该层中图像输出的平方和最高时,该图像在该层的激活值最大
    • 提取输入像素值相对于损失的梯度变化
  • 根据提取的梯度变化更新输入图像像素值
  • 为更新的输入图像像素值计算所有选定网络层上的损失值,即网络层激活值的平方和
  • 如果损失值大于预定义的阈值,则停止更新图像

2. DeepDream 算法实现

在本节中,我们使用 keras 实现 DeepDream 算法,生成更具艺术风格的图像。 导入相关的库,并加载图片:

from keras import backend as K
from keras.applications.vgg19 import VGG19
from keras.preprocessing import image
import matplotlib.pyplot as plt
import numpy as np
import cv2
from PIL import Image

file_path = '5.png'
img_nrows = 224
img_ncols = 224
original_shape = cv2.imread(file_path).shape
复制代码

定义图像预处理函数,以便随后可以将其传递给VGG19模型:

def preprocess_image(image_path):
    img = image.load_img(image_path, target_size=(img_nrows, img_ncols))
    img = image.img_to_array(img)
    # 维度扩展
    img = np.expand_dims(img, axis=0)
    # 数据预处理
    img[:, :, :, 0] -= 103.939
    img[:, :, :, 1] -= 116.779
    img[:, :, :, 2] -= 123.68
    img = img[:, :, :, ::-1] / 255
    return img
复制代码

构建一个对处理后的图像进行逆操作的函数,以进行可视化:

def deprocess_image(x):
    x = x[:, :, :, ::-1] * 225
    x[:, :, :, 0] += 103.939
    x[:, :, :, 1] += 116.779
    x[:, :, :, 2] += 123.68
    x = np.clip(x, 0, 255).astype('uint8')[0]
    x = Image.fromarray(x).resize((original_shape[1], original_shape[0]))
    return x
复制代码

使用以上预处理函数 preprocess_image,预处理图像,并加载预训练的 VGG19 模型:

img = preprocess_image(file_path)
model = VGG19(include_top=False, weights='imagenet')
复制代码

定义用于总损失值计算的神经网络层,使用第 2 个和第 5 个池化层用于总损失的计算,并为它们分配权重,使不同层将对总损失值具有不同贡献,可以使用其他的网络层和权重组合,生成不同图片:

layer_contributions = {
    'block2_pool':0.5,
    'block5_pool': 1.2}
复制代码

初始化损失函数以及模型中各个层的字典:

layer_dict = dict([(layer.name, layer) for layer in model.layers])
loss = K.variable(0.)
复制代码

计算激活的总损失值,遍历选定用于计算激活的层 (layer_contributions),并记录分配给每个层的权重 (coeff)。另外,我们计算选定神经网络层的输出 (activation),并在缩放后使用激活值的平方和来更新损失值:

for layer_name in layer_contributions:
    coeff = layer_contributions[layer_name]
    activation = layer_dict[layer_name].output
    scaling = K.prod(K.cast(K.shape(activation), 'float32'))
    loss = loss + coeff * K.sum(K.square(activation)) / scaling
    print(loss)
复制代码

初始化梯度值,使用 K.gradients 方法可以用于计算损失相对于输入 dream 的梯度变化:

dream = model.input
grads = K.gradients(loss, dream)[0]
print(dream, grads)
复制代码

标准化梯度值,以使梯度的变化更加平缓:

grads /= K.maximum(K.mean(K.abs(grads)), 1e-7)
复制代码

创建函数,将输入图像 dream 映射到损失值和损失值相对于输入像素值的梯度变化:

outputs = [loss, grads]

fetch_loss_and_grads = K.function([dream], outputs)
复制代码

定义函数 eval_loss_and_grads,使用 fetch_loss_and_grads 函数计算输入图像的损失和梯度变化,并返回:

def eval_loss_and_grads(img):
    outs = fetch_loss_and_grads([img])
    loss_value = outs[0]
    grad_values = outs[1]
    return loss_value, grad_values
复制代码

多次迭代计算的损失和梯度变化更新原始图像,我们循环遍历图像 100 次,首先定义更改学习率以及最大的损失上限,即图像修改的上限:

for i in range(100):
    learning_rate = 0.01
    max_loss = 30
复制代码

接下来,计算图像的损失和梯度变化值,如果损失值大于定义的阈值,则停止修改图像:

    loss_value, grad_values = eval_loss_and_grads(img)
    if max_loss is not None and loss_value > max_loss:
        print(loss_value)
        break
    print('...Loss value at', i, ':', loss_value)
复制代码

基于梯度变化修改图像,并对图像进行逆向处理并进行可视化:

    img += learning_rate * grad_values
    img2 = deprocess_image(img.copy())
    plt.imshow(img2)
    plt.axis('off')
    plt.show()
复制代码

代码生成的最终图像如下所示:

结果图片

相关链接

Keras深度学习——使用对抗攻击生成可欺骗神经网络的图像

猜你喜欢

转载自juejin.im/post/7108321283997171742