見出し画像

VB.netでML.NET経由のTensorFlowで駄目だったサンプルソースコードで画像判定する(その7)

例外エラーが発生する原因が分かりません。
こういう場合、何が原因なのか、絞り込む事が大事です。

動く部分と動かない部分を切り分けていき、最終的な問題にたどり着くのです。
関数LearningProcを改造し、2つの引数を渡せるように改造します。

引数は学習用の画像フォルダと、判定用の画像ファイルです。
まず、学習データの妥当性から確認していくことにします。

プログラム起動時のオプションが3つになり、Learningと学習フォルダと判定画像ファイルを指定します。

一旦、model.zipに学習結果を保存し、このmodel.zipを読み込んで、testSetで判定させてみます。
このソースコードならば、画像の判定が可能でした。

つまり、問題があるのは、判定画像ファイルの指定方法です。

このコード、以下の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":
//学習の場合
LearningProc(args[1],args[2]);
break;

case "Judgment":
//画像判定の場合
JudgmentProc(args[1]);
break;

default:
Console.WriteLine("引数が間違っていますので、処理を行いません");
Console.WriteLine("学習の場合");
Console.WriteLine("Learning 学習用フォルダ");
Console.WriteLine("画像判定の場合");
Console.WriteLine("Judgment 判定する画像ファイル");
break;
}

return ;

}


//画像を 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 == 3)
{
//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;
}


/// <summary>
/// 学習用フォルダの画像データから、プログラムと同じフォルダに学習結果のZIPファイルを保存する
/// </summary>
/// <param name="FolderToLearn">学習させたいフォルダを指定する</param>
/// <returns></returns>
public static Boolean LearningProc(String FolderToLearn, String ImageFileToIdentify)
{
//パスを定義し、変数を初期化する
//プログラムの実行場所から、3つ上の階層に移動する
var projectDirectory = System.AppDomain.CurrentDomain.BaseDirectory;

//計算されたボトルネック値、モデルの .pb バージョンを定義します。
var workspaceRelativePath = Path.Combine(projectDirectory, "workspace");

//アセットの場所(学習用の画像データのフォルダ)
var assetsRelativePath = FolderToLearn;


//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");

//Define DataViewSchema for data preparation pipeline and trained model
DataViewSchema modelSchema;

// Load trained model
ITransformer trainedModel2 = mlContext.Model.Load("model.zip", out modelSchema);

//モデルを使用する
//モデルをトレーニングできたところで、モデルを利用して画像を分類しましょう。
ClassifySingleImage(mlContext, testSet, trainedModel2);

return true;
}


/// <summary>
/// 画像ファイルを指定し、結果を表示する
/// </summary>
/// <param name="ImageFileToIdentify">判定したい画像ファイル</param>
/// <returns></returns>
public static Boolean JudgmentProc(String ImageFileToIdentify)
{

//MLContext の新しいインスタンスを使用して mlContext 変数を初期化します。
//MLContext クラスは、すべての ML.NET 操作の開始点で、mlContext を初期化することで、
//モデル作成ワークフローのオブジェクト間で共有できる新しい ML.NET 環境が作成されます。
//これは Entity Framework における DBContext と概念的には同じです。
MLContext mlContext = new MLContext();


//データを準備する

//データを読み込む
//データ読み込みユーティリティ メソッドを作成する
//イメージは 2 つのサブディレクトリに保存されます。
//データを読み込む前に、ImageData オブジェクトの一覧に書式設定する必要があります。
//そのためには、LoadImagesFromDirectory メソッドを Main メソッドの下で作成します。

List<string> ImageFileToIdentifys = new List<string>();

ImageFileToIdentifys.Add(ImageFileToIdentify);

IEnumerable<ImageData> images = LoadImagesFromFile(ImageFileToIdentifys);


//LoadFromEnumerable メソッドを利用して IDataView に画像を読み込みます。
IDataView imageData = mlContext.Data.LoadFromEnumerable(images);

//Define DataViewSchema for data preparation pipeline and trained model
DataViewSchema modelSchema;

// Load trained model
ITransformer trainedModel = mlContext.Model.Load("model.zip", out modelSchema);

//モデルを使用する
//モデルをトレーニングできたところで、モデルを利用して画像を分類しましょう。
ClassifyImages(mlContext, imageData, trainedModel);


Console.ReadKey();


return true;
}

/// <summary>
///
/// </summary>
/// <param name="ImageFileToIdentify"></param>
/// <returns></returns>
//データ読み込みユーティリティ メソッドを作成する
//イメージは 2 つのサブディレクトリに保存されます。
//データを読み込む前に、ImageData オブジェクトの一覧に書式設定する必要があります。
//そのためには、LoadImagesFromDirectory メソッドを Main メソッドの下で作成します。
public static IEnumerable<ImageData> LoadImagesFromFile(List<string> ImageFileToIdentifys)
{

foreach (var ImageFileToIdentify in ImageFileToIdentifys)
{


//ファイルの拡張子がサポートされていることを確認します。
//Image Classification API では、JPEG 形式と PNG 形式がサポートされています。
//if ((Path.GetExtension(ImageFileToIdentify) == ".jpg") || (Path.GetExtension(ImageFileToIdentify) == ".png"))
//{
//ModelInput の新しいインスタンスを作成します。
//戻り値はImageDataが連続している構造体となる
yield return new ImageData()
{
ImagePath = ImageFileToIdentify,
Label = ""
};
//}

}

}

}


//最初に読み込んだデータを表わすために使用されます。
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; }
}
}


この記事が気に入ったらサポートをしてみませんか?