Про Deep Dream с примером

Знаменитые «сны» нейросетей, которые заполонили интернет в 2015 году — это заслуга DeepDream от Google. Однако сама технология их создания родилась раньше и изначально применялась, например, для синтеза текстур. Проходя курсы по CV, одним из блоков у меня было изучение ее. В ходе статьи буду опираться на гугл колаб ноутбук и личные наблюдения.

Кстати DD была создана нашим соотечественником Александром Мордвинцевым.
Кстати DD была создана нашим соотечественником Александром Мордвинцевым.

Let's train)

Импортируем библиотеки и скачиваем веса InceptionV3 ImageNet - https://www.tensorflow.org/api_docs/python/tf/keras/applications/inception_v3

imp
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
tf.__version__

base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')

ort tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
tf.__version__

base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')

Cмотрим структуру сети, она содержит 311 слоев. Покажем начало и конец вывода, дабы на заполнять пространство простыней.

base_model.summary()

input_layer         │ (None, None,      │          0 │ -                 │
│ (InputLayer)        │ None, 3)          │            │                   │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ conv2d (Conv2D)     │ (None, None,      │        864 │ input_layer[0][0] │
│                     │ None, 32)         │            │                   │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ batch_normalization │ (None, None,      │         96 │ conv2d[0][0]      │
│ (BatchNormalizatio… │ None, 32)         │            │                   │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤

----------------------------------------------------------------------------
  
│ activation_85       │ (None, None,      │          0 │ batch_normalizat… │
│ (Activation)        │ None, 320)        │            │                   │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ mixed9_1            │ (None, None,      │          0 │ activation_87[0]… │
│ (Concatenate)       │ None, 768)        │            │ activation_88[0]… │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ concatenate_1       │ (None, None,      │          0 │ activation_91[0]… │
│ (Concatenate)       │ None, 768)        │            │ activation_92[0]… │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ activation_93       │ (None, None,      │          0 │ batch_normalizat… │
│ (Activation)        │ None, 192)        │            │                   │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ mixed10             │ (None, None,      │          0 │ activation_85[0]… │
│ (Concatenate)       │ None, 2048)       │            │ mixed9_1[0][0],   │

 Total params: 21,802,784 (83.17 MB)

 Trainable params: 21,768,352 (83.04 MB)

 Non-trainable params: 34,432 (134.50 KB)

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

  • mixed3 (нижние слои) — реагирует на простые формы: линии, углы, текстуры, пятна.

  • mixed5 (средние слои) — реагирует на более сложные паттеры: глаза, лапы и т.д.

#определяем слои
names = ['mixed3', 'mixed5']

Создаем новую модель:

#Узнаем сколько слоев в базовой модели
len(base_model.layers)

#смотрим на входные данные модели
base_model.input

#Создание новой модели для DeepDream
layers = [base_model.get_layer(name).output for name in names]
deep_dream_model = tf.keras.Model(inputs = base_model.input, outputs = layers)

#Сохраняем в файлик
!pip list > res.txt

Загружаем и препроцессим любое изображение (в моем случае это картина серфера)

image = tf.keras.preprocessing.image.load_img('ВАШЕ_ИЗОБРАЖЕНИЕ',target_size=(225, 375))
plt.imshow(image) - открываем картинку
plt.imshow(image) - открываем картинку
#Узнаем тип (класс) объекта
type(image)

#Получаем размер картинки в пикселях (ширина, высота)
image.size

# Проверяем режим изображения и количество каналов
('RGB', 3)

#Преобразуем изображение в плоский список всех пикселей
list(image.getdata())

# Конвертируем изображение из формата PIL в массив NumPy
image = tf.keras.preprocessing.image.img_to_array(image)

# Проверяем тип объекта - теперь это <class 'numpy.ndarray'>
type(image)

# Смотрим форму массива
image.shape

# Проверяем диапазон значений в массиве (0, 255) - минимальное 0, максимальное 255
image.min(), image.max()

#получаем

(np.float32(0.0), np.float32(255.0))

Преобразуем значения пикселей в тот формат, который ожидает модель InceptionV3 (preprocess_input() - приводим числа к тому формату, который понимает нейросеть.

image = tf.keras.applications.inception_v3.preprocess_input(image)

image.min(), image.max()

#получаем

(np.float32(-1.0), np.float32(1.0))

Дальше посмотрим что происходит внутри нейросети в реальном времени, когда она обрабатывает изображение вызывая метод predict(image_batch)

activations = deep_dream_model.predict(image_batch)
deep_dream_model.outputs
activations[1]

Смотрим вывод :

начало

array([[[[2.3790298e+00, 1.8560309e+00, 0.0000000e+00, ...,
          0.0000000e+00, 0.0000000e+00, 0.0000000e+00],
         [0.0000000e+00, 0.0000000e+00, 1.7179147e+00, ...,
          0.0000000e+00, 0.0000000e+00, 0.0000000e+00],
         [0.0000000e+00, 1.8772808e+00, 0.0000000e+00, ...,
          0.0000000e+00, 0.0000000e+00, 0.0000000e+00],

------------------------------------------------------------4 - 5 слоев

         [0.0000000e+00, 3.0609429e-01, 1.9720533e-01, ...,
          3.3833697e-01, 0.0000000e+00, 0.0000000e+00],
         [0.0000000e+00, 0.0000000e+00, 6.1899835e-01, ...,
          2.5179255e-01, 0.0000000e+00, 0.0000000e+00],
         [0.0000000e+00, 0.0000000e+00, 0.0000000e+00, ...,
          1.3538781e-01, 0.0000000e+00, 0.0000000e+00]]]], dtype=float32)

конец

смотрим форму :
  
activations[0].shape, activations[1].shape
((1, 12, 21, 768), (1, 12, 21, 768))

аctivations — это список массивов чисел, где каждый массив соответствует одному из выбранных слоев (mixed3, mixed5)

Считаем loss

def calculate_loss(image, network):
  image_batch = tf.expand_dims(image, axis = 0)
  activations = network(image_batch)

  losses = []
  for act in activations:
    loss = tf.math.reduce_mean(act)
    losses.append(loss)

  #print(losses)
  #print(np.shape(losses))
  #print(tf.reduce_sum(losses))

  return tf.reduce_sum(losses)

loss = calculate_loss(image, deep_dream_model)
loss

Получаем 0.59 — это средний уровень "возбуждения" нейронов в выбранных слоях (mixed3, mixed5)

<tf.Tensor: shape=(), dtype=float32, numpy=0.5989360809326172> 

Если ближе к 0 — тем меньше галлюцинаций (скучное изображение)

Если дальше от 0 — тем ярче её "галлюцинации" (уже не скучно)

Формируем градиентный спуск ( буквально процесс воплощения галлюцинаций нейросети в пикселях )

@tf.function
def deep_dream(network, image, learning_rate):
  with tf.GradientTape() as tape:
    tape.watch(image)
    loss = calculate_loss(image, network)

  gradients = tape.gradient(loss, image) # Derivate
  gradients /= tf.math.reduce_std(gradients)
  image = image + gradients * learning_rate
  image = tf.clip_by_value(image, -1, 1)

  return loss, image

def inverse_transform(image):
  image = 255 * (image + 1.0) / 2.0
  return tf.cast(image, tf.uint8)

def run_deep_dream(network, image, epochs, learning_rate):
  for epoch in range(epochs):
    loss, image = deep_dream(network, image, learning_rate)

    if epoch % 200 == 0:
      plt.figure(figsize=(12,12))
      plt.imshow(inverse_transform(image))
      plt.show()
      print('Epoch {}, loss {}'.format(epoch, loss))

Запускаем генерцию снов!

run_deep_dream(network=deep_dream_model, image=image, epochs = 8000, learning_rate=0.0001)

Смотрим на результат с каждой эпохи картинка погружается в "сон"

2 эпоха
2 эпоха
20 эпоха
20 эпоха
40 эпоха
40 эпоха

Deep Dream показал, что нейросети не просто вычисляют, а имеют своеобразное "воображение" — способность находить знакомые паттерны даже там, где их нет.

По сути: мы заставляем ИИ делиться своими "внутренними видениями" и воплощаем их в реальные изображения.

Ссылка на колаб - https://colab.research.google.com/drive/1Yp2IH3dpcaUUqOd2yvkxOZqTto4uGOFM?usp=sharing

До новых встреч)

Комментарии (0)