TadaoYamaokaの日記

山岡忠夫Homeで公開しているプログラムの開発ネタを中心に書いていきます。

TensorFlowのC#バインディング

TensorFlowのC#バインディングとして、検索するとTensorFlowSharpが見つかるが、更新が止まっているようで.NET Coreには対応していない(ビルドすれば可能そう)。

TensorFlow.NET

他のライブラリを探したところ、TensorFlow.NETというライブラリを見つけた。
最近でも活発にメンテされており、.NET Coreに対応しており、TensorFlowのバージョンも最新に対応している。
情報が少なく(日本語の情報は皆無)で、今後もメンテされ続けるか不安はあるが試してみた。

TensorFlow.NETの導入方法

.NET Coreのアプリに導入する方法を説明する。

プロジェクト作成

.NET Coreのコンソールプロジェクトを作成する。

mkdir example
cd example
dotnet new console
ライブラリ追加

プロジェクトにTensorFlow.NETを追加する。

dotnet add package TensorFlow.NET

CPUで実行する場合は、プロジェクトにSciSharp.TensorFlow.Redistを追加する。

dotnet add package SciSharp.TensorFlow.Redist


LinuxGPUを使用する場合は、TensorFlow for Cのビルド済みバイナリが別途必要になる。

GPU版のビルド済みバイナリをダウンロードして、適当な場所に解凍する。
※CUDA 10.0、cuDNN 7.4以上が必要(参考

wget https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-gpu-linux-x86_64-1.13.1.tar.gz
tar xzf libtensorflow-gpu-linux-x86_64-1.13.1.tar.gz

/usr/local/lib/にコピーして、キャッシュを更新する。

cp lib/libtensorflow_framework.so /usr/local/lib/
cp lib/libtensorflow.so /usr/local/lib/
ldconfig


WindowsGPUで実行する場合は、プロジェクトにSciSharp.TensorFlow.Redist-Windows-GPUを追加する。

dotnet add package SciSharp.TensorFlow.Redist-Windows-GPU

サンプルコード

以下のように3層パーセプトロンで2値分類するコードを試した。

using System;
using NumSharp;
using Tensorflow;
using static Tensorflow.Binding;

namespace tensorflowcs
{
    class Program
    {
        static Tensor x, y;
        static Tensor loss;
        static Operation optimizer;

        static private Tensor fc_layer(Tensor x, int num_units, string name, bool use_relu = true)
        {
            var in_dim = x.shape[1];

            var initer = tf.truncated_normal_initializer(stddev: 0.01f);
            var W = tf.get_variable("W_" + name,
                        dtype: tf.float32,
                        shape: (in_dim, num_units),
                        initializer: initer);

            var initial = tf.constant(0f, num_units);
            var b = tf.get_variable("b_" + name,
                        dtype: tf.float32,
                        initializer: initial);

            var layer = tf.matmul(x, W) + b;
            if (use_relu)
                layer = tf.nn.relu(layer);

            return layer;
        }

        static private Tensor sigmoid_cross_entropy_with_logits(Tensor labels, Tensor logits)
        {
            return tf.add(logits - logits * labels, tf.log1p(tf.exp(-logits)));
        }

        static private void BuildGraph()
        {
            var graph = new Graph().as_default();

            x = tf.placeholder(tf.float32, shape: (-1, 1), name: "X");
            y = tf.placeholder(tf.float32, shape: (-1, 1), name: "Y");

            var fc1 = fc_layer(x, 3, "FC1", use_relu: true);
            var output_logits = fc_layer(fc1, 1, "OUT", use_relu: false);
            var entropy = sigmoid_cross_entropy_with_logits(labels: tf.reshape(y, new[] { -1 }), logits: tf.reshape(output_logits, new[] { -1 }));
            loss = tf.reduce_mean(entropy, name: "loss");
            optimizer = tf.train.AdamOptimizer(learning_rate: 0.1f).minimize(loss);
        }
        
        static void Main(string[] args)
        {
            BuildGraph();

            using (var sess = tf.Session())
            {
                Train(sess);
            }
        }

        static private void Train(Session sess)
        {
            var init = tf.global_variables_initializer();
            sess.run(init);

            var x_train = np.array(new float[4, 1] { {0.1f}, {0.2f}, {0.8f}, {0.9f} }, dtype: np.float32);
            var y_train = np.array(new float[4, 1] { {1}, {1}, {0}, {0} }, dtype: np.float32);

            foreach (var iteration in range(100))
            {
                var result = sess.run((optimizer, loss), (x, x_train), (y, y_train));

                float loss_val = result.Item2;

                print($"iter {iteration.ToString("000")}: Loss={loss_val.ToString("0.0000")}");
            }
        }
    }
}

実行結果

iter 000: Loss=0.6931
iter 001: Loss=0.6921
iter 002: Loss=0.6869
...
iter 097: Loss=0.0038
iter 098: Loss=0.0038
iter 099: Loss=0.0037

Pythonで同じ処理をした場合と同じ結果になることが確認できた。
tf.nn.sigmoid_cross_entropy_with_logitsは未実装のようで、動かなかったため、独自に実装した。
まだ、ライブラリは実装されていない部分が残っているようだ。