ML.NETを使用して簡単にモバイルアプリにAIを実装する方法(.NET MAUI)

# はじめに
.NETの大きな枠組みに機械学習用のライブラリ、ML.NETというものがあり、この機能を使い、.NET MAUIで簡単にAIを実装する方法をご紹介します。

# 概要と作業の流れ
ML.NETの導入としてまずMicrosoftが公開しているML.NETのチュートリアルを参考にモバイルアプリに実装していきたいと思います。
以下のリンクがチュートリアルになります。
https://learn.microsoft.com/ja-jp/dotnet/machine-learning/tutorials/sentiment-analysis
まずこのチュートリアルがどんなものかというと、学習用のデータとしてレストランのレビューとそのレビューがポジティブな発言かネガティブな発言かを0と1であらわしたものを使い、ある入力されたレビューがポジティブな発言なのかネガティブな発言なのかをAIを使用して判断するというプログラムになります。

今回はML.NET Model BuilderというVisualStudioの拡張機能を使い簡単に学習モデルを作成し、そのモデルをモバイルアプリのプログラムに実装することで、モバイルアプリでも簡単にAIを使おうというものになります。

モバイルアプリだとデバイスのCPUが弱いのかARMアーキテクチャの問題か学習モデルのトレーニングができないので、外部的にML.NET Model Builderを使い、あらかじめ学習しているモデルをモバイルアプリに直接取り込むことでモバイルアプリでAIを使えるようにします。

# 手順
### 1.ML.NET Model Builderを拡張機能としてインストールする
デフォルトではVisualStudioにすでにML.NET Model Builderはインストールされているとは思いますが、されていない場合は以下の手順でインストールしてください。
VisualStudioの「拡張機能」メニュー→「拡張機能の管理」→検索窓に"ML.NET Model Builder"と入力→表示された"ML.NET Model Builder"のインストール→VisualStudioを再起動

### 2.サンプルアプリにML.NET Model Builderを追加し学習モデルを作成する。
サンプルアプリを新規作成し、ソリューションエクスプローラーのプロジェクト名で右クリック→「追加」→「機械学習モデル…」を選択
名前を適当につける。(ここでつけた名前はそのままクラス名になります。今回はMLNETという名前で作成しました。)
学習モデルを作成する画面が出てきます。ML.NET Model BuilderはGUI操作で簡単にAIの学習モデルを作成する拡張機能となります。

以下の手順でモデルを作成してください

1.シナリオ・・・学習するモデルケースに合わせて選択できるメニューが表示されます。今回は「データ分類」を選択します。

2.環境・・・「ローカル(CPU)」を選択します。選択したシナリオによって選択項目が変わるようです。

3.データ・・・ラジオボタンは「ファイル」を選択し、以下のリンクからデータセットをダウンロード、解凍し、「yelp_labelled.txt」のパスを指定します。
https://archive.ics.uci.edu/ml/machine-learning-databases/00331/sentiment%20labelled%20sentences.zip
予測する列(ラベル)では、レビューがポジティブかネガティブかを判断したいので、判断先であるcol1(2列目)を選択します。

4.トレーニング・・・学習モデルのトレーニング時間を記入します。学習用のデータセットのデータ量とPCのスペックで変わってきますが今回は15秒としました。
詳細は以下のリンクから確認してください。
https://learn.microsoft.com/ja-jp/dotnet/machine-learning/automate-training-with-model-builder#how-long-should-i-train-for
トレーニング結果の「最適の精度」が0.8(80%)以上になったらあらかたOKです。それより下回る場合は再度トレーニングボタンを押して精度を高めましょう。

5.評価・・・簡易テストの場所です。任意の文字列を入力して判定が合っているか確認してください。1がポジティブ、0がネガティブです。

6.使用・・・このモデルを使用するためのコードスニペットが表示されます。アプリにこのスニペットを使用することでML.NETのメソッドが呼び出され結果が出力されます。

7.次の手順・・・ここは無視してかまいません。ページを閉じてください。

### 3.学習モデルをアプリに移動する
ソリューションエクスプローラーを見ると「MLNET.mbconfig」の下の階層にcsファイル2つとzipファイルが追加されています。
この中のtraning.csファイルはトレーニングをしないため削除します。
consumption.csファイルはプロジェクトにMLフォルダーを追加しそこに移動、.zipはResources,Rawフォルダに移動します。
.zipはプロパティを開き、ビルドアクションが「MauiAsset」になっているか確認します。
(Resources,Rawフォルダにいれたファイルは自動的に「MauiAsset」になると思うが一応確認)
![MLModelBuilder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3007605/512b8508-1e76-36bc-4ff5-76d1ebfe42f2.png)

