DeepDream

GoogLeNetの各レイヤーを可視化する

コマンドライン引数 :

  • --layer_list : GoogLeNetのレイヤー一覧
  • --random_noise : ランダムノイズ画像に対してDeepDreamを適用する
  • --layer : 可視化するレイヤー
  • --channel : 可視化するレイヤーのチャネル
  • --in_img : 入力画像
  • --out_img : 画像の出力先(拡張子は省略する)

モデルおよびセッションデータのダウンロード :

$ wget https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip
$ unzip inception5h.zip

サンプルコード :

# coding:utf-8
"""
filename: tensorflow_deep_dream.py
"""
import numpy as np
import PIL.Image
import tensorflow as tf

# 結果が同じになるように、乱数のシードを設定する
tf.set_random_seed(20200724)
np.random.seed(20200724)

# コマンドライン引数の定義
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_bool("layer_list", False, "GoogLeNetのレイヤー一覧を表示する")
tf.app.flags.DEFINE_bool("random_noise", False, "ランダムノイズ画像を使用する")
tf.app.flags.DEFINE_string("layer", None, "画像生成に使うレイヤー名")
tf.app.flags.DEFINE_integer("channel", None, "画像生成に使うレイヤーのチャネル")
tf.app.flags.DEFINE_string("in_img", None, "入力画像のパス")
tf.app.flags.DEFINE_string("out_img", "deep_dream.jpg", "出力画像のパス 画像の拡張子は要省略")

# モデルおよびセッション情報を格納したファイル
model_fn = 'tensorflow_inception_graph.pb'
# モデルの構築とセッションの読み込み
graph = tf.Graph()
sess = tf.InteractiveSession(graph=graph)
# pbファイルを読み込む
with tf.gfile.FastGFile(model_fn, 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())

# 画像を格納するTensor
t_input = tf.placeholder(np.float32, name='input') # define the input tensor
imagenet_mean = 117.0
t_preprocessed = tf.expand_dims(t_input - imagenet_mean, 0)
tf.import_graph_def(graph_def, {'input':t_preprocessed})

# 各層の名前を出力する
def show_layer_list():
    """レイヤー一覧を表示する"""
    for op in graph.get_operations():
        if op.type=='Conv2D' and 'import/' in op.name:
            print op.name

def read_image(name):
    """画像を読み込み、nparrayに変換する"""
    img_arr = PIL.Image.open(name)
    return np.array(img_arr, dtype=np.float32)

def save_image(a, name):
    """nparrayを画像として保存する"""
    x = np.uint8(np.clip(a, 0.0, 1.0) * 255.0)
    img = PIL.Image.fromarray(x)
    img.save(name)

def visstd(a, s=0.1):
    """画像の正規化する"""
    return (a - a.mean()) / max(a.std(), 1e-4) * s + 0.5

def T(layer):
    """指定した層の出力Tensorを取得する"""
    return graph.get_tensor_by_name("import/%s:0" % layer)

def render_naive(t_obj, iter_n=20, step=1.0, name="noise_dream.jpg"):
    """ランダムノイズ画像に対してDeepdreamを適用する"""
    img0 = np.random.uniform(size=(224,224,3)) + 100.0
    # 最適化を行うTensor
    t_score = tf.reduce_mean(t_obj)
    # 自動微分の累乗
    t_grad = tf.gradients(t_score, t_input)[0]

    img = img0.copy()
    for i in range(iter_n):
        g, score = sess.run([t_grad, t_score], {t_input:img})
        # 勾配を正規化する
        g /= g.std() + 1e-8
        img += g * step
    save_image(visstd(img), name)

def tffunc(*argtypes):
    """TFグラフ関数のヘルパー関数"""
    placeholders = list(map(tf.placeholder, argtypes))
    def wrap(f):
        out = f(*placeholders)
        def wrapper(*args, **kw):
            return out.eval(dict(zip(placeholders, args)), session=kw.get('session'))
        return wrapper
    return wrap

def resize(img, size):
    """TensorFlowによって画像をリサイズする"""
    img = tf.expand_dims(img, 0)
    return tf.image.resize_bilinear(img, size)[0,:,:,:]
