スタンフォードCS 231 n作業コード(漢化)Assignment 3 Q 4

33323 ワード

スタイルの移行
編纂:張礼俊/Slyned
校正:毛麗
総校対と審査:寒小陽
この作業では、この文章「Image Style Transfer Using Convolutional Neural Networks」(Gatys et al.,CVPR 2015)からスタイルの移行を実現します.
主な考えは2枚の図を持って、それから1枚の新しい画像を生成して1枚の図の内容と別の図の芸術スタイルを反応させることです.まず,深さネットワークの特徴空間において対応するピクチャの内容とスタイルをそれぞれ一致させ,次にピクチャ自体に勾配降下を行う損失関数を定義する.
我々が特徴抽出器として用いる深さネットワークは,ImageNet上で訓練された小さなモデルであるSqueezeNetである.どのネットワークを使ってもいいですが、SqueezeNetを使うことを選びました.小さくて効果的です.
ここには、このジョブの最後に生成されるサンプル画像があります.
Setup

%load_ext autoreload
%autoreload 2
from scipy.misc import imread, imresize
import numpy as np

from scipy.misc import imread
import matplotlib.pyplot as plt

# Helper functions to deal with image preprocessing
from cs231n.image_utils import load_image, preprocess_image, deprocess_image
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "2"   #      GPU

%matplotlib inline

def get_session():
    """Create a session that dynamically allocates memory."""
    # See: https://www.tensorflow.org/tutorials/using_gpu#allowing_gpu_memory_growth
    config = tf.ConfigProto()
    config.gpu_options.allow_growth = True
    session = tf.Session(config=config)
    return session

def rel_error(x,y):
    return np.max(np.abs(x - y) / (np.maximum(1e-8, np.abs(x) + np.abs(y))))

# Older versions of scipy.misc.imresize yield different results
# from newer versions, so we check to make sure scipy is up to date.
def check_scipy():
    import scipy
    vnum = int(scipy.__version__.split('.')[1])
    assert vnum >= 16, "You must install SciPy >= 0.16.0 to complete this notebook."

check_scipy()

事前訓練されたSqueezeNetモデルがロードされ、このモデルはPyTorchから変換され、詳細はcs231n/classifiers/squeezenet.py参照モデルアーキテクチャを参照してください.
SqueezeNetを使用するには、まずモデルパラメータをダウンロードする必要があります.cs231n/datasetsディレクトリに切り替え、get_squeezenet_tf.shを実行します.get_assignment3_data.shを走ったことがある場合は、SqueezeNetがダウンロードされているはずです.
from cs231n.classifiers.squeezenet import SqueezeNet
import tensorflow as tf

tf.reset_default_graph() # remove all existing variables in the graph 
sess = get_session() # start a new Session

# Load pretrained SqueezeNet model
SAVE_PATH = 'cs231n/datasets/squeezenet.ckpt'
#if not os.path.exists(SAVE_PATH):
#    raise ValueError("You need to download SqueezeNet!")
model = SqueezeNet(save_path=SAVE_PATH, sess=sess)

# Load data for testing
content_img_test = preprocess_image(load_image('styles/tubingen.jpg', size=192))[None]
style_img_test = preprocess_image(load_image('styles/starry_night.jpg', size=192))[None]
answers = np.load('style-transfer-checks-tf.npz')
INFO:tensorflow:Restoring parameters from cs231n/datasets/squeezenet.ckpt

