面向物联网的人工智能秘籍-全-

面向物联网的人工智能秘籍(全)

原文:zh.annas-archive.org/md5/c21b4d7a66ecf910d571f88f5162d065

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

人工智能 (AI) 正在迅速在各行各业找到实际应用,物联网 (IoT) 是其中之一。开发人员正在寻找方法使 IoT 设备更智能,从而使用户的生活更轻松。通过这本 AI 菜谱书,您将学习如何利用 IoT 数据实现智能分析,获取洞察,预测结果并做出知情决策,同时涵盖促进各种 IoT 应用中的分析和学习的高级 AI 技术。

本书采用基于配方的方法,将引导您完成数据收集、数据分析、建模、统计和监控以及部署等基本过程。您将使用智能家居、工业 IoT 和智能设备的真实数据集来训练和评估简单和复杂的模型,并使用训练过的模型进行预测。后续章节将指导您面对实施机器学习、深度学习和其他 AI 技术(如自然语言处理 (NLP)、计算机视觉和嵌入式机器学习)时面临的主要挑战,以构建智能 IoT 系统。除此之外,您还将学习如何轻松部署模型并提高其性能。

通过阅读本书,您将能够打包和部署端到端的 AI 应用程序,并将最佳实践解决方案应用于常见的 IoT 问题。

本书适合的读者

如果您是 IoT 从业者,希望整合 AI 技术来构建智能 IoT 解决方案,但又不想深究太多 AI 理论,那么本 AI IoT 书籍非常适合您。数据科学家和 AI 开发人员希望构建以 IoT 为重点的 AI 解决方案,也会发现本书非常有用。理解本 AI 书籍中涵盖的概念,需要掌握 Python 编程语言和基本的 IoT 概念。

本书涵盖的内容

第一章,设置 IoT 和 AI 环境,将专注于为成功设置正确的环境。您将学习如何选择满足 AI 需求的设备,无论该模型是否需要在边缘或云中运行。您还将学习如何与设备内的模块、其他设备或云安全通信。最后,您将设置一种方式将数据导入云中,然后设置 Spark 和 AI 工具来分析数据、训练模型和扩展运行机器学习模型。

第二章,处理数据,讨论了确保任何格式数据可以被数据科学家有效使用的基础知识。

第三章,物联网的机器学习,将讨论使用诸如逻辑回归和决策树等机器学习模型来解决常见的 IoT 问题,如分类医疗结果、检测不安全的驾驶员和分类化学读数。

第四章,预测性维护的深度学习,将专注于各种分类技术,以使物联网设备成为智能设备。

第五章,异常检测,将解释当警报检测不能分类特定问题时,如何导致问题的发现,以及如果设备表现异常,您可能希望派出维修工人检查设备。

第六章,计算机视觉,将讨论在云端实现计算机视觉以及在诸如 NVIDIA Jetson Nano 之类的边缘设备上实现计算机视觉。

第七章,自助点餐亭的 NLP 和机器人,将讨论使用 NLP 和机器人使用户能够在餐厅点餐亭上与用户进行交互。

第八章,微控制器和管道优化,将讨论如何利用强化学习与智能交通信号交汇处,做出减少交通等待时间、促进交通流畅的交通灯决策。

第九章,边缘部署,将讨论将预训练的机器学习模型应用于边缘设备的各种方式。本章将详细讨论物联网边缘。部署是 AI 管道的重要组成部分。本章还将讨论如何使用 TensorFlow.js 和 ONNX 将机器学习模型部署到 Web 应用程序和移动应用程序。

为了从本书中获得最大收益

读者应具备软件开发的基本理解。本书使用 Python、C、Java 语言。了解如何在这些语言中安装库和包,以及基本的编程概念如数组和循环将有所帮助。以下是几个有助于您复习不同语言基础的网站:

为了从本书中获得最大收益,对机器学习原理的基本理解将是有益的。本书使用的硬件是现成的传感器和常见的物联网开发套件,可以从 Adafruit.com 和 Amazon.com 等网站购买。大多数代码在不同设备之间是可移植的。用 Python 编写的设备代码可以轻松移植到各种微处理器,如树莓派、Nvidia Jetson、Lotte Panda,有时甚至是个人电脑。用 C 语言编写的代码可以移植到多种微控制器,如 ESP32、ESP8266 和 Arduino。用 Java 编写的代码可以移植到任何 Android 设备,如平板电脑或手机。

本书在一些实验中使用了 Databricks。Databricks 有一个免费版本可在community.cloud.databricks.com获取。

如果您使用本书的数字版本,我们建议您自行输入代码或通过 GitHub 存储库(下一节中提供的链接)访问代码。这样做将有助于避免与复制粘贴代码相关的任何潜在错误。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件,链接为github.com/PacktPublishing/Artificial-Intelligence-for-IoT-Cookbook。如果代码有更新,将在现有的 GitHub 存储库中进行更新。

我们还有来自我们丰富书籍和视频目录的其他代码包,可以在github.com/PacktPublishing/上获取。看看吧!

下载彩色图像

我们还提供一份 PDF 文件,其中包含本书使用的截图/图表的彩色图像。您可以在这里下载它:static.packt-cdn.com/downloads/9781838981983_ColorImages.pdf

使用的约定

本书中使用了多种文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。以下是一个例子:“这将给您一个运行中容器的列表。然后,打开/data文件夹。”

代码块设置如下:

import numpy as np 
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torchvision import datasets, transforms, models
from torch.utils.data.sampler import SubsetRandomSampler

任何命令行输入或输出如下所示:

cd jetson-inference
mkdir build
cd build

粗体:表示一个新术语、一个重要词或屏幕上看到的词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这里是一个例子:“点击新项目瓦片。然后,填写创建新项目向导。”

警告或重要说明会显示为这样。

提示和技巧会显示为这样。

部分

在本书中,您将会发现几个经常出现的标题(准备工作如何做它是如何工作的更多信息另请参阅)。

为了清晰地说明如何完成一个配方,请按照以下部分操作:

准备工作

本节告诉您在配方中可以期待什么,并描述设置任何软件或配方所需的任何初步设置的方法。

如何做…

本节包含完成该配方所需的步骤。

它是如何工作的…

本节通常包括对上一节中发生的事情的详细解释。

更多信息…

本节包含有关配方的附加信息,以使您更加了解配方。

另请参阅

本节为配方提供了有用信息到其他有用信息的链接。

联系我们

我们的读者的反馈总是受欢迎的。

总体反馈:如果您对本书的任何方面有疑问,请在您的邮件主题中提及书名,并发送电子邮件至 customercare@packtpub.com

勘误:尽管我们已尽一切努力确保内容的准确性,但错误是不可避免的。如果您发现本书中的错误,请向我们报告。请访问 www.packtpub.com/support/errata,选择您的书籍,点击勘误提交表单链接并输入详细信息。

盗版:如果您在互联网上发现我们作品的任何形式的非法副本,我们将不胜感激您提供地址或网站名称。请发送邮件至 copyright@packt.com 并附上材料链接。

如果您有兴趣成为作者:如果您在某个专题上拥有专业知识,并且有兴趣撰写或贡献书籍,请访问 authors.packtpub.com

评论

请留下您的评论。一旦您阅读并使用了本书,为什么不在您购买它的网站上留下评论呢?潜在的读者可以看到并使用您的客观意见来做购买决策,我们在 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们书籍的反馈。谢谢!

要了解有关 Packt 的更多信息,请访问 packt.com

第一章:设置 IoT 和 AI 环境

物联网IoT)和人工智能AI)正在对人们的生活产生重大影响。像医疗这样的行业正在通过可穿戴传感器的革命性进展来监测病人出院后的情况。在工业设备上使用的机器学习ML)通过异常检测、预测性维护和指导性行动等技术,实现了更好的监控和更少的停机时间。

构建能够提供结果的 IoT 设备依赖于收集正确信息。本书提供支持端到端 IoT/ML 生命周期的配方。下一章提供了确保设备配备正确传感器和数据最佳化以支持 ML 结果的配方。工具如解释性因子分析和数据收集设计被广泛使用。

本章将涵盖以下主题:

  • Choosing a device

  • 设置 Databricks

将涵盖以下配方:

  • 设置 IoT Hub

  • 设置 IoT Edge 设备

  • 将 ML 模块部署到 Edge 设备

  • 设置 Kafka

  • 在 Databricks 上安装 ML 库

Choosing a device

在开始经典的食谱格式化烹饪书之前,我们将从涵盖几个基础主题开始。选择合适的硬件为 AI 铺平道路。在 IoT 中工作意味着要处理各种限制。在云端使用 ML 通常是一种经济高效的解决方案,只要数据量不大。图像、视频和音频数据通常会拖慢网络速度。更糟的是,如果使用蜂窝网络,成本可能会非常高昂。谚语在硬件上没有钱指的是大多数 IoT 赚钱来自销售服务,而不是生产昂贵设备。

Dev kits

通常,公司会让电气工程师设计他们的设备,这是一种经济高效的选择。定制板不会有额外的组件,比如不必要的蓝牙或额外的 USB 端口。然而,在板设计时预测 ML 模型的 CPU 和 RAM 需求是困难的。入门套件可以是在了解硬件需求之前使用的有用工具。以下板块是市场上被广泛采用的:

  • Manifold 2-C 与 NVIDIA TX2

  • i.MX 系列

  • LattePanda

  • 树莓派类

  • Arduino

  • ESP8266

它们通常被用作功能的标尺。例如,树莓派类设备在自定义视觉应用方面可能会有些吃力,但在音频或通用 ML 应用方面表现良好。许多数据科学家的一个决定性因素是编程语言。ESP8266 和 Arduino 需要用低级语言如 C 或 C++进行编程,而像树莓派类及以上的设备可以使用任何语言。

不同的设备具有不同的价格和功能。类似树莓派或更高级别的设备可以处理边缘 ML 运行,减少云成本但增加设备成本。决定是对设备收取一次性价格还是订阅模式可能有助于确定需要的设备类型。

Manifold 2-C with NVIDIA TX2

NVIDIA Jetson 是运行复杂 ML 模型(如边缘实时视频)的最佳选择之一。NVIDIA Jetson 配备内置的 NVIDIA GPU。该产品的 Manifold 版本设计用于安装在 DJI 无人机上,并执行图像识别或自主飞行等任务。运行 NVIDIA Jetson 的唯一不利之处是其使用的 ARM64 架构。ARM64 在 TensorFlow 上表现不佳,尽管其他库如 PyTorch 在 ARM64 上运行良好。Manifold 的零售价为$500,这使其成为高价位选择,但在边缘实时 ML 方面通常是必要的:

价格 典型模型 使用案例
$500 强化学习,计算机视觉 自主飞行无人机,机器人技术

i.MX 系列

i.MX 系列芯片是开源的,具有强大的 RAM 和 CPU 能力。开放设计有助于工程师轻松构建板卡。i.MX 系列使用 Freescale 半导体。Freescale 半导体保证 10 到 15 年的生产周期,这意味着板卡设计在未来数年内将保持稳定。i.MX 6 的成本可在$200 到$300 之间,并且可以轻松处理 CPU 密集型任务,例如实时流视频中的目标识别:

价格 典型模型 使用案例
$200+ 计算机视觉,自然语言处理 情感分析,人脸识别,物体识别,语音识别

LattePanda

单板计算机SBCs)如 LattePanda 能够运行大量传感器工作负载。这些设备通常可以运行 Windows 或 Linux。像 i.MX 系列一样,它们能够在设备上运行目标识别;然而,识别对象的帧率可能较慢:

价格 典型模型 使用案例
$100+ 人脸检测,语音识别,高速边缘模型 启用音频的信息亭,高频率心脏监测

树莓派类型

树莓派是物联网的标准入门套件。以$35 的价格标签,它们为成本提供了很多功能:它们可以在边缘上通过容器运行 ML。它们具有 Linux 或 IoT Core 操作系统,可以轻松插拔组件,并有开发者社区构建类似的平台工具。尽管树莓派类型设备能够处理大多数 ML 任务,但在某些更具挑战性的任务(如视频识别)上可能存在性能问题:

价格 典型模型 使用案例
$35 决策树,人工神经网络,异常检测 智能家居,工业物联网

Arduino

在$15 处,Arduino 是一种经济实惠的解决方案。Arduino 得到了大型社区的支持,使用 Arduino 语言,一组 C/C++函数。如果需要在 Arduino 设备上运行 ML 模型,则可以将建立在诸如 PyTorch 等流行框架上的 ML 模型打包到嵌入式学习库ELL)中。ELL 允许在设备上部署 ML 模型,而无需大型操作系统的开销。由于 Arduino 的内存和计算能力有限,使用 ELL 或 TensorFlow Lite 移植 ML 模型可能具有挑战性:

价格 典型模型 使用案例
$15 线性回归 传感器读数分类

ESP8266

在低于$5 的情况下,诸如 ESP8266 和更小的设备代表了一类设备,它们接收数据并将其传输到云端进行 ML 评估。除了价格低廉外,它们通常也是低功耗设备,因此可以通过太阳能、网络电源或长寿命电池供电:

价格 典型模型 使用案例
$5 或更低 仅在云中 仅在云中

设置 Databricks

在单台计算机上处理大量数据是不可能的。这就是像 Spark(由 Databricks 制作)这样的分布式系统的用武之地。Spark 允许您在许多计算机上并行处理大量工作负载。

Spark 的开发是为了帮助解决Netflix Prize,该奖项为开发最佳推荐引擎的团队提供了 100 万美元的奖金。Spark 使用分布式计算来处理大型和复杂的数据集。还有分布式 Python 等效库,如 Koalas,它是 pandas 的分布式等效版本。Spark 还支持需要大量计算和内存的分析和特征工程,如图理论问题。Spark 有两种模式:用于训练大型数据集的批处理模式和用于几乎实时评分数据的流处理模式。

IoT 数据通常是庞大且不平衡的。设备可能有 10 年的数据表明它在正常条件下运行,只有少数记录表明需要立即关闭以防止损坏。Databricks 在 IoT 中的价值是双重的。首先是处理数据和训练模型。在 TB 和 PB 级别上处理数据可能会超出单个机器的能力。Databricks 通过其可扩展性来解决这个问题。其次是其流处理能力。ML 模型可以在云中几乎实时地运行。然后可以将消息推送回设备。

设置 Databricks 非常简单。您可以访问您的云服务提供商,在门户中注册帐户或注册免费社区版。如果您要将产品投入生产,则应该选择 Azure、AWS 或 Google Cloud。

IoT 和 ML 从根本上讲是一个大数据问题。设备可能在数年间发送遥测数据,直到发送表明设备存在问题的遥测数据为止。从数据管理的角度来看,搜索数百万或数十亿条记录以找到所需的少数记录可能是具有挑战性的。因此,优化的数据存储是关键。

存储数据

如今有一些工具可以帮助处理大量数据变得更加容易。但需要记住几件事情。有一些优化的数据存储方式可以使处理大数据集更加容易。

处理数据时,来自 IoT 设备的大型数据集类型可能对许多公司来说成本过高。例如,将数据存储在 Delta Lake 中可以比通过 JSON 访问数据提供 340 倍的性能提升。接下来的三节将介绍三种可以将数据分析工作从数周缩短到数小时的存储方法。

Parquet

Parquet 是大数据中最常见的文件格式之一。Parquet 的列式存储格式使其能够存储高度压缩的数据。其优势在于占用硬盘空间少,网络带宽消耗小,非常适合加载到 DataFrame 中。Parquet 在 Spark 中的数据导入速度比 JSON 快 34 倍。

Avro

Avro 格式是 IoT 中常用的存储格式。虽然它没有 Parquet 那样的高压缩比,但由于使用了行级数据存储模式,存储数据的计算成本较低。Avro 是流数据(如 IoT Hub 或 Kafka)的常见格式。

Delta Lake

Delta Lake 是由 Databricks 在 2019 年发布的开源项目。它将文件存储在 Parquet 中。此外,它还能够跟踪数据的检入,使数据科学家能够查看特定时间点的数据。这在尝试确定特定 ML 模型精度下降原因时非常有用。它还保存有关数据的元数据,使其在分析工作负载中比标准 Parquet 提供 10 倍的性能提升。

虽然在选择设备和设置 Databricks 时都需要考虑,但本章的其余部分将采用模块化、基于配方的格式。

设置 IoT Hub

开发 IoT 解决方案可能会很复杂。需要处理的问题包括 ML、边缘部署、安全性、监控设备状态以及云中遥测数据的摄取等。云服务提供商(如 Azure)提供了一个现成的解决方案,其中可能包括数据存储和云到设备消息等组件。

在本配方中,我们将在 Azure 中为 IoT Edge 设备设置 IoT Hub,该设备将在边缘上进行 ML 计算。

准备工作

在使用 IoT Hub 之前,您需要拥有一个设备和一个 Azure 订阅。如果您还没有订阅,可以使用免费试用订阅。您还需要某种类型的设备。

如何做…

要设置 IoT Hub,首先需要一个资源组。资源组类似于 Windows 或 macOS 上的文件夹。它们允许您将特定项目的所有资源放置在同一位置。资源组图标位于 Azure 门户左侧面板的收藏夹菜单中:

需要执行以下操作:

  1. 选择 创建资源。然后,向导将指导您完成创建资源组的步骤。

  2. 然后,点击顶部的 + 图标创建 IoT Hub 实例。

  3. 在搜索框中键入 IoT Hub。然后,向导将指导您如何设置 IoT Hub。

在规模页面上需要注意的一点是,您需要选择 S1 或更高的定价层。S1 层使您可以与设备进行双向通信,并且还能使用高级功能,例如设备双和将 ML 模型推送到边缘设备的能力。

工作原理...

IoT Hub 是专为 IoT 开发的平台。解决影响 IoT 的问题,例如不可靠的通信,通过诸如高级消息队列协议AMQP)和消息队列遥测传输MQTT)等机制进行处理。IoT Hub 拥有丰富的工具生态系统,可帮助 IoT 开发人员,如设备双生,云到设备消息,设备安全中心,Kubernetes 集成以及边缘模块市场。

设置 IoT Edge 设备

在本示例中,我们将设置一个能够与 IoT Hub 通信并接收新的 ML 容器的 IoT Edge 设备,以便在设备上执行 ML 评估。

IoT Edge 设备比传统 IoT 设备有优势。主要优势在于它们能够通过空中更新OTA)进行更新。通过使用容器,可以轻松部署模型,而无需担心设备砖化的问题。

准备工作

在创建 IoT Edge 设备之前,请确保您的设备受 IoT Edge 支持。某些设备架构(例如 ARM64)不受支持。接下来,请确保您在前一个示例中的 IoT Hub 实例正在运行。必须在设备上安装 IoT Edge 运行时。为了本教程的目的,我们将假设用户有一个 Raspberry Pi Class 设备。

如何做...

要设置 IoT Edge 设备,您需要同时设置云端和设备端。IoT 设备需要在云端有一个位置来发送其信息。本示例有两个部分。第一部分是在 IoT Hub 中配置 IoT Edge 设备。第二部分是配置设备与云端通信。

配置 IoT Edge 设备(云端)

步骤如下:

  1. 在 IoT Hub 刀片中,选择 IoT Edge。

  2. 点击“+ 添加 IoT Edge 设备”按钮。这将带您进入添加 IoT Edge 设备向导。

  3. 给您的设备一个独特的设备 ID,并选择保存。

  4. 屏幕中央将显示一个新设备。点击该设备并复制其主连接字符串:

接下来的部分解释了如何使设备与云端通信。为此,您将需要设备连接字符串。设备连接字符串可以在设备属性部分找到。点击您想要获取连接字符串的设备,并复制连接字符串。

配置 IoT Edge 设备(设备端):

首先要做的是安装 Moby。Moby 是 Docker 的精简版本。Docker 允许您将 Edge 模块推送到设备上。这些模型可以是从传感器收集数据的模块,也可以是 ML 模块。步骤如下:

  1. 下载并安装 Moby 引擎到设备上:
curl -L https://aka.ms/moby-engine-armhf-latest -o moby_engine.deb && sudo dpkg -i ./moby_engine.deb
  1. 下载并安装 Moby CLI:
curl -L https://aka.ms/moby-cli-armhf-latest -o moby_cli.deb && sudo dpkg -i ./moby_cli.deb
  1. 修复安装问题:
sudo apt-get install -f
  1. 安装 IoT Edge 安全管理器:
curl -L https://aka.ms/libiothsm-std-linux-armhf-latest -o libiothsm-std.deb && sudo dpkg -i ./libiothsm-std.deb
  1. 安装安全守护程序:
curl -L https://aka.ms/iotedged-linux-armhf-latest -o iotedge.deb && sudo dpkg -i ./iotedge.deb
  1. 修复安装问题:
sudo apt-get install -f
  1. 编辑 Edge 设备的配置文件。如果您的设备上尚未安装 nano,可能需要先安装它。nano 是基于命令行的文本编辑器,可通过 SSH 进行工作:
 sudo nano /etc/iotedge/config.yaml
  1. nano 文本编辑器中查找设备连接字符串。然后,将您从 IoT Hub 门户复制的设备连接字符串粘贴到 "<ADD DEVICE CONNECTION STRING HERE>" 部分:
provisioning:
  source: "manual"
  device_connection_string: "<ADD DEVICE CONNECTION STRING HERE>"

接下来,您需要保存并退出 nano。为此,请按下 Ctrl + X。终端将显示保存确认消息。按 Y 确认并保存。然后,是时候重新启动设备上的服务以应用更改了。

  1. 使用以下命令重新启动 Edge 服务:
sudo systemctl restart iotedge

工作原理如下...

在此教程中,我们创建了一个在云中具有特定密钥的设备。这是安全措施的一部分,每个设备都有自己的唯一密钥。如果设备被 compromise,可以关闭它。

然后我们将 IoT Edge SDK 添加到设备上,并将其连接到云端。在这一点上,设备已完全连接到云端,并准备好接收 ML 模型并将其遥测发送到云端。下一步是将 Edge 模块部署到设备上。这些 Edge 模块是 docker 化的容器,可以访问设备上的传感器,并将遥测发送到云端或运行经过训练的模型。

将 ML 模块部署到 Edge 设备:

Docker 是 IoT 设备部署的主要方法。Docker 允许您在本地创建和测试容器,并将其部署到边缘设备上。Docker 文件可以根据不同的芯片架构(如 x86 和 ARM)进行特别脚本化部署。在此教程中,我们将演示如何从云端创建一个带有 ML 库的 IoT Edge 模块。

准备工作:

要创建 IoT Edge 模块,首先安装 Visual Studio Code。安装并运行 Visual Studio Code 后,安装 Azure IoT Edge 扩展。可以通过在 Visual Studio Code 侧边栏中找到扩展图标()来完成。在扩展搜索栏中搜索 azure iot edge 并安装扩展:

安装完扩展程序后,Visual Studio Code 现在有一个向导,可以创建 IoT Edge 部署。通过少量修改,可以配置它以部署 ML 模型。

如何操作...

此配方的步骤如下:

  1. 在 Visual Studio Code 中,按下Ctrl + Shift + P以打开命令窗口,并找到 Azure IoT Edge: New IoT Edge Solution:

  1. 选择代码的位置。

  2. 输入解决方案名称。

  3. 选择一种语言。为了本书的目的,我们将使用 Python 作为我们的语言。

  4. 创建一个模块名称。

  5. 选择本地端口以在本地运行代码。

工作原理...

完成向导后,您应该在 Visual Studio Code 的资源管理器中看到类似以下内容:

让我们探索为您创建的内容。项目的主入口点是 main.pymain.py 中有一个示例,可以帮助加快开发时间。要部署 main.py,您将使用 deployment.template.json 文件。右键单击 deployment.template.json 将弹出一个菜单,其中包含创建部署清单的选项。在 modules 文件夹中,有一个包含三个 Docker 文件的示例模块,用于 ARM32、AMD64 和调试模式下的 AMD64。这些是当前支持的芯片架构。Dockerfile.arm32v7 是支持树莓派 v3 的架构。

为了确保构建 ARM32 容器而不是 AMD64 容器,请进入 module.json 文件并删除任何其他 Docker 文件的引用。例如,以下内容有三个 Docker 引用:

platforms": {
      "amd64": "./Dockerfile.amd64",
      "amd64.debug": "./Dockerfile.amd64.debug",
      "arm32v7": "./Dockerfile.arm32v7"
    }

删除两个未使用的 Docker 引用后,新文件现在应如下所示:

platforms": {      
"arm32v7": "./Dockerfile.arm32v7"
}

还有更多...

要安装 TensorFlow(这是一个 ML 库)、Keras(这是一个在 TensorFlow 之上的抽象层,使编程更加简单)和 h5py(这是一个序列化层,允许您序列化和反序列化 TensorFlow 模型),请进入目标 Docker 容器,然后进入 requirements.txt 文件,并插入以下内容来安装库:

tensorflow
keras
h5py

设置 Kafka

Kafka 是一个开源项目,在规模上非常经济实惠,可以以毫秒级延迟执行 ML 模型,并具有多主题发布/订阅模型。有几种设置 Kafka 的方式。它是一个开源项目,因此您可以下载 Kafka 项目并在本地运行 Zookeeper 和 Kafka。Kafka 的母公司 Confluent 提供了一个付费服务,提供许多额外的功能,如仪表板和 KSQL。它们在 Azure、AWS 和 Google Cloud 中作为托管服务提供,您也可以将 Kafka 作为 Docker 容器来进行开发使用。

使用 Kafka 的一个缺点是需要大量额外的开销来使其成为一个良好的物联网项目。例如,Kafka 默认情况下不安全。安全性通过一系列插件处理,包括设备端通过 x.509 证书以及云端通过轻量级目录访问协议LDAP)、Ranger 或 Kerberos 插件。部署 ML 模型也并非易事。任何 ML 库都需要转换为 Java 编译器可用的形式。TensorFlow 有适用于 Java 的 TensorFlow,但许多 ML 库在 Java 中不可用。

准备就绪

在本示例中,我们将在docker-compose中使用 Confluent Kafka。您需要在计算机上安装 Git、Docker 和docker-compose才能运行此食谱。要将 ML 模型添加到 Kafka 流中,您需要使用在 Java 上运行的平台,例如 H2O 或 TensorFlow。

如何实现...

此食谱的步骤如下:

  1. 克隆存储库:
git clone https://github.com/confluentinc/cp-all-in-on
  1. 运行docker-compose
docker-compose up -d --build

Confluent Kafka 带有许多容器。等待大约 10 分钟使容器完成启动后,打开浏览器,访问localhost:9091以查看 Kafka 控制中心。

工作原理...

Kafka 使用日志来记录来自最终用户的数据流入主题。这些主题可以被数据的消费者读取。使 Kafka 在物联网社区中成为流行工具的原因是其先进的特性。多个流可以合并,并且流可以转换为基于键/值的表格,其中最新的流更新表格。但更重要的是,对于本书的目的,ML 算法可以在毫秒级延迟的流数据上运行。本示例展示了如何将数据推送到 Kafka,然后创建一个 Java 项目以实时处理数据。

这还不是全部...

将数据流入 Kafka 相当简单。有生产者发送设备到云的消息和消费者接收云到设备的消息。在以下示例中,我们将实现一个生产者:

  1. 下载示例项目:
git clone https://github.com/Microshak/KafkaWeatherStreamer.git
cd KafkaWeatherStreamer
  1. 安装必要的依赖:
pip install -r requirements.txt
  1. 运行weather.py文件:
python3 weather.py

现在您应该能够查看 Kafka 控制中心,并看到数据正在流入。Kafka Streams API 是一个实时平台,可以以毫秒级延迟执行 ML 计算。Streams API 具有 KTables 和 KStreams 的概念。KStreams 是流入 Kafka 的各种主题的数据流。KTables 是流转换成表格,其中数据每次有与其主键关联的新记录时更新。这允许将多个流类似于数据库中的表进行联接,使 Kafka 能够不仅处理单个设备,还能够从多个源获取设备数据并将流组合在一起。

要使用 Streams API,您必须首先在计算机上安装 Java 和 Maven。此外,您还需要安装用于 Java 开发的集成开发环境,例如 IntelliJ。安装完所有先决条件后,运行 Maven 原型以生成启动 Kafka Streams API 项目所需的代码:

mvn archetype:generate \
    -DarchetypeGroupId=org.apache.kafka \
    -DarchetypeArtifactId=streams-quickstart-java \
    -DarchetypeVersion=2.2.0 \
    -DgroupId=streams.examples \
    -DartifactId=streams.examples \
    -Dversion=0.1 \
    -Dpackage=myapps

在 IntelliJ 中打开新创建的项目,您就可以开始编写针对 Kafka Streams API 的代码了。Confluent,Kafka 的制造商的座右铭是:“It's just Java”。他们的意思是,一旦您进入 Streams API,您可以编写 Java 代码来做任何您想做的事情。这可能是向网站发送 WebSocket 信息或运行 ML 模型。如果可以用 Java 做到,那么在 KStreams 事件循环中也可以做到。有一些框架,如deeplearning4j,可以接受在 Python 中训练的 Keras 模型,并在 Java 中运行它们。

在 Databricks 上安装 ML 库

Databricks 是一个统一的大数据和分析平台。它非常适合训练 ML 模型并处理 IoT 中常见的大规模数据。有一些扩展如 Delta Lake 允许研究人员查看数据在特定时间段的存在方式,以便在模型漂移时进行分析。还有像 MLflow 这样的工具,允许数据科学家比较多个模型之间的差异。在这个示例中,我们将在 Databricks 上安装各种 ML 包,如 TensorFlow、PyTorch 和 GraphFrames。大多数 ML 包可以通过 PyPI 安装。例如,用于安装 TensorFlow 的格式可用于 OpenAI Gym、Sonnet、Keras 和 MXNet 等各种 ML 框架。Databricks 中有一些在 Python 中不可用的工具。对于这些工具,我们使用 GraphX 和 GraphFrame 探索的模式,通过 Java 扩展安装包。

准备工作

在我们开始之前,了解各组件如何互相配合是很重要的。让我们从工作空间开始。工作空间区域是您可以通过 Databricks 笔记本与数据科学家和工程师共享结果的地方。笔记本可以与 Databricks 中的文件系统进行互操作,以存储 Parquet 或 Delta Lake 文件。工作空间部分还存储诸如 Python 库和 JAR 文件等文件。在工作空间部分,您可以创建文件夹来存储共享文件。我通常创建一个packages文件夹来存储 Python 和 JAR 文件。在安装 Python 包之前,让我们首先通过转到集群部分来检查集群是什么。

在您的 Databricks 实例中,转到“Clusters”菜单。您可以创建一个集群或使用已创建的集群。对于集群,您可以指定所需的计算量。Spark 可以处理大型数据集,也可以用于 ML 优化工作负载的 GPU。一些集群预装了诸如 Conda 等 ML 工具,其他允许您安装自己的库。

如何做...

传统的机器学习笔记本可能存在与安装的机器学习包不同版本的问题。Databricks 通过允许用户设置预安装包资源来规避这些问题。在这个示例中,我们将把各种机器学习包安装到 Databricks 中。这些包可以分配给所有新的集群或特定的集群。这使得数据科学家可以灵活地使用新版本的机器学习包,同时支持他们开发的旧版机器学习模型。我们将在三个部分中介绍这个示例。

导入 TensorFlow

或许最简单的导入 Python 库(如 TensorFlow)的方法是使用 PyPI。只需访问 pypi.org/ 并搜索 TensorFlow。这将为您提供所需的信息,并能查看不同的版本。安装步骤如下:

  1. 前往 pypi.org/ 并搜索 TensorFlow。

  2. 复制您想要的名称和版本号,格式为 tensorflow==1.14.0

  3. 在 Databricks 的 Workspace 选项卡中,右键单击任何位置,然后从下拉菜单中选择创建,然后选择库:

  1. 在创建库页面上,选择 PyPI 作为库源:

  1. 复制库的名称和版本号,然后粘贴到“包”部分。

  2. 点击创建。

如果您已经创建了集群,可以将 TensorFlow 附加到它上面。您也可以在所有集群上安装 TensorFlow。

安装 PyTorch

PyTorch 是一个流行的原生 Python 编写的机器学习库,内置支持 GPU。安装 PyTorch 非常类似于安装 TensorFlow。您可以在创建 | 库菜单中通过 PyPI 安装它。在 PyPI 导入库菜单中,输入当前版本的 PyPI (torch==1.1.0.post2)。安装步骤如下:

  1. 前往 pypi.org/ 并搜索 PyTorch。

  2. 复制您想要的名称和版本号,格式为 torch==1.1.0.post2

  3. 在 Databricks 的 Workspace 选项卡中,右键单击任何位置,然后从下拉菜单中选择创建,然后选择库。

  4. 选择 PyPI 作为库源。

  5. 复制库的名称和版本号,然后粘贴到“包”部分。

  6. 点击创建。

如果您已经创建了集群,可以将 PyTorch 附加到它上面。您也可以在所有集群上安装 PyTorch。

安装 GraphX 和 GraphFrames

Spark 有一些分布式库,在数据科学中无法找到。GraphFrames 就是其中之一。在图论中,您可以执行诸如寻找最短路径、网络流、同源性、中心度和影响力等操作。因为 GraphFrames 是建立在 Java 库 GraphX 上的,所以您需要安装 Java 库,然后使用 Python 封装器,您需要 pip 安装访问 Java JAR 文件的 Python 库。安装步骤如下:

  1. spark-packages.org/package/graphframes/graphframes 下载一个 JAR 文件。你需要找到一个与你集群中运行的 Spark 版本匹配的版本。

  2. 在 Databricks 的 Workspace 标签页中,右键点击任意位置,从下拉菜单中选择 Create,然后选择 Library。

  3. 将 JAR 文件拖放到名为 Drop JAR here 的空间中。

  4. 点击 Create。

  5. 然后,导入另一个库。

  6. 在 Databricks 的 Workspace 标签页中,右键点击任意位置,从下拉菜单中选择 Create,然后选择 Library。

  7. 选择 PyPI 作为库源,并在 Package 部分输入 graphframes

  8. 点击 Create。

为了测试你的安装,你可以在这里下载一个示例笔记本和数据文件:github.com/Microshak/Databricks/tree/master/Graph

工作原理如下...

Databricks 专为数据工程师和数据科学家设计,支持多版本软件和多语言。通过允许用户分别为每个集群配置不同版本的 ML 包来实现此目的。TensorFlow 隐式安装在流处理集群上。另一个集群安装了流行的 Conda 环境。最后,测试环境没有安装 TensorFlow。

第二章:处理数据

用于收集数据的技术通常决定了可以使用的模型类型。如果地震仪每小时只报告一次地震活动的当前读数,那就毫无意义。数据的高保真度不足以预测地震。在 IoT 项目中,数据科学家的工作并不是在收集数据之后开始,而是需要参与设备的建造。当设备建成时,数据科学家需要确定设备是否发出适合机器学习的数据。接下来,数据科学家帮助电气工程师确定传感器是否放置在正确的位置,传感器之间是否存在相关性,最后,数据科学家需要以高效的方式存储数据以进行分析。通过这样做,我们避免了 IoT 的第一个主要陷阱,即收集和存储最终对机器学习无用的数据。

