欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

机器学习框架ML.NET学习笔记【6】TensorFlow图片分类

程序员文章站 2022-06-19 16:44:21
一、概述 通过之前两篇文章的学习,我们应该已经了解了多元分类的工作原理,图片的分类其流程和之前完全一致,其中最核心的问题就是特征的提取,只要完成特征提取,分类算法就很好处理了,具体流程如下: 之前介绍过,图片的特征是不能采用像素的灰度值的,这部分原理的台阶有点高,还好可以直接使用通过TensorFl ......

 一、概述

通过之前两篇文章的学习,我们应该已经了解了多元分类的工作原理,图片的分类其流程和之前完全一致,其中最核心的问题就是特征的提取,只要完成特征提取,分类算法就很好处理了,具体流程如下:

机器学习框架ML.NET学习笔记【6】TensorFlow图片分类

之前介绍过,图片的特征是不能采用像素的灰度值的,这部分原理的台阶有点高,还好可以直接使用通过tensorflow训练过的特征提取模型(美其名曰迁移学习)。

模型文件为:tensorflow_inception_graph.pb

 

二、样本介绍

 我随便在网上找了一些图片,分成6类:男孩、女孩、猫、狗、男人、女人。tags文件标记了每个文件所代表的类型标签(label)。

机器学习框架ML.NET学习笔记【6】TensorFlow图片分类

通过对这六类图片的学习,期望输入新的图片时,可以判断出是何种类型。

 

三、代码

 全部代码:

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

  

四、分析

 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张图片进行验证,发现验证成功率是非常高的,基本都是准确的,只有两个出错了。

机器学习框架ML.NET学习笔记【6】TensorFlow图片分类

上图片被识别为girl(我认为是woman),这个情有可原,本来girl和worman在外貌上也没有一个明确的分界点。

机器学习框架ML.NET学习笔记【6】TensorFlow图片分类

上图被识别为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学习笔记系列文章目录