損失関数の計算
損失関数の3つの構成部分を計算します.損失関数は3つの部分の重み付けと:コンテンツ損失+スタイル損失+全体多様性損失である.これらの重み付け部分は、次の関数で計算されます.
コンテンツロス
私たちは1枚の画像を生成したいと思っています.この画像は1枚の画像の内容と別の画像のスタイルを反映することができます.そのため、私たちはこの2つを私たちの損失関数に追加します.コンテンツピクチャに対するコンテンツのオフセットと,スタイルピクチャに対するスタイルのオフセットを罰することを望んでいる.このような混合損失関数を用いて勾配降下を行うことができ,モデルのパラメータではなく,我々の元のピクチャの画素値で勾配降下を行うことに注意した.
まず、内容の損失関数を書きます.コンテンツ損失は、生成されたピクチャの特徴図とソースピクチャの特徴図の違いの程度を測定する.ネットワークのある階層のコンテンツ表現(例えば、階層×Cℓ×Hℓ×Wℓ A ℓ ∈ R 1 × C ℓ × H ℓ × W ℓ . C$C$は、この層の通路(filters/channels)の数であり、H$H$and W$W$は高さと幅である.reshapeの後の特徴図で計算します(2次元画像を1次元に引き伸ばします).仮定F$∈RN$×Mℓ F ℓ ∈ R N ℓ × M$は現在のピクチャの特徴図及びP$∈RN$である×Mℓ P ℓ ∈ R N ℓ × M$は、コンテンツのソースピクチャの特徴図であり、M$=H$×Wℓ M ℓ = H ℓ × W$は各特徴図中の要素の個数である.各行のF$F$or P$Pは、特定のfilterがピクチャのすべての位置に畳み込んだ後の量子化された活性化値を表し、最後にwc w cが損失関数のコンテンツ損失の重みであると仮定する.
コンテンツ損失値は次のとおりです.
Lc=wc×∑i,j(Fℓij−Pℓij)2 L c = w c × ∑ i , j ( F i j ℓ − P i j ℓ ) 2
def content_loss(content_weight, content_current, content_original):
    """
    Compute the content loss for style transfer.

    Inputs:
    - content_weight: scalar constant we multiply the content_loss by.
    - content_current: features of the current image, Tensor with shape [1, height, width, channels]
    - content_target: features of the content image, Tensor with shape [1, height, width, channels]

    Returns:
    - scalar content loss
    """
    # tf.squared_difference(x,y,name=None)     (x-y)(x-y)
    return content_weight * tf.reduce_sum(tf.squared_difference(content_current, content_original))

コンテンツ損失をテストすると、0.001未満のエラーが表示されるはずです.
def content_loss_test(correct):
    content_layer = 3
    content_weight = 6e-2
    c_feats = sess.run(model.extract_features()[content_layer], {model.image: content_img_test})
    bad_img = tf.zeros(content_img_test.shape)
    feats = model.extract_features(bad_img)[content_layer]
    student_output = sess.run(content_loss(content_weight, c_feats, feats))
    error = rel_error(correct, student_output)
    print('Maximum error is {:.3f}'.format(error))

content_loss_test(answers['cl_out'])
Maximum error is 0.000

スタイルロス
今、私たちはスタイルの損失を解決することができます.指定されたレベル
まず、各filter間の相関を表すGram行列Gを計算し、Fは上記の定義と同様である.Gram行列は共分散行列の近似であり,生成したピクチャの活性化値の統計情報と我々のスタイルピクチャの活性化値の統計情報とを一致させ,(近似)共分散を一致させることがその一つの方法であることを望んでいる.これを実現する方法はたくさんありますが、Gram行列が良いのは、実際に計算が簡単で効果的であることです.
特徴的な図F$F$が与えられ、サイズは(1,C$,M$)(1,C$,M$)であり、Gram行列は(1,C$,C$)(1,C$,C$)である(1,C$,C$)その要素は以下のように計算される.
Gℓij=∑kFℓikFℓjk G i j ℓ = ∑ k F i k ℓ F j k ℓ
G$G$が現在のピクチャの特徴図のGram行列であると仮定し、A$A$がソースピクチャの特徴図Gram行列であると仮定し、w$w$が重み値であると仮定すると、レイヤ$に対するスタイル損失は2つのGram行列間の重み付けユークリッド距離である.
Lℓs=wℓ∑i,j(Gℓij−Aℓij)2 L s ℓ = w ℓ ∑ i , j ( G i j ℓ − A i j ℓ ) 2
実際には、1つのレベルだけでなく、いくつかのレベルLを計算します.全体的なスタイル損失は、各層のスタイル損失の和です.
Ls=∑ℓ∈LLℓs L s = ∑ ℓ ∈ L L s ℓ
まずgramマトリクスの計算を始めます.
def gram_matrix(features, normalize=True):
    """
    Compute the Gram matrix from features.

    Inputs:
    - features: Tensor of shape (1, H, W, C) giving features for
      a single image.
    - normalize: optional, whether to normalize the Gram matrix
        If True, divide the Gram matrix by the number of neurons (H * W * C)

    Returns:
    - gram: Tensor of shape (C, C) giving the (optionally normalized)
      Gram matrices for the input image.
    """

    features = tf.transpose(features, [0,3,1,2]) # (1,C, H, W)
    shape = tf.shape(features)
    features = tf.reshape(features, (shape[0],shape[1],-1)) #   H W (1,C, H*W)
    transpose_features = tf.transpose(features,[0,2,1]) # (1, H*W, C)

    result = tf.matmul(features,transpose_features)
    if normalize:
        result = tf.div(result,tf.cast(shape[0]*shape[1]*shape[2]*shape[3],tf.float32))
    return result

Gramマトリクスコードをテストすると、0.001未満のエラーが表示されるはずです.
def gram_matrix_test(correct):
    gram = gram_matrix(model.extract_features()[5])
    student_output = sess.run(gram, {model.image: style_img_test})
    error = rel_error(correct, student_output)
    print('Maximum error is {:.3f}'.format(error))

gram_matrix_test(answers['gm_out'])
Maximum error is 0.000

次に、スタイルの損失を実現します.
def style_loss(feats, style_layers, style_targets, style_weights):
    """
    Computes the style loss at a set of layers.

    Inputs:
    - feats: list of the features at every layer of the current image, as produced by
      the extract_features function.
    - style_layers: List of layer indices into feats giving the layers to include in the
      style loss.
    - style_targets: List of the same length as style_layers, where style_targets[i] is
      a Tensor giving the Gram matrix the source style image computed at
      layer style_layers[i].
    - style_weights: List of the same length as style_layers, where style_weights[i]
      is a scalar giving the weight for the style loss at layer style_layers[i].

    Returns:
    - style_loss: A Tensor contataining the scalar style loss.
    """
    # Hint: you can do this with one for loop over the style layers, and should
    # not be very much code (~5 lines). You will need to use your gram_matrix function.
    style_losses = 0
    for i in range(len(style_layers)):
        cur_index = style_layers[i]
        cur_feat = feats[cur_index]
        cur_weight = style_weights[i]
        cur_style_target = style_targets[i] #         Gram   
        gramMatrix = gram_matrix(cur_feat) #          gram  
        style_losses += cur_weight * tf.reduce_sum(tf.squared_difference(gramMatrix, cur_style_target))
    return style_losses

あなたのスタイルの損失の実現をテストします.誤差は0.001未満であるべきである.
def style_loss_test(correct):
    style_layers = [1, 4, 6, 7]
    style_weights = [300000, 1000, 15, 3]

    feats = model.extract_features()
    style_target_vars = []
    for idx in style_layers:
        style_target_vars.append(gram_matrix(feats[idx]))
    style_targets = sess.run(style_target_vars,
                             {model.image: style_img_test})

    s_loss = style_loss(feats, style_layers, style_targets, style_weights)
    student_output = sess.run(s_loss, {model.image: content_img_test})
    error = rel_error(correct, student_output)
    print('Error is {:.3f}'.format(error))

style_loss_test(answers['sl_out'])
Error is 0.000

全体分散の正規化(TV loss)
画像にスムーズさを加えるのも役に立つことが実証されています.損失関数にもう1つを加えてwiggles(波形に似た形状)を罰したり、画素値の全体的な変化(total variation)を罰したりすることができます.
水平方向または垂直方向のすべての画素対間の値の差の二乗と全体の画素値としての変化(total variation)を計算することができます.ここでは、RGBチャネルごとに全体的な画素値の変化の正則値を計算し、加算し、重みwt w tで重み付けする.
Ltv=wt×∑3c=1∑H−1i=1∑W−1j=1((xi,j+1,c−xi,j,c)2+(xi+1,j,c−xi,j,c)2) L t v = w t × ∑ c = 1 3 ∑ i = 1 H − 1 ∑ j = 1 W − 1 ( ( x i , j + 1 , c − x i , j , c ) 2 + ( x i + 1 , j , c − x i , j , c ) 2 )
以下では、TV損失項目(total variation regularization)の定義を完了し、満点を得るためにループを使用してはいけません.
def tv_loss(img, tv_weight):
    """
    Compute total variation loss.

    Inputs:
    - img: Tensor of shape (1, H, W, 3) holding an input image.
    - tv_weight: Scalar giving the weight w_t to use for the TV loss.

    Returns:
    - loss: Tensor holding a scalar giving the total variation loss
      for img weighted by tv_weight.
    """
    # Your implementation should be vectorized and not require any loops!
    shape = tf.shape(img)
    img_row_before = tf.slice(img, [0,0,0,0],[-1,shape[1]-1, -1, -1])
    img_row_after = tf.slice(img,[0,1,0,0],[-1,shape[1]-1, -1,-1])
    img_col_before = tf.slice(img,[0,0,0,0],[-1,-1,shape[2]-1,-1])
    img_col_after = tf.slice(img,[0,0,1,0],[-1,-1,shape[2]-1,-1])   
    #print(img_row_before.get_shape())
    result = tv_weight *(tf.reduce_sum(tf.squared_difference(img_row_before, img_row_after)) + tf.reduce_sum(tf.squared_difference(img_col_before, img_col_after)))
    return result           

あなたのTV loss実装をテストします.誤差は0.001未満であるべきである.
def tv_loss_test(correct):
    tv_weight = 2e-2
    t_loss = tv_loss(model.image, tv_weight)
    student_output = sess.run(t_loss, {model.image: content_img_test})
    print(student_output)
    print(correct)
    error = rel_error(correct, student_output)
    print('Error is {:.3f}'.format(error))

tv_loss_test(answers['tv_out'])
303.47
303.223052979
Error is 0.000

スタイルの移行
これらを組み合わせてきれいな画像を生成します!次のstyle_transfer方程式は、前に書いたすべての損失をまとめ、全体の損失値を最小化するために画像を最適化します.
def style_transfer(content_image, style_image, image_size, style_size, content_layer, content_weight,
                   style_layers, style_weights, tv_weight, init_random = False):
    """Run style transfer!

    Inputs:
    - content_image: filename of content image
    - style_image: filename of style image
    - image_size: size of smallest image dimension (used for content loss and generated image)
    - style_size: size of smallest style image dimension
    - content_layer: layer to use for content loss
    - content_weight: weighting on content loss
    - style_layers: list of layers to use for style loss
    - style_weights: list of weights to use for each layer in style_layers
    - tv_weight: weight of total variation regularization term
    - init_random: initialize the starting image to uniform random noise
    """
    # Extract features from the content image
    content_img = preprocess_image(load_image(content_image, size=image_size))
    feats = model.extract_features(model.image)
    content_target = sess.run(feats[content_layer],
                              {model.image: content_img[None]})

    # Extract features from the style image
    style_img = preprocess_image(load_image(style_image, size=style_size))
    style_feat_vars = [feats[idx] for idx in style_layers]
    style_target_vars = []
    # Compute list of TensorFlow Gram matrices
    for style_feat_var in style_feat_vars:
        style_target_vars.append(gram_matrix(style_feat_var))
    # Compute list of NumPy Gram matrices by evaluating the TensorFlow graph on the style image
    style_targets = sess.run(style_target_vars, {model.image: style_img[None]})

    # Initialize generated image to content image

    if init_random:
        img_var = tf.Variable(tf.random_uniform(content_img[None].shape, 0, 1), name="image")
    else:
        img_var = tf.Variable(content_img[None], name="image")

    # Extract features on generated image
    feats = model.extract_features(img_var)
    # Compute loss
    c_loss = content_loss(content_weight, feats[content_layer], content_target)
    s_loss = style_loss(feats, style_layers, style_targets, style_weights)
    t_loss = tv_loss(img_var, tv_weight)
    loss = c_loss + s_loss + t_loss

    # Set up optimization hyperparameters
    initial_lr = 3.0
    decayed_lr = 0.1
    decay_lr_at = 180
    max_iter = 200

    # Create and initialize the Adam optimizer
    lr_var = tf.Variable(initial_lr, name="lr")
    # Create train_op that updates the generated image when run
    with tf.variable_scope("optimizer") as opt_scope:
        train_op = tf.train.AdamOptimizer(lr_var).minimize(loss, var_list=[img_var])
    # Initialize the generated image and optimization variables
    opt_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope=opt_scope.name)
    sess.run(tf.variables_initializer([lr_var, img_var] + opt_vars))
    # Create an op that will clamp the image values when run
    clamp_image_op = tf.assign(img_var, tf.clip_by_value(img_var, -1.5, 1.5))

    f, axarr = plt.subplots(1,2)
    axarr[0].axis('off')
    axarr[1].axis('off')
    axarr[0].set_title('Content Source Img.')
    axarr[1].set_title('Style Source Img.')
    axarr[0].imshow(deprocess_image(content_img))
    axarr[1].imshow(deprocess_image(style_img))
    plt.show()
    plt.figure()

    # Hardcoded handcrafted 
    for t in range(max_iter):
        # Take an optimization step to update img_var
        sess.run(train_op)
        if t < decay_lr_at:
            sess.run(clamp_image_op)
        if t == decay_lr_at:
            sess.run(tf.assign(lr_var, decayed_lr))
        if t % 100 == 0:
            print('Iteration {}'.format(t))
            img = sess.run(img_var)
            plt.imshow(deprocess_image(img[0], rescale=True))
            plt.axis('off')
            plt.show()
    print('Iteration {}'.format(t))
    img = sess.run(img_var)        
    plt.imshow(deprocess_image(img[0], rescale=True))
    plt.axis('off')
    plt.show()

