#author("2025-05-26T16:25:12+09:00","default:kyo-in","kyo-in")
#author("2025-07-14T11:03:59+09:00","default:kyo-in","kyo-in")
**機械学習による図形推定 [#e2875e85]
機械学習ライブラリ[[ML.net:https://docs.microsoft.com/ja-jp/dotnet/machine-learning/]]を使用し,描かれたストロークの図形を推定して,整形された図形に置き換える機能を実装する.

その前段階として,推定結果をテキストで表示する.

今回は,楕円と四角どちらかを推定する,二項分類を行う.

#ref(./predicttext.gif,50%);

**ML.netのインストール [#sf3d923f]
ML.netは.NET standard 2.0に依存している.UWPで.NET standard 2.0を使用可能な状態にするには最小ターゲットバージョンを1809にする必要がある.

ソリューションエクスプローラーから,Kisozemiプロジェクトのプロパティを開き,[アプリケーション]-[ターゲットバージョン]と[最小バージョン]を共に,1809に変更し,保存,いったんVisual Studioを再起動する.

#ref(./target.png,50%);

再起動後,[プロジェクト]-[NuGetパッケージの管理]を開き,[参照]からML.netを検索し,プロジェクトにインストールする.

#ref(./nuget.png,50%);

**学習モデルの読み込み [#r5ec9fc9]
本来,今回のような分類の機械学習を行う場合,学習データの準備と学習モデルの作成が必要となる.

しかし,時間も限られているため,今回はすでに作成されている学習モデルを読み込み,図形の分類推定に使用することとする.

以下のmodel.zipは,楕円と四角の1ストロークで描いた図形を約60個学習させたモデルとなる.

#ref(./model.zip);

model.zipは,プロジェクトのAssetsフォルダにドラッグ&ドロップなどで追加し,「プロパティ」から「ビルドアクション」を「コンテンツ」にしておく.

#ref(./modelzip.png);


適当なButtonを用意し,Clickイベントでmodel.zipを読み込む.

ML.netを使用するため,usingに以下を追加:

 using Microsoft.ML;
 using Microsoft.ML.Data;

クラスのメンバに以下を追加:

 MLContext mlContext = null; 
 ITransformer strokePredictModel = null;

クリックイベント内で実際にmodel.zipを読み込む,''非同期処理(await, async)を使用しているため,イベントメソッドの先頭にasyncを追加すること,System.IOのクラスを使用しているのでusing System.IOを追加する必要があるが,System.IO.PathとWindows.UI.Xaml.Shapes.Pathがあいまいになるので,Windows.UI.Xaml.Shapes.Pathクラスを使用する場合,明示的にすべて書く必要がある'':

            Windows.Storage.StorageFolder installedFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
            Windows.Storage.StorageFolder assetsFolder = await installedFolder.GetFolderAsync("Assets");
            Windows.Storage.StorageFile modelFile = await assetsFolder.GetFileAsync("model.zip");
 
            mlContext = new MLContext();
 
            //Define DataViewSchema for data preparation pipeline and trained model
            DataViewSchema modelSchema;
 
            // Load trained model
            using (System.IO.Stream stream = await modelFile.OpenStreamForReadAsync()) //using System.IO;が必要
            {
                strokePredictModel = mlContext.Model.Load(stream, out modelSchema);
                stream.Flush();
            }

''注意''

UWPで作成されるアプリはiOSやAndroidのアプリ同様に,端末内のストレージに自由にアクセスすることはできない.

そのため,

            Windows.Storage.StorageFolder installedFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
            Windows.Storage.StorageFolder assetsFolder = await installedFolder.GetFolderAsync("Assets");

によって,プロジェクトのAssetsフォルダの場所を取得し,そこからmodel.zipを読み込んでいる.

             Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder;

また上記では,アプリが自由に使えるフォルダを取得できる.

**図形の推定 [#ab8dd1e6]
以下の.csファイルをプロジェクトに追加する.中には機械学習用の入力データ(StrokeData),出力データ(StrokePredict)クラスが定義されている.

#ref(./StrokeData.cs);

学習モデルを読み込んだ状態であれば,ストロークを描き終えた後,そのストロークを特徴ベクトルに変換し,それを学習モデルに入力,図形の推定を行い,推定結果を基に該当する図形をストロークの大きさ,位置に生成する.

以下のdrawingStrokeは描画中のWindows.UI.Xaml.Shapes.Pathである.

       private void MainCanvas_PointerReleased(object sender, PointerRoutedEventArgs e)
       {
           //drawingStrokeがnullでなければ,描き終わったことを示すためnullにしておく
           if (drawingStroke != null)
           {
               if (strokePredictModel != null)
               {
                   var predEngine = mlContext.Model.CreatePredictionEngine<StrokeData, StrokePredict>(strokePredictModel);
                   var result = predEngine.Predict(GetStrokeData(drawingStroke));
                   if (result.Prediction)
                   {
                       AddMessage("Ellipse");
                   }
                   else
                   {
                       AddMessage("Rect");
                   }
               }
 
               drawingStroke = null;
           }
       }

        private void AddMessage(string str)
        {
            StatusBlock.Text += str + System.Environment.NewLine;
            StatusBlockScroll.ChangeView(0, StatusBlockScroll.ScrollableHeight, StatusBlockScroll.ZoomFactor, false);
        }

        StrokeData GetStrokeData(Windows.UI.Xaml.Shapes.Path stroke)
        {
            StrokeData data = new StrokeData();
            data.DirCount = new float[360];
 
            //DirCountにに ストロークを特徴ベクトル化したデータを生成(後述)
 
            return data;
        }

**ストロークから特徴ベクトルの生成 [#ea8301d7]
ストロークの点群そのままでは,機械学習に使用できないため,特徴ベクトルへ変換する.

今回は,ストロークを構成する点群の差分ベクトル(Pi+1 - Pi)を算出し,その角度を整数値0〜359とし,各角度の出現回数をカウントすることで,360次元のベクトルを特徴ベクトルとする(少なくとも楕円と四角ではその分布は大きく異なるはず,という仮定)

#ref(./vector.png);

ヒント:

 data.DirCount = new float[360];
 //点群全体に以下の処理
 //差分ベクトルの角度をMath.Atan2で算出し0〜359整数値に変換->deg
 data.DirCount[deg] += 1.0f;
 //一通り終わったら全体から最大値を求める->max
 //すべてをmaxで割り正規化
 data.DirCount[i] /= max;


**課題提出方法 [#r54a0cd6]
招待されているBoxアップロードフォルダ「情報メディア基礎ゼミ(森谷)」の「第5回」へ,完成させた課題のソリューションフォルダをZIP圧縮し,ファイルを以下の名前でアップロードしてください.

ファイル名:XXFIXXX_5th.zip(例:18FI999_5th.zip)

''提出締め切り:6月6日(金) 23:59''
''提出締め切り:7月18日(金) 23:59''

BOXアップロードフォルダの招待メールが来ていない方は森谷までメール連絡ください.

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS