読者です 読者をやめる 読者になる 読者になる

画風変換をtensorflowで実装し、最適化の項を変化させた時の出力画像の変化を見る

python 機械学習 深層学習

学会に参加してるのですが、昼休みが長すぎたので実験しました。CPUでも待ち時間で計算できたことに少し驚き。 最適化の箇所を 2/17 に追記する予定。

Neural Style Transfer

の二番煎じですが、tensorflowで実装。 使用した画像はpixabeyの商用可能画像です。

を含めたくさんの実装が公開されてます。

VGGネット

訓練済みモデルのダウンロード

neural style transferでは訓練済みの画像分類モデルVGG-19を用いるので、それを定義する。 モデルの重みは Matconvnetのこちらから取得できる。

モデルの定義

画像に直接加える前処理(中間画像を引く、など)は preprocess内に全て記述する。 訓練をする際にある損失関数を計算する箇所は後ほど登場。

class vgg:
    # init
    def __init__(self):
        self.name = "default"
        self.vgg_path = "vgg19.mat"
        self.mean_pixel = None # [R, G, B]
        self.SKIP_PREPROCESS = False
        print("[vgg]init")
        
    # network
    def build(self):
        # layer name
        self.layers = (
            'conv1_1', 'relu1_1', 'conv1_2', 'relu1_2', 'pool1',
            'conv2_1', 'relu2_1', 'conv2_2', 'relu2_2', 'pool2',
            'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', 'conv3_3',
            'relu3_3', 'conv3_4', 'relu3_4', 'pool3',
            'conv4_1', 'relu4_1', 'conv4_2', 'relu4_2', 'conv4_3',
            'relu4_3', 'conv4_4', 'relu4_4', 'pool4',
            'conv5_1', 'relu5_1', 'conv5_2', 'relu5_2', 'conv5_3',
            'relu5_3', 'conv5_4', 'relu5_4'
        )
        
        # load weight
        data = scipy.io.loadmat(self.vgg_path)
        mean = data['normalization'][0][0][0]
        self.mean_pixel = np.mean(mean, axis=(0, 1))
        self.weights = data['layers'][0]
        
        print("[vgg]build")
        
    def conv_layer(self, input, weights, bias):
        conv = tf.nn.conv2d(input,\
                            tf.constant(weights),\
                            strides=(1, 1, 1, 1),\
                            padding='SAME')
        return tf.nn.bias_add(conv, bias)

    def pool_layer(self, input):
        return tf.nn.max_pool(input,\
                              ksize=(1, 2, 2, 1),\
                              strides=(1, 2, 2, 1),\
                              padding='SAME')
    
    # eval
    ## 画像をモデルに入力
    def process_img(self, input_image):
        net = {}
        current = input_image
        for i, name in enumerate(self.layers):
            kind = name[:4]
            if kind == 'conv':
                kernels, bias = self.weights[i][0][0][0][0]
                kernels = np.transpose(kernels, (1, 0, 2, 3))
                bias = bias.reshape(-1)
                current = self.conv_layer(current, kernels, bias)
            elif kind == 'relu':
                current = tf.nn.relu(current)
            elif kind == 'pool':
                current = self.pool_layer(current)
            net[name] = current
        assert len(net) == len(self.layers)
        return net

    # preprocess
    ## 中間画像を足す or 引く
    def _add_mean(self, image, sign=-1):
        return image + sign*self.mean_pixel
    
    def preprocess(self, image):
        if not self.SKIP_PREPROCESS:
            image = self._add_mean(image, sign=-1)
        return image
        
    def unprocess(self, image):
        image = self._add_mean(image, sign=1)
        return image
    

モデルを宣言

model = vgg()
model.build()

中間層での画像特徴量の抽出

ローカルで実験しているので tf.device('/cpu:0') としていますが、GPUデバイスを認識できる環境ならばこの箇所を tf.device('/gpu:k') などと適当に変更する必要があります。

input_img = cv2.imread("sample.jpg")
content_image = raw_content.astype(np.float)
content_shape = (1,) + content_image.shape # (h, w, nch) =>  (1, h, w, nch) 

with tf.Graph().as_default(), tf.Session() as sess, tf.device('/cpu:0'):
    image = tf.placeholder('float', shape=content_shape)
    nets = model.process_img(image)
    img_prep = np.array([model.preprocess(content_image)])
    img_feat = nets['relu2_2'].eval(feed_dict={image: img_prep})

プロット用の関数

plot_data, plot_titleにリスト形式でプロットしたいデータを渡し、pltmethodでプロットする手法を指定。一度に5~10このグラフを出力することが何回もあるので、毎回これを使用する。

# plot image data
def plot_image(plot_data, plot_title, size=(10,10,3,3), pltmethod=plt.imshow):
    plt.figure(figsize=(size[0], size[1]))
    
    for i in range(len(plot_data)):
        plt.subplot(size[2],size[3],i+1)
        pltmethod(plot_data[i])
        
        if len(plot_title) == len(plot_data):
            plt.title(plot_title[i])
    
    plt.tight_layout()
    plt.show()

中間層の画像の可視化

抽出した画像をプロットしてみる。 グレースケールにした方が良かった…?

plot_data = [input_img]+[img_feat[0, :, :, i] for i in range(5)]
plot_title = ["original_img"]+["%d-layer" % (i) for i in range(5)]
plot_image(plot_data, plot_title, size=(10,10,3,3))

f:id:misos:20170208012339p:plain

最適化問題

元論文を参考にしながら「コンテンツロス」+「スタイルロス」の和が最小になるようにモデルを訓練する。

損失関数の計算

はじめに、このモデルの基となった同著者のモデルの損失の計算(の概略)が以下。

paper.hatenadiary.jp

+後ほど詳細追記予定。

出力結果

後ほど追記予定。

f:id:misos:20170208030607p:plainf:id:misos:20170208030628p:plainf:id:misos:20170208030642p:plain

画風変換に関する論文一覧

ほぼ時系列順、専門ではないので見落としたくさんあると思います。

  • Texture Synthesis Using Convolutional Neural Networks
  • A Neural Algorithm of Artistic Style
  • Visualizing and Understanding Deep Texture Representations
  • Compact Bilinear Pooling
  • From A to Z: Supervised Transfer of Style and Content Using Deep Neural Network Generators
  • Texture Networks: Feed-forward Synthesis of Textures and Stylized Images
  • Generative Image Modeling using Style and Structure Adversarial Networks
  • Perceptual Losses for Real-Time Style Transfer and Super-Resolution
  • Precomputed Real-Time Texture Synthesis with Markovian Generative Adversarial Networks
  • Improving the Neural Algorithm of Artistic Style
  • A Powerful Generative Model Using Random Weights for the Deep Image Representation
  • Preserving Color in Neural Artistic Style Transfer