本章将检查存储、收集和分析数据,以确保有足够的数据进行有效和高效的机器学习。我们将首先看看数据的存储和访问方式。然后,我们将查看数据收集设计,以确保设备上的数据适合机器学习。

本章将涵盖以下教程:

  • 使用 Delta Lake 存储分析数据

  • 数据收集设计

  • 窗口化

  • 探索因子分析

  • 在 Mongo/hot path 存储中实现分析查询

  • 将 IoT 数据导入 Spark

使用 Delta Lake 存储分析数据

如今,有许多处理分析数据的选择。您可以将其存储在数据湖、Delta Lake 或 NoSQL 数据库中。本教程涵盖数据存储和检索,并使用 Delta Lake。Delta Lake 提供了处理数据的最快方式和最有效的存储数据方式。它还允许您查看过去任意时间点存在的数据。

准备就绪

虽然 Delta Lake 是一个开源项目,但在 Delta Lake 中存储文件的最简单方法是通过 Databricks。Databricks 的设置在 第一章 中讨论过,《设置 IoT 和 AI 环境》。本教程假设您已经设置和运行了 Databricks。

如何执行...

将文件导入 Delta Lake 很容易。数据可以通过文件或流导入。本教程的步骤如下:

  1. 在 Databricks 中,点击“数据”按钮打开数据面板,然后点击“添加数据”按钮,将文件拖入上传区域。

  2. 在 Notebook 中点击“创建表”。为您生成的代码将从这里开始:

# File location and type
file_location = "/FileStore/tables/soilmoisture_dataset.csv"
file_type = "csv"

# CSV options
infer_schema = "false"
first_row_is_header = "false"
delimiter = ","

df = spark.read.format(file_type) \
  .option("inferSchema", infer_schema) \
  .option("header", first_row_is_header) \
  .option("sep", delimiter) \
  .load(file_location)

display(df)
  1. 查看数据,并在准备好保存到 Delta Lake 时,取消注释最后一行:
# df.write.format("parquet").saveAsTable(permanent_table_name)
  1. 然后,将 "parquet" 更改为 "delta"
df.write.format("delta").saveAsTable(permanent_table_name)
  1. 从这里查询数据:
%sql
SELECT * FROM soilmoisture
  1. 或者,您可以优化 Delta Lake 保存文件的方式,使查询速度更快:
%sql
OPTIMIZE soilmoisture ZORDER BY (deviceid)

Delta Lake 数据可以进行更新、过滤和聚合。此外,它可以轻松转换为 Spark 或 Koalas DataFrame。

工作原理...

Delta Lake 建立在 Parquet 之上。利用列压缩和元数据存储,可以使数据检索速度比标准 Parquet 快 10 倍。除了更快的性能外,Delta Lake 的数据版本控制允许数据科学家查看数据在特定时间点的情况,使数据科学家在模型漂移时进行根本原因分析。

数据采集设计

机器学习和物联网中最重要的因素是数据采集设计。如果收集的数据是垃圾**数据,那么上面无法进行机器学习。假设您正在查看泵的振动(如下图所示),以确定泵是否存在机械或滚珠轴承问题,以便在机器受到严重损坏之前进行预防性维护:

重要的是,以 100 Hz 的实时数据存储在云中是成本高昂的。为了降低成本,工程师通常以 1 分钟的频率发送数据。低频传感器数据通常不能准确表示所查看的问题。下图显示了仅每分钟采样一次时数据的外观:

这里,我们看到与以 1 分钟间隔收集的数据叠加的振动计数据。数据有一定的用处,但不准确,因为它未显示数据发生的真实幅度。使用平均值更糟糕。下图显示了振动计在 1 分钟内的平均读数:

对于 1 分钟窗口内的平均读数取平均值是更糟糕的解决方案,因为在泵出现问题时平均值不会改变。下图显示了振动计在 1 分钟内的标准读数:

使用标准偏差技术与均值比较显示是否存在泵问题。这比平均技术更精确的解决方案。

使用 1 分钟窗口内的最小和最大值可以提供情况的最佳表示。下图显示了读数的样子:

因为物联网设备可能在出现问题之前正常工作多年,并且在云中传送高频数据是成本高昂的,因此使用其他测量方法来确定设备是否需要维护。可以使用最小/最大标准偏差峰值等技术来触发云到设备的消息,告知设备以更高的频率发送数据。高频诊断数据可以使用 Blob 存储来存储大文件。

IoT 的一个挑战之一是在海量数据中找到有意义的数据。在本章中,我们将展示挖掘有价值数据的技术。

准备工作

为了准备好数据收集设计,您需要一个以高速率流式传输数据的设备。在《第一章》中,设置 IoT 和 AI 环境,我们讨论了将设备数据流入 IoT Hub 的过程。通常在生产中,设备数据以 15 秒或 1 分钟的间隔发送。但是在数据收集设计中,一个设备以 10 Hz 的高速率发送数据,即每秒 10 次。一旦数据流入,您可以将其拉入 Databricks 进行实时分析。

如何做…

在我们的 Databricks 笔记本中,我们将使用方差、Z-尖峰和最小/最大值技术分析 IoT 数据。

方差

方差是数据与均值的变化程度的度量。在接下来的代码中,我们使用 Koalas,即 pandas 的分布式克隆,来执行基本的数据工程任务,如确定方差。以下代码使用滚动窗口上的标准偏差来显示数据尖峰问题:

import databricks.koalas as ks 

df = ks.DataFrame(pump_data)
print("variance: " + str(df.var()))
minuite['time'] = pd.to_datetime(minuite['time'])
minuite.set_index('time')
minuite['sample'] = minuite['sample'].rolling(window=600,center=False).std() 

在 IoT 产品线上使用占空比,直到收集到足够的数据用于机器学习。它们通常是简单的措施,例如设备是否过热或者振动过多。

我们还可以查看高低值,例如最大值,以确定传感器是否输出适当的读数。以下代码显示了我们数据集的最大读数:

max = DF.agg({"averageRating": "max"}).collect()[0]

Z-尖峰

尖峰可以帮助确定是否存在问题,方法是查看读数变化的快速程度。例如,户外 IoT 设备在南极和死亡谷直射阳光下的工作温度可能不同。找出设备是否存在问题的一种方法是查看温度变化的速度。Z-尖峰是典型的基于时间的异常检测方法。它的使用原因是它仅查看该设备的读数,并且可以提供独立于环境因素的数值。

Z-尖峰查看尖峰与标准偏差的差异。它使用统计 z 检验来确定尖峰是否大于人群中 99.5% 的尖峰。

最小/最大值

最小值和最大值可以显示系统上的最大压力值。以下代码展示了如何获取一个 1 分钟窗口的最小值和最大值:

minute['max'] = minute['sample'].rolling(window=600,center=False).max() 

minute['sample'] = minute['sample'].rolling(window=600,center=False).min() 

最小值和最大值可以突出异常值。这在确定异常时非常有用。

窗口操作

有三种主要的窗口函数:滚动窗口、跳跃窗口和滑动窗口。Spark 和 Stream Analytics 都可以进行窗口操作。窗口操作允许您查看聚合函数,如平均值、计数和总和。它还允许您查看最小值和最大值。窗口操作是一种特征工程技术,有助于使数据更易管理。在本章中,我们将介绍几种窗口操作的工具和窗口的方式。

准备工作

要准备好,您还需要一个将数据流式传输到 IoT Hub 的设备。该流需要被 Azure 的流分析、Spark 或 Databricks 摄取。

如何实现...

使用 Databricks 笔记本或流分析工作空间执行配方。窗口化可以将大数据集的静态转化为机器学习模型的有意义特征。

滚动

滚动窗口函数将数据流分组为时间段(如下图所示)。滚动窗口意味着窗口不会重复或将数据从一个段落传递到下一个段落:

流分析

在流分析中,使用滚动窗口每 10 秒计算事件发生的一种方法如下:

SELECT EventTime, Count(*) AS Count
FROM DeviceStream TIMESTAMP BY CreatedAt
GROUP by EventTime, TumbelingWindow(minuites, 10)

Spark

在 Spark 中,要每 10 分钟计算事件发生的次数,可以按以下步骤进行:

from pyspark.sql.functions import * 
windowedDF = eventsDF.groupBy(window("eventTime", "10 minute")).count()

跳跃

跳跃窗口是重叠的滚动窗口。它们允许您设置特定的命令和条件,例如每 5 分钟,给我过去 10 分钟内传感器读数的计数。要使跳跃窗口与滚动窗口相同,需要将跳跃大小设置为窗口大小,如下图所示:

流分析

下面的流分析示例显示了在 10 分钟窗口内的消息计数。每 5 分钟进行一次计数:

SELECT EventTime, Count(*) AS Count
FROM DeviceStream TIMESTAMP BY CreatedAt
GROUP by EventTime, HopingWindow(minuites, 10, 5)

Spark

在 PySpark 中,可以通过窗口函数来完成这一操作。下面的示例显示了一个窗口化的 Spark DataFrame,在 10 分钟时间段内每 5 分钟生成一个新的 DataFrame 条目:

from pyspark.sql.functions import * 
windowedDF = eventsDF.groupBy(window("eventTime", "10 minute", "5 minute")).count()

滑动

滑动窗口在事件发生时产生输出。下图说明了这个概念:

流分析

在流分析示例中,通过使用滑动窗口,只有在 10 分钟窗口内的消息数超过 100 条时才会收到结果。与查看确切时间窗口并显示该窗口的消息不同,在滑动窗口中,我们将在每个输入消息上收到一条消息。另一个用途是显示滚动平均值:

SELECT EventTime, Count(*) AS Count
FROM DeviceStream TIMESTAMP BY CreatedAt
GROUP by EventTime,
SlidingWindow(minutes, 10)
WHERE COUNT(*) > 100

它的工作原理...

使用窗口化,IoT 数据可以显示诸如频率、总和、标准差和百分位分布等因素,时间段内的窗口化可以用于丰富数据的特征工程或将数据转换为聚合数据集。例如,窗口化可以显示工厂生产了多少设备或显示传感器读数中的调制。

探索性因子分析

垃圾数据是困扰物联网的主要问题之一。在收集数据之前,数据通常没有经过验证。常常存在与错误的传感器放置或数据看起来随机的问题,因为它不适合所使用的数据类型的适当度量。例如,由于中心极限定理,振动计可能显示数据集围绕平均值集中,而实际上数据显示了一个数量级的大增加。为了应对这些问题,对设备数据进行探索性因子分析至关重要。

在这个示例中,我们将探讨几种因子分析技术。在 Databricks 笔记本中使用聚合数据和原始遥测数据来执行此分析。

准备工作

您需要在 Databricks 的表中拥有数据,我们在使用 Delta Lake 存储数据进行分析示例中实现了这一点。一旦数据存储在 Spark 数据表中,即可进行因子分析。

如何做…

本示例由两个部分组成。第一部分是对数据进行视觉检查。视觉检查可以揭示软件缺陷,了解设备行为以及确定设备数据模式。第二部分则关注相关性和协方差。这些技术通常用于确定传感器是否冗余。

视觉探索

Spark 允许您查看基本的图表而无需太多代码。使用笔记本段落顶部的魔术符号,您可以轻松地从 Python 切换到 Scala 或 SQL。关于使用 Databricks 内置的图表系统的一个警告是,它只查看前 10,000 条记录。对于大型数据集,可以使用其他图表库。步骤如下:

  1. 使用 %sql 魔法在 Databricks 中查询数据,如下所示:
%sql
select * from Telemetry
  1. 选择返回数据网格底部的图表图标。这将显示图表构建器 UI,如下屏幕截图所示:

  1. 选择最能代表数据的图表类型。有些图表更适合变量比较,而其他图表则可以帮助揭示趋势。

以下部分将审查何时以及为什么使用不同类型的图表。

图表类型

不同类型的图表阐明了数据的不同方面,比如比较、组成、关系和分布。关系图表用于测试假设或查看一个因素如何影响其他因素。组成显示数据集的百分比分布。它通常用于显示各因素之间的比较情况。饼图是一种简单的组成图表。分布图表用于显示人群的分布情况。它们通常用于确定数据是否随机,是否具有较大的扩展或是否已归一化。比较图表用于将一个值与其他值进行比较。

条形和柱形图

条形图和柱形图用于比较项目之间的差异。条形图由于页面布局的关系可以有很多项。柱形图和条形图还可以显示随时间变化的情况。以下图表是条形图和柱形图的一个例子:

散点图

散点图可以显示两个变量之间的关系。它还可以显示趋势线。以下是散点图的一个例子:

气泡图

当你想要展示三个变量之间的关系时,可以使用气泡图。这可以用来显示异常行为。以下是气泡图的一个例子:

折线图

这些图表显示随时间变化的情况,并可用于显示设备数据在一天内的变化。如果设备具有季节性数据,则可能需要将时间作为算法的一部分或使用去季节性算法。以下是折线图的一个例子:

面积图

面积图类似于折线图,但用于显示一个部分的体积与另一个部分的比较。以下是面积图的一个例子:

分位数图

通过将数据分成分段(分位数)来帮助确定人口形状。常见的分位数是 25%,50%和 75%,或 33%和 66%,或 5%和 95%(一般百分比是四分位数)。了解数据是否符合预期参数是理解设备是否存在问题的重要因素。以下是分位数图的一个例子:

冗余传感器

物联网的一个挑战是确定传感器放置的位置以及需要多少个传感器。以泵为例:判断泵轴承是否损坏的一种方法是使用麦克风听高音尖叫声。另一种方法是使用参数来判断是否振动更多。还有一种方法是测量电流是否波动。没有一种确定泵轴承是否损坏的正确方法;然而,实施这三种技术可能成本过高且冗余。评估不同传感器之间的相关性的常见方法是使用热图。在下面的代码中,我们使用热图来找出传输冗余信息的传感器:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns    

# load the sample training data
train = pd.read_csv('/dbfs/FileStore/tables/Bike_train.csv')

for i in range(50):
    a = np.random.normal(5,i+1,10)
    b.append(a)
c = np.array(b)
cm =np.corrcoef(c)

plt.imshow(cm,interpolation='nearest')
plt.colorbar()

#heat map
plt.figure(figsize=(17,11))
sns.heatmap(train.iloc[:,1:30].corr(), cmap= 'viridis', annot=True)
display(plt.show())

下面的屏幕截图显示了热图:

在上面的例子中,我们可以看到countregistered具有非常高的相关性,因为这两个数字接近 1。同样,我们可以看到tempatemp之间有很高的相关性。如果在不去除相关数据的情况下使用这些数据,可能会给在数据集上训练的机器学习模型带来加权影响。

当设备数据很少时,仍然进行方差分析、分布和偏差分析可能很有价值。因为它的进入门槛低于机器学习,所以可以在机器生命周期的早期阶段部署。进行统计分析有助于确保设备设置的数据正确,不重复或虚假,并可用于机器学习。

交叉制表提供频率分布表。这可以用于确定两个不同传感器是否计算相同的情况。以下是显示交叉制表表格的代码:

display(DF.stat.crosstab("titleType", "genres"))

样本协方差和相关性

协方差测量两个传感器相对于彼此的联合变化性。正数表明传感器报告相同的数据。负数表明传感器之间存在反向关系。可以使用 Spark DataFrame 中的 DataFrame stat.cov函数计算两个传感器的协方差:

df.stat.cov('averageRating', 'numVotes')

工作原理...

在生产后修改物理设备可能很昂贵。本文展示了如何检查原型设备,以确保其产生的数据不会毫无意义。使用数据分析工具如 Databricks 进行初步数据分析,可以避免物联网和人工智能中常见的问题,如传感器放置不当、通信过度或不足以及无法用于机器学习的数据。执行标准的机器学习任务,如预测维护、异常检测或剩余有用寿命,取决于良好的数据。

更多内容...

您可以通过创建一个过滤小部件来进一步探索数据。例如,您可以使用如下所示的CREATE WIDGET DROPDOWN查询:

%sql
CREATE WIDGET DROPDOWN tytleType DEFAULT "movie" CHOICES SELECT DISTINCT titleType FROM imdbTitles

创建一个小部件允许您创建一个数据查询,可以轻松分段,如下代码所示:

%sql
select * from imdbTitles where titleType = getArgument("tytleType")

其他小部件类型,如文本、组合框和多选框,也是可用的。

在 Mongo/hot 路径存储中实现分析查询

在物联网架构中,存在热路径数据和冷路径数据。热路径数据可以立即访问。这通常存储在 NoSQL 或时间序列数据库中。例如,可以使用时间序列数据库如 InfluxDB 来计算每小时每台设备的重置次数。这可以用于辅助特征工程。热数据的另一个用途是精确分析。如果一个设备在现场故障,可以查询诸如 MongoDB 之类的数据库,仅获取该设备在过去一个月内生成的数据。

冷路径数据通常用于批处理,如机器学习和月度报告。冷路径数据主要存储在 Blob、S3 存储或 HDFS 兼容数据存储中。将热路径与冷路径分开通常涉及成本和可扩展性因素。IoT 数据通常属于大数据范畴。如果数据科学家从 NoSQL 数据库中查询多年的数据,使用它的 Web 应用程序可能会崩溃。对于存储在磁盘上的冷路径数据,这种情况并不适用。另一方面,如果数据科学家需要从数十亿条记录中查询几百条记录,那么 NoSQL 数据库就是合适的选择。

本文重点是处理热数据。本文主要关注从 MongoDB 提取 IoT 数据。首先,我们从一个设备中提取数据,然后我们将其在多个设备间进行聚合。

准备工作

流分析可以将 IoT 数据传输到 MongoDB。要做到这一点,请启动 MongoDB。可以通过 Azure Kubernetes Service 或使用 Atlas MongoDB 云提供商来完成此操作。一旦有了数据库,您可以使用函数应用程序在 IoT Hub 和 MongoDB 之间移动数据。

如何做...

Mongo 具有一系列与 SQL 可比较的过滤选项。以下代码显示了如何连接到 Mongo 的本地版本,并查询所有库存状态为A的产品:

df = spark.read.format("mongo").option("uri",
"mongodb://127.0.0.1/products.inventory").load()
pipeline =  "{'deviceid':'8ea23889-3677-4ebe-80b6-3fee6e38a42c'}" 
df = spark.read.format("mongo").option("pipeline", pipeline).load()
df.show() 

下一个示例展示了如何执行复杂的过滤操作,接着进行分组操作。最终输出将显示状态为A的项目计数:

pipeline = "[ { '$match': { 'status': 'A' } }, { '$group': { '_id': '$item', 'total': { '$sum': '$qty' } } } ]"
df = spark.read.format("mongo").option("pipeline", pipeline).load()
df.show() 

工作原理...

Mongo 将索引数据存储在多台计算机或分区上。这使得以毫秒为单位的延迟时间内检索特定数据成为可能。NoSQL 数据库可以为数据提供快速查找。在本文中,我们讨论了如何从 MongoDB 查询数据到 Databricks。

将 IoT 数据导入 Spark

要将 Spark 连接到 IoT Hub,首先创建一个消费者组。消费者组是指示消费者已达到的日志当前位置的指针。可以在同一数据日志的多个消费者之间存在多个消费者。消费者组是并行和可分布的,使您能够编写即使在大规模的情况下也能保持稳定的程序。

准备工作

对于本文,进入 Azure IoT Hub 门户,单击内置终端菜单选项。然后,通过输入一些文本来添加一个消费者组。在仍在该屏幕上的情况下,复制事件中心兼容端点连接字符串。

如何做...

本文的步骤如下:

  1. 在 Databricks 中,启动一个新的笔记本,并输入连接到 IoT Hub 所需的信息。然后,输入以下代码:
import datetime as dt
import json

ehConf = {}
ehConf['eventhubs.connectionString'] = ["The connection string you copies"]
ehConf['eventhubs.consumerGroup'] = "[The consumer group you created]"

startingEventPosition = {
  "offset": -1, 
  "seqNo": -1, #not in use
  "enqueuedTime": None, #not in use
  "isInclusive": True
}

endingEventPosition = {
  "offset": None, #not in use
  "seqNo": -1, #not in use
  "enqueuedTime": endTime,
  "isInclusive": True
}
ehConf["eventhubs.recieverTimeout"] = 100
  1. 将数据放入 Spark DataFrame 中:
df = spark \
 .readStream \
 .format("eventhubs") \
 .options(**ehConf) \
 .load()
  1. 下一步是对数据应用结构,以便您可以使用结构化流处理:
from pyspark.sql.types import *
Schema = StructType([StructField("deviceEndSessionTime", StringType()), StructField("sensor1", StringType()),
 StructField("sensor2", StringType()),
 StructField("deviceId", LongType()),
 ])
  1. 最后一步是将架构应用到 DataFrame 中。这样可以使您像处理表格一样处理数据:
from pyspark.sql.functions import *

rawData = df. \
  selectExpr("cast(Body as string) as json"). \
  select(from_json("json", Schema).alias("data")). \
 select("data.*")

工作原理...

在这个示例中,我们连接到了 IoT Hub 并将数据放入了一个 DataFrame 中。稍后,我们为该数据框架添加了结构,使我们能够像查询数据库表一样查询数据。

在接下来的几章中,我们将讨论如何创建模型。在使用冷路径数据创建模型之后,您可以通过将这些训练好的模型推送到 Databricks 结构化流中,实现几乎实时的机器学习。

第三章:IoT 的机器学习

机器学习已经极大地改变了制造商在物联网(IoT)中的应用。今天,有许多行业有特定的物联网需求。例如,医疗物联网IoMT)拥有像家中可穿戴的门诊心脏监测器这样的设备。这些设备通常需要在网络上传输大量数据或在边缘进行大量的计算以处理与心脏相关的事件。另一个例子是农业物联网AIoT)设备,通常放置在没有 Wi-Fi 或蜂窝网络的地方。处方或模型被推送到这些半连接设备上。这些设备中的许多需要在边缘做出决策。当使用 LoRAWAN 或电视白色空间等技术最终建立连接时,模型被下载到设备上。

在本章中,我们将讨论使用逻辑回归和决策树等机器学习模型来解决常见的物联网问题,如分类医疗结果、检测不安全驾驶员和分类化学读数。我们还将探讨处理受限设备的技术,并研究使用无监督学习获取对原型等数据较少设备的见解的技术。

本章包含以下内容:

  • 使用异常检测分析化学传感器

  • 使用 IoMT 的逻辑回归

  • 使用决策树对化学传感器进行分类

  • 使用 XGBoost 进行简单的预测性维护

  • 检测不安全的驾驶员

  • 受限设备上的人脸检测

使用异常检测分析化学传感器

准确的预测模型需要大量的设备在现场失败,以便它们有足够的故障数据用于预测。对于一些精心制作的工业设备来说,这种规模的故障可能需要多年时间。异常检测可以识别不像其他设备的设备。它还可以用于筛选成千上万条类似消息,并确定不同于其他消息的消息。

机器学习中的异常检测可以是无监督的,监督的,或半监督的。通常,它从使用无监督机器学习算法将数据聚类为行为模式或群组开始。这将数据呈现为桶。当机器被检查时,一些桶识别出行为,而一些桶则识别出设备存在问题。设备可能在静止状态、使用状态、冷态或需要调查的状态中表现出不同的行为模式。

此配方假定使用的数据集中对数据了解不多。异常检测过程被用作发现过程的一部分,并且通常与原型一起使用。

准备工作

异常检测是最容易实现的机器学习模型之一。在这个示例中,我们将使用从化学传感器获取的数据集,检测中性、香蕉或葡萄酒。为了准备好,您需要导入 numpysklearnmatplotlib 库。

如何做...

完成这个示例需要遵循以下步骤:

  1. 导入所需的库:
import numpy as np
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
  1. 将数据文件上传到 DataFrame:
df = spark.read.format("csv" \
    .option("inferSchema", True) \
    .option("header", True) \
    .option("sep", "\t") \
    .load("/FileStore/tables/HT_Sensor_metadata.dat")
  1. 查看数据集以查看数据分组是否与簇的数量相关:
pdf = df.toPandas()

y_pred = KMeans(n_clusters=3, 
                random_state=2).fit_predict(pdf[['dt','t0']])

plt.scatter(pdf['t0'],pdf['dt'], c=y_pred)
display(plt.show())

输出如下:

上述图表显示了三组不同的数据。紧密聚集的簇代表具有明确定义边界的数据。如果我们将簇的数量调整为 10,可能会更好地分离不同的群组。这些簇段帮助我们识别数据的不同段落。这反过来可能帮助我们确定原型的最佳传感器放置或在机器学习模型中进行特征工程。

工作原理...

在这个示例中,我们使用 numpy 进行数据操作,sklearn 进行机器学习算法,matplotlib 查看结果。接下来,我们将制表符分隔的文件转换为 Spark 数据框架。在这一步中,我们将数据转换为 pandas DataFrame。然后我们使用三个簇运行 k-means 算法,输出图表。

K-means 是一种将数据分组成簇的算法。K-means 是一种流行的聚类算法,用于在没有标签的情况下检查数据。K-means 首先随机初始化簇的质心。在我们的例子中,它有三个簇的质心。然后将这些质心分配给最近的数据点。接下来,它将每个质心移动到其相应簇的中间位置。它重复这些步骤直到达到适当的数据点分割。

还有更多...

在图表中,您可能已经注意到异常值。在查看原型时,这些异常值非常重要。异常值可以代表机器内部的电力波动、传感器安置不当或其他问题。以下示例显示了我们数据的简单标准差计算。从这里,我们能够看到两个超出均值三个标准差的值:

from numpy import mean
from numpy import std

data_mean, data_std = mean(pdf['dt']), std(pdf['dt'])

cut_off = data_std * 3
lower, upper = data_mean - cut_off, data_mean + cut_off

outliers = [x for x in pdf['dt'] if x < lower or x > upper]
print('Identified outliers: %d' % len(outliers))
print(outliers)

使用 IoMT 的逻辑回归

在这个示例中,我们将讨论使用逻辑回归来对乳腺 X 光数据进行分类。最近,IoMT 大大扩展。许多设备被患者佩戴,当他们离开医生时提供家庭医疗监测解决方案,同时其他设备则在医院内,为医生提供运行的医疗测试的额外反馈。在许多情况下,机器学习算法能够发现医生可能忽略的疾病和问题,或为他们提供额外的建议。在这个示例中,我们将使用乳腺癌数据集,并确定乳腺 X 光记录是恶性还是良性。

准备就绪

数据集与 Databricks 笔记本一起,可以在 GitHub 仓库中找到。 数据集笨重。 它有高度相关的坏列,这是说一些传感器是重复的,还有未使用的列和多余的数据。 为了便于阅读,在 GitHub 仓库中将有两个笔记本。 第一个笔记本执行所有数据操作,并将数据放入数据表中。 第二个笔记本进行机器学习。 我们将重点介绍数据操作笔记本。 在配方结束时,我们将讨论另外两个笔记本,以展示 MLflow 的示例。

在本配方中,您还需要一个 MLflow 工作区。 要设置 MLflow 工作区,您需要进入 Databricks 并为此实验创建工作区。 我们将在那里写入我们实验的结果。

如何实现...

按照以下步骤完成本配方:

  1. 导入所需的库:
import pandas as pd

from sklearn import neighbors, metrics
from sklearn.metrics import roc_auc_score, classification_report,\
precision_recall_fscore_support,confusion_matrix,precision_score, \
roc_curve,precision_recall_fscore_support as score
from sklearn.model_selection import train_test_split

import statsmodels.api as sm
import statsmodels.formula.api as smf
  1. 导入数据:
df = spark.sql("select * from BreastCancer")
pdf = df.toPandas()
  1. 分割数据:
X = pdf
y = pdf['diagnosis']

X_train, X_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.3, random_state=40) 
  1. 创建公式:
cols = pdf.columns.drop('diagnosis')
formula = 'diagnosis ~ ' + ' + '.join(cols)
  1. 训练模型:
model = smf.glm(formula=formula, data=X_train, 
                family=sm.families.Binomial())
logistic_fit = model.fit()
  1. 测试我们的模型:
predictions = logistic_fit.predict(X_test)
predictions_nominal = [ "M" if x < 0.5 else "B" for x in \
                        predictions]
  1. 评估模型:
print(classification_report(y_test, predictions_nominal, digits=3))

输出显示了恶性 (M) 和良性 (B) 的 精确度召回率f1-score:

  1. 评估混淆矩阵:
cfm = confusion_matrix(y_test, predictions_nominal)
precision,recall,fscore,support=score(y_test, predictions_nominal,
                                      average='macro')

print('Confusion Matrix: \n', cfm, '\n')

输出如下:

结果显示,在我们的测试集中共有 171 条记录,其中 112 条是真负样本,49 条是真正样本,这意味着在 171 条记录中,它能够正确识别出 161 条记录。 其中 10 条预测是错误的: 5 条假负样本和 5 条假正样本。

它是如何工作的...

在本配方中,我们使用了逻辑回归。 逻辑回归是一种可用于传统统计学以及机器学习的技术。 由于其简单性和强大性,许多数据科学家将逻辑回归作为他们的第一个模型,并将其用作超越的基准。 逻辑回归是一个二元分类器,意味着它可以将某些东西分类为 truefalse。 在我们的情况下,分类是良性或恶性。

首先,我们导入 koalas 进行数据操作,以及 sklearn 用于我们的模型和分析。 接下来,我们从我们的数据表中导入数据并将其放入 Pandas DataFrame 中。 然后,我们将数据分割为测试和训练数据集。 接下来,我们创建一个描述模型所使用的数据列的公式。 接下来,我们向模型提供公式、训练数据集以及它将使用的算法。 然后,我们输出一个可以用来评估新数据的模型。 现在我们创建一个名为 predictions_nominal 的 DataFrame,我们可以用它来与我们的测试结果数据集进行比较。 分类报告给出了 精确度召回率f1-score:

  • 精确度: 正确报告的正预测与预期的正预测之比

  • 召回率: 正确报告的正预测与总体人口的比率

  • F 分数: 精度和召回率的混合分数

接下来,我们可以查看模型的结果,并确定其如何准确预测真实值。我们将检查的一些因素如下:

  • 真阴性:测试集中实际为负的预测负例

  • 假阳性:训练模型在训练集中预测为正但实际不是

  • 假阴性:测试集中实际上是正例但被预测为负例的假负例

  • 真阳性:模型实际上正确获取的数量

还有更多...

我们将记录 MLflow 中的结果以与其他算法进行比较。我们还将保存其他参数,如使用的主要公式和预测族:

import pickle
import mlflow

with mlflow.start_run():
    mlflow.set_experiment("/Shared/experiments/BreastCancer")

    mlflow.log_param("formula", formula)
    mlflow.log_param("family", "binomial")

    mlflow.log_metric("precision", precision)
    mlflow.log_metric("recall", recall)
    mlflow.log_metric("fscore", fscore)
    filename = 'finalized_model.sav'
    pickle.dump(model, open(filename, 'wb'))

    mlflow.log_artifact(filename)

使用决策树分类化学传感器

在本篇文章中,我们将使用金属氧化物MOx)传感器的化学传感器数据来确定空气中是否有葡萄酒。这种类型的传感器通常用于确定空气中是否存在食品或化学颗粒。化学传感器可以检测对人类有害或仓库内食品泄漏的气体。

如何实现...

按照以下步骤完成这个配方:

  1. 导入库:
import pandas as pd
import numpy as np

from sklearn import neighbors, metrics
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier 
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
  1. 导入数据:
df = spark.sql("select * from ChemicalSensor")
pdf = df.toPandas()
  1. 编码数值:
label_encoder = LabelEncoder()
integer_encoded = \
    label_encoder.fit_transform(pdf['classification'])
onehot_encoder = OneHotEncoder(sparse=False)

integer_encoded = integer_encoded.reshape(len(integer_encoded), 1)
onehot_encoded = onehot_encoder.fit_transform(integer_encoded)
  1. 测试/训练分割数据:
X = pdf[feature_cols]
y = onehot_encoded

X_train, X_test, y_train, y_test = \
train_test_split(X, y, test_size=0.2, random_state=5)
  1. 训练和预测:
clf = DecisionTreeClassifier()
clf = clf.fit(X_train,y_train)
y_pred = clf.predict(X_test)
  1. 评估准确性:
print("Accuracy:",metrics.accuracy_score(y_test, y_pred))
print("AUC:",roc_auc_score(y_test, y_pred))

工作原理...

和往常一样,我们导入这个项目所需的库。接下来,我们将 Spark 数据表中的数据导入到 Pandas DataFrame 中。独热编码可以将分类值(例如我们的葡萄酒无葡萄酒示例)更改为机器学习算法更好使用的编码值。在步骤 4中,我们将特征列和我们的独热编码列分割成一个测试和训练集。在步骤 5中,我们创建一个决策树分类器,使用X_trainy_train数据来训练模型,然后使用X_test数据创建一个y_prediction数据集。换句话说,最后,我们将根据数据集在X_test集上的预测得到一组名为y_pred的预测。在步骤 6中,我们评估模型的准确性和曲线下面积AUC)。

当数据复杂时,决策树分类器被用来使用一系列逻辑规则进行分类。您可以像下图所示使用决策树跟随一组是/否问题:

机器学习算法可以训练决策树模型使用数值数据,如下图所示:

机器学习算法训练模型以准确选择给定可用数据的最佳路径。

还有更多...

sklearn 决策树分类器有两个超参数可以调整:准则最大深度。通常会更改超参数以提高准确性。准则可以是基尼系数或信息熵。这两个准则评估子节点中的不纯度。接下来是最大深度。决策树的最大深度会影响欠拟合和过拟合。

欠拟合与过拟合

欠拟合的模型不准确,无法有效地表示它们所训练的数据。

过拟合的模型无法从训练数据中进行泛化。它会错过与训练集相似的数据,因为它只适用于与其训练时完全相同的数据。

使用 XGBoost 进行简单预测维护

每个设备都有寿命终止或需要定期维护。预测维护是物联网中最常用的机器学习算法之一。下一章将深入探讨预测维护,重点关注序列数据及其如何随季节性变化的情况。本配方将从分类的简单视角来看待预测维护。

在此配方中,我们将使用 NASA 涡轮风扇引擎退化模拟 数据集。我们将看到三种分类情况。绿色表示引擎不需要维护;黄色表示引擎在接下来的 14 个维护周期内需要维护;红色表示引擎在下一个周期内需要维护。作为算法,我们将使用极限梯度提升XGBoost)。近年来,XGBoost 因其在 Kaggle 竞赛中的表现优异而变得流行。

准备工作

要做好准备,您需要 NASA 涡轮风扇引擎退化模拟 数据集。此数据以及一个 Spark 笔记本可以在本书的伴随 GitHub 仓库或 NASA 网站上找到。接下来,您需要确保在 Databricks 中安装 XGBoost 作为库。

