1、概述html
經過以前兩篇文章的學習,咱們應該已經瞭解了多元分類的工做原理,圖片的分類其流程和以前徹底一致,其中最核心的問題就是特徵的提取,只要完成特徵提取,分類算法就很好處理了,具體流程以下:git
以前介紹過,圖片的特徵是不能採用像素的灰度值的,這部分原理的臺階有點高,還好能夠直接使用經過TensorFlow訓練過的特徵提取模型(美其名曰遷移學習)。github
模型文件爲:tensorflow_inception_graph.pb算法
2、樣本介紹數組
我隨便在網上找了一些圖片,分紅6類:男孩、女孩、貓、狗、男人、女人。tags文件標記了每一個文件所表明的類型標籤(Label)。網絡
經過對這六類圖片的學習,指望輸入新的圖片時,能夠判斷出是何種類型。app
3、代碼框架
所有代碼:機器學習
namespace TensorFlow_ImageClassification { class Program { //Assets files download from:https://gitee.com/seabluescn/ML_Assets static readonly string AssetsFolder = @"D:\StepByStep\Blogs\ML_Assets"; static readonly string TrainDataFolder = Path.Combine(AssetsFolder, "ImageClassification", "train"); static readonly string TrainTagsPath = Path.Combine(AssetsFolder, "ImageClassification", "train_tags.tsv"); static readonly string TestDataFolder = Path.Combine(AssetsFolder, "ImageClassification","test"); static readonly string inceptionPb = Path.Combine(AssetsFolder, "TensorFlow", "tensorflow_inception_graph.pb"); static readonly string imageClassifierZip = Path.Combine(Environment.CurrentDirectory, "MLModel", "imageClassifier.zip"); //配置用常量 private struct ImageNetSettings { public const int imageHeight = 224; public const int imageWidth = 224; public const float mean = 117; public const float scale = 1; public const bool channelsLast = true; } static void Main(string[] args) { TrainAndSaveModel(); LoadAndPrediction(); Console.WriteLine("Hit any key to finish the app"); Console.ReadKey(); } public static void TrainAndSaveModel() { MLContext mlContext = new MLContext(seed: 1); // STEP 1: 準備數據 var fulldata = mlContext.Data.LoadFromTextFile<ImageNetData>(path: TrainTagsPath, separatorChar: '\t', hasHeader: false); var trainTestData = mlContext.Data.TrainTestSplit(fulldata, testFraction: 0.1); var trainData = trainTestData.TrainSet; var testData = trainTestData.TestSet; // STEP 2:建立學習管道 var pipeline = mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "LabelTokey", inputColumnName: "Label") .Append(mlContext.Transforms.LoadImages(outputColumnName: "input", imageFolder: TrainDataFolder, inputColumnName: nameof(ImageNetData.ImagePath))) .Append(mlContext.Transforms.ResizeImages(outputColumnName: "input", imageWidth: ImageNetSettings.imageWidth, imageHeight: ImageNetSettings.imageHeight, inputColumnName: "input")) .Append(mlContext.Transforms.ExtractPixels(outputColumnName: "input", interleavePixelColors: ImageNetSettings.channelsLast, offsetImage: ImageNetSettings.mean)) .Append(mlContext.Model.LoadTensorFlowModel(inceptionPb). ScoreTensorFlowModel(outputColumnNames: new[] { "softmax2_pre_activation" }, inputColumnNames: new[] { "input" }, addBatchDimensionInput: true)) .Append(mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy(labelColumnName: "LabelTokey", featureColumnName: "softmax2_pre_activation")) .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabelValue", "PredictedLabel")) .AppendCacheCheckpoint(mlContext); // STEP 3:經過訓練數據調整模型 ITransformer model = pipeline.Fit(trainData); // STEP 4:評估模型 Console.WriteLine("===== Evaluate model ======="); var evaData = model.Transform(testData); var metrics = mlContext.MulticlassClassification.Evaluate(evaData, labelColumnName: "LabelTokey", predictedLabelColumnName: "PredictedLabel"); PrintMultiClassClassificationMetrics(metrics); //STEP 5:保存模型 Console.WriteLine("====== Save model to local file ========="); mlContext.Model.Save(model, trainData.Schema, imageClassifierZip); } static void LoadAndPrediction() { MLContext mlContext = new MLContext(seed: 1); // Load the model ITransformer loadedModel = mlContext.Model.Load(imageClassifierZip, out var modelInputSchema); // Make prediction function (input = ImageNetData, output = ImageNetPrediction) var predictor = mlContext.Model.CreatePredictionEngine<ImageNetData, ImageNetPrediction>(loadedModel); DirectoryInfo testdir = new DirectoryInfo(TestDataFolder); foreach (var jpgfile in testdir.GetFiles("*.jpg")) { ImageNetData image = new ImageNetData(); image.ImagePath = jpgfile.FullName; var pred = predictor.Predict(image); Console.WriteLine($"Filename:{jpgfile.Name}:\tPredict Result:{pred.PredictedLabelValue}"); } } } public class ImageNetData { [LoadColumn(0)] public string ImagePath; [LoadColumn(1)] public string Label; } public class ImageNetPrediction { //public float[] Score; public string PredictedLabelValue; } }
4、分析ide
一、數據處理通道
能夠看出,其代碼流程與結構與上兩篇文章介紹的徹底一致,這裏就介紹一下核心的數據處理模型部分的代碼:
var pipeline = mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "LabelTokey", inputColumnName: "Label") .Append(mlContext.Transforms.LoadImages(outputColumnName: "input", imageFolder: TrainDataFolder, inputColumnName: nameof(ImageNetData.ImagePath))) .Append(mlContext.Transforms.ResizeImages(outputColumnName: "input", imageWidth: ImageNetSettings.imageWidth, imageHeight: ImageNetSettings.imageHeight, inputColumnName: "input")) .Append(mlContext.Transforms.ExtractPixels(outputColumnName: "input", interleavePixelColors: ImageNetSettings.channelsLast, offsetImage: ImageNetSettings.mean)) .Append(mlContext.Model.LoadTensorFlowModel(inceptionPb). ScoreTensorFlowModel(outputColumnNames: new[] { "softmax2_pre_activation" }, inputColumnNames: new[] { "input" }, addBatchDimensionInput: true)) .Append(mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy(labelColumnName: "LabelTokey", featureColumnName: "softmax2_pre_activation")) .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabelValue", "PredictedLabel"))
MapValueToKey與MapKeyToValue以前已經介紹過了;
LoadImages是讀取文件,輸入爲文件名、輸出爲Image;
ResizeImages是改變圖片尺寸,這一步是必須的,即便全部訓練圖片都是標準劃一的圖片也須要這個操做,後面須要根據這個尺寸肯定容納圖片像素信息的數組大小;
ExtractPixels是將圖片轉換爲包含像素數據的矩陣;
LoadTensorFlowModel是加載第三方模型,ScoreTensorFlowModel是調用模型處理數據,其輸入爲:「input」,輸出爲:「softmax2_pre_activation」,因爲模型中輸入、輸出的名稱是規定的,因此,這裏的名稱不能夠隨便修改。
分類算法採用的是L-BFGS最大熵分類算法,其特徵數據爲TensorFlow網絡輸出的值,標籤值爲"LabelTokey"。
二、驗證過程
MLContext mlContext = new MLContext(seed: 1); ITransformer loadedModel = mlContext.Model.Load(imageClassifierZip, out var modelInputSchema); var predictor = mlContext.Model.CreatePredictionEngine<ImageNetData, ImageNetPrediction>(loadedModel); ImageNetData image = new ImageNetData(); image.ImagePath = jpgfile.FullName; var pred = predictor.Predict(image); Console.WriteLine($"Filename:{jpgfile.Name}:\tPredict Result:{pred.PredictedLabelValue}");
兩個實體類代碼:
public class ImageNetData { [LoadColumn(0)] public string ImagePath; [LoadColumn(1)] public string Label; } public class ImageNetPrediction { public string PredictedLabelValue; }
三、驗證結果
我在網絡上又隨便找了20張圖片進行驗證,發現驗證成功率是很是高的,基本都是準確的,只有兩個出錯了。
上圖片被識別爲girl(我認爲是Woman),這個情有可原,原本girl和worman在外貌上也沒有一個明確的分界點。
上圖被識別爲woman,這個也情有可原,不解釋。
須要瞭解的是:無論你輸入什麼圖片,最終的結果只能是以上六個類型之一,算法會尋找到和六個分類中特徵最接近的一個分類做爲結果。
四、調試
注意看實體類的話,咱們只提供了三個基本屬性,若是想看一下在學習過程當中數據是如何處理的,能夠給ImageNetPrediction類增長一些字段進行調試。
首先咱們須要看一下IDateView有哪些列(Column)
var predictions = trainedModel.Transform(testData); var OutputColumnNames = predictions.Schema.Where(col => !col.IsHidden).Select(col => col.Name); foreach (string column in OutputColumnNames) { Console.WriteLine($"OutputColumnName:{ column }"); }
將咱們要調試的列加入到實體對象中去,特別要注意數據類型。
public class ImageNetPrediction { public float[] Score; public string PredictedLabelValue; public string Label; public void PrintToConsole() { //打印字段信息 } }
查看數據集詳細信息:
var predictions = trainedModel.Transform(testData); var DataShowList = new List<ImageNetPrediction>(mlContext.Data.CreateEnumerable<ImageNetPrediction>(predictions, false, true)); foreach (var dataline in DataShowList) { dataline.PrintToConsole(); }
5、資源獲取
源碼下載地址:https://github.com/seabluescn/Study_ML.NET
工程名稱:TensorFlow_ImageClassification
資源獲取:https://gitee.com/seabluescn/ML_Assets