このページは Cloud Translation API によって翻訳されました。
Switch to English

DeepDream

TensorFlow.orgで見る Google Colabで実行 GitHubでソースを表示 ノートブックをダウンロード

このチュートリアルには、DeepDreamの最小限の実装が含まれています。これは、Alexander Mordvintsevによるこのブログ投稿で説明されています。

DeepDreamは、ニューラルネットワークによって学習されたパターンを視覚化する実験です。子供が雲を見てランダムな形状を解釈しようとするときと同様に、DeepDreamは画像に表示されるパターンを過剰に解釈して拡張します。

これは、ネットワークを介して画像を転送し、特定のレイヤーのアクティブ化に関する画像の勾配を計算することによって行われます。次に、これらのアクティベーションを増やすように画像を変更し、ネットワークから見えるパターンを強化して、夢のような画像を作成します。このプロセスは「インセプショニズム」( InceptionNet 、および映画 「インセプション」への参照)と呼ばれていました。

ニューラルネットワークを「夢」にして、画像に表示されるシュールなパターンを強化する方法を説明しましょう。

Dogception

import tensorflow as tf
import numpy as np

import matplotlib as mpl

import IPython.display as display
import PIL.Image

from tensorflow.keras.preprocessing import image

夢を実現する画像を選択してください

このチュートリアルでは、 ラブラドールの画像を使用します。

url = 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg'
# Download an image and read it into a NumPy array.
def download(url, max_dim=None):
  name = url.split('/')[-1]
  image_path = tf.keras.utils.get_file(name, origin=url)
  img = PIL.Image.open(image_path)
  if max_dim:
    img.thumbnail((max_dim, max_dim))
  return np.array(img)

# Normalize an image
def deprocess(img):
  img = 255*(img + 1.0)/2.0
  return tf.cast(img, tf.uint8)

# Display an image
def show(img):
  display.display(PIL.Image.fromarray(np.array(img)))


# Downsizing the image makes it easier to work with.
original_img = download(url, max_dim=500)
show(original_img)
display.display(display.HTML('Image cc-by: <a "href=https://commons.wikimedia.org/wiki/File:Felis_catus-cat_on_snow.jpg">Von.grzanka</a>'))

png

特徴抽出モデルを準備する

事前トレーニング済みの画像分類モデルをダウンロードして準備します。最初はDeepDreamで使用されていたモデルと同様のInceptionV3を使用します。これを変更する場合は以下のレイヤー名を調整する必要がありますが、 事前トレーニング済みのモデルはすべて機能することに注意してください。

base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/inception_v3/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5
87916544/87910968 [==============================] - 2s 0us/step

DeepDreamのアイデアは、1つまたは複数のレイヤーを選択し、画像がレイヤーを次第に「興奮」させるように「損失」を最大化することです。組み込まれた機能の複雑さは、選択したレイヤーに依存します。つまり、下のレイヤーはストロークまたは単純なパターンを生成し、深いレイヤーは画像またはオブジェクト全体に洗練された機能を提供します。

InceptionV3アーキテクチャは非常に大きいです(モデルアーキテクチャのグラフについては、TensorFlowのリサーチリポジトリをご覧ください)。 DeepDreamの場合、対象となるレイヤーは、たたみ込みが連結されるレイヤーです。 InceptionV3には、「mixed0」から「mixed10」までの11のレイヤーがあります。別のレイヤーを使用すると、別の夢のような画像になります。より深いレイヤーはより高いレベルの機能(目や顔など)に応答し、以前のレイヤーはより単純な機能(エッジ、形状、テクスチャなど)に応答します。以下で選択したレイヤーを自由に試してみてください。ただし、グラデーションの計算が深いため、より深いレイヤー(インデックスが高いレイヤー)のトレーニングには時間がかかることに注意してください。

# Maximize the activations of these layers
names = ['mixed3', 'mixed5']
layers = [base_model.get_layer(name).output for name in names]

# Create the feature extraction model
dream_model = tf.keras.Model(inputs=base_model.input, outputs=layers)

損失を計算する

損失は​​、選択したレイヤーのアクティブ化の合計です。損失は​​各レイヤーで正規化されるため、大きなレイヤーからの寄与が小さなレイヤーを上回ることはありません。通常、損失は勾配降下法で最小化したい量です。 DeepDreamでは、勾配上昇によってこの損失を最大化します。