如何实现...

此配方的步骤如下:

  1. 导入库:
import pandas as pd
import numpy as np
from pyspark.sql.types import *
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_score
import pickle
import mlflow
  1. 导入数据:
file_location = "/FileStore/tables/train_FD001.txt"
file_type = "csv"

schema = StructType([
                     StructField("engine_id", IntegerType()),
                     StructField("cycle", IntegerType()),
                     StructField("setting1", DoubleType()),
                     StructField("setting2", DoubleType()),
                     StructField("setting3", DoubleType()),
                     StructField("s1", DoubleType()),
                     StructField("s2", DoubleType()),
                     StructField("s3", DoubleType()),
                     StructField("s4", DoubleType()),
                     StructField("s5", DoubleType()),
                     StructField("s6", DoubleType()),
                     StructField("s7", DoubleType()),
                     StructField("s8", DoubleType()),
                     StructField("s9", DoubleType()),
                     StructField("s10", DoubleType()),
                     StructField("s11", DoubleType()),
                     StructField("s12", DoubleType()),
                     StructField("s13", DoubleType()),
                     StructField("s14", DoubleType()),
                     StructField("s15", DoubleType()),
                     StructField("s16", DoubleType()),
                     StructField("s17", IntegerType()),
                     StructField("s18", IntegerType()),
                     StructField("s19", DoubleType()),
                     StructField("s20", DoubleType()),
                     StructField("s21", DoubleType())
                     ])

df = spark.read.option("delimiter"," ").csv(file_location,
                                            schema=schema, 
                                            header=False)
  1. 在数据上创建表视图:
df.createOrReplaceTempView("raw_engine")
  1. 转换数据:
%sql

 drop table if exists engine;

 create table engine as
 (select e.*, CASE WHEN mc - e.cycle = 1 THEN 1 ELSE
 CASE WHEN mc - e.cycle < 14 THEN 2 ELSE
 0 END END as label
 from raw_engine e
 join (select max(cycle) mc, engine_id from raw_engine group by engine_id) m
 on e.engine_id = m.engine_id)
  1. 测试、训练和拆分数据:
new_input = spark.sql("select * from engine").toPandas()
training_df, test_df = train_test_split(new_input)
  1. 准备模型:
dtrain = xgb.DMatrix(training_df[['setting1','setting2','setting3', 's1', 's2', 's3',
 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', 's12', 's13', 's14',
 's15', 's16','s17', 's18', 's19', 's20', 's21']], label=training_df["label"])
 param = {'max_depth': 2, 'eta': 1, 'silent': 1, 'objective': 'multi:softmax'}
 param['nthread'] = 4
 param['eval_metric'] = 'auc'
 param['num_class'] = 3
  1. 训练模型:
num_round = 10
 bst = xgb.train(param, dtrain, num_round)
  1. 评估模型:
dtest = xgb.DMatrix(test_df[['setting1', 'setting2', 'setting3', 
                             's1', 's2', 's3', 's4', 's5', 's6', 
                             's7', 's8', 's9', 's10', 's11', 
                             's12', 's13', 's14', 's15', 's16',
                             's17', 's18', 's19', 's20', 's21']])
ypred = bst.predict(dtest)

pre_score = precision_score(test_df["label"], ypred, 
                            average='micro')
print("xgb_pre_score:",pre_score)
  1. 存储结果:
with mlflow.start_run():
    mlflow.set_experiment("/Shared/experiments/\
                           Predictive_Maintenance")
    mlflow.log_param("type", 'XGBoost')
    mlflow.log_metric("precision_score", pre_score)
    filename = 'bst.sav'
    pickle.dump(bst, open(filename, 'wb'))
    mlflow.log_artifact(filename)

它的工作原理...

首先,我们导入pandaspysparknumpy进行数据处理,xgboost作为我们的算法,sklearn用于评分结果,最后使用mlflowpickle保存这些结果。在步骤 2 中,我们在 Spark 中指定了一个模式。Databricks 的推断模式功能通常会出现模式错误。通常我们需要指定数据类型。在接下来的步骤中,我们创建了数据的临时视图,以便在 Databricks 中使用 SQL 工具。在步骤 4 中,我们在页面顶部使用魔术标签 %sql 将语言切换为 SQL。然后,我们创建了一个名为engine的表,该表包含引擎数据及一个新列,如果引擎剩余循环大于 14,则给出0,如果只剩一个循环,则给出1,如果剩余 14 个循环,则给出2。然后我们切换回默认的 Python 语言,并将数据拆分为测试和训练数据集。在步骤 6 中,我们指定了模型中的列以及超参数。从这里开始训练模型。然后我们测试我们的模型并打印精确度分数。接下来,我们将结果存储在 MLflow 中。在第四章,预测性维护的深度学习,我们将对此数据集进行其他实验,以查看哪种表现最佳。

XGBoost 有大量可以调整的参数。这些调整参数可以是允许算法使用的线程数,以及帮助提高准确性或防止过拟合和欠拟合的树参数。其中一些包括:

  • learning_rate: 学习率是算法更新节点的步长。它有助于防止过拟合,但也可能会对训练完成所需的时间产生负面影响。

  • max_depth: 深度树倾向于过拟合,浅树倾向于欠拟合。

  • predictor: 这是一个标志,告诉程序在 CPU 或 GPU 上进行计算。GPU 可以显著提高性能,但并非所有计算机都配备 GPU。

还有十几个可以在 XGBoost 中调整的参数。

XGBoost 决策树在内部采用弱学习器或浅树,并使用维度评分系统将它们组合成强学习器。这类似于从医生那里得到不良诊断,然后寻求第二和第三意见。第一个医生可能是错的,但不太可能三个医生都错。

检测不安全的驾驶员

在机器学习中的计算机视觉使我们能够判断道路上是否有事故或者不安全的工作环境,并且可以与复杂系统(如智能销售助手)结合使用。计算机视觉在物联网中开辟了许多可能性。从成本的角度来看,计算机视觉也是最具挑战性的之一。在接下来的两个示例中,我们将讨论两种不同的使用计算机视觉的方式。第一种方法是接收大量从物联网设备生成的图像,并使用高性能分布式 Databricks 格式对其进行预测和分析。在下一个示例中,我们将使用一种在边缘设备上执行机器学习的技术,使用低计算量的算法。

准备工作

准备工作,您将需要 Databricks。在这个示例中,我们将从 Azure Blob Storage 中提取图像。

如何做…

此示例的步骤如下:

  1. 导入库和配置:
from pyspark.ml.classification import LogisticRegression
from pyspark.ml import Pipeline
from sparkdl import DeepImageFeaturizer
from pyspark.ml.evaluation import \
MulticlassClassificationEvaluator
from pyspark.sql.functions import lit
import pickle
import mlflow

storage_account_name = "Your Storage Account Name"
storage_account_access_key = "Your Key"
  1. 读取数据:
safe_images = "wasbs://unsafedrivers@"+storage_account_name+\
               ".blob.core.windows.net/safe/"
safe_df = spark.read.format('image').load(safe_images)\
          .withColumn("label", lit(0))

unsafe_images = "wasbs://unsafedrivers@"+storage_account_name+\
                ".blob.core.windows.net/unsafe/"
unsafe_df = spark.read.format('image').load(unsafe_images)\
            .withColumn("label", lit(1))
  1. 查询数据:
display(unsafe_df)
  1. 创建测试和训练数据集:
unsafe_train, unsafe_test = unsafe_df.randomSplit([0.6, 0.4])
safe_train, safe_test = safe_df.randomSplit([0.6, 0.4])

train_df = unsafe_train.unionAll(safe_train)
test_df = safe_test.unionAll(unsafe_test)
  1. 构建管道:
featurizer = DeepImageFeaturizer(inputCol="image", 
                                 outputCol="features", 
                                 modelName="ResNet50")
lr = LogisticRegression(maxIter=20, regParam=0.05, 
                        elasticNetParam=0.3, labelCol="label")
p = Pipeline(stages=[featurizer, lr])
  1. 训练模型:
p_model = p.fit(train_df)
  1. 评估模型:
predictions = p_model.transform(test_df)

predictions.select("filePath", "prediction").show(truncate=False)
df = p_model.transform(test_df)

predictionAndLabels = df.select("prediction", "label")
evaluator = \
MulticlassClassificationEvaluator(metricName="accuracy")
print("Training set accuracy = " + \
       str(evaluator.evaluate(predictionAndLabels)))
  1. 记录结果:
with mlflow.start_run():
    mlflow.set_experiment("/Shared/experiments/Workplace Safety")

    mlflow.log_param("Model Name", "ResNet50")
    # Log a metric; metrics can be updated throughout the run
    precision, recall, fscore, support=score(y_test, y_pred,
                                             average='macro')

    mlflow.log_metric("Accuracy", \
                      evaluator.evaluate(predictionAndLabels))

    filename = 'finalized_model.sav'
    pickle.dump(p_model, open(filename, 'wb'))
    # Log an artifact (output file)
    mlflow.log_artifact(filename)

它是如何工作的…

首先,我们定义文件的位置。对于这个示例,我们使用 Azure Blob Storage,但是任何存储系统,比如 S3 或 HDFS,也同样适用。用你的 Blob Storage 账户的密钥替换storage_account_namestorage_account_access_key字段。从我们的存储账户中读取安全不安全的图像到一个 Spark 图像 DataFrame 中。在我们的示例中,我们将安全图像放在一个文件夹中,不安全图像放在另一个文件夹中。查询图像 DataFrame 以查看是否成功获取了图像。创建安全和不安全的测试和训练集。然后,将我们的数据集合并成一个训练集和一个测试集。接下来,创建一个机器学习管道。我们使用 ResNet-50 算法作为特征提取器。然后,我们使用逻辑回归作为分类器。将它放入管道中并训练我们的模型。接下来,将我们的管道运行我们的训练 DataFrame,得出一个经过训练的模型。然后,评估我们模型的准确性。最后,将结果存储在 MLflow 中,以便与其他模型进行比较。

有许多图像分类模型已经开发出来,比如 ResNet-50 和 Inception v3。在我们的例子中,我们使用了 ResNet-50,这是一种调整过的卷积神经网络。ResNet-50 是一种强大的用于图像特征提取的机器学习模型。在机器学习中,有无免费午餐定理,它指出没有一个模型会在所有情况下表现最好。因此,数据科学家会测试不同的算法。可以通过改变参数如指标名称来简单地完成这一过程。

我们还使用了 Spark ML 流水线。流水线允许数据科学家声明处理过程的不同步骤,并独立实现它们。在我们的例子中,我们使用 ResNet-50 对图像进行特征化。ResNet-50 输出一个特征向量,可以通过分类器进行分类。在我们的情况下,我们使用逻辑回归,但也可以使用 XGBoost 或其他神经网络。

还有更多...

要将我们的流水线更改为使用 Inception 而不是ResNet50,我们只需更改模型:

featurizer = DeepImageFeaturizer(inputCol="image", outputCol="features", 
                                 modelName="ResNet50")

使用Inception v3,我们能够在图像集上测试不同模型的准确性:

featurizer = DeepImageFeaturizer(inputCol="image", outputCol="features",
                                 modelName="InceptionV3")

我们可以使用一系列模型,并在 MLflow 中记录结果:

for m in ['InceptionV3', 'Xception','ResNet50', 'VGG19']:
    featurizer = DeepImageFeaturizer(inputCol="image", 
                                     outputCol="features", 
                                     modelName=m)

受限设备上的面部检测

深度神经网络往往优于其他分类技术。然而,在物联网设备上,RAM、计算能力或存储量并不是很大。在受限设备上,RAM 和存储通常以 MB 为单位,而不是 GB,使得传统的分类器不可行。一些云端视频分类服务每个设备的实时视频收费超过 10,000 美元。OpenCV 的 Haar 分类器具有与卷积神经网络相同的基本原理,但计算和存储量只是其一小部分。OpenCV 支持多种语言,并可以在一些受限制的设备上运行。

在本示例中,我们将设置一个 Haar 级联,以便检测人物是否靠近摄像头。这通常用于 Kiosk 和其他交互式智能设备。Haar 级联可以以高速运行,当发现接近设备的人脸时,可以通过云服务或其他设备上的机器学习模型发送该图像。

准备工作

我们需要做的第一件事是安装 OpenCV 框架:

pip install opencv-python

接下来,我们下载模型。模型可以从 OpenCV GitHub 页面或书籍的 GitHub 页面下载。文件是haarcascade_frontalface_default.xml

接下来,我们通过导入haarcascade_frontalface_default.xml文件并创建一个 Python 文件来创建一个新文件夹,以用于代码。最后,如果设备没有连接摄像头,请连接一个。在接下来的示例中,我们将使用 OpenCV 实现 Haar 级联。

如何操作...

此示例的步骤如下:

  1. 导入库和设置:
import cv2
from time import sleep

debugging = True
classifier = \
cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
video = cv2.VideoCapture(0)
  1. 初始化摄像头:
while True:
    if not video.isOpened():
    print('Waiting for Camera.')
    sleep(5)
    pass
  1. 捕获和转换图像:
ret, frame = video.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  1. 对图像进行分类:
faces = classifier.detectMultiScale(gray,
                                    minNeighbors=5,
                                    minSize=(100, 100)
                                    )
  1. 调试图像:
if debugging:
    # Draw a rectangle around the faces
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)

    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
  1. 检测面部:
if len(faces) > 0:
    # Your advanced code here
    pass

工作原理...

首先,导入库并设置设置。接下来,我们导入 opencvpython 库,同时也导入 time 以便在摄像头未就绪时等待。然后,我们设置一些调试标志,以便在调试时可以视觉化测试输出。接着,我们将 Haar Cascade XML 文件导入分类器。最后,我们打开连接到机器的第一个视频摄像头。在 第 2 步 中,我们等待摄像头就绪。在开发软件时,这通常不是问题,因为系统已经识别了摄像头。然后,我们将此程序设置为自动运行;当系统重新启动时,摄像头可能最多需要一分钟才能可用。我们还启动了一个无限循环来处理摄像头图像。在接下来的步骤中,我们捕获并将图像转换为黑白。接着,我们运行分类器。detectMultiScale 分类器可以检测不同尺寸的人脸。minNeighbors 参数指定在检测到一个人脸之前需要多少个相邻的协作检测。将 minNeighbors 参数设置得太小可能会导致误报。设置得太大可能根本无法检测到人脸。最后,还有人脸需要的最小像素大小。为了调试代码并确保摄像头工作准确,我们添加了一些调试代码,将视频和边界框输出到连接的监视器上。在部署设备上,这会增加相当大的负载。但是在测试中,这可以显示问题并允许进行调优。如果检测到人脸,则可以执行任务,如本地情感分析或将其发送到外部服务(如 Azure Face API)以通过人脸 ID 识别人员。

Haar Cascade 是高效的人脸检测分类器。在幕后,它会取图像的矩形部分,并将其与图像的另一部分进行比较,从而得出具有人脸特征的东西。在我们的示例中,我们使用设备上的摄像头,对其进行转换,然后使用 Haar Cascade 进行分类。

第四章:预测性维护的深度学习

预测性维护是物联网中最受追捧的机器学习解决方案之一。但它也是最难以捉摸的机器学习解决方案之一。机器学习的其他领域可以轻松解决,例如使用 OpenCV 或 Keras 等工具实现计算机视觉,可以在几小时内完成。要成功实施预测性维护,首先需要合适的传感器。第二章中的数据采集设计部分可以帮助确定适当的传感器位置。第二章中的探索性因子分析部分可以帮助确定数据存储的频率。实施预测性维护的最大难题之一是需要有足够数量的设备故障。对于坚固的工业设备来说,这可能需要很长时间。将维修记录与设备遥测数据进行关联也是关键步骤。

尽管挑战艰巨,回报是巨大的。一个正确实施的预测性维护解决方案可以通过确保关键设备在需要时准备就绪来拯救生命。它们还可以增加客户忠诚度,因为它们有助于公司比市场上类似产品少停机。最后,它们可以通过在服务设备之前提供服务技术人员所需的信息来降低成本并提高效率。这可以帮助他们诊断设备,并确保他们在服务设备时携带正确的零件。

在本章中,我们将继续使用 NASA Turbofan 数据集进行预测性维护,并涵盖以下配方:

  • 利用特征工程增强数据

  • 使用 Keras 进行跌倒检测

  • 实施 LSTM 进行设备故障预测

  • 部署模型到 Web 服务

利用特征工程增强数据

在改进模型中,最好的时间利用是特征工程。物联网生态系统有许多工具可以简化这一过程。设备可以通过数字孪生、图帧和 GraphX 进行地理连接或层次连接。这些工具可以增加一些特征,如显示与其他故障设备的联系程度。窗口化可以显示当前读数在一段时间内的差异。流式处理工具如 Kafka 可以合并不同的数据流,允许你将来自其他来源的数据进行整合。户外的机器可能会受到高温或潮湿的负面影响,而在气候控制建筑物内的机器则不会。

在这个配方中,我们将通过分析时间序列数据(如变化量、季节性和窗口化)来增强我们的数据。对于数据科学家来说,时间最宝贵的用途之一是进行特征工程。能够将数据切分为有意义的特征可以极大地提高我们模型的准确性。

准备工作

在上一章节的使用 XGBoost 进行预测性维护配方中,我们使用 XGBoost 来预测机器是否需要维护。我们导入了 NASA 的涡轮风扇引擎退化模拟数据集,可以在data.nasa.gov/dataset/Turbofan-engine-degradation-simulation-data-set/vrks-gjie找到。在本章的其余部分,我们将继续使用该数据集。为了做好准备,您将需要该数据集。

然后,如果您还没有将numpypandasmatplotlibseaborn导入到 Databricks 中,请立即这样做。

如何做到这一点...

需要遵循此配方的以下步骤:

  1. 首先,导入所需的库。我们将使用pyspark.sqlnumpypandas进行数据操作,使用matplotlibseaborn进行可视化:
from pyspark.sql import functions as F
from pyspark.sql.window import Window

import pandas as pd
import numpy as np
np.random.seed(1385)

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
  1. 接下来,我们将导入数据并为其应用模式,以便正确使用数据类型。为此,我们通过向导导入数据文件,然后将我们的模式应用于它:
file_location = "/FileStore/tables/train_FD001.txt"
file_type = "csv"

from pyspark.sql.types import *
schema = StructType([
  StructField("engine_id", IntegerType()),
  StructField("cycle", IntegerType()),
  StructField("setting1", DoubleType()),
  StructField("setting2", DoubleType()),
  StructField("setting3", DoubleType()),
  StructField("s1", DoubleType()),
  StructField("s2", DoubleType()),
  StructField("s3", DoubleType()),
  StructField("s4", DoubleType()),
  StructField("s5", DoubleType()),
  StructField("s6", DoubleType()),
  StructField("s7", DoubleType()),
  StructField("s8", DoubleType()),
  StructField("s9", DoubleType()),
  StructField("s10", DoubleType()),
  StructField("s11", DoubleType()),
  StructField("s12", DoubleType()),
  StructField("s13", DoubleType()),
  StructField("s14", DoubleType()),
  StructField("s15", DoubleType()),
  StructField("s16", DoubleType()),
  StructField("s17", IntegerType()),
  StructField("s18", IntegerType()),
  StructField("s19", DoubleType()),
  StructField("s20", DoubleType()),
  StructField("s21", DoubleType())
  ])
  1. 最后,我们将其放入一个 Spark DataFrame 中:
df = spark.read.option("delimiter"," ").csv(file_location, 
                                            schema=schema, 
                                            header=False)
  1. 然后我们创建一个临时视图,以便我们可以在其上运行一个 Spark SQL 作业:
df.createOrReplaceTempView("raw_engine")
  1. 接下来,我们计算剩余使用寿命 (RUL)。使用 SQL 魔法,我们从刚刚创建的raw_engine临时视图中创建一个名为engine的表。然后,我们使用 SQL 来计算 RUL:
%sql

drop table if exists engine;

create table engine as
(select e.*
,mc - e.cycle as rul
, CASE WHEN mc - e.cycle < 14 THEN 1 ELSE 0 END as needs_maintenance 
from raw_engine e 
join (select max(cycle) mc, engine_id from raw_engine group by engine_id) m
on e.engine_id = m.engine_id)
  1. 然后,我们将数据导入到一个 Spark DataFrame 中:
df = spark.sql("select * from engine")
  1. 现在我们计算变化率 (ROC)。在 ROC 计算中,我们查看当前记录与上一记录之间的变化百分比。ROC 计算获取当前周期与前一个周期之间的变化百分比:
my_window = Window.partitionBy('engine_id').orderBy("cycle")
df = df.withColumn("roc_s9", 
                   ((F.lag(df.s9).over(my_window)/df.s9) -1)*100)
df = df.withColumn("roc_s20", 
                   ((F.lag(df.s20).over(my_window)/df.s20) -1)*100)
df = df.withColumn("roc_s2", 
                   ((F.lag(df.s2).over(my_window)/df.s2) -1)*100)
df = df.withColumn("roc_s14", 
                   ((F.lag(df.s14).over(my_window)/df.s14) -1)*100)
  1. 接下来,我们审查静态列。为了做到这一点,我们将 Spark DataFrame 转换为 Pandas,以便查看数据的汇总统计信息,如均值、四分位数和标准差:
pdf = df.toPandas()
pdf.describe().transpose()

这将得到以下输出:

  1. 现在,我们删除在这个练习中对我们没有价值的列。例如,我们将删除settings3s1列,因为这些值从不改变:
columns_to_drop = ['s1', 's5', 's10', 's16', 's18', 's19', 
                   'op_setting3', 'setting3']
df = df.drop(*columns_to_drop)
  1. 接下来,我们将审查值之间的相关性。我们寻找完全相同的列。首先,在 DataFrame 上执行相关性函数。然后,使用np.zeros_like掩盖上三角。接下来,设置图表大小。然后,使用diverging_palette定义自定义颜色映射,接着使用heatmap函数绘制热力图:
corr = pdf.corr().round(1)
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True

f, ax = plt.subplots(figsize=(20, 20))

cmap = sns.diverging_palette(220, 10, as_cmap=True)

sns.heatmap(corr, mask=mask, cmap=cmap, vmin=-1, vmax=1, center=0,
            square=True, linewidths=.5, cbar_kws={"shrink": .5}, 
            annot=True)

display(plt.tight_layout())

下面的热力图显示了具有高度相关性的值。数值为1表示它们完全相关,因此可以从分析中删除:

  1. 删除相似的列。我们发现S14S9完全相同,因此我们将删除该列:
columns_to_drop = ['s14']
df = df.drop(*columns_to_drop)
  1. 现在我们拿到 DataFrame 并将其可视化。使用直方图或分布表显示数据的潜在问题,如异常值、偏斜数据、随机数据和不影响模型的数据:
pdf = df.toPandas()

plt.figure(figsize = (16, 8))
plt.title('Example temperature sensor', fontsize=16)
plt.xlabel('# Cycles', fontsize=16)
plt.ylabel('Degrees', fontsize=16)
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
pdf.hist(bins=50, figsize=(18,16))
display(plt.show())

以下直方图截图展示了结果:

  1. 然后我们检查模型的噪声,以确保它不会过度受到波动的影响:
values = pdf[pdf.engine_id==1].values
groups = [5, 6, 7, 8, 9, 10, 11,12,13]
i = 1
plt.figure(figsize=(10,20))
for group in groups:
    plt.subplot(len(groups), 1, i)
    plt.plot(values[:, group])
    plt.title(pdf.columns[group], y=0.5, loc='right')
    i += 1
display(plt.show())

以下是输出结果:

  1. 根据前一步骤,很明显数据是嘈杂的。这可能导致错误的读数。滚动平均可以帮助平滑数据。使用 7 个周期的滚动平均,我们去噪了数据,如下所示:
w = (Window.partitionBy('engine_id').orderBy("cycle")\
     .rangeBetween(-7,0))
df = df.withColumn('rolling_average_s2', F.avg("s2").over(w))
df = df.withColumn('rolling_average_s3', F.avg("s3").over(w))
df = df.withColumn('rolling_average_s4', F.avg("s4").over(w))
df = df.withColumn('rolling_average_s7', F.avg("s7").over(w))
df = df.withColumn('rolling_average_s8', F.avg("s8").over(w))

pdf = df.toPandas()
values = pdf[pdf.engine_id==1].values
groups = [5, 25, 6, 26, 8, 27]
i = 1
plt.figure(figsize=(10,20))
for group in groups:
    plt.subplot(len(groups), 1, i)
    plt.plot(values[:, group])
    plt.title(pdf.columns[group], y=0.5, loc='right')
    i += 1
display(plt.show())

以下截图展示了 rolling_average_s4 相对于 s4 的图表:

  1. 由于我们希望其他笔记本能访问这些数据,我们将其保存为一个 ML 准备好的表格:
df.write.mode("overwrite").saveAsTable("engine_ml_ready") 

工作原理...

在这个配方中,我们进行了特征工程,使我们的数据更适用于我们的机器学习算法。我们移除了没有变化、高相关性的列,并对数据集进行了去噪。在 步骤 8 中,我们移除了没有变化的列。该方法用多种方式描述数据。审查图表显示许多变量根本不发生变化。接下来,我们使用热力图找到具有相同数据的传感器。最后,我们使用滚动平均值将原始数据集的数据平滑成新数据集。

还有更多...

到目前为止,我们只看过训练数据。但是我们还需要查看测试数据。有一个测试数据集和一个 RUL 数据集。这些数据集将帮助我们测试我们的模型。要导入它们,您需要运行额外的 2 个导入步骤:

  1. 导入测试数据:依赖于训练集的架构,导入测试集并放入名为 engine_test 的表格中:
# File location and type
file_location = "/FileStore/tables/test_FD001.txt"
df = spark.read.option("delimiter"," ").csv(file_location, 
                                            schema=schema, 
                                            header=False)
df.write.mode("overwrite").saveAsTable("engine_test")

  1. 导入 RUL 数据集:接下来是导入剩余寿命数据集并将其保存到一个表格中:
file_location = "/FileStore/tables/RUL_FD001.txt"
RULschema = StructType([StructField("RUL", IntegerType())])
df = spark.read.option("delimiter"," ").csv(file_location,
                                            schema=RULschema, 
                                            header=False)
df.write.mode("overwrite").saveAsTable("engine_RUL")

使用 keras 进行摔倒检测

一种预测性维护的策略是查看给定记录的设备故障模式。在这个配方中,我们将分类表现出在设备故障之前发生的模式的数据。

我们将使用 keras,这是一个非常强大的机器学习库。Keras 简化了 TensorFlow 和 PyTorch 的一些复杂性。对于机器学习初学者来说,Keras 是一个很好的框架,因为它易于入门,并且在 Keras 中学到的概念可以转移到更具表现力的机器学习库,如 TensorFlow 和 PyTorch。

准备工作

本配方扩展了我们在之前配方中进行的预测性维护数据集的特征工程。如果你还没有这样做,你需要将 kerastensorflowsklearnpandasnumpy 库导入到你的 Databricks 集群中。

如何操作...

请注意以下步骤:

  1. 首先,导入所需的库。我们导入pandaspyspark.sqlnumpy用于数据操作,keras用于机器学习,以及sklearn用于评估模型。在评估模型后,我们使用iopicklemlflow保存模型和结果,以便与其他模型进行比较:
from pyspark.sql.functions import *
from pyspark.sql.window import Window

import pandas as pd
import numpy as np
import io
import keras
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_score
from sklearn.preprocessing import MinMaxScaler

from keras.models import Sequential
from keras.layers import Dense, Activation, LeakyReLU, Dropout

import pickle
import mlflow
  1. 接下来,我们导入训练和测试数据。我们的训练数据将用于训练模型,而测试数据将用于评估模型:
X_train = spark.sql("select rolling_average_s2, rolling_average_s3, 
                    rolling_average_s4, rolling_average_s7, 
                    rolling_average_s8 from \
                    engine_ml_ready").toPandas()

y_train = spark.sql("select needs_maintenance from \
                    engine_ml_ready").toPandas()

X_test = spark.sql("select rolling_average_s2, rolling_average_s3, 
                    rolling_average_s4, rolling_average_s7, 
                    rolling_average_s8 from \
                    engine_test_ml_ready").toPandas()

y_test = spark.sql("select needs_maintenance from \
                    engine_test_ml_ready").toPandas()
  1. 现在我们对数据进行缩放。数据集的每个传感器具有不同的比例。例如,S1的最大值是518,而S16的最大值是0.03。因此,我们将所有值转换为01的范围。使每个度量影响模型的方式类似。我们将使用sklearn库中的MinMaxScaler函数调整比例:
scaler = MinMaxScaler(feature_range=(0, 1))
X_train.iloc[:,1:6] = scaler.fit_transform(X_train.iloc[:,1:6])
X_test.iloc[:,1:6] = scaler.fit_transform(X_test.iloc[:,1:6])
dim = X_train.shape[1]
  1. 第一层,即输入层,有 32 个节点。激活函数LeakyReLU在给定输入时定义输出节点。为了防止过拟合,在训练时将 25%的隐藏层和可见层丢弃:
model = Sequential()
model.add(Dense(32, input_dim = dim))
model.add(LeakyReLU())
model.add(Dropout(0.25))
  1. 类似于输入层,隐藏层使用 32 个节点作为输入层,LeakyReLU作为输出层。它还使用 25%的 dropout 来防止过拟合:
model.add(Dense(32))
model.add(LeakyReLU())
model.add(Dropout(0.25))
  1. 最后,我们添加一个输出层。我们给它一层,以便可以得到在01之间的输出。sigmoid作为我们的激活函数,有助于预测输出的概率。我们的优化器rmsprop和损失函数帮助优化数据模式并减少误差率:
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.compile(optimizer ='rmsprop', loss ='binary_crossentropy',
              metrics = ['accuracy'])
  1. 现在我们训练模型。我们使用model.fit函数指定我们的训练和测试数据。批量大小用于设置算法迭代中使用的训练记录数量。5个 epoch 意味着它将通过数据集 5 次:
model.fit(X_train, y_train, batch_size = 32, epochs = 5, 
          verbose = 1, validation_data = (X_test, y_test))
  1. 下一步是评估结果。我们使用训练好的模型和我们的X_test数据集来获取预测值y_pred。然后,我们将预测结果与实际结果进行比较,并查看其准确性:
y_pred = model.predict(X_test)
pre_score = precision_score(y_test,y_pred, average='micro')
print("Neural Network:",pre_score)
  1. 接下来,我们将结果保存到mlflow中。结果将与本书中使用的其他预测维护 ML 算法进行比较:
with mlflow.start_run():
    mlflow.set_experiment("/Shared/experiments/Predictive_Maintenance")
    mlflow.log_param("model", 'Neural Network')
    mlflow.log_param("Inputactivation", 'Leaky ReLU')
    mlflow.log_param("Hiddenactivation", 'Leaky ReLU')
    mlflow.log_param("optimizer", 'rmsprop')
    mlflow.log_param("loss", 'binary_crossentropy')
    mlflow.log_metric("precision_score", pre_score)
    filename = 'NeuralNet.pickel'
    pickle.dump(model, open(filename, 'wb'))
    mlflow.log_artifact(filename)

工作原理...

通常神经网络有三个任务:

  • 导入数据

  • 通过训练识别数据的模式

  • 预测新数据的结果

神经网络接收数据,训练自己识别数据的模式,然后用于预测新数据的结果。这个流程使用上一个流程中保存的清理和特征工程后的数据集。X_train数据集从spark数据表中取出并转换为 Panda DataFrame。训练数据集X_trainy_train用于训练模型。X_test提供了设备列表,这些设备已经发生了故障,而y_test提供了这些机器的实时故障。这些数据集用于训练模型和测试结果。

首先,我们有输入层。数据被馈送到我们的 32 个输入神经元中的每一个。神经元通过通道连接。通道被分配一个称为权重的数值。输入被相应的权重乘以,并且它们的总和被发送为输入到隐藏层中的神经元。每个这些神经元与一个称为偏置的数值相关联,该偏置被加到输入总和中。然后,这个值被传递到称为激活函数的阈值函数。激活函数决定神经元是否会被激活。在我们的前两层中,我们使用了 Leaky ReLU 作为我们的激活函数。ReLU修正线性单元是一种流行的激活函数,因为它解决了梯度消失问题。在这个示例中,我们使用了 Leaky ReLU。Leaky ReLU 解决了 ReLU 存在的问题,即大梯度可能导致神经元永远不会被激活。激活的神经元通过通道将其数据传递到下一层。这种方法允许数据通过网络传播。这称为前向传播。在输出层中,具有最高层的神经元被激活并确定输出。

当我们首次将数据传入网络时,数据通常具有较高的误差程度。我们的误差和优化器函数使用反向传播来更新权重。前向传播和反向传播的循环重复进行,以达到更低的误差率。下图显示了输入、隐藏和输出层如何连接在一起:

还有更多...

在这个示例中,我们将LeakyReLU作为激活函数,rmsprop作为优化器,binary_crossentropy作为损失函数。然后我们将结果保存到mlflow。我们可以通过尝试不同的组合来调整这个实验的参数,比如神经元的数量或层数。我们也可以改变激活函数,使用 ReLU 或者 TanH。我们也可以将Adam作为优化器。将这些结果保存到mlflow可以帮助我们改进模型。

实施 LSTM 以预测设备故障

递归神经网络预测数据序列。在前一个示例中,我们查看了某一时刻并确定是否需要维护。正如我们在第一个示例中看到的那样,涡轮风扇失效数据集具有高度的变异性。在任何时间点读取的数据可能表明需要维护,而下一个时间点可能表明不需要维护。在确定是否派出技术员时,有一个振荡信号可能会导致问题。长短期记忆网络LSTM)经常与时间序列数据一起使用,例如涡轮风扇失效数据集。

使用 LSTM,我们查看一系列数据,类似于窗口。LSTM 使用有序序列来帮助确定,例如,基于先前数据序列,涡轮风扇引擎是否即将故障。

准备工作

对于这个配方,我们将使用 NASA 的涡轮风扇运行到故障数据集。在这个配方中,我们将使用 Databricks 笔记本。此配方需要安装几个库。对于数据处理,我们需要安装numpypandas,用于创建 LSTM 模型的keras,以及用于评估和保存模型结果的sklearnmlflow

即使在先前的配方中我们添加了窗口处理和预处理数据,但在这个配方中我们将使用原始数据。LSTM 窗口化数据,并且还有大量与这种类型 ML 算法独特的提取和转换。

如何做…

我们将为这个配方执行以下步骤:

  1. 首先,我们将导入后续需要使用的所有库。我们将导入pandasnumpy进行数据处理,keras用于 ML 模型,sklearn用于评估,以及pickelmlflow用于存储结果:
import pandas as pd
import numpy as np

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, LSTM, Activation

from sklearn import preprocessing
from sklearn.metrics import confusion_matrix, recall_score, precision_score
import pickle
import mlflow
  1. 接下来,我们将设置变量。我们将设置 2 个周期期间。此外,我们使用一个序列长度变量。序列长度允许 LSTM 回溯 5 个周期。这类似于第一章中讨论的窗口处理,设置 IoT 和 AI 环境。我们还将获取数据列的列表:
week1 = 7
week2 = 14
sequence_length = 100
sensor_cols = ['s' + str(i) for i in range(1,22)]
sequence_cols = ['setting1', 'setting2', 'setting3', 'cycle_norm']
sequence_cols.extend(sensor_cols)
  1. 接下来,我们从第三章的IoT 机器学习中的简单预测性维护与 XGBoost配方中导入spark数据表的数据。我们还删除label列,因为我们将重新计算标签。我们将导入三个数据框。train数据框用于训练模型。test数据框用于测试模型的准确性,而truth数据框则是test数据框的实际故障:
train = spark.sql("select * from engine").toPandas()
train.drop(columns="label" , inplace=True)
test = spark.sql("select * from engine_test2").toPandas()
truth = spark.sql("select * from engine_rul").toPandas()
  1. 然后,我们生成标签,显示设备是否需要维护。label1显示设备在 14 个周期内将会故障,而label2显示设备将在 7 个周期内故障。首先,我们创建一个 DataFrame,显示每台发动机的最大周期数基于 RUL。接下来,我们使用 RUL DataFrame 在我们的 train DataFrame 中创建一个 RUL 列。通过从当前周期中减去最大寿命来完成此操作。然后,我们放弃我们的max列。接下来,我们创建一个新列label1。如果 RUL 小于 14 个周期,则label1的值为1。然后将其复制到label2中,并且如果 RUL 小于 1 周,则添加值2
rul = pd.DataFrame(train.groupby('engine_id')['cycle']\
                   .max()).reset_index()
rul.columns = ['engine_id', 'max']
train = train.merge(rul, on=['engine_id'], how='left')
train['RUL'] = train['max'] - train['cycle']
train.drop('max', axis=1, inplace=True)
train['label1'] = np.where(train['RUL'] <= week2, 1, 0 )
train['label2'] = train['label1']
train.loc[train['RUL'] <= week1, 'label2'] = 2
  1. 除了为训练数据生成标签外,我们还需要为测试数据做同样的事情。训练数据和测试数据有所不同。训练数据有一个表示机器故障时间的结束日期。训练集没有。相反,我们有一个truth数据框,显示了机器实际故障的时间。在我们计算标签之前,需要将testtruth数据集结合起来添加标签列:
rul = pd.DataFrame(test.groupby('engine_id')['cycle'].max())\
                   .reset_index()
rul.columns = ['engine_id', 'max']
truth.columns = ['more']
truth['engine_id'] = truth.index + 1
truth['max'] = rul['max'] + truth['more']
truth.drop('more', axis=1, inplace=True)

test = test.merge(truth, on=['engine_id'], how='left')
test['RUL'] = test['max'] - test['cycle']
test.drop('max', axis=1, inplace=True)

test['label1'] = np.where(test['RUL'] <= week2, 1, 0 )
test['label2'] = test['label1']
test.loc[test['RUL'] <= week1, 'label2'] = 2
  1. 由于列具有不同的最小值和最大值,我们将对数据进行归一化,以防止一个变量掩盖其他变量。为此,我们将使用sklearn库的MinMaxScaler函数。该函数将值转换为01之间的范围。在我们的情况下,由于没有很多异常值,这是一个很好的缩放器。我们将对训练集和测试集执行相同的归一化步骤:
train['cycle_norm'] = train['cycle']
cols_normalize = train.columns.difference(['engine_id','cycle','RUL',
                                           'label1','label2'])
min_max_scaler = preprocessing.MinMaxScaler()
norm_train = \
pd.DataFrame(min_max_scaler.fit_transform(train[cols_normalize]), 
                                          columns=cols_normalize, 
                                          index=train.index)
join = \
train[train.columns.difference(cols_normalize)].join(norm_train)
train = join.reindex(columns = train.columns)

test['cycle_norm'] = test['cycle']
norm_test = \
pd.DataFrame(min_max_scaler.transform(test[cols_normalize]), 
                                      columns=cols_normalize, 
                                      index=test.index)
test_join = \
test[test.columns.difference(cols_normalize)].join(norm_test)
test = test_join.reindex(columns = test.columns)
test = test.reset_index(drop=True)
  1. Keras 中的 LSTM 算法要求数据以序列形式呈现。在我们的变量部分中,我们选择将sequence_length设为100。这是在实验过程中可以调整的超参数之一。由于这是对一段时间内数据的顺序观察,序列长度是我们训练模型所需数据序列的长度。关于序列的最佳长度没有明确的规则。但通过实验,小序列的准确性较低已经显而易见。为了生成我们的序列,我们使用函数以符合 LSTM 算法的预期方式返回顺序数据:
def gen_sequence(id_df, seq_length, seq_cols):
    data_array = id_df[seq_cols].values
    num_elements = data_array.shape[0]
    for start, stop in zip(range(0, num_elements-seq_length), 
                           range(seq_length, num_elements)):
        yield data_array[start:stop, :]

seq_gen = (list(gen_sequence(train[train['engine_id']==engine_id],
                             sequence_length, sequence_cols)) 
           for engine_id in train['engine_id'].unique())

seq_array = np.concatenate(list(seq_gen)).astype(np.float32)
  1. 下一步是构建神经网络。我们构建 LSTM 的第一层。我们从一个序列模型开始。然后给它输入形状和序列的长度。单元告诉我们输出形状的维度,它将传递给下一层。接下来,它返回truefalse。然后我们添加Dropout以增加训练中的随机性,防止过拟合:
nb_features = seq_array.shape[2]
nb_out = label_array.shape[1]

model = Sequential()

model.add(LSTM(input_shape=(sequence_length, nb_features), 
               units=100, return_sequences=True))
model.add(Dropout(0.25))
  1. 接着,我们构建网络的隐藏层。与第一层类似,隐藏层是一个 LSTM 层。然而,与将整个序列状态传递给输出不同,它只传递最后节点的值:
model.add(LSTM(units=50, return_sequences=False))
model.add(Dropout(0.25))
  1. 接着,我们构建网络的输出层。输出层指定输出维度和activation函数。通过这样,我们已经建立了神经网络的形状:
model.add(Dense(units=nb_out, activation='sigmoid'))
  1. 接下来,我们运行compile方法来配置模型进行训练。在其中,我们设置我们正在评估的度量标准。在这种情况下,我们正在使用accuracy作为我们的度量标准。然后,我们定义我们的误差或损失度量。在这个例子中,我们使用binary_crossentropy作为我们的度量标准。最后,我们指定将减少每次迭代中的错误的优化器:
model.compile(loss='binary_crossentropy', optimizer='adam', 
              metrics=['accuracy'])
print(model.summary())
  1. 然后,我们使用fit函数来训练模型。我们的epochs参数意味着数据将通过 10 次。由于随机丢弃,额外的运行将提高准确性。我们使用batch_size200。这意味着模型在更新梯度之前将通过 200 个样本进行训练。接下来,我们使用validation_split将 95% 的数据用于训练模型,将 5% 用于验证模型。最后,我们使用EarlyStopping回调函数在模型停止提高准确性时停止训练:
model.fit(seq_array, label_array, epochs=10, batch_size=200, 
          validation_split=0.05, verbose=1, 
          callbacks = \
          [keras.callbacks.EarlyStopping(monitor='val_loss', 
                                         min_delta=0, patience=0, 
                                         verbose=0, mode='auto')])
  1. 接下来,我们根据我们在训练数据上进行的 95%/5% 分割来评估我们的模型。结果显示我们的模型在评估我们保留的 5% 数据时达到了 87% 的准确性:
scores = model.evaluate(seq_array, label_array, verbose=1, 
                        batch_size=200)
print('Accuracy: {}'.format(scores[1]))
  1. 接下来,我们看一下混淆矩阵,它显示了引擎是否需要维护的正确或错误评估的矩阵:
y_pred = model.predict_classes(seq_array,verbose=1, batch_size=200)
y_true = label_array
print('Confusion matrix\n- x-axis is true labels.\n- y-axis is predicted labels')
cm = confusion_matrix(y_true, y_pred)
cm

我们的混淆矩阵看起来像下面的网格:

实际不需要维护 预测需要维护
实际不需要维护 13911 220
实际需要维护 201 1299
  1. 然后我们计算精度和召回率。由于数据集不平衡,即不需要维护的值远远超过需要维护的值,精度和召回率是评估此算法的最合适的指标:
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
print( 'precision = ', precision, '\n', 'recall = ', recall)
  1. 接下来,我们需要转换数据,使得测试数据与训练数据的顺序数据类型相同。为此,我们执行类似于我们对训练数据所做的数据转换步骤:
seq_array_test_last = [test[test['engine_id']==engine_id]\
[sequence_cols].values[-sequence_length:] for engine_id in \
test['engine_id'].unique() if \
len(test[test['engine_id']==engine_id]) >= sequence_length]

seq_array_test_last = \
np.asarray(seq_array_test_last).astype(np.float32)

y_mask = [len(test[test['engine_id']==engine_id]) >= \
          sequence_length for engine_id in \
          test['engine_id'].unique()]

label_array_test_last = \
test.groupby('engine_id')['label1'].nth(-1)[y_mask].values
label_array_test_last = label_array_test_last.reshape(
    label_array_test_last.shape[0],1).astype(np.float32)
  1. 接下来,我们评估使用训练数据集生成的模型对测试数据集的准确性,以查看模型预测引擎何时需要维护的准确程度:
scores_test = model.evaluate(seq_array_test_last, 
                             label_array_test_last, verbose=2)
print('Accuracy: {}'.format(scores_test[1]))
y_pred_test = model.predict_classes(seq_array_test_last)
y_true_test = label_array_test_last
print('Confusion matrix\n- x-axis is true labels.\n- y-axis is predicted labels')
cm = confusion_matrix(y_true_test, y_pred_test)
print(cm)

pre_score = precision_score(y_true_test, y_pred_test)
recall_test = recall_score(y_true_test, y_pred_test)
f1_test = 2 * (pre_score * recall_test) / (pre_score + recall_test)
print('Precision: ', pre_score, '\n', 'Recall: ', recall_test,
      '\n', 'F1-score:', f1_test )
  1. 现在我们已经有了我们的结果,我们将这些结果与我们的模型一起存储在我们的 MLflow 数据库中:
with mlflow.start_run():
    mlflow.set_experiment("/Shared/experiments/Predictive_Maintenance")
    mlflow.log_param("type", 'LSTM')
    mlflow.log_metric("precision_score", pre_score)
    filename = 'model.sav'
    pickle.dump(model, open(filename, 'wb'))
    mlflow.log_artifact(filename)

工作原理...

LSTM 是一种特殊类型的循环神经网络RNN)。RNN 是一种神经网络架构,通过保持序列在内存中来处理序列数据。相比之下,典型的前馈神经网络不保持序列信息,不允许灵活的输入和输出。递归神经网络使用递归从一个输出调用到其输入,从而生成一个序列。它传递网络在任何给定时间的状态的副本。在我们的案例中,我们使用了两层 RNN。这一额外的层有助于提高准确性。

LSTM 通过使用门控函数解决了传统 RNN 存在的数据消失问题。数据消失问题是指神经网络过早停止训练但不准确的情况。通过使用丢失数据,我们可以帮助解决这个问题。LSTM 通过使用门控函数来实现这一点。

将模型部署到网络服务

模型的部署可以因设备能力而异。一些带有额外计算能力的设备可以直接运行机器学习模型。而其他设备则需要帮助。在本章中,我们将把模型部署到一个简单的网络服务中。使用现代化的云网络应用或 Kubernetes,这些网络服务可以扩展以满足设备群的需求。在下一章中,我们将展示如何在设备上运行模型。

准备工作

到目前为止,在这本书中,我们已经研究了三种不同的机器学习算法,用于解决 NASA Turbofan 故障预测数据集的预测性维护问题。我们记录了结果到 MLflow。我们可以看到,我们的 XGBoost 笔记本在性能上超过了更复杂的神经网络。以下截图显示了 MLflow 结果集,显示了参数及其相关分数。

从这里我们可以下载我们的模型并将其放入我们的 web 服务中。为此,我们将使用 Python Flask web 服务和 Docker 使服务可移植。在开始之前,使用 pip install 安装 python 的 Flask 包。还要在本地计算机上安装 Docker。Docker 是一个工具,允许您构建复杂的部署。

如何做...

在这个项目中,您需要创建三个文件来测试预测器 web 服务和一个文件来扩展到生产环境。首先创建 app.py 作为我们的 web 服务器,requirements.txt 作为依赖项,以及从 mlflow 下载的 XGBoost 模型。这些文件将允许您测试 web 服务。接下来,要投入生产,您需要将应用程序 docker 化。将文件 docker 化后,可以将其部署到诸如基于云的 web 应用程序或 Kubernetes 服务等服务中。这些服务易于扩展,使新的 IoT 设备接入无缝进行。

  1. 文件 app.py 是 Flask 应用程序。为了 web 服务,导入 Flask,用于内存中读取模型的 ospickle,数据操作的 pandas,以及运行我们的模型的 xgboost
from flask import Flask, request, jsonify
import os
import pickle
import pandas as pd
import xgboost as xgb
  1. 接下来是初始化我们的变量。通过将 Flask 应用程序和 XGBoost 模型加载到内存中的函数外部,我们确保它仅在每次 web 服务调用时加载一次,而不是每次。通过这样做,我们极大地提高了 web 服务的速度和效率。我们使用 pickle 来重新加载我们的模型。pickle 可以接受几乎任何 Python 对象并将其写入磁盘。它还可以像我们的情况一样,从磁盘中读取并将其放回内存中:
application = Flask(__name__)
model_filename = os.path.join(os.getcwd(), 'bst.sav')
loaded_model = pickle.load(open(model_filename, "rb"))
  1. 然后我们创建 @application.route 来提供一个 http 端点。POST 方法部分指定它将仅接受 post web 请求。我们还指定 URL 将路由到 /predict。例如,当我们在本地运行时,可以使用 http://localhost:8000/precict URL 来发布我们的 JSON 字符串。然后我们将其转换为 pandas DataFrame,然后 XGBoost 数据矩阵变为调用 predict。然后我们确定它是否大于 .5 或小于并返回结果:
@application.route('/predict', methods=['POST']) 
def predict():
    x_test = pd.DataFrame(request.json)
    y_pred = loaded_model.predict(xgb.DMatrix(x_test))
    y_pred[y_pred > 0.5] = 1
    y_pred[y_pred <= 0.5] = 0
    return int(y_pred[0])
  1. 最后,在任何 Flask 应用程序中执行的最后一件事是调用 application.run 方法。此方法允许我们指定一个主机。在本例中,我们指定了一个特殊的主机 0.0.0.0,告诉 Flask 接受来自其他计算机的请求。接下来,我们指定一个端口。端口可以是任何数字。但是它确实需要与 Dockerfile 中的端口匹配:
if __name__ == '__main__':
    application.run(host='0.0.0.0', port=8000)
  1. 然后我们创建一个要求文件。requirements.txt 文件将安装项目的所有 python 依赖项。docker 将使用此文件安装依赖项:
flask
pandas
xgboost
pickle-mixin
gunicorn
  1. 接着,我们创建 Dockerfile。docker文件允许将预测器部署到 Web 端点。docker 文件的第一行将从 Docker Hub 拉取官方的 Python 3.7.5 镜像。接下来,我们将本地文件夹复制到名为app的 docker 容器中的新文件夹中。然后,我们设置工作目录为app文件夹。接着,我们使用pip install来安装我们在步骤 5中创建的要求文件中的要求。然后,我们暴露端口8000。最后,我们运行启动 Gunicorn 服务器的gunicorn命令:
FROM python:3.7.5
ADD . /app
WORKDIR /app

RUN pip install -r requirements.txt

EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app"]

它的工作原理……

Flask 是一个轻量级的 Web 服务器。我们使用pickle从磁盘上保存的模型来恢复模型。然后,我们创建一个http端点进行调用。

这还不是全部……

现代基于云的 Web 应用程序,例如Azure Web Apps,可以自动将新的 Docker 镜像拉入生产环境。还有许多 DevOps 工具可以拉取镜像,并通过各种测试来运行它们,然后使用 Docker 容器实例或诸如 Kubernetes 之类的 Docker 编排工具部署它们。但要让它们这样做,首先必须将它们放入诸如Azure Container RegistryDocker Hub之类的容器注册表中。为此,我们需要执行几个步骤。首先,我们将构建我们的容器。接下来,我们可以运行我们的容器以确保它工作正常。然后,我们登录到我们的容器注册表服务并将容器推送到其中。详细步骤如下:

  1. 首先,我们构建容器。为此,我们导航到包含 docker 文件的文件夹,并运行 docker build。我们将使用-t命令标记它为ch4。然后,我们使用句点.指定 docker 文件位于本地文件夹中:
docker build -t ch4 .
  1. 现在我们已经构建了一个 Docker 镜像,接下来我们将基于该镜像运行容器,使用docker run命令。我们将使用-it交互命令,以便能够查看服务器的任何输出。我们还将使用-pport命令指定将 Docker 容器的内部端口8000映射到外部端口8000
docker run -it -p 8000:8000 ch4
  1. 然后,我们需要将容器放入能够被我们计算资源访问的地方。为此,首先注册 Docker Registry 服务,如 Docker Hub 或 Azure Container Registry。然后创建一个新的仓库。仓库提供者将为该仓库提供一个路径。

  2. 接下来是登录到您的容器注册表服务,标记容器并推送容器。请记住用注册表服务提供的注册名或路径替换[Your container path]

docker login

docker tag ch4 [Your container path]:v1
docker push [Your container path]:v1

然后,您可以使用启用了 docker 的云技术将该预测器服务推送到生产环境。然后,您的设备可以将其传感器读数发送到 Web 服务,并通过云到设备消息接收设备是否需要维护的反馈。

第五章:异常检测

设备的预测/处方 AI 生命周期始于数据收集设计。数据分析包括相关性和变异等因素。然后开始制造设备。除了少量样品设备外,通常没有导致机器学习模型的设备故障。为了弥补这一点,大多数制造商使用工作周期阈值来确定设备处于良好状态还是坏状态。这些工作周期标准可能是设备运行过热或传感器上设置了警报的任意值。但是数据很快需要更高级的分析。庞大的数据量可能对个人来说是令人生畏的。分析师需要查看数百万条记录,以找到大海捞针的情况。使用分析师中介方法结合异常检测可以有效帮助发现设备问题。异常检测通过统计、非监督或监督的机器学习技术来进行。换句话说,通常分析师首先查看正在检查的单个数据点,以检查是否存在尖峰和波谷等情况。然后,多个数据点被引入无监督学习模型,该模型对数据进行聚类,使数据科学家能够看到某组数值或模式与其他组的数值不同。最后,在发生足够的设备故障之后,分析师可以使用预测性维护所使用的相同类型的机器学习。一些机器学习算法,如孤立森林,更适合用于异常检测,但其原则是相同的。

异常检测可以在收集足够的数据进行监督学习之前进行,也可以作为连续监控解决方案的一部分。例如,异常检测可以警示您关于不同工厂的生产问题。当电气工程师将物理设计交给工厂时,他们进行物料清单(BOM)优化。简而言之,他们修改设计,使其更易组装或更具成本效益。大多数物理设备的生产周期为十年。在此期间,可能不再有设备初次制造时存在的零部件,这意味着需要对 BOM 进行更改。转向新的制造商也将改变设计,因为他们会进行自己的 BOM 优化。异常检测可以帮助精确定位在您的设备群中出现的新问题。

有多种方法可以观察异常检测。在第三章,物联网的机器学习,在用异常检测分析化学传感器的配方中,我们使用了 K 均值,一种流行的异常检测算法,来确定食物的化学特征是否与空气不同。这只是一种异常检测的类型。有许多不同类型的异常检测。有些是针对特定机器的,观察一段时间内是否异常。其他异常检测算法则通过监督学习来观察设备的正常和异常行为。有些设备受其本地环境或季节性影响。最后,在本章中,我们将讨论将异常检测部署到边缘设备上。

本章包含以下配方:

  • 在 Raspberry Pi 和 Sense HAT 上使用 Z-Spikes

  • 使用自编码器检测标记数据中的异常

  • 对无标签数据使用孤立森林

  • 使用 Luminol 检测时间序列异常

  • 检测季节调整的异常

  • 使用流式分析检测尖峰

  • 在边缘检测异常

在 Raspberry Pi 和 Sense HAT 上使用 Z-Spikes

对个别设备的突然变化可能需要警报。物联网设备经常受到移动和天气影响。它们可能受到一天中的时间或一年中的季节的影响。设备群可能分布在全球各地。试图在整个设备群中获得清晰的洞察力可能是具有挑战性的。使用整个设备群的机器学习算法使我们能够单独处理每个设备。

Z-Spikes 的用例可以是电池的突然放电或温度的突然增加。人们使用 Z-Spikes 来判断是否被震动或突然振动了。Z-Spikes 可以用于泵,以查看是否存在堵塞。因为 Z-Spikes 在非同质环境中表现出色,它们经常成为边缘部署的一个很好的选择。

准备工作

在这个配方中,我们将在一个带有 Sense HAT 的 Raspberry Pi 上部署 Z-Spikes。硬件本身是一个相当常见的开发板和传感器设置,适合学习物联网的人使用。事实上,学生们可以将他们的代码发送到国际空间站上,以在他们的 Raspberry Pi 和 Sense HAT 上运行。如果你没有这个设备,GitHub 仓库中有一个模拟该设备的替代代码。

一旦您启动了 Raspberry Pi 并连接了 Sense HAT,您需要安装 SciPy。在 Python 中,通常可以使用pip安装所需的一切,但在这种情况下,您需要通过 Linux 操作系统来安装它。要做到这一点,请在终端窗口中运行以下命令:

sudo apt update
apt-cache show python3-scipy
sudo apt install -y python3-scipy

您将需要pip安装numpykafkasense_hat。您还需要在 PC 上设置 Kafka。在设置 IoT 和 AI 环境设置 Kafka配方中有指南,请勿尝试在树莓派上设置 Kafka,因为其需要太多内存。请在 PC 上设置。

对于树莓派,您需要连接显示器、键盘和鼠标。开发者工具菜单中有 Python 编辑器。您还需要知道 Kafka 服务的 IP 地址。

如何做...

此配方的步骤如下:

  1. 导入库:
from scipy import stats
import numpy as np
from sense_hat import SenseHat
import json
from kafka import KafkaProducer
import time
  1. 等待 Sense HAT 与操作系统注册:
time.sleep(60)
  1. 初始化变量:
device= "Pi1"
server = "[the address of the kafka server]:9092"
producer = KafkaProducer(bootstrap_servers=server)
sense = SenseHat()
sense.set_imu_config(False, True, True) 
gyro = []
accel = [] 
  1. 创建一个 Z-score 辅助函数:
def zscore(data):
    return np.abs(stats.zscore(np.array(data)))[0]
  1. 创建一个sendAlert辅助函数:
def sendAlert(lastestGyro,latestAccel):
    alert = {'Gyro':lastestGyro, 'Accel':latestAccel}
    message = json.dumps(alert)
    producer.send(device+'alerts', key=bytes("alert", 
                                             encoding='utf-8'),
                  value=bytes(message, encoding='utf-8'))

  1. 创建一个combined_value辅助函数:
def combined_value(data):
    return float(data['x'])+float(data['y'])+float(data['z'])
  1. 运行main函数:
if __name__ == '__main__': 
    x = 0
    while True:
        gyro.insert(0,sense.gyro_raw)
        accel.insert(0,sense.accel_raw)
        if x > 1000: 
            gyro.pop() 
            accel.pop() 
        time.sleep(1)
        x = x + 1
        if x > 120:
            if zscore(gyro) > 4 or zscore(accel) > 4:
                sendAlert(gyro[0],accel[0]) 

它是如何工作的...

此算法正在检查最后一条记录是否比前 1,000 个值的 4 个标准偏差(σ)更多。 应该在每 15,787 次读数或每 4 小时中有一个异常。如果我们将其更改为 4.5,则每 40 小时有一次异常。

我们导入scipy进行 Z-score 评估和numpy进行数据操作。然后我们将脚本添加到树莓派的启动中,这样每次有电源重置时脚本都会自动启动。设备需要等待外围设备初始化,如 Sense HAT。60 秒的延迟允许操作系统在尝试初始化 Sense HAT 之前感知 Sense HAT。然后我们初始化变量。这些变量是设备名称、Kafka 服务器的 IP 地址和 Sense HAT。然后我们启用 Sense HAT 的内部测量单元IMUs)。我们禁用罗盘并启用陀螺仪和加速度计。最后,我们创建两个数组来存放数据。接下来,我们创建一个 Z-score 辅助函数,可以输入一个值数组并返回 Z-scores。接下来,我们需要一个函数来发送警报。sense.gyro_raw函数获取最新的陀螺仪和加速度计读数并将它们放入 Python 对象中,然后转换为 JSON 格式。然后我们创建一个 UTF-8 字节编码的键值。类似地,我们编码消息负载。接下来,我们创建一个 Kafka 主题名称。然后,我们将键和消息发送到主题。然后,我们在__main__下检查是否从命令行运行当前文件。如果是,我们将计数器x设置为0。然后我们开始一个无限循环。然后我们开始输入陀螺仪和加速度计数据。然后我们检查数组中是否有 1,000 个元素。如果是这样,我们会移除数组中的最后一个值,以保持数组的小型化。然后我们增加计数器以累积 2 分钟的数据。最后,我们检查是否超过了来自我们数组的 1,000 个值的 4 个标准偏差;如果是,我们发送警报。

虽然这是查看设备的一个很好的方式,但我们可能希望在整个设备群中进行异常检测。在接下来的配方中,我们将创建一个消息发送和接收器。如果我们要在这个配方中执行此操作,我们将创建一个 Kafka 生产者消息,以在循环的每次迭代中发送数据。

使用自编码器来检测标记数据中的异常

如果您有标记数据,您可以训练一个模型来检测数据是否正常或异常。例如,读取电动机的电流可以显示电动机由于如轴承失效或其他硬件失效而受到额外负载的情况。在物联网中,异常可能是先前已知的现象或以前未见过的新事件。顾名思义,自编码器接收数据并将其编码为输出。通过异常检测,我们可以看到模型能否确定数据是否非异常。在本配方中,我们将使用一个名为pyod的 Python 对象检测库。

准备工作

在本配方中,我们将使用从 Sense HAT 运动传感器收集的数据。本章最后的配方展示了如何生成此数据集。我们还将这个带标签的数据集放在了本书的 GitHub 存储库中。我们将使用一个名为pyodPython 异常检测的 Python 异常检测框架。它包装了 TensorFlow 并执行各种机器学习算法,如自编码器和孤立森林。

如何操作...

此配方的步骤如下:

  1. 导入库:
from pyod.models.auto_encoder import AutoEncoder
from pyod.utils.data import generate_data
from pyod.utils.data import evaluate_print
import numpy as np
import pickle
  1. 使用 NumPy 数组将文本文件加载到我们的笔记本中:
X_train = np.loadtxt('X_train.txt', dtype=float)
y_train = np.loadtxt('y_train.txt', dtype=float)
X_test = np.loadtxt('X_test.txt', dtype=float)
y_test = np.loadtxt('y_test.txt', dtype=float)
  1. 使用自编码器算法来修复模型到数据集:
clf = AutoEncoder(epochs=30)
clf.fit(X_train)
  1. 获取预测分数:
y_test_pred = clf.predict(X_test) # outlier labels (0 or 1)
y_test_scores = clf.decision_function(X_test) # outlier scores
evaluate_print('AutoEncoder', y_test, y_test_scores)
  1. 保存模型:
pickle.dump( clf, open( "autoencoder.p", "wb" ) )

工作原理...

首先,我们导入pyod,我们的 Python 对象检测库。然后我们导入numpy进行数据操作和pickle用于保存我们的模型。接下来,我们使用numpy加载我们的数据。然后我们训练我们的模型并获得预测分数。最后,我们保存我们的模型。

自编码器将数据作为输入并通过较小的隐藏层减少节点数量,迫使其降低维度。自编码器的目标输出是输入。这允许我们使用机器学习来训练模型,以识别非异常情况。然后,我们可以确定一个值与训练模型相比的差距有多大。这些值将是异常的。以下图表概念性地展示了数据如何编码为一组输入。然后,在隐藏层中降低其维度,最后输出到一组较大的输出中:

还有更多...

在训练完我们的模型之后,我们需要知道在什么级别发送警报。在训练时,设置污染度(参见以下代码)确定触发警报功能所需的数据中异常值的比例:

AutoEncoder(epochs=30, contamination=0.2)

我们还可以更改正则化器,如下例所示。正则化器用于平衡偏差和方差,以防止过度拟合和欠拟合:

AutoEncoder(epochs=30, l2_regularizer=0.2)

我们还可以更改神经元的数量、损失函数或优化器。这通常被称为在数据科学中改变或调整超参数。调整超参数允许我们影响成功的指标,从而改善模型。

使用孤立森林处理无标签数据集

孤立森林是一种流行的异常检测机器学习算法。孤立森林可以帮助处理有重叠值的复杂数据模型。孤立森林是一种集成回归。与其他机器学习算法使用聚类或基于距离的算法不同,它将离群数据点与正常数据点分开。它通过构建决策树并计算基于节点遍历的分数来实现这一点。换句话说,它计算它遍历的节点数来确定结果。模型训练的数据越多,孤立森林需要遍历的节点数就越多。

与上一篇介绍类似,我们将使用 pyod 轻松训练一个模型。我们将使用 GitHub 仓库中的 Sense HAT 数据集。

准备就绪

如果您已经完成了关于自动编码器的前一篇介绍,那么您所需的一切都已准备就绪。在这个示例中,我们使用 pyod 进行目标检测库。本书的 GitHub 仓库中包含训练数据集和测试数据集。

如何执行……

本篇的步骤如下:

  1. 导入库:
from pyod.models.iforest import IForest
from pyod.utils.data import generate_data
from pyod.utils.data import evaluate_print
import numpy as np
import pickle
  1. 上传数据:
X_train = np.loadtxt('X_train.txt', dtype=float)
y_train = np.loadtxt('y_train.txt', dtype=float)
X_test = np.loadtxt('X_test.txt', dtype=float)
y_test = np.loadtxt('y_test.txt', dtype=float)
  1. 训练模型:
clf = IForest()
clf.fit(X_train)
  1. 对测试数据进行评估:
y_test_pred = clf.predict(X_test) # outlier labels (0 or 1)
y_test_scores = clf.decision_function(X_test) 
print(y_test_pred)

# evaluate and print the results
print("\nOn Test Data:")
evaluate_print('IForest', y_test, y_test_scores)
  1. 保存模型:
pickle.dump( clf, open( "IForest.p", "wb" ) )

工作原理……

首先,我们导入 pyod。然后导入 numpy 进行数据处理,以及 pickle 用于保存我们的模型。接下来,进行孤立森林训练。然后我们评估我们的结果。我们得到两种不同类型的结果:一种是用 10 表示是否正常或异常,另一种给出测试的分数。最后,保存我们的模型。

孤立森林算法使用基于树的方法对数据进行分割。数据越密集,分割得越多。孤立森林算法通过计算需要遍历的分割数量来查找不属于密集分割区域的数据。

还有更多内容……

异常检测是一种分析技术,可通过可视化帮助我们确定要使用的超参数和算法。scikit-learn 在其网站上有一个示例,展示了如何做到这一点(scikit-learn.org/stable/auto_examples/miscellaneous/plot_anomaly_comparison.html)。这本书的 GitHub 存储库中有参考资料。接下来的图表是使用多种算法和设置进行异常检测的示例。在异常检测中不存在绝对正确的答案,只有适合手头问题的最佳解决方案:

使用 Luminol 检测时间序列异常

Luminol 是 LinkedIn 发布的时间序列异常检测算法。它使用位图来检查在数据集中具有稳健性的检测策略,往往会漂移。它还非常轻量级,可以处理大量数据。

在这个例子中,我们将使用芝加哥市的公共可访问的物联网数据集。芝加哥市有物联网传感器测量其湖泊的水质。因为数据集在进行异常检测之前需要进行一些整理,我们将使用prepdata.py文件从一个湖泊中提取一个数据点。

准备就绪

为了准备这个食谱,您需要从这本书的 GitHub 存储库下载 CSV 文件。接下来,您需要安装luminol

pip install luminol

如何操作...

此食谱涉及的步骤如下:

  1. 使用 prepdata.py 准备数据:
import pandas as pd 

df = pd.read_csv('Beach_Water_Quality_-_Automated_Sensors.csv', 
                  header=0)

