C#の深層学習ライブラリKelpNetをUnityで使う

# はじめに
KelpNetは[春条さん](https://twitter.com/harujoh)が開発されたC#で深層学習を行うためのライブラリです。文法的にはChainerに近いとのことです（私はChainer使ってないのでよくわかりませんが）。

これを、今回はUnityで使ってみます。UnityにはML-Agentsという公式の機械学習のライブラリがありますが、あちらはおもに強化学習を目的としているので、それ以外の機械学習（画像認識など）にKelpNetを使おうかと考えているところです。

# KelpNetを入手する
KelpNetは[こちらのGitHubレポジトリ](https://github.com/harujoh/KelpNet)からダウンロードしてください。Zip形式でダウンロードすればよいです。ダウンロードしたら、Zipを解凍してください。KelpNet-masterというフォルダが作られるはずです。

# プロジェクトのセットアップ
Unityの方で空のプロジェクトを作ってください。Unityエディターが起動したら、以下の通りエディターからC#プロジェクトを立ち上げてください。UnityエディターとVisual Studioが連携されていれば、Visua Studioが立ち上がるはずです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/92edeb54-74e1-d4f7-2ca2-80b00ed379a0.png)

Visua Studioのソリューションエクスプローラーを開き、一番上の「ソリューション、、、」というアイコンを右クリックして、「追加」→「既存のプロジェクト」を選んでください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/0e0b623b-8957-e063-0576-b52fd04ad9e7.png)

ファイルを選択するダイアログボックスが開くので、先ほど解凍したKelpNet-masterフォルダの中のKelpNetというフォルダにあるKelpNet.csprojを選んでください。KelpNetがソリューションに追加されます。

同じようにKelpNet-master\KelpNet-master\Cloo にあるCloo.csprojも追加してください。ソリューションエクスプローラーはこういう感じになっているはずです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/c58af67a-ffd2-e19d-7749-c09eb0cf4625.png)

次に、ソリューションエクスプローラーでKelpNetのアイコンを右クリックしてメニュー一番下のプロパティを選択してください。設定画面が出てきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/0fee2bcd-400a-ec2b-3a0a-23fc7b15e6eb.png)

設定画面の中で「ビルド」のタブを選び、下の方にある「出力」の項目の「出力パス」のところにある「参照、、」ボタンを押してください。ここで、上で作ったUnityプロジェクト内のAssetsフォルダの下（下ならどこでもよい）を選んでください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/c8ece03b-7f32-2157-58fe-2a8985fad1f1.png)

これにより、ビルドした時の出力ファイルがUnityプロジェクト内に生成されて、Unityで使えるようになります。

次に、再びソリューションエクスプローラーでKelpNetのアイコンの左にある矢印アイコンをクリックしてサブメニューを開き、その中の「参照」を右クリックしてください。メニューが出てくるので、その中の「参照の追加」を選んでください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/400c07c3-a208-6ded-671e-6924db6ac773.png)

ここで、**自分が使っている**UnityのUnityEngine.dllを選択します。場所はUnityのインストール先をどこにしたかによりますが、　C:\Program Files\Unity\Hub\Editor\2018.3.8f1\Editor\Data\Managed　のような感じです。うまくいったら、ソリューションエクスプローラーでKelpNetの参照に「UnityEngine」が加わっていることを確認してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/529b847c-63b6-a236-7908-ae0cedd2c08a.png)

次に、その「UnityEngine」をクリックし、下に出てくる「プロパティ」を見てください。その中に「ローカルにコピー」という項目があるので、それをtrueからfalseに変更してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/2f048886-04b1-25d6-dec2-e98303a957ee.png)

すべて設定したら、Ctrl+Sで保存しておくと良いでしょう。

#スクリプトを書いてみる

それでは、機械学習用のスクリプトを書いてみましょう。ソリューションエクスプローラーでKelpNetのアイコンを右クリックして、追加→新しい項目を選んでください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/c0e2a3c3-f01a-acbc-3be3-36a83ebfd88f.png)