# tf関数化
resize = tffunc(np.float32, np.int32)(resize)

def calc_grad_tiled(img, t_grad, tile_size=512):
    """入力画像に対する勾配を計算する"""
    sz = tile_size
    h, w = img.shape[:2]
    sx, sy = np.random.randint(sz, size=2)
    # ランダムに配列の要素を回転させる
    img_shift = np.roll(np.roll(img, sx, 1), sy, 0)
    grad = np.zeros_like(img)
    for y in range(0, max(h-sz//2,sz), sz):
        for x in range(0, max(w-sz//2,sz), sz):
            sub = img_shift[y:y+sz,x:x+sz]
            g = sess.run(t_grad, {t_input:sub})
            grad[y:y+sz,x:x+sz] = g
    return np.roll(np.roll(grad, -sx, 1), -sy, 0)

def render_deepdream(t_obj, img,
                     iter_n=10, step=1.5, octave_n=4, octave_scale=1.4, name="deep_dream"):
    """ノイズ画像に対して Deep dream画像を生成する"""
    # 最適化を行うTensor
    t_score = tf.reduce_mean(t_obj)
    # Tensorの勾配
    t_grad = tf.gradients(t_score, t_input)[0]

    octaves = []
    for i in range(octave_n-1):
        hw = img.shape[:2]
        lo = resize(img, np.int32(np.float32(hw)/octave_scale))
        hi = img-resize(lo, hw)
        img = lo
        octaves.append(hi)

    # オクターブごとに画像を生成する
    for octave in range(octave_n):
        if octave > 0:
            hi = octaves[-octave]
            img = resize(img, hi.shape[:2])+hi
        for i in range(iter_n):
            g = calc_grad_tiled(img, t_grad)
            img += g*(step / (np.abs(g).mean()+1e-7))
        save_image(img/255.0, "%s_%i.jpg"%(name,octave))

if __name__ == "__main__":
    output_path = FLAGS.out_img
    if FLAGS.layer_list:
        show_layer_list()
        quit()
    if FLAGS.random_noise:
        layer = "mixed4d_3x3_bottleneck_pre_relu"
        tensor = T(layer)[:,:,:,139]
        render_naive(tensor, name=output_path)
        quit()
    if FLAGS.in_img:
        # 入力画像
        img = read_image(FLAGS.in_img)
        tensor = tf.square(T('mixed4c'))
        if FLAGS.layer and FLAGS.channel:
            tensor = T(FLAGS.layer)[:,:,:,FLAGS.channel]
        render_deepdream(tensor, img, name=output_path)

python tensorflow_deep_dream.py --layer_listの実行結果 :

import/conv2d0_pre_relu/conv
import/conv2d1_pre_relu/conv
import/conv2d2_pre_relu/conv
import/mixed3a_1x1_pre_relu/conv
import/mixed3a_3x3_bottleneck_pre_relu/conv
import/mixed3a_3x3_pre_relu/conv
import/mixed3a_5x5_bottleneck_pre_relu/conv
import/mixed3a_5x5_pre_relu/conv
...略...
import/mixed5b_1x1_pre_relu/conv
import/mixed5b_3x3_bottleneck_pre_relu/conv
import/mixed5b_3x3_pre_relu/conv
import/mixed5b_5x5_bottleneck_pre_relu/conv
import/mixed5b_5x5_pre_relu/conv
import/mixed5b_pool_reduce_pre_relu/conv
import/head0_bottleneck_pre_relu/conv
import/head1_bottleneck_pre_relu/conv

python tensorflow_deep_dream.py --random_noiseの実行結果 :

元画像のダウンロード :

$ wget https://github.com/FaBoPlatform/TensorFlow/raw/master/img/scenery.jpgg

python tensorflow_deep_dream.py --in_img scenery.jpg --out_img scenery_deepの実行結果 :

元画像

Deepdreamを適用した画像

python tensorflow_deep_dream.py --in_img scenery.jpg --out_img scenery_deep --layer mixed3b_1x1_pre_relu --channel 101の実行結果 :

参考