df = df[df['Beach Name'] == 'Rainbow Beach']
df = df[df['Water Temperature'] > -100]
df = df[df['Wave Period'] > -100]
df['Measurement Timestamp'] = pd.to_datetime(df['Measurement
                                                 Timestamp'])

Turbidity = df[['Measurement Timestamp', 'Turbidity']]
Turbidity.to_csv('Turbidity.csv', index=False, header=False)
  1. Luminol.py中导入库:
from luminol.anomaly_detector import AnomalyDetector
import time
  1. 执行异常检测:
my_detector = AnomalyDetector('Turbidity.csv')
score = my_detector.get_all_scores()
  1. 打印异常:
for (timestamp, value) in score.iteritems():
    t_str = time.strftime('%y-%m-%d %H:%M:%S', 
                          time.localtime(timestamp))
    if value > 0:
        print(f'{t_str}, {value}')

工作原理...

dataprep Python 库中,您只需导入pandas,这样我们就可以将 CSV 文件转换为pandas DataFrame。一旦我们有了pandas DataFrame,我们将会在Rainbow Beach上进行过滤(在我们的情况下,我们只关注Rainbow Beach)。然后,我们将剔除水温低于-100 度的异常数据。接着,我们将把time字符串转换成pandas可以读取的格式。我们这样做是为了输出时采用标准的时间序列格式。然后,我们只选择需要分析的两列,Measurement TimestampTurbidity。最后,我们将文件以 CSV 格式保存。

接下来,我们创建一个 Luminol 文件。从这里开始,我们使用pip安装luminoltime。然后,我们在 CSV 文件上使用异常检测器并返回所有分数。最后,如果我们的分数项的值大于 0,则返回分数。换句话说,只有在存在异常时才返回分数。

还有更多...

除了异常检测外,Luminol 还可以进行相关性分析。这有助于分析师确定两个时间序列数据集是否彼此相关。例如,芝加哥市的数据集测量了其湖泊中水质的各个方面。我们可以比较不同湖泊之间是否存在同时发生的共同影响。

检测季节性调整后的异常

如果设备在户外,温度传感器的数据可能会在一天中趋于上升。同样地,户外设备的内部温度在冬季可能会较低。并非所有设备都受季节性影响,但对于受影响的设备,选择处理季节性和趋势的算法至关重要。根据 Twitter 的数据科学家在研究论文《云中的自动异常检测通过统计学习》中指出,季节性 ESD是一种机器学习算法,通过考虑季节性和趋势来发现异常。

对于本示例,我们将使用芝加哥市湖泊水质数据集。我们将导入我们在使用 Luminol 检测时间序列异常示例中准备的数据文件。

准备工作

为了准备好,您将需要 Seasonal ESD 库。您可以通过以下pip命令简单安装:

pip install sesd

本书的 GitHub 仓库中可以找到数据集。

如何执行...

执行这个示例的步骤如下:

  1. 导入库:
import pandas as pd 
import sesd
import numpy as np
  1. 导入和操作数据:
df = pd.read_csv('Beach_Water_Quality_-_Automated_Sensors.csv',
                  header=0)
df = df[df['Beach Name'] == 'Rainbow Beach']
df = df[df['Water Temperature'] > -100]
df = df[df['Wave Period'] > -100]
waveheight = df[['Wave Height']].to_numpy()
  1. 执行异常检测:
outliers_indices = sesd.seasonal_esd(waveheight, hybrid=True,
                                     max_anomalies=2)
  1. 输出结果:
for idx in outliers_indices:
    print("Anomaly index: {}, anomaly value: {}"\
           .format(idx, waveheight[idx]))

它是如何工作的...

在这个示例中,我们首先导入了numpypandas用于数据操作。接着,我们导入了我们的异常检测包sesd。然后,我们准备好了机器学习的原始数据。我们通过移除明显存在问题的数据(比如传感器工作不正常的数据)来完成这一步骤。接下来,我们将数据过滤到一个列中。然后,我们将该列数据输入季节性 ESD 算法中。

与第一个示例中的 Z-score 算法类似,本示例使用在线方法。在进行异常检测之前,它使用局部加权回归分解的季节性和趋势分解(STL)作为预处理步骤。数据源可能具有趋势和季节性,如下图所示:

分解的目的是让你能够独立查看趋势和季节性(如下图所示的趋势图)。这有助于确保数据不受季节性影响:

季节性 ESD 算法比 Z-score 算法更复杂。例如,Z-score 算法可能会在户外设备中显示错误的阳性结果。

使用流式分析检测尖峰

流分析是一个工具,它使用 SQL 接口将 IoT Hub 连接到 Azure 内部的其他资源。流分析将数据从 IoT Hub 移动到 Cosmos DB、存储块、无服务器函数或多个其他可扩展的选项。流分析内置了一些函数,您还可以使用 JavaScript 创建更多函数;异常检测就是其中之一。在本例中,我们将使用树莓派将陀螺仪和加速度数据流式传输到 IoT Hub。然后,我们将连接流分析,并使用其 SQL 接口仅输出异常结果。

准备工作

对于这个实验,您将需要 IoT Hub。接下来,您需要创建一个流分析作业。为此,您将进入 Azure 门户,并通过“创建新资源”向导创建一个新的流分析作业。创建新的流分析作业后,您将看到“概述”页面上有三个主要组件。这些是输入、输出和查询。输入如其名称所示,是您想要输入的流;在我们的情况下,我们正在输入 IoT Hub。要连接到 IoT Hub,您需要点击“输入”,然后选择 IoT Hub 的输入类型,然后选择您为此配方创建的 IoT Hub 实例。接下来,您可以创建一个输出。这可以是诸如 Cosmos DB 之类的数据库,或者是函数应用程序,以便通过任何数量的消息系统发送警报。为了简单起见,本配方不会指定输出。为测试目的,您可以在流分析查询编辑器中查看输出。

如何操作…

此配方的步骤如下:

  1. 导入库:
#device.py

import time
from azure.iot.device import IoTHubDeviceClient, Message
from sense_hat import SenseHat
import json
  1. 声明变量:
client = IoTHubDeviceClient.create_from_connection_string("your device key here")
sense = SenseHat()
sense.set_imu_config(True, True, True) 
  1. 获取连接的设备值:
def combined_value(data):
    return float(data['x'])+float(data['y'])+float(data['z'])
  1. 获取并发送数据:
while True:
    gyro = combined_value(sense.gyro_raw)
    accel = combined_value(sense.accel_raw)

    msg_txt_formatted = msg.format(gyro=gyro, accel=accel)
    message = Message(msg_txt_formatted)
    client.send_message(message)

    time.sleep(1)
  1. 创建使用AnomalyDetection_SpikeAndDip算法检测异常的 SQL 查询:
    SELECT
        EVENTENQUEUEDUTCTIME AS time,
        CAST(gyro AS float) AS gyro,
        AnomalyDetection_SpikeAndDip(CAST(gyro AS float), 95, 120, 'spikesanddips')
            OVER(LIMIT DURATION(second, 120)) AS SpikeAndDipScores
    INTO output
    FROM tempin

工作原理…

要在树莓派上导入库,您需要登录树莓派并使用pip安装azure-iot-deviceSenseHat。接下来,您需要进入该设备并创建一个名为device.py的文件。然后,您将导入time、Azure IoT Hub、Sense HAT 和json库。接下来,您需要进入 IoT Hub,并通过门户创建设备,获取您的连接字符串,并将其输入到标有“在此处输入您的设备密钥”的位置。然后,初始化SenseHat并将内部测量单位设置为True,初始化我们的传感器。然后创建一个帮助函数,将我们的xyz数据结合起来。接下来,从传感器获取数据并将其发送到 IoT Hub。最后,在再次发送数据之前等待一秒钟。

接下来,进入您已设置的流分析作业,并单击“编辑查询”。从这里,创建一个公共表达式。公共表达式允许您简化复杂的查询。然后使用内置的异常检测尖峰和低谷算法,在 120 秒的窗口内执行。快速编辑器允许您测试实时数据流,并查看异常检测器给出的异常或非异常结果分数。

在边缘上检测异常:

在这个最终的教程中,我们将使用树莓派上的SenseHat来收集数据,在我们的本地计算机上训练这些数据,然后在设备上部署机器学习模型。为了避免冗余,在记录数据之后,您需要在本章前面的自编码器或孤立森林中运行任何一个配方。

人们在物联网中使用运动传感器来确保集装箱安全地运输到船上。例如,证明一个集装箱在特定港口被丢弃会有助于保险理赔。它们还用于保障工人的安全,以便检测跌倒或工人不安全行为。它们还用于设备在发生故障时产生振动的情况。例如,洗衣机、风力发电机和水泥搅拌机等设备。

在数据收集阶段,您需要安全地模拟跌倒或不安全工作。您还可以在不平衡的洗衣机上放置传感器。GitHub 仓库中的数据包含正常工作和来自跳舞的数据,我们称之为异常

准备工作:

为了做好准备,您将需要一台带有 Sense HAT 的树莓派。您需要一种从树莓派获取数据的方式。您可以通过启用SSH或使用 USB 驱动器来完成。在树莓派上,您需要使用pip安装sense_hatnumpy

如何操作...

该教程的步骤如下:

  1. 导入库:
#Gather.py

import numpy as np
from sense_hat import SenseHat
import json
import time
  1. 初始化变量:
sense = SenseHat()
sense.set_imu_config(True, True, True) 
readings = 1000
gyro,accel = sense.gyro_raw, sense.accel_raw
actions = ['normal', 'anomolous']
dat = np.array([gyro['x'], gyro['y'], gyro['z'], accel['x'],
                accel['y'], accel['z']])
x = 1
  1. 等待用户输入开始:
for user_input in actions:
     activity = input('Hit enter to record '+user_input + \
                      ' activity')
  1. 收集数据:
    x = 1
    while x < readings:
        x = x + 1
        time.sleep(0.1)
        gyro,accel = sense.gyro_raw, sense.accel_raw
        dat = np.vstack([dat, [[gyro['x'], gyro['y'], gyro['z'],
                         accel['x'], accel['y'], accel['z']]]])
        print(readings - x)
  1. 将文件输出到磁盘进行训练:
X_test = np.concatenate((np.full(800,0), np.full(800,1)), axis=0) 
y_test = np.concatenate((np.full(200,0), np.full(200,1)), axis=0) 
X_train = np.concatenate((dat[0:800,:],dat[1000:1800]))
y_train = np.concatenate((dat[800:1000],dat[1800:2000]))

np.savetxt('y_train.txt', y_train,delimiter=' ', fmt="%10.8f")
np.savetxt('y_test.txt',y_test, delimiter=' ',fmt="%10.8f") 
np.savetxt('X_train.txt', X_train,delimiter=' ', fmt="%10.8f")
np.savetxt('X_test.txt',X_test, delimiter=' ',fmt="%10.8f") 
  1. 通过使用便携式存储设备,从树莓派复制文件到本地计算机。

  2. 使用孤立森林配方训练孤立森林,并输出pickle文件。

  3. 复制iforrest.p文件到树莓派,并创建一个名为AnomalyDetection.py的文件。

  4. 导入库:

#AnomalyDetection.py

import numpy as np
from sense_hat import SenseHat
from pyod.models.iforest import IForest
from pyod.utils.data import generate_data
from pyod.utils.data import evaluate_print
import pickle
sense = SenseHat()
  1. 加载机器学习文件:
clf = pickle.load( open( "IForrest.p", "rb" ) )
  1. 为 LED 创建输出:
def transform(arr):
    ret = []
    for z in arr:
        for a in z:
            ret.append(a)
    return ret

O = (10, 10, 10) # Black
X = (255, 0 ,0) # red

alert = transform([
        [X, X, O, O, O, O, X, X],
        [X, X, X, O, O, X, X, X],
        [O, X, X, X, X, X, X, O],
        [O, O, X, X, X, X, O, O],
        [O, O, X, X, X, X, O, O],
        [O, X, X, X, X, X, X, O],
        [X, X, X, O, O, X, X, X],
        [X, X, O, O, O, O, X, X]
        ])

clear = transform([
        [O, O, O, O, O, O, O, O],
        [O, O, O, O, O, O, O, O],
        [O, O, O, O, O, O, O, O],
        [O, O, O, O, O, O, O, O],
        [O, O, O, O, O, O, O, O],
        [O, O, O, O, O, O, O, O],
        [O, O, O, O, O, O, O, O],
        [O, O, O, O, O, O, O, O]
        ])
  1. 预测异常:
while True:
    dat = np.array([gyro['x'], gyro['y'], gyro['z'], accel['x'],
                    accel['y'], accel['z']])
    pred = clf.predict(dat)
    if pred[0] == 1:
        sense.set_pixels(alert)
    else:
        sense.set_pixels(clear)

    time.sleep(0.1)

工作原理...

我们创建两个文件 – 一个收集信息(称为Gather.py)和另一个在设备上检测异常(称为AnomalyDetection.py)。在Gather.py文件中,我们导入类,初始化SenseHat,设置一个变量来收集读数的数量,获取陀螺仪和加速度计读数,创建一个正常的匿名字符串数组,并设置初始的陀螺仪和传感器评分。然后,我们循环执行操作,并告诉用户在想要记录正常问候时按Enter,然后在想要记录异常读数时再次按Enter。从那里开始,我们收集数据并向用户反馈,告诉他们他们将收集多少个数据点。此时,您应该以正常的使用方式使用设备,例如通过将其靠近身体来检测跌落。然后,在下一个异常读数循环中,您会放下设备。最后,我们创建用于机器学习模型的训练集和测试集。然后,我们需要将数据文件复制到本地计算机,并像在本章早期使用孤立森林时一样执行分析。然后,我们将得到一个将在AnomalyDetection.py文件中使用的pickle文件。

从这里开始,我们需要创建一个名为AnomalyDetection.py的文件,该文件将在我们的树莓派上使用。然后我们加载我们的pickle文件,这是我们的机器学习模型。从这里开始,我们将创建alert和非alertclear)变量,这些变量可以在 sense set 的 LED 显示上切换。最后,我们运行循环,如果预测设备行为异常,我们在 sense set 上显示一个alert信号;否则,我们显示一个clear信号。

第六章:计算机视觉

近年来,计算机视觉取得了长足的进步。与许多其他需要复杂分析的机器学习形式不同,大多数计算机视觉问题来自简单的 RGB 摄像头。诸如 Keras 和 OpenCV 之类的机器学习框架内置了标准和高精度的神经网络。几年前,在 Python 中实现面部识别神经网络,例如,是复杂的,并且在高速设备上使用 C++或 CUDA 设置更是挑战。如今,这一过程比以往任何时候都更加简单和可访问。在本章中,我们将讨论在云中实现计算机视觉,以及在 NVIDIA Jetson Nano 等边缘设备上的应用。

我们将在本章节中涵盖以下的配方:

  • 通过 OpenCV 连接摄像头

  • 使用 Microsoft 的自定义视觉来训练和标记您的图像

  • 使用深度神经网络和 Caffe 检测面部

  • 在树莓派 4 上使用 YOLO 检测物体

  • 在 NVIDIA Jetson Nano 上使用 GPU 检测物体

  • 使用 PyTorch 在 GPU 上训练视觉

通过 OpenCV 连接摄像头

通过 OpenCV 连接摄像头非常简单。问题通常出现在安装 OpenCV 上。在台式电脑上,OpenCV 安装很容易,但在资源受限的设备上,可能需要额外的工作。例如,在树莓派 3 上,您可能需要启用交换空间。这允许系统将 SD 卡用作临时内存存储。根据设备的不同,有各种在线说明可以帮助您在具有挑战性的设备上安装 OpenCV。

在这个配方中,我们将 OpenCV 连接到树莓派 Zero 上的摄像头应用程序,但如果您没有硬件,您也可以在 PC 上运行代码。在接下来的配方中,我们将假设您已掌握这些知识,并简略解释正在进行的事情。

准备工作

从编码的角度来看,使用 OpenCV 可以屏蔽硬件的差异。无论您使用的是$5 的树莓派 Zero 还是$120 的 LattePanda,这个配方所需的唯一物品是一台电脑和一个摄像头。大多数笔记本电脑都有内置摄像头,但对于台式电脑或者如树莓派或 LattePanda 这样的单板计算机(SBC),您将需要一个 USB 网络摄像头。

接下来,您需要安装 OpenCV。如前所述,有多种方法可以在受限设备上获取 OpenCV。这些方法都是根据具体设备的特性而定。在我们的情况中,我们将在树莓派 Zero 上安装 PiCam 模块。以下是 PiCam 模块的参考图像:

要将 PiCam 添加到 Pi Zero 上,您只需从连接器中拉出黑色标签,插入 PiCam 模块,然后将标签推入,如下图所示:

从这里开始,您需要在树莓派上启用摄像头。您需要将显示器、键盘和鼠标插入树莓派中。然后,通过执行以下命令确保您的系统是最新的:

sudo apt-get update
sudo apt-get upgrade

然后,您可以通过进入 Rasp Config 菜单来启用摄像头。在终端中,键入以下内容:

sudo raspi-config

从这里选择 Camera 然后启用它:

有三个不同的库可以使用 pip 安装:opencv-contrib-python 包含所有的 OpenCV 扩展功能,opencv-python 提供更快但功能更少的特性列表,最后 opencv-cython 提供更快的 Python 使用体验。

对于本书,我建议执行以下命令:

pip install open-contrib-python

如何操作...

该示例的步骤如下:

  1. 导入 OpenCV:
import cv2
  1. 选择摄像头:
cap = cv2.VideoCapture(0)
  1. 检查摄像头是否可用:
if not (cap.isOpened()):
    print('Could not open video device')

  1. 捕获、保存并显示摄像头帧:
x = 0
while(True):
    ret, frame = cap.read()
    cv2.imshow('preview',frame)
    time.sleep(1)
    cv2.imwrite(f'./images/cap{x}.jpg', frame) 
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
  1. 释放摄像头:
cap.release()
cv2.destroyAllWindows()

工作原理...

在这个示例中,首先我们导入 OpenCV。然后我们选择它找到的第一个摄像头(camera(0))。如果我们要找到第二个找到的摄像头,那么我们会增加摄像头号码(camera(1))。接下来,我们检查摄像头是否可用。摄像头可能不可用的原因有几种。首先,它可能被其他东西打开了。例如,您可以在不同的应用程序中打开摄像头以查看其是否工作,这将阻止 Python 应用程序检测和连接到摄像头。另一个常见的问题是,代码中释放摄像头的步骤未执行,需要重新设置摄像头。接下来,我们捕获视频帧并在屏幕上显示,直到有人按下 Q 键。最后,在有人退出应用程序后,我们释放摄像头并关闭打开的窗口。

还有更多内容...

OpenCV 具有许多工具,可以将文本写入屏幕或在识别对象周围绘制边界框。此外,它还能够对 RGB 图像进行降采样或转换为黑白图像。过滤和降采样是机器学习工程师在允许它们高效运行的受限设备上执行的技术。

使用 Microsoft 的自定义视觉来训练和标记您的图像

微软的认知服务为训练图像和部署模型提供了一站式解决方案。首先,它提供了上传图像的方法。然后,它有一个用户界面,可以在图像周围绘制边界框,最后,它允许您部署和公开 API 端点,用于计算机视觉。

准备工作

要使用 Microsoft 的自定义视觉服务,您需要一个 Azure 订阅。然后,您需要启动一个新的自定义视觉项目。有一个免费层用于测试小型模型,有一个付费层用于更大的模型和规模化服务模型。在 Azure 门户中创建自定义视觉项目后,您将在资源组中看到两个新项目。第一个用于训练,第二个名称后附有-prediction标签,将用于预测。

然后,您将需要所分类物体的图像。在我们的情况下,我们正在识别含有铅和致癌物暴露的饮料。如果您完成了上一个配方,您将会有一个摄像机以每秒 1 次的间隔捕捉图像。要在认知服务中创建一个对象检测模型,您需要至少 30 张您要分类的每样东西的图像。更多图像将提高准确性。为了获得良好的准确性,您应该变化光线、背景、角度、大小和类型,并使用单独和组合的物体图像。

你还需要安装 Microsoft 的认知服务计算机视觉 Python 包。为此,请执行以下命令:

pip3 install azure-cognitiveservices-vision-customvision

如何做...

本配方的步骤如下:

  1. 转到您创建自定义视觉项目的 Azure 门户。

  2. 将浏览器导航到customvision.ai,并使用您的 Azure 凭据登录。这将带您到项目页面。有一些示例项目,但您会想创建自己的项目。点击“新项目”磁贴。然后,填写创建新项目向导。对于本配方,我们拍摄食品和饮料项目的照片,以便我们可以在工作场所安全计算机视觉项目中使用它们。这种计算机视觉可以在电子店中使用,在那里人们在含有铅或致癌物质等污染物的环境中进食。

  3. 在项目的主页上,您会看到一个“标签”按钮。点击“未标记”选项(如下图所示),您将看到您上传的所有图像:

  1. 点击图像,使用工具在图像周围绘制一个边界框。从这里开始,你可以在图像周围绘制边界框并打标签:

  1. 接下来,点击绿色的“训练”按钮来训练模型:

  1. 点击“训练”后,它将开始训练一个模型。这可能需要相当长的时间。一旦完成,点击迭代,然后点击“预测 URL”按钮:

这将为您提供一个窗口,其中包含发送图像到对象检测服务所需的一切。

测试模型的代码如下:

import requests
file = open('images/drink1/cap0.jpg', 'rb')
url = 'Your iteration url goes here'
headers = {'Prediction-Key': 'key from the prediction url', \
           'Content-Type':'application/octet-stream'}
files = {'file': file}
r = requests.post(url, data=file, headers=headers)
json_data = r.json()
print(json_data)

工作原理如下...

认知服务使用标记的图像创建一个模型,以在更大的图像中查找这些图像。随着图像数量的增加,准确性也会提高。然而,随着准确性达到收敛点或者俗称的不再改善,会有一点。要找到这个收敛点,添加和标记更多图像,直到精度、召回率和 mAP 的迭代指标不再改善为止。下面的自定义视觉仪表板显示了我们用来显示模型准确性的三个因素:

使用深度神经网络和 Caffe 检测人脸

使用 OpenCV 的视觉神经网络实现的一个优势是它们适用于不同的平台。为了清晰和简洁起见,我们在安装了 Python 的环境中使用 Python。然而,在 ARM-CortexM3 上使用 OpenCV 的 C++实现或在 Android 系统上使用 OpenCV 的 Java 实现也可以得到相同的结果。在本示例中,我们将使用基于Caffe机器学习框架的 OpenCV 实现的人脸检测神经网络。本示例的输出将是 PC 上的一个窗口,其中有围绕面部的边界框。

准备工作

要运行此示例,您需要将网络摄像头连接到设备上。如果尚未安装 OpenCV、NumPy 和 Imutils,您需要先安装它们。在资源非常有限的设备上安装 OpenCV 可能会有挑战性。如果您无法在设备上本地安装它,可以尝试几种方法。许多具有额外存储空间的设备将允许您将磁盘用作内存的交换空间。如果相关设备支持 docker 化,那么可以在计算机上编译并在设备上运行容器。本示例使用了一个预训练模型,可以在本书的 GitHub 附录中的Ch6目录中找到。

如何做...

此示例的步骤如下:

  1. 导入库:
import cv2
import numpy as np
import imutils
  1. Ch6 GitHub 仓库的预训练模型中导入一个神经网络,然后初始化 OpenCV 的摄像头操作员:
net = cv2.dnn.readNetFromCaffe("deploy.prototxt.txt", "res10_300x300_ssd_iter_140000.caffemodel")
cap = cv2.VideoCapture(0)
  1. 创建一个函数,对图像进行降采样并将其转换为我们神经网络的预定义形状,然后执行推理:
def FaceNN(frame):
    frame = imutils.resize(frame, width=300, height=300)
    (h, w) = frame.shape[:2]
    blob = cv2.dnn.blobFromImage(frame, 1.0, (300, 300), 
                                 (103.93, 116.77, 123.68))
    net.setInput(blob)
    detections = net.forward()
  1. 绘制边界框:
for i in range(0, detections.shape[2]):
    confidence = detections[0, 0, i, 2]
    if confidence < .8:
        continue
    box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
    (startX, startY, endX, endY) = box.astype("int")
    text = "{:.2f}%".format(confidence * 100)
    y = startY - 10 if startY - 10 > 10 else startY + 10
    cv2.rectangle(frame, (startX, startY), (endX, endY), 
                  (0, 0, 300), 2)
    cv2.putText(frame, text, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 
                0.45, (0, 0, 300), 2)
  1. 返回带有边界框的图像:
return frame
  1. 创建一个无休止的循环,从摄像头读取图像,进行推理并获取叠加效果,然后将图像输出到屏幕上:
while True:
    ret, frame = cap.read()
    image = FaceNN(frame)
    cv2.imshow('frame',image)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
  1. 最后,清理并销毁所有窗口:
cap.release()
cv2.destroyAllWindows()

工作原理...

导入库后,我们将预训练的人脸检测模型导入到我们的 net 变量中。然后我们打开第一个摄像头(0)。接着我们使用 FacNN 来预测图像并绘制边界框。然后我们将图像缩小到适当的尺寸。接下来我们使用 imutils 来调整来自摄像头的大图像大小。然后我们将图像设置在网络中并获取人脸检测结果。接着我们获取人脸检测结果并获取对象确实是脸的置信度。在我们的例子中,我们使用了 .880% 的阈值。我们还过滤掉置信度较低的脸部。然后我们在脸部周围绘制边界框并在框上放置置信度文本。最后,我们将这些图像返回到我们的主 while True 循环并在屏幕上显示它们。我们还等待按下 Q 键来退出。最后,我们释放摄像头并销毁 UI 窗口。

在树莓派 4 上使用 YOLO 检测物体

YOLO 代表 you only look once。它是一个快速的图像分类库,专为 GPU 处理进行了优化。YOLO 往往优于所有其他计算机视觉库。在本教程中,我们将使用 OpenCV 实现的 YOLO 进行计算机视觉对象检测。在这个例子中,我们将使用一个已经训练好的模型,其中包含 40 种常见对象。

准备工作

准备好之后,您需要克隆本书的 GitHub 仓库。在 Ch6 部分,您会找到 yolov3.cfg 配置文件和 yolov3.txt 类别文本文件。接下来,您需要下载大的 weights 文件。为此,您需要打开命令提示符并 cdCh6 目录,然后使用以下命令下载 weights 文件:

wget https://pjreddie.com/media/files/yolov3.weights

此外,您需要安装 OpenCV 和 NumPy。

如何做…

此教程的步骤如下:

  1. 导入库:
import cv2
import numpy as np
  1. 设置变量:
with open("yolov3.txt", 'r') as f:
    classes = [line.strip() for line in f.readlines()]
colors = np.random.uniform(0, 300, size=(len(classes), 3))
net = cv2.dnn.readNet("yolov3.weights", "yolov3.cfg")
cap = cv2.VideoCapture(0)
scale = 0.00392
conf_threshold = 0.5
nms_threshold = 0.4
  1. 定义我们的输出层:
def get_output_layers(net):
    layer_names = net.getLayerNames()
    output_layers = [layer_names[i[0] - 1] for i in \
                      net.getUnconnectedOutLayers()]
    return output_layers
  1. 创建边界框:
def create_bounding_boxes(outs,Width, Height):
    boxes = []
    class_ids = []
    confidences = []
    for out in outs:
        for detection in out:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
            if confidence > conf_threshold:
                center_x = int(detection[0] * Width)
                center_y = int(detection[1] * Height)
                w = int(detection[2] * Width)
                h = int(detection[3] * Height)
                x = center_x - w / 2
                y = center_y - h / 2
                class_ids.append(class_id)
                confidences.append(float(confidence))
                boxes.append([x, y, w, h])
    return boxes, class_ids, confidences
  1. 绘制边界框:
def draw_bounding_boxes(img, class_id, confidence, box): 
    x = round(box[0])
    y = round(box[1])
    w = round(box[2])
    h =round(box[3])
    x_plus_w = x+w
    y_plus_h = y+h
    label = str(classes[class_id])
    color = colors[class_id]
    cv2.rectangle(img, (x,y), (x_plus_w,y_plus_h), color, 2)
    cv2.putText(img, label, (x-10,y-10), cv2.FONT_HERSHEY_SIMPLEX, 
                0.5, color, 2)
  1. 处理图像:
def Yolo(image):
    try:
        Width = image.shape[1]
        Height = image.shape[0]
        blob = cv2.dnn.blobFromImage(image, scale, (416,416), 
                                     (0,0,0), True, crop=False)
        net.setInput(blob)
        outs = net.forward(get_output_layers(net))
        boxes, class_ids, confidences = \
            create_bounding_boxes(outs, Width, Height)
        indices = cv2.dnn.NMSBoxes(boxes, confidences, 
                                   conf_threshold, nms_threshold)

        for i in indices:
            i = i[0]
            box = boxes[i]

            draw_bounding_boxes(image, class_ids[i], 
                                confidences[i], box)
    except Exception as e:
    print('Failed dnn: '+ str(e))

    return image
  1. 读取摄像头:
while True:
    ret, frame = cap.read()
    image = Yolo(frame)
    cv2.imshow('frame',image)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
  1. 最后,清理和销毁所有窗口:
cap.release()
cv2.destroyAllWindows()

工作原理…

YOLO 一次性查看图像并将图像分割成网格。然后它使用边界框来划分网格。YOLO 首先确定边界框是否包含对象,然后确定对象的类别。通过在算法中加入预过滤器,可以筛选掉不是对象的图像部分,从而显著加快搜索速度。

在本示例中,在导入我们的库之后,我们设置我们的变量。首先,我们打开yolov3.txt。这个文件包含我们将使用的预训练库的类别。接下来,我们创建一个随机的color数组来表示我们的不同对象为不同的颜色。然后,我们导入我们的库并设置我们的摄像头为计算机上的第一个摄像头。然后,我们设置阈值并缩放图像,以便图像大小适合分类器能够识别。例如,如果我们添加一个高分辨率图像,分类器可能只会识别非常小的物体而忽略较大的物体。这是因为 YOLO 试图确定围绕对象的边界框,以过滤掉物体。接下来,我们定义我们的输出层,并基于我们的置信度阈值创建边界框。然后,我们使用这些边界框在图像周围绘制矩形,并将该图像及其标签文本传回我们的图像处理器。我们的主要图像处理循环调用Yolo函数。最后,在清理资源之前,我们通过执行执行 YOLO 分析的主循环。

使用 NVIDIA Jetson Nano 在 GPU 上检测对象

NVIDIA 制造了一系列带 GPU 的 SBC。其中一些,如 TX2,因为它们轻便且能在其 GPU 启用系统下提供大量计算机视觉功能,所以被用于无人机上。与标准 CPU 相比,GPU 与张量处理单元TPUs)能够提供多倍的计算机视觉能力。在本示例中,我们将使用 NVIDIA Jetson Nano,这是他们售价最低的开发板,售价为 99 美元。Jetson 有一个只能在他们产品上运行的库生态系统。

准备工作

首先,你需要一台 NVIDIA Jetson。接下来需要安装操作系统。为此,你需要使用 NVIDIA 的 Jetpack 映像来刷写一个 Micro USB。Jetpack 映像包含了一个基于 Ubuntu 的基础映像,并且包含了你启动所需的许多开发工具。一旦你有了操作系统映像,就将其放入 Jetson 中,并连接显示器、键盘、鼠标和网络。

然后,你将按以下步骤更新操作系统:

sudo apt-get update

之后,你需要安装额外的软件来运行代码:

sudo apt-get install git
sudo apt-get install cmake
sudo apt-get install libpython3-dev
sudo apt-get install python3-numpygpu sbc

一旦完成上述步骤,你将需要从 Jetson 下载起始项目:

git clone --recursive https://github.com/dusty-nv/jetson-inference

接下来你将创建并导航至build目录:

cd jetson-inference
mkdir build
cd build

从这里开始,我们将编译、安装并链接存储库中的代码:

cmake ../
make
sudo make install
sudo ldconfig

运行make后,你将在终端中收到一个对话框,询问你是否要下载一些不同的预训练模型,以及 PyTorch,以便你可以训练自己的模型。使用向导首先选择你想要下载的模型:

工具将下载你选择的所有模型:

对于这个示例,您可以保留默认的模型。选择“确定”后,它将要求您安装 PyTorch,以便您可以训练自己的模型。选择 PyTorch,然后选择“确定”。

如何做...

这个示例的步骤如下:

  1. 导入 Jetson 库:
import jetson.inference
import jetson.utils
  1. 设置变量:
net = jetson.inference.detectNet("ssd-inception-v2", threshold=0.5)
camera = jetson.utils.gstCamera(1280,720,"/dev/video0")
display = jetson.utils.glDisplay()
  1. 然后,运行摄像头显示循环:
while display.IsOpen():
    img, width, height = camera.CaptureRGBA()
    detections = net.Detect(img,width, height)
    display.RenderOnce(img,width,height)

工作原理...

在这个示例中,我们添加了库,然后克隆了 Jetson 推理存储库。然后,我们运行了一系列的制作和链接工具,以确保安装正确运行。在此过程中,我们下载了大量预训练模型。然后我们开始编写我们的代码。由于 Jetson 在功能和内存方面有限,安装一个功能齐全的 IDE 可能会浪费资源。这个问题的一个解决方法是使用支持 SSH 的 IDE,比如 Visual Studio Code,并通过 IDE 远程连接到设备上。这样您就可以在不占用 Jetson Nano 资源的情况下与设备一起工作。

要构建这个项目,首先我们要导入 Jetson 推理和utils库。在之前的示例中,我们自己处理了许多低级工作,如使用 OpenCV 获取摄像头,然后使用其他库来操作图像并绘制边界框。使用 Jetson 的库,这些大部分代码都已为您处理好了。在导入了库之后,我们导入了之前下载的模型并设置了阈值。然后我们设置了摄像头的尺寸并将摄像头设置为/dev/video0。接下来,我们设置了视觉显示。最后,我们获取摄像头图像,运行检测算法,然后将带有边界框的摄像头图像输出到屏幕上。

还有更多...

正如我们之前提到的,NVIDIA 为他们的产品生态系统提供了支持。他们有帮助的容器、模型和教程,可以有效地与他们的硬件一起工作。为了帮助您,他们有一个产品网站,可以帮助您开始训练模型和构建容器化的笔记本电脑。他们提供了数十个预构建的容器,涵盖了不同的库,包括 PyTorch 和 TensorFlow 等。他们还有数十个使用从姿势检测到特定行业模型的预训练模型。他们甚至有自己的云,如果您愿意,可以在那里训练您的模型。但是您也可以在本地运行。他们的网站是ngc.nvidia.com/

使用 PyTorch 在 GPU 上进行视觉训练

在前一个示例中,我们使用 GPU 和 NVIDIA Jetson Nano 实现了对象分类器。还有其他类型的启用 GPU 的设备。从能够放置在无人机上以进行实时管道分析的 NVIDIA TX2,到运行 GPU 并使用计算机视觉来执行工作场所安全性分析的工业 PC。在这个示例中,我们将通过向其添加我们自己的图像来训练并增加现有的图像分类模型。

物联网面临的挑战包括空中升级(OTA)和车队管理。物联网边缘是一个解决这些问题的概念框架。在 OTA 升级中,Docker 容器被用作升级机制。在不必担心设备完全失效的情况下,可以更新底层系统。如果更新不起作用,可以回滚系统,因为容器故障不会影响主操作系统,Docker 守护进程可以执行更新和回滚。

在这个示例中,我们将使用 NVIDIA Docker 容器来构建我们的模型。稍后,我们将使用该模型进行推断。

准备工作

为了做好准备,我们将使用版本大于 19 的 Docker 应用程序。在 Docker 19 中,添加了--gpu标签,允许您本地访问 GPU。根据您的 GPU,您可能需要安装额外的驱动程序以使 GPU 在您的计算机上工作。

我们还将使用Visual Studio CodeVS Code),借助插件,允许您直接在 NVIDIA 的 GPU PyTorch 容器中编写代码。您需要执行以下步骤:

  1. 下载并安装 VS Code,然后使用扩展管理器通过点击扩展图标添加Remote Development Extension Pack

  2. 可选地,您可以注册 NVIDIA GPU Cloud,它具有容器和模型的目录。

  3. 拉取用于 PyTorch 的 NVIDIA Docker 镜像:

docker pull nvcr.io/nvidia/pytorch:20.02-py3
  1. 在您希望将代码映射到的计算机上创建一个文件夹。然后,在终端窗口中,导航到您创建的目录。

  2. 运行 Docker 容器:

docker run --gpus all -it --rm -v $(pwd):/data/ nvcr.io/nvidia/pytorch:20.02-py3 
  1. 打开 VS Code 并通过点击  按钮连接到您的容器,然后在对话框中输入Remote-Containers: Attach to a running container。这将为您列出正在运行的容器。接着,打开/data文件夹。

  2. 将您的图像放在一个数据文件夹中,文件夹以类名标记。在 GitHub 仓库中有一个包含完整示例的文件夹及其图像。

  3. 测试容器以确保容器已启动并运行,并安装了所有驱动程序。在您启动容器的终端窗口中,输入python,然后执行以下代码:

import torch
print(torch.cuda.is_available())

如果返回True,您可以准备使用 GPU 进行训练。如果没有,您可能需要排查您的环境问题。

如何操作...

本示例的步骤如下:

  1. 导入库:
import numpy as np
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torchvision import datasets, transforms, models
from torch.utils.data.sampler import SubsetRandomSampler
  1. 声明您的变量:
datadir = './data/train'
valid_size = .3
epochs = 3
steps = 0
running_loss = 0
print_every = 10
train_losses = []
test_losses = []
  1. 创建一个准确率打印机:
def print_score(torch, testloader, inputs, device, model, criterion, labels):
test_loss = 0
accuracy = 0
model.eval()daimen

with torch.no_grad():
    for inputs, labels in testloader:
        inputs, labels = inputs.to(device), labels.to(device)
        logps = model.forward(inputs)
        batch_loss = criterion(logps, labels)
        test_loss += batch_loss.item()

        ps = torch.exp(logps)
        top_p, top_class = ps.topk(1, dim=1)
        equals = top_class == labels.view(*top_class.shape)
        accuracy += torch.mean(equals.type(torch.FloatTensor)).item()

train_losses.append(running_loss/len(trainloader))
test_losses.append(test_loss/len(testloader))
print(f"Epoch {epoch+1}/{epochs} \
      Train loss: {running_loss/print_every:.3f} \
      Test loss: {test_loss/len(testloader):.3f} \
      Test accuracy: {accuracy/len(testloader):.3f}")

 return test_loss, accuracy
  1. 导入图像:
train_transforms = transforms.Compose([transforms.Resize(224),
                                       transforms.ToTensor()])
test_transforms = transforms.Compose([transforms.Resize(224),
                                      transforms.ToTensor()])
train_data = datasets.ImageFolder(datadir, 
                                  transform=train_transforms)
test_data = datasets.ImageFolder(datadir, 
                                 transform=test_transforms)
num_train = len(train_data)
indices = list(range(num_train))
split = int(np.floor(valid_size * num_train))
np.random.shuffle(indices)
train_idx, test_idx = indices[split:], indices[:split]
train_sampler = SubsetRandomSampler(train_idx)
test_sampler = SubsetRandomSampler(test_idx)
trainloader = torch.utils.data.DataLoader(train_data, 
                                          sampler=train_sampler, 
                                          batch_size=1)
testloader = torch.utils.data.DataLoader(test_data, 
                                         sampler=test_sampler, 
                                         batch_size=1)
  1. 设置网络:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = models.resnet50(pretrained=True)

for param in model.parameters():
    param.requires_grad = False

model.fc = nn.Sequential(nn.Linear(2048, 512), nn.ReLU(), 
                         nn.Dropout(0.2), nn.Linear(512, 10), 
                         nn.LogSoftmax(dim=1))
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.fc.parameters(), lr=0.003)
model.to(device)
  1. 训练模型:
for epoch in range(epochs):
    for inputs, labels in trainloader:
        steps += 1
        inputs, labels = inputs.to(device), labels.to(device)
        optimizer.zero_grad()
        logps = model.forward(inputs)
        loss = criterion(logps, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()

        if steps % print_every == 0:
            test_loss, accuracy = print_score(torch, testloader, 
                                              inputs, device, 
                                              model, criterion,
                                              labels)
            running_loss = 0
            model.train()
  1. 保存您的模型:
torch.save(model, 'saftey.pth')

工作原理...

在这个示例中,我们使用了 NVIDIA 的 Docker 容器来跳过在本地计算机上安装 NVIDIA GPU 所需的许多步骤。我们使用 VS Code 连接到正在运行的 Docker 容器,并测试确保容器能够使用 GPU。然后,我们开发了我们的代码。

首先,像往常一样,我们导入了我们的库。然后我们声明了我们的变量。第一个变量是训练数据的位置,分割量,epochs 数量和运行步骤。然后我们制作了一个在屏幕上打印结果的功能,以便我们能够看到我们的模型是否随着超参数的更改而改进。然后我们从我们的训练文件夹导入了图像。之后,我们设置了我们的神经网络。接下来,我们导入了 ResNet 50 模型。我们将模型的requires_grad参数设置为false,这样我们的代码就不会影响已有的模型。我们正在使用一个使用 ReLU 作为激活函数的序列线性神经网络,丢失率为 20%。然后,我们添加了一个较小的网络作为我们的输出层,使用 softmax 作为我们的激活函数。我们使用Adam进行随机优化。然后我们通过我们的 epochs 运行它并训练模型。最后,模型被保存了。

还有更多...

你可能想测试你新训练的图像分类器。在本书的 GitHub 存储库中的Ch6 -> pyImage -> inferance.py目录下有一个推理测试器。在 NVIDIA 开发者门户网站上,你会找到一切所需信息,从如何在 Kubernetes 集群中有效管理 GPU 使用,到如何将刚刚创建的模型部署到像 TX2 这样的无人机设备上。

第七章:自然语言处理和自助订购亭的机器人

近年来,语言理解已经取得了显著进展。新算法和硬件大大提高了语音激活系统的有效性和可行性。此外,计算机准确地模仿人类的能力已经接近完美。机器学习在近年来取得了重大进展的另一个领域是自然语言处理(NLP),或者有些人称之为语言理解。

将计算机语音与语言理解结合,就会为智能亭和智能设备等语音激活技术打开新的市场。

在本章中,我们将介绍以下几个示例:

  • 唤醒词检测

  • 使用 Microsoft Speech API 进行语音转文本

  • 开始使用 LUIS

  • 实施智能机器人

  • 创建自定义语音

  • 使用 QnA Maker 提升机器人功能

唤醒词检测

唤醒词检测用于确保您的语音激活系统不会出现意外行为。实现高准确率的音频是具有挑战性的。背景噪音会干扰主要的语音命令。实现更高准确率的一种方法是使用阵列麦克风。阵列麦克风用于消除背景噪音。在此示例中,我们使用 ROOBO 阵列麦克风和 Microsoft Speech Devices SDK。ROOBO 阵列麦克风非常适合语音亭,因为其形状使其可以平放在亭面上。

ROOBO 配备了一个基于 Android 的计算模块。Android 是亭子的常见平台,因为它价格低廉,并且具有触摸优先界面。在这个示例中,我们将使用 Microsoft Speech Devices SDK 的 Android 版本。Speech Devices SDK 与 Speech SDK 不同。Speech Devices SDK 可以同时使用阵列和圆形麦克风,而 Speech SDK 则用于单麦克风使用。以下是 ROOBO 阵列麦克风的照片:

准备工作

对于这个示例,您将需要一个 Azure 订阅和 ROOBO 线性阵列或圆形麦克风。在您的个人电脑上,您还需要下载并安装 Android Studio 和 Vysor,以便与 ROOBO 一起使用。要设置设备,请执行以下步骤:

  1. 下载并安装 Android Studio。

  2. 下载并安装 Vysor。

  3. 打开设备并将其连接到您的计算机。有两个 USB 连接器:一个标有电源,一个标有调试。将电源连接器连接到电源源,将调试 USB 电缆连接到您的计算机:

  1. 打开 Vysor 并选择要查看的设备:

  1. 点击设置:

现在我们已经完成了设备设置,让我们生成一个唤醒词。要生成唤醒词,请执行以下步骤:

  1. 前往speech.microsoft.com/,然后点击“开始使用”:

  1. 选择新项目并填写自定义语音表单,然后点击创建

  1. 点击“创建模型”。

  2. 填写希望训练的唤醒词表单。然后,点击“下一步”:

  1. 听取并批准发音,然后点击“训练”:

  1. 模型将花费 20 分钟进行训练。完成后,点击下载。

如何实现…

此食谱的步骤如下:

  1. 在 Android Studio 中,使用 Java 创建一个新项目:

  1. 在 Gradle 脚本部分,更改Gradle Voice Projects文件夹并添加对库的引用:
allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven {
            url 'https://csspeechstorage.blob.core.windows.net/maven/'
        }
    }
}
  1. 在 Gradle 脚本部分,在 Gradle 构建应用程序部分,将此行添加到依赖项部分:
implementation 'com.microsoft.cognitiveservices.speech:client-sdk:1.10.0'
  1. 导入此项目所需的库:
import androidx.appcompat.app.AppCompatActivity;
import com.microsoft.cognitiveservices.speech.KeywordRecognitionModel;
import com.microsoft.cognitiveservices.speech.SpeechConfig;
import com.microsoft.cognitiveservices.speech.SpeechRecognizer;
import com.microsoft.cognitiveservices.speech.audio.AudioConfig;

import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import android.content.res.AssetManager;
import android.os.Bundle;
import android.text.Layout;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
  1. 在主活动类中,添加训练模型的键和位置。此外,添加麦克风类型;在本例中,我们使用的是线性麦克风:
public class MainActivity extends AppCompatActivity {
    private static String SpeechSubscriptionKey = "Your key here";
    private static String SpeechRegion = "westus2";
    //your location here

    private TextView recognizedTextView;
    private static String LanguageRecognition = "en-US";
    private Button recognizeKwsButton;

    private static String Keyword = "computer";
    private static String KeywordModel = "computer.zip";

    private static String DeviceGeometry = "Linear4";
    private static String SelectedGeometry = "Linear4";
    protected static ExecutorService s_executorService;

    final AssetManager assets = this.getAssets();
  1. 创建将结果显示到 UI 的方法:
    private void setTextbox(final String s) {
        MainActivity.this.runOnUiThread(() -> {
           recognizedTextView.setText(s);
           final Layout layout = recognizedTextView.getLayout();
           if (layout != null) {
               int scrollDelta = layout.getLineBottom(
                   recognizedTextView.getLineCount() - 1)
                       - recognizedTextView.getScrollY() - 
                       recognizedTextView.getHeight();
               if (scrollDelta > 0) {
                   recognizedTextView.scrollBy(0, scrollDelta);
               }
           }
       });
   }
  1. 使用默认麦克风设置音频输入:
    private AudioConfig getAudioConfig() {
        return AudioConfig.fromDefaultMicrophoneInput();
    }
  1. 设置完成事件的任务监听器:
    private interface OnTaskCompletedListener<T> {
        void onCompleted(T taskResult);
    }
  1. 配置语音设置,如设备几何形状、语音区域和语言:
    public static SpeechConfig getSpeechConfig() {
        SpeechConfig speechConfig = SpeechConfig.fromSubscription(
            SpeechSubscriptionKey, SpeechRegion);

        speechConfig.setProperty("DeviceGeometry", DeviceGeometry);
        speechConfig.setProperty("SelectedGeometry", 
                                  SelectedGeometry);
        speechConfig.setSpeechRecognitionLanguage(
            LanguageRecognition);

        return speechConfig;
    }
  1. 设置一个完成任务监听器:
private <T> void setOnTaskCompletedListener(Future<T> task,
    OnTaskCompletedListener<T> listener) {
        s_executorService.submit(() -> {
            T result = task.get();
            listener.onCompleted(result);
            return null;
        });
    }
  1. 设置点击按钮和关键字监听器:
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recognizeKwsButton = 
        findViewById(R.id.buttonRecognizeKws);
        recognizedTextView = findViewById(R.id.recognizedText);

        recognizeKwsButton.setOnClickListener(new 
        View.OnClickListener() {
            private static final String delimiter = "\n";
            private final ArrayList<String> content = new 
            ArrayList<>();
            private SpeechRecognizer reco = null;

            @Override
            public void onClick(View view) {
                content.clear();
                content.add("");
                content.add("");
                try {
                    final KeywordRecognitionModel 
                    keywordRecognitionModel = 
                     KeywordRecognitionModel.fromStream(
                     assets.open(KeywordModel),Keyword,true);

                    final Future<Void> task = 
                    reco.startKeywordRecognitionAsync(
                        keywordRecognitionModel);
                    setOnTaskCompletedListener(task,result ->{
                        content.set(0, "say `" + Keyword + 
                                    "`...");
                        setTextbox(TextUtils.join(delimiter, 
                        content));
                    });

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }});
    }
}

工作原理…

Microsoft Speech Devices SDK 设计用于与线性和圆形麦克风阵列配合使用。在本篇食谱中,我们创建了一个 Android 应用程序,为用户提供与语音相关的用户界面。Android 的触摸优先界面是亭子的常见形态因素。我们还在 Azure 的 Speech Studio 中创建了一个唤醒词文件。然后,我们从我们的服务中检索到了密钥。

更多信息…

Speech Devices SDK 不仅仅是创建唤醒词。它还包括语音识别、语言理解和翻译功能。如果您的亭子将被放置在可能会干扰语音识别主体的背景噪音环境中,那么阵列麦克风将是您最佳的选择。

在本篇食谱的开头,我们提到 Speech Devices SDK 也支持圆形麦克风。虽然阵列麦克风设计用于直接对准说话者,但圆形麦克风设计为垂直放置于说话人旁边。它们有助于确定说话人的方向,并且常用于多人说话情境,如日程安排。

使用 Microsoft Speech API 进行语音识别:

Microsoft Speech Services 是一个语音转文本、文本转语音和翻译等功能的生态系统。它支持多种语言,并具有高级功能,如自定义语音识别以支持口音、专有名称(如产品名称)、背景噪音和麦克风质量。在本示例中,我们将使用 Python 实现 Microsoft Speech SDK。

准备就绪

首先,您需要进入 Azure 门户并创建一个语音服务。然后,转到快速入门部分并复制下密钥。

然后,安装 Azure Speech SDK:

 python -m pip install azure-cognitiveservices-speech

如何实现...

本示例的步骤如下:

  1. 导入库:
import azure.cognitiveservices.speech as speechsdk
import time
  1. 导入在准备就绪部分生成的密钥:
speech_key, service_region = "Your Key", "westus2"
  1. 初始化语音服务:
speech_config = speechsdk.SpeechConfig(subscription=speech_key,
                                       region=service_region)
speech_recognizer = \
speechsdk.SpeechRecognizer(speech_config=speech_config)
speech_recognizer.session_started.connect(lambda evt: \
    print('SESSION STARTED: {}'.format(evt)))
speech_recognizer.session_stopped.connect(lambda evt: \
    print('\nSESSION STOPPED {}'.format(evt)))
speech_recognizer.recognized.connect(lambda evt: \
    print('\n{}'.format(evt.result.text)))
  1. 然后,通过使用无限循环执行连续语音识别:
try:
    while True:
        speech_recognizer.start_continuous_recognition()
        time.sleep(10)
        speech_recognizer.stop_continuous_recognition()
  1. 最后,清理并断开会话:
except KeyboardInterrupt:
    speech_recognizer.session_started.disconnect_all()
    speech_recognizer.recognized.disconnect_all()
    speech_recognizer.session_stopped.disconnect_all()

工作原理...

认知服务将单个词汇使用机器学习组合成有意义的句子。SDK 负责找到麦克风,将音频发送到认知服务,并返回结果。

在下一个示例中,我们将使用语言理解来确定语音的含义。之后,我们将使用 Bot Framework 创建一个智能机器人,该框架建立在语言理解的基础上,为点餐自助服务点提供状态和逻辑。您可以将语音作为该系统的输入。

Microsoft Speech SDK 允许您通过其自定义语音服务考虑口音、发音和声音质量。您还可以在连接性受限的环境中使用 Docker 容器。

开始使用 LUIS

语言理解,或LUIS,来自 Microsoft,是一项服务,它从文本中提取实体、句子所涉及的主题、意图和句子的动作。由于有一个狭窄的专注领域可以帮助减少错误率,LUIS 授权服务帮助用户创建 LUIS 解析的预定义实体和意图列表。

准备就绪

LUIS 是 Azure 认知服务的一款产品。您需要登录 Azure 门户并创建 LUIS 资源。然后,转到preview.luis.ai,点击新建用于对话的应用程序。然后,填写名称、语言和您设置的预测资源的表单。

然后,在侧边菜单中点击实体,并添加,如我们的餐厅点餐自助服务点的奶酪汉堡薯条健怡可乐奶昔巧克力香草等等:

一旦您添加了足够的实体,您将需要添加意图。点击意图,然后添加一个意图。在我们的示例中,我们将添加一个Menu.Add item意图。然后,我们添加一些例句来展示某人如何在一个自助服务点点餐。然后,我们点击句子中的实体并对其进行标记:

当有足够的内容来代表整个菜单时,点击窗口右上角的“训练”按钮。训练完成后,点击“发布”按钮。发布完成后,屏幕上将出现通知,提供密钥、端点和一个样本查询,您可以将其放入浏览器的 URL 栏以获取预测。

然后,为在点餐亭中采取的其他操作创建一个新的意图,例如从订单中删除项目或更改订单。复制该查询字符串,因为我们将稍后使用它。

如何做...

此教程的步骤如下:

  1. 导入requests库以允许我们使用 web 服务:
import requests
  1. 输入您的订单文本:
text_query = "give me a vanilla milk shake"
  1. 发送消息给 LUIS:
r = requests.get(f'Your Copied URL String={text_query}')
  1. 从响应中获取意图和实体:
message = r.json()
print(message['prediction']['topIntent'])
for entity in message['prediction']['entities']['$instance']:
    print(entity)

工作原理...

LUIS 是一个能够分解句子并提取其对象(实体)和动作(意图)的系统。在我们的教程中,我们创建了一组实体和意图。即使句子与我们键入的示例短语相似但并非完全相同,LUIS 也能够从中提取这些实体和意图。例如,短语一杯香草奶昔将是可爱的并不是我们模型训练的内容,但是 LUIS 仍然能够理解这是一个香草奶昔的订单。

还有更多内容...

向 LUIS 发送文本并获取 JSON 负载仅仅是 LUIS 的冰山一角。LUIS 与 Microsoft Speech SDK 集成,这意味着您可以使用麦克风从中获取实体和意图。您可以在设备如智能手机上使用内置语音识别,并将文本发送至 LUIS。就像我们的唤醒词检测教程一样,您可以使用阵列麦克风来过滤背景噪音或理解声音的方向性,并将其集成到 LUIS 中。

实施智能机器人

在这个教程中,我们将使用 Microsoft Bot Framework 创建智能机器人。智能机器人实现用户与机器人之间的对话。这些对话触发一系列操作。机器人跟踪对话状态,以便知道它在对话中的位置。机器人还跟踪用户状态,更确切地说,它跟踪用户输入的变量。

机器人已被用于输入复杂表单,如法律文件或财务文件。对于我们的自助点餐亭场景,我们将实施一个简单的机器人,允许用户添加食物到他们的订单中。我们将在前一个教程中实现的 LUIS 模型基础上进行构建。

准备工作

要在本地测试机器人,您需要从 Microsoft 下载并安装 Bot Framework Emulator。安装说明和文档链接可以在 GitHub 页面github.com/microsoft/BotFramework-Emulator找到。

接下来,您需要安装依赖项。对于此项目,我们使用 Python,并且有一个要求文件。要安装这些要求,请克隆本书的 GitHub 存储库并导航到 Ch7/SmartBot 文件夹。然后,输入以下 pip install 脚本:

pip3 install -r requirements.txt

这将安装 Bot Framework 组件以及 Flask(我们的机器人将使用的 Web 服务器平台)和 async.io(一个异步库)。

如何做...

此处的步骤如下:

  1. 创建一个 app.py 文件并导入所需的库:
from flask import Flask,request,Response
from botbuilder.schema import Activity
from botbuilder.core import (
    BotFrameworkAdapter,
    BotFrameworkAdapterSettings,
    ConversationState,
    UserState,
    MemoryStorage
  )
import asyncio
from luisbot import LuisBot
  1. 初始化 Flask Web 服务器:
app = Flask(__name__)
  1. 初始化事件循环:
loop = asyncio.get_event_loop()
  1. 初始化机器人的记忆和对话状态以及用户状态:
botadaptersettings = BotFrameworkAdapterSettings("","")
botadapter = BotFrameworkAdapter(botadaptersettings)
memstore = MemoryStorage()
constate = ConversationState(memstore)
userstate = UserState(memstore)
botdialog = LuisBot(constate,userstate)
  1. 设置 URL 路由:
@app.route("/api/messages",methods=["POST"])
  1. 循环执行 LUIS 和 Bot Framework 逻辑:
def messages():
    if "application/json" in request.headers["content-type"]:
        body = request.json
    else:
        return Response(status = 415)

    activity = Activity().deserialize(request.json)

    auth_header = (request.headers["Authorization"] if \
                  "Authorization" in request.headers else "")

    async def call_fun(turncontext):
        await botdialog.on_turn(turncontext)

    task = \
    loop.create_task(botadapter.process_activity(activity,
                                                 "",call_fun))
    loop.run_until_complete(task)
  1. 创建一个 luisbot.py 文件,并在 luisbot.py 文件中导入所需的库:
from botbuilder.ai.luis import LuisApplication, \
LuisPredictionOptions, LuisRecognizer
from botbuilder.core import(
ConversationState
, UserState
, TurnContext
, ActivityHandler
, RecognizerResult
, MessageFactory
)
from enum import Enum
  1. 创建一个 Order 数据存储。这将作为保存信息的地方:
class EnumOrder(Enum): 

    ENTREE=1
    SIDE=2
    DRINK=3
    DONE=4

class Order:
    def __init__(self):
        self.entree = ""
        self.drink=""
        self.side=""

    @property
    def Entree(self):
        return self.entree
    @Entree.setter
    def Entree(self,entree:str):
        self.entree = entree

    @property
    def Drink(self):
        return self.drink
    @Drink.setter
    def Drink(self,drink:str):
        self.drink = drink

    @property
    def Side(self):
        return self.side
    @Side.setter
    def Side(self,side:str):
        self.side = side
  1. 添加一个对话状态数据类。这将保存对话状态:
class ConState:
    def __init__(self):
        self.orderstatus = EnumOrder.ENTREE
    @property
    def CurrentPos(self):
        return self.orderstatus
    @CurrentPos.setter
    def EnumOrder(self,current:EnumOrder):
        self.orderstatus = current
  1. 创建一个 LuisBot 类并初始化变量:
class LuisBot(ActivityHandler):
    def __init__(self, constate:ConversationState, 
    userstate:UserState):
        luis_app = LuisApplication("APP ID","primary starter key",\
                    "https://westus.api.cognitive.microsoft.com/")

        luis_option = LuisPredictionOptions(
            include_all_intents=True,include_instance_data=True)
        self.LuisReg = LuisRecognizer(luis_app,luis_option,True)
        self.constate = constate
        self.userstate = userstate
        self.conprop = self.constate.create_property("constate")
        self.userprop = self.userstate.create_property("userstate")
  1. 在每次轮询中记录当前状态:
    async def on_turn(self,turn_context:TurnContext):
        await super().on_turn(turn_context)
        await self.constate.save_changes(turn_context)
        await self.userstate.save_changes(turn_context)
  1. 设置 on_message_activity 来从 LUIS 获取状态和实体:
    async def on_message_activity(self,turn_context:TurnContext):
        conmode = await self.conprop.get(turn_context,ConState)
        ordermode = await self.userprop.get(turn_context,Order)
        luis_result = await self.LuisReg.recognize(turn_context)
        intent = LuisRecognizer.top_intent(luis_result)
        await turn_context.send_activity(f"Top Intent : {intent}")
        retult = luis_result.properties["luisResult"]
        item = ''
        if len(retult.entities) != 0:
            await turn_context.send_activity(f" Luis Result 
                                            {retult.entities[0]}")
            item = retult.entities[0].entity
  1. 定义步骤逻辑。这将是我们完成订单所需的一组步骤:
        if(conmode.orderstatus == EnumOrder.ENTREE):
            await turn_context.send_activity("Please enter a main \
                                             Entree")
            conmode.orderstatus = EnumOrder.SIDE
        elif(conmode.orderstatus == EnumOrder.SIDE):
            ordermode.entree = item
            await turn_context.send_activity("Please enter a side \
                                             dish")
            conmode.orderstatus = EnumOrder.DRINK
        elif(conmode.orderstatus == EnumOrder.DRINK):
            await turn_context.send_activity("Please a drink")
            ordermode.side = item
            conmode.orderstatus = EnumOrder.DONE
        elif(conmode.orderstatus == EnumOrder.DONE):
            ordermode.drink = item
            info = ordermode.entree + " " + ordermode.side + \
                    " " + ordermode.drink
            await turn_context.send_activity(info)
            conmode.orderstatus = EnumOrder.ENTREE

工作原理...

Bot Framework 是由 Microsoft 开发的一个机器人构建框架。它包括活动和状态。有许多不同类型的活动,例如消息、事件和对话结束。为了跟踪状态,有两个变量,即 UserStateConversationState。用户状态用于记录用户输入的信息。在我们的示例中,这是食品订单。对话状态允许机器人按顺序询问问题。

还有更多...

Bot Framework 跟踪对话状态和用户数据,但不限于一个对话。例如,您可以使用 LUIS 确定意图可能属于不同的对话。在我们的订购场景中,您可以允许用户开始订购,然后允许他们询问营养信息或订单的当前成本。此外,您还可以添加文本转语音以为自助售货亭添加语音输出。

创建自定义语音

近年来,语音技术已经取得了长足的进步。几年前,合成语音很容易识别。它们都具有相同的语音字体,具有机器人的声音,是单调的,因此难以表达情感。如今,我们可以创建自定义语音字体,并为它们添加强调、速度和情感。在本文中,我们将介绍如何从您的声音或某位演员的声音创建自定义语音字体。

准备工作

要创建自定义语音字体,我们将使用 Microsoft 的自定义语音服务。要开始,请访问 speech.microsoft.com/portal 并单击自定义语音。在自定义语音页面上,单击新建项目:

图像

给你的项目起名字和描述后,是时候上传一些音频文件进行训练了。截至撰写本书时,最佳语音系统神经语音处于私人预览阶段。这意味着你需要申请访问权限才能使用它。如果你能访问神经语音功能,你将需要 1 小时的语音数据。为了获得略低保真度的语音合成,你可以使用标准的语音训练系统。你可以提供至少 1 小时的音频样本,但为了获得高质量的语音,你需要 8 小时的音频。

创建一个新项目后,你将进入微软语音工作室。首先,点击数据,然后上传数据。然后,选择仅音频,除非你有一些预转录的音频:

然后,将所有你的.mp3文件压缩成一个文件。根据你的音频数量不同,处理音频可能需要几个小时。然后,选择训练选项卡,点击训练模型。你将有三种不同的训练方法可选:统计参数化,连接法和神经网络:

选择对你最适用的方法。统计参数化是最低质量的选项。它还需要最少的数据。接下来的方法,连接法,需要几个小时的音频。最后,质量最高的选项是神经网络,其训练可能需要几个小时。

训练完成后,转到测试选项卡并测试你的新语音。在测试选项卡中,你可以听到和下载音频。你可以使用文本生成音频或语音合成标记语言SSML),这是一种基于 XML 的语音标记语言。SSML 允许你(如果你使用神经语音)添加情感,如愉快和共鸣。此外,它还允许你微调发音、重音和速度。

在测试完自定义语音后,转到部署选项卡并部署你的语音。这也可能需要一段时间进行处理。完成后,转到部署信息。你将需要这些信息发送请求给认知服务。

怎么做...

此处的步骤如下:

  1. 导入库:
import requests
from playsound import playsound
  1. 设置变量。这些是我们在准备工作部分中检索的键和变量:
Endpoint_key = "you will find this in your deployment"
location = 'the location you deployed it like australiaeast'
deploymentid = 'you will find this in your deployment' 
project_name = 'The name you gave to your entire project'
text = "Hey, this is a custom voice demo for Microsoft's Custom Voice"
  1. 生成一个令牌:
def get_token():
    fetch_token_url = f"https://{location}.api.cognitive.microsoft\
    .com/sts/v1.0/issueToken"
    headers = {
            'Ocp-Apim-Subscription-Key': Endpoint_key
        }
    response = requests.post(fetch_token_url, headers=headers)
    access_token = str(response.text)
    return access_token
  1. 发送请求给定制语音,以及我们希望它创建并返回响应的单词:
constructed_url = f"https://{location}.voice.speech.microsoft\
.com/cognitiveservices/v1?deploymentId={deploymentid}"
headers = {
     'Authorization': 'Bearer ' + get_token(),
     'Content-Type': 'application/ssml+xml',
     'X-Microsoft-OutputFormat': 'riff-24khz-16bit-mono-pcm',
     'User-Agent': project_name 
}

body = f"""<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"http://www.w3.org/2001/mstts\" xml:lang=\"en-US\">
<voice name=\"Siraj\">{text}</voice></speak>""" 

response = requests.post(constructed_url, headers=headers, 
                         data=body)
  1. 从响应中保存.wav文件,然后播放它:
if response.status_code == 200:
    with open('sample.wav', 'wb') as audio:
        audio.write(response.content)
        playsound('sample.wav')
        print("\nStatus code: " + str(response.status_code) + 
              "\nYour TTS is ready for playback.\n")
else:
    print("\nStatus code: " + str(response.status_code) + 
          "\nSomething went wrong. Check your subscription\
      key and headers.\n")

工作原理...

在这个配方中,我们使用了认知服务的自定义语音转文本功能。自定义语音转文本既有预训练的语音字体,也允许您创建自己的自定义语音字体。在幕后,它接受语音输入,然后使用语音转文本从文本中解析单词,然后使用单词和语音集合创建自定义语音。培训完成后,您可以公开一个端点来从语音模型中检索音频。

使用 QnA Maker 增强机器人

Microsoft 的 QnA Maker 是一个工具,可以将常见问题(FAQs)转换为一组问题和答案,使用语言理解技术,允许用户以不同的方式提问,以获得与问题相匹配的答案。QnA Maker 可以处理一系列的数据源,包括以制表符分隔的值(TSVs)、FAQ 网页和 PDF 等。在这个配方中,我们将使用包含问题和答案的 TSV。

QnA Maker 解决了解释语音并确定用户问题的模糊逻辑。作为认知服务语音生态系统的一部分,它可以轻松与 Bot Framework 和语音集成,为客户提供丰富的互动体验。

准备工作

在使用 QnA Maker 之前,您需要一系列问题和答案。您可以指向一个网站并让它解析问题和答案,或者上传一个 TSV。对于这个配方,我们将使用一个 TSV。在本书的 Git 存储库中有一个示例。

要创建一个 QnA Maker 项目,请访问www.qnamaker.ai/,并单击创建知识库。它将带您完成一个五步向导来创建一个 QnA 机器人。第一步部署资源到 Azure 中。第二步让您选择语言,并将机器人与刚刚创建的新服务关联起来。然后,您将为项目命名并上传包含问题和答案的文件。

添加问题和答案的最直接方式是使用 TSV。您需要几个字段,包括questionanswersourcemetametasource是您可以用来查询数据的字段。例如,在我们的营养常见问题解答中,我们可能有几种不同的方式来理解和回答关于汉堡热量的查询。

在上传并创建服务后,我们可以查看系统上传的内容,并向现有数据添加问题和答案:

接下来,我们将点击查看选项,并选择显示元数据。我们将添加使用 Speech Studio 内容创建器创建的音频。我们在创建自定义语音配方中介绍了 Speech Studio。在元标签部分,我们将添加我们使用内容创建器创建的音频文件:

下一步是选择保存和训练按钮,保存模型后,选择测试按钮并与您的 QnA Maker 机器人聊天。一旦您对您的 QnA Maker 机器人满意,选择发布按钮。训练完成后,QnA Maker 将显示curl命令以向 QnA Maker 发送问题。从这里,我们将提取所需的密钥以将请求转换为 Python 字符串。

如何操作...

此示例的步骤如下:

  1. 导入所需的库以发送网络请求和播放声音:
import requests
import json
from playsound import playsound
  1. 设置变量。密钥和项目 URL 可以在准备就绪部分找到:
auth = 'EndpointKey '
question = 'how many calories in a cheese burger'
projectURL = ''
  1. 生成正确格式的数据:
headers = {
    'Authorization': auth,
    'Content-type': 'application/json',
}

data = '{ "question":"'+question+'"}'
  1. 发送请求到项目 URL 上的语音服务:
response = requests.post(projectURL, headers=headers, data=data)
json_data = json.loads(response.text)
  1. 从响应中提取音频并在扬声器上播放:
for meta in json_data['answers'][0]['metadata']:
    if meta['name'] == "file":
        audiofile = 'audio/' + meta['value']
        print(audiofile)
        playsound(audiofile)

工作原理...

在幕后,QnA Maker 使用机器学习来基于问题-答案对训练模型。然后解析传入的文本,确定客户正在询问哪些问题。在我们的亭台示例中,QnA Maker 用于回答诸如食物的营养价值和餐厅信息位置等简单问题。

在这个示例中,我们使用 QnA Maker 服务来访问训练好的模型。QnA Maker 通过http post进行访问。来自 QnA Maker 的结果被转换为音频文件并在扬声器上播放。

还有更多...

Chit-chat 已经整合到 QnA Maker 中。要启用它,在创建 QnA Maker 项目时,有一个关于 chit-chat 的选项。Chit-chat 允许用户输入更多的问题并与机器人进行非正式的对话。Chit-chat 有几种个性,例如专业和对话。

第八章:使用 Microcontrollers 和 Pipelines 进行优化

大多数物联网设备运行在微控制器单元MCUs)上,而大多数机器学习则发生在 CPU 上。人工智能领域最前沿的创新之一是在受限设备上运行模型。过去,人工智能局限于具有传统操作系统(如 Windows 或 Linux)的大型计算机。现在,小型设备可以使用 ONYX 和 TensorFlow Lite 等技术执行机器学习模型。这些受限设备成本低廉,可以在没有互联网连接的情况下使用机器学习,并且可以大大节省云成本。

许多物联网项目由于高昂的云成本而失败。物联网设备通常以固定价格出售,没有重复订阅模式。然后通过执行机器学习或分析产生高昂的云成本。没有理由这样做。即使对于微控制器,通过将机器学习和分析推送到设备本身,成本也可以大大降低。

在本章中,我们将重点介绍两种不同的开发板。第一种是ESP32,第二种是STM32。ESP32 是一种带有 Wi-Fi 功能的微控制器(MCU)。它们通常的成本在$5 - $10 之间,非常适合需要在设备上添加几个传感器的较小项目,比如天气站。相比之下,STM32开发板通常被电气工程师用来快速启动项目。有几十种不同类型的开发板,但它们使用不同的计算模块,如 Cortex M0、M4 和 M7。在 ESP32 方面,电气工程师通常将它们作为其物联网设备上的计算单元。而 STM32 等其他平台被视为入门套件,电气工程师用它们来确定所需的芯片组,并设计出专门满足其需求的板子。

