教程:训练 ML.NET 分类模型以对图像进行分类

了解如何使用预先训练的 TensorFlow 模型对图像进行分类,以便对图像进行处理。

TensorFlow 模型经过训练,将图像分类为一千个类别。 由于 TensorFlow 模型知道如何识别图像中的模式,因此 ML.NET 模型可以使用其管道中的一部分来将原始图像转换为特征或输入来训练分类模型。

本教程介绍如何执行下列操作:

  • 了解问题
  • 将预先训练的 TensorFlow 模型合并到 ML.NET 管道中
  • 训练和评估 ML.NET 模型
  • 对测试图像进行分类

可以在 dotnet/samples 存储库中找到本教程的源代码。 默认情况下,本教程的 .NET 项目配置面向 .NET core 2.2。

先决条件

选择正确的机器学习任务

深度学习

深度学习 是机器学习的一部分,它正在彻底改变计算机视觉和语音识别等领域。

深度学习模型是使用包含多个学习层的大量已标记的数据神经网络来定型。 深度学习:

  • 在某些任务中(如计算机视觉)表现更好。
  • 需要大量的训练数据。

图像分类是一项特定的分类任务,允许我们自动将图像分类为类别,例如:

  • 检测图像中是否有人脸。
  • 检测猫狗的区别。

或者,如以下图像所示,确定图像是否为食品、玩具或设备:

披萨图像泰迪熊图像烤箱图像

注意

上述图像属于 Wikimedia Commons,其特性如下:

从头开始训练 图像分类 模型,需要设置数百万个参数、大量标记的训练数据和大量的计算资源,即数百小时的GPU运算。 虽然不如从零开始训练自定义模型的效果好,但使用预训练模型可以简化这个过程,因为只需处理数千张图像,而不是数百万张标记的图像,从而能够相当快速地构建自定义模型(在没有 GPU 的计算机上可以在一小时内完成)。 本教程仅使用十几张训练图像来进一步缩减该过程。

Inception model 经过训练,可将图像分类为一千个类别,但在本教程中,你只需要在更小的类别集中分类图像。 您可以利用 Inception model的图像识别能力,将图像分类到自定义图像分类器中有限的新类别。

  • 食物
  • 玩具
  • 设备

本教程使用 TensorFlow Inception 深度学习模型,这是在 ImageNet 数据集上训练的热门图像识别模型。 TensorFlow 模型将整个图像分类为一千个类,如“Umbrella”、“Jersey”和“洗衣机”。

由于 Inception model 已预先在数千个不同图像上进行过训练,因此内部包含图像识别所需的图像特征。 我们可以利用模型中的这些内部图像特征来训练具有更少类的新模型。

如下图所示,在 .NET 或 .NET Framework 应用程序中添加对 ML.NET NuGet 包的引用。 实际上,ML.NET 包括并引用本机 TensorFlow 库,可用于编写代码来加载已训练的现有 TensorFlow 模型文件。

TensorFlow 转换 ML.NET 体系结构图

多类分类

使用 TensorFlow 初始模型提取适合经典机器学习算法输入的特征后,可以添加 ML.NET 多类分类器

本例中使用的特定训练器是 多项式逻辑回归算法

此训练器实现的算法在解决大量特征的问题时表现良好,尤其适用于处理图像数据的深度学习模型。

有关详细信息,请参阅 深度学习与机器学习

数据

有两个数据源:.tsv 文件和图像文件。 tags.tsv 文件包含两列:第一列定义为 ImagePath,第二列是对应于图像的 Label。 以下示例文件没有标题行,如下所示:

broccoli.jpg	food
pizza.jpg	food
pizza2.jpg	food
teddy2.jpg	toy
teddy3.jpg	toy
teddy4.jpg	toy
toaster.jpg	appliance
toaster2.png	appliance

训练和测试图像位于你即将从 zip 文件中下载的资产文件夹中。 这些图像属于维基媒体共享资源。

Wikimedia Commons(免费媒体存储库)。 检索于2018年10月17日10:48,来源:https://commons.wikimedia.org/wiki/Pizzahttps://commons.wikimedia.org/wiki/Toasterhttps://commons.wikimedia.org/wiki/Teddy_bear

安装

