機器學習框架ML.NET學習筆記【6】TensorFlow圖片分類

seabluescn發表於2019-05-31

 一、概述

通過之前兩篇文章的學習,我們應該已經瞭解了多元分類的工作原理,圖片的分類其流程和之前完全一致,其中最核心的問題就是特徵的提取,只要完成特徵提取,分類演算法就很好處理了,具體流程如下:

之前介紹過,圖片的特徵是不能採用畫素的灰度值的,這部分原理的臺階有點高,還好可以直接使用通過TensorFlow訓練過的特徵提取模型(美其名曰遷移學習)。

模型檔案為:tensorflow_inception_graph.pb

 

二、樣本介紹

 我隨便在網上找了一些圖片,分成6類:男孩、女孩、貓、狗、男人、女人。tags檔案標記了每個檔案所代表的型別標籤(Label)。

通過對這六類圖片的學習,期望輸入新的圖片時,可以判斷出是何種型別。

 

三、程式碼

 全部程式碼:

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;
    }   
}
View Code

  

四、分析

 1、資料處理通道

可以看出,其程式碼流程與結構與上兩篇文章介紹的完全一致,這裡就介紹一下核心的資料處理模型部分的程式碼:

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"。

 

2、驗證過程
            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;
    } 

 

3、驗證結果
我在網路上又隨便找了20張圖片進行驗證,發現驗證成功率是非常高的,基本都是準確的,只有兩個出錯了。

上圖片被識別為girl(我認為是Woman),這個情有可原,本來girl和worman在外貌上也沒有一個明確的分界點。

上圖被識別為woman,這個也情有可原,不解釋。

需要了解的是:不管你輸入什麼圖片,最終的結果只能是以上六個型別之一,演算法會尋找到和六個分類中特徵最接近的一個分類作為結果。


4、除錯
注意看實體類的話,我們只提供了三個基本屬性,如果想看一下在學習過程中資料是如何處理的,可以給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();                               
            }

 


五、資源獲取 

原始碼下載地址:https://github.com/seabluescn/Study_ML.NET

工程名稱:TensorFlow_ImageClassification

資源獲取:https://gitee.com/seabluescn/ML_Assets

點選檢視機器學習框架ML.NET學習筆記系列文章目錄

相關文章