C#
Keras
TensorFlow
VGG16

C#で学習済みVGG16モデルを使ってアプリを作る方法

kerasやtensorflowをpythonを使って機械学習をやっている.
良く抽象化されとても使いやすい.

しかし,アルゴリズムを作成後,アプリケーションとしてユーザへ提示したい場合に, Pythonだと少々壁がある.と感じている.

これまではPySideやPyQtを使って,デモアプリを作っていた.しかし,リアルタイムでの動画処理や音声処理をやっていくと,とてつも無く遅い.

個人的には C# + Pythonで"AIアプリ”をつくりたい.

ひとまずIronPython3の完成を待っているのだが,後半年~1年程かかりそう.

そんなとき,TensorflowのC#ラッパーを発見した.こちら

どうもモデルのロードに対応している.

それならPythonで学習させたもでるをロードさせてサクサク動く,仕事やっている感がでるあぷりをつくってみようと思う.

ハマリまくったのでVGG16を動かすまでの方法を載せる

環境

  • Python3 (Anaconda)
  • Keras 2.0.6
  • C# 64bit .NET 4.6.1
    • TensorFlowShaprが64bitで.NET4.6以上でないと動かない.

やること

  • Pythonでやること
    • Kerasに標準で入っているvgg16を使ってモデルをロード
    • モデルを保存(.pb)
  • C#でやること
    • モデル(.pb)のロード
    • Prediction用の画像をロード(test.jpg)
    • 推論を実行!

となる

はまりポイント1

  • KerasでTensorFlowShaprの読めるかたちでモデルを吐き出す(保存する)

  • keras標準のVGG16はInput要素にname="input"が指定されていない.これだとTensorflowSharpで層の指定ができない.

  • TensorfloSharpでモデルを利用するためには,最低でもinputの要素とoutputの要素が必要

  • ここでは,kerasのVGG16関数を改造して使うので,下のConvert_VGG16.pyと同じ階層にmy_vgg16.pyとして作成し,input要素に名前をつけてあげる.変更は1行だけ.

  • Convert_VGG16.pyではmy_vgg16からVGG16を読み出す.その後,吐き出しとして利用する層のoutput要素をtfの型?に適当に変更(ここでは0を足しているだけ)(良い方法があるはずなので,だれか教えてください). そして,nameを指定している.

  • あとはおまじない

  • graphの吐き出しにはwrite_graphを使っている.write_graphを使う場合には,convert_variables_to_constantsを使うかどうかがあるみたい.その名前のとおり,値を持った変数としての重みを,constに変換している.これにより,C#で「重みが初期化されていません」みたいなエラーがでなくなる.し.かつ.軽量になるらしい.VGG16は重たいからそのほうが嬉しい.

"""
Convert_VGG16.py


"""
import tensorflow as tf
import shutil
import os.path

print("Keras-application vgg16")

# Import data
# from tensorflow.examples.tutorials.mnist import input_data
# mnist = input_data.read_data_sets("./tmp/data/", one_hot=True)

g = tf.Graph()

with g.as_default():

    # ===================================================================
    from my_vgg16 import VGG16

    model = VGG16(weights='imagenet', include_top=True)
    model.summary()  # variablesをconstantsに変換したグラフを生成する

    # ===================================================================
    # kerasのモデルだと上手く名前指定ができないので,わざわざTFに変換
    # ここらへんは良く分からなかったので,適当に0を足している.

    zero_const = tf.constant(0.0, name="dummy_zero")
    y_ = tf.add(model.get_layer(name="fc2").output, zero_const, name="output_fc2") # 4096
    y_ = tf.add(model.get_layer(name="fc1").output, zero_const, name="output_fc1") # 4096

    # ==================================================================
    sess = tf.Session()
    init = tf.initialize_all_variables();
    sess.run(init)

    # ===================================================================

    # 定数化(const)し無い場合は初期かエラーが発生してしまう
    # graph_def = g.as_graph_def()
    # tf.train.write_graph(graph_def, './tmp/beginner-export','beginner-graph.pb', as_text=False)
    # print("[ OK ] Output : beginner-graph.pb")
    # tf.train.write_graph(graph_def, './tmp/beginner-export','beginner-graph.pbtxt', as_text=True)
    # print("[ OK ] Output : beginner-graph.pbttxt")


    # ===================================================================

    # 計算済みの重みを定数にする

    from tensorflow.python.framework import graph_util
    # 出力ノードの名前を指定
    converted_graph = graph_util.convert_variables_to_constants(sess, sess.graph_def, ['input', 'output_fc1', 'output_fc2'])
    # プロトコルバッファとして書き出し
    tf.train.write_graph(converted_graph, './tmp/beginner-export','beginner-const-graph.pb', as_text=False)

    # テキストで吐き出せる
    # tf.train.write_graph(converted_graph, './tmp/beginner-export','beginner-const-graph.pbtxt', as_text=True)


    # ===================================================================

    sess.close()