创建项目

  1. 创建名为“TransferLearningTF”的 C# 控制台应用程序。 单击“下一步”按钮。

  2. 选择 .NET 8 作为要使用的框架。 单击“创建”按钮。

  3. 安装 Microsoft.ML NuGet 包

    注意

    此示例使用提到的 NuGet 包的最新稳定版本,除非另有说明。

    • 在“解决方案资源管理器”中,右键单击项目,然后选择“管理 NuGet 包”
    • 选择“nuget.org”作为包源,选择“浏览”选项卡,搜索 Microsoft.ML
    • 选择“安装”按钮
    • 选择“预览更改”对话框中的“确定”按钮
    • 如果同意所列包的许可条款,请选择“接受许可”对话框中的“我接受”按钮。
    • Microsoft.ML.ImageAnalyticsSciSharp.TensorFlow.RedistMicrosoft.ML.TensorFlow重复这些步骤。

下载素材

  1. 下载 项目资产目录 zip 文件,然后解压缩。

  2. assets 目录复制到 TransferLearningTF 项目目录。 此目录及其子目录包含本教程所需的数据和支持文件(Inception模型除外,该模型将在下一步下载并添加)。

  3. 下载 Inception 模型,然后解压缩。

  4. 将刚刚解压缩的“inception5h”目录的内容复制到 TransferLearningTF 项目的“assets/inception”目录中。 此目录包含本教程所需的模型和其他支持文件,如下图所示:

    Inception 目录内容

  5. 在解决方案资源管理器中,右键单击资产目录和子目录中的每个文件,然后选择 属性。 在“高级”下,将“复制到输出目录”的值更改为“如果较新则复制”

创建类并定义路径

  1. 将以下附加 using 指令添加到 Program.cs 文件的顶部:

    using Microsoft.ML;
    using Microsoft.ML.Data;
    
  2. 将以下代码添加到 using 指令下面的行,以指定资产路径:

    string _assetsPath = Path.Combine(Environment.CurrentDirectory, "assets");
    string _imagesFolder = Path.Combine(_assetsPath, "images");
    string _trainTagsTsv = Path.Combine(_imagesFolder, "tags.tsv");
    string _testTagsTsv = Path.Combine(_imagesFolder, "test-tags.tsv");
    string _predictSingleImage = Path.Combine(_imagesFolder, "toaster3.jpg");
    string _inceptionTensorFlowModel = Path.Combine(_assetsPath, "inception", "tensorflow_inception_graph.pb");
    
  3. 为输入数据和预测创建类。

    public class ImageData
    {
        [LoadColumn(0)]
        public string? ImagePath;
    
        [LoadColumn(1)]
        public string? Label;
    }
    

    ImageData 是输入图像数据类,具有以下 String 字段:

    • ImagePath 包含映像文件名。
    • Label 包含一个用于图像标签的数值。
  4. 向项目添加 ImagePrediction 的新类:

    public class ImagePrediction : ImageData
    {
        public float[]? Score;
    
        public string? PredictedLabelValue;
    }
    

    ImagePrediction 是图像预测类,具有以下字段:

    • Score 包含给定图像分类的置信度百分比。
    • PredictedLabelValue 包含预测图像分类标签的值。

    ImagePrediction 是在训练模型后用于预测的类。 它包含图像路径的 string (ImagePath)。 Label 用于重用和训练模型。 PredictedLabelValue 在预测和评估过程中使用。 对于计算,将使用带定型数据的输入、预测值和模型。

初始化变量

  1. 使用 MLContext的新实例初始化 mlContext 变量。 将 Console.WriteLine("Hello World!") 行替换为以下代码:

    MLContext mlContext = new MLContext();
    

    MLContext 类是所有 ML.NET 操作的起点,初始化 mlContext 创建一个新的 ML.NET 环境,该环境可在模型创建工作流对象之间共享。 从概念上讲,它类似于实体框架中的 DBContext

为 Inception 模型参数创建结构

  1. Inception 模型有多个需要传入的参数。 创建一个结构,在初始化 mlContext 变量后,使用以下代码将参数值映射到友好名称:

    struct InceptionSettings
    {
        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;
    }
    

创建显示实用工具方法

由于将多次显示图像数据和相关预测,因此请创建一个显示实用工具方法来处理显示图像和预测结果。

  1. 使用以下代码在 InceptionSettings 结构之后创建 DisplayResults() 方法:

    void DisplayResults(IEnumerable<ImagePrediction> imagePredictionData)
    {
    
    }
    
  2. 填充 DisplayResults 方法的主体:

    foreach (ImagePrediction prediction in imagePredictionData)
    {
        Console.WriteLine($"Image: {Path.GetFileName(prediction.ImagePath)} predicted as: {prediction.PredictedLabelValue} with score: {prediction.Score?.Max()} ");
    }
    

