ML-NET-机器学习实用指南-三-

52 阅读25分钟

ML.NET 机器学习实用指南(三)

原文:annas-archive.org/md5/33e1ce66b918ce1fb5ef8f09832c0dab

译者:飞龙

协议:CC BY-NC-SA 4.0

第四部分:扩展 ML.NET

本节解释了如何使用其他格式的预训练模型与 ML.NET 结合使用,包括 TensorFlow 和 ONNX。此外,一些章节还将涵盖如何进行大规模训练以及使用 DMTP 项目所获得的经验教训。

本节包含以下章节:

  • 第十一章, 训练和构建生产模型

  • 第十二章, 使用 TensorFlow 与 ML.NET

  • 第十三章, 使用 ONNX 与 ML.NET

第十一章:训练和构建生产模型

随着我们进入本书的最后一部分,本章提供了在生产环境中使用机器学习的概述。在本书的这一部分,你已经学习了 ML.NET 提供的各种算法,并且已经创建了一套三个生产级应用。在积累了所有这些知识之后,你可能会想:我如何立即创建下一个杀手级机器学习应用?在直接回答这个问题之前,本章将帮助你为这一旅程的下一步做好准备。正如在前几章中讨论和使用的,训练模型有三个主要组成部分:特征工程、样本收集和创建训练管道。在本章中,我们将重点关注这三个组成部分,扩展你如何成功创建生产级模型的思考过程,并提供一些建议工具,以便能够通过生产级训练管道重复这一成功。

在本章中,我们将讨论以下内容:

  • 调查特征工程

  • 获取训练和测试数据集

  • 创建你的模型构建管道

调查特征工程

正如我们在前几章中讨论的,特征是模型构建过程中最重要的组成部分——客观上也是最重要的组成部分。在处理一个新问题时,出现的主要问题是:你将如何解决这个问题?例如,在网络安全领域的一个常见攻击手段是使用隐写术。隐写术可以追溯到公元前 440 年,是一种在容器中隐藏数据的行为。这个容器包括绘画、填字游戏、音乐、图片等。在网络安全领域,隐写术用于在通常会被忽略的文件中隐藏恶意负载,例如图像、音频和视频文件。

查看以下食物篮子的图像。这张图像——使用在线隐写术工具创建——其中包含一个嵌入的消息;看看你是否能在以下图像中找到任何异常模式:

目前大多数工具都可以在复杂和纯色图像中隐藏内容,以至于作为最终用户的你甚至都不会注意到——正如前一个示例所示。

在这个场景继续进行的情况下,你现在可能需要回答的一个快速问题是:文件中是否包含其他文件格式?另一个需要考虑的因素是问题的范围。尝试回答上述问题可能会导致对每个使用递归解析器分析的文件格式进行耗时深入分析——这并不是一开始就要解决的问题。更好的选择是将问题范围缩小到可能只分析音频文件或图像文件。进一步思考这个思路,让我们将问题范围缩小到特定的图像类型和有效载荷类型。

嵌入可执行文件的 PNG 图像文件

让我们深入探讨这个更具体的问题:我们如何在便携式网络图形PNG)文件中检测 Windows 可执行文件?对于那些好奇的人来说,选择 PNG 文件的具体原因在于,由于它们具有极高的图像质量与文件大小的比率,PNG 文件是一种非常常见的无损图像格式,被广泛应用于视频游戏和互联网。这种广泛的使用为攻击者提供了一个机会,他们可以在你的机器上获取 PNG 文件,而你作为最终用户甚至不会多想,这与专有格式或 Windows可执行文件EXE)不同,这很可能会引起最终用户的警觉。

在下一节中,我们将把 PNG 文件分解成以下步骤:

图片

要进一步深入了解 PNG 文件格式,PNG 的规范可以在以下链接找到:libpng.org/pub/png/spec/1.2/PNG-Contents.html

创建一个 PNG 解析器

现在,让我们深入探讨将 PNG 文件格式分解成特性,以便为检测隐藏的有效载荷驱动潜在的模型。PNG 文件由连续的 chunks 组成。每个 chunk 由一个头部描述字段和一个数据有效载荷组成。PNG 文件所需的 chunks 包括IHDRIDATIEND。根据规范,这些部分必须按此顺序出现。以下将解释每个部分。

在 chunks 之前的第一要素是实施检查,以确保文件实际上是一个 PNG 图像文件。这个检查通常被称为文件魔数检查。在我们数字世界的绝大多数文件都有一个独特的签名,这使得这些文件的解析和保存变得更加容易。

对于那些对其他文件格式签名好奇的人来说,一个详尽的列表可以在以下链接找到:www.garykessler.net/library/file_sigs.html

PNG 文件特别以以下字节开始:

137, 80, 78, 71, 13, 10, 26, 10

通过使用这些文件魔数字节,我们可以利用.NET 的SequenceEqual方法来比较文件数据的第一序列字节,如下面的代码所示:

using var ms = new MemoryStream(data);

byte[] fileMagic = new byte[FileMagicBytes.Length];

ms.Read(fileMagic, 0, fileMagic.Length);

if (!fileMagic.SequenceEqual(FileMagicBytes))
{
     return (string.Empty, false, null);
}

如果SequenceEqual方法与FileMagicBytes属性比较不匹配,我们返回 false。在这种情况下,该文件不是 PNG 文件,因此我们想要停止进一步解析文件。

从这个点开始,我们现在将遍历文件的块。在任何时候,如果字节没有正确设置,应该注意这一点,因为 Microsoft Paint 或 Adobe PhotoShop 会按照 PNG 文件格式的规范保存文件。另一方面,恶意生成者可能会像这里展示的那样,扭曲遵守 PNG 文件格式规范的规则:

while (ms.Position != data.Length)
{
    byte[] chunkInfo = new byte[ChunkInfoSize];

    ms.Read(chunkInfo, 0, chunkInfo.Length);

    var chunkSize = chunkInfo.ToInt32();

    byte[] chunkIdBytes = new byte[ChunkIdSize];

    ms.Read(chunkIdBytes, 0, ChunkIdSize);

    var chunkId = Encoding.UTF8.GetString(chunkIdBytes);

    byte[] chunk = new byte[chunkSize];

    ms.Read(chunk, 0, chunkSize);

    switch (chunkId)
    {
        case nameof(IHDR):
            var header = new IHDR(chunk);

            // Payload exceeds length
            if (data.Length <= (header.Width * header.Height * MaxByteDepth) + ms.Position)
            {
                break;
            }

            return (FileType, false, new[] { "SUSPICIOUS: Payload is larger than what the size should be" });
        case nameof(IDAT):
            // Build Embedded file from the chunks
            break;
        case nameof(IEND):
            // Note that the PNG had an end
            break;
    }
}