面白い画像を生成します!style_transfer関数を次の3つの異なるパラメータのセットで実行してみます.3つのグループが実行されていることを確認します.自分のパラメータを任意に追加しますが、提出したNotebookに3番目のパラメータ(星空)のセットで実行された結果が含まれていることを確認してください.*content_imageコンテンツピクチャ*style_imageスタイルピクチャ*image_sizeコンテンツピクチャの最小緯度サイズ(コンテンツ損失と生成のためのピクチャ)*style_sizeスタイルピクチャの最小緯度サイズ*content_layerコンテンツ損失に対してどのレイヤ*content_weightコンテンツ損失の全局損失関数上の重みを指定するこのパラメータの値を大きくすると、画像が元の図に近づくように見えます*style_layersスタイル損失を指定します*style_weightsをstyle_Layersの各レイヤは一連の重みを指定し、通常、より局所的/小的な特徴を記述するため、前のスタイルレイヤほど大きな重みを使用します.画像のテクスチャは、後の受信しきい値よりも大きい特徴にとって重要です.要するに、これらの重みを大きくすると、結果として元のピクチャとは異なり、スタイルピクチャに近づくようになります.*tv_weightは、全体の損失関数におけるピクチャ全体の変化正則の重みを指定する.この値を大きくすると、結果の画像がより滑らかに見えますが、スタイルやコンテンツがより歪む代わりになります.
次の3つのコード(スーパーパラメータを変更しないでください)は、さまざまなパラメータを貼り付けて実行し、結果の画像を見てください.
# Composition VII + Tubingen
params1 = {
    'content_image' : 'styles/tubingen.jpg',
    'style_image' : 'styles/composition_vii.jpg',
    'image_size' : 192,
    'style_size' : 512,
    'content_layer' : 3,
    'content_weight' : 5e-2, 
    'style_layers' : (1, 4, 6, 7),
    'style_weights' : (20000, 500, 12, 1),
    'tv_weight' : 5e-2
}

