Keras 2 : examples : 生成深層学習 – ニューラル・スタイル変換 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/02/2022 (keras 2.9.0)
* 本ページは、Keras の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
- Code examples : Generative Deep Learning : Neural style transfer (Author: fchollet)
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- 人工知能研究開発支援
- 人工知能研修サービス(経営者層向けオンサイト研修)
- テクニカルコンサルティングサービス
- 実証実験(プロトタイプ構築)
- アプリケーションへの実装
- 人工知能研修サービス
- PoC(概念実証)を失敗させないための支援
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
- 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
- sales-info@classcat.com ; Web: www.classcat.com ; ClassCatJP
Keras 2 : examples : 生成深層学習 – ニューラル・スタイル変換
Description : 勾配降下を使用して参照画像のスタイルをターゲット画像に変換する。
イントロダクション
スタイル変換は、ベース画像と同じ「コンテンツ」を持ちながら、異なるピクチャー (通常は芸術的) の「スタイル」を持つ画像を生成することにあります。これは 3 つの成分を持つ損失関数の最適化で達成されます : 「スタイル損失」、「コンテンツ損失」そして「total variation 損失」です :
- total variation 損失は、組み合わせ画像のピクセル間の局所的な空間的連続性を強制し、視覚的な一貫性を与えます。
- スタイル損失は深層学習が在る場所です — 深層畳み込みニューラルネットワークを使用して定義されます。正確には、それはベース画像の表現の Gram 行列と、(ImageNet で訓練された) convnet の異なる層から抽出された、スタイル参照画像の間の L2 距離の合計から成ります。一般的なアイデアは異なる空間スケールで (かなり大きなスケール — 考えられる層の depth により定義されます) カラー/テクスチャ情報を捕捉することです。
- コンテンツ損失は (深い層から抽出された) ベース画像の特徴と組み合わせ画像の特徴の間の L2 距離で、生成画像を元の画像に十分に近く保持します。
Reference: A Neural Algorithm of Artistic Style
セットアップ
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.applications import vgg19
base_image_path = keras.utils.get_file("paris.jpg", "https://i.imgur.com/F28w3Ac.jpg")
style_reference_image_path = keras.utils.get_file(
"starry_night.jpg", "https://i.imgur.com/9ooB60I.jpg"
)
result_prefix = "paris_generated"
# Weights of the different loss components
total_variation_weight = 1e-6
style_weight = 1e-6
content_weight = 2.5e-8
# Dimensions of the generated picture.
width, height = keras.preprocessing.image.load_img(base_image_path).size
img_nrows = 400
img_ncols = int(width * img_nrows / height)
ベース (コンテンツ) 画像とスタイル参照画像を見てみましょう
from IPython.display import Image, display
display(Image(base_image_path))
display(Image(style_reference_image_path))
画像前処理 / deprocessing ユティリティ
def preprocess_image(image_path):
# Util function to open, resize and format pictures into appropriate tensors
img = keras.preprocessing.image.load_img(
image_path, target_size=(img_nrows, img_ncols)
)
img = keras.preprocessing.image.img_to_array(img)
img = np.expand_dims(img, axis=0)
img = vgg19.preprocess_input(img)
return tf.convert_to_tensor(img)
def deprocess_image(x):
# Util function to convert a tensor into a valid image
x = x.reshape((img_nrows, img_ncols, 3))
# Remove zero-center by mean pixel
x[:, :, 0] += 103.939
x[:, :, 1] += 116.779
x[:, :, 2] += 123.68
# 'BGR'->'RGB'
x = x[:, :, ::-1]
x = np.clip(x, 0, 255).astype("uint8")
return x
スタイル変換損失の計算
最初に、4 つのユティリティ関数を定義する必要があります :
- gram_matrix (スタイル損失の計算に使用されます)
- style_loss 関数、これは生成画像をスタイル参照画像の局所的テキスチャに近く保持します。
- content_loss、これは生成画像の高位表現をベース画像のそれに近く保持します。
- total_variation_loss 関数、正則化損失でこれは生成画像を局所的一貫性を保持するようにします。
# The gram matrix of an image tensor (feature-wise outer product)
def gram_matrix(x):
x = tf.transpose(x, (2, 0, 1))
features = tf.reshape(x, (tf.shape(x)[0], -1))
gram = tf.matmul(features, tf.transpose(features))
return gram
# The "style loss" is designed to maintain
# the style of the reference image in the generated image.
# It is based on the gram matrices (which capture style) of
# feature maps from the style reference image
# and from the generated image
def style_loss(style, combination):
S = gram_matrix(style)
C = gram_matrix(combination)
channels = 3
size = img_nrows * img_ncols
return tf.reduce_sum(tf.square(S - C)) / (4.0 * (channels ** 2) * (size ** 2))
# An auxiliary loss function
# designed to maintain the "content" of the
# base image in the generated image
def content_loss(base, combination):
return tf.reduce_sum(tf.square(combination - base))
# The 3rd loss function, total variation loss,
# designed to keep the generated image locally coherent
def total_variation_loss(x):
a = tf.square(
x[:, : img_nrows - 1, : img_ncols - 1, :] - x[:, 1:, : img_ncols - 1, :]
)
b = tf.square(
x[:, : img_nrows - 1, : img_ncols - 1, :] - x[:, : img_nrows - 1, 1:, :]
)
return tf.reduce_sum(tf.pow(a + b, 1.25))
次に、VGG19 の中間活性を (dict として、名前により) 取得する特徴抽出モデルを作成しましょう。
# Build a VGG19 model loaded with pre-trained ImageNet weights
model = vgg19.VGG19(weights="imagenet", include_top=False)
# Get the symbolic outputs of each "key" layer (we gave them unique names).
outputs_dict = dict([(layer.name, layer.output) for layer in model.layers])
# Set up a model that returns the activation values for every layer in
# VGG19 (as a dict).
feature_extractor = keras.Model(inputs=model.inputs, outputs=outputs_dict)
最後に、スタイル変換損失を計算するコードがここにあります。
# List of layers to use for the style loss.
style_layer_names = [
"block1_conv1",
"block2_conv1",
"block3_conv1",
"block4_conv1",
"block5_conv1",
]
# The layer to use for the content loss.
content_layer_name = "block5_conv2"
def compute_loss(combination_image, base_image, style_reference_image):
input_tensor = tf.concat(
[base_image, style_reference_image, combination_image], axis=0
)
features = feature_extractor(input_tensor)
# Initialize the loss
loss = tf.zeros(shape=())
# Add content loss
layer_features = features[content_layer_name]
base_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :]
loss = loss + content_weight * content_loss(
base_image_features, combination_features
)
# Add style loss
for layer_name in style_layer_names:
layer_features = features[layer_name]
style_reference_features = layer_features[1, :, :, :]
combination_features = layer_features[2, :, :, :]
sl = style_loss(style_reference_features, combination_features)
loss += (style_weight / len(style_layer_names)) * sl
# Add total variation loss
loss += total_variation_weight * total_variation_loss(combination_image)
return loss
損失 & 勾配計算に tf.function デコレータを追加する
それをコンパイルして高速化するために。
@tf.function
def compute_loss_and_grads(combination_image, base_image, style_reference_image):
with tf.GradientTape() as tape:
loss = compute_loss(combination_image, base_image, style_reference_image)
grads = tape.gradient(loss, combination_image)
return loss, grads
訓練ループ
損失を最小化するために vanilla 勾配降下ステップを繰り返し実行し、100 イテレーション毎に結果の画像をセーブします。
学習率を 100 ステップ毎に 0.96 で減衰します。
optimizer = keras.optimizers.SGD(
keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate=100.0, decay_steps=100, decay_rate=0.96
)
)
base_image = preprocess_image(base_image_path)
style_reference_image = preprocess_image(style_reference_image_path)
combination_image = tf.Variable(preprocess_image(base_image_path))
iterations = 4000
for i in range(1, iterations + 1):
loss, grads = compute_loss_and_grads(
combination_image, base_image, style_reference_image
)
optimizer.apply_gradients([(grads, combination_image)])
if i % 100 == 0:
print("Iteration %d: loss=%.2f" % (i, loss))
img = deprocess_image(combination_image.numpy())
fname = result_prefix + "_at_iteration_%d.png" % i
keras.preprocessing.image.save_img(fname, img)
Iteration 100: loss=11018.36 Iteration 200: loss=8514.28 Iteration 300: loss=7571.70 Iteration 400: loss=7064.09 Iteration 500: loss=6736.33 Iteration 600: loss=6501.82 Iteration 700: loss=6323.21 Iteration 800: loss=6181.44 Iteration 900: loss=6065.30 Iteration 1000: loss=5967.72 Iteration 1100: loss=5884.61 Iteration 1200: loss=5812.84 Iteration 1300: loss=5750.36 Iteration 1400: loss=5695.61 Iteration 1500: loss=5647.19 Iteration 1600: loss=5604.15 Iteration 1700: loss=5565.45 Iteration 1800: loss=5530.61 Iteration 1900: loss=5498.99 Iteration 2000: loss=5470.26 Iteration 2100: loss=5444.05 Iteration 2200: loss=5420.09 Iteration 2300: loss=5398.12 Iteration 2400: loss=5377.92 Iteration 2500: loss=5359.31 Iteration 2600: loss=5342.14 Iteration 2700: loss=5326.28 Iteration 2800: loss=5311.56 Iteration 2900: loss=5297.89 Iteration 3000: loss=5285.14 Iteration 3100: loss=5273.21 Iteration 3200: loss=5262.05 Iteration 3300: loss=5251.60 Iteration 3400: loss=5241.82 Iteration 3500: loss=5232.64 Iteration 3600: loss=5224.02 Iteration 3700: loss=5215.90 Iteration 3800: loss=5208.26 Iteration 3900: loss=5201.06 Iteration 4000: loss=5194.26
After 4000 iterations, you get the following result:
display(Image(result_prefix + "_at_iteration_4000.png"))
以上