对于每个块,我们读取ChunkInfoSize变量,它定义为 4 字节。一旦读取了这个ChunkInfoSize数组,它就包含了要读取的块的大小。除了确定我们要读取的块类型外,我们还读取了 4 字节的块,用于 4 个字符的字符串(IHDRIDATIEND)。

一旦我们有了块大小和类型,我们就构建每个类的对象表示。在这个代码示例的范围内,我们将只查看 IHDR 类的片段,它包含高级图像属性,如尺寸、位深度和压缩:

public class IHDR
{
    public Int32 Width;

    public Int32 Height;

    public byte BitDepth;

    public byte ColorType;

    public byte Compression;

    public byte FilterMethod;

    public byte Interlace;

    public IHDR(byte[] data)
    {
        Width = data.ToInt32();

        Height = data.ToInt32(4);
    }
}

我们只提取WidthHeight属性,它们是前 8 个字节(每个 4 字节)。在这个例子中,我们还使用了一个扩展方法将字节数组转换为**Int32**数组。在大多数情况下,BitConverter会是理想的选择,然而,在这个代码示例中,我想简化数据的顺序访问,例如在检索前面提到的Height属性时偏移 4 字节。

之前提到的 IDAT 块是实际图像数据——以及可能包含嵌入有效载荷的块。IEND,正如其名所示,只是告诉 PNG 解析器文件已完整,也就是说,IEND 块中没有有效载荷。

一旦文件被解析,我们返回文件类型(PNG)——无论它是否是一个有效结构的 PNG 文件——并且我们注意任何可疑之处,例如如果文件大小远远大于根据宽度、高度和最大位深度(24)应有的大小。对于这些注释中的每一个,它们都可以在生产模型中与有效/无效标志一起标准化。此外,这些可以用简单的枚举表示数字。

对于那些对完整应用程序的源代码感到好奇的人,请参阅github.com/jcapellman/virus-tortoise,它利用了在第九章“使用 ML.NET 与 ASP.NET Core”部分中展示的创建文件分类应用程序中展示的许多相同原则。

将这个例子进一步扩展,迭代包含实际图像数据——以及可能的可执行有效载荷的 IDAT 块,将完成生产应用程序中的提取器。

现在我们已经看到了构建生产级功能所需的工作量,让我们深入探讨构建生产级训练数据集。

获取训练和测试数据集

现在我们已经完成了关于特征工程讨论,下一步是获取一个数据集。对于某些问题,这可能非常困难。例如,当试图预测别人没有做过的事情,或者在一个新兴领域时,拥有一个用于训练的训练集会比我们之前的例子中寻找恶意文件更困难。

另一个需要考虑的方面是多样性和数据是如何划分的。例如,考虑您如何使用 ML.NET 提供的异常检测训练器根据行为分析预测恶意 Android 应用程序。当思考构建您的数据集时,我敢说,大多数 Android 用户,他们的应用程序中恶意软件的比例不会超过一半。因此,训练和测试集的恶意和良性(50/50)划分可能会过度拟合恶意应用程序。弄清楚和分析您的目标用户将遇到的实际代表性是至关重要的,否则您的模型可能会倾向于假阳性或假阴性,这两种情况您的最终用户都不会满意。

在训练和测试数据集时需要考虑的最后一个元素是如何获取您的数据集。由于您的模型主要基于训练和测试数据集,因此找到代表您问题集的真实数据集至关重要。以之前的隐写术为例,如果您没有验证就随机抽取 PNG 文件,那么训练一个基于不良数据的模型是有可能的。对此的一种缓解措施是检查 IDAT 块中的隐藏有效载荷。同样,在 PNG 示例中对实际文件进行验证也是至关重要的。当您只针对生产应用中的 PNG 文件运行时,在 PNG 文件中混合训练 JPG、BMP 或 GIF 文件可能会导致假阳性或假阴性,因为其他图像格式的二进制结构不同于 PNG,这种非代表性数据将使训练集偏向于不受支持的格式。

对于网络安全领域的人来说,VirusTotal (www.virustotal.com) 和 Reversing Labs (www.reversinglabs.com) 提供了广泛的文件数据库,您可以付费下载,如果难以获取各种文件类型的数据源。

创建您的模型构建管道

一旦创建了特征提取器并获取了数据集,接下来要建立的是模型构建管道。模型构建管道的定义可以在以下图中更好地展示:

在下一节中,我们将讨论每个步骤如何与您选择的管道相关联。

讨论在管道平台中需要考虑的属性

有很多管道工具可用于本地部署,无论是在云中还是在SaaS软件即服务)服务中。我们将审查行业中一些更常用的平台。然而,以下是一些无论你选择哪个平台都需要记住的要点:

  • 速度对于多个原因来说都很重要。在构建你的初始模型时,迭代的速度非常重要,因为你很可能会调整你的训练集和超参数,以测试各种组合。在流程的另一端,当你处于预生产或生产阶段时,与测试人员或客户(他们正在等待新模型以解决问题或添加功能)迭代的速度在大多数情况下是关键的。

  • 可重复性同样重要,以确保在给定相同的训练数据集、特征和超参数的情况下,每次都能重建一个完美的模型。尽可能多地利用自动化是避免模型训练中人为错误的一个方法,同时也有助于提高可重复性。下一节将要审查的所有平台都提倡在启动新的训练会话后,无需任何人工输入即可定义流程。

  • 版本控制和比较跟踪对于确保在做出更改时能够进行比较非常重要。例如,无论是超参数——比如 FastTree 模型中树的深度——还是你添加的额外样本,在迭代过程中跟踪这些变化至关重要。假设你进行了一次文档化的更改,而你的效果显著下降,你总是可以回过头来评估这一变化。如果你没有为比较版本化或记录你的个人更改,这种简单的更改可能很难确定效果下降的原因。跟踪的另一个要素是跟踪一段时间内的进度,比如按季度或按年。这种级别的跟踪可以帮助描绘一幅图景,也可以帮助推动下一步行动或跟踪效果的趋势,以便获取更多样本或添加更多特征。

  • 最后,质量保证对于多个原因来说都很重要,在几乎所有情况下,对项目的成功或失败都至关重要。想象一下,一个模型直接部署到生产环境中,没有任何由专门的质量保证团队进行的额外检查,包括手动和自动测试。自动测试——就像一组单元测试,确保在发布和进入生产之前,样本在模型之间测试相同或更好——可以是一个好的临时解决方案,而不是一个完整的自动测试套件,该套件具有特定的效果范围,需要保持在其中。

