TensorFlow 2.0 : 上級 Tutorials : 生成 :- DeepDream (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/21/2019
* 本ページは、TensorFlow org サイトの TF 2.0 – Advanced Tutorials – Generative の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
Facebook: https://www.facebook.com/ClassCatJP/ |
生成 :- DeepDream
このチュートリアルは、Alexander Mordvintsev の ブログ投稿 で説明されている、DeepDream の最小限の実装を含みます。
DeepDream はニューラルネットワークにより学習されたパターンを可視化する実験です。子供が雲を見てランダムな形状を解釈しようとするときと同様に、DeepDream はそれが画像で見るパターンを過剰解釈 (= over-interpret) して強化します (= enhance)。
それはネットワークを通して画像を forward してから、特定の層の活性に関して画像の勾配を計算することによりそれを行ないます。それから画像はこれらの活性を増加するために変更されて、ネットワークにより見られたパターンを強化して、そして夢のような画像という結果になります。このプロセスは “Inceptionism” として呼称されました (InceptionNet と、映画 Inception への参照です)。
ニューラルネットワーク “dream” をどのように作成してそれが画像で見たシュールなパターンを強化できるかを実演しましょう。
from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
import numpy as np import matplotlib as mpl from IPython.display import clear_output from matplotlib import pyplot as plt from tensorflow.keras.preprocessing import image
夢にする (= dream-ify) 画像を選択する
このチュートリアルのために、ラブラドール の画像を使用しましょう。
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, target_size=None): name = url.split('/')[-1] image_path = tf.keras.utils.get_file(name, origin=url) img = tf.keras.preprocessing.image.load_img(image_path, target_size=target_size) return 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): plt.figure(figsize=(12,12)) plt.grid(False) plt.axis('off') plt.imshow(img) plt.show() # Downsizing the image makes it easier to work with. original_img = download(url, target_size=[225, 375]) original_img = np.array(original_img) show(original_img)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg 90112/83281 [================================] - 0s 0us/step
特徴抽出モデル
事前訓練された画像分類モデルをダウンロードして準備します。InceptionV3 を使用します、これは DeepDream で元々使用されたモデルに類似しています。任意の 事前訓練されたモデル は動作しますが、けれども下で層の名前を (これを変更している場合) 調整しなければならないことに注意してください。
base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.5/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5 87916544/87910968 [==============================] - 6s 0us/step
DeepDream のアイデアは層 (or 層群) を選択して画像が層を「励起する」ような方法で「損失」を段々と最大化することです。組み入れられる特徴の複雑さは貴方により選択された層に依拠します、i.e., より低い層はストローク (短い線) や単純なパターンを生成する一方で、より深い層は画像の洗練された特徴、あるいはオブジェクト全体でさえも与えます。
InceptionV3 アーキテクチャは非常に巨大です (モデル・アーキテクチャのグラフについては TensorFlow の 研究 repo を見てください)。DeepDream については、関心のある層は畳み込みが結合されてるところのものです。InceptionV3 では 11 のこれらの層があります、’mixed0′ から ‘mixed10’ と名前付けられています。異なる層の使用は異なる夢のような画像という結果になります。より深い層は (目や顔のような) 高位な特徴に対応する一方で、早い層は (エッジ、形状とテクスチャーのような) 単純な特徴に対応します。下で選択された層で自由に実験してください、しかしより深い層はその上で訓練するためにより長い時間かかることに留意してください、何故ならば勾配計算がより深いためです。
# 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) losses = [] for act in layer_activations: loss = tf.math.reduce_mean(act) losses.append(loss) return tf.reduce_sum(losses)
勾配上昇 (= gradient = ascent)
ひとたび選択された層のための損失を計算すれば、残されたことの総ては画像に関する勾配を計算してそれらを元の画像に追加することです。
勾配を画像に追加することはネットワークにより見られたパターンを強化します。各ステップで、ネットワークの特定の層の活性を段々と励起する画像を作成します。
@tf.function def deepdream(model, img, step_size): 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, 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
def run_deep_dream_simple(model, 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) for step in range(steps): loss, img = deepdream(model, img, step_size) if step % 100 == 0: clear_output(wait=True) show(deprocess(img)) print ("Step {}, loss {}".format(step, loss)) result = deprocess(img) clear_output(wait=True) show(result) return result
dream_img = run_deep_dream_simple(model=dream_model, img=original_img, steps=800, step_size=0.001)
それをオクターブ引き上げる (= Taking it up an octave)
悪くはないですが、この最初の試みでは 2, 3 の問題点があります :
- 出力は noisy です (これは tf.image.total_variation 損失で対処されるでしょう)。
- 画像は低解像度です。
- パターンはそれらは総て同じ細かさ (= granularity) で起きているようです。
これら総ての問題に対処する一つのアプローチは勾配上昇を異なるスケールで適用することです。これはより小さいスケールで生成されたパターンがより高いスケールのパターンに組み込まれるて追加の詳細で満たされることを可能にします。
これを行なうために前の勾配上昇アプローチを遂行してから画像のサイズを増やし (これはオクターブとして参照されます)、そしてこのプロセスを複数のオクターブのために繰り返すことができます。
OCTAVE_SCALE = 1.3 img = tf.constant(np.array(original_img)) base_shape = tf.cast(tf.shape(img)[:-1], tf.float32) for n in range(3): new_shape = tf.cast(base_shape*(OCTAVE_SCALE**n), tf.int32) img = tf.image.resize(img, new_shape).numpy() img = run_deep_dream_simple(model=dream_model, img=img, steps=200, step_size=0.001) clear_output(wait=True) show(img)
タイルでスケールアップする
考えるべき一つのことは画像がサイズ的に増加するために、勾配計算を遂行するために必要な時間とメモリです。上のオクターブ実装は非常に巨大な画像や多くのオクターブ上では動作しません。この問題を回避するために画像をタイルに分割して各タイルのための勾配を計算することができます。
各 tiled された計算の前に画像にランダムシフトを適用することはタイルの継ぎ目が現れることを防ぎます。
ランダムシフトを実装することから始めます :
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)
ここに先に定義された deepdream 関数の tiled された同値があります :
@tf.function def get_tiled_gradients(model, 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) for x in tf.range(0, img_rolled.shape[0], tile_size): for y in tf.range(0, img_rolled.shape[1], tile_size): # 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, 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
これをまとめるとスケーラブルで、octave-aware な deepdream 実装を与えます :
def run_deep_dream_with_octaves(model, img, steps_per_octave=100, step_size=0.01, num_octaves=3, octave_scale=1.3): img = tf.keras.preprocessing.image.img_to_array(img) img = tf.keras.applications.inception_v3.preprocess_input(img) for octave in range(num_octaves): # Scale the image based on the octave if octave>0: new_size = tf.cast(tf.convert_to_tensor(img.shape[:2]), tf.float32)*octave_scale img = tf.image.resize(img, tf.cast(new_size, tf.int32)) for step in range(steps_per_octave): gradients = get_tiled_gradients(model, img) img = img + gradients*step_size img = tf.clip_by_value(img, -1, 1) if step % 10 == 0: clear_output(wait=True) show(deprocess(img)) print ("Octave {}, Step {}".format(octave, step)) clear_output(wait=True) result = deprocess(img) show(result) return result
dream_img = run_deep_dream_with_octaves(model=dream_model, img=original_img, step_size=0.01) clear_output() show(original_img) show(dream_img)
遥かに良いです!貴方の DeepDream-ed 画像がどのように見えるかを変更するためにオクターブの数、オクターブ・スケールそして活性化された層で遊んでください。
読者はまた TensorFlow Lucid に興味があるかもしれません、これはニューラルネットワークを可視化して解釈するためにこのチュートリアルで紹介されたアイデアを拡張します。
以上