DeepDream для глубокого обучения Python

глубокое обучение Python
DeepDream для глубокого обучения Python

Это 14-й день моего участия в августовском испытании обновлений.Подробности о событии:Испытание августовского обновления

Deep Learning with Python

Эта статья — одна из серии заметок, которые я написал, изучая Deep Learning with Python (второе издание, Франсуа Шолле). Содержимое статьи конвертировано из блокнотов Jupyter в Markdown, вы можете перейти наGitHubилиGiteeнайти оригинал.ipynbноутбук.

ты можешь идтиЧитайте оригинальный текст этой книги онлайн на этом сайте(Английский). Автор этой книги также дает соответствиеJupyter notebooks.

Эта статьяГлава 8 Генеративное глубокое обучение (Chapter 8. Generative deep learning) одной из записок.

8.2 DeepDream

DeepDream — это технология, которая позволяет машинам изменять изображения, используя сверточные нейронные сети для создания очень психоделических изображений:

DeepDream 输出图像示例

Поскольку CNN, используемая DeepDream, обучена на ImageNet, а ImageNet имеет большое количество изображений животных, изображения, созданные DeepDream, содержат много артефактов животных и частей животных.

Алгоритм, используемый DeepDream, очень похож на метод визуализации фильтра сверточной нейронной сети. Вспомните, что делает визуализация фильтра сверточной нейронной сети: запускайте сверточную нейронную сеть в обратном порядке, беря на вход пустое изображение со случайным шумом, и выполняйте градиентное восхождение, чтобы максимизировать активацию определенного фильтра.

Основные различия между DeepDream и визуализацией фильтров:

  • В DeepDream мы пытаемся максимально активировать все слои, а не только один слой, чтобы смешать множество визуальных особенностей, что приводит к более психоделическим изображениям.
  • Если начать с существующего изображения, а не со случайным шумом, получится изображение, которое включает в себя визуальные паттерны, уже присутствующие во входном изображении, и искажает некоторые его элементы, что приводит к более психоделическому изображению.
  • Входное изображение обрабатывается в разных масштабах — эти масштабы называются «октавами», что позволяет улучшить качество вывода.

Реализация DeepDream с Keras

Перед запуском нам нужно отключить режим точного выполнения Tensorflow 2.x, см.tensorflow #33135.

import tensorflow as tf
tf.compat.v1.disable_eager_execution()

Затем на первом шаге выберите предварительно обученную на ImageNet свёрточную нейронную сеть: VGG16, Inception, ResNet50 и т. д. Практика показала, что Inception можно генерировать лучше, поэтому здесь мы используем встроенную в Inception V3 модель Keras.

Загрузите предварительно обученную модель Inception V3:

from tensorflow.keras.applications import inception_v3
from tensorflow.keras import backend as K

K.set_learning_phase(0)

model = inception_v3.InceptionV3(weights='imagenet', include_top=False)

Затем определите потери - количество, которое необходимо максимизировать с помощью градиентного подъема. В DeepDream мы хотим максимизировать активацию всех фильтров одновременно на нескольких слоях. Конкретная реализация: взвешенное суммирование нормы L2 набора активаций слоя около вершины, а затем максимизация этого значения. Выбор слоев и распределение весов имеют большое влияние на генерируемые результаты:

  • Слои в нижней части генерируют основные геометрические узоры;
  • Слои ближе к верху генерируют изображения, которые показывают определенные объекты (шаблоны в ImageNet, такие как птицы или собаки).

Выведите структуру модели Inception V3 (можно использоватьtf.keras.utils.plot_model(model)), выберите несколько слоев случайным образом, здесь выбраны смешанный4, смешанный5, смешанный6 и смешанный7.

Запишите эти необходимые слои в конфигурацию DeepDream:

layer_contributions = {
    'mixed4': 0.0,
    'mixed5': 3.0,
    'mixed6': 2.0,
    'mixed7': 1.5,
}