在执行上一节中讨论的模型构建管道的每个步骤时,应考虑这四个要素。交付的最后一步取决于前三个要素的正确完成。实际的交付取决于您的应用程序。例如,如果您正在创建一个 ASP.NET 应用程序,例如我们在第九章中创建的应用程序,使用 ML.NET 与 ASP.NET Core,将 ML.NET 模型作为 Jenkins 管道的一部分添加——这样它就会自动与您的部署捆绑在一起——将是一个好的方法。

探索机器学习平台

以下是我个人使用过,以及/或者同事使用过以解决各种问题的平台。每个平台都有其优缺点,尤其是在我们试图解决的每个问题的独特性方面。

Azure Machine Learning

微软的 Azure 云平台提供了一个完整的平台,包括 Kubernetes、虚拟机和数据库,以及提供机器学习平台。该平台提供了直接连接到 Azure SQL 数据库、Azure 文件存储和公共 URL 等,仅举几个例子,用于训练和测试集。在 Visual Studio Community 2019 中提供了一个免费的不具备扩展性的轻量级版本。以下截图显示了完整的用户界面:

图片

此外,还完全支持非.NET 技术,如 TensorFlow、PyTorch 和 scikit-learn。流行的 Jupyter Notebook 和 Azure Notebook 等工具也完全支持。

与 Apache Airflow 类似,在 Azure 机器学习中比较运行历史记录以比较版本也很容易做到。

所有上述模型构建管道的阶段都得到了支持。以下是 Azure 机器学习的优缺点:

优点:

  • 与多个数据源广泛集成

  • ML.NET 原生支持

  • 根据您的需求进行扩展和缩减

  • 无需基础设施设置

缺点:

  • 训练时可能很昂贵

Apache Airflow

Apache Airflow,一款开源软件,提供了创建几乎无限复杂性的管道的能力。虽然不是原生支持的框架,但.NET Core 应用程序——例如本书中创建的那些——可以在安装.NET Core 运行时或简单地使用自包含标志编译的情况下运行。虽然学习曲线高于微软的 Azure 机器学习平台,但在某些场景下免费,尤其是在仅仅进行实验时,可能更有益。以下截图显示了 Airflow 的用户界面:

图片

与 Azure 机器学习类似,管道的可视化确实使得特定管道的配置比 Apache Spark 更容易。然而,与 Apache Spark 类似,设置和配置(取决于你的技能水平)可能相当令人望而却步,尤其是在 pip 安装之后。一个更容易上手的方法是使用预构建的 Docker 容器,例如 Puckel 的 Docker 容器(hub.docker.com/r/puckel/docker-airflow)。

下面是 Apache Airflow 的一些优缺点:

优点:

  • 免费且开源

  • 提供了 4 年以上的文档和示例

  • 在 Windows、Linux 和 macOS 上运行

缺点:

  • 设置复杂(尤其是使用官方 pip 说明)

  • .NET 不是原生支持的

Apache Spark

Apache Spark,作为另一个开源工具,虽然通常用于大数据管道,但也可以配置用于特征提取、训练和大规模模型的生成。例如,当内存和 CPU 限制阻碍你构建模型时,比如使用大规模数据集进行训练,我亲眼看到 Apache Spark 可以扩展到利用多个 64C/128T AMD 服务器,并最大化超过 1TB 的 RAM。我发现这个平台比 Apache Airflow 或 Azure 的机器学习平台设置起来更困难,但一旦设置好,它就可以非常强大。以下截图显示了 Apache Spark 的用户界面:

图片

可以在微软的 Apache Spark 页面(dotnet.microsoft.com/learn/data/spark-tutorial/intro)上找到优秀的分步安装指南,适用于 Windows 和 Linux。这个指南确实消除了许多未知因素,然而,与 Azure 或 Airflow 相比,它仍然远非容易上手。以下是 Apache Spark 的一些优缺点:

优点:

  • 免费且开源

  • 来自微软的.NET 绑定

  • 由于其悠久的历史(超过 5 年),有大量的文档

  • 在 Windows、macOS 和 Linux 上运行

缺点:

  • 配置和启动可能比较困难

  • 对 IT 基础设施变化敏感

微软为 Apache Spark 编写了.NET 绑定,并将其免费发布:dotnet.microsoft.com/apps/data/spark。这些绑定适用于 Windows、macOS 和 Linux。

摘要

在本章的整个过程中,我们深入探讨了从原始目的问题到训练模型的生产就绪模型训练所涉及的内容。通过这次深入探讨,我们检查了创建详细特征所需的工作量,这些特征是通过生产思维过程和特征工程实现的。然后,我们回顾了挑战、解决训练的方法以及如何测试数据集问题。最后,我们还深入探讨了实际模型构建管道的重要性,使用完全自动化的流程。

在下一章中,我们将在一个 WPF 应用程序中使用预构建的 TensorFlow 模型来确定提交的图像是否包含某些对象。这次深入探讨将介绍 ML.NET 如何为 TensorFlow 模型提供一个易于使用的接口。

第十二章:使用 TensorFlow 与 ML.NET

在本章中,我们将使用预训练的 TensorFlow 模型,特别是 Inception 模型,并将其集成到Windows Presentation Foundation(WPF)应用程序中。我们将使用预训练的模型并应用迁移学习,通过添加一些食物和水的图片。在迁移学习完成后,我们允许用户选择他们自己的图片。到本章结束时,你应该对将 TensorFlow 模型集成到你的 ML.NET 应用程序中所需的内容有一个牢固的掌握。

本章将涵盖以下主题:

  • 拆解谷歌的 Inception 模型

  • 创建图像分类桌面应用程序

  • 探索额外的生产应用程序增强功能

拆解谷歌的 Inception 模型

谷歌的 Inception 模型(github.com/google/inception)已经在数百万张图像上进行了训练,以帮助解决我们社会中日益增长的问题之一——我的图像中有什么?想要回答这个问题的应用程序类型范围很广,从匹配人脸、自动检测武器或不希望的对象、游戏图片中的运动品牌(如运动鞋品牌),到为用户提供无需手动标签即可搜索的支持的图像归档器,仅举几例。

这类问题通常用物体识别来回答。你可能已经熟悉的一种物体识别应用是光学字符识别(OCR)。OCR 是指字符图像可以被解释为文本,例如在微软的 OneNote 手写笔迹到文本功能中,或在读取车牌的收费站中。我们将特别关注的物体识别的特定应用称为图像分类

Inception 模型通过使用深度学习来分类图像来帮助解决这个问题。该模型在数百万张图像上以监督方法进行了训练,输出是一个神经网络。这种方法的优点是,预构建的模型可以通过添加较小子集的图像来增强,这就是我们在本章下一节将要做的。这种添加额外数据和标签的方法称为迁移学习。当创建特定于客户模型的时,这种方法也可能很有帮助。