创建用于进行预测的方法

  1. 使用以下代码在 DisplayResults() 方法之前创建 ClassifySingleImage() 方法:

    void ClassifySingleImage(MLContext mlContext, ITransformer model)
    {
    
    }
    
  2. 创建 ImageData 对象,其中包含单个 ImagePath 的完全限定路径和图像文件名。 将以下代码添加为 ClassifySingleImage() 方法中的下一行:

    var imageData = new ImageData()
    {
        ImagePath = _predictSingleImage
    };
    
  3. 通过将以下代码添加为 ClassifySingleImage 方法中的下一行来进行单个预测:

    // Make prediction function (input = ImageData, output = ImagePrediction)
    var predictor = mlContext.Model.CreatePredictionEngine<ImageData, ImagePrediction>(model);
    var prediction = predictor.Predict(imageData);
    

    若要获取预测,请使用 Predict() 方法。 PredictionEngine 是一种方便的 API,可用于对单个数据实例执行预测。 PredictionEngine 不是线程安全的。 可以在单线程或原型环境中使用。 为了提高生产环境中的性能和线程安全性,请使用 PredictionEnginePool 服务,该服务可创建 PredictionEngine 对象的 ObjectPool,以便在应用程序中使用。 请参阅本指南,了解如何在 ASP.NET Core Web API中 使用

    注意

    PredictionEnginePool 服务扩展目前为预览版。

  4. ClassifySingleImage() 方法中将预测结果显示为下一行代码:

    Console.WriteLine($"Image: {Path.GetFileName(imageData.ImagePath)} predicted as: {prediction.PredictedLabelValue} with score: {prediction.Score?.Max()} ");
    

构造 ML.NET 模型管道

ML.NET 模型管道是一系列估算器。 管道构造过程中不会发生任何执行。 估算器对象已创建但尚未执行。

  1. 添加用于生成模型的方法

    此方法是本教程的核心。 它为模型创建管道,并训练管道以生成 ML.NET 模型。 它还根据以前未见的测试数据评估模型。

    使用以下代码在 InceptionSettings 结构之后和 DisplayResults() 方法之前创建 GenerateModel() 方法:

    ITransformer GenerateModel(MLContext mlContext)
    {
    
    }
    
  2. 添加估算器以加载、调整大小并从图像数据中提取像素:

    IEstimator<ITransformer> pipeline = mlContext.Transforms.LoadImages(outputColumnName: "input", imageFolder: _imagesFolder, inputColumnName: nameof(ImageData.ImagePath))
                    // The image transforms transform the images into the model's expected format.
                    .Append(mlContext.Transforms.ResizeImages(outputColumnName: "input", imageWidth: InceptionSettings.ImageWidth, imageHeight: InceptionSettings.ImageHeight, inputColumnName: "input"))
                    .Append(mlContext.Transforms.ExtractPixels(outputColumnName: "input", interleavePixelColors: InceptionSettings.ChannelsLast, offsetImage: InceptionSettings.Mean))
    

    需要将图像数据处理成 TensorFlow 模型所需的格式。 在这种情况下,图像将加载到内存中,大小调整为一致大小,并将像素提取到数值向量中。

  3. 添加估算器以加载 TensorFlow 模型并对其进行评分:

    .Append(mlContext.Model.LoadTensorFlowModel(_inceptionTensorFlowModel).
        ScoreTensorFlowModel(outputColumnNames: new[] { "softmax2_pre_activation" }, inputColumnNames: new[] { "input" }, addBatchDimensionInput: true))
    

    管道中的此阶段将 TensorFlow 模型加载到内存中,然后通过 TensorFlow 模型网络处理像素值的向量。 将输入应用于深度学习模型并使用该模型生成输出的过程称为评分。 整体使用模型时,评分会进行推理或预测。

    在这种情况下,可以使用除最后一层以外的所有 TensorFlow 模型,这是进行推理的层。 倒数第二层的输出标有 softmax_2_preactivation。 此层的输出实际上是特征的矢量,用于描述原始输入图像的特征。

    TensorFlow 模型生成的此功能向量将用作 ML.NET 训练算法的输入。

  4. 添加估算器以将训练数据中的字符串标签映射到整数键值:

    .Append(mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "LabelKey", inputColumnName: "Label"))
    

    接下来追加的 ML.NET 训练程序要求其标签采用 key 格式而不是任意字符串。 键是一个数字,一对一映射到字符串值。

  5. 添加 ML.NET 训练算法:

    .Append(mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy(labelColumnName: "LabelKey", featureColumnName: "softmax2_pre_activation"))
    
  6. 添加估算器以将预测的键值映射回字符串:

    .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabelValue", "PredictedLabel"))
    .AppendCacheCheckpoint(mlContext);
    