让这些板子运行起来,与云通信,并运行机器学习模型是非常不平凡的。本章专注于使设备执行复杂计算并连接到云的过程。为此,我们将探索所需的具体工具。通常使用 Python 等高级语言进行机器学习,而设备通常使用 C 或 C++。

本章将涵盖以下配方:

  • 介绍 ESP32 与物联网

  • 实现 ESP32 环境监测器

  • 优化超参数

  • 处理 BOM 变更

  • 使用 sklearn 构建机器学习流水线

  • 使用 Spark 和 Kafka 进行流式机器学习

  • 使用 Kafka 的 KStreams 和 KTables 丰富数据

让我们开始吧!

介绍 ESP32 与物联网

在这个配方中,我们将使用 ESP32 与 Azure IoT Hub 进行接口交互。使用低级设备,我们将编写网络接口的代码。我们还需要从计算机部署代码到 ESP32,然后使用串行监视器查看结果。

准备就绪

在这个示例中,我们将使用 Arduino 框架来编程裸金属 IoT 解决方案。在您的 PC 上,您需要安装 Arduino 集成开发环境IDE)。这将安装支持软件,以便我们可以使用 Arduino 框架来编程 ESP32。接下来,我们将安装 Visual Studio CodeVS Code)。VS Code IDE 有一个扩展程序,使得选择板子和添加库变得简单。它还有一个串行监视器和几个内置工具。

安装 Arduino IDE 和 VS Code 后,您需要在 VS Code 中找到所需的扩展工具。然后,在下面的屏幕截图中搜索 platformIO

安装了 PlatformIO IDE 后,通过 USB 将您的 ESP32 连接到计算机。然后,在左侧面板中找到 PlatformIO 按钮。接下来,在 快速访问 菜单中,单击 打开

在这里,您可以找到主 PlatformIO 窗口,并单击 打开项目

启动向导将引导您选择项目名称、框架(在我们的案例中为 Arduino)和板子类型。为了使引脚正确工作,您必须选择正确的板子类型。一些板子上有标记,可以让您查找板子类型,而其他板子则没有。因此,在购买 ESP32 时,确定板子类型非常重要:

可选地,您可以更改项目存储位置。

接下来,您需要安装 Azure IoT Hub 库和快速入门代码。返回到 快速访问 菜单,单击 。然后,在搜索菜单中键入 Azure IoT,并点击来自 Microsoft 的 AzureIoTHub 库。完成后,将发布版本更改为最新可用版本,然后单击 安装。然后,对于 AzureIoTUtilityWiFiAzureIoTProtocol_MQTT 库,您需要执行相同的操作。

然后,返回到 AzureIoTHub 库。这里有一些快速入门代码,可以让您快速连接到本地 Wi-Fi 和 IoT Hub。对于此示例,我们将使用一些样例代码来测试与 IoT Hub 的连接。在 示例 部分,您将找到三个名为 iothub_II_telemetry_samplesample_initiot_configs 的代码文件,如下面的屏幕截图所示。从 iothub_II_telemetry_sample 中获取代码,并替换源代码中的 main.cpp 代码。接下来,创建两个新文件分别称为 sample_init.hiot_configs.h,并将示例代码从 PlatformIO 示例中粘贴进去:

如何做…

此示例的步骤如下:

  1. 添加您的 Wi-Fi 连接字符串。更改 iot_configs.h 文件中第 10 和第 11 行的字符串:
#define IOT_CONFIG_WIFI_SSID "IoT_Net"
#define IOT_CONFIG_WIFI_PASSWORD "password1234"
  1. 从 Azure IoT Hub 获取设备连接字符串,并将其插入到 iot_configs.h 的第 19 行:
#define DEVICE_CONNECTION_STRING "HostName=myhub.azure-devices.net;DeviceId=somerandomname;SharedAccessKey=TWnLEcXf/sxZoacZry0akx7knPOa2gSojrkZ7oyafx0="
  1. 将您的 ESP32 通过 USB 连接到计算机,在左侧面板中点击 PlatformIO 图标,然后点击 上传并监控

工作原理...

在这里,我们将代码上传到 ESP32 并启用了串行监视器。在 Visual Studio 的下方面板应该会在连接到 Wi-Fi 网络并向 IoT Hub 发送消息时开始显示文本。我们还创建了一些用于接收云到设备消息的示例代码。

更多内容...

在这个示例中,我们仅仅触及了 IoT Hub SDK 能做的一小部分。例如,我们甚至可以发送云到设备消息,允许我们为设备排队一组消息进行处理。我们也可以发送直接消息。这类似于云到设备消息,它发送消息到设备,但不会将消息排队。如果设备离线,消息就不会被发送。另一个选择是上传到 Blob。这允许我们安全地直接上传日志或二进制文件到 Blob 存储。最后,我们还可以使用设备双生对象,允许我们在设备上设置配置文件,并且可以在设备群中查询。这有助于我们找出更新未生效或设置未正确设置的情况。

实施 ESP32 环境监控

使用硬件设置简单的环境监控相当简单。在这个示例中,我们将使用一些简单的硬件来进行概念验证。在 更多内容 部分,我们将讨论如何进行设计,并进行批量生产,即使您的团队没有电气工程师EEs)。为此,我们将介绍Fritzing,一个硬件设计工具。虽然它没有KiCadAltuim Designer那么强大,但非电气工程师也能使用它,并将电路板设计并打印出来。

这个示例的目标并不是教您如何创建温度和湿度传感器。温度和湿度传感器是 IoT 的 Hello World。相反,这个示例侧重于通过制造快速在受限设备上实施这些功能。并非所有的 IoT 项目都可以这样做。当然也有需要电气工程师构建复杂设备的 IoT 项目,例如具有视频显示和声音的设备,或者医疗行业中使用的高速设备。

准备工作

在这个配方中,我们将基于前一个配方进行构建。我们将使用 ESP32,并且必须安装 Arduino IDE 和 VS Code。在 VS Code 中,我们将添加PlatformIO扩展。最终,我们将通过 USB 将 ESP32 连接到我们使用的计算机,但在连接传感器之前,请保持未连接状态。对于这个配方,您将需要一个 DHT11 数字湿度和温度传感器,跳线电缆,一个 10k 欧姆电阻器和一个面包板。您应该能够以大约 20 美元购买所有这些组件。

从这里开始,我们需要进入 VS Code,并使用PlatformIO扩展创建一个新项目。然后,您需要从PlatformIO库管理器安装 DHT 传感器库。您还需要下载 Fritzing。这是一个开源程序。您可以在其网站上为项目做出贡献并获得副本,但您还可以转到 GitHub,在Releases下下载并安装该程序。ESP32 有多个硬件版本。您的 ESP32 可能具有不同的引脚和功能。您 ESP32 的数据表将告诉您它具有哪些引脚。例如,有些引脚可以用来进行时钟周期或电压测量。还有各种引脚上的电源和地面。这些用于给外部传感器供电。通过查看 DHT11 和 ESP32,您可以创建各种组件的输入和输出的映射。

如何做到这一点...

此配方的步骤如下:

  1. 打开 Fritzing,在右侧面板的Parts部分,单击菜单,然后选择Import...。然后,选择 ESP32 和 DHT11。这两者都可以在本章的源代码中找到:

  1. 在零件列表中搜索电阻器。一旦将其拖到屏幕上,调整其属性为4.7kΩ

  1. 现在,在板子上放置 DTH11 并使用 3.3 伏特和地面连接电源轨:

  1. 然后,将电源轨连接到板上。同时,将通用输入/输出GPIO)引脚 27 连接到 DHT11 的数据引脚。我们还必须在 3.3V 电源轨和 DHT11 的数据引脚之间添加一个 4.7k 欧姆电阻器:

  1. 接下来,将 ESP32 连接到计算机,并从我们在准备工作部分开始的PlatformIO项目中拉起/src/main.cpp文件。

  2. main.cpp中,包含DHT.h库的引用:

#include "DHT.h"
  1. 创建对 ESP32 上的数据引脚和 DHT 传感器类型的引用:
#define DHTPIN 27
#define DHTTYPE DHT11
  1. 初始化DHT变量:
DHT dht(DHTPIN, DHTTYPE);
  1. 设置串行端口并打印测试消息。然后初始化dht对象:
void setup()
{
    Serial.begin(115200);
    Serial.println("DHT11 sensor!");
    dht.begin();
}
  1. 在主循环中,读取温度和湿度传感器的数据。然后,调用打印部分,并在继续循环之前等待 2 秒:
void loop() {
    float h = dht.readHumidity();
    float t = dht.readTemperature();
    printResults(h,t);
    delay(2000);
}
  1. 创建一个检查错误的函数。如果没有找到错误,则打印结果:
void printResults(float h,float t)
{
    if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
}
Serial.print("Humidity: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(t);
Serial.println(" *C ");
}

它的工作原理...

在这里,温湿度传感器从 ESP32 获取电源和地线。一旦我们确保这一点发生,我们指定了一个数据 GPIO 引脚,并添加了一个电阻来匹配电压。

当您购买 DHT11 时,有些带有三个引脚,而其他带有四个引脚。您可以根据传感器的引脚规格调整这些引脚。同样,ESP32 的不同制造商有不同的引脚规格。在处理任何硬件之前,检查该特定产品的数据表格总是很重要的。

还有更多...

此时,我们有一个工作原型。您可以选择多种路径来设计电路板,并在工厂中批量生产产品。您可以雇用电子工程师来完成此任务,但对于这种小型项目,您通常可以找一家专门从事电路板设计的公司,例如 Seeed Studios。许多制造工厂提供硬件设计服务,可以将 Fritzing 草图转化为产品。这些制造工厂通常可以打印出原型并在您准备就绪时批量生产电路板。

优化超参数

调整超参数有许多不同的方法。如果我们手动进行此操作,我们可以将随机变量放入我们的参数中,并查看哪一个是最好的。为了做到这一点,我们可以执行网格式的方法,其中我们映射可能的选项并放入一些随机尝试,并继续进行看起来产生最佳结果的路线。我们可能使用统计或机器学习来帮助我们确定哪些参数可以给我们带来最佳结果。这些不同的方法具有依赖于实验损失形状的优缺点。

有许多机器学习库可以帮助我们更轻松地执行这些常见任务。例如,sklearn具有一个RandomizedSearchCV方法,可以根据一组参数搜索最佳模型以获得最小损失。在本篇文章中,我们将从第三章的物联网机器学习中扩展使用决策树分类化学传感器的食谱,并使用随机森林。然而,我们还将添加网格搜索以优化我们的结果。

准备工作

在本篇文章中,我们将使用第三章的 MOX 传感器数据集,物联网机器学习中,我们将我们的数据保存在 Delta Lake 中。由于这一点,我们可以轻松地将其拉入我们的 Spark Notebook 中。我们还将使用 Python 包koalassklearnnumpy

如何做...

此食谱的步骤如下:

  1. 导入必要的库:
import koalas as pd
import numpy as np

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
  1. 从 Databricks Delta Lake 导入数据:
df = spark.sql("select * from ChemicalSensor where class <> 'banana'")
pdf = df.toPandas()
  1. 选择和编码数据:
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder

pdf.rename(columns = {'class':'classification'}, inplace = True) 
X = pdf
y = pdf['classification']

label_encoder = LabelEncoder()

integer_encoded = \
label_encoder.fit_transform(pdf['classification'])
onehot_encoder = OneHotEncoder(sparse=False)

integer_encoded = integer_encoded.reshape(len(integer_encoded), 1)
onehot_encoded = onehot_encoder.fit_transform(integer_encoded)

feature_cols = ['r1', 'r2', 'r4', 'r5', 'r6','r7', 'r8', 'temp',
                'humidity', 't0', 'td']
X = pdf[feature_cols]
y = onehot_encoded

X_train, X_test, y_train, y_test = \
train_test_split(X, y, test_size=0.3, random_state=40)
  1. 选择您希望调整的参数:
model_params = {
    'n_estimators': [50, 150, 250],
    'max_features': ['sqrt', 0.25, 0.5, 0.75, 1.0],
    'min_samples_split': [2, 4, 6]
}
  1. 创建一个随机森林分类器算法的实例,以便我们稍后可以调整其超参数:
rf_model = RandomForestClassifier(random_state=1)

  1. 设置一个网格搜索估计器,以便我们可以调整参数:
clf = GridSearchCV(rf_model, model_params, cv=5)
  1. 训练决策树分类器:
model = clf.fit(X_train,y_train)
  1. 预测测试数据集的响应:
y_pred = clf.predict(X_test)
  1. 打印获胜的超参数集:
from pprint import pprint
pprint(model.best_estimator_.get_params())

工作原理...

此算法是我们在第三章中使用的易于实施的算法,物联网机器学习。在那里,我们随意选择了一个算法。然后,我们让它运行一次代码以获得必要的输出。然而,在本示例中,我们使其运行更多次,以找到我们能找到的最佳估计器。我们可以使用电子表格做同样的事情来跟踪所有运行的结果。然而,这使我们能够自动化执行实验并跟踪结果的过程。

处理 BOM 更改

物料清单BOMs)是构成设备的组件。这些可以是电阻器、芯片和其他组件。典型物联网产品的生命周期约为 10 年。在此期间,产品可能发生变化。例如,组件制造商可能会停产某个部件,如芯片系列。外包制造商通常会在电路板布局上进行 BOM 优化,尽管 BOM 优化可能会改变设备的质量。例如,它可能会改变传感器的灵敏度或设备的寿命。

这可能会影响经过训练的模型,并对剩余有效寿命的计算和预测性维护模型产生显著影响。在处理物联网和机器学习时,跟踪基于 BOM 和工厂更改的剩余有效寿命的变化可以帮助我们检测设备质量和寿命的问题。

这通常是通过数据库完成的。当设备在工厂中生产时,该设备的序列号、BOM 版本和工厂详细信息存储在该工厂中。这是可以为设备应用总预期寿命的地方。

准备工作

在本示例中,我们将启动一个 SQL Server 数据库的 Docker 实例。要开始,请安装 Docker。

下一步是使用docker构建和运行 SQL Server 数据库:

docker pull mcr.microsoft.com/mssql/server:2017-latest 

然后,运行 Docker 容器:

docker run -e 'ACCEPT_EULA=Y' -e 'MSSQL_AGENT_ENABLED=true' \
-e 'MSSQL_PID=Standard' -e 'SA_PASSWORD=Password!' \ 
-p 1433:1433 --name sqlserver_1 \ 
-d mcr.microsoft.com/mssql/server:2017-latest 

现在我们有一个工作的 SQL Server 数据库,我们需要为其添加一个数据库和两个表。你可以通过在 VS Code 中安装mssql插件,然后使用 Docker 文件中的用户名和密码连接到 SQL 数据库:

完成后,在左侧面板中点击新的 SQL Server 工具。然后,点击加号(+)按钮,跟随向导创建数据库连接。当向导询问ado.net连接字符串时输入localhost,它会要求你输入用户名和密码。用户名输入sa,密码输入Password!

然后,通过点击屏幕右上角的绿色箭头运行以下 SQL 语句:

CREATE DATABASE MLTracking
GO
USE MLTracking
GO
CREATE TABLE Product( 
  productid INTEGER IDENTITY(1,1) NOT NULL PRIMARY KEY, 
  productName VARCHAR(255) NOT NULL, 
  BeginLife Datetime NOT NULL, 
EndLife Datetime NULL, 
 ); 
GO
CREATE TABLE RUL( 
  RULid INTEGER IDENTITY(1,1) NOT NULL PRIMARY KEY, 
ProductId int,
TotalRULDays int, 
DateCalculated datetime not null 
) 
GO

从这里,从pypi安装pyodbc并在 VS Code 中创建一个新的 Python 脚本。

如何做到...

此示例的步骤如下:

  1. 导入 pyodbc 库:
import pyodbc 
  1. 连接数据库:

conn = pyodbc.connect('Driver={SQL Server};'
 'Server=localhost;'
 'Database=MLTracking;'
 'uid=sa;'
 'pwd=Password!;')
  1. 创建数据库连接光标,以便可以运行查询:
cursor = conn.cursor()
  1. 将产品和制造日期插入 Device 表并提交事务:
cursor.execute('''
  INSERT INTO MLTracking.dbo.Product (Product,BeginLife)
  VALUES
  ('Smoke Detector 9000',GETDATE()),
  ''')
conn.commit()
  1. 一旦计算出产品的剩余有用寿命,请将该信息添加到数据库中:
cursor.execute('''
  INSERT INTO MLTracking.dbo.RUL (ProductId,TotalRULDays,DateCalculated )
  VALUES
  (1,478,GETDATE()),
  ''')
conn.commit()

工作原理...

在这个示例中,我们向您展示了如何使用数据库来跟踪您随时间推移的结果。数据库允许我们随时间插入和更新有关我们模型的信息。

还有更多内容...

在这个例子中,我们关注的是产品。跟踪设备的寿命结束可以为我们的模型提供真实世界的反馈,并告诉我们何时应该重新训练它们。我们可以存储预测的错误或损失率,并将其与真实设备进行比较。

使用 sklearn 构建机器学习管道

sklearn pipeline 软件包使我们能够管理特征工程和建模的多个阶段。进行机器学习实验不仅仅是训练模型。它结合了几个因素。首先,您需要清洗和转换数据。然后,您必须通过特征工程丰富数据。这些常见任务可以组合成一系列称为管道的步骤。当我们在实验中尝试不同的变体时,我们可以使用这些管道来训练一系列非常复杂的步骤,使它们变成一些简单和可管理的东西,可以重复使用。

准备工作

在这个示例中,我们将使用之前在第四章的增强数据使用特征工程示例中进行特征工程的数据,预测性维护的深度学习。在那个示例中,我们将数据放入 Databricks,然后清洗数据,以便在其他实验中使用。要检索此数据,我们只需使用 Delta Lake 的select语句。对于这个示例,您需要在您的 Spark 集群上安装 pandassklearn

如何操作...

此示例的步骤如下:

  1. 导入 pandassklearn
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.compose import ColumnTransformer
  1. 从 Delta Lake 导入数据:
train = spark.sql("select * from engine").toPandas()
train.drop(columns="label" , inplace=True)
test = spark.sql("select * from engine_test2").toPandas()
  1. 创建转换器将数据转换为标准化的数值或分类数据:
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())])
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='constant', 
                              fill_value='missing')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))])

  1. 提取必要的特征并创建处理器:
numeric_features = \
train.select_dtypes(include=['int64', 'float64']).columns
categorical_features = \
train.select_dtypes(include=['object']).drop(['cycle'], 
                                             axis=1).columns

preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)])
  1. 创建随机森林管道步骤:
rf = Pipeline(steps=[('preprocessor', preprocessor),
                     ('classifier', RandomForestClassifier())])
  1. 拟合分类器:
rf.fit(X_train, y_train)
  1. 执行分类:
y_pred = rf.predict(X_test)

工作原理...

机器学习流水线的构建在数据科学中非常普遍。它有助于简化复杂操作,并为软件代码增加了可重复使用性。在这个示例中,我们使用sklearn来在一个简单的流水线上执行复杂操作。在我们的流水线中,我们创建了一组转换器。对于数值数据,我们使用了一个缩放器,而对于分类数据,我们使用了独热编码。接下来,我们创建了一个处理器流水线。在我们的案例中,我们使用了一个随机森林分类器。请注意,这个流水线步骤是一个数组,所以我们可以将更多的分类器传递到我们的数组中。然而,为了简单起见,我们将把这个留到更多内容部分。最后,我们训练并从我们的模型中得到了预测结果。

更多内容...

正如我们在这个示例的介绍中提到的,流水线的目的是使您能够轻松调整流水线步骤。在本节中,我们将调整这些步骤以帮助我们实现更高的准确率。在这种情况下,我们只是扩展了前面的代码,并添加了一个机器学习算法分类器数组。从那里,我们将评分模型,以便确定哪一个是最好的。此代码如下所示:

from sklearn.metrics import accuracy_score, log_loss
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
classifiers = [
    KNeighborsClassifier(3),
    DecisionTreeClassifier(),
    RandomForestClassifier(),
    AdaBoostClassifier(),
    GradientBoostingClassifier()
    ]
for classifier in classifiers:
    pipe = Pipeline(steps=[('preprocessor', preprocessor),
                      ('classifier', classifier)])
    pipe.fit(X_train, y_train) 
    print(classifier)
    print("model score: %.3f" % pipe.score(X_test, y_test))

使用 Spark 和 Kafka 进行流式机器学习

Kafka 是一个实时流消息中心。结合 Kafka,Databrick 的能力可以实时摄取流并对其进行机器学习,从而使您能够在几乎实时中执行强大的机器学习。在这个示例中,我们将使用 Confluent。Confluent 是由 Kafka 的创造者创立的公司。他们在 Azure、GCP 和 AWS 上提供云服务。我们还将使用在 Azure、GCP 和 AWS 上可用的 Databricks。

准备就绪

在云市场中,启动 Confluent 和 Databricks。这将为您提供一个具有弹性可扩展性的 Kafka 和 Spark 系统。一旦您启动了这些系统,请访问 Confluent 网站confluent.cloud,并输入您在云市场中设置的用户名和密码。然后,点击创建集群。按照向导创建您的第一个集群。一旦您进入集群,请点击菜单中的API 访问。然后,找到创建密钥按钮,该按钮将允许您创建一个 API 访问密钥:

一旦您创建了密钥,请记下其用户名和密码;稍后您会需要这些详细信息。

接下来,转到主题部分,并使用创建主题按钮创建两个主题:一个名为Turbofan,另一个名为Turbofan_RUL。接下来,我们将创建一个 Python 文件,以便我们可以测试我们的新主题。创建一个带有以下代码的 Python 文件,以生成TurboFan主题的消息:

from confluent_kafka import Producer
from datetime import datetime as dt
import json
import time

producer = Producer({
    'bootstrap.servers': "pkc-lgwgm.eastus2.azure.confluent.cloud:9092",
    'security.protocol': 'SASL_SSL',
    'sasl.mechanism': "PLAIN",
    "sasl.username": "",
    "sasl.password": "",
    'auto.offset.reset': 'earliest'
})

data = json.dumps({'Record_ID':1,'Temperature':'100','Vibration':120,
                   'age':1000, 'time':time.time()})
producer.send('TurboFan', data)

现在,您可以转到 Confluent Cloud UI 中的主题,并通过选择主题(TurboFan)然后点击消息来查看该主题上的消息:

如果您运行上述代码,您将看到一条消息发送到 Kafka。

如何做到...

本配方的步骤如下:

  1. 将 Kafka 流入 Databricks。在 Databricks 笔记本中,输入以下代码:
from pyspark.sql.types import StringType
import json 
import pandas as pd
from sklearn.linear_model import LogisticRegression

 df.readStream.format("kafka") 
.option("kafka.bootstrap.servers", "...azure.confluent.cloud:9092") 
.option("subscribe", "TurboFan") 
.option("startingOffsets", "latest") 
.option("kafka.security.protocol","SASL_SSL") 
.option("kafka.sasl.mechanism", "PLAIN") 
.option("kafka.sasl.jaas.config", "kafkashaded.org.apache.kafka.common.security.plain.PlainLoginModule required username=\"Kafka UserName\" password=\"Kafka Password\";") 
.load() 
.select($"value") 
.withColumn("Value", $"value".cast(StringType)) 
  1. 指定从 JSON 文件中的字段序列化为对象:
val jsDF1 = kafka1.select( get_json_object($"Value", "$.Temperature").alias("Temp"), 
get_json_object($"Value", "$.Vibration").alias("Vibration") 
,get_json_object($"Value", "$.age").alias("Age") 
)
  1. 定义执行推理的函数:
def score(row):
    d = json.loads(row)
    p = pd.DataFrame.from_dict(d, orient = "index").transpose() 
    pred = model.predict_proba(p.iloc[:,0:10])[0][0]
    result = {'Record_ID': d['Record_ID'], 'pred': pred }
    return str(json.dumps(result))
  1. 使用 UDF 执行推理并将结果保存到 DataFrame 中:
df = df.selectExpr("CAST(value AS STRING)")
score_udf = udf(score, StringType()) 
df = df.select( score_udf("value").alias("value"))
  1. 将失败的设备写入另一个 DataFrame:
failure_df = df.filter(df.value > 0.9)
  1. 将该 DataFrame 作为一个新主题流回 Kafka,并将结果写入 Kafka:
query = df.writeStream.format("kafka") 
.option("kafka.bootstrap.servers", "{external_ip}:9092") 
.option("topic", "Turbofan_Failure") 
.option("kafka.security.protocol","SASL_SSL") 
.option("kafka.sasl.mechanism", "PLAIN") 
.option("kafka.sasl.jaas.config", "kafkashaded.org.apache.kafka.common.security.plain.PlainLoginModule required username=\"Kafka UserName\" password=\"Kafka Password\";") 
 .option("checkpointLocation", "/temp").start()

工作原理...

Kafka 是一个设计用于处理大量数据的流引擎。数据被摄取到 Kafka 中,然后发送到 Spark,可以将概率发送回 Kafka。在这个示例中,我们使用了 Confluent Cloud 和 Databricks。这些托管服务可以在所有主要的云市场上找到。

在这个配方中,我们从引擎接收了实时数据。然后,我们在 Spark 中将这些数据流式传输并对其进行推理。一旦我们收到结果,我们将其流回一个单独的 Kafka 主题。使用 Kafka 主题和 Kafka 本身,我们可以将这些数据推送到数据库、数据湖和微服务中,所有这些都来自一个单一的数据管道。

还有更多...

除了将所有数据放入主题以便将其转储到数据存储中,我们还可以将数据流式传输到警报系统中。为此,我们可以创建一个 Kafka 消费者,如下面的代码所示。在这里,我们将数据流式传输到本地系统,然后有一个msg_process函数,我们可以用它来写入警报系统,比如Twilio

from confluent_kafka import Consumer

conf = {'bootstrap.servers': "host1:9092,host2:9092",
        'group.id': "foo",
        'kafka.security.protocol':'SASL_SSL, 
        'kafka.sasl.mechanism':'PLAIN', 
        'kafka.sasl.jaas.config': 'kafkashaded.org.apache.kafka.common.security.plain.PlainLoginModule required username=\"Kafka UserName\" password=\"Kafka Password\";') 
        'auto.offset.reset': 'smallest'}

running = True
consumer = Consumer(conf)
consumer.subscribe('Turbofan_Failure')
 while running:
    msg = consumer.poll(timeout=1.0)
    if msg is None: continue
    msg_process(msg)

def msg_process(msg):
    pass

使用 Kafka 的 KStreams 和 KTables 丰富数据

在物联网中,经常有必须包含的外部数据源。这可能是影响设备性能的天气数据或来自其他附近设备的数据。一个简单的方法是使用 Kafka KSQL 服务器。与之前的示例一样,我们将使用 Confluent Cloud 的 KSQL 服务器,如果您有 Confluent Cloud 订阅的话。

在这个示例中,我们将从天气服务主题获取数据,并将其放入 KTable 中。KTable 类似于数据库表。所有进入 Kafka 的数据都以键值对的形式出现。使用 KTable 时,当新的键入数据到来时,我们将其插入到我们的 KTable 中。如果它包含在我们的 KTable 中已经存在的键,则我们会对其进行更新。我们还将把我们的主题转换为 KStream。这使我们可以在我们的表和流上运行标准类似 SQL 的查询。通过这样做,例如,我们可以查询当前天气并将其与我们之前的配方中的引擎数据进行连接。这样可以丰富数据。

准备工作

Confluent Cloud ksqlDB门户中,转到ksqlDB选项卡并添加一个应用程序:

完成所有设置步骤后,您将拥有一个 KSQL 查询编辑器。从这里,我们将编辑我们的查询。

这个配方是上一个配方的延续。你需要运行在 Kafka 中设置的流数据TurboFan,还需要运行名为weatherstreamer.py的 Kafka 天气流 Python 脚本,该脚本可以在本书 GitHub 存储库的ch10目录中找到。

最后,你需要进入 ksqlDB,在那里你会找到查询编辑器。我们将使用该编辑器来创建我们的流和表。

如何做到…

这个配方的步骤如下:

  1. 使用我们的天气主题创建一个 KTable:
CREATE TABLE users (
     TurboFanNum BIGINT PRIMARY KEY,
     temperature BIGINT,
     humidity BIGINT
   ) WITH (
     KAFKA_TOPIC = 'weather', 
     VALUE_FORMAT = 'JSON'
   );
  1. TurboFan主题转换为数据流:
CREATE STREAM TurboFan (
    TurboFanNum BIGINT,
    HoursLogged BIGINT,
    VIBRATIONSCORE BIGING
  ) WITH (
    KAFKA_TOPIC='TurboFan',
    VALUE_FORMAT='JSON'
  );
  1. 将表和流连接到一个新主题:
CREATE STREAM TurboFan_Enriched AS
  SELECT 
     TurnboFan.TurboFanNum, 
     HoursLogged, 
     VIBRATIONSCORE, 
     temperature,
     humidity 

  FROM TurboFan
    LEFT JOIN Weather ON Weather.TurboFanNum = TurboFan.TurboFanNum
  EMIT CHANGES;

它是如何工作的…

KSQL Server 是建立在 Kafka Streams API 之上的技术。这个工具的目标是允许实时进行数据增强和数据转换。在这个配方中,我们获取了这些流并将其中一个转换为最近键的表。我们使用这些键来更新我们表中的值。接下来,我们取了一个主题,并在其上创建了一个流视图。最后,我们将我们的表与我们的流进行了连接,并创建了一个输出作为一个新的流。这也是 Kafka 中的一个新主题。

还有更多的…

使用 KSQL Server,我们可以利用 SQL 提供的更多语义,例如 group by、count 和 sum。由于 Kafka 是一个无尽的数据集,我们可以使用窗口来按时间段获取数据。例如,我们可能想知道平均温度是否超过了 100 度。我们可能想在 20 秒的时间段内查看这个数据。在 KSQL 中,我们可以将其远程化为另一个流:

CREATE STREAM TurboFan_ToHot AS
  SELECT 
     TurnboFan.TurboFanNum, 
     avg(temperature)
  FROM TurboFan_Enriched
  WINDOW TUMBLING (SIZE 20 SECONDS)
  GROUP BY TurboFanNum
  HAVING avg(temperature) > 100
  EMIT CHANGES;

第九章:部署到边缘

在单个计算机上执行机器学习和运维MLOps)可能具有挑战性。当我们考虑在成千上万台计算机上进行模型训练、部署和维护时,这样做的复杂性可能令人生畏。幸运的是,有减少此复杂性的方法,如使用容器化和持续集成/持续部署CI/CD)流水线工具。在本章中,我们将讨论以安全、可更新且优化当前硬件的方式部署模型。

在构建可更新模型方面,我们将讨论使用 Azure IoT Hub Edge 设备在单一管理平面上实现OTA更新。我们还将使用设备双子来维护车队并推送配置设置到我们的模型中。此外,我们还将学习如何在一个计算机架构(如 x86)上训练模型并在 ARM 上运行。最后,我们将讨论如何使用雾计算在不同类型的设备上执行分布式机器学习。

本章包括以下步骤:

  • OTA 更新 MCU

  • 使用 IoT Edge 部署模块

  • 使用 TensorFlow.js 将负载卸载到网络

  • 部署移动模型

  • 使用设备双子维护你的车队

  • 使用雾计算启用分布式机器学习

让我们开始吧!

OTA 更新 MCU

OTA 更新对于部署安全更新、新功能和更新模型至关重要。有两种不同的 OTA 更新技术。第一种是构建一个自定义程序,理想情况下,它运行在与尝试更新的主程序不同的程序或线程上。这个软件会将新固件下载到闪存中,并注册并启动新固件。如果新固件启动失败,自定义软件可以启动工作版本的软件。通常需要将一半的可用闪存内存保存用于 OTA 更新。

第二种方法是使用 Azure IoT Edge 等系统来更新设备上的 Docker 容器。这需要设备运行完整操作系统,如 Raspbian、Ubuntu 或 Windows。大多数物联网设备无法支持 IoT Edge 所需的计算能力。在这个示例中,我们将讨论 MCU 的 OTA 更新,而在下一个示例中,我们将讨论 IoT Edge 上的 OTA 更新。

准备工作

在这个示例中,我们将使用 ESP32 对小型 MCU 设备进行 OTA 更新。使用 ESP32,我们将在 IDF 框架中进行编程。Espressif IoT 开发框架ESP-IDF)是一个低级编程框架。它比 Arduino 框架具有较少的预构建组件,但更快速,更适合工业应用。

对于开发,我们将使用带有PlatformIO扩展的 VS Code。我们可以通过访问PlatformIO主页并选择+ New Project来创建项目:

接下来,添加项目名称,然后选择将要使用的开发板和开发框架。在我这里,我使用 NodeMCU-32S 作为开发板:

然后,在您的根目录中将empty.c重命名为main.c并开始编码。

如何操作...

这个配方的步骤如下:

  1. 导入必要的库:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "cJSON.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "wifi_functions.h"
  1. 设置固件版本、证书和缓冲区大小:
#define FIRMWARE_VERSION 0.1
#define UPDATE_JSON_URL "https://microshak.com/esp32/firmware.json"

extern const char server_cert_pem_start[] asm("_binary_certs_pem_start");
extern const char server_cert_pem_end[] asm("_binary_certs_pem_end");

char rcv_buffer[200];
  1. 创建 HTTP 事件处理程序:
esp_err_t _http_event_handler(esp_http_client_event_t *evt) 
{    
  switch(evt->event_id) {
        case HTTP_EVENT_ERROR:
            break;
        case HTTP_EVENT_ON_CONNECTED:
            break;
        case HTTP_EVENT_HEADER_SENT:
            break;
        case HTTP_EVENT_ON_HEADER:
            break;
        case HTTP_EVENT_ON_DATA:
            if (!esp_http_client_is_chunked_response(evt->client)){
              strncpy(rcv_buffer, (char*)evt->data, evt->data_len);
            }
            break;
        case HTTP_EVENT_ON_FINISH:
            break;
        case HTTP_EVENT_DISCONNECTED:
            break;
    }
    return ESP_OK;
}
  1. 接下来,我们将创建一个无限循环(为简洁起见,我们将忽略机器学习算法):
void ml_task(void *pvParameter) 
{
    while(1) 
    {
        //ML on this thread
    }
}
  1. 检查 OTA 更新。这样做会下载清单。然后,如果版本与当前版本不同,它将触发下载并重新启动设备:
void check_update_task(void *pvParameter) 
{  
  while(1) 
  {
    printf("Looking for a new firmware...\n");

    esp_http_client_config_t config = 
    {
        .url = UPDATE_JSON_URL,
        .event_handler = _http_event_handler,
    };
    esp_http_client_handle_t client = 
        esp_http_client_init(&config);  
    esp_err_t err = esp_http_client_perform(client);
    if(err == ESP_OK) {      
      cJSON *json = cJSON_Parse(rcv_buffer);
      if(json == NULL) printf("downloaded file is not a valid json,
      aborting...\n");
      else { 
        cJSON *version = cJSON_GetObjectItemCaseSensitive(json, 
        "version");
        cJSON *file = cJSON_GetObjectItemCaseSensitive(json, 
        "file");
        if(!cJSON_IsNumber(version)) printf("unable to read new 
        version, aborting...\n");
        else {
          double new_version = version->valuedouble;
          if(new_version > FIRMWARE_VERSION) {           
            printf("current firmware version (%.1f) is lower than 
            the available one (%.1f), upgrading...\n", 
            FIRMWARE_VERSION, new_version);
            if(cJSON_IsString(file) && (file->valuestring != NULL)) 
            {
              printf("downloading and installing new 
                     firmware(%s)...\n", file->valuestring);              
              esp_http_client_config_t ota_client_config = 
              {
                .url = file->valuestring,
                .cert_pem = server_cert_pem_start,
              };              
              esp_err_t ret = esp_https_ota(&ota_client_config);
              if (ret == ESP_OK) 
              {
                printf("OTA OK, restarting...\n");
                esp_restart();
              } 
              else 
              {
                printf("OTA failed...\n");
              }
            }
            else printf("unable to read the new file name, 
                        aborting...\n");
          }
          else printf("current firmware version (%.1f) is greater
                      or equal to the available one (%.1f), 
                      nothing to do...\n", 
                      FIRMWARE_VERSION, new_version);
        }
      }
    }
    else printf("unable to download the json file, aborting...\n");

    esp_http_client_cleanup(client);

    printf("\n");
        vTaskDelay(60000 / portTICK_PERIOD_MS);
    }
}
  1. 初始化 Wi-Fi:
static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    switch(event->event_id) 
    {
      case SYSTEM_EVENT_STA_START:
            esp_wifi_connect();
            break;

      case SYSTEM_EVENT_STA_GOT_IP:
        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
        break;

      case SYSTEM_EVENT_STA_DISCONNECTED:
        esp_wifi_connect();
        break;

      default:
        break;
    }

  return ESP_OK;
}

void wifi_initialise(void) 
{

  ESP_ERROR_CHECK(nvs_flash_init());

  wifi_event_group = xEventGroupCreate();
  tcpip_adapter_init();
  ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
  wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
  ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config));
  ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
  wifi_config_t wifi_config = {
        .sta = {
            .ssid = "mynetwork",
            .password = "mywifipassword",
        },
    };
  ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, 
                                      &wifi_config));
  ESP_ERROR_CHECK(esp_wifi_start());
}

void wifi_wait_connected()
{
  xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true,
                      portMAX_DELAY);
}
  1. 在主循环中,初始化 Wi-Fi 并创建两个任务(OTA 更新任务和我们的模拟机器学习任务):
void app_main() {

  printf("HTTPS OTA, firmware %.1f\n\n", FIRMWARE_VERSION);

  wifi_initialise();
  wifi_wait_connected();
  printf("Connected to wifi network\n");

  xTaskCreate(&ml_task, "ml_task", configMINIMAL_STACK_SIZE, NULL,
              5, NULL);
  xTaskCreate(&check_update_task, "check_update_task", 8192, NULL,
              5, NULL);
}

工作原理...

程序有三个任务。第一个任务是设置并确保连接到 Wi-Fi。在建立连接之前,它不会执行其他操作。该程序使用 Free RTOS 作为其实时操作系统。RTOS 允许线程独立执行。这使我们能够拥有两个非阻塞线程。我们的第一个线程执行机器学习任务,而第二个执行更新任务。更新任务允许我们以较低的频率轮询我们的 Web 服务器。

更多内容...

此配方中的 OTA 更新器需要一个清单,以便它可以根据其当前版本检查并找到要下载的文件。以下是清单的.json文件示例:

{
    "version":1.2.
    "file":"https://microshak.com/firmware/otaml1_2.bin"
}

OTA 更新对任何 IoT 设备都是重要的事情。大多数芯片设备制造商,如 ESP32 或 STM32,已解决了这个 OTA 更新问题。这些制造商通常有示例代码,可以帮助您快速启动项目。

使用 IoT Edge 部署模块

在边缘部署模型可能存在风险。在前面的配方中,我们对小型 IoT 设备进行了简单的更新。如果更新导致整个设备群失败,它们可能永远丢失。如果我们有一个更强大的设备,那么我们可以启动独立运行的程序,彼此不受影响。如果更新失败,程序可以回滚到一个正常工作的版本。这就是 IoT Edge 的作用所在。IoT Edge 通过使用 Docker 技术专门处理在 IoT 设备上运行多个程序的问题。例如,这可能是需要执行地理围栏操作的采矿设备,用于设备故障预测的机器学习和用于自动驾驶汽车的强化学习。这些程序中的任何一个都可以更新,而不会影响其他模块。

在这个配方中,我们将使用 Azure 的 IoT Hub 和 IoT Edge 功能。这涉及使用 Docker 和 IoT Hub 将模型推送到设备。

准备工作

对于这个教程,您将需要一个位于云端的 Azure IoT Hub 和 Azure 容器注册表。您还需要安装了 Azure IoT 扩展的Visual Studio CodeVS Code)和一个 Raspberry Pi。对于这个教程,您将需要三个主要组件。第一个是我们的 Raspberry Pi,它必须进行设置。这将涉及安装 Moby,即 Docker 的轻量级版本。接下来是编写代码。在我们的情况下,我们将在基于 x86 的笔记本上编写代码,并将模型部署到基于 ARM 的 Raspberry Pi 上。最后,我们将把代码部署到一个或一系列设备上。

设置我们的 Raspberry Pi

对于这个教程,我们将远程从笔记本电脑上的 Raspberry Pi 进行编码。为了实现这一点,我们需要允许 SSH 并通过 VS Code 连接到 Raspberry Pi。在 Raspberry Pi 上,您需要转到Menu | Preferences | Raspberry Pi Configuration。然后,单击Interfaces并启用SSH

在终端窗口中,输入以下命令:

hostname -I

这将为您提供 Raspberry Pi 的 IP 地址。将该 IP 地址记录下来,然后回到您的桌面电脑,在 VS Code 中安装 SSH 插件并连接到 Raspberry Pi。然后,通过使用连接到 SSH 按钮连接到 Raspberry Pi。然后,按照向导的指示使用设备的 IP 地址和密码连接到 Raspberry Pi。完成这些步骤后,您可以在设备上创建一个新项目。

此外,在您使用设备时,您需要安装 IoT Edge 代理。要做到这一点,请按照docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-edge-linux中的说明操作。

编码设置

现在,创建一个新的 IoT Edge 项目。要做到这一点,请打开 Visual Studio 并安装 Azure IoT Edge 扩展,以及 Docker 扩展。然后,使用Ctrl + Shift + P打开命令窗口,输入Azure IoT Edge:进入搜索栏,并选择Azure IoT Edge: New IoT Edge Solution

完成这些步骤后,您将看到一个向导,要求您为项目命名。然后,向导将提示您添加一个模块。一个项目可以有多个执行不同任务的模块。这些模块可以用不同的语言编写,或者使用 Azure 机器学习服务来集成该平台上的预构建模型。在我们的情况下,我们正在制作一个自定义的 Python 模块。然后,它会要求您提供 Azure 容器注册表模块的位置,因此根据需要提供位置,如下面的屏幕截图所示:

从这里开始,我们可以在树莓派上进行开发。在树莓派上开发机器学习的一个需要注意的事项是,像环境构建这样的任务可能会花费 10 倍的时间。在具有 16 核心和 32 GB RAM 的桌面上,需要几分钟的机器学习 Docker 构建,在只有 1 个核心和 2 GB RAM 的情况下,可能需要 10 倍的时间来编译。

此时,VS Code 的代码生成器已创建了一个main.py文件,其中包含一个接收来自 IoT Hub 消息并将其回显的起始模板。在如何执行……部分,我们将修改它以包含用于您的机器学习代码的存根。在还有更多……部分,我们将讨论如何为 ARM32 环境构建模块。

如何执行……

此处的步骤如下:

  1. main.py文件中,导入必要的库:
import time
import os
import sys
import asyncio
from six.moves import input
import threading
from azure.iot.device.aio import IoTHubModuleClient
from azure.iot.device import Message
import uuid
  1. 为您的 ML 代码创建一个存根:
def MLCode():
    # You bispoke ML code here
    return True
  1. 创建一个发送消息的函数:
    async def send_d2c_message(module_client):
        while True:
            msg = Message("test machine learning ")
            msg.message_id = uuid.uuid4()
            msg.custom_properties["MachineLearningBasedAlert"]=\ 
            MLCode()
            await module_client.send_message_to_output(msg, 
                                                       "output1")
  1. 创建一个接收消息的函数:
def stdin_listener():
    while True:
        try:
            selection = input("Press Q to quit\n")
            if selection == "Q" or selection == "q":
                print("Quitting...")
                break
        except:
            time.sleep(10)
  1. 启动我们的消息发送线程和消息接收线程:
async def main():
    try:
        module_client = \
        IoTHubModuleClient.create_from_edge_environment()
        await module_client.connect()
        listeners = asyncio.gather(send_d2c_message(module_client))

        loop = asyncio.get_event_loop()
        user_finished = loop.run_in_executor(None, stdin_listener)

        # Wait for user to indicate they are done listening for 
        # messages
        await user_finished

        # Cancel listening
        listeners.cancel()

        # Finally, disconnect
        await module_client.disconnect()

    except Exception as e:
        print ( "Unexpected error %s " % e )
        raise
  1. 设置标准的 Python 主程序入口点:
if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.close()

工作原理……

在这个示例中,我们学习了如何为开发一个边缘模块准备设备和开发环境,您可以在其上部署代码。IoT Edge 编程范式的工作方式是接收消息、执行操作,然后发送消息。在这个示例的代码中,我们将这些操作分成了可以独立运行的不同任务。这使我们能够在慢速循环中执行获取和发送消息等操作,并在更快的循环中评估我们的数据。为此,我们使用了asyncio,这是一个在 Python 中支持多线程的库。一旦您的代码准备好,您可以构建一个 Docker 容器,并将其部署到安装有边缘模块或整个设备群的其他设备上。在还有更多……部分,我们将讨论如何执行这些操作。

还有更多……

现在您已将代码添加到设备上,您需要在设备的架构上本地构建代码。确保设备镜像正常运行后,您可以将其上传到您的容器注册表。这可以确保您的设备位于 IoT Hub 中。要执行此操作,请进入 Visual Studio 项目,并右键单击module.json文件。将出现一个新的上下文菜单,允许您选择本地构建或构建并推送到容器注册表:

从这里开始,您可以通过右键单击deployment.template.json文件并选择生成 IoT Edge 部署清单来创建一个部署清单。VS Code 将生成一个包含deployment.arm32.json文件的config文件夹:

定位并右键单击deployemtn.arm32.json文件;将出现一个新的上下文菜单,允许您部署到单个设备或设备群:

此相同的菜单还允许您推送到一组设备。一旦部署了更新,您可以在门户中查看更新。如果您让该部署更新设备双胞胎,您可以使用它来查询整个设备组的部署状态。

使用 TensorFlow.js 将计算卸载到 Web

IoT 中失败的最大驱动因素之一是成本。通常,设备以固定的低价格出售,然后对设备制造商来说有再发生的成本。有多种方法可以减少再发生的成本。其中一种方法是将一些机器学习计算卸载到访问数据的设备或应用程序中。在这个案例中,我们将使用 TensorFlow.js 将昂贵的计算卸载到查看网页的浏览器中。

准备工作

对于这个案例,我们将基于 第四章 中的 实现 LSTM 预测设备故障 案例进行扩展,该章节是关于预测维护的深度学习,我们研究了 NASA 的 Turbofan Run to Failure 数据集。您可以在本章的仓库中找到 Databricks 笔记本。对于这个案例,我们将使用 MLflow 实验来获取我们的模型。我们将把该模型转换为可以在前端使用 TensorFlow.js 运行的模型。在开始使用 TensorFlow.js 之前,您需要运行 pip install tensorflowjs

接着,您需要找到从 MLflow artifact 下载的模型;也就是说,保存的 Keras 模型。为此,请运行以下命令:

tensorflowjs_converter --input_format=keras model.h5 tfjs_model

在这里,model.h5 是从预测维护数据集中保存的 Keras LSTM 模型,tfjs_model 是该模型将被放置的文件夹。

接着,打开 Visual Studio。在这里,我们将写两个文件。第一个文件将是一个 HTML 文件,而第二个文件将是一个 JavaScript 文件。创建这些文件后,您可以在本章的 GitHub 仓库中使用 webserver.py 文件在本地运行它们。这将在您的 web 浏览器中运行 index.html 文件和任何其他文件,地址为 http://localhost:8080/index.html。本章的 GitHub 仓库中还有一个 data.json 文件,表示一个 Web 服务,该服务会向网页返回数据。

如何实现...

这个案例的步骤如下:

  1. index.js 文件中,添加一个 GetData 函数,从 data.json 中获取数据:
async function GetData(){
$.get( "/data.json", function( data ) {
$( "#data" ).text( data["dat"] );
predict(data["dat"]) 
});
}
  1. 制作一个函数,引入模型并评估数据:
async function predict(dat)
{
    const model = await tf.loadLayersModel('/tfjs_model/model.json');
    console.log(model)
    dat = tf.tensor3d(dat, [1, 50, 25] )
    dat[0] = null
    console.log(dat)
    var pred = model.predict( dat)
    const values = pred.dataSync();
    let result = "Needs Maintenance"
    if(values[0] < .8)
        result = "Does not need Maintenance"

    $('#needed').html(result )
} 
  1. 创建一个 index.html 文件,该文件将调用您的 js 文件:
<!DOCTYPE html>
<html>
<head>
<title>Model</title>
<script src="img/tf.min.js"></script>
<script src="img/tfjs-vis.umd.min.js"></script>
</head>
<body>
    <button onclick="GetData()">Maintenance Needed</button>
<textarea id="data" style="width:400px;height:400px;"></textarea>
<div id="needed"></div>
</body>
<script src="img/jquery.min.js"></script>
<script type="text/javascript" src="img/index.js"></script>

</html>

工作原理是如何...

在这个案例中,我们采用了为 Python 编写的预训练模型,并使用转换工具将其转换为在 Web 上运行的模型。然后,我们从 Web 服务中获取数据,并将其与机器学习模型进行评估。最后,当我们的机器学习模型对涡轮风扇引擎的剩余寿命有 80% 的信心时,我们显示文本 "Needs Maintenance"

还有更多内容...

近年来,Web 浏览器的功能大大增强。其中一个方面是能够处理数据并在后台进行处理。例如,可以在本书的 GitHub 存储库中找到一个例子,称为Dexie,展示了向浏览器的数据库添加数据的示例。您还可以在现代 Web 浏览器中使用服务工作者。服务工作者是在 Web 浏览器中后台运行的后台作业。它们甚至可以在页面不活动时工作。

部署移动模型

许多物联网场景要求具有图形用户界面,即高级计算、蓝牙、Wi-Fi 和蜂窝网络。大多数现代手机都有这些功能。一个廉价的物联网设备可以通过蓝牙与智能手机上的应用程序通信,并使用该应用程序执行机器学习并与云端通信。

使用手机可以缩短物联网设备的上市时间。这些设备可以使用安全且易于更新的应用程序将数据发送到云端。手机的便携性是一个优点,但也是一个缺点。一个设备持续与云端通信可能会耗尽手机的电池,以至于它的电池寿命只有短短 8 小时。因此,公司经常倾向于使用边缘处理来执行诸如机器学习之类的计算任务。这使得设备可以更少地发送数据。

手机如何用于物联网是无处不在的。像 Fitbit 和 Tile 这样的公司使用低功耗的蓝牙低能耗BLE)将数据发送到消费者手机。物联网设备本身可以是低功耗的,并将大部分工作卸载到附加的手机上。其他设备,如患者心脏监测仪、仓库库存工具和语音激活的信息亭,可以拥有专门设计以满足应用程序需求的智能设备。

在这个示例中,我们将向您展示如何在 Android 上使用 TensorFlow Lite。我们将学习如何使用一个简单的 Android kiosk 关键词激活应用程序,并将其部署到设备上。然后,我们将学习如何将其侧载到设备上。

准备就绪

在这个示例中,我们将创建一个简单的 Android Studio 应用程序,并向其添加机器学习代码。为此,您需要下载并安装 Android Studio。从那里,创建一个新项目并按照以下步骤操作:

  1. 打开 Android Studio 后,从“开始”菜单中选择+开始一个新的 Android Studio 项目

  1. 然后,您需要选择一个 UI 模板。在这个示例中,我们将选择一个空的活动:

  1. 在接下来的屏幕上,您将看到一个向导,可以为项目命名并选择语言。对于这个项目,我们将选择Java作为我们的语言:

  1. 好了,一个新项目就这样打开了。 现在,我们需要将 TensorFlow Lite 导入到我们的项目中。 要做到这一点,请转到build.gradle(Module:app)部分,在Gradle Scripts下:

  1. build.gradle的 JSON 文件中,在dependencies部分下,添加对 TensorFlow Lite 的引用(implementation 'org.tensorflow:tensorflow-lite:+'):

  1. 从这里,在 Android Studio 中,右键单击app文件夹,选择New,然后选择Folder,然后Assets folder

这是我们将放置我们训练过的模型的地方。 就像我们在使用 TensorFlow.js 将数据外载至网络的方法中使用了tfliteJS转换工具一样,我们可以使用tflite转换工具来转换我们的模型。

如何操作...

此方法的步骤如下:

  1. MainActivity.java文件的头部部分,添加必要的 TensorFlow 引用:
import org.tensorflow.lite.Interpreter
  1. variables部分,初始化tflite解释器:
Interpreter tflite;
  1. OnCreate方法中,添加代码,从文件中加载模型到tflite解释器中:
tflite = new Interpreter(loadModelFile(activity));
  1. 然后,创建一个加载模型文件的方法:
private MappedByteBuffer loadModelFile(Activity activity) throws IOException {
 AssetFileDescriptor fileDescriptor = 
  activity.getAssets().openFd(getModelPath());
 FileInputStream inputStream = new 
  FileInputStream(fileDescriptor.getFileDescriptor());
 FileChannel fileChannel = inputStream.getChannel();
 long startOffset = fileDescriptor.getStartOffset();
 long declaredLength = fileDescriptor.getDeclaredLength();
 return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, 
                        declaredLength);
 }
  1. 在从蓝牙数据源调用的方法中执行必要的推理:
tflite.run(inputdata, labelProbArray);

它是如何工作的...

类似于使用 TensorFlow.js 将数据外载至网络的方法,此方法接受一个 TensorFlow Lite 模型,执行推理,并返回概率值。 TensorFlow Lite 模型适用于 Android 等小型设备,并可用于应用程序和服务。

使用设备双重维护您的设备群

设备双重是一套旨在帮助我们处理设备群的工具。 它们可用于将信息传递给设备,例如该设备应使用的模型。 它们也可用于将更有状态的信息传递回云端,例如模型的实际错误率。

设备的双面。 在设备一侧,有一个行为像可写配置文件的 JSON 文件,而在云端,则有一个可写属性数据库。 这两个方面有序地同步,允许您推理您的设备群。

设备双重的一个优点是您可以看到模型部署是否实际有效。 通常,机器学习模型会随信息变化而更新,并将新模型推送到设备上。 这些模型可能会触发内存不足异常并失败; 它们也可能会使设备变砖。 在物联网产品的生命周期中,如果制造商更改硬件或某些组件不再可用,通常可以更换硬件。

在开始之前,我们需要讨论一些基本概念。 我们将在它是如何工作的...部分进行更深入的研究。 设备双重由三部分组成:

  • 标签区域负责通用标签,如设备的名称、位置或所有者。 标签区域的数据由云端设置。

  • 接下来是期望的属性。期望的属性部分也由云端设置。在概念上,这是云端希望设备处于的状态。例如,这可能是一个模型版本或阈值。

  • 最后一个属性是一个报告属性。该属性由设备设置。它可以是数据值,也可以是对所需属性的响应。

例如,如果所需属性或模型版本发生更改,我们可以尝试更新到最新版本,并且如果更新成功,将我们的报告属性设置为所需版本。如果不起作用,我们可以在云中查询。我们还可以使用标签部分在称为更新环的集合中更新我们的设备。我们可以使用更新环来获取滚动更新,这使我们可以首先更新很少的设备,然后稍后更新多个设备。我们还可以根据设备的某些特征(例如位置和所有者)部署不同的模型。

准备工作:

在这个配方中,我们将使用 Azure IoT Hub 和 Python。我们示例中的 Python 版本需要在 3.6 以上。我们需要安装以下库:

pip3 install azure-iot-device
pip3 install asyncio

您还需要从 IoT Hub 获取设备连接字符串。在第一章的设置 IoT 和 AI 环境配方中,我们向您展示了如何在 Azure 中设置 IoT Hub。从那里开始,您需要获取该单独设备的密钥。为此,请导航到您创建的 IoT Hub,并在左侧面板中单击IoT 设备菜单项。然后,单击+按钮并添加具有对称密钥身份验证的设备:

在这里,您将看到设备显示在设备列表中,如下面的截图所示。您可以单击该项并获取设备密钥:

您还需要进入共享访问策略菜单项并复制服务策略连接字符串。此连接字符串用于连接到 IoT Hub,以便您管理设备群。先前的密钥适用于单个设备。

如何操作...

此配方的步骤如下:

  1. 在设备端,导入必要的库:
import asyncio
from six.moves import input
from azure.iot.device.aio import IoTHubDeviceClient
  1. 创建一个main()函数并连接到设备:
async def main():
    device_client = \
    IoTHubDeviceClient.create_from_connection_string("Connection
                                                     String")

    await device_client.connect()
  1. 创建一个孪生监听器:
    def quit_listener():
        while True:
        selection = input("Press Q to quit\n")
        if selection == "Q" or selection == "q":
            print("Quitting...")
            break
  1. 创建一个监听任务:
    asyncio.create_task(twin_patch_listener(device_client))
  1. 监听退出信号:
    loop = asyncio.get_running_loop()
    user_finished = loop.run_in_executor(None, quit_listener)
  1. 等待用户完成信号并断开连接:
    await user_finished
    await device_client.disconnect()
  1. 使用asyncio运行循环:
if __name__ == "__main__":
    asyncio.run(main())
  1. 在云端(这是帮助您管理设备群的计算机)使用以下代码设置期望的机器学习模型版本。首先,导入必要的库:
import sys
from time import sleep
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.models import Twin, TwinProperties
  1. 使用服务连接字符串连接到 IoT Hub:
iothub_registry_manager = \
IoTHubRegistryManager("Service Connection String")
  1. 设置所需的属性,使其成为模型版本:
twin = iothub_registry_manager.get_twin("Device_id")
twin_patch = Twin( properties= \
TwinProperties(desired={'Vision_Model_Version' : 1.2}))
twin = iothub_registry_manager.update_twin(DEVICE_ID, twin_patch, 
                                           twin.etag)
  1. 稍后,在另一个 Python 文件中,我们将查询版本是否已更新。首先,导入必要的库:
import sys
from time import sleep
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.models import Twin, TwinProperties, \
QuerySpecification, QueryResult
  1. 然后,我们可以查询所有具有报告属性但不符合我们期望属性的设备:
query_spec = QuerySpecification(query="SELECT * FROM devices WHERE properties.reported.Vision_Model_Version <> 1.2")
query_result = iothub_registry_manager.query_iot_hub(query_spec, None, 100)
print("Devices that did not update: {}".format(', '.join([twin.device_id for twin in query_result.items])))

工作原理...

这个示例有三个不同的代码段。第一个在设备端。这段代码收集了通过设备双胞胎对设备进行的任何更改。在接下来的部分中,我们指示 IoT Hub 更新特定设备的报告属性。然后,我们查询了我们的设备群,并检查是否所有设备都更新到我们想要使用的模型。

还有更多...

设备双胞胎基本上是一个大的 JSON 文件,位于云端和设备端。它可用于调整设置、控制设备并设置关于设备的元数据。还有另一个服务建立在设备双胞胎之上。它被称为数字双胞胎。数字双胞胎在设备和云端之间同步相同的 JSON 文件。它们还有将设备连接成图的额外好处。图是将设备链接在一起的一种方式。这可以根据地理位置完成。换句话说,您可以根据位置将设备连接在一起。它还可以在本地将设备连接在一起。当您拥有相关设备时,这非常有用。例如,智能城市希望具有地理相关性的设备。在这个智能城市中,我们希望知道某个地理位置的所有交叉口是否有交通停止。在工厂中,可能有包含相关数据的制造线。这些制造线可能包含数十个提供不同类型读数的物联网设备。数字双胞胎可以帮助我们诊断慢装配线的问题,并进行根本原因分析。

使用雾计算启用分布式 ML

在物联网中工作通常意味着处理大数据。传感器可能会有详细说明,设备也可能很大。例如,CERN 的粒子加速器每秒产生超过一 PB 的数据。将这些原始数据发送到中央存储库将是不切实际的。许多公司面临处理极大数据集或极快速数据集时可能会面临挑战。

在这个示例中,我们将把工作负载分布到几个系统中,从而允许一个系统拍摄图像,另一个处理图像。在我们的示例中,一个小设备可以拍摄图像并将其流式传输到工业 PC 或工厂中一组服务器。在这里,我们将使用dockerdocker-compose,而对于我们的算法,我们将使用 YOLO(一种图像分类算法)的 OpenCV 实现。

准备工作

本示例将在代码量上非常详细,但所有操作都将在 Docker 中完成。您可以使用 VS Code 的 Docker 扩展直接在 Docker 容器中工作。您还需要一个连接了摄像头的设备。这可以是带有摄像头的笔记本电脑或树莓派 – 这并不重要。在这个示例中,我们将设置一个机器学习服务、一个摄像头流服务,并允许设备知道其他设备的位置,并允许您查看您在整个设备群中的分类。

尽管这相当简单,但列出所有容器的代码将需要几十页。为了简洁起见,在这个示例中,我们将展示计算机视觉模块。其余模块可以使用 Docker 运行,并使用本书的 GitHub 仓库中的代码。

如何做…

本示例的步骤如下:

  1. 在您的计算设备上,下载 YOLO 的机器学习模型文件:
wget https://pjreddie.com/media/files/yolov3.weights
wget https://raw.githubusercontent.com/microshak/AI_Benchtest_Device/yolov3.txt
wget https://raw.githubusercontent.com/microshak/AI_Benchtest_Device/yolov3.cfg
  1. 创建一个 CPU 文件夹,并在其中创建一个 __init__.py 文件:
from flask import Flask
cpu = Flask(__name__)

from CPU.Yolo import yolo
from CPU.manifest import manifest
cpu.register_blueprint(yolo)
cpu.register_blueprint(manifest)
  1. 创建一个 manifest.py 文件,将计算服务器的功能发送到集中服务器:
from flask_apscheduler import APScheduler
from flask import Blueprint, request, jsonify, session
import requests
import socket
import json
import os
manifest = Blueprint('manifest','manifest',url_prefix='/manifest')
scheduler = APScheduler()

def set_manifest():
    f = open("manifest_cpu.json", "r")
    manifest = f.read()
    data = json.loads(manifest)
    data['host_name'] = socket.gethostname()
    gw = os.popen("ip -4 route show default").read().split()
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect((gw[2], 0))
    ipaddr = s.getsockname()[0]

    data['ip_address'] = ipaddr
    url = 'https://ai-benchtest.azurewebsites.net/device'
    r = requests.post(url = url, json =data)
    txt = r.text

set_manifest()
scheduler.add_job(id ='Scheduled task', func =set_manifest, 
                  trigger = 'interval', minutes = 10)
scheduler.start()
  1. 创建一个 Yolo.py 文件并导入必要的库:
import cv2
import pickle
from io import BytesIO
import time
import requests
from PIL import Image
import numpy as np
from importlib import import_module
import os
from flask import Flask, render_template, Response
from flask import request
import imutils
import json
import requests
from flask import Blueprint, request, jsonify, session
  1. 将页面初始化为 Flask 页面:
yolo = Blueprint('yolo', 'yolo', url_prefix='/yolo')
  1. 初始化我们的绘图变量:
classes = None
COLORS = np.random.uniform(0, 300, size=(len(classes), 3))
  1. 导入模型类名称:
with open("yolov3.txt", 'r') as f:
    classes = [line.strip() for line in f.readlines()]
  1. 创建一个获取输出层的帮助函数:
def get_output_layers(net):
    layer_names = net.getLayerNames()
    output_layers = [layer_names[i[0] - 1] for i in 
                     net.getUnconnectedOutLayers()]
    return output_layers
  1. 创建一个帮助函数,该函数将在识别到的对象周围绘制一个矩形并插入分类文本:
def draw_prediction(img, class_id, confidence, x, y, x_plus_w,
                    y_plus_h):
    label = str(classes[class_id])
    color = COLORS[class_id]
    cv2.rectangle(img, (x,y), (x_plus_w,y_plus_h), color, 2)
    cv2.putText(img, label, (x-10,y-10), cv2.FONT_HERSHEY_SIMPLEX, 
                0.5, color, 2)
  1. 创建一个 Yolo 方法,该方法接收图像和神经网络,然后缩小图像:
def Yolo(image, net):
    try:
        Width = image.shape[1]
        Height = image.shape[0]
        scale = 0.00392

        blob = cv2.dnn.blobFromImage(image, scale, (416,416), 
                                     (0,0,0), True, crop=False)
  1. 将图像设置为神经网络的输入,并执行 YOLO 分析:
        net.setInput(blob)
        outs = net.forward(get_output_layers(net))
  1. 初始化变量并设置置信度阈值:
        class_ids = []
        confidences = []
        boxes = []
        conf_threshold = 0.5
        nms_threshold = 0.4
  1. 将机器学习结果集转换为我们可以应用于图像的坐标集:
        for out in outs:
            for detection in out:
                scores = detection[5:]
                class_id = np.argmax(scores)
                confidence = scores[class_id]
                if confidence > 0.5:
                    center_x = int(detection[0] * Width)
                    center_y = int(detection[1] * Height)
                    w = int(detection[2] * Width)
                    h = int(detection[3] * Height)
                    x = center_x - w / 2
                    y = center_y - h / 2
                    class_ids.append(class_id)
                    confidences.append(float(confidence))
                    boxes.append([x, y, w, h])
  1. 抑制任何不符合阈值标准的边界框:
        indices = cv2.dnn.NMSBoxes(boxes, confidences, 
                                   conf_threshold,
                                   nms_threshold)
  1. 获取边界框并在图像内绘制它们:
        for i in indices:
            i = i[0]
            box = boxes[i]
            x = box[0]
            y = box[1]
            w = box[2]
            h = box[3]
            draw_prediction(image, class_ids[i], 
                            confidences[i], round(x),
                            round(y), round(x+w), 
                            round(y+h))
  1. 返回图像:
    return image
  1. 创建一个名为 gen 的函数,该函数将导入模型并持续从摄像头设备获取图像:
def gen(height,width, downsample, camera):

    net = cv2.dnn.readNet("yolov3.weights", "yolov3.cfg")
    while True:
        url = f'http://{camera}:5000/image.jpg?\
        height={height}&width={width}'
        r = requests.get(url) # replace with your ip address
        curr_img = Image.open(BytesIO(r.content))
  1. 调整图像大小并调整颜色:
        frame = cv2.cvtColor(np.array(curr_img), cv2.COLOR_RGB2BGR)
        dwidth = float(width) * (1 - float(downsample))
        dheight = float(height) * (1 - float(downsample))
        frame = imutils.resize(frame, width=int(dwidth), 
                               height=int(dheight))
  1. 执行机器学习算法并返回结果流:
        frame = Yolo(frame, net)

        frame = cv2.imencode('.jpg', frame)[1].tobytes()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + 
               frame + b'\r\n\r\n')
  1. 创建一个 Web 地址,将抓取 URL 参数并通过算法处理它们:
@yolo.route('/image.jpg')
def image():

    height = request.args.get('height')
    width = request.args.get('width')
    downsample = request.args.get('downsample')
    camera = request.args.get('camera')

    """Returns a single current image for the webcam"""
    return Response(gen(height,width, downsample, camera), 
                    mimetype='multipart/x-mixed-replace; 
                    boundary=frame')
  1. 回到根目录内,创建一个 manifest.json 文件,该文件将广播我们正在使用的机器的功能:
{
     "FriendlyName":"Thinkstation",
     "name":"Thinkstation",
     "algorithm":[{"name":"Object Detection"
                    ,"category":"objectdetection"
                    ,"class":"Computer Vision"
                    ,"path":"yolo/image.jpg"}

     ]
     , "ram":"2gb"
     , "cpu": "amd"
 }
  1. 创建一个 runcpu.py 文件。这将是启动 Flask 服务器并注册其他代码文件的文件:
 from os import environ
 from CPU import cpu

 if __name__ == '__main__':
     HOST = environ.get('SERVER_HOST', '0.0.0.0')
     try:
         PORT = int(environ.get('SERVER_PORT', '8000'))
     except ValueError:
         PORT = 5555
     cpu.run(HOST, PORT)

它是如何工作的…

这个边缘计算配方展示了如何将几种不同类型的系统集成到一起以作为一个整体运行。在本示例中,我们展示了从另一个系统获取视频流的设备代码,对其进行计算,然后将其传递给另一个系统。在这种情况下,我们的最终系统是一个 Web 应用程序。

不同系统之间要进行通信,需要有集中式的状态管理。在这个示例中,我们使用了 Flask 和 Redis。我们集群中的每台机器每 10 分钟注册一次其状态和能力。这样其他机器就可以利用网络上的机器,避免一个机器成为瓶颈。当新机器上线时,它只需向我们的状态服务器注册其状态;只要它继续广播,就可以供其他机器使用。

还有更多...

这个示例依赖于其他组件。这些组件位于本章节的 GitHub 存储库中,位于AI_Benchtest文件夹下。您可以进入各自的文件夹并运行dockerdocker-compose来启动这些程序。要在终端中运行摄像机服务器,请进入AI_Benchtest_API文件夹并运行以下命令:

docker-compose up

接下来,您需要运行AI_Benchtest_Cam模块。在终端中,CDAI_Benchtest_Cam文件夹,并运行与启动 API 服务器相同的docker-compose命令。此时,摄像机和计算服务器都将启动并向 API 服务器传输其状态。接下来,您需要运行一个 UI 服务器,以便可以向其他服务器发送命令。要做到这一点,请CDAI_Benchtest_API文件夹,并运行以下docker命令来启动 UI 应用程序:

docker build -t sample:dev . docker run -v ${PWD}:/app -v /app/node_modules -p 3001:3000 --rm sample:dev
posted @ 2024-07-23 14:53  绝不原创的飞龙  阅读(6)  评论(0编辑  收藏  举报