想象一下就像在 GitHub 的 master 分支中创建一个分支;你可能只想添加一个类或修改一个元素,而不必重新创建整个代码库。至于模型,以汽车图像分类器为例。假设你获得了数百万张涵盖美国和外国汽车、卡车、面包车等图片。一位新客户来找你,要求你创建一个模型来帮助监控进入政府设施的车辆。之前的模型不应该被丢弃,也不需要完全重新训练,只需添加更多带有标签的商业(或可能是军事)车辆即可。

对于更深入地了解 Google 的图像分类,一个很好的资源是他们的开发者文档,可以在developers.google.com/machine-learning/practica/image-classification/找到。

创建 WPF 图像分类应用程序

如前所述,我们将要创建的应用程序是一个图像分类应用程序,具体允许用户选择一张图片并确定它是食物还是水。这是通过上述和包含的预训练 TensorFlow Inception 模型实现的。当应用程序第一次运行时,ML.NET 版本的模型使用图片和tags.tsv文件(将在下一节中讨论)进行训练。

与前几章一样,完成的项目代码、样本数据集和项目文件可以在此处下载:github.com/PacktPublishing/Hands-On-Machine-Learning-With-ML.NET/tree/master/chapter12.

探索项目架构

在本章中,我们将深入探讨一个 WPF 桌面应用程序。正如本章第一部分所提到的,我们将使用 WPF 框架来创建我们的应用程序。你可能会问,为什么不使用 UWP 应用程序,比如我们在第十章中创建的浏览器应用程序?至少在撰写本文时,原因在于 TensorFlow 在 UWP 应用程序中,特别是对于图像分类,并不完全受支持。也许在 ML.NET 的未来版本中,这将被添加。对于其他非图像类应用程序,你可能在 UWP 应用程序中使用 TensorFlow。

那些之前进行过 WPF 开发并且仔细观察的人会注意到,该项目使用了.NET Core 3.1。在.NET Core 3.0 中,Microsoft 添加了对 WPF 和 WinForms 的支持,因此,你不再仅限于 Windows 的.NET Framework 进行 GUI 开发。相反,这种支持是通过Microsoft.WindowsDesktop.App.WPF NuGet 包添加的。

在这个例子中,我们将使用Microsoft.ML (1.3.1) NuGet 包——以及几个额外的 NuGet 包——以便在我们的.NET 应用程序中利用 TensorFlow。以下包括以下内容:

  • Microsoft.ML.ImageAnalytics (1.3.1)

  • Microsoft.ML.TensorFlow (1.3.1)

  • SciSharp.TensorFlow.Redist (1.14.0)

到你阅读这个的时候,可能已经有这些包的新版本,它们应该可以工作,然而,上面提到的版本是我们将在这次深入研究中使用,以及 GitHub 仓库中可用的版本。

在以下屏幕截图中,您将找到解决方案的 Visual Studio Solution Explorer 视图。由于 TensorFlow 对项目类型和 CPU 目标的要求更为严格,我们回到了一个单一的项目,而不是在前面几章中使用过的三个项目架构:

图片

tags.tsv文件(位于代码仓库中的assets\images文件夹中)包含八行,这些行将包含的图像映射到预分类:

ChickenWings.jpg food
Steak.jpg food
Pizza.jpg food
MongolianGrill.jpg food
Bay.jpg water
Bay2.jpg water
Bay3.jpg water
Beach.jpg water

如果你想尝试自己的分类,请删除包含的图像,复制你的图像,并更新tags.tsv文件以包含标签。我应该注意的是,所有包含的图像都是我在各种加州度假时拍摄的——请随意使用。

assets/inception文件夹中的文件包含所有 Google 预训练文件(以及许可文件)。

深入 WPF 图像分类应用程序

如在开头部分所述,我们的桌面应用程序是一个 WPF 应用程序。在本例的范围内,如第十章中所述,使用 ML.NET 与 UWP,我们通过遵循模型-视图-视图模型MVVM)设计模式来使用标准方法处理应用程序架构。

在本节中我们将深入研究的文件如下:

  • MainWindowViewModel

  • MainWindow.xaml

  • MainWindow.xaml.cs

  • BaseML

  • ImageDataInputItem

  • ImageDataPredictionItem

  • ImageClassificationPredictor

WPF 项目中的其余文件都未从默认的 Visual Studio .NET Core 3.1 WPF 应用程序模板中进行修改;例如,App.xamlAssemblyInfo.cs文件。

MainWindowViewModel

MainWindowViewModel类的目的是包含我们的业务逻辑并控制视图,如下所示:

  1. 我们首先实例化之前讨论过的ImageClassificationPredictor类,以便它可以用于运行预测:
private readonly ImageClassificationPredictor _prediction = new ImageClassificationPredictor();
  1. 下一个代码块处理了 MVVM 对分类字符串的强大功能,并存储选定的图像。对于这些属性中的每一个,当值发生变化时,我们调用OnPropertyChanged,这触发了视图与这些属性绑定的任何字段的绑定刷新:
private string _imageClassification;

public string ImageClassification
{
    get => _imageClassification;

    set
    {
        _imageClassification = value;
        OnPropertyChanged();
    }
}

private ImageSource _imageSource;

public ImageSource SelectedImageSource
{
    get => _imageSource;

    set
    {
        _imageSource = value;
        OnPropertyChanged();
    }
}
  1. 接下来,我们定义 Initialize 方法,它调用预测器的 Initialize 方法。该方法将返回一个元组,表示模型无法加载或找不到,以及异常(如果抛出):
public (bool Success, string Exception) Initialize() => _prediction.Initialize();
  1. 然后,我们处理用户点击选择图片按钮时会发生什么。该方法打开一个对话框,提示用户选择一个图片。如果用户取消对话框,则方法返回。否则,我们调用两个辅助方法将图片加载到内存中并对图片进行分类:
public void SelectFile()
{
    var ofd = new OpenFileDialog
    {
        Filter = "Image Files(*.BMP;*.JPG;*.PNG)|*.BMP;*.JPG;*.PNG"
    };

    var result = ofd.ShowDialog();

    if (!result.HasValue || !result.Value)
    {
        return;
    }

    LoadImageBytes(ofd.FileName);

    Classify(ofd.FileName);
}
  1. LoadImageBytes 方法接受文件名并将图片加载到我们的基于 MVVM 的 ImageSource 属性中,因此选择后,图片控件会自动更新为所选图片的视图:
private void LoadImageBytes(string fileName)
{
    var image = new BitmapImage();

    var imageData = File.ReadAllBytes(fileName);

    using (var mem = new MemoryStream(imageData))
    {
        mem.Position = 0;

        image.BeginInit();

        image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
        image.CacheOption = BitmapCacheOption.OnLoad;
        image.UriSource = null;
        image.StreamSource = mem;

        image.EndInit();
    }

    image.Freeze();

    SelectedImageSource = image;
}
  1. 最后,Classify 方法接受路径并将其传递给 Predictor 类。在返回预测后,分类和置信度将集成到我们的 MVVM ImageClassification 属性中,因此 UI 会自动更新:
public void Classify(string imagePath)
{
 var result = _prediction.Predict(imagePath);

 ImageClassification = $"Image ({imagePath}) is a picture of {result.PredictedLabelValue} with a confidence of {result.Score.Max().ToString("P2")}";
}

MainWindowViewModel 类的最后一个元素是我们之前在第十章 使用 ML.NET 与 UWP 中定义的相同的 OnPropertyChanged 方法,它允许 MVVM 魔法发生。定义了我们的 ViewModel 类后,让我们继续到 MainWindow XAML 文件。

MainWindow.xaml

如在第十章 分解 UWP 架构 部分的 使用 ML.NET 与 UWP 节中所述,当描述开发时,XAML 标记用于定义用户界面。对于本应用程序的范围,我们的 UI 相对简单:ButtonImage ControlTextBlock

现在我们来看看代码:

  1. 我们首先定义的是我们的网格。在 XAML 中,网格是一个类似于网络开发中的 <div> 的容器。然后我们定义我们的行。与 Bootstrap 类似(但在我看来更容易理解),是预先定义每行的高度。将行设置为 Auto 将自动调整高度以适应内容的高度,而星号则表示根据主容器的高度使用所有剩余的高度:
<Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
    <RowDefinition Height="Auto" />
</Grid.RowDefinitions>
  1. 我们首先定义我们的 Button 对象,它将在我们的 ViewModel 类中触发上述的 SelectFile 方法:
<Button Grid.Row="0" Margin="0,10,0,0" Width="200" Height="35" Content="Select Image File" HorizontalAlignment="Center" Click="btnSelectFile_Click" />
  1. 我们接着定义我们的 Image 控件,它绑定到我们之前审查过的 SelectedImageSource 属性,该属性位于我们的 ViewModel 类中:
<Image Grid.Row="1" Margin="10,10,10,10" Source="{Binding SelectedImageSource}" />
  1. 我们接着添加将显示我们分类的 TextBlock 控件:
<TextBlock Grid.Row="2" Text="{Binding ImageClassification, Mode=OneWay}" TextWrapping="Wrap" Foreground="White" Margin="10,10,10,10" HorizontalAlignment="Center" FontSize="16" />

在定义了视图的 XAML 方面之后,现在让我们深入了解 MainWindow 类的代码部分。

MainWindow.xaml.cs 文件

MainWindow.xaml.cs 文件包含 XAML 视图的代码,相关内容在此讨论:

  1. 我们首先定义的是围绕 DataContext 属性的包装属性,该属性是基 Window 类中内置的:
private MainWindowViewModel ViewModel => (MainWindowViewModel) DataContext;
  1. 接下来,我们定义MainWindow的构造函数,以便将DataContext属性初始化为我们的MainWindowViewModel对象。如果初始化失败,我们不希望应用程序继续运行。此外,我们需要使用MessageBox对象让用户知道为什么它失败了:
public MainWindow()
{
    InitializeComponent();

    DataContext = new MainWindowViewModel();

    var (success, exception) = ViewModel.Initialize();

    if (success)
    {
        return;
    }

    MessageBox.Show($"Failed to initialize model - {exception}");

    Application.Current.Shutdown();
}
  1. 最后,我们调用 ViewModel 的SelectFile方法来处理图像选择和分类:
private void btnSelectFile_Click(object sender, RoutedEventArgs e) => ViewModel.SelectFile();

在我们完成了MainWindow类的代码之后,这就完成了 WPF 组件。现在让我们专注于示例中的机器学习部分。

基础 ML 类

BaseML类,在大多数之前的示例中都被使用,暴露了我们的 ML.NET 类的基础类。在这个示例中,由于使用了预训练模型,我们实际上简化了类。现在这个类只是初始化了MLContext属性:

public class BaseML
{
    protected MLContext MlContext;

    public BaseML()
    {
        MlContext = new MLContext(2020);
    }
}

在审查了简化的BaseML类之后,让我们深入到ImageDataInputItem类。

图像数据输入项类

ImageDataInputItem类包含了传递给模型的类;基本属性是ImagePath属性:

public class ImageDataInputItem
{
    [LoadColumn(0)]
    public string ImagePath;

    [LoadColumn(1)]
    public string Label;
}

尽管比我们的大多数输入类小,Inception 模型只需要两个属性。现在,让我们深入到被称为ImageDataPredictionItem的输出类。

图像数据预测项类

ImageDataPredictionItem类包含了预测响应,包括预测值字符串的置信度(在包含的图像中包含WaterFood):

public class ImageDataPredictionItem : ImageDataInputItem
{
    public float[] Score;

    public string PredictedLabelValue;
}

与输入类类似,输出类也只有两个属性,类似于之前的示例。在处理完输入和输出类之后,让我们深入到使用这些类进行迁移学习和预测的ImageClassificationPredictor类。

图像分类预测器类

ImageClassificationPredictor类包含了加载和针对 Inception TensorFlow 模型进行预测所需的所有代码:

  1. 首先,我们需要定义几个辅助变量来访问图像和.tsv文件:
// Training Variables
private static readonly string _assetsPath = Path.Combine(Environment.CurrentDirectory, "assets");
private static readonly string _imagesFolder = Path.Combine(_assetsPath, "images");
private readonly string _trainTagsTsv = Path.Combine(_imagesFolder, "tags.tsv");
private readonly string _inceptionTensorFlowModel = Path.Combine(_assetsPath, "inception", "tensorflow_inception_graph.pb");

private const string TF_SOFTMAX = "softmax2_pre_activation";
private const string INPUT = "input";

private static readonly string ML_NET_MODEL = Path.Combine(Environment.CurrentDirectory, "chapter12.mdl");
  1. 接下来,我们定义预训练的 Inception 模型所需的设置:
private 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. 接下来,我们创建我们的Predict方法,并重载它,它简单地接受图像文件路径。像之前的示例一样,我们通过调用我们的MLContext对象创建PredictionEngine,传入我们的输入类(ImageDataInputItem)和输出类(ImageDataPredictionItem),然后调用Predict方法来获取我们的模型预测:
public ImageDataPredictionItem Predict(string filePath) => 
    Predict(new ImageDataInputItem 
        {
            ImagePath = filePath 
        }
    );