ウィンドウが開いたら、項目の中から「クラス」を選び、ファイル名（ウィンドウの一番下で設定）をSample01.csにしてください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/cc41e590-cce3-f73b-14d1-d6385ff5fa0a.png)

次に、この中にKelpNetについてくるサンプルプログラム[Sample01.cs](https://github.com/harujoh/KelpNet/blob/master/KelpNet.Sample/Samples/Sample01.cs)の内容をそのままコピーしてください。次に、ソースコードの7行目の

```
class Sample01
```
を

```
public class Sample01
```
に変更してください。次に、メニューの「ビルド」→「ソリューションのビルド」を選んでビルドしてください。

終わったら、Unityプロジェクトの方を開きましょう。すると、新しいファイルが作られていることがわかります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/b0e9c6c1-bd4e-f15d-b56e-0d62a25055f1.png)

これで、Unity側でKelpNetのソースコードが使える状態になっています。

# UnityのスクリプトからKelpNetのスクリプトを使う。

上記の手段で作ったKelpNetのスクリプトは、Unity側から呼び出せます。Unityのエディタ内でTest.csというファイルを作り、適当なゲームオブジェクトにアタッチしてください。次に、Test.csを開いて、以下の内容を入力してください。

```csharp:Test.cs
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        KelpNet.Sample.Sample01.Run();
    }        
}
```
終わったら、Unityエディタに戻ってプロジェクトを実行してください。エディタがフリーズしたような感じになりますが、KelpNetの方で学習が行われています。終わると、XmlExceptionが出てしまいます。作者の春条さんによりますと、エラーの元はModelIO.csのシリアライズ・デシリアライズの部分とのことなので、そこの部分を以下のように修正しました。

```csharp:ModelIO.cs

using System.IO;
using System.Runtime.Serialization;
using System;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;


namespace KelpNet
{
    public class ModelIO
    {
        public static void Save(FunctionStack functionStack, string fileName)
        {

            FileStream fs = new FileStream(fileName, FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();
            try
            {
                formatter.Serialize(fs, functionStack);
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Failed to serialize. Reason: " + e.Message);
                throw;
            }
            finally
            {
                fs.Close();
            }

        }

        public static FunctionStack Load(string fileName)
        {

            FunctionStack result;
            FileStream fs = new FileStream(fileName, FileMode.Open);

            try
            {
                BinaryFormatter formatter = new BinaryFormatter();
                result = (FunctionStack)formatter.Deserialize(fs);
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
                throw;
            }
            finally
            {
                fs.Close();
            }

            foreach (Function function in result.Functions)
            {
                function.ResetState();

                for (int i = 0; i < function.Optimizers.Length; i++)
                {
                    function.Optimizers[i].ResetParams();
                }

                if (function is IParallelizable)
                {
                    ((IParallelizable)function).CreateKernel();
                }
            }

            return result;
        }

    }
}
```
BinaryFormatterの部分はマイクロソフトのサイトを参考にしています（https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter?view=netframework-4.7.2）

これでエラーが出なくなるはずです。


##KelpNetのスクリプトをUnityのスクリプトと同じように使いたい。

KelpNetのスクリプトにMonoBehaviourを継承させることで、Unityのスクリプトと同じようにゲームオブジェクトにアタッチできるようになります。Sample01.csの二行目のusing System.Linq;の下に

```
Using UnityEngine;
```
を追加し、

```
public class Sample01
```

は

```
public class Sample01:MonoBehaviour
```
に変更してください。変更したら、Unityエディタに戻ってください。同じようにファイルが作られますが、KelpNet.dllのところに三角のアイコンが追加されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/dc460d29-0501-5836-f324-1b7bed9095f7.png)

これをクリックすると、Sample01ファイルが出てくるので、これを適当なゲームオブジェクトにドラッグアンドドロップすると、Sample01をアタッチすることができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58503/34a9beee-842b-cc4c-22a5-80273ad9529f.png)

このように、KelpNetのスクリプトをUnityのスクリプトと同じように扱うことができます。ただし、KelpNetの方のスクリプトは自動ビルドされない（自動にする方法あったと思うけど忘れた）ので、スクリプトを変更したらVisual Studioでビルドするのを忘れずに。