Затем нам нужно рассчитать потери для этих выбранных слоев. Определите потери, которые необходимо максимизировать:

layer_dict = dict([(layer.name, layer) for layer in model.layers])

loss = K.variable(0.)

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 += coeff * K.sum(K.square(activation[:, 2: -2, :])) / scaling
    # 应该用下面的代码?。参考:https://github.com/fchollet/deep-learning-with-python-notebooks/issues/43
    loss = loss + coeff * K.sum(K.square(activation[:, 2: -2, :])) / scaling

Сделайте градиентное восхождение на проигрыше:

dream = model.input

grads = K.gradients(loss, dream)[0]

grads /= K.maximum(K.mean(K.abs(grads)), 1e-7)

outputs = [loss, grads]
fetch_loss_and_grads = K.function([dream], outputs)

def eval_loss_and_grads(x):
    outs = fetch_loss_and_grads([x])
    loss_value = outs[0]
    grad_values = outs[1]
    return loss_value, grad_values

def gradient_ascent(x, iterations, step, max_loss=None):
    for i in range(iterations):
        loss_value, grad_values = eval_loss_and_grads(x)
        if max_loss is not None and loss_value > max_loss:
            break
        print(f'   loss value at {i}: {loss_value}')
        x += step * grad_values
    return x

Наконец, реализуйте алгоритм DeepDream: сначала определите список гамм (также называемых октавами, октавами), которые содержат изображения, подлежащие обработке. Изображение последней шкалы увеличено в определенный раз по сравнению с первой. DeepDream следует этому списку от меньшего к большему, запускает градиентное восхождение в текущем масштабе, а затем увеличивает результирующее изображение. После увеличения изображение становится размытым, поэтому потерянные детали снова добавляются в изображение.

DeepDream 过程:空间处理尺度的连续放大(八度)与放大时重新注入细节

Некоторые вспомогательные функции:

import scipy
import imageio

from tensorflow.keras.preprocessing import image

def resize_img(img, size):
    img = np.copy(img)
    factors = (1, 
               float(size[0]) / img.shape[1], 
               float(size[1]) / img.shape[2], 
               1)
    return scipy.ndimage.zoom(img, factors, order=1)

def save_img(img, fname):
    pil_img = deprocess_image(np.copy(img))
    # scipy.misc.imsave(fname, pil_img)
    imageio.imsave(fname, pil_img)

Откройте изображение, измените его размер и преобразуйте формат изображения в тензор, который может обрабатывать модель Inception V3:

def preprocess_image(image_path):
    img = image.load_img(image_path)
    img = image.img_to_array(img)
    img = np.expand_dims(img, axis=0)
    img = inception_v3.preprocess_input(img)
    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

Запустите градиентное восхождение в нескольких непрерывных масштабах:

import numpy as np

step = 0.01     # 梯度上升的步长
num_octave = 3  # 尺度个数
octave_scale = 1.4 # 两个尺度的大小比例
iterations = 20 # 在每个尺度上运行梯度上升的步数

max_loss = 10.  # 损失上升的太大时中止梯度上升,以免得到丑陋的伪影

base_image_path = './img.png'

img = preprocess_image(base_image_path)

original_shape = img.shape[1:3]
successive_shapes = [original_shape]
for i in range(1, num_octave):
    shape = tuple([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)
    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=f'dream_at_scale_{shape}.png')
    
save_img(img, fname='final_dream.png')

Окончательный результат:

DeepDream结果

Вы можете видеть, что DeepDream нарисовал несколько собак на картинке final_dream ?.

Примечание. Из-за размера исходного обучения Inception V3 реализованная здесь реализация DeepDream может достигать лучших результатов на изображениях размером от 300×300 до 400×400, но это не является строгим ограничением, все размеры возможны.


Примечание. Улучшенный DeepDream также можно легко реализовать с помощью активного режима TensorFlow 2, см. подробности.Это официальное руководство по TensorFlow.