public ImageDataPredictionItem Predict(ImageDataInputItem image)
{
    var predictor = MlContext.Model.CreatePredictionEngine<ImageDataInputItem, ImageDataPredictionItem>(_model);

    return predictor.Predict(image);
}

最后,我们使用自己的样本初始化并扩展我们的预训练模型:

public (bool Success, string Exception) Initialize()
{
    try
    {
        if (File.Exists(ML_NET_MODEL))
        {
            _model = MlContext.Model.Load(ML_NET_MODEL, out DataViewSchema modelSchema);

            return (true, string.Empty);
        }

       ...
    }
    catch (Exception ex)
    {
        return (false, ex.ToString());
    }
} 

对于完整代码,请参阅以下 GitHub 仓库链接:github.com/PacktPublishing/Hands-On-Machine-Learning-With-ML.NET/blob/master/chapter12/chapter12.wpf/ML/ImageClassificationPredictor.cs。完成 Initialize 方法后,代码深入探讨就结束了。现在让我们运行应用程序!

运行图像分类应用程序

由于我们使用的是预训练模型,因此可以直接从 Visual Studio 运行应用程序。运行应用程序后,您将看到一个几乎为空窗口:

图片

点击“选择图像文件”按钮并选择一个图像文件将触发模型运行。在我的情况下,我选择了一张最近去德国度假的照片,置信度得分为 98.84%:

图片

随意尝试您机器上的各种文件以查看置信度得分和分类——如果您开始注意到问题,请按照前面章节所述,向图像文件夹和 tags.tsv 文件中添加更多样本。在做出这些更改之前,请务必删除 chapter12.mdl 文件。

改进建议的额外想法

现在我们已经完成了深入探讨,还有一些额外的元素可能会进一步增强应用程序。这里讨论了一些想法。

基于最终用户输入的自训练

如本章开头所述,其中一个优点是能够在动态应用中利用迁移学习。与本书中已审查的先前示例应用不同,此应用实际上允许最终用户选择一系列(或文件夹)图像,并且只需进行少量代码更改,就可以构建新的 .tsv 文件并训练新的模型。对于 Web 应用或商业产品,这将提供很高的价值,同时也会减轻您获取各种类型图像的负担——这是一个令人畏惧的、很可能徒劳的目标。

日志记录

如第十章中提到的使用 ML.NET 与 UWP 的日志记录部分所述,桌面应用程序有其优点和缺点。最大的缺点是需要日志记录,因为您的桌面应用程序可以安装在从 Windows 7 到 Windows 10 的任何配置上,几乎有无限多的排列组合。如前所述,强烈建议使用 NLog (nlog-project.org/)或类似的开源项目进行日志记录,并结合远程日志解决方案,如 Loggly,以便从用户的机器上获取错误数据。鉴于 GDPR 和最近的 CCPA,我们需要确保离开最终用户机器的数据得到传达,并且这些日志不包含个人数据(或通过日志记录机制上传到远程服务器的实际图像)。

使用数据库

与第十章中性能优化建议使用 ML.NET 与 UWP 类似,如果用户多次选择相同的图像,尤其是在这个应用程序被用于自助终端或转换为 Web 应用程序的情况下,存储分类的性能优势可能相当显著。实现这一点的快速简单方法可能是对图像执行 SHA256 哈希,并检查该哈希值与数据库。根据用户数量以及他们是否将并发,我建议两种选择之一:

  • 如果用户逐个使用,并且应用程序将保持为 WPF 应用程序,那么推荐使用之前提到的轻量级数据库——LiteDB (www.litedb.org/)。

  • 如果您正在使用生产环境启动大型 Web 应用程序,那么为了确保数据库查找不会比重新执行模型预测慢,建议使用 MongoDB 或水平可扩展的数据库,如微软的 CosmosDB。

摘要

在本章的整个过程中,我们深入探讨了如何使用预训练的 TensorFlow 模型创建一个 WPF 应用程序。我们还回顾并仔细研究了谷歌的图像分类 Inception 模型。此外,我们学习了如何将这个模型集成到应用程序中,以便对用户选择的图像进行图像分类。最后,我们还讨论了一些进一步改进示例应用程序的方法。

在下一章和最后一章中,我们将专注于在 WPF 应用程序中使用预训练的 ONNX 模型进行目标检测。

第十三章:使用 ML.NET 与 ONNX

现在我们已经完成了使用 TensorFlow 和 ML.NET 的深入探索,现在是时候深入使用 Open Neural Network eXchangeONNX)与 ML.NET 的结合了。具体来说,在本章的最后一部分,我们将回顾 ONNX 是什么,以及创建一个名为 YOLO 的预训练 ONNX 模型的新示例应用程序。这个应用程序将基于上一章的内容,并显示模型检测到的对象的边界框。此外,我们将在本章结束时提出改进示例的建议,使其成为生产级应用程序或集成到生产应用程序中。

在本章中,我们将涵盖以下主题:

  • 解构 ONNX 和 YOLO

  • 创建 ONNX 物体检测应用程序

  • 探索额外的生产应用增强功能

解构 ONNX 和 YOLO

如 第一章 中所述,使用 ML.NET 开始机器学习之旅,ONNX 标准在业界被广泛认为是一个真正通用的格式,适用于各种机器学习框架。在接下来的两节中,我们将回顾 ONNX 提供的内容,以及将在本章示例中驱动的 YOLO 模型。

介绍 ONNX

ONNX 的创建是为了在处理预训练模型或跨框架训练模型时提供一个更开放和自由流动的过程。通过为框架提供一个开放的导出格式,ONNX 允许互操作性,从而促进了由于几乎每个框架中使用的专有格式的性质而可能难以进行的实验。

目前,支持的平台包括 TensorFlow、XGBoost 和 PyTorch——当然,还包括 ML.NET。

如果你想进一步深入了解 ONNX,请访问他们的网站:onnx.ai/index.html

YOLO ONNX 模型

基于 第十二章 中进行的操作,使用 ML.NET 的 TensorFlow,其中我们使用了预训练的 Inception 模型,在本章中,我们将使用预训练的 YOLO 模型。这个模型提供了非常快速和准确的对象检测,这意味着它可以在图像中找到多个对象,并具有一定的置信度。这与上一章提供的纯图像分类模型不同,例如水或食物。

为了帮助可视化两种模型之间的差异,可以将上一章的 TensorFlow 模型(用于分类水)与本章的汽车对象检测进行比较,如下面的截图所示:

图片

