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

82 阅读43分钟

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

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

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章:异常检测

设备的预测/处方 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还是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 有几种个性,例如专业和对话。