一, 概述
通过之前两篇文章的学习, 我们应该已经了解了多元分类的工作原理, 图片的分类其流程和之前完全一致, 其中最核心的问题就是特征的提取, 只要完成特征提取, 分类算法就很好处理了, 具体流程如下:
之前介绍过, 图片的特征是不能采用像素的灰度值的, 这部分原理的台阶有点高, 还好可以直接使用通过 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 学习笔记系列文章目录
来源: https://www.cnblogs.com/seabluescn/p/10944579.html