def calc_loss(img, model):
  # Pass forward the image through the model to retrieve the activations.
  # Converts the image into a batch of size 1.
  img_batch = tf.expand_dims(img, axis=0)
  layer_activations = model(img_batch)
  if len(layer_activations) == 1:
    layer_activations = [layer_activations]

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

  return  tf.reduce_sum(losses)

勾配上昇

選択したレイヤーの損失を計算したら、あとは画像に関する勾配を計算し、元の画像に追加するだけです。

画像にグラデーションを追加すると、ネットワークから見えるパターンが強調されます。各ステップで、ネットワーク内の特定のレイヤーのアクティブ化をますます刺激するイメージを作成します。

以下では、これを行うtf.functionをパフォーマンスのためにtf.functionでラップしています。これはinput_signatureを使用して、異なる画像サイズまたはsteps / step_size値に対して関数がinput_signatureれないようにします。詳細については、 具象関数ガイドを参照してください。

class DeepDream(tf.Module):
  def __init__(self, model):
    self.model = model

  @tf.function(
      input_signature=(
        tf.TensorSpec(shape=[None,None,3], dtype=tf.float32),
        tf.TensorSpec(shape=[], dtype=tf.int32),
        tf.TensorSpec(shape=[], dtype=tf.float32),)
  )
  def __call__(self, img, steps, step_size):
      print("Tracing")
      loss = tf.constant(0.0)
      for n in tf.range(steps):
        with tf.GradientTape() as tape:
          # This needs gradients relative to `img`
          # `GradientTape` only watches `tf.Variable`s by default
          tape.watch(img)
          loss = calc_loss(img, self.model)

        # Calculate the gradient of the loss with respect to the pixels of the input image.
        gradients = tape.gradient(loss, img)

        # Normalize the gradients.
        gradients /= tf.math.reduce_std(gradients) + 1e-8 
        
        # In gradient ascent, the "loss" is maximized so that the input image increasingly "excites" the layers.
        # You can update the image by directly adding the gradients (because they're the same shape!)
        img = img + gradients*step_size
        img = tf.clip_by_value(img, -1, 1)

      return loss, img
deepdream = DeepDream(dream_model)

メインループ

def run_deep_dream_simple(img, steps=100, step_size=0.01):
  # Convert from uint8 to the range expected by the model.
  img = tf.keras.applications.inception_v3.preprocess_input(img)
  img = tf.convert_to_tensor(img)
  step_size = tf.convert_to_tensor(step_size)
  steps_remaining = steps
  step = 0
  while steps_remaining:
    if steps_remaining>100:
      run_steps = tf.constant(100)
    else:
      run_steps = tf.constant(steps_remaining)
    steps_remaining -= run_steps
    step += run_steps

    loss, img = deepdream(img, run_steps, tf.constant(step_size))
    
    display.clear_output(wait=True)
    show(deprocess(img))
    print ("Step {}, loss {}".format(step, loss))


  result = deprocess(img)
  display.clear_output(wait=True)
  show(result)

  return result
dream_img = run_deep_dream_simple(img=original_img, 
                                  steps=100, step_size=0.01)

png

1オクターブ上げる

かなり良いですが、この最初の試みにはいくつかの問題があります。

  1. 出力はノイズが多いです(これはtf.image.total_variation lossで対処できます)。
  2. 画像の解像度が低い。
  3. パターンは、すべて同じ粒度で発生しているように見えます。

これらすべての問題に対処する1つのアプローチは、異なるスケールで勾配上昇を適用することです。これにより、小さいスケールで生成されたパターンを、より高いスケールのパターンに組み込み、詳細を追加することができます。

これを行うには、前の勾配上昇アプローチを実行してから、画像のサイズ(オクターブと呼ばれます)を大きくし、このプロセスを複数のオクターブに対して繰り返します。

import time
start = time.time()

OCTAVE_SCALE = 1.30

img = tf.constant(np.array(original_img))
base_shape = tf.shape(img)[:-1]
float_base_shape = tf.cast(base_shape, tf.float32)

for n in range(-2, 3):
  new_shape = tf.cast(float_base_shape*(OCTAVE_SCALE**n), tf.int32)

  img = tf.image.resize(img, new_shape).numpy()

  img = run_deep_dream_simple(img=img, steps=50, step_size=0.01)

display.clear_output(wait=True)
img = tf.image.resize(img, base_shape)
img = tf.image.convert_image_dtype(img/255.0, dtype=tf.uint8)
show(img)

end = time.time()
end-start

png

5.453245639801025

オプション:タイルを使用した拡大