训练模型

  1. 使用 LoadFromTextFile 包装器加载训练数据。 将以下代码添加为 GenerateModel() 方法中的下一行:

    IDataView trainingData = mlContext.Data.LoadFromTextFile<ImageData>(path:  _trainTagsTsv, hasHeader: false);
    

    ML.NET 中的数据表示为 IDataView 接口IDataView 是描述表格数据(数字和文本)的灵活高效方法。 可以将数据从文本文件或实时(例如 SQL 数据库或日志文件)加载到 IDataView 对象。

  2. 使用上面加载的数据训练模型:

    ITransformer model = pipeline.Fit(trainingData);
    

    Fit() 方法通过将训练数据集应用于管道来训练模型。

评估模型的准确性

  1. 通过将以下代码添加到 GenerateModel 方法的下一行来加载和转换测试数据:

    IDataView testData = mlContext.Data.LoadFromTextFile<ImageData>(path: _testTagsTsv, hasHeader: false);
    IDataView predictions = model.Transform(testData);
    
    // Create an IEnumerable for the predictions for displaying results
    IEnumerable<ImagePrediction> imagePredictionData = mlContext.Data.CreateEnumerable<ImagePrediction>(predictions, true);
    DisplayResults(imagePredictionData);
    

    可以使用一些示例图像来评估模型。 与训练数据一样,需要将这些数据加载到 IDataView中,以便模型可以转换它们。

  2. 将以下代码添加到 GenerateModel() 方法以评估模型:

    MulticlassClassificationMetrics metrics =
        mlContext.MulticlassClassification.Evaluate(predictions,
            labelColumnName: "LabelKey",
            predictedLabelColumnName: "PredictedLabel");
    

    在你设置预测后,Evaluate() 方法便能:

    • 评估模型(将预测值与测试数据集 labels进行比较)。
    • 返回模型性能指标。
  3. 显示模型准确性指标

    使用以下代码显示指标、共享结果,然后对其执行操作:

    Console.WriteLine($"LogLoss is: {metrics.LogLoss}");
    Console.WriteLine($"PerClassLogLoss is: {String.Join(" , ", metrics.PerClassLogLoss.Select(c => c.ToString()))}");
    

    为图像分类评估以下指标:

    • Log-loss - 请参阅对数损失。 通常会希望对数损失尽可能接近 0。
    • Per class Log-loss。 希望每个类的 Log 损失尽可能接近零。
  4. 添加以下代码,将经过训练的模型作为下一行代码返回:

    return model;
    

运行应用程序

  1. 在创建 MLContext 类后,添加对 GenerateModel 的调用:

    ITransformer model = GenerateModel(mlContext);
    
  2. 在调用 GenerateModel() 方法后,添加对 ClassifySingleImage() 方法的调用。

    ClassifySingleImage(mlContext, model);
    
  3. 运行控制台应用(Ctrl + F5)。 结果应类似于以下输出。 (你可能会看到警告或处理消息,但这些消息已从以下结果中删除,以便清楚起见。

    =============== Training classification model ===============
    Image: broccoli2.jpg predicted as: food with score: 0.8955513
    Image: pizza3.jpg predicted as: food with score: 0.9667718
    Image: teddy6.jpg predicted as: toy with score: 0.9797683
    =============== Classification metrics ===============
    LogLoss is: 0.0653774699265059
    PerClassLogLoss is: 0.110315812569315 , 0.0204391272836966 , 0
    =============== Making single image classification ===============
    Image: toaster3.jpg predicted as: appliance with score: 0.9646884
    

祝贺! 现在,你已在 ML.NET 中成功构建了一个分类模型,以便使用预先训练的 TensorFlow 对图像进行分类。

可以在 dotnet/samples 存储库中找到本教程的源代码。

在本教程中,你学习了如何:

  • 了解问题
  • 将预先训练的 TensorFlow 模型合并到 ML.NET 管道中
  • 训练和评估 ML.NET 模型
  • 对测试图像进行分类

查看机器学习示例 GitHub 存储库,浏览扩展的图像分类示例。