style_transfer(**params1)
Iteration 0
Iteration 100
Iteration 199
# Scream + Tubingen
params2 = {
    'content_image':'styles/tubingen.jpg',
    'style_image':'styles/the_scream.jpg',
    'image_size':192,
    'style_size':224,
    'content_layer':3,
    'content_weight':3e-2,
    'style_layers':[1, 4, 6, 7],
    'style_weights':[200000, 800, 12, 1],
    'tv_weight':2e-2
}

style_transfer(**params2)
Iteration 0
Iteration 100
Iteration 199
# Starry Night + Tubingen
params3 = {
    'content_image' : 'styles/tubingen.jpg',
    'style_image' : 'styles/starry_night.jpg',
    'image_size' : 192,
    'style_size' : 192,
    'content_layer' : 3,
    'content_weight' : 6e-2,
    'style_layers' : [1, 4, 6, 7],
    'style_weights' : [300000, 1000, 15, 3],
    'tv_weight' : 2e-2
}

style_transfer(**params3)
Iteration 0
Iteration 100
Iteration 199

フィーチャーの反転
あなたが書いたコードは他の面白いこともできます.ボリュームネットワークで学習された特徴のタイプを理解するために,最近の論文[1]では,画像の特徴表現から1枚の画像を復元しようと試みた.私たちは事前に訓練されたネットワーク上の画像勾配でこの考えを簡単に実現することができます.これは実は私たちが前にやったことです(しかし、上は2つの異なる特徴で表されています).
これで、スタイルウェイトを0に設定し、初期化開始のピクチャをコンテンツソースピクチャではなくランダムノイズに設定すると、コンテンツ元のピクチャの特徴表示からこのピクチャを再構築できます.初期化はランダムノイズを使っていますが、最後に得たのは元の画像に似ているように見える画像です.
(同様に、「テクスチャ合成」を行うことができます.コンテンツの重みを0に設定し、画像をランダムノイズに初期化しますが、ここでは必要ありません.
[1] Aravindh Mahendran, Andrea Vedaldi, “Understanding Deep Image Representations by Inverting them”, CVPR 2015
#         
# Feature Inversion -- Starry Night + Tubingen
params_inv = {
    'content_image' : 'styles/tubingen.jpg',
    'style_image' : 'styles/starry_night.jpg',
    'image_size' : 192,
    'style_size' : 192,
    'content_layer' : 3,
    'content_weight' : 6e-2,
    'style_layers' : [1, 4, 6, 7],
    'style_weights' : [0, 0, 0, 0], # we discard any contributions from style to the loss
    'tv_weight' : 2e-2,
    'init_random': True # we want to initialize our image to be random
}

style_transfer(**params_inv)
Iteration 0
Iteration 100
Iteration 199
#           
# Feature Inversion -- Starry Night + Tubingen
params_inv = {
    'content_image' : 'styles/tubingen.jpg',
    'style_image' : 'styles/starry_night.jpg',
    'image_size' : 192,
    'style_size' : 192,
    'content_layer' : 3,
    'content_weight' : 0,
    'style_layers' : [1, 4, 6, 7],
    'style_weights' : [300000, 1000, 15, 3], # we discard any contributions from style to the loss
    'tv_weight' : 2e-2,
    'init_random': True # we want to initialize our image to be random
}

style_transfer(**params_inv)
Iteration 0
Iteration 100
Iteration 199