Unityで深層学習用ライブラリ "KelpNet" を使う

# 本記事の概要
本記事ではC#向け深層学習用ライブラリ[KelpNet](https://github.com/harujoh/KelpNet)を使って簡単な学習を行う方法を解説します。
学習のサンプルとしてはFNNでXORの学習を行うコードを作成し、Editor拡張上で学習を実行します。

# KelpNet
[KelpNet](https://github.com/harujoh/KelpNet)はC#向けの深層学習用ライブラリとして[春条さん](https://twitter.com/harujoh)が作成されたものです。
ソースは全てC#で書かれているため内部の可読性が高く、かつUnityへの導入も比較的簡単にできます。


# 導入方法

Unity上でKelpNetを使用するためには、まずKelpNetのdllを作成を作成する必要があります。
dllの作成の仕方は[こちらの記事](https://qiita.com/yanosen_jp/items/4ca7d16908f0956ef7d8)が非常に参考になります。
作成したdllをUnityのプロジェクト上に配置することでスクリプト上からKelpNetを呼び出せるようになります。配置するdllは以下の8個です。

- KelpNet.CL.Common.dll
- KelpNet.CL.Function.dll
- KelpNet.CL.Function.Double.dll
- KelpNet.Common.dll
- KelpNet.Function.dll
- KelpNet.Function.Double.dll
- KelpNet.Tools.dll
- KelpNet.Tools.Double.dll

![スクリーンショット 2023-02-28 10.09.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685272/cd000ad3-6413-2748-18b6-4e9ba50e8a2a.png)
> 図1. dll配置の例

基本的な流れとしては
1. KelpNetのプロジェクトを git clone もしくはZipで落としてきて展開
2. VisualStudio等でビルドする
3. 生成されたdllをAssets以下に専用ファイルを作成して配置する

という形になります。

# 実装
今回はUnityのエディタ拡張上でKelpNetを用いてFNNによるXORの学習を行います。
XOR（排他的論理和）とは論理演算の一種です。[IT用語辞典](https://e-words.jp/w/XOR.html)の説明を引用すると

>XORとは、論理演算の一つで、二つの命題のいずれか一方のみが真のときに真となり、両方真や両方偽のときは偽となるもの。論理回路や2進数の数値におけるXORは、二つの入力のうち片方のみが1であるときのみ出力が1となり、両方1や両方0の場合は0となる。

というものです。
このXORと同じ入出力関係を再現できるようにニューラルネットを学習させるのが今回の目的になります。

Assets/EditorにKelpNetTest.csを作成してクラス内にXORの学習を実行するメソッドを記述します。
コードは以下のようになります。

```C#:KelpNet.cs
using System;
using System.Linq;
using UnityEngine;
using UnityEditor;
using KelpNet;
using KelpNet.CPU;
using Real = System.Single;


public class KelpNetTest
{
    // XORの学習を行うメソッド
    [MenuItem("KelpNet/Test/XOR")]
    public static void TestXOR()
    {
        // 学習エポック数
        const int epochs = 10000;
        // 学習進行度管理用変数
        float progress = 0;
        
        // 学習に用いる入力データ
        Real[][] trainData =
        {
            new Real[] {0,0},
            new Real[] {0,1},
            new Real[] {1,0},
            new Real[] {1,1}
        };
        
        // 学習に用いる出力の正解ラベルデータ
        int[][] trainLabel =
        {
            new[] {0},
            new[] {1},
            new[] {1},
            new[] {0}
        };
        
        // 層間の結合と活性化関数を積み上げてネットワークを構築する
        FunctionStack<Real> nn = new FunctionStack<Real>(
            new Linear<Real>(2,2,name:"l1 Linear"),
            new Sigmoid<Real>(name:"l1 Sigmoid"),
            new Linear<Real>(2,2,name:"l2 Linear")
        );

        Debug.Log("学習開始");
        // 学習用ループ
        for(int i = 0; i < epochs; i++)
        {
            for(int j = 0; j < trainData.Length; j++)
            {
                // 学習用の入力データと正解データを用いてソフトマックス交差エントロピー誤差を計算する
                // 計算した誤差を減らす方向にネットワークの重みを変化させる
                Trainer.Train(nn,new[]{trainData[j]},new[]{trainLabel[j]},new SoftmaxCrossEntropy<Real>(),new MomentumSGD<Real>());
            }
            // 学習進行度の計算
            progress = (float) i / epochs;
            // 学習進行度をプログレスバーに表示する
            EditorUtility.DisplayProgressBar("学習中", "epoch:" + i + "(" + (progress * 100).ToString("F0") + "%)",progress);
        }
        
        // プログレスバー消去
        EditorUtility.ClearProgressBar();
        Debug.Log("学習終了");
        
        Debug.Log("テスト開始");
        // 入力データに対して正しい出力ができるかテストする
        foreach(Real[] input in trainData)
        {
            NdArray<Real> result = nn.Predict(input)[0];
            int resultIndex = Array.IndexOf(result.Data,result.Data.Max());
            Debug.Log(input[0] + " xor " + input[1] + " = " + resultIndex + " " + result);
        }
        Debug.Log("テスト完了");

        // 学習済みモデルを保存する
        ModelIO<Real>.Save(nn,"Assets/TrainedModel/samplexor.nn");
        // 学習済みモデルのロードも可能
        Function<Real> testnn = ModelIO<Real>.Load("Assets/TrainedModel/samplexor.nn");
    }
    
}

```

### 結果
Unityのタブから KelpNet/Test/XOR でEditor拡張を使用します。
![スクリーンショット 2023-02-28 9.25.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685272/23f78f3c-2b48-f92a-428e-be8126f187c9.png)

学習を開始するとプログレスバーが表示され、学習が完了するとテスト結果がコンソール上に出力されます。
![スクリーンショット 2023-02-28 9.22.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685272/39dad94f-15c6-c6a7-70f5-15982b8174df.png)
> 図2. 学習およびテスト結果のコンソール出力

上記のコンソール出力を見ると入力が[0,0],[1,1]の時は0を、[0,1],[1,0]の時は1を出力しており、XORの規則が学習できていることが確認できました。

# まとめ
今回はKelpNetを使って簡単な学習をUnityのEditor拡張上で実行してみました。
KelpNet以外のC#向け深層学習用ライブラリとしては[TorchSharp](https://github.com/dotnet/TorchSharp)というライブラリも新しく出てきているみたいなので、今後そちらも触ってみたいと思います。

# 参考文献
- [KelpNet](https://github.com/harujoh/KelpNet)
- [C#の深層学習ライブラリKelpNetをUnityで使う](https://qiita.com/yanosen_jp/items/4ca7d16908f0956ef7d8)
- [IT用語辞典](https://e-words.jp/w/XOR.html)