1. ホーム
  2. ディープラーニング

TensorFlow2、カスタム写真のDIYデジタルペイントのためのニューラル・スタイル・マイグレーションを実装

2022-03-02 14:28:22
<パス

TensorFlow2によるニューラル・スタイル・マイグレーションの実装、DIYデジタル油絵カスタムフォト

前書き

  ニューラル・スタイルマイグレーションは、ユーザーがスタイルマイグレーション用の写真をアップロードできるサイトや、商品化(例:"DIY digital oil painting custom photos" from the treasure troveなど)に利用するサイトもあり、業界で大きな関心を集めている。しかし、技術オタクとしては、技術でできることをどうやって買えばいいのか?最も重要なのは、神経スタイルの移行を学び、100ドル相当のDIYデジタル油絵カスタムフォトを無料で手に入れることです,モンクレール ダウン 激安。

ニューラル・スタイルへの移行

  画像は、画像に写っている花や木などの構図を表す「コンテンツ」と、湖の質感や木の色など画像の細部を表す「スタイル」に分解することができる。同じ建物でも、時間帯によって色調や明るさが異なる写真は、同じ内容でもスタイルが異なると見ることができます。
  Gatysらが発表した論文では、CNNを使用して、ある画像の芸術的なスタイルを別の画像に転送しています。


  大量の学習データを必要とする多くのディープラーニングモデルとは異なり、ニューラル・スタイルマイグレーションに必要な画像は、コンテンツ画像とスタイル画像の2つだけです。スタイル画像からコンテンツ画像へのスタイルの移行は、学習済みのCNN(VGGなど)を用いて行うことができます。
  このように、(A)がコンテンツ画像、(B)〜(D)がスタイル画像とスタイル化されたコンテンツ画像で、驚くべき結果が得られています。このアルゴリズムを使って、作品を作って販売する人もいます。ウェブサイトやアプリの中には、根本的な原理を理解しなくても、写真をアップロードしてスタイルマイグレーションを実行できるものがありますが、技術者としては、このモデルを自分たちで実装したいのは確かです。

