VB.netでML.NET経由のTensorFlowで駄目だったサンプルソースコードで画像判定する(その5)
とりあえず、前回のサンプルコードを元にして、.NET Coreを実行する時の不明点を洗い出します。
.Net Coreのコンソールアプリケーションを実行する時、引数は指定できるのでしょうか。
1つのプログラムで2つに分けた処理を行う場合、2つの処理のどちらかを実行するのか判断しなくてはなりません。
この判断するための材料、一般的にはコマンドライン引数で実現します。
.Net Coreでもコマンドライン引数が引き取れるのでしょうか。
プロジェクトのプロパティーを表示させ、デバッグの項目を見てみます。
アプリーけション引数の設定がありました。
どうやら引数は使えそうですので、適当にABCと入力しておきます。
ブレークポイントをMain関数に仕掛けて、文字列配列のargsにABCが入るか確認します。
args[0]にABCと入っていました。
これで引数を使った処理の分岐が可能になります。
ブレークのまま強制終了するのが怖いので、プログラムは最後まで実行させます。
簡単にアプリケーションの設計したいと思います。
引数は合計2つまで、引数の数が2つ以外ならば、引数の使い方を表示させます。
学習させたい場合は Learning と 学習用フォルダの指定が必要です。
画像判定の場合は Judgment と 判定する画像の指定が必要です。
まず、引数の数が2つなのか、確認するソースコードから作っていきましょう。
次回に続きます。
このコード、以下のWebアドレスに掲載されているものを基本にしています。
https://docs.microsoft.com/ja-jp/dotnet/machine-learning/
印刷して学びたい場合もPDFファイルがあるので参考になります。
https://docs.microsoft.com/ja-jp/dotnet/opbuildpdf/machine-learning/toc.pdf?branch=live
#学習 #勉強 #AI #機械学習 #プログラミング #Visual #CSharp #BASIC #ソースコード #Windows #自動判定
#画像 #判定 #検査
中途半端なソースコードですが、今後改良していきます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.ML;
using static Microsoft.ML.DataOperationsCatalog;
using Microsoft.ML.Vision;
namespace ML2TestCore
{
class Program
{
static void Main(string[] args)
{
//起動オプションの判定結果
string JudgmentResult = "";
//プログラム引数の確認をする
ConfirmationOfArguments(args,ref JudgmentResult);
//引数が正常と判断されたか
switch (JudgmentResult)
{
case "Learning":
break;
case "Judgment":
break;
default:
Console.WriteLine("引数が間違っていますので、処理を行いません");
Console.WriteLine("学習の場合");
Console.WriteLine("Learning 学習用フォルダ");
Console.WriteLine("画像判定の場合");
Console.WriteLine("Judgment 判定する画像ファイル");
break;
}
return ;
//パスを定義し、変数を初期化する
//プログラムの実行場所から、3つ上の階層に移動する
var projectDirectory = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../"));
//計算されたボトルネック値、モデルの .pb バージョンを定義します。
var workspaceRelativePath = Path.Combine(projectDirectory, "workspace");
//アセットの場所(学習用の画像データのフォルダ)
var assetsRelativePath = Path.Combine(projectDirectory, "assets");
//MLContext の新しいインスタンスを使用して mlContext 変数を初期化します。
//MLContext クラスは、すべての ML.NET 操作の開始点で、mlContext を初期化することで、
//モデル作成ワークフローのオブジェクト間で共有できる新しい ML.NET 環境が作成されます。
//これは Entity Framework における DBContext と概念的には同じです。
MLContext mlContext = new MLContext();
//データを準備する
//データを読み込む
//データ読み込みユーティリティ メソッドを作成する
//イメージは 2 つのサブディレクトリに保存されます。
//データを読み込む前に、ImageData オブジェクトの一覧に書式設定する必要があります。
//そのためには、LoadImagesFromDirectory メソッドを Main メソッドの下で作成します。
IEnumerable<ImageData> images = LoadImagesFromDirectory(folder: assetsRelativePath, useFolderNameAsLabel: true);
//LoadFromEnumerable メソッドを利用して IDataView に画像を読み込みます。
IDataView imageData = mlContext.Data.LoadFromEnumerable(images);
//データはディレクトリから読み取られた順序で読み込まれます。
//データのバランスを維持するために、ShuffleRows メソッドを利用してシャッフルします。
IDataView shuffledData = mlContext.Data.ShuffleRows(imageData);
//機械学習モデルでは、数値形式で入力する必要があります。
//そのため、トレーニングの前にデータでいくつかの処理を行う必要があります。
//MapValueToKey 変換と LoadRawImageBytes 変換から構成される EstimatorChain を作成します。
//MapValueToKey 変換では、Label 列のカテゴリ値を受け取り、それを数値 KeyType に変換し、LabelAsKey という名前の新しい列に保存します。
//LoadImages では、imageFolder パラメーターと共に ImagePath から値を取得し、トレーニングのために画像を読み込みます。
var preprocessingPipeline = mlContext.Transforms.Conversion.MapValueToKey(inputColumnName: "Label", outputColumnName: "LabelAsKey")
.Append(mlContext.Transforms.LoadRawImageBytes(outputColumnName: "Image", imageFolder: assetsRelativePath, inputColumnName: "ImagePath"));
//データを preprocessingPipeline EstimatorChain に適用する Fit メソッドを使用し、
//事前処理済みのデータが含まれる IDataView メソッドを返す Transform メソッドを続けます。
IDataView preProcessedData = preprocessingPipeline.Fit(shuffledData).Transform(shuffledData);
//モデルをトレーニングするには、トレーニング データセットと検証データセットを用意することが重要です。
//モデルはトレーニングセットでトレーニングされます。
//初見のデータの予測精度は、検証セットに対するパフォーマンスで計測されます。
//そのパフォーマンスの結果に基づき、改善努力の中でモデルが学習したものに調整が加えられます。
//検証セットは、元のデータセットを分割することで取得するか、この目的のために既に用意されていた別の情報源から取得します。
//今回、事前処理済みのデータセットがトレーニング セット、検証セット、テスト セットに分割されています。
//コード サンプルでは、2 回分割されます。
//まず、事前処理済みのデータが分割され、70% がトレーニングに使用され、残りの 30 % が検証に使用されます。
//次に、30 % の検証セットがさらに検証セットとテスト セットに分割され、90 % が検証に使用され、10 % がテストに使用されます。
//このようなデータ分割の目的は試験を受けるようなものです。
//試験勉強するとき、ノート、教科書、参考書で復習し、試験に出る概念をつかみます。
//トレーニング セットがこれに相当します。
//次に、模擬試験を受け、自分の知識を確認します。 ここで役立つのが検証セットです。
//実際に試験を受ける前に概念をしっかり理解しているかを確認したくなることでしょう。
//模擬試験の結果に基づき、間違った箇所や良く理解していなかった箇所をメモし、本番の試験に向けて復習する中、変えるべきところを組み込みます。
//最後に、試験を受けます。 テスト セットがこれに相当します。
//試験に出ている問題は今まで見たことがありません。
//トレーニングと検証から学習したことを活用し、手元の課題に自分の知識を応用します。
TrainTestData trainSplit = mlContext.Data.TrainTestSplit(data: preProcessedData, testFraction: 0.3);
TrainTestData validationTestSplit = mlContext.Data.TrainTestSplit(trainSplit.TestSet);
//トレーニング データ、検証データ、テスト データそれぞれの値をパーティションに割り当てます。
IDataView trainSet = trainSplit.TrainSet;
IDataView validationSet = validationTestSplit.TrainSet;
IDataView testSet = validationTestSplit.TestSet;
//トレーニング パイプラインを定義する
//モデル トレーニングはいくつかのステップから構成されます。
//まず、Image Classification API を利用し、モデルがトレーニングされます。
//次に、PredictedLabel 列のエンコード済みラベルが MapKeyToValue 変換により元のカテゴリ値に戻されます。
//ImageClassificationTrainer の必須パラメーターと省略可能なパラメーターのセットを格納するために、新しい変数を作成します。
var classifierOptions = new ImageClassificationTrainer.Options()
{
//モデルの入力として使用される列です。
FeatureColumnName = "Image",
//予測する値の列です。
LabelColumnName = "LabelAsKey",
//検証データが含まれる IDataView です。
ValidationSet = validationSet,
//画像の学習モデル選択
//使用する事前トレーニング済みモデル アーキテクチャが定義されます。
//このチュートリアルでは、ResNetv2モデルの 101 レイヤー型が使用されます。
Arch = ImageClassificationTrainer.Architecture.ResnetV2101,
//トレーニング中に進捗状況を追跡記録する関数がバインドされます。
MetricsCallback = (metrics) => Console.WriteLine(metrics),
//検証セットがないとき、トレーニング セットに対してパフォーマンスを検証するようにモデルに指示が出ます。
TestOnTrainSet = false,
//後続の実行でボトルネック フェーズからキャッシュされた値を使用するかどうかがモデルに伝えられます。
//ボトルネック フェーズは、初回実行時の計算処理が激しいワンタイム パススルー計算です。
//トレーニング データが変わらないとき、エポック数やバッチ サイズを変えて実験する場合、キャッシュした値を利用すると、
//モデルのトレーニングに必要な時間が大幅に短縮されます。
ReuseTrainSetBottleneckCachedValues = true,
//検証データセット用になります。
ReuseValidationSetBottleneckCachedValues = true,
//計算されたボトルネック値と .pb バージョンのモデルを格納するディレクトリを定義します。
WorkspacePath = workspaceRelativePath
};
//mapLabelEstimator と ImageClassificationTrainer の両方から構成される EstimatorChain トレーニング パイプラインを定義します。
var trainingPipeline = mlContext.MulticlassClassification.Trainers.ImageClassification(classifierOptions)
.Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel"));
//モデルをトレーニングします。
ITransformer trainedModel = trainingPipeline.Fit(trainSet);
//トレーニング結果を保存する
// Save model
mlContext.Model.Save(trainedModel, trainSet.Schema, "model.zip");
//モデルを使用する
//モデルをトレーニングできたところで、モデルを利用して画像を分類しましょう。
ClassifySingleImage(mlContext, testSet, trainedModel);
//複数の画像を分類する
ClassifyImages(mlContext, testSet, trainedModel);
Console.ReadKey();
}
//画像を 1 回予測し、出力します。
public static void ClassifySingleImage(MLContext mlContext, IDataView data, ITransformer trainedModel)
{
//PredictionEngine は、データの 1 つのインスタンスを渡してから、その予測を実行できる便利な API です。
PredictionEngine<ModelInput, ModelOutput> predictionEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(trainedModel);
//1つの ModelInput インスタンスにアクセスするには、CreateEnumerable メソッドを利用して data IDataView をIEnumerable に変換し、最初の観察を取得します。
ModelInput image = mlContext.Data.CreateEnumerable<ModelInput>(data, reuseRowObject: true).First();
//画像を分類します。
ModelOutput prediction = predictionEngine.Predict(image);
//コンソールに予測を出力します。
Console.WriteLine("Classifying single image");
OutputPrediction(prediction);
}
//画像を複数回予測し、出力します。
public static void ClassifyImages(MLContext mlContext, IDataView data, ITransformer trainedModel)
{
//Transform メソッドを利用し、予測を含む IDataView を作成します。
IDataView predictionData = trainedModel.Transform(data);
//予測を反復処理するには、CreateEnumerable メソッドを利用して predictionData IDataView を IEnumerable に変換し、最初の 10 件の観察を取得します。
IEnumerable<ModelOutput> predictions = mlContext.Data.CreateEnumerable<ModelOutput>(predictionData, reuseRowObject: true).Take(10);
//予測を反復処理し、元のラベルと予測後のラベルを出力します。
Console.WriteLine("Classifying multiple images");
foreach (var prediction in predictions)
{
OutputPrediction(prediction);
}
}
//コンソールに予測情報を表示する。
private static void OutputPrediction(ModelOutput prediction)
{
string imageName = Path.GetFileName(prediction.ImagePath);
Console.WriteLine($"Image: {imageName} | Actual Value: {prediction.Label} | Predicted Value: {prediction.PredictedLabel}");
}
//データ読み込みユーティリティ メソッドを作成する
//イメージは 2 つのサブディレクトリに保存されます。
//データを読み込む前に、ImageData オブジェクトの一覧に書式設定する必要があります。
//そのためには、LoadImagesFromDirectory メソッドを Main メソッドの下で作成します。
public static IEnumerable<ImageData> LoadImagesFromDirectory(string folder, bool useFolderNameAsLabel = true)
{
//サブディレクトリからすべてのファイル パスを取得します。
var files = Directory.GetFiles(folder, "*", searchOption: SearchOption.AllDirectories);
//各ファイルを反復処理します。
foreach (var file in files)
{
//ファイルの拡張子がサポートされていることを確認します。
//Image Classification API では、JPEG 形式と PNG 形式がサポートされています。
if ((Path.GetExtension(file) != ".jpg") && (Path.GetExtension(file) != ".png"))
//拡張子がjpgとpng以外は処理がスキップされる
continue;
//ファイルのラベルを取得します。
//useFolderNameAsLabel パラメーターが true に設定されている場合、
//ファイルが保存されている親ディレクトリがラベルとして使用されます。
//それ以外の場合、ラベルは、ファイルのプレフィックスか名前自体にする必要があります。
var label = Path.GetFileName(file);
if (useFolderNameAsLabel)
label = Directory.GetParent(file).Name;
else
{
//サンプルコードの場合、こちらは処理されません
for (int index = 0; index < label.Length; index++)
{
if (!char.IsLetter(label[index]))
{
label = label.Substring(0, index);
break;
}
}
}
//ModelInput の新しいインスタンスを作成します。
//戻り値はImageDataが連続している構造体となる
yield return new ImageData()
{
ImagePath = file,
Label = label
};
}
}
/// <summary>
/// プログラム起動時の引数が正しいか確認する
/// </summary>
/// <param name="args">プログラム起動時の引数が入る</param>
/// <param name="JudgmentResult">学習=Learning 判定=Judgment その他は失敗</param>
/// <returns>true=成功 false=例外エラー発生</returns>
public static Boolean ConfirmationOfArguments(string[] args,ref string JudgmentResult)
{
//戻り値の初期化
JudgmentResult = "";
//引数が2の場合のみ処理を行う
if (args.Length == 2)
{
//1つ目の指定で処理を分ける
switch(args[0])
{
case "Learning":
//学習
JudgmentResult = args[0];
//指定されたフォルダが存在するか確認する
if (Directory.Exists(args[1]) == false)
{
//ファイルが存在しないので、失敗とする
JudgmentResult = "";
}
break;
case "Judgment":
//画像判定
JudgmentResult = args[0];
//指定されたファイルが存在するか確認する
if (File.Exists(args[1])==false)
{
//ファイルが存在しないので、失敗とする
JudgmentResult = "";
}
break;
default:
break;
}
}
return true;
}
}
//最初に読み込んだデータを表わすために使用されます。
class ImageData
{
//画像が保存されている完全修飾パスです。
public string ImagePath { get; set; }
//画像が属するカテゴリです。 これが予測する値です。
public string Label { get; set; }
}
//入力データのスキーマを定義します。
class ModelInput
{
//画像の byte[] 表現です。
//モデルでは、トレーニングのために画像データがこの種類になることが求められます。
//モデルのトレーニングと予測に使用されます。
public byte[] Image { get; set; }
//Label の数値表現です。
//モデルのトレーニングと予測に使用されます。
public UInt32 LabelAsKey { get; set; }
//画像が保存されている完全修飾パスです。
//元の画像ファイルの名前やカテゴリにアクセスするときに便利なため、維持されます。
public string ImagePath { get; set; }
//画像が属するカテゴリです。
//これが予測する値です。
//元の画像ファイルの名前やカテゴリにアクセスするときに便利なため、維持されます。
public string Label { get; set; }
}
//出力データのスキーマを定義します。
class ModelOutput
{
//画像が保存されている完全修飾パスです。
//元の画像ファイルの名前やカテゴリにアクセスするときに便利なため、保持されます。
public string ImagePath { get; set; }
//画像が属する元のカテゴリです。
//これが予測する値です。
public string Label { get; set; }
//モデルで予測された値です。
//元の画像ファイルの名前やカテゴリにアクセスするときに便利なため、保持されます。
public string PredictedLabel { get; set; }
}
}