由于互联网上图像数量的显著增加以及安全需求,图像(和视频)中的目标检测需求不断增加。想象一下拥挤的环境,比如足球场,尤其是前门附近。保安巡逻并监控这个区域;然而,就像你一样,他们也是凡人,只能以一定程度的准确性瞥见那么多人。将机器学习中的目标检测实时应用于检测武器或大包,然后可以用来提醒保安追捕嫌疑人。

YOLO 模型本身有两种主要形式——小型和完整模型。在本例的范围内,我们将使用较小的模型(约 60 MB),它可以对图像中发现的 20 个对象进行分类。小型模型由九个卷积层和六个最大池化层组成。完整模型可以分类数千个对象,并且,在适当的硬件(特别是图形处理单元GPU))的支持下,可以比实时运行得更快。

以下图表展示了 YOLO 模型的工作原理(以及在一定程度上,神经网络):

图片

实际上,图像(或图像)被转换为 3 x 416 x 416 的图像。3 个组件代表红-绿-蓝RGB)值。416 个值代表调整大小后的图像的宽度和高度。这个输入层随后被输入到模型的隐藏层。对于我们在本章中使用的 Tiny YOLO v2 模型,在输出层之前共有 15 层。

要深入了解 YOLO 模型,请阅读这篇论文:arxiv.org/pdf/1612.08242.pdf

创建 ONNX 目标检测应用程序

如前所述,我们将创建的应用程序是一个使用预训练 ONNX 模型的目标检测应用程序。以我们在第十二章中开发的应用程序为起点,即使用 ML.NET 与 TensorFlow,我们将添加对模型分类已知对象时图像上叠加的边界框的支持。这种对公众的实用性在于图像目标检测提供的各种应用。想象一下,你正在为警察或情报社区的项目工作,他们有图像或视频,并希望检测武器。正如我们将展示的,利用 YOLO 模型与 ML.NET 将使这个过程变得非常简单。

与前几章一样,完整的项目代码、预训练模型和项目文件可以在此处下载:github.com/PacktPublishing/Hands-On-Machine-Learning-With-ML.NET/tree/master/chapter13

探索项目架构

在前几章中创建的项目架构和代码的基础上,我们将审查的架构得到了增强,使其更加结构化和便于最终用户使用。

如同一些前几章一样,如果您想利用 ONNX 模型进行对象检测,以下两个额外的 NuGet 包是必需的:

  • Microsoft.ML.ImageAnalytics

  • Microsoft.ML.OnnxTransformer

这些 NuGet 包已在包含的示例代码中引用。这些包的 1.3.1 版本在 GitHub 中的示例和本章的深入探讨中均使用。

在下面的屏幕截图中,您将找到项目的 Visual Studio 解决方案资源管理器视图。解决方案中添加了几个新内容,以方便我们针对的生产用例。我们将在本章后面的部分详细审查以下解决方案截图中的每个新文件:

由于当前 ML.NET 的限制,截至本文撰写时,ONNX 支持仅限于使用预存模型进行评分。本例中包含的预训练模型可在 assets/model 文件夹中找到。

深入代码

如前所述,对于这个应用程序,我们正在构建在 第十二章 完成的作品之上,使用 TensorFlow 与 ML.NET。虽然 用户界面UI) 没有太大变化,但运行 ONNX 模型的底层代码已经改变。对于每个更改的文件——就像前几章一样——我们将审查所做的更改及其背后的原因。

已更改或添加的类如下:

  • DimensionsBase

  • BoundingBoxDimensions

  • YoloBoundingBox

  • MainWindow.xaml

  • ImageClassificationPredictor

  • MainWindowViewModel

还有一个额外的文件,其中包含 YoloOutputParser 类。这个类是从 麻省理工学院MIT)许可的接口派生出来的,用于 TinyYOLO ONNX 模型。由于这个类的长度,我们不会对其进行审查;然而,代码易于阅读,如果您想逐步进行预测,流程将很容易跟随。

DimensionsBase

DimensionsBase 类包含坐标以及 HeightWidth 属性,如下面的代码块所示:

public class DimensionsBase
{
    public float X { get; set; }

    public float Y { get; set; }

    public float Height { get; set; }

    public float Width { get; set; }
}

这个基类被 YoloOutputParserBoundingBoxDimensions 类使用,以减少代码重复。

YoloBoundingBox 类

YoloBoundingBox 类提供了用于在生成叠加时填充边界框的容器类,如下面的代码块所示:

public class YoloBoundingBox
{
    public BoundingBoxDimensions Dimensions { get; set; }

    public string Label { get; set; }

    public float Confidence { get; set; }

    public RectangleF Rect => new RectangleF(Dimensions.X, Dimensions.Y, Dimensions.Width, Dimensions.Height);

    public Color BoxColor { get; set; }
}

此外,在同一个类文件中定义了我们的 BoundingBoxDimensions 类,如下面的代码块所示:

public class BoundingBoxDimensions : DimensionsBase { }

再次强调,这种继承用于减少代码重复。

MainWindow.xaml 文件

我们应用程序的可扩展应用程序标记语言XAML)视图已被简化为仅包含按钮和图像控件,如下面的代码块所示:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Button Grid.Row="0" Margin="0,10,0,0" Width="200" Height="35" Content="Select Image File" HorizontalAlignment="Center" Click="btnSelectFile_Click" />

    <Image Grid.Row="1" Margin="10,10,10,10" Source="{Binding SelectedImageSource}" />
</Grid>

此外,由于所选定的外接矩形和图像的性质,窗口默认设置为最大化,如下面的代码块所示:

<Window x:Class="chapter13.wpf.MainWindow"

        mc:Ignorable="d"
        ResizeMode="NoResize"
        WindowStyle="SingleBorderWindow"
        WindowState="Maximized"
        WindowStartupLocation="CenterScreen"
        Background="#1e1e1e"
        Title="Chapter 13" Height="450" Width="800">

在 XAML 更改完成后,现在让我们深入探讨修订后的ImageClassificationPredictor类。

ImageClassificationPredictor 类

ImageClassificationPredictor类,与第十二章中提到的类似,即使用 ML.NET 与 TensorFlow 结合使用,其中包含了运行图像预测的方法。在本章中,我们需要创建几个额外的类对象来支持 ONNX 模型的运行,具体如下:

  1. 首先,我们定义ImageNetSettings结构体,它定义了网络的宽度和高度。YOLO 模型需要使用 416 像素×416 像素,如下面的代码块所示:
public struct ImageNetSettings
{
    public const int imageHeight = 416;
    public const int imageWidth = 416;
}   
  1. 接下来,我们定义TinyYoloModelSettings结构体,用于与 ONNX 模型一起使用,如下所示:
public struct TinyYoloModelSettings
{
    public const string ModelInput = "image";