VGGを用いた特徴量の抽出

  分類器 CNN は2つの部分に分けられます:最初の部分は特徴抽出器 ( feature extractor ) は主に畳み込み層で構成され、後者はクラス確率のスコアを出力するいくつかの完全連結層で構成され、分類器ヘッドと呼ばれる ( classifier head ). 分類タスクのためにImageNet上で事前学習されたCNNは、他のタスクにも利用することができ、これは移動学習( transfer learning この場合、学習した知識を新しいネットワークやアプリケーションに転送したり、再利用したりすることができる。
  CNNでは、画像再構成の2つのステップを以下のように行う。

  1. CNNを通して画像の前方を計算し、特徴を抽出する。
  2. ランダムに初期化された入力を用い、ステップ1の参照特徴量と最もよく一致する特徴量を再構築するように学習させる。

  通常のネットワーク学習では、入力画像を固定し、バックプロパゲートグラジェントを用いてネットワークの重みを更新する。ニューラル・スタイル・マイグレーションでは、すべてのネットワーク層を凍結し、勾配を使って入力を修正します。元の論文では VGG19 は、その Keras VGGの特徴抽出器は5つのブロックからなり、それぞれのブロックの最後にダウンサンプリングが行われる。各ブロックは2~4層の畳み込み層を持ち、VGG19全体では16層の畳み込み層と3層の完全連結層を持つ。
  以下では、コンテンツの再構成を実装し、さらにスタイルマイグレーションを実行するように拡張します。以下のコードでは、事前に学習したVGGを用いて、block4_conv2の出力層を抽出しています。

# Because we only need to extract features, we freeze the network parameters when instantiating the VGG model using include_top = False
vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
content_layers = ['block4_conv2']
content_outputs = [vgg.get_layer(x).output for x in content_layers]
model = Model(vgg.input, content_outputs)


  事前に学習した Keras CNN モデルは2つのパートに分かれている。下の部分は畳み込み層からなり、しばしば特徴抽出器と呼ばれ、上の部分は完全連結層からなる分類器頭です。我々は特徴を抽出したいだけで、分類器は気にしないので、VGGモデルをインスタンス化するときに、VGGモデルを設定します。 include_top = False .

画像読み込み

  まず、コンテンツ画像とスタイル画像を読み込む必要があります。

def scale_image(image):
    MAX_DIM = 512
    scale = np.max(image.shape)/MAX_DIM
    print(image.shape)
    new_shape = tf.cast(image.shape[:2]/scale, tf.int32)
    image = tf.image.resize(image, new_shape)
    return image
content_image = scale_image(np.asarray(Image.open('7.jpg')))
style_image = scale_image(np.asarray(Image.open('starry-night.jpg')))

Keras

VGGプリプロセッシング

[0, 255] 事前に学習されたモデルは、入力画像のBGRの範囲が RGB . したがって、最初のステップでは、カラーチャンネルを反転して BGR から VGG . tf.keras.applications.vgg19.preprocess_input() 色チャンネルごとに異なる平均値を使用するには preprocess_input() 前処理は 103.939 内部では,B, G, R チャンネルの画素値を引き算することで 116.779 は、その 123.68 def extract_features(image): image = tf.keras.applications.vgg19. preprocess_input(image *255.) content_ref = model(image) return content_ref content_image = tf.reverse(content_image, axis=[-1]) content_ref = extract_features(content_image) .
  以下は前方計算のコードで、前方計算の前に画像を前処理し、モデルに送り込んで内容特徴を返す。そして、コンテンツの特徴を抽出し、それをターゲットとする。

[0., 1.]

  コード中、画像は正規化されているので [0., 255.] ということで、255倍して復元する必要があります。 image = tf.Variable(tf.random.normal( shape=content_image.shape)) . 次に、ランダムに初期化された入力を作成し、これがスタイル化された画像になります。

VGG

  次に、バックプロパゲーションを用いて、コンテンツの特徴から画像を再構成します。

コンテンツの再構築

  学習ステップでは、画像を凍結した def calc_loss(y_true, y_pred): loss = [tf.reduce_sum((x-y)***2) for x, y in zip(y_pred, y_true)] return tf.reduce_mean(loss) を使用してコンテンツの特徴を抽出し、次に L 2 L_2 <スパン <スパン <スパン L <スパン <スパン <スパン <スパン <スパン <スパン <スパン 2 <スパン この損失は、ターゲットコンテンツの特徴量に対して測定され、各特徴量のレイヤーにおけるL2損失を計算するために使用される。

tf.GradientTape()

  使用方法 [0., 1.] を使用して勾配を計算します。通常のニューラルネットワークの学習では、勾配の更新は学習可能な変数、つまりニューラルネットワークの重みに適用されます。しかし、ニューラルスタイルマイグレーションでは、勾配は画像に適用されます。その後、画像の値をクリップして for i in range(1,steps+1): with tf.GradientTape() as tape: content_features = self.extract_features(image) loss = calc_loss(content_features, content_ref) grad = tape.gradient(loss, image) optimizer.apply_gradients([(grad, image)]) image.assign(tf.clip_by_value(image, 0., 1.)) の間で、以下のようになります。

Gram

  2000ステップの学習後、block1_1を用いて画像を再構成すると、再構成されたコンテンツ画像が得られる。

  ブロック4_1を用いて2000ステップの学習を行い、再構成されたコンテンツ画像を得る。


  レイヤーブロック4_1を使用すると、葉の形などの詳細が失われ始めることがわかります。ブロック5_1 を使用すると、ほとんどすべての詳細が失われ、ランダムなノイズで埋め尽くされていることがわかります:。


  よく見ると、葉の構造と縁はまだ保存されており、適切な場所にあることがわかる。さて、ここまででコンテンツの特徴を抽出したら、次はスタイルの特徴を抽出する。

グラム行列を用いたスタイルの再構築

  コンテンツの再構成でお分かりのように、特徴マップ(特に最初の数層)にはスタイルとコンテンツの両方が含まれています。では、どのようにして画像からスタイルの特徴を抽出するのでしょうか?その方法は (H, W, C) 行列があり、異なるフィルタ応答間の相関を計算する。コンボリューション層1の活性化形状を次のように仮定する。 H ここで W C は空間次元であり (H, W, C) はチャンネル数で、それぞれが異なる画像特徴を検出するフィルタの数と等しい。
  共通する特徴(色やエッジなど)がある場合、同じテクスチャーであると判断します。例えば、草の画像を畳み込み層に投入すると、縦線と緑を検出するフィルタの方が、その特徴マップの応答が大きくなる。このように、特徴マップ同士の相関を利用して、画像中の質感を表現することができる。
  の形状を渡すために C を使ってグラム行列を作成するために、まずそれを H×W ベクターになります。各ベクトルは、サイズ C の1次元特徴マップを作成する。については C×C Gram を得るために、ベクトルは対称的な TensorFlow の行列を作成します。には Gram を計算する。 tf.squeeze() 行列の詳細な手順は以下の通りである。

  1. を使用します。 (1, H, W, C) を使って、バッチサイズを設定します。 (H, W, C) から (H, W, C)
  2. テンソルを転置して、形状を (C, H, W) から (C, H×W)
  3. 最後の2つの次元を次のように広げます。 (C, C) を使用します。
  4. フィーチャーのドット積を行い、以下のような形状を作成します。 Gram (H × W) のマトリックスになります。
  5. この行列を各スプレッドの特徴マップの要素数で割ることで Gram を使用して正規化を行う。

を計算します。 def gram_matrix(x): x = tf.transpose(tf.squeeze(x), (2,0,1)); x = tf.keras.backend.batch_flatten(x) num_points = x.shape[-1] gram = tf.linalg.matmul(x, tf.transpose(x))/num_points return gram 行列のコードは以下の通りです。

VGG

  この関数は、指定されたスタイルレイヤーのそれぞれの Gram レイヤーを取得するために Gram の行列を作成します。次に Gram の行列は L 2 L_2 <スパン <スパン <スパン L <スパン <スパン <スパン <スパン <スパン <スパン 2 <スパン 損失 損失関数はコンテンツの再構築と同じです。作成する def extract_features(image): image = tf.keras.applications.vgg19.preprocess_input(image *255.) styles = self.model(image) styles = [self.gram_matrix(s) for s in styles] return styles 行列リストのコードは以下の通りである。

vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
default_content_layers = ['block5_conv1']
default_style_layers = ['block1_conv1',
                        'block2_conv1',
                        'block3_conv1',
                        'block4_conv1',
                        'block5_conv1']
content_layers = content_layers if content_layers else default_content_layers
style_layers = style_layers if style_layers else default_style_layers
self.content_outputs = [vgg.get_layer(x).output for x in content_layers]
self.style_outputs = [vgg.get_layer(x).output for x in style_layers]
self.model = Model(vgg.input, [self.content_outputs, self.style_outputs])


  以下の画像は、異なるVGGレイヤーのスタイル特徴から再構成されたものである。


  block1_1から再構成されたスタイル画像では、コンテンツ情報が完全に消え、高周波のテクスチャーの詳細のみが表示されている。上位レイヤーのblock3_1では、カールした形状がいくつか見られる。


  これらの形状は、入力画像に含まれる高いレベルのスタイルを捉えます。グラム行列の損失関数は、平均二乗誤差ではなく、二乗誤差の和である。その結果、より高いレベルのスタイルを持つ層は、より高い固有重みを持つ。これにより、ブラシストロークなど、より高レベルのスタイル表現の伝達が可能になる。平均二乗誤差を使用した場合、低レベルのスタイル特徴(テクスチャなど)が視覚的に目立つようになり、高周波ノイズのように見えることがあります。

ニューラル・スタイル変換の実装

  ここで、コンテンツとスタイルのリファクタリングでコードをマージして、ニューラル スタイルの転送を実行することができます。
  まず、コンテンツ用とスタイル用の2つの特徴ブロックを抽出するモデルを作成することから始める。コンテンツの再構成にはblock5_conv1層を使用し、block1_conv1からblock5_conv1までの5層を使用して、以下のように異なる階層からスタイルを取り込むようにします。

content_ref, _ = self.extract_features(content_image)
_, style_ref = self.extract_features(style_image)



  学習ループを開始する前に、ターゲットとなるそれぞれの画像からコンテンツとスタイルの特徴を抽出する。コンテンツとスタイルの再構成には、ランダムに初期化された入力を用いることもできるが、コンテンツ画像から学習を開始する方が、より高速に

def train_step(self, image, content_ref, style_ref):
    with tf.GradientTape() as tape:
        content_features, style_features = self.extract_features(image)
        content_loss = self.content_weight * self.calc_loss(content_ref, content_features)
        style_loss = self.style_weight * self.calc_loss( style_ref, style_features)
        loss = content_loss + style_loss
    grad = tape.gradient(loss, image)
    self.optimizer.apply_gradients([(grad, image)])
    image.assign(tf.clip_by_value(image, 0., 1.))
    return content_loss, style_loss  


  次に、コンテンツとスタイルの損失を計算し、追加します。

def train_step(self, image, content_ref, style_ref):
    with tf.GradientTape() as tape:
        content_features, style_features = self.extract_features(image)
        content_loss = self.content_weight * self.calc_loss(content_ref, content_features)
        style_loss = self.style_weight * self.calc_loss( style_ref, style_features)
        loss = content_loss + style_loss
    grad = tape.gradient(loss, image)
    self.optimizer.apply_gradients([(grad, image)])
    image.assign(tf.clip_by_value(image, 0., 1.))
    return content_loss, style_loss  


エフェクト表示

  異なるウェイトとコンテンツレイヤーを使用して生成された4つのスタイライズされた画像は以下のとおりです。


  ウェイトやレイヤーを変更することで、好みのスタイルに仕上げることができます。
もちろんこのモデルは、画像生成に数分かかり、リアルタイムに移行できないというデメリットがあり、関連するモデルの改良は後ほど検討する。

シリーズリンク

ニューラル・スタイル・マイグレーションの改善
スタイルマイグレーション用CycleGANの学習とカスタムフィルタの実装