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

103 阅读29分钟

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

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

译者:飞龙

协议:CC BY-NC-SA 4.0

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

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

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

在本章中,我们将重点介绍两种不同的开发板。第一种是ESP32,第二种是STM32。ESP32 是一种带有 Wi-Fi 功能的微控制器(MCU)。它们通常的成本在55 - 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