    public const string ModelOutput = "grid";
}
  1. 与前一章不同,在前一章中,TensorFlow 模型在第一次运行时被导入并导出为 ML.NET 模型,但截至本文写作时,ONNX 不支持该路径。因此,我们必须在Initialize方法中每次都加载 ONNX 模型,如下面的代码块所示:
public (bool Success, string Exception) Initialize()
{
    try
    {
        if (File.Exists(ML_NET_MODEL))
        {
            var data = MlContext.Data.LoadFromEnumerable(new List<ImageDataInputItem>());

            var pipeline = MlContext.Transforms.LoadImages(outputColumnName: "image", imageFolder: "", 
                    inputColumnName: nameof(ImageDataInputItem.ImagePath))
                .Append(MlContext.Transforms.ResizeImages(outputColumnName: "image", 
                    imageWidth: ImageNetSettings.imageWidth, 
                    imageHeight: ImageNetSettings.imageHeight, 
                    inputColumnName: "image"))
                .Append(MlContext.Transforms.ExtractPixels(outputColumnName: "image"))
                .Append(MlContext.Transforms.ApplyOnnxModel(modelFile: ML_NET_MODEL, 
                    outputColumnNames: new[] { TinyYoloModelSettings.ModelOutput }, 
                    inputColumnNames: new[] { TinyYoloModelSettings.ModelInput }));

            _model = pipeline.Fit(data);

            return (true, string.Empty);
        }

        return (false, string.Empty);
    }
    catch (Exception ex)
    {
        return (false, ex.ToString());
    }
}
  1. 接下来,我们广泛修改Predict方法以支持YoloParser调用,调用DrawBoundingBox方法来叠加外接矩形,然后返回更新后的图像的字节,如下所示:
public byte[] Predict(string fileName)
{
    var imageDataView = MlContext.Data.LoadFromEnumerable(new List<ImageDataInputItem> { new ImageDataInputItem { ImagePath = fileName } });

    var scoredData = _model.Transform(imageDataView);

    var probabilities = scoredData.GetColumn<float[]>(TinyYoloModelSettings.ModelOutput);

    var parser = new YoloOutputParser();

    var boundingBoxes =
        probabilities
            .Select(probability => parser.ParseOutputs(probability))
            .Select(boxes => parser.FilterBoundingBoxes(boxes, 5, .5F));

    return DrawBoundingBox(fileName, boundingBoxes.FirstOrDefault());
}

为了简洁起见,这里没有展示DrawBoundingBox方法。从高层次来看,原始图像被加载到内存中,然后模型的外接矩形被绘制在图像上,包括标签和置信度。然后,这个更新后的图像被转换为字节数组并返回。

MainWindowViewModel 类

MainWindowViewModel类内部,由于示例的性质,需要进行一些更改。我们在这里看看它们:

  1. 首先,LoadImageBytes方法现在只需将解析后的图像字节转换为Image对象,如下所示:
private void LoadImageBytes(byte[] parsedImageBytes)
{
    var image = new BitmapImage();

    using (var mem = new MemoryStream(parsedImageBytes))
    {
        mem.Position = 0;

        image.BeginInit();

        image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
        image.CacheOption = BitmapCacheOption.OnLoad;
        image.UriSource = null;
        image.StreamSource = mem;

        image.EndInit();
    }

    image.Freeze();

    SelectedImageSource = image;
}
  1. 最后,我们修改Classify方法,在成功运行模型后调用LoadImageBytes方法,如下所示:
public void Classify(string imagePath)
{
    var result = _prediction.Predict(imagePath);

    LoadImageBytes(result);
}

针对Classify方法的更改已经实施,这标志着本章示例所需的代码更改已经完成。现在,让我们运行应用程序吧!

运行应用程序

运行应用程序的过程与第十二章中的示例应用程序相同,即使用 ML.NET 与 TensorFlow 结合使用。要从 Visual Studio 内部运行应用程序,只需单击工具栏中的播放图标,如图下所示:

图片

启动应用程序后,就像在第十二章使用 TensorFlow 与 ML.NET 中一样,选择一个图像文件,模型就会运行。例如,我选择了一张我在德国度假时拍摄的图片(注意汽车边界框),如下面的截图所示:

图片

随意尝试选择您硬盘上的图像,以查看检测的置信水平和边界框围绕对象的形成情况。

探索额外的生产应用增强

现在我们已经完成了深入探讨,还有一些额外的元素可以进一步增强应用程序。一些想法将在接下来的章节中讨论。

日志记录

如前所述,在桌面应用程序中强调日志记录的重要性是至关重要的。随着应用程序复杂性的增加,强烈建议使用 NLog (nlog-project.org/)或类似的开源项目进行日志记录。这将允许您以不同的级别将日志记录到文件、控制台或第三方日志解决方案,如 Loggly。例如,如果您将此应用程序部署给客户,将错误级别至少分解为调试、警告和错误,将有助于远程调试问题。

图像缩放

如您可能已注意到,对于相当大的图像(那些超出您的屏幕分辨率),在图像预览中对边界框进行文本标注和调整大小并不像 640 x 480 这样的图像那样容易阅读。在此方面的一个改进点可能是提供悬停功能,将图像调整到窗口的尺寸或动态增加字体大小。

利用完整的 YOLO 模型

此外,对于此示例的另一个改进点是在应用程序中使用完整的 YOLO 模型。正如之前在示例应用程序中使用的 Tiny YOLO 模型一样,只提供了 20 个标签。在生产应用程序或您希望构建的应用程序中,使用更大、更复杂的模型是一个不错的选择。

您可以在此处下载完整的 YOLO 模型:github.com/onnx/models/tree/master/vision/object_detection_segmentation/yolov3

摘要

在本章的整个过程中,我们深入探讨了 ONNX 格式的内容以及它为社区提供的功能。此外,我们还使用 ML.NET 中的预训练 Tiny YOLO 模型创建了一个全新的检测引擎。

伴随着这一点,您对 ML.NET 的深入研究也就此结束。从这本书的第一页到这一页,您可能已经逐渐了解到 ML.NET 提供的非常直接且功能丰富的抽象能力。由于 ML.NET(就像 .NET 一样)不断进化,ML.NET 的功能集和部署目标的发展也将毫无疑问,从嵌入式 物联网IoT)设备到移动设备。我希望这本书对您深入理解 ML.NET 和机器学习有所帮助。此外,我希望在您未来遇到问题时,您首先会考虑这个问题是否可以通过利用 ML.NET 来更高效甚至更好地解决问题。鉴于世界上的数据持续以指数级增长,使用非暴力/传统方法的需求只会继续增长,因此,从这本书中获得的知识和技能将帮助您多年。