考慮すべきことの1つは、画像のサイズが大きくなると、勾配計算を実行するために必要な時間とメモリも増えることです。上記のオクターブの実装は、非常に大きな画像、または多くのオクターブでは機能しません。

この問題を回避するには、画像をタイルに分割し、各タイルのグラデーションを計算します。

各タイル計算の前にランダムシフトを画像に適用すると、タイルの継ぎ目が表示されなくなります。

ランダムシフトを実装することから始めます。

def random_roll(img, maxroll):
  # Randomly shift the image to avoid tiled boundaries.
  shift = tf.random.uniform(shape=[2], minval=-maxroll, maxval=maxroll, dtype=tf.int32)
  shift_down, shift_right = shift[0],shift[1] 
  img_rolled = tf.roll(tf.roll(img, shift_right, axis=1), shift_down, axis=0)
  return shift_down, shift_right, img_rolled
shift_down, shift_right, img_rolled = random_roll(np.array(original_img), 512)
show(img_rolled)

png

以下は、前に定義したdeepdream関数のタイル状の同等物です。

class TiledGradients(tf.Module):
  def __init__(self, model):
    self.model = model

  @tf.function(
      input_signature=(
        tf.TensorSpec(shape=[None,None,3], dtype=tf.float32),
        tf.TensorSpec(shape=[], dtype=tf.int32),)
  )
  def __call__(self, img, tile_size=512):
    shift_down, shift_right, img_rolled = random_roll(img, tile_size)

    # Initialize the image gradients to zero.
    gradients = tf.zeros_like(img_rolled)
    
    # Skip the last tile, unless there's only one tile.
    xs = tf.range(0, img_rolled.shape[0], tile_size)[:-1]
    if not tf.cast(len(xs), bool):
      xs = tf.constant([0])
    ys = tf.range(0, img_rolled.shape[1], tile_size)[:-1]
    if not tf.cast(len(ys), bool):
      ys = tf.constant([0])

    for x in xs:
      for y in ys:
        # Calculate the gradients for this tile.
        with tf.GradientTape() as tape:
          # This needs gradients relative to `img_rolled`.
          # `GradientTape` only watches `tf.Variable`s by default.
          tape.watch(img_rolled)

          # Extract a tile out of the image.
          img_tile = img_rolled[x:x+tile_size, y:y+tile_size]
          loss = calc_loss(img_tile, self.model)

        # Update the image gradients for this tile.
        gradients = gradients + tape.gradient(loss, img_rolled)

    # Undo the random shift applied to the image and its gradients.
    gradients = tf.roll(tf.roll(gradients, -shift_right, axis=1), -shift_down, axis=0)

    # Normalize the gradients.
    gradients /= tf.math.reduce_std(gradients) + 1e-8 

    return gradients 
get_tiled_gradients = TiledGradients(dream_model)

これをまとめると、スケーラブルなオクターブ対応のディープドリーム実装が得られます。

def run_deep_dream_with_octaves(img, steps_per_octave=100, step_size=0.01, 
                                octaves=range(-2,3), octave_scale=1.3):
  base_shape = tf.shape(img)
  img = tf.keras.preprocessing.image.img_to_array(img)
  img = tf.keras.applications.inception_v3.preprocess_input(img)

  initial_shape = img.shape[:-1]
  img = tf.image.resize(img, initial_shape)
  for octave in octaves:
    # Scale the image based on the octave
    new_size = tf.cast(tf.convert_to_tensor(base_shape[:-1]), tf.float32)*(octave_scale**octave)
    img = tf.image.resize(img, tf.cast(new_size, tf.int32))

    for step in range(steps_per_octave):
      gradients = get_tiled_gradients(img)
      img = img + gradients*step_size
      img = tf.clip_by_value(img, -1, 1)

      if step % 10 == 0:
        display.clear_output(wait=True)
        show(deprocess(img))
        print ("Octave {}, Step {}".format(octave, step))
    
  result = deprocess(img)
  return result
img = run_deep_dream_with_octaves(img=original_img, step_size=0.01)

display.clear_output(wait=True)
img = tf.image.resize(img, base_shape)
img = tf.image.convert_image_dtype(img/255.0, dtype=tf.uint8)
show(img)

png

ずっといい!オクターブ、オクターブスケール、アクティブ化されたレイヤーの数をいじって、DeepDreamで編集された画像の外観を変更します。

読者は、このチュートリアルで紹介されたアイデアを拡張してニューラルネットワークを視覚化および解釈するTensorFlow Lucidにも興味を持つ可能性があります。