### 4.consumption.csのソースコードを修正
生成されたコードをモバイルアプリを使うのに適したものに変更します。基本的にデータを取得するパス関係の処理です。
```cs
public partial class MLNET
    {
        /// <summary>
        /// model input class for MLNET.
        /// </summary>
        #region model input class
        public class ModelIn...//省略
        #endregion

        /// <summary>
        /// model output class for MLNET.
        /// </summary>
        #region model output class
        public class ModelOut... //省略
        #endregion

        //↓クロスプラットフォーム開発ではプラットフォームによってパスが違うのでパスからデータを取得する方法を用いないため削除
        //private static string MLNetModelPath = Path.GetFullPath("MLNET.zip");

        public static readonly Lazy<PredictionEngine<ModelInput, ModelOutput>> PredictEngine = new Lazy<PredictionEngine<ModelInput, ModelOutput>>(() => CreatePredictEngine(), true);

        /// <summary>
        /// Use this method to predict on <see cref="ModelInput"/>.
        /// </summary>
        /// <param name="input">model input.</param>
        /// <returns><seealso cref=" ModelOutput"/></returns>
        public static ModelOutput Predict(ModelInput input)
        {
            var predEngine = PredictEngine.Value;
            return predEngine.Predict(input);
        }
        
        //↓streamを取得する関係上このメソッドが非同期処理とする必要があるためasync,Task<T>をつける
        //private static PredictionEngine<ModelInput, ModelOutput> CreatePredictEngine()
        private static async Task<PredictionEngine<ModelInput, ModelOutput>> CreatePredictEngine()
        {
            var mlContext = new MLContext();
            //↓追加//パスが取得できないのでResource,Rawフォルダにあるファイルを直接Streamに書き出す(これをしたいために.zipフォルダを移動した)
            using ver stream = await FileSystem.OpenAppPackageFileAsync("MLNET.zip");
            ITransformer mlModel = mlContext.Model.Load(stream, out var _);
            return mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(mlModel);
        }
    }
```

### 5.MainPage.xamlに入力用のEntryと判定結果を表示するLabel,判定ボタンを追加する
サンプルアプリに以下のように追加。今回はLabelやButtonはテンプレートにあらかじめ入っているものを修正している。
```xml
<Entry x:Name="entryString"
                   Placeholder="Input String"/>
<Label x:Name="resultString"
       FontSize="18"
       HorizontalOptions="Center" />

<Button x:Name="FeedBack"
        Text="Click me"
        SemanticProperties.Hint="Counts the number of times you click"
        Clicked="FeedBackClicked"
        HorizontalOptions="Center" />
```

### 6.コードビハインドで処理を追加
コードビハインドで「entryString」に入力された文字を「FeedBackClickedイベントハンドラー」から判定し、「resultString」に判定結果を表示する処理
```cs
	private async void FeedBackClicked(object sender, EventArgs e)
	{
        //非同期処理なのでTask.RunをしているがML.NET Model Builderで作成したときの「6.使用」で表示されたコードスニペットそのままだと気付くだろう
		resultString.Text = await Task.Run(string () =>
		{
			var sampleData = new MLNET.ModelInput()
			{
                //判定用の文字列としてentryStringに入力された文字を入れている
				Col0 = entryString.Text
			};

            //PredicatedLabel以下は1の場合は"Positive"を返し、それ以外は"Negative"を返す処理を直接書いている。
            //PredicatedLabel以下がない場合はそのまま0か1が返ってくる。
			return MLNET.Predict(sampleData).PredictedLabel == 1 ? "Positive" : "Negative";
		});
	}
```
ML.NET Model Builder「6.使用」で表示されたコードスニペット
```cs
//Load sample data
var sampleData = new MLModel1.ModelInput()
{
    Col0 = @"Crust is not good.",
};

//Load model and predict output
var result = MLModel1.Predict(sampleData);
```

# 実行
試しに「C# is GOD」と入力して判定。見事ポジティブが表示されました。
![20221204_214317.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3007605/5ee1c7c3-27f4-57ce-8e57-8b7d9b15bd39.gif)