print("FIN")

my_vgg16.py

1行だけ変更している

あとimportの部分を変更している.

もっと良い方法があると思うのでだれか教えてくらさい


# -*- coding: utf-8 -*-
"""VGG16 model for Keras.

# Reference

- [Very Deep Convolutional Networks for Large-Scale Image Recognition](https://arxiv.org/abs/1409.1556)

"""
from __future__ import print_function
from __future__ import absolute_import

import os
import warnings

# 相対参照を絶対参照に変換

# from ..models import Model
# from ..layers import Flatten
# from ..layers import Dense
# from ..layers import Input
# from ..layers import Conv2D
# from ..layers import MaxPooling2D
# from ..layers import GlobalAveragePooling2D
# from ..layers import GlobalMaxPooling2D
# from ..engine.topology import get_source_inputs
# from ..utils import layer_utils
# from ..utils.data_utils import get_file
# from .. import backend as K
# from .imagenet_utils import decode_predictions
# from .imagenet_utils import preprocess_input
# from .imagenet_utils import _obtain_input_shape

from keras.models import Model
from keras.layers import Flatten
from keras.layers import Dense
from keras.layers import Input
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import GlobalAveragePooling2D
from keras.layers import GlobalMaxPooling2D
from keras.engine.topology import get_source_inputs
from keras.utils import layer_utils
from keras.utils.data_utils import get_file
from keras import backend as K
from keras.applications.imagenet_utils import decode_predictions
from keras.applications.imagenet_utils import preprocess_input
from keras.applications.imagenet_utils import _obtain_input_shape

# そのまま
WEIGHTS_PATH = 'https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg16_weights_tf_dim_ordering_tf_kernels.h5'
WEIGHTS_PATH_NO_TOP = 'https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5'


def VGG16(include_top=True, weights='imagenet',
          input_tensor=None, input_shape=None,
          pooling=None,
          classes=1000):

    # (省略)

    # ======================================================
    # Input要素にname="input"を追加
    # ======================================================
    if input_tensor is None:
        # img_input = Input(shape=input_shape)
        img_input = Input(shape=input_shape, name="input") # Input要素にname="input"を追加
    else:
        if not K.is_keras_tensor(input_tensor):
            img_input = Input(tensor=input_tensor, shape=input_shape)
        else:
            img_input = input_tensor

    # (省略)

    return model


if __name__ == '__main__':
    import tensorflow as tf

    sess = tf.Session()

    model = VGG16(weights='imagenet', include_top=True)
    model.summary()

    sess.close()


あとはC#で.pbファイルを読み込む

using System;
using System.IO;
using TensorFlow;

namespace tmp_tf1
{
    static class Program
    {

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {


            string file = "flat_001.png";
            TFTensor tensor = ImageUtil.CreateTensorFromImageFile(file);


            var graph = new TFGraph();


            // シリアル化されたGraphDefをファイルからロードします。
            string modelFile = @"E:\****\beginner-const-graph.pb.pb";
            var model = File.ReadAllBytes(modelFile);
            graph.Import(model, "");



            using (var session = new TFSession(graph))
            {

                var runner = session.GetRunner();

                // 学習モデルのグラフを指定する。
                // 入出力テンソルの名前をsessionに登録する
                runner.AddInput(graph["input"][0], tensor);
                runner.Fetch(graph["output_fc2"][0]);
                var output = runner.Run();

                var result = output[0];

            }

        }
    }
}


いつもはkerasユーザでtensorflowはほとんど触ったことが無かったので,基本が分からずはまりまくった.

これでVGG16を使ったそれっぽいデスクトップアプリが作れるはず.

[意見求む]

もっとスマートなやりかたがあると思いますので,賢者のみなさまのご意見をお待ちしています.よろしくおねがいします.