見出し画像

ML.netで画像ファイルの正常と異常を判別する その2

前回のソースコード、コピペしてみると分かりますが、サンルソースコードままではコンパイルエラーが出ています。
サンプルソースコードがそのまま動かないのでは、学習がとても困難です。

コンパイルエラーの7行目、using指定が怪しい感じです。
using Microsoft.ML.Vision;

これ、もしかしてPDFに記載漏れのNuGetのパッケージが有るのかなと思いました。
そのままMicrosoft.ML.Visionで、NuGetを検索したら、1件ヒット。

これをインストールすることで、1つを除いてエラーが消えました。
残るエラーは記述ミスっぽい感じです。

アクセシビリティに一貫性がありません。
戻り値の型 'IEnumerable<ImageData>' のアクセシビリティはメソッド 'Class1.LoadImagesFromDirectory(string, bool)' よりも低く設定されています。

戻り値の処理に何かエラーがありそうです。
その前に怪しいコードがあります。

yield return new ImageData()
{
ImagePath = file,
Label = label
};

これは何だろうと思いましたが、関数を起動したら配列の戻り地になる処理っぽいです。
知らない文法だから、疑ってしまいました。

こうなると、何かしら致命的な文法違反が有ることになります。
以前に書いた時系列予測のソースコードを見直して、コード上の違いを見つけました。

入力データと出力データのクラス定義、なんでpublic抜けてるの?
githubで公開されているソースコードも確認しましたが、public抜けています。

コンパイル通らないソースコードを公開して、どうするんだいと思った次第です。
異なるclassへのデータを使うんだから、publicにしておかないと文法エラーです。

10分ぐらい悩んで、解決しました。
C#で書いたクラスライブラリーのコード部分のみ掲載します。

こちらのソースコードは、以下で公開されているサンプルソースを元に作成しています。
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 ClassLibrary1
{

//最初に読み込んだデータ
//LoadColumn 属性は、データセット内の、どの列 (列インデックス)を読み込むかを指定します。
public class ImageData
{
//画像が保存されている完全修飾パスです。
public string ImagePath { get; set; }

//画像が属するカテゴリです。 これが予測する値です。
public string Label { get; set; }
}


//入力データのスキーマを定義
//Image と LabelAsKey のみ、モデルのトレーニングと予測に使用されます。
//ImagePath プロパティと Label プロパティは、元の画像ファイルの名前やカテゴリにアクセスするときに便利なため、維持されます。
public class ModelInput
{
//画像の byte[] 表現です。
//モデルでは、トレーニングのために画像データがこの種類になることが求められます。
public byte[] Image { get; set; }

//Label の数値表現です
public UInt32 LabelAsKey { get; set; }

//画像が保存されている完全修飾パス
public string ImagePath { get; set; }

//画像が属するカテゴリです。 これが予測する値です。
public string Label { get; set; }
}

//出力データのスキーマ
//予測には PredictedLabel のみが必要になります。
//モデルによる予測が含まれているためです。
//ImagePath プロパティと Label プロパティは、元の画像ファイルの名前やカテゴリにアクセスするときに便利なため、保持されます。
public class ModelOutput
{
//画像が保存されている完全修飾パスです。
public string ImagePath { get; set; }

//画像が属する元のカテゴリです。 これが予測する値です。
public string Label { get; set; }

//PredictedLabel はモデルで予測された値です。
public string PredictedLabel { get; set; }
}


public class Class1
{

public void SampleMachineLearning(string SpecifyFileToJudged)
{
//パスを定義し、変数を初期化する
//アセットの場所、計算されたボトルネック値、モデルの .pb バージョンを定義します。
var projectDirectory = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../"));
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();


//LoadFromDirectory ユーティリティを使用し、トレーニングに使用される画像の一覧を取得します。
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,

//ReuseTrainSetBottleneckCachedValues に似ていますが、これは検証データセット用になります。
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);

//画像のテスト セットを利用して ClassifySingleImage を呼び出します。
ClassifySingleImage(mlContext, testSet, trainedModel);

//画像のテスト セットを利用して ClassifyImages を呼び出します。
ClassifyImages(mlContext, testSet, trainedModel);


}


//データを読み込む
//データ読み込みユーティリティ メソッドを作成する
//イメージは 2 つのサブディレクトリに保存されます。
//データを読み込む前に、ImageData オブジェクトの一覧に書式設定する必要があります。
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"))
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;
}
}
}

yield return new ImageData()
{
ImagePath = file,
Label = label
};

}

}


//モデルを使用する
//モデルをトレーニングできたところで、モデルを利用して画像を分類しましょう。
//コンソールに予測情報を表示する、OutputPrediction という名前の新しいユーティリティ メソッドを作成します。
private static void OutputPrediction(ModelOutput prediction)
{
string imageName = Path.GetFileName(prediction.ImagePath);
Console.WriteLine($"Image: {imageName} | Actual Value: {prediction.Label} | Predicted Value:{ prediction.PredictedLabel}");
}


//1枚の画像を分類する
//画像を 1 回予測し、出力します。
public static void ClassifySingleImage(MLContext mlContext, IDataView data, ITransformer trainedModel)
{
//データの 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 を作成します。 ClassifyImages メソッド内に次のコードを追加します。
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);
}
}


}
}


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