原文:IoT Machine Learning Applications

协议:CC BY-NC-SA 4.0

六、配置能量计

在第五章的继续部分,您将连接施尼德电气公司的三相 Conzerv EM6400 电能表,在这一章中,您将做两件事。首先,您将编写从 Arduino Mega 从机上的 EM6400 能量计获取数据的代码,然后在 Raspberry Pi 主机上将数据读入 Python。第二种方法是使用单相电气连接设置另一个电能表,然后通过 USB 串行 Modbus 协议将其数据直接读取到 Raspberry Pi 中。第二个设置是为了让那些希望了解工业设备(如电表和机器)如何产生真实数据的 IIoT 爱好者。然而,在现实世界中,您会看到 EM6400 等三相电能表和机器通过 Modbus 协议进行通信。我想再次提醒读者,我们使用 Arduino Mega 2560 是因为它是真正的工业级 SBC,支持连接多个设备,如相互串联或以菊花轮电气配置连接的电能表或工业机器。如果您只有一个器件,无论是单相、两相还是三相,您都可以直接将其连接到 Raspberry Pi 3B+,就像我在本章中展示的那样。因此,让我们开始编写代码,从 Arduino 上的 EM6400 能量计获取数据。你可以从 www.PMAuthor.com/raspbian/ 下载名为modbus01_EM6400_Tested.ino的代码包中的代码。记得在 Arduino IDE 中打开它,在执行代码之前,像我在第三章中展示的那样配置和测试 Arduino IDE。

EM6400 电能表的编码

在 Arduino IDE 中安装 Modbus 库是开始运行任何代码之前的第一步。这是必需的,这样您就可以获得一些 C 语言头文件。ModbusMaster.hSPI.h以及其他库可用于通过您的电能表的 Arduino 从机与 Modbus 通信。您可以打开如图 6-1 到 6-3 所示的库。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-1

打开“管理库”菜单

从菜单草图➤包括库➤管理库中打开管理库菜单或使用快捷键 Control + Shift + I,将打开库屏幕,如图 6-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-2

在 Arduino IDE 中打开库管理部分

现在您有了库管理器,它是 Arduino IDE 中为安装在其环境中的库维护的存储库。对开发人员来说,美妙的事情是您不需要担心文件存储或保存在哪里,或者与它们相关联的路径。图书馆管理员自己做这项工作;您只需要在代码中包含它所公开的库。在您的情况下,您需要 Modbus 库,它提供了前面提到的两个头文件,因此您的 Arduino 程序可以与电能表通信。在“过滤您的搜索”字段中键入 Modbus 并按回车键,以允许库管理器为您获取描述中包含 Modbus 的所有库。如图 6-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-3

Modbus 库的过滤器搜索

正如你在图 6-3 中看到的,这里有一个名字中带有 Modbus 的库的列表。你只需要第一个,Arduino 的 ArduinoModbus,版本 1.0.1。请注意,当您在 IDE 中执行此操作时,您可能会看到不同的版本,因为 Arduino 组织通过修复错误和向用户提供增强功能来不断更新其库。点击 Modbus 库描述下方的安装按钮安装 Modbus 库后,你会看到一条屏幕信息“已安装”,如图 6-3 所示。请注意,“Install”按钮是灰色的,因为我已经在我的环境中安装了它。成功完成后,您可以关闭库管理器窗口并返回到 Arduino IDE 中的代码窗口,如图 6-4 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-4

加载名为 modbus01_EM6400_Tested.ino 的代码文件

在前几行代码中,您会看到 include 语句#include <ModbusMaster.h>。这是已经加载的库。在 include 语句之后,继续声明全局变量,如清单 6-1 所示。

// ****** GLOBALS ******
ModbusMaster node;  // instantiate ModbusMaster object
float reg1;         // will store the float value found at 3910/3911
float reg2;         // will store the float value found at 3914/3915
int low_word;       // this will hold the low 16-bit resgter value after a read
int high_word;      // this will hold the high 16-bit resgter value after a read

Listing 6-1Code for Declaring Global Variables

第一个全局变量是reg1,它是一个浮点型变量,用于存储来自 Conzerv EM6400 电能表的寄存器 3910 和 3911 中的值。寄存器是通过 Modbus 协议找到各种能量参数值的实际位置。Schnieder Electric 在 EM6400 用户手册的官方网址: www.schneider-electric.com/resources/sites/SCHNEIDER_ELECTRIC/content/live/FAQS/345000/FA345958/en_US/EM%206433%20series%20Communication.pdf 中提供了这些信息。本手册还在第 55 页的“通信测试”标题下为您提供了使用免费软件测试协议的简单方法。你可以自己试试这个;这超出了本书的范围,所以我把它留给你。好在手册用截图描述了整个过程,极其容易理解。标题为“数据地址”的第 57 页给出了一个详细的表格,其中列出了电能表可以通过 Modbus 协议传输的各种电气数据参数。该表包含单个参数 address,并提供了获取特定数据所需的寄存器地址号信息。例如,对于线对中性电压值(VLN)的数据值,寄存器地址是 3911。值的类型是 float,所以您在代码中使用一个相应的变量(图 6-5 )作为寄存器地址 3910 和 3911 的float reg1和用于存储寄存器 3914/3915 的值的float reg2。请注意,每个寄存器都是 16 位的,因此每个寄存器都有一个高位字和一个低位字,所以在通过 Arduino Mega 2560 从电能表读取全局变量int low_word;int high_word;后,您需要声明它们。现在,您已经了解了寄存器的概念以及数据是如何通过 Arduino Mega 从电表传输回来的,您可以看看将它们转换为浮点值的联合结构。这在清单 6-2 中给出。

// converting to floating point - union structure
union u_tag {
  int bdata[2];
  float flotvalue;
} uniflot;

Listing 6-2Union Structure to Store Register Values from the Energy Meter

这里声明了一个数组int variable bdata[2],它存储两个值:来自寄存器的高位字和低位字。浮点变量flotvalue实际上将值转换并存储为浮点数据类型,因为它将获得的实际值是浮点数据类型,这是程序工作所需要的。在稍后代码的loop()功能中,这种联合结构的用法将变得更加清晰。现在,让我们声明一些函数,它们将帮助您使用 Modbus 接口协议完成与电能表的通信过程。通信顺序如图 6-5 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-5

Arduino Mega 2560 与电能表之间的通信

如您所见,Arduino Mega 和电能表之间有三次传输。首先是预传输,然后是实际的数据传输,在数据从电能表传回 Arduino 后,是后传输。理解这个循环很重要,因为代码分为这三个序列。

您一定在第五章中注意到,当您配置 Arduino Mega 板并将引脚连接到板上时,有两个引脚(黄色和棕色)连接到标记为 Rx 和 Tx 的引脚。这些是实际交流发生的地方。当通过这些引脚读取数据时,Arduino Mega 2560 板上有一个名为 ATMEL MEGA 16U2 的芯片,它的 led 会亮起;你可以清楚地看到它们在向 Arduino 板反馈信息,表明你的连接是正确的。然而,这并不能保证您将获得数据;这取决于许多因素,如函数中传递的正确寄存器和参数数等。引脚 2 和 3 分别连接到 TTL 至 RS485 转换器模块上的橙色和绿色导线,进而连接到电能表。它使用 TTL 与电能表通信;然而,通过 PCB 上的芯片,它可以将其转换回 Modbus 协议,反之亦然。为了获取数据并初始化它们,以便它们准备好来回传输数据,你需要定义变量,如清单 6-3 所示。

// ****** DEFINES ******
#define MAX485_DE      3
#define MAX485_RE_NEG  2

Listing 6-3Defining the Pins for Communicating with the Energy Meter

请记住,由变量MAX485_DE标记的 DX 或 DE 通过 Arduino Mega 板上的 3 号引脚进行通信,该引脚依次连接到标有相应 DE 公引脚的 Modbus 转换器模块。同样,变量MAX485_RE为负,并通过 Arduino 板上标记的引脚 2 进行通信,该引脚依次连接到其上标记的相应 RE 公引脚上的 Modbus 转换器模块。

在传输前和传输后,后续功能的主要工作是写入这些引脚,以开始和结束通信。这显示在清单 6-4 中的代码片段中。

// ****** Transmission Functions ******
void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
  Serial.println("preTransmission");
}

void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
  Serial.println("postTransmission");
}

Listing 6-4Pre- and Post-transmission Functions

代码中给出的两个函数通过使用digitalWrite(MAX485_RE_NEG, 1);来初始化函数void preTransmission(){}中的通信。此函数初始化 RE 引脚# 2 以开始写入。digitalWrite功能中的第一个参数是管脚号,在变量MAX485_RE_NEG中定义,第二个参数是开/关二进制参数,允许写入值为 1 的管脚,并通过传递值 0 停止写入。接下来,MAX485_DE被初始化,然后postTransmission(){}功能将值 0 传递给两个引脚以停止传输。现在您已经有了这些函数,您已经准备好执行 Arduino Mega 的标准函数,它们是void setup()void loop()。回想第三章,首先在程序中定义void setup()功能,以初始化和设置 Arduino Mega 2560 的代码,使其开始运行。对于此功能,首先初始化引脚 2 和 3 的输出,以便开始使用 Modbus 485 协议控制通过它们的数据流。完成后,您需要初始化相同引脚的接收模式。之后,您可以设置波特率频率,以此频率接收和发送信号。这个电表的默认值是 9600 赫兹,我不打算改变它,因为它在这个速率下通信良好。此后,为该从机定义一个从机 id。如果您有多个从机连接到 Arduino Mega 板,您可以在此部分为它们分配多个从机 id。此后,调用preTransmission()postTransmission()功能。作为一种检查 Arduino 和电能表之间的通信是否正确的方法,您可以编写类似“Hello World”的代码这正是您在清单 6-5 中看到的。

// ****** STANDARD ARDUINO SETUP FUNCTION ******
void setup() {

  // make pins 2 and 3 output pins for Max485 flow control
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);

  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  Serial.begin(9600);    // TX0/RX0 serial monitor
  Serial1.begin(9600);   // TX1/RX1 Modbus comms

  // Modbus slave ID = 1
  node.begin(1, Serial1);

  // Callbacks allow us to configure the RS485 transceiver correctly
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);

  Serial.println("Hello World");

}

Listing 6-5The void setup() Function

Arduino 将Serial.println()函数作为输出发送到串行端口。因此,您可以使用 Python 程序从 Arduino Mega slave 所连接的 Raspberry Pi 主机读取这些语句。现在,您已经在 void setup()函数中完成了 Arduino 的初始配置,您可以进入最后一步,定义您希望草图使用 Arduino Mega 执行的主要工作。首先,您需要从能量计中读取值,为此您需要定义变量来保存结果和数据,如清单 6-6 所示。

// ****** STANDARD ARDUINO LOOP FUNCTION ******
void loop() {

  uint8_t result;

  // Read Line to Neutral Voltage
  result = node.readHoldingRegisters(3910, 2);
   if (result == node.ku8MBSuccess)
  {

    high_word = node.getResponseBuffer(0x00);
    low_word = node.getResponseBuffer(0x01);

    uniflot.bdata[1] = low_word;   // Modbus data 16-bit low word
    uniflot.bdata[0] = high_word;  // Modbus data 16-bit high word

    reg1 = uniflot.flotvalue;

    Serial.print("Line to Neutral Voltage: ");
    Serial.println(reg1);

  }
//  node.clearResponseBuffer();
  delay(500);   // small delay between reads

  //Read Frequency
  result = node.readHoldingRegisters(3914, 2);
  if (result == node.ku8MBSuccess)
  {
    high_word = node.getResponseBuffer(0x00);
    low_word = node.getResponseBuffer(0x01);

    uniflot.bdata[1] = low_word;   // Modbus data 16-bit low word
    uniflot.bdata[0] = high_word;  // Modbus data 16-bit high word

    reg2 = uniflot.flotvalue;

    Serial.print("Frequency: ");
    Serial.println(reg2);
  }

  delay(5000);    // repeat reading every 5 seconds
  node.clearResponseBuffer();

}

Listing 6-6Code for void loop()

由于这个草图是建立在 C 和 C++之上的,所以 C 和 C++中使用的函数及其数据类型也明显集成到了这种语言中。在 sketch 语言中,8 位无符号整数由数据类型 uint8_t 表示,这是一种在 C++编译器上具有包装器的编译器。您定义了这个数据类型的result变量来存储保持寄存器的结果,您将在下一行中尝试这样做。首先使用语句result = node.readHoldingRegisters(3910, 2);从保持寄存器# 3910 读取中性电压值。在草图中自动可用的节点对象用于与多个 Arduino 从属设备通信。在您的情况下,只有一个,并且您已经将它连接到树莓 Pi。您使用语句node.begin(1, Serial1);初始化它,在void setup()函数中给它一个序列号从属 id 1。因此,现在当您调用节点对象时,草图会自动将它作为您之前初始化的节点。第二参数值 2 表示第一参数 3910 中提到的寄存器的字节长度。

下一段代码使用一个if语句来检查从 Arduino 从机收到的响应是否正常。为了检查这一点,您需要首先了解 Modbus 主库中给出的 Modbus 功能代码和异常代码。这是 Arduino 库,用于通过 RS232/485(通过 RTU 协议)与 Modbus 从机通信(参见 http://4-20ma.io/ModbusMaster/group__constant.html )。虽然这个 Arduino 库有各种变量可供使用,但您想要的特定变量是ku8MBSuccess,它仅在 Modbus 主设备和从设备之间的通信中发现以下检查正常时返回 true:

  1. 从属 ID 有效。

  2. 功能代码有效。

  3. 响应代码有效。

  4. 返回有效数据。

  5. CRC 是有效的。数据传输没有错误。

你可以在这里了解更多: http://4-20ma.io/ModbusMaster/group__constant.html#ga81dd9e8d2936e369359777d67769a657

一旦这个条件被if语句测试为真,通过在high_word = node.getResponseBuffer(0x00);low_word = node.getResponseBuffer(0x01);语句中给出寄存器的起始和结束地址,就可以从寄存器中获得数据值。作为参考,0x00 是高位字的十六进制值 0,0x01 是第二个低位字的十六进制值 1。现在您需要将high_wordlow_word变量的值连接成一个 float 类型的变量。您创建了一个 union 结构,它有一个名为unifloat的 float 值变量。reg1 = uniflot.flotvalue;语句返回第一个寄存器的完整浮点值,这是线到中性电压的值。使用语句Serial.println(reg1);打印出该值。既然变量中有了线电压值,就可以得到另一个频率值,它存储在寄存器地址 3914 中。你增加了 500 毫秒的延迟;这是可选的,因为它取决于您希望如何处理数据。如果您正在创建一个监控能源设备电流吞吐量的报警系统,您可能不希望出现延迟,因为您需要线路中性电压和电流频率的实时数据。然而,如果您有多个从机,非常频繁地轮询电能表会给 Raspberry Pi 带来负载,这是您在编写这部分代码时需要记住的应用程序设计的一个方面。清单 6-7 中的代码从寄存器地址 3914 获取数据,也是 2 字节长。

delay(500);   // small delay between reads

  //Read Frequency
  result = node.readHoldingRegisters(3914, 2);
  if (result == node.ku8MBSuccess)
  {
    high_word = node.getResponseBuffer(0x00);
    low_word = node.getResponseBuffer(0x01);

    uniflot.bdata[1] = low_word;   // Modbus data 16-bit low word
    uniflot.bdata[0] = high_word;  // Modbus data 16-bit high word

    reg2 = uniflot.flotvalue;

    Serial.print("Frequency: ");
    Serial.println(reg2);
  }

Listing 6-7Code for Getting the Values of the Current Frequency

使用if条件,通过语句if (result == node.ku8MBSuccess)检查从电能表到 Arduino 从机的数据传输是否成功。一旦成功,if语句块与图 6-11 中的代码块非常相似,除了变量reg2的使用,该变量用于存储第二个寄存器的浮点值,该值包含寄存器中的当前频率值。使用语句Serial.println(reg2);,将显示频率的值打印到串行总线上。在关闭功能void loop()之前,添加 2 秒的延迟,以便在连续读取之间有一些时间。同样,这种时间延迟取决于您正在构建的应用程序的类型,并且可以根据应用程序的业务需求进行更改。请记住,这里的时间延迟越短,应用程序在三个设备上占用的 CPU 和内存等系统资源就越多:Raspberry Pi 3 B+、Arduino Mega 2560 和 EM 6400 电能表。在这之后,您清除语句node.clearResponseBuffer() ;中的响应缓冲区。这是为了确保旧值不再被收集,新值从寄存器进入缓冲器,然后进入 Arduino 串行总线。

  delay(2000);    // repeat reading every 2 seconds
  node.clearResponseBuffer();

这就结束了对程序的 Arduino 端进行编程的代码。您可以使用第三章和第五章中所示的步骤对其进行编译并上传至 Arduino Mega 2560。您应该能够看到代码编译成功并上传成功消息,如图 6-6 和 6-7 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-7

上传 EM6400 代码后,出现“上传完成”消息

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-6

编译 EM6400 代码后出现“编译完成”消息

您已经完成了代码的 Arduino 端的步骤。然而,在 Arduino 方面,您不会看到线到中性线电压和频率的结果。请记住,您是通过语句Serial.write()将所有内容写入串行端口的。要读取这些值,您需要编写一个非常小的 Python 程序,向您显示 Arduino Mega 2560 正在向其串行端口写入什么。

首先需要安装一个名为 serial 的 Python 包或模块,可以在 Raspberry Pi 3 B+命令行上完成,如图 6-8 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-8

在 Raspberry Pi 3 B+上安装 Python 中的串行模块

Python 安装默认有这个模块,所以您可能不需要安装它。您可以通过在 Python 提示符下运行简单的导入语句import serial来检查它是否需要安装,如图 6-9 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-9

检查是否安装了串行 Python 模块

如果您得到一个错误,那么您的 Python 版本的模块没有被安装。如果您没有得到一个错误,并且 Python 提示符>>>在没有给出任何消息的情况下返回,这表明库模块已经存在,您不需要再次安装它。

现在您已经安装了串行库,您可以继续为 Python 编写代码来从串行端口获取数据。参见清单 6-8 。

import serial
ser= serial.Serial('/dev/ttyACM0', 9600)
while 1:
    print("Reading...")
    print(ser.readline())

Listing 6-8Python Code to Read Data from the Arduino Serial Port

在这个 Python 程序中,首先导入serial对象,然后通过给定第一个参数/dev/ttyACM0,用serial.Serial()函数创建一个名为ser的对象;通常这是 Arduino 通信的串行端口。第二个参数9600是它与电能表通信的波特率。这是您在图 6-10 的代码块中设置的频率值。如果您出于某种原因更改了该频率,您需要在此将其更改为相同的值,否则由于波特率频率不匹配,通信将不会发生。当您运行这个 Python 程序时,您将看到类似于清单 6-9 中给出的输出。代码ser.readline()从连接到 Arduino Mega 2560 的串行端口读取一行值,然后依次读取到 EM6400 电能表。

>>> %Run arduino.py
Reading...
b'Hello World\r\n'
Reading...
b'preTransmission\r\n'
Reading...
Line to Neutral Voltage:
221.5
Frequency:
49.67
b'postTransmission\r\n'

Listing 6-9Sample Output of the Python Program on Raspberry P 3 B+

您可以在示例输出中看到,程序以写入preTransmission开始,然后打印“线到中性线电压:”及其值 221.5,然后打印“频率:”及其值 49.67。这每 2 秒钟运行一次无限循环,每次设置运行时,串行通信输出中都会显示“Hello World”。您可以将其更改为有意义的内容,如“安装已初始化”我让你决定。

至此,Conzerv EM6400 电能表的配置和设置工作结束。现在,正如我在本章开始时所承诺的那样,您将只需一个单相双线设置就能配置一个电能表,这种设置和配置要简单得多。该电表由 Eastron SDM630 制造,它也具有 Modbus 接口。这次你不打算用 Arduino Mega 2560 您将使用 Python 和 Raspberry Pi 直接从中获取数据。

现在,您将首先通过单相连接与一台伊士龙 SDM 630 连接。我不建议以串行或菊花轮方式连接一个以上的电表,因为与 Arduino Mega 2560 不同,Raspberry Pi 3 B+不是坚固的工业级电路板,很容易烧毁。因此,我在这里给出的连接系统仅用于一个小项目,不像前面章节中的 Conzerv EM6400 连接和配置。

Eastron SDM630 能量计(图 6-10 )由一家中国公司制造,在全球范围内都很容易买到。如果你需要的话,可以查看 www.pmauthor.com/raspbian 上的链接。这款智能电表是最受欢迎的家用和工业用电表之一,同时支持三相连接。我推荐这一款,因为我和我的团队已经针对客户项目的个人和商业应用对它进行了充分的测试,它表现良好。虽然市场上有一些竞争对手,但我们最常用这款。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-10

Eastron SDM630

第一步是将电表连接到 Raspberry Pi,为此,您需要以下组件:

  1. rs485 modbus usb 转换器

  2. 至少 1 米长的双线,可通过 5V 电流

  3. Eastron SDM630 电能表

  4. 单相机器,如电机或铣床,具有电子线圈和电机以产生无功功率

  5. 至少 20W LED 灯泡产生有功功率

www.pmauthor.com/raspbian/ 中提到了所有列出的项目及其规格。让我们开始将它们连接在一起。

RS485 USB 转换器需要用双线连接,如图 6-11 所示。可以看到要连接的具体端子。USB 转换器的底部有两个端子引脚。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-11

rs485 usb 转换器

它们被标记为 A 和 B。请注意,它们是电能表中标记为 A 和 B 的相同端子,如图 6-12 所示。如果您将粉色电线插入标有 A 的端子,那么您必须将同一根粉色电线的另一端连接到标有 A 的电能表端子;如果你反其道而行之,可能会损坏电能表,所以要记住这一点。同样,将标有 USB 转换器端子 B 的灰色电线连接到电能表中标有 B 的电线。这就完成了 USB 转换器与电能表的连接。为了您自身的安全,请确保树莓派和电能表都没有运行。确保它们处于关闭位置,并从任何电气端子上拔下。这是为了防止意外冲击。此外,除非您的电气系统中安装了 MCB 或断路器,否则切勿连接电源,这样,万一发生短路,MCB 会跳闸,不会对电气布线系统造成损坏。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-12

连接 RS485 Modbus 转换器的电线

查看图 6-13 中相应的接线,灰色线连接到 Eastron SDM630 电能表的 B 端子,粉色线连接到 A+端子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-13

连接到 Eastron SDM630 内部数据端子的电线

现在双股线的两端都已连接,您必须连接 SDM630 电能表两次。一个是输入线连接,来自电源并为电能表供电;另一个是输出线连接,为负载或电流源(如 LED 灯泡、电机或铣床)提供电流。这在图 6-14 中进行了描述。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-14

Eastron SDM630 电能表的输入连接

如您所见,电表的输入端有四个接线端子。在四个端子中,由于您在此电能表中使用的是单相双线模式,因此您只需要用黑线或零线连接 4 号端子,用红线或电源线连接 1 号端子,也称为火线。一旦你连接好这些电线,你就可以用插头连接这些电线的插座端。请注意,在此架构中,您没有使用第三根线,即地线。为方便起见,插头连接如图 6-15 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-15

电能表输入线的插头连接

现在,您必须连接到电能表的输出端子,并在另一端放置一个负载,如 LED 灯泡、电动机或铣床。记住它们都需要是单相的。因此,如图 6-16 所示连接它们,其中输出红色或火线连接到端子 6,输出黑色或中性线连接到端子 8。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-16

电能表的输出接线

连接完成后,您需要将端子线的另一端连接到负载,如 LED 灯泡、单相铣床或电动机。在图 6-17 中,可以看到一个 LED 灯泡作为负载连接;然而,这不会给出非常真实的数据,因为无功功率总是为零。不过,这是测试电路的好方法。另一端的负载可以随时被其他负载替换。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-17

连接到电能表输出端的 LED 灯泡

在此阶段,所有的输出和输入连接都已完成,您可以通过将电能表从输入端连接到电源插座来为其通电。您应该会看到电能表开始工作,LED 灯泡亮起,分别如图 6-18 和 6-19 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-19

通电后的 LED 灯泡和电能表

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-18

通电后的 Eastron SDM630 电能表

这就完成了硬件方面的设置。请记住,您尚未将树莓 Pi 3 B+连接到能量计。这只是为了检查与电能表的所有连接是否正常工作,以及负载是否与电能表一起通电。如果成功,您可以连接 Raspberry Pi,然后使用 Python 程序从中获取数据,并将其存储在 SQLite3 数据库中。给电能表上电后,可以给树莓 Pi 3 B+上电。但在此之前,请记得将 RS485 Modbus 转换器连接到树莓 Pi 中的一个 USB 端口,如图 6-20 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-20

将 RS485 Modbus 转换器连接到 Raspberry Pi 3 B+

请注意,该 RS485 转换器模块 PCB 上没有 LED 灯,因此除了 Python 软件之外,无法判断它是否正在接收来自电能表的信号。它被命名为sdm630_tested.py,你可以从 www.pmauthor.com/raspbian/ 的捆绑包中解压它。清单 6-10 中给出了完整的代码。在运行 Python 代码之前,您需要安装 Raspberry Pi 使用 RS485 Modbus 协议与电能表通信所需的 Python 库。为此,您需要安装一个名为 minimalmodbus 的 Python 库。首先,运行命令sudo apt-get update,然后运行sudo apt-get upgrade,因为保持操作系统库和内核的更新和升级是一个很好的实践,这样新的 Python 库就不会因为依赖于新的 Raspbian 结构而失败。图 6-21 和 6-22 显示了该安装程序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-22

升级 Raspbian 内核

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-21

更新 raspbian 内核

安装好 minimalmodbus Python 模块后(图 6-23 ,就可以加载 Python 代码并运行了(清单 6-10 )。提醒一下:此时,您的电表应该已经启动,并且使用 RS485 Modbus 转换器连接了 Raspberry Pi 3 B+。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-23

安装 minimalmodbus Python 模块

#!/usr/bin/Python
#Loading modbus library
import minimalmodbus
from datetime import datetime

#Initializing searial communication on the modbus library
sdm630 = minimalmodbus.Instrument('/dev/ttyUSB0', 1)
sdm630.serial.baudrate = 9600
sdm630.serial.bytesize = 8
sdm630.serial.parity = minimalmodbus.serial.PARITY_NONE
sdm630.serial.stopbits = 1
sdm630.serial.timeout = 1
sdm630.debug = False
sdm630.mode = minimalmodbus.MODE_RTU

print(sdm630)

while 1:
        Volts = sdm630.read_float(0, functioncode=4)
        Current = sdm630.read_float(6, functioncode=4)
        Active_Power = sdm630.read_float(12, functioncode=4)
        Apparent_Power = sdm630.read_float(18, functioncode=4)
        Reactive_Power = sdm630.read_float(24, functioncode=4)
        Power_Factor = sdm630.read_float(30, functioncode=4)
        Phase_Angle = sdm630.read_float(36, functioncode=4)
        Frequency = sdm630.read_float(70, functioncode=4)
        Import_Active_Energy = sdm630.read_float(72, functioncode=4)
        Export_Active_Energy = sdm630.read_float(74, functioncode=4)
        Import_Reactive_Energy = sdm630.read_float(76, functioncode=4)
        Export_Reactive_Energy = sdm630.read_float(78, functioncode=4)
        Total_Active_Energy = sdm630.read_float(342, functioncode=4)
        Total_Reactive_Energy = sdm630.read_float(344, functioncode=4)

        print('Voltage: {0:.1f} Volts'.format(Volts))
        print('Current: {0:.1f} Amps'.format(Current))
        print('Active power: {0:.1f} Watts'.format(Active_Power))
        print('Apparent power: {0:.1f} VoltAmps'.format(Apparent_Power))
        print('Reactive power: {0:.1f} VAr'.format(Reactive_Power))
        print('Power factor: {0:.1f}'.format(Power_Factor))
        print('Phase angle: {0:.1f} Degree'.format(Phase_Angle))
        print('Frequency: {0:.1f} Hz'.format(Frequency))
        print('Import active energy: {0:.3f} Kwh'.format(Import_Active_Energy))
        print('Export active energy: {0:.3f} kwh'.format(Export_Active_Energy))
        print('Import reactive energy: {0:.3f} kvarh'.format(Import_Reactive_Energy))
        print('Export reactive energy: {0:.3f} kvarh'.format(Export_Reactive_Energy))
        print('Total active energy: {0:.3f} kwh'.format(Total_Active_Energy))
        print('Total reactive energy: {0:.3f} kvarh'.format(Total_Reactive_Energy))
        print('Current Yield (V*A): {0:.1f} Watt'.format(Volts * Current))

        import sqlite3
        conn = sqlite3.connect('/home/pi/IoTBook/Chapter6/energymeter.db')
        #df.to_sql(name='tempdata', con=conn)
        curr=conn.cursor()
        query="INSERT INTO sdm630data(timestamp, voltage , current ,activepow ,apparentpow ,reactivepow ,powerfactor ,phaseangle , frequency , impactiveng , expactiveeng ,impreactiveeng , expreactiveeng ,totalactiveeng ,totalreactiveeng ,currentyield, device ) VALUES(" + "'" + str(datetime.now()) + "'" + "," + "'" + str(Volts) + "'" + "," +  "'" + str(Current) + "'" + "," +  "'" + str(Active_Power) + "'" + "," +  "'"  + str(Apparent_Power) + "'" + "," +  "'" + str(Reactive_Power) +  "'" + "," +  "'" + str(Power_Factor) + "'" + "," +  "'" +  str(Phase_Angle) + "'" + "," +  "'" + str(Frequency) + "'" + "," +  "'" +  str(Import_Active_Energy) + "'" + "," +  "'" + str(Export_Active_Energy) + "'" + "," +  "'" + str(Import_Reactive_Energy) + "'" + "," +  "'" + str(Export_Reactive_Energy) + "'" + "," +  "'" + str(Total_Active_Energy) + "'" + "," +  "'" + str(Total_Reactive_Energy) + "'" + "," +  "'" + str((Volts * Current)) + "'" + "," + "'" + str("millmachine")+ "'" + ")"
        print(query)
        curr.execute(query)
        conn.commit()

Listing 6-10Python Code to Get Data from the Energy Meter and Store It in a Database

在这段代码中,首先要导入 minimalmodbus Python 库,以便在 Raspberry Pi 和电能表之间实现 modbus 通信。然后,您导入 datetime 来存储一个时间戳以及来自能量计的一条数据记录。它的用法在代码的后面。之后,您通过 Rs485 Modbus USB 转换器初始化 Modbus 仪器,该转换器在 Raspbian 设备列表上连接为/dev/tty/USB0。这是默认设备,在大多数情况下应该可以工作;但是,如果您遇到任何通信错误,请使用命令lsusb来检查您的 485 转换器连接到哪个 tty 端口。接下来,以 9600 的波特率初始化串行端口通信。这是默认值;如果您已经更改了电能表的波特率,您需要在代码语句sdm630.serial.baudrate = 9600中将其更改为新值。将寄存器的字节大小初始化为 8,并设置其他参数,如停止位和超时间隔。print(sdm630)将对象打印到 Raspbian 上的控制台,因此您应该可以通过这个打印语句看到所有的连接设置配置。在此之后,您的电能表设置完成,现在您可以与它通信并从中获取数据。为此,在下一段代码中,您使用一个无限的while循环将数据连续打印在屏幕上,然后最后将其存储在 SQLite 3 数据库中。该代码与 Conzerv EM6400 电能表的代码非常相似,因此我不会深入研究。然而,需要注意的是,您将从电能表获得总共 15 个值,包括其电压、电流、有功功率和无功功率与电流产出的比值。运行程序后,您应该会看到类似于图 6-24 的输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-24

sdm630_tested.py 程序的输出

用于将数据存储到 SQLite3 数据库中的insert into语句的输出进入名为sdm630data的表中。表格创建脚本在文件sdm630_db_creation_script.sql中,这个文件和您从www . pm author . com/rasp bian/下载的 zip 文件一起提供。

摘要

这就完成了设置、配置,并将数据从电能表输入数据库系统。到目前为止,您已经成功下载了用于设置电表设备的脚本。您通过使用 Arduino IDE 中的 Modbus 库为 EM6400 电能表编码。您还通过编写一个小的“Hello World”程序,学习并实现了 Arduino Mega 2560 和 Conzerv EM6400 电能表之间的前后通信。然后编写代码,通过电表的寄存器从电表中获取数据值。之后,您配置了另一个电能表,即更常见的 Eastron SDM630。连接硬件,然后加载所需的 Python 库,用于 Raspberry Pi 和 SDM360 电能表之间的 Modbus 通信。最后,您编写了一个 Python 程序来从电能表获取数据,如电压、电流和功率,并将其存储在 SQLite3 数据库中。

接下来,您将研究一些案例研究,并设计一些有趣的物联网和 IIoT 解决方案。

七、电信行业案例研究:利用物联网解决通话丢失问题

这个案例研究是基于一个虚构的民族国家,一个虚构的公司和一个虚构的场景。然而,通过本案例研究,您将获得一个近乎真实的掉话问题,这是大多数电信公司都在努力解决的问题。本案例研究将带您了解一家电信运营商的形成过程,为您提供足够的业务背景。这个案例研究给你一个共同的政治和商业背景,以及相对统一的社会政治利益,因此一个特定的国家对此并不重要。在这个案例研究中包括政治和商业方面的背景的原因是为了让你的机器学习工程师意识到基于你建立的预测模型的决策变得多么重要,以及它们如何能够对商业和整个社会产生重大影响。现在让我们进入案例研究。

电信案例研究概述

Regoniatel 于 2016 年开始运营,是 Regonia 的 2G 电信运营商,Regonia 是一个三面环山的国家。

首席执行官是 Kaun Methi John。他还有其他生意,比如采矿和炼油,通过这些生意他变得富有。他在这个欧洲小国建立了一个帝国。

当 Regonia 将新的 2G 技术应用于移动电话业务时,政府向感兴趣的企业征集建议。约翰明白这是他拓展并进入电信行业的黄金机会,这一行业在未来将会蓬勃发展。由于他不具备在本国建立电信基础设施的技术专长,他积极在全球范围内寻找合作伙伴,以帮助他在这项新事业中取得成功。他找到了 Samtel,一家总部位于美国的公司,该公司正在与其他公司建立合资企业。

项目计划和可行性研究完成后,联合合作伙伴为政府制定了 2G 频谱提案。投标给了他们和另外三家公司。他们成功地获得了政府合同;然而,在建立电信网络和让客户加入网络方面将会有激烈的竞争。其他三家公司也是由当地公司和国际电信公司合资成立的。政府认为将合同授予这样的合资企业是明智的,希望可以利用国际经验。

政府对该国的 2G 网络进行了数月的测试,以确定其可行性。一旦内部电信机构成功做到这一点,企业开始运营的大门就打开了。

该国 1 万亿美元的经济增长要求通信基础设施升级。这个国家的主要职业是农业和采矿业。这个国家四面被陆地包围;它没有海岸。人们对这个国家有幸拥有的黄金和钻石矿很感兴趣,它们是很大的收入来源。在金矿或钻石矿工作的矿工平均年收入为 6000 美元。工资较高的原因是这些矿山出产一些最好的黄金和钻石。这个国家的钻石需求量很大,全世界都在寻找。采矿业对 Regonia 的 GDP 贡献了近 50%。

这个国家日益增长的需求需要一个良好的电信基础设施来为其公民提供强大的通信系统。这个小国能负担起世界上最好的基础设施。

在获得频谱合同后的三年里,Regoniatel 在市区建立了良好的基础设施。它的竞争对手 Ytel 和 Fretel 也在城市地区和农村地区设立了移动塔。在开发办公基础设施四年后,索特勒面临的问题是,它在很大程度上变成了一家面向城市的公司。

政府设立了一个电信监管机构,公布所有公司电信用户的客户投诉和网络故障等消费者互动数据。

在其 2019 年的报告中,它强调了所有运营商的通话下降了 35%。这个问题的严重性达到了这样的程度,执政党和反对党开始就电信监管机构和政府部门如何保护电信运营商进行诽谤比赛。总理召集三家公司的负责人召开紧急会议,讨论这个问题及其可能的解决方案。Sautele 收到以下有关其掉话投诉的报告,如表 7-1 所示。

它清楚地表明,Regoniatel 网络的掉话投诉上升了 41%。在所有运营商中,它的掉话率最高。总理设立的电信委员会建议对超过 10%掉话投诉门槛的电信运营商处以高达 9 亿美元的罚款。这是一笔很重的罚款,如果电话掉线继续发生在公司的网络上,公司将会损失一大笔钱。

面对这种威胁和公众的强烈抗议,Regoniatel 的总裁召集其运营团队开会,以了解掉话率飙升背后的关键原因。根据运营经理给出的建议,需要建立一个试点项目,纳入机器学习和物联网等有前途的创新新兴技术,以便提出解决这一技术商业问题的解决方案。运营经理召集了最有经验的机器学习工程师和两位最好的数据科学家,在试点团队中合作,以便找到解决方案。

试点团队首次会面并研究了导致掉话的各种技术原因,包括对表 7-1 中给出的各种网络位置阶段的研究。

表 7-1

Regonia 电信提供商的掉话数据,2018 年

|

网状名字

|

平均信号强度(dBM)

|

订阅者

|

电话密度%

|

网络停机时间%

|

成功呼叫百分比

|

通话掉线率

|

电话掉线投诉

|
| — | — | — | — | — | — | — | — |
| 埃蒂特尔 | Ninety-two | One million one hundred and sixty thousand | One hundred and thirteen | Zero point zero five | Ninety-eight point two | Zero point zero four eight | Fifty-five thousand six hundred and eighty |
| 格里巴特尔 | One hundred and seven | One million thirty thousand nine hundred | Ninety-six | Zero point eight seven | Ninety-seven point four | 0.8352 | Eight hundred and sixty-one thousand and seven point six eight |
| 雷戈尼亚特尔 | eighty-nine | One million two hundred and forty-three thousand and eighty | Ninety-seven | One point three | Ninety-six point two | One point two four eight | One million five hundred and fifty-one thousand three hundred and sixty-three point eight four |

如您所见,信号强度以 dB m 为单位,这是衡量信号强度的单位。它显示了用户数量、电话密度百分比、网络中断时间百分比、成功呼叫百分比以及掉话率百分比。该表还显示了三家电信运营商各自收到的掉话投诉数量。最好的平均信号强度是 GribaTel 的,然后是 EtiTel 的,然后是 RegoniaTel 的。RegoniaTel 的用户数量最多,约为 1240 万。据报道,RegoniaTel 的电话密度为 97 %, EtiTel 的电话密度最高。据报道,RegoniaTel 的最高网络宕机时间为 1.3%。EtiTel 的成功呼叫百分比最高,RegoniaTel 最低。RegoniaTel 的掉话率为 1.24 8%,EtiTel 的掉话率最低。RegoniaTel 掉话投诉也是最高的。因此,您可以清楚地看到 RegoniaTel 的网络故障、低平均信号强度和最高掉话率的问题。

RegoniaTel 的运营经理报告称,用户数量正在下降,服务客户反馈称,超过 60%的人表示由于掉线问题而转向了竞争对手的网络。订户报告在表 7-2 中。

表 7-2

7 个月 RegoniaTel 的用户数量

|

|

订户数量

|
| — | — |
| 2018 年 4 月 | One million two hundred and eighty-six thousand five hundred and eighty-eight |
| 5 月 18 日 | One million two hundred and seventy-four thousand one hundred and fifty-seven |
| 18 年 6 月 | One million two hundred and sixty-nine thousand eight hundred and six |
| 7 月 18 日 | One million two hundred and sixty-six thousand five hundred and eighty |
| 8 月 18 日 | One million two hundred and sixty-two thousand eight hundred and forty-five |
| 9 月 18 日 | One million two hundred and sixty-one thousand seven hundred and twenty-six |
| 10 月 18 日 | One million two hundred and forty-three thousand and eighty |

正如你所看到的,从 2018 年 4 月到 10 月有一个峰值,然后用户数量稳步下降。在 7 个月的时间里,它的用户群稳步下降了 3.4%。公司总裁向运营经理表达了他的担忧,并要求他加快项目进度,以便找到解决这个问题的方法。

这个试点项目的机器学习工程师和数据科学家意识到了这个问题的严重性,并意识到它与 RegoniaTel 的收入减少直接相关。如果用户继续流失,用户基数预计将再减少 4.75%(表 7-3 )。运营经理还解释说,用户数量每下降一个百分点,平均收入就会相应下降 1000 万美元。对于努力保持网络质量完好无损的公司来说,这是一个巨大的数字。首席执行官已经在想,他可能不得不解雇一些员工来降低运营成本。这是一个可怕的想法,但它是基于那些呈现给他的数字。他向运营经理和试点团队解释了这个基于 IOT 的机器学习试点项目的成功对公司的重要性。

表 7-3

2019 年 RegoniaTel 的预计用户

|

|

订户数量

|
| — | — |
| 十月十八日 | One million two hundred and forty-three thousand and eighty |
| 11 月 18 日 | One million two hundred and twelve thousand and three |
| 12 月 18 日 | One million two hundred and ten thousand seven hundred and sixty |
| 1 月 19 日 | One million one hundred and eighty-four thousand and thirty-four |
| 2 月 19 日 | One million one hundred and twenty-nine thousand three hundred and thirty-eight |

试点团队被要求设计和实现一个能够解决直接掉话问题的试点系统,并应解决以下问题:

  1. 是什么导致了这个掉线问题?

  2. 电话掉线问题在哪个地区更普遍?

  3. 给定某个位置,您将如何衡量通话质量?

  4. 掉话可以预测吗?怎么会?

这是完整的案例研究。如果你来自电信行业,你会发现这些问题非常熟悉,你应该能够很容易地联系到他们。作为 RegoniaTel 的机器学习顾问,请跟我一起看看我将如何为这个问题构建一个解决方案。

案例研究的设置和解决方案

设置工具包括硬件和软件。硬件元素如下:

  1. 一个 Android 应用程序,通过嵌入的实时 Regoniatel SIM 卡来捕获通话数据

  2. 一种工业级无人机,可以处理 500 克或 1 磅重的手机。

  3. Raspberry Pi 型号 3 B+带有 Wi-Fi 模块,可从无人机上安装的手机接收数据

软件元素:

  1. 一个 Android 应用程序,用于测量和捕获通话信号质量数据,并将其存储在该团队构建的 CSV 文件格式中

  2. 一个 Python 机器学习程序,具有来自 Android 应用程序设备的导入数据的预测模型

图 7-1 显示了这些要素如何组合在一起形成试点解决方案。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-1

硬件和软件的案例研究解决方案设置

这种设置的关键是携带移动电话在特定区域周围飞行的无人机。无人机由地面上的人手动驾驶,在人类高度水平上绕着呼叫被引导的移动塔飞行。无人机上安装的手机每隔几分钟就会自动向同一网络中的另一部手机发出呼叫。目的是自动捕获呼叫质量数据,并将其存储到 CSV 格式的数据文件中。当移动电话已经打了足够次数的电话时,它被人工操作员取下,然后被带到具有不同移动塔的另一个位置。这样,数据被捕获,然后整理到一个 SQLite3 数据库中。一旦从所有试点移动塔获得足够的数据,Python 机器学习程序就会运行,以查看模型的结果。移动数据集如图 7-2 所示,Python 如图 7-1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-2

移动呼叫质量数据集

请记住,这只是一个基本的通话质量数据集;在现实世界中,您需要测量更多参数来确定通话质量,如通话时长、语音质量参数、电话反馈等。该数据集的目的是向您展示如何从基础电信数据中进行分析。

将这个数据集导入 SQLite3 数据库后,我们可以使用清单 7-1 中给出的程序。

# -*- coding: utf-8 -*-
"""
Author: Puneet Mathur
Copyright 2020
Free to copy this code with following attribution text: Author Puneet Mathur, www.PMAuthor.com
"""

import pandas as pd
import sqlite3
conn = sqlite3.connect('C:\\ machinemon.db')
#df.to_sql(name='tempdata', con=conn)
curr=conn.cursor()
#query="INSERT INTO TEMPERATURE VALUES(" +"'" + str(datetime.date(datetime.now())) + "'" +"," + "'" + str(datetime.time(datetime.now())) + "'"+ "," + "'" + str(tem) +  "'" + "," + "'" + tempstatus +"'" + ")"
df = pd.read_sql_query("select * from fielddata;", conn)
print(df)
#curr.execute(query)
#conn.commit()

#Looking at data
print(df.columns)
print(df.shape)
#Looking at datatypes
print(df.dtypes)
df.tail(1)

#Checking for missing values
print(df.isnull().any())

#EDA- Exploratory Data Analysis
import numpy as np
print("----------EDA STATISTICS---------------")
pd.option_context('display.max_columns', 40)
with pd.option_context('display.max_columns', 40):
    print(df.describe(include=[np.number]))

#Correlation results
print("----------Correlation---------------")
with pd.option_context('display.max_columns', 40):
    print(df.corr())

#Dividing data into features and target
target=df['Calldrop']
nm=['CID1','CID2','Speed','OutsideTemperature','OutsideHumidity','SignalStrength','BatteryLevel']
features=df[nm]
with pd.option_context('display.max_columns', 40):
    features.head(1)
    target.head(1)

#Building the Model
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split( features, target, test_size=0.25, random_state=0)

from sklearn.linear_model import LogisticRegression

lr  = LogisticRegression()
lr.fit(x_train, y_train)
# Returns a NumPy Array
# Predict for One Observation (image)
lr.predict(x_test)

predictions = lr.predict(x_test)

# Use score method to get accuracy of model
score = lr.score(x_test, y_test)
print(score)

import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import metrics
import numpy as np

cm = metrics.confusion_matrix(y_test, predictions)
print(cm)

plt.figure(figsize=(9,9))
sns.heatmap(cm, annot=True, fmt=".3f", linewidths=.5, square = True, cmap = 'Blues_r');
plt.ylabel('Actual Calldrops');
plt.xlabel('Predicted Calldrops');
all_sample_title = 'Accuracy Score: {0}'.format(score)
plt.title(all_sample_title, size = 15);

plt.figure(figsize=(7,7))
plt.imshow(cm, interpolation="nearest", cmap="Pastel1")
plt.title('Confusion matrix', size = 15)
plt.colorbar()
tick_marks = np.arange(1)
plt.xticks(tick_marks, ["0", "1"], rotation=45, size = 15)
plt.yticks(tick_marks, ["0", "1"], size = 15)
plt.tight_layout()
plt.ylabel('Actual Calldrops', size = 15)
plt.xlabel('Predicted Calldrops', size = 15)
width, height = cm.shape
for x in range(width):
 for y in range(height):
  plt.annotate(str(cm[x][y]), xy=(y, x),
  horizontalalignment='center',
  verticalalignment='center')
plt.show()

Listing 7-1Code for the Solution to the Case Study

请记住,从 LightSensor 和其他物联网传感器获取数据的代码在第三章的图 3-25 中给出。无论我们尝试实现何种解决方案,从传感器获取数据的过程都是一样的。唯一的区别是,在这种情况下,树莓 Pi 在收集数据时位于无人机内部。机器监控代理类似于第三章的图 3-33 程序,将 LDR 模块物联网传感器数据存储在 SQLite3 数据库中,图 3-35 代码用于模拟基于物联网的解决方案,这两者共同为我们提供了从物联网传感器获取数据的能力,然后在需要时存储并处理这些数据以获得高级警告信号。在这种情况下,我们不需要任何预警信号。

数据由无人机收集,它携带树莓 Pi 作为其有效载荷飞往指定的塔,而代理则从物联网温度和湿度传感器以及附加的 4G/LTE 模块收集数据。这些数据被收集并存储在 Raspberry Pi 上的 SQLite 数据库中之后,就可以进行进一步的分析了。因此,该解决方案假设已经使用设置完成了数据收集。

代码从 pandas 和 SQLite3 库的常规导入开始。它与 machinemon SQLite3 数据库建立连接,然后使用Select * from fielddata SQL 查询从数据库中获取数据。Fielddata是存储无人机物联网传感器和 4G/LTE 模块数据的表格。接下来,在构建任何模块之前,我们检查是否有丢失的值,在我们的例子中没有,所以我们不需要对它们做任何处理。之后,我们对数据集进行探索性数据分析。

Spyder 等 Python IDEs 的问题是,如果我们的数据集中有大量的列,它们会截断这些列。所以诀窍是对熊猫数据帧使用语句pd.option_context('display.max_columns', 40)。这里它被设置为 40,但是如果你有更多的列,你可以把它设置为你需要的值。df.describe()功能的输出如表 7-4 所示。

表 7-4

查看 EDA 统计数据

| `----------EDA STATISTICS---------------` | |   | `Call#` | `CID1` | `CID2` | `Speed  \` | | `count` | `2000.000000` | `2.000000e+03` | `2.000000e+03` | `2000.000000` | | `mean` | `1000.500000` | `1.693192e+06` | `1.688596e+06` | `59.706000` | | `std` | `577.494589` | `1.709635e+05` | `1.673130e+05` | `35.214196` | | `min` | `1.000000` | `1.399112e+06` | `1.398797e+06` | `0.000000` | | `25%` | `500.750000` | `1.540734e+06` | `1.543660e+06` | `29.000000` | | `50%` | `1000.500000` | `1.698208e+06` | `1.685488e+06` | `59.000000` | | `75%` | `1500.250000` | `1.841730e+06` | `1.830084e+06` | `90.000000` | | `max` | `2000.000000` | `1.987112e+06` | `1.986768e+06` | `120.000000` | |   | `OutsideTemperature` | `OutsideHumidity` | `SignalStrength` | `BatteryLevel \` | | `count` | `2000.000000` | `2000.000000` | `2000.000000` | `2000.000000` | | `mean` | `28.046000` | `64.728000` | `-67.895895` | `52.089500` | | `std` | `4.288482` | `12.714828` | `13.327106` | `19.043945` | | `min` | `21.000000` | `40.000000` | `-92.105263` | `20.000000` | | `25%` | `24.000000` | `55.000000` | `-78.947368` | `36.000000` | | `50%` | `28.000000` | `63.000000` | `-66.000000` | `51.000000` | | `75%` | `32.000000` | `75.000000` | `-57.894737` | `66.000000` | | `max` | `35.000000` | `88.000000` | `-42.000000` | `96.000000` | |   | `Calldrop` | | `count` | `2000.00000` | | `mean` | `0.78900` | | `std` | `0.40812` | | `min` | `0.00000` | | `25%` | `1.00000` | | `50%` | `1.00000` | | `75%` | `1.00000` | | `max` | `1.00000` |

需要注意的是,速度列的值介于某些区域的最小速度 0.0 和其他区域的最小速度 120 之间;室外温度介于 21 摄氏度至最高 35 摄氏度之间;外部湿度介于 40%和 88%之间,这表明是一个相当潮湿的区域。信号强度 ASU 介于-92.1 和-42.0 之间;记住,越低越好。

完成这些之后,我们现在来看看这些列之间在统计上是否有任何关系。最好的方法是对数字列进行相关运算。这里使用了 df.corr()()并在表 7-5 中显示了一些关于我们可以在什么基础上构建模型的很好的见解。

表 7-5

看相关性

| `n [102]: print("----------Correlation---------------")` | | `with pd.option_context('display.max_columns', 40):` | | `print(df.corr())` | | `----------Correlation---------------` | |   | `Call#` | `CID1` | `CID2` | `Speed  \` | | `Call#` | `1.000000` | `-0.028051` | `0.005757` | `-0.005541` | | `CID1` | `-0.028051` | `1.000000` | `0.012414` | `-0.023611` | | `CID2` | `0.005757` | `0.012414` | `1.000000` | `0.022053` | | `Speed` | `-0.005541` | `-0.023611` | `0.022053` | `1.000000` | | `OutsideTemperature` | `-0.026658` | `0.013036` | `0.001161` | `-0.021969` | | `OutsideHumidity` | `-0.016984` | `0.046798` | `0.018531` | `-0.025885` | | `SignalStrength` | `0.016455` | `-0.047521` | `-0.018348` | `0.026063` | | `BatteryLevel` | `0.017357` | `-0.023449` | `-0.017436` | `0.008438` | | `Calldrop` | `-0.029110` | `0.038461` | `-0.001414` | `-0.045044` | |   | `OutsideTemperature` | `OutsideHumidity` | `SignalStrength \` | | `Call#` | `-0.026658` | `-0.016984` | `0.016455` | | `CID1` | `0.013036` | `0.046798` | `-0.047521` | | `CID2` | `0.001161` | `0.018531` | `-0.018348` | | `Speed` | `-0.021969` | `-0.025885` | `0.026063` | | `OutsideTemperature` | `1.000000` | `0.771495` | `-0.772994` | | `OutsideHumidity` | `0.771495` | `1.000000` | `-0.999792` | | `SignalStrength` | `-0.772994` | `-0.999792` | `1.000000` | | `BatteryLevel` | `0.032395` | `0.026747` | `-0.026404` | | `Calldrop` | `0.496304` | `0.689299` | `-0.689721` | |   | `BatteryLevel` | `Calldrop` | | `Call#` | `0.017357` | `-0.029110` | | `CID1` | `-0.023449` | `0.038461` | | `CID2` | `-0.017436` | `-0.001414` | | `Speed` | `0.008438` | `-0.045044` | | `OutsideTemperature` | `0.032395` | `0.496304` | | `OutsideHumidity` | `0.026747` | `0.689299` | | `SignalStrength` | `-0.026404` | `-0.689721` | | `BatteryLevel` | `1.000000` | `0.037316` | | `Calldrop` | `0.037316` | `1.000000` |

在这种相关性中有相当多的东西需要注意。外部温度和湿度 0.77 之间存在显著的正相关,而外部温度和信号强度之间存在负相关。这意味着当温度下降时,信号强度也会下降(信号强度具有相反的效果,因此越低越好)。通话掉线,室外温度 0.49,不太显著。最值得注意的是,湿度和信号强度之间存在完美的负相关关系。如果湿度上升,信号强度肯定会下降。实际上,这意味着在雨天,当室外湿度很高时,信号强度会很低。我们都没有经历过下雨时通话语音质量下降的情况吗?这个数据集正好证明了这一点。

外部湿度和通话掉线之间的相关性也非常高,为 0.68,因此在建模时也应考虑这一点。该模型需要注意的相关结果是信号强度和掉话率之间的关系,在-0.68 处为负。这意味着信号强度越好,通话掉线的频率就越低。现在我们知道我们的数据集具有潜力,我们可以使用这些选定的变量来建立我们的机器学习模型。

在下一步中,我们着手将数据集分割成一个目标,该目标是预测值,并以与目标或预测值高度相关的列为特征。为此,我们从我们的特征中省略了以下几列:CID1CID2SpeedBatteryLevel,因为它们在结果中没有显著的相关性。我们在代码中只为我们的模型选择了三个变量:nm=[‘外部温度’,‘外部湿度’,‘信号强度’],features=df[nm]。

此外,我们可以通过 x_train,x_test,y_train,y _ Test = train _ Test _ split(features,target,test_size=0.25,random_state=0)将数据集拆分为训练和测试,从而开始构建我们的模型。我选择 25%作为测试数据集,75%作为训练数据集。您可以进一步试验,看看在您的预测模型中是否会得到不同的结果。接下来,我们仅使用逻辑回归分类器算法来构建预测模型;然而,我强烈建议你在选择合适的分类器算法时使用类似的方法,就像我在我的书《使用 Python 的机器学习应用程序》的第三章图 3-32 中所做的那样。这里,我们使用代码lr.fit(x_train, y_train)将数据集拟合到逻辑回归对象,然后对 x_test 进行预测,这是我们的特征训练数据集。然后,我们对我们的预测进行评分,以查看模型执行的准确性,logsitic 回归模型的准确性评分为 1.0。这说明我们的模型存在过拟合的问题。因此,正如我提到的,我们需要评估其他分类器算法,找出不会过度拟合的最佳算法。

混淆矩阵和准确度分数可以通过使用热图和颜色条以图形方式可视化。这就是我们接下来通过使用 matplotlib 和 Seaborn 库在代码中要做的事情。代码通过语句cm = metrics.confusion_matrix(y_test, predictions)使用 sklearn 库度量包构建了一个混淆矩阵。就混淆矩阵而言,这种简单的输出有时不会产生视觉上吸引人的颜色条或热图所能产生的影响,这就是在代码语句sns.heatmap(cm, annot=True, fmt=".3f", linewidths=.5, square = True, cmap = 'Blues_r')中使用 Seaborn heatmap 库包在代码的下一部分中所做的事情。这里注释被设置为 true,这样就可以在热图上看到输出;我们也使用cmap或者一个有蓝色色调比例的彩色地图。要了解更多关于带注释的色彩映射表,请参考 this this matplotlib URL:https://matplotlib.org/gallery/images_contours_and_fields/image_annotated_heatmap.html

摘要

在本章中,您看到了一个近乎现实的真实场景:一家虚构的电信公司正在经历一场社会政治危机,其老板被要求调查并纠正掉线问题。世界各地的电信公司面临着类似的技术挑战和问题,并正在使用人工智能来解决这些问题。在这个案例研究解决方案中,我展示了如何预测掉话问题。在找到问题的解决方案之前,预测问题是企业必须跨越的第一个障碍。这个案例研究就是这样做的。我们使用逻辑回归分类算法建立了一个模型来预测掉话发生的时间。有了这些数据,企业就可以知道需要什么样的技术升级来解决预测模型给出的湿度问题。有没有更好的发射塔和接收塔可以在如此潮湿的条件下正常工作?他们需要在远离水体的地方建一些塔吗?一旦预测模型提供了结果,企业就必须解决这些问题。

八、甘塔拉发电厂:工业机器的预测性维护

这一章是关于一个能源行业的案例研究,我们试图模拟工业机器的预测性维护数据。案例研究的背景是非洲地区未来的一个虚拟国家。它提出了在一个第三世界国家建立一个发电厂所面临的挑战,从土地征用到概念设计再到实际建立。还包括建立电厂的技术要求和技术规范,其中包括电厂的工业相关土地,以便您了解电厂真正需要什么才能建立。如果您是新手或者对能源行业有所了解,您会发现有帮助的是电厂要求的输入处理和输出格式表,它奠定了企业的完整结构和基础。虽然人物、场景和国家都是虚构的,但它们会让你了解社会政治环境压力是如何对发电这样的基本事情产生影响的。作为案例研究的一部分,还包括一位机器学习工程师,他的性格在案例研究中得到了仔细的刻画,让你了解一个典型的机器学习工程师在处理这类问题时会经历什么样的压力。因此,让我们开始我们的案例研究。

案例研究

一家私营发电厂的首席执行官刘诺琪·奥拉刚刚抵达非洲诺巴格省的首都甘塔拉,由于最近当选的政府的进步政策,该市正在快速发展。然而,随着发展而来的是为工业和居民需求生产足够电力的挑战。从该电厂的概念形成到目前的峰值负荷为 500 兆瓦的发电状态,刘诺琪一直与该公司在一起。旅程并不平坦;一路走来问题百出。事实是,在第三世界国家建立发电厂面临着巨大的挑战,从寻找资金合作伙伴到寻找愿意安装的工厂设备供应商。

2024 年叙利亚战争结束后,当主要国家走到一起开拓这个国家时,诺巴格成为非洲大陆上的一个独立国家。自独立以来,该国对发展矿业和农业的兴趣越来越大,这是当地人民的主要职业。中国政府建立了各种经济特区,为来这里创业的公司提供各种税收优惠。刘诺琪的公司 Nobag Power Enterprise Ltd .就是这样一家公司,它是由一些欧洲企业集团建立的,目的是利用该国的发展潜力,促进其基础设施建设。Nobag 主席 Ag Zisi 向 Nobag 电力企业颁发了以下特许状,作为总统令的一部分,以成功完成其使命:

Nobag 电力企业获准生产和分配高达 500 MW 容量的热能,以支持甘塔拉首都地区在两年内的电力需求。

该公司的愿景声明反映了总统的宪章,内容如下

Nobag 电力企业的愿景是生产和分配电力,满足 Nobag 人民的电力需求,以帮助其住宅和工业发展。

项目背景

当 Nobag 电力企业对 Gantara 发电厂进行概念设计时,它制定了发电厂建设和安装的土地需求计划,因为这是 Nobag 政府建立该项目的关键因素。土地需求已传达给政府官员,因为这是一个火力发电厂,需要尽快完成土地征用,以便启动电厂建设流程。表 8-1 给出了 500MW 电厂的土地规划进度表。

表 8-1

甘达拉电厂的电厂用地要求

|

以英亩计的面积

|
| — |
|

描述

|

500MW

|
| — | — |
| 电厂边界内的设施 |
| 主厂房 | 25 |
| 输煤系统 | 230 |
| 水系统 | 45 |
| 蓄水池 | 30 |
| 开关站 | 25 |
| BOP、商店和道路 | 70 |
| 绿化带面积 | 150 |
| 电厂边界外的设施 |
| 除灰区 | 270 |
| 员工乡镇 | 100 |
| 灰、原水和煤处理 | 250 |
| 大合计 | 第一千一百九十五章 |

虽然诺巴格有足够的土地来建立一个发电厂项目,但甘塔拉市周围的大部分土地都被农民占据了。他们对新成立的 Nobag 政府征用土地开发电厂的努力进行了坚决抵制。这导致了暴力,政府宣布该公司寻求征地的地区进入紧急状态,因为该地区靠近一个水库。最后,在政府同意农民要求的适当补偿后,经过 9 个月的斗争,土地征用继续进行,鉴于政府试图为大坝和其他项目征用土地的类似情况,这是一个记录;它遭遇了诉讼和暴力抗议。虽然诺巴格人民理解发电厂是为了他们的利益,正如政府和刘诺琪·奥拉在与甘塔拉农民和人民的互动中所表达的那样,但问题是信任政府给他们一个双赢的交易。在来自欧洲企业集团的压力下,政府屈服于压力,通过给予基于市场的补偿来获得土地。

54 岁的刘诺琪·奥拉是一名出生于非洲的电气工程师,曾在通用电气等公司工作过,在为其欧洲合作伙伴建立电厂设施方面有着丰富的经验。他在中东成功建立了一家工厂后,升任首席执行官。作为这个大陆的本地人,他对地理非常了解。他在政治上人脉很广,能够利用一些关系使这个项目成功。遴选委员会从三名潜在候选人中挑选出了他,这三名候选人有着相似的经历,但不具备担任这份工作的种族优势。

Nobag 拥有丰富的煤矿储量,可以再开采 100 年。因此,建立一个火力发电厂是最好的选择,因为它有较少的石油储备来满足发电厂对天然气的需求。它将不得不从其他中东国家购买,这将是昂贵的,并将耗尽其本已微薄的外汇储备。此外,它将依赖天然气供应发电,因为邻国刚刚结束长期战争,如果该地区局势再次动荡,天然气管道可能成为军事打击的目标。尽管 Nobag 有自己的污染控制法律,但由于它正在接受欧洲集团等外部机构的帮助,它必须遵守这些机构设定的清洁能源要求。火力发电厂必须实现 CCS,这是捕获碳并将其放入地下( https://en.wikipedia.org/wiki/Carbon_capture_and_storage )的过程,从而减少碳向大气中的释放。发电厂的主要目的是将煤的化学能转化为电能。这通常是通过提高锅炉中的蒸汽,使其通过蒸汽轮机膨胀,然后将蒸汽轮机连接到发电机来实现的,发电机将机械能转化为电能。该热电厂的输入和输出容量见表 8-2 。请注意,所有数字都是针对 24 小时的日常消费周期。

表 8-2

甘达拉发电厂的输入和输出

|

描述

|

投入

|

处理

|

描述

|

输出

|
| — | — | — | — | — |
| | Six thousand |   | 二氧化碳 | Fifteen thousand |
| 炉油 | Fifty point five | 500 兆瓦 | SO2 + NO2 | Three hundred and forty |
| | Four thousand nine hundred |   | GT | Four hundred and sixty |
| | Forty |   | 灰烬 | Two thousand one hundred |

典型的以煤为基础的火力发电厂的问题是产生像 Co2、SO2 和 NO2 这样的气体,这些气体严重污染环境。所以这些气体需要通过捕获并埋到地下来处理,尤其是二氧化碳。这增加了项目成本和资金需求。发电厂本身需要电力供自己消耗,因此需要大约 40 兆瓦的电流来运行,只有 460 兆瓦的电流被释放出来进一步输送到甘塔拉市。每天 4900 立方米的需求是寻找水库附近特定土地的原因,也是农田为其设置带来问题的原因。每天产生 2100 吨火山灰,政府邀请私人团体建造工厂,用火山灰生产混凝土产品和墙板,以免污染水库周围的土地,因为农民也使用火山灰。当发电厂在甘塔拉附近启动时,几乎没有任何生产波特兰火山灰水泥(PPC)的工厂,这种水泥可以回收产生的灰烬,因此它们被释放到发电厂附近的干燥垃圾填埋场。环保主义者提出抗议,反对给电厂周围的农民造成这样的环境危害;然而,政府向他们保证,完全利用发电厂产生的飞灰的设施将在几年内准备就绪,从而减少拟建发电厂周围的环境危害。

为了在甘塔拉附近建造热电厂,该公司从一个全球投资者财团获得了 6 亿美元的贷款,以满足附近地区不断增长的电力消耗负荷。

新发电厂的装机峰值负荷能力为 500 兆瓦,而目前的峰值负荷需求为 640 兆瓦,在新发电厂投产后,困扰其运行的问题是电力系统损耗高、发电厂效率低、供电不稳定、窃电、停电以及发电厂维护资金短缺。总的来说,在过去十年里,该国的发电厂无法满足系统需求。中国目前从一个邻国购买电力,但必须用美元支付,这耗尽了中国至关重要的外汇储备。每当这个发电厂停电时,都会从邻国获取备用电力,以便在停电期间供电。它在夏季和冬季高峰负荷季节发电有问题。

Nobag 的总裁 Ag Zisi 上个月与首席执行官刘诺琪·奥拉召开了一次紧急会议,原因是夏季几个月来消费者对电厂停电的大量投诉,导致首都停电数小时。最后一次停电持续了 48 个多小时,直到邻国提供替代电源后,整个城市才恢复供电。总统想知道为什么一个八个月前才投产的新电厂会出现问题。火力发电厂的寿命超过 30 年。由于媒体对其政府无力向国家首都提供充足电力的压力越来越大,总统召集欧洲集团官员进行调查,并提交一份关于频繁停电的原因以及如何解决这一问题的报告。委员会花了 20 多天时间研究工厂的基础设施。它的团队中有一位名叫卡尔的机器学习工程师,他也被拉进来帮助团队根据发电厂提供的数据确定这些断电的真正原因。Carl 使用 Modbus RS485 接口设备在两周的时间内从关键的电厂机器中获取数据。他的发现如下:

  • 由于汽轮机的原因,停机是电厂中最常见的问题。

  • 需要找到缩短停机时间的方法。

  • 需要减少停机次数。

  • 需要围绕发电厂现在的负载和未来几个小时的预计负载建立预测模型。

诺巴格政府根据第一个委员会的调查结果成立了第二个全国委员会,以查明并纠正停电的主要原因。报告发现,故障实际上是由于燃气轮机的操作缺乏协调。

卡尔也是第二国家委员会的成员,他制定了以下参数,以从燃气轮机/电网收集停机数据:

  • 总周期,T

  • 在周期 T 内发生的故障数量

  • 维护之间的故障次数

  • 维护之间的总运行时间

  • 每年总停机时间

  • 每年的失败次数

  • 燃气轮机的正常运行时间

  • 燃气轮机停机时间

与任何传统的燃煤电厂设施一样,这些设备和机器在没有集中存储的连接单一数据源的筒仓中工作。从涡轮机到变压器到锅炉,每个单元都受到独立监控。此外,卡尔要求的数据没有被电厂存储,因此委员会成员不可能确定燃气轮机发生故障的真正原因。国家委员会要求刘诺琪·奥拉的公司使用物联网传感器从燃气轮机收集数据,并将数据中继和存储在新的私有云服务器中。结果将在两周内 24x7 全天候生成,以便可以清楚地记录大修持续时间。目的是帮助 Carl 创建一个模型来预测断电。当然,描述和诊断停机是流程的一部分。委员会的努力是让发电厂公司变得向前看,而不仅仅是向后看,并对发电厂内部发生的停电做出预测和规定。

正如任何改变行动方式的新举措一样,委员会正在做的事情也遇到了阻力。工会聚在一起,急切地想知道这次活动的结果。这会导致失业吗?这会导致减薪吗?每个人都很好奇。为了获得一组新的数据,在涡轮机周围放置新的物联网传感器存在阻力。尽管如此,卡尔和他的团队决心继续前进,实现他们的计划。

该计划采用了许多传感器,比如测量涡轮机周围温度和光线的传感器,以及测量涡轮机附近超声波的传感器。卡尔想确保他不遗余力地确定涡轮机故障的真正原因。卡尔虽然是机器学习工程师,但他必须研究他工作的领域,即发电厂,所以他必须学习发电厂的完整功能。他制作了一个简短的发电厂示意图,其单元如图 8-1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-1

热电厂示意图

他把这份报告给委员会官员和其他成员看,他们不知道火力发电计划是如何运作的。他的报告内容如下:

“请记住,这不是技术图,而是帮助你理解火力发电厂关键部件的草图。实际的电厂更复杂;但是,组件保持不变。让我们逐一讨论每个组件。

锅炉是一种容器,水被输入其中,蒸汽以所需的压力、温度和流量排出。对容器进行加热。为此,锅炉应该有燃烧燃料和释放热量的设施。锅炉的主要功能如下:

  • 将燃料(如空气)的化学能转化为热能。

  • 将这种热能转化为水用于蒸发,也转化为蒸汽用于过热。

锅炉的基本部件有

  • 炉子和燃烧器

  • 蒸汽和过热

类似地,另一个部件,省煤器,通过从烟气中提取热量来加热水并将其送到锅炉汽包来提高锅炉的效率。

顾名思义,节能器的优点包括

  • 节约燃料:用于节约燃料,提高锅炉设备的整体效率

  • 减小锅炉尺寸:由于给水在省煤器中预热,然后以较高的温度进入锅炉管,蒸发所需的传热面积大大减小。

在低压和高压加热器中,当烟气流出省煤器时,热量从烟气中被带走,然后在将空气供应给燃烧室之前,进一步用于预热空气。它是送热风干燥煤粉系统中的煤,以促进炉内燃料燃烧的关键设备。如果该功能不存在,燃烧室可能会出现故障。发电厂的炉子也有一个再热器部件,它的管子被从管子中流出的热烟气加热。流出高压涡轮机的排出蒸汽流入再热器管内,以获得能量,从而运行中间低压涡轮机。

汽轮机主要用作所有热电站的关键部件。汽轮机有两种类型:

  • 冲击式涡轮机

  • 冲击反击式水轮机

涡轮发电机由一系列相互连接的蒸汽轮机和一个位于同一轴上的发电机组成(铁棒结构)。一端有一个高压涡轮(HPT),接着是一个中压涡轮(IPT),两个低压涡轮(LPT)和发电机。蒸汽在高温(536 摄氏度至 540 摄氏度)下流动,压力(140 至 170 千克/平方厘米)在汽轮机中膨胀。

冷凝器将从汽轮机排汽口流出的蒸汽冷凝成液体,以便进一步泵送。冷凝器的功能是

  • 为蒸汽提供最低的排热温度。

  • 将汽轮机排出的蒸汽转化为备用水,从而节省电厂的给水需求。

冷却塔是一种半封闭装置,通过与空气接触来蒸发冷却水。从冷凝器出来的热水被输送到塔顶,并以薄片或水滴的形式滴下。空气从塔底或垂直于水流方向流动,经有效冷却后排入大气。

发电机是汽轮发电机组的电气端。它是将燃气轮机的机械能转化为电能的转换器。电的产生是基于电磁感应的原理。没有发电机,燃气轮机的输出就不会发出电。所以它是发电厂中最重要的设备。"

在他的报告中解释了这一点后,Carl 解释了从燃气轮机周围的物联网传感器获取数据所需的设置。正如第一个委员会的报告所指出的,停机发生在燃气轮机中。然而,它无法总结根本原因或指出任何可能导致燃气轮机重复停机的潜在问题。这使得它变得更加复杂,因此它需要与燃气轮机的附属电子单元产生的数据一起被监控,并被发送到 Raspberry Pi 站以使用 Modbus 协议收集数据。为了确定根本原因并查看外部环境温度是否有任何影响,在燃气轮机上安装了一些物联网传感器。监控三个参数:环境光、环境温度和物联网传感器,以测量机器设备周围的超声波。当然,在这个阶段,外界环境中的任何因素都可能导致困扰电厂工程师和设备制造商的大修,这只是一个假设。只有精心收集的数据才能证明或推翻这一假设。设置如图 8-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-2

燃气轮机上用于收集数据的物联网传感器

Raspberry Pi 3 B+使用 SQLite 格式的 Python 程序记录数据至少两周,在此期间燃气轮机可能会发生停机。使用 Python 将机器学习程序应用于这些收集的数据,以得出最终结论。

首席执行官刘诺琪·奥拉希望卡尔和他的团队提供以下结果:

  • 流氓汽轮机停机原因的确凿证据

  • 能够实现监控和预测停机的自动化流程的可能解决方案

两者都必须向第二委员会展示,作为概念验证阶段,以便进行进一步的商业开发。

至此,我们结束了案例研究。我们将从名为powerplant.db的 SQLite3 数据库中的汽轮机收集的数据集得出我们的解决方案。我们将使用这些数据,假设电厂技术人员从物联网传感器收集了这些数据,并将其发送到 Raspberry Pi 程序。清单 8-1 显示了使用机器学习的 Python 代码的整个解决方案。

# -*- coding: utf-8 -*-
"""
Author: Puneet Mathur
Copyright 2020
Free to copy this code with following attribution text: Author Puneet Mathur, www.PMAuthor.com
"""

import pandas as pd
import sqlite3
#Change the path as follows:
#On Raspbian: change to a path to your sqlite3 database file with *.db extension
conn = sqlite3.connect('/home/pi/sqlite3/powerplant.db')
#df.to_sql(name='tempdata', con=conn)
curr=conn.cursor()
#The table with power plant data is named: TURBINE_INLET_DATASET
df = pd.read_sql_query("select * from TURBINE_INLET_DATASET;", conn)
print(df)
#curr.execute(query)
#conn.commit()

#Looking at data
print(df.columns)
print(df.shape)
#Looking at datatypes
print(df.dtypes)
df.tail(1)

#Checking for missing values
print(df.isnull().any())

#EDA- Exploratory Data Analysis
import numpy as np
print("----------EDA STATISTICS---------------")
pd.option_context('display.max_columns', 40)
with pd.option_context('display.max_columns', 40):
    print(df.describe(include=[np.number]))
#Looking at the distribution graphically
df.hist(figsize=(10, 6))

#Correlation results
print("----------Correlation---------------")
with pd.option_context('display.max_columns', 40):
    print(df.corr())

#Dividing data into features and target
target=df['Defect']
nm=['Voltage','Current','Temperature','InletGap_mm']
features=df[nm]
with pd.option_context('display.max_columns', 40):
    features.head(1)
    target.head(1)

#Building the Model
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split( features, target, test_size=0.25, random_state=0)

from sklearn.linear_model import LogisticRegression

lr  = LogisticRegression()
lr.fit(x_train, y_train)
# Returns a NumPy Array
# Predict for One Observation (image)
lr.predict(x_test)

predictions = lr.predict(x_test)

# Use score method to get accuracy of model
score = lr.score(x_test, y_test)
print(score)

import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import metrics
import numpy as np

cm = metrics.confusion_matrix(y_test, predictions)
print(cm)

plt.figure(figsize=(9,9))
sns.heatmap(cm, annot=True, fmt=".3f", linewidths=.5, square = True, cmap = 'Blues_r');
plt.ylabel('Actual Defects');
plt.xlabel('Predicted Defects');
all_sample_title = 'Accuracy Score: {0}'.format(score)
plt.title(all_sample_title, size = 15);

plt.figure(figsize=(7,7))
plt.imshow(cm, interpolation="nearest", cmap="Pastel1")
plt.title('Confusion matrix', size = 15)
plt.colorbar()
tick_marks = np.arange(2)
plt.xticks(tick_marks, ["0", "1"], rotation=45, size = 15)
plt.yticks(tick_marks, ["0", "1"], size = 15)
plt.tight_layout()
plt.ylabel('Actual Defects', size = 15)
plt.xlabel('Predicted Defects', size = 15)
width, height = cm.shape
for x in range(width):
 for y in range(height):
  plt.annotate(str(cm[x][y]), xy=(y, x),
  horizontalalignment='center',
  verticalalignment='center')
plt.show()

Listing 8-1Code for the Solution to the Case Study

如你所见,代码实现完全符合第三章中的机器学习代码实现。但是,请注意,结果和输出与第三章中的解决方案非常不同。

由于我们是复制第三章的机器码,我就简单描述一下这个问题的解决方案。理解汽轮机是至关重要的,因此让我们将它们作为本案例研究解决方案的一部分。

火力发电厂的原动机介质是蒸汽。简单地说,水被加热,变成蒸汽,并在蒸汽涡轮中旋转,进而驱动发电机。此后,蒸汽通过涡轮机,在冷凝器中冷凝;这就是众所周知的兰金循环。

汽轮机总是在高蒸汽压力环境下运行,并具有许多高速运动部件。喷嘴(入口阀和出口阀)和涡轮叶片是通过仔细分析设计的,其零件以高精度制造。

蒸汽轮机设备通常具有实现高达 95%可用性的历史,并且可以在停机维护和检查之间运行一年以上。他们的计划外或被迫停机率通常不到 2%,或者每年不到一周。在我们的案例研究中,在仔细分析了整个工厂的数据后,工厂制造工程师(包括机器学习工程师 Carl)将错误停机的问题缩小到汽轮机故障。

在清单 8-1 中,我们首先导入像 pandas 和 sqlite3 这样的库来连接数据库。数据库名为powerplant.db,表名为TURBINE_INLET_DATASET。我们使用这些信息连接到我们的电厂数据集,首先使用sqlite3.connect()建立到数据库的连接,然后使用代码pd.read_sql_query("select * from TURBINE_INLET_DATASET;", conn)从表TURBINE_INLET_DATASET中读取所有行。在我们成功地将数据从数据库读取到 pandas dataframe df之后,我们就可以通过df.columnsdf.shapedf.dtypes语句来查看它的结构。在这之后,我们通过代码df.tail(1)看最后一行;为了看到第一行,我们也可以做一个df.head(1)。这只是为了检查数据是否正确加载。下一步检查是查看我们的数据集是否有任何缺失值,这种可能性较小,因为在我们的案例中用于监控汽轮机入口和出口阀的仪器是振动计,它测量汽轮机内部阀门系统的声音和电气输入等值。使用的代码是print(df.isnull().any()),在我们的例子中,它给出 False 值作为输出,因为我们在任何列中都没有空值。一旦我们通过了这个数据检查,我们就可以对我们的电厂数据集进行探索性的数据分析了。在现实世界中,您将需要合并来自物联网传感器和设备(如振动计)的数据以及蒸汽轮机监控软件(如 ge 的 GateCycle)的数据。对于团队中的机器学习工程师来说,这将是最耗时的活动。合并时,您可能会遇到数据不兼容的问题,例如日期时间的不同格式,您需要在最终合并发生之前解决这些问题。

为了快速完成 EDA,我们使用了 pandas 数据帧的df.describe()功能。它为我们提供了基本的统计数据,如平均值、标准差、变量最小值、变量最大值、第 25 个百分点、第 50 个百分点和第 75 个百分点的数据。在这之后,我们来看看数字列的直方图,如图 8-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-3

所有列的直方图

入口间隙变量的曲线更接近正态曲线;然而,它在左侧很重。温度几乎是均匀分布的。然而,电压变量在图形上显示了一些前景。让我们通过相关性来确认这些关系。要运行的语句是df.corr(),输出在表 8-3 中给出。

表 8-3

电厂数据集中的关联结果

|

相互关系

|

循环

|

入口间隙 _ 毫米

|

电压

|

目前的

|

温度

|

缺点

|
| — | — | — | — | — | — | — |
| 迭代 | one | Zero point zero zero five | -0.1 | -0.17 | Zero point two one one | Zero point two five one |
| 进气间隙 _ 毫米 | Zero point zero zero five | one | Zero point nine six four | Zero point seven eight two | Zero point one seven one | Zero point one nine three |
| 电压 | -0.1 | Zero point nine six four | one | Zero point eight two six | Zero point zero eight four | Zero point zero nine four |
| 电流 | -0.17 | Zero point seven eight two | Zero point eight two six | one | -0.176 | -0.21 |
| 温度 | Zero point two one one | Zero point one seven one | Zero point zero eight four | -0.176 | one | Zero point eight five three |
| 缺陷 | Zero point two five one | Zero point one nine three | Zero point zero nine four | -0.21 | Zero point eight five three | one |

我们可以推断柱之间存在的关系,例如入口间隙和电压,其在 0.964 高度相关;同样将入口间隙和电流列为 0.782。电压和电流也高度相关,如预期的那样,为 0.826。通过工业级物联网温度传感器测量的进口阀附近的内部温度与 0.853 处的缺陷呈正相关。汽轮机进汽门操作的缺陷或异常情况见图 8-4 ,进汽门操作循环的正常操作见图 8-5 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-5

进气门操作循环中的正常操作

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-4

进气门操作周期中的异常或缺陷

入口阀操作图上的红色箭头显示了入口间隙、电压和电流之间的异常操作。这显示了电厂发生断电的情况,根本原因是汽轮机入口阀的电子控制器出现故障。

那么这个数据有什么用呢?嗯,它允许机器学习工程师为这样的机器建立预测模型,不仅用于异常检测,还用于为蒸汽涡轮机的预测性维护提供提前警告。当然,这将需要比您在这里看到的更多的数据,但这是建立一个在电厂运行中有效的概念证明的良好开端。让我们继续构建我们的预测模型。

在下一部分代码中,在计算变量之间的相关性后,我们现在将数据集划分为目标变量、Defect列和特征变量,即VoltageTemperatureInletGap_mm。请注意,我没有使用交叉验证(CV)或缩放,因为这是原型级别的代码,只是看看构建模型是否可行。你可以根据我的书使用 Python 的机器学习应用的第三章添加它们。

之后,我们导入 sklearn 库,如 train_test_split,以及分类器算法,如逻辑回归、线性判别分析、KNeighbors 分类器、高斯、朴素贝叶斯、决策树分类器和 SVM。然后初始化每个 sklearn 算法库,然后对每个算法库使用fit()方法,使模型使用 x_train 和 y_train 数据集进行学习。一旦系统进行了预测,我们就可以在测试数据集上测试它的预测精度。这是使用每个分类器的predict()函数来完成的。接下来,使用accuracy_score()函数,我们对每个算法进行评分,并检查它们的结果。请注意,在现实世界中,您不会对所有分类器都获得 1.0 的准确预测。所以基于这些结果,你需要选择最好的分类器,在最短的时间内给出最准确的结果。同样,我在我的书使用 Python 的机器学习应用中以代码格式讨论了这一点。

分类器有分类变量作为预测器,所以它们应该有一个混淆矩阵,这是我们以文本和图形格式创建的,作为代码的最后一部分来查看结果。

摘要

在这一章中,你已经看到了一个属于能源领域的非常技术性的案例研究:一个火力发电厂。本案例研究将您带到了发电厂公司所面临的一些挑战的前沿,这些挑战不仅包括建立发电厂,还包括成功运营发电厂。本案例研究提出了一个非常年轻的电厂的断电问题,该问题很可能是由温度引起的,就像炎热气候中的电厂一样。该团队使用数据将问题定位到汽轮机内部的一个流氓入口操作单元,但需要数据和预测模型来创建一个针对此类异常的高级警告系统。这将触发预测性维护的请求,只要通过数据的定期监控看到这种异常的症状。我们创建了一个解决方案作为概念验证,以确定构建这样一个模型是否可行,以及是否有任何切实的结果。通过使用分类算法,我们创建了一个模型,该模型可以 100%地预测时间,或者具有 1.0 的准确性分数,这在现实世界中很少见。您看到了如何使用来自物联网传感器和 Raspberry Pi Model 3 B+的数据轻松创建整个解决方案。

九、农业产业案例研究:预测经济作物产量

本章介绍了一个基于农业行业的案例研究,用于预测经济作物的产量。这个案例研究让你了解了一个中型农业综合企业集团在努力达到下一个水平并变得和其创始人的愿景一样大时所面临的挑战。作物产量的问题对这些组织来说非常重要,因为他们希望最大限度地利用土地资源,以获得尽可能高的收入。你在案例研究中主要学到的是,一家公司的愿景应该与它正在进行的机器学习操作联系在一起;否则这将是一笔浪费的支出。当我的大多数客户聘请我提供咨询,以了解他们当前的机器学习应用程序如何帮助他们时,我就是这么告诉他们的。它能降低成本吗?如果是,增加多少收入?那是多少?项目的目标必须是可量化的,否则它不会成功,但会让企业所有者和利益相关者不满。所以请继续阅读…

农业行业案例研究概述

一家名为 Aystsaga Agro 的国际农业集团投资于农业、化肥生产和拖拉机。总裁 Tamio Polskab 也是该公司的创始人。他将公司从其父亲继承的一个北美农场发展成为横跨北美、非洲和亚洲三大洲的农业综合企业。农业企业并不容易,因为关税战和气候变化带来了许多挑战。在世界范围内,从南亚到非洲国家都在发生农业危机,试图保护小农场主免受如此快速变化的冲击。有一个巨大的无声的变化正在发生:使用技术,如机器人,来代替人类从事农业。像 Aystsaga Agro 这样的农业综合企业,其经营高度商业化,正在拥抱技术,看看他们如何能够采用并从中受益。由于与服务业等其他行业相比,农业不是一个利润丰厚的行业,因此由于投资回报率低,很难获得此类业务的资金。这就是这些商业公司专注于玉米、甘蔗、小麦和大豆等经济作物的原因。如表 9-1 所示,甘蔗、甜菜和番茄的产量高于 Aystsaga Agro 的其他作物。

表 9-1

Aystasaga Agro 三种经济作物的全球年产量

|

艾尔斯萨加阿格拉

|
| — |
|

2017-2018 年全球产量

|

公吨

|

土地总面积

|
| — | — | — |
| 甘蔗 | Four hundred and seventy thousand eight hundred and seventeen point nine nine | Five thousand three hundred and sixty-one point seven |
| 糖用甜菜 | Five hundred and three thousand nine hundred and ninety-nine point eight | Five thousand three hundred and sixty-one point seven |
| 番茄 | Two hundred and seventy-three thousand four hundred and forty-six point seven | Five thousand three hundred and sixty-one point seven |

2013 年,Aystsaga Agro 在巴西和南非收购了大型农场。巴西农场位于圣帕洛地区,南非农场位于内陆地区。该公司的创始人 Tamio 通过购买土地并将其用于商业经营,成功地扩大了公司的农业经营。他的愿景是将他的公司打造成世界上主要的农业企业。

Tamio 一边喝着早茶,一边看着首席财务官当天早上发给他的年度财务报告。见表 9-2 。

表 9-2

Aystsaga Agro 的全球产量和收入数据

|

2017-2018 年全球产量

|

公吨

|

土地总面积

|

我们

|
| — | — | — | — |
| 甘蔗 | Four hundred and seventy thousand eight hundred and seventeen point nine nine | Five thousand three hundred and sixty-one point seven | Two hundred and forty-one million fifty-eight thousand eight hundred and nine point three |
| 糖用甜菜 | Five hundred and three thousand nine hundred and ninety-nine point eight | Five thousand three hundred and sixty-one point seven | Twenty-five million one hundred and ninety-nine thousand nine hundred and ninety |
| 番茄 | One hundred and seventy-eight thousand four hundred and forty-six point seven | Five thousand three hundred and sixty-one point seven | One hundred and eighty million two hundred and thirty-one thousand one hundred and sixty-seven |
| 农业经营收入 |   |   | Four hundred and forty-six million four hundred and eighty-nine thousand nine hundred and sixty-six point three |

当他正看着这些数字时,有人敲了敲他那豪华的南非办公室小屋的门。他抬起头,看到首席运营官格兰佐微笑着。他示意他过来坐在他前面。格兰佐走过来坐下时,塔尼奥站起来,走到窗户的右边。当他开始慢慢说话时,他看着南非首都令人惊叹的海景。

问题是

"你看到今天早上 Nambi 发来的数字了吗?"塔米奥问道。

格兰佐点点头说,“我认为他们相当令人印象深刻,因为今年我们在巴西和南非的农场周围已经经历了几次风暴。”

“你不明白我的设想,是吗?”塔米奥直视着他的得力助手问道。“我的目标是实现 5 亿美元的营业额,并在 6 年内达到 10 亿美元。我在去年的年度股东大会上告诉了董事会。你也在那里,”老板说。

格兰佐耸耸肩说道,“我们都在那里,但我以为你这么说是为了取悦投资者和董事会。”

“不,那是我过去 15 年来一直努力的目标,自从这家公司上市以来。这不仅仅是一个愿景或目标;这是我向你们所有人提出的挑战,”塔米奥斩钉截铁地说。

格兰佐反驳道:“是的,我理解成长的需要;否则,我们将被行业内的大鱼吃掉。但你必须了解我们的运营目前面临的挑战,除非我们找到真正的解决方案,否则我们不会以你希望的速度增长。”

当塔米奥坐下来,把头靠在他巨大的皮办公椅上时,他皱起了眉头。当格兰佐继续往下说时,他正聚精会神地听着。“阻碍我们发展的唯一因素是我们预测特定土地上农作物产量的能力。”Glanzo 继续说道,“我们的农业经营是我们的一个主要问题,在那些地区,我们做出了一个错误的决定,购买了低产的贫瘠土地。低产土地需要我们通过在公顷土地上施用不同的化学物质来纠正,这增加了生产成本,侵蚀了我们的利润。如果我们必须以你所说的速度增长,我们需要一种方法来确定哪块土地对特定的作物高产。如果我们能够做到这一点,我们就可以避免购买非生产性和低产的农场,就像我们在巴西拥有的那些农场,那里的土壤高度酸性,必须用石灰进行处理,使其碱性更强。”

塔米奥微微抬起头,示意他明白格兰佐在说什么。他问道:“格兰索,你知道如何预测某一特定类型土地的产量吗?”

格兰佐回应道,“我一直在看世界各地大学一直在进行的一些研究;然而,没有具体的可用。但是,在我看来,数据机器学习和人工智能显示了解决问题的一些希望。我们可以雇用一些机器学习工程师和数据科学家,他们可以帮助我们创建一个模型,以确定特定土地位置的作物产量。”

“听起来很棒。你为什么不组建一个团队来调查这个问题,并提出可能的解决方案?”塔米奥问,对格兰佐微笑。

格兰佐说:“是的,这正是我打算做的。首先,我会聘请机器学习工程师和数据科学家,然后我会将我们业务运营部门的人加入团队。”

“给我发一封电子邮件,请求批准继续,”塔米奥说,当格兰佐起身离开他的小屋时,他从桌子上拿起了眼镜。

机器学习拯救世界?

机器学习团队在两个月后成立,Hert Liu 被聘为试点项目的机器学习工程师。他和三名数据科学家一起,参观了 Aystsaga Agro 在巴西、南非和印度的农业运营。入职培训结束后,业务运营团队成员被介绍给他们。通过详细的简报,Hert 和他的团队在他们的词汇中加入了各种典型的农业词汇。他们也了解了生产农作物的内部过程。详细的参观确实有助于团队更深入地了解公司的运营。然而,他们缺少的是经验知识,而这正是业务运营团队成员要在这个试点团队中填补的空白。

赫特和他的团队在一起后见过几次面。他们也见过格兰索几次。他有效地传达了公司创始人的愿景和手头的问题,这些问题与公司的成长有关。Hert 是一名经验丰富的机器学习工程师,早先在保险领域工作。他与农业的唯一接触是创建了一个预测农业农民索赔的模型。所以这对他来说是一个非常高的学习曲线,他必须了解商业农业业务的复杂性,以及管理它所涉及的复杂性,例如由于虫害造成的作物歉收,天气模式的变化,土壤结构及其对农业产量的影响。赫特和他的团队开始收集一些参数,他们认为这些参数可以帮助预测农作物的产量。他们将分析分为天气、土壤和经济环境。在两周一次的会议上,他们与格兰佐分享了他们的理解。Hert 展示了由于农场附近不可预测的降雨和洪水,天气如何损害他们印度农场的作物。Glanzo 指出,他们面临的最大问题是在购买之前确定高产农田的轮廓。“根据我们创始人的愿景,我们希望在世界各地购买大量农田,如澳大利亚、泰国和其他地区;然而,我们不能简单地通过盲目购买土地,然后发现它的产量低于平均农业生产来做到这一点。这对我们的投资回报率意味着灾难。你们作为一个团队应该建立一个模型,帮助我们确定高产经济作物农田的概况。要做到这一点,你应该看看土壤剖面,什么使土壤高产或低产。想用什么乐器就用什么乐器;我们可以买下来。资金并不短缺,但你必须建立一个系统,让我们在追求指数增长的过程中受益最大,”他表示。

在明确了前进的方向后,Hert 和他的团队与业务运营团队成员一起了解了甘蔗作物的土壤剖面,这是他们为试点项目选择的。他们选择了表 9-3 中所示的参数。

表 9-3

监测土壤养分的参数

|

土壤 pH 值

|
| — |
| 有机碳% |
| 氮千克/公顷 |
| 磷千克/公顷 |
| 钾千克/公顷 |
| 锌毫克/千克 |
| 铁毫克/千克 |
| 铜毫克/千克 |
| 锰毫克/千克 |
| 硫磺千克/公顷 |

在决定专注于这些参数以创建土壤剖面图后,他们现在希望从 Aystsaga Agro 全球的每个农场收集数据。Glanzo 帮助他们购买了以下设备:

  1. 商业级物联网传感器套件,用于读取土壤养分数据

  2. 物联网传感器难以运行的地方的土壤养分手动测试套件

我们现在将基于来自文件cashcrop_Yield_dataset.csv的数据集,为预测甘蔗经济作物产量的问题构建一个解决方案。

解决办法

我们可以假设数据集是在从物联网传感器和手动土壤养分测试工具包中读取土壤样本后产生的,用于数据集文件cashcrop_Yield_dataset.csv中给出的各种参数。清单 9-1 中的代码并不是这个问题的最权威的解决方案,但它简单快捷,就像任何试点项目一样。在现实世界中,为土壤养分收集的数据将有更多的参数。如果业务规模跨越几个大洲,这种数据收集工作可能需要几个月才能完成。农业经营远离城市,因此对任何公司来说都是一项令人兴奋的工作。在设计解决方案时,我们会牢记这些因素。请注意,我们只使用线性回归,因为它给出了高度准确的分数;然而,我让你来尝试其他回归算法。

Python 代码

清单 9-1 给出了 Aystsaga Agro 面临的问题的基于 Python 的解决方案。代码从常见 Python 库(如 pandas、StringIO、requests 等)的常见导入开始。之后,我们从 CSV 文件cashcrop_Yield_dataset.csv中加载数据集。您可以使用 SQLite 数据库来代替。之后,我们通过查看数据框架中每一列的平均值、中值、众数、标准差、最小值和最大值来进行探索性数据分析。在这之后,我们查看异常值并对它们进行计数,比如在代码df['Yield_per_ha'].loc[df['Yield_per_ha'] <=151.50000].count()中的每公顷产量列。这将是我们的预测因素,因为该公司希望知道增加这一产量的参数,正如案例研究中所讨论的那样。然后,我们使用箱线图可视化数据,并查看数字列的偏斜度和峰度。我们还使用面积图和直方图将其可视化。在这之后,是时候查看参考预测值列Yield_per_hadf.corr()代码的列之间的关系了。接下来,在一个三步流程中,第一步中的代码将数据分为要素和目标变量。在第 2 步中,它将最终数据集打乱并拆分为用于构建预测模型的训练数据集和测试数据集。最后一步是模型建立和评估,我们使用线性回归,因为我们的目标是预测每公顷的可变产量。代码的最后几行让我们能够获取任何新的农场价值,并通过代码predicted= regr.predict([[26,1500,6.8,0.9,367,32,490,35]])预测每公顷产量。要了解更多关于代码的结果以及它是如何被执行的,请看清单 9-1 之后的讨论。

# -*- coding: utf-8 -*-
"""
Created on Tue Oct 08 19:33:25 2019

@author: PUNEETMATHUR
"""
#importing python libraries
import pandas as pd
from io import StringIO
import requests
import os
os.getcwd()

#Loading dataset
fname="cashcrop_Yield_dataset.csv"
agriculture= pd.read_csv(fname, low_memory=False, index_col=False)
df= pd.DataFrame(agriculture)
print(df.head(1))

#Checking data sanctity
print(df.size)
print(df.shape)
print(df.columns)
df.dtypes

#Check if there are any columns with empty/null dataset
df.isnull().any()
#Checking how many columns have null values
df.info()

#Using individual functions to do EDA
#Checking out Statistical data Mean Median Mode correlation
df.mean()
df.median()
df.mode()

#How is the data distributed and detecting Outliers
df.std()
df.max()
df.min()
df.quantile(0.25)*1.5
df.quantile(0.75)*1.5

#How many Outliers in the Total Food ordered column
df.columns
df.dtypes
df.set_index(['FarmID'])
df['Yield_per_ha'].loc[df['Yield_per_ha'] <=151.50000].count()
df['Yield_per_ha'].loc[df['Yield_per_ha'] >=159.285].count()

#Visualizing the dataset
df.boxplot(figsize=(17, 10))
df.plot.box(vert=False)
df.kurtosis()
df.skew()
import scipy.stats as sp
sp.skew(df['Yield_per_ha'])

#Visualizing dataset
df.plot()
df.hist(figsize=(10, 6))
df.plot.area()
df.plot.area(stacked=False)

#Now look at correlation and patterns
df.corr()

#Change to dataset columns and look at scatter plots closely
df.plot.scatter(x='Yield_per_ha', y="Soil_pH",s=df['Yield_per_ha']*2)
df.plot.hexbin(x='Yield_per_ha', y="Soil_pH", gridsize=20)

#Data Preparation Steps
#Step 1 Split data into features and target variable
# Split the data into features and target label
cropyield = pd.DataFrame(df['Yield_per_ha'])

dropp=df[['Iron mg/kg','Copper mg/kg','Crop','Center','FarmID','Yield_per_ha']]
features= df.drop(dropp, axis=1)
cropyield.columns
features.columns

#Step 2 Shuffle & Split Final Dataset
# Import train_test_split
from sklearn.cross_validation import train_test_split
from sklearn.utils import shuffle

# Shuffle and split the data into training and testing subsets
features=shuffle(features,  random_state=0)
cropyield=shuffle(cropyield,  random_state=0)
# Split the 'features' and 'income' data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(features, cropyield, test_size = 0.2, random_state = 0)

# Show the results of the split
print("Training set has {} samples.".format(X_train.shape[0]))
print("Testing set has {} samples.".format(X_test.shape[0]))

# Step 3 Model Building & Evaluation
#Creating the the Model for prediction

#Loading model Libraries
import matplotlib.pyplot as plt
import numpy as np
from sklearn import linear_model
from sklearn.metrics import mean_squared_error, r2_score

#Creating Linear Regression object
regr = linear_model.LinearRegression()

regr.fit(X_train,y_train)
y_pred= regr.predict(X_test)
#Printing Codfficients
print('Coefficients: \n',regr.coef_)
#print(LinearSVC().fit(X_train,y_train).coef_)
regr.score(X_train,y_train)
#Mean squared error
print("mean squared error:  %.2f" %mean_squared_error(y_test,y_pred))

#Variance score
print("Variance score: %2f"  % r2_score(y_test, y_pred))

#Plot and visualize the Linear Regression plot
plt.plot(X_test, y_pred, linewidth=3)
plt.show()

#Predicting Yield per hectare for a new farmland
X_test.dtypes
predicted= regr.predict([[26,1500,6.8,0.9,367,32,490,35]])
print(predicted)

Listing 9-1Code for the Solution of the Case Study

这是简单明了的代码。它首先将数据集加载到 pandas 数据帧中,然后通过df.sizedf.dtypes和其他语句检查数据的神圣性。EDA 是用df.mean()df.median()df.mode()语句完成的。相关关系如图 9-1 所示。在我们的案例中,我们可以在清单 9-2 和图 9-2 到 9-9 的输出中查看土壤 pH 值和其他土壤养分的平均值。异常值检测告诉我们 yield 数据列没有超出阈值上限的异常值;但是,所有值都低于阈值下限。这可以通过使用代码中的命令df.hist()绘制直方图来直观地看到。在这之后,我们可以看看相关性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-1

变量之间的相关性

正如我们从图 9-1 中看到的,我们感兴趣的是每公顷预测产量或每公顷产量。我们认为它取决于其他变量或土壤养分;这可以通过查看 Yield_per_ha 变量与其他变量之间的相关值来确认。土壤 ph 值与产量变量的相关系数为 0.936215238,有机碳%为 0.868255792,氮公斤/公顷为 0.831095999,磷公斤/公顷为 0.831095999,钾公斤/公顷为 0.78733627,铁毫克/公斤为-0.030561931,铜毫克/公斤为 0.006008786,硫为了构建我们的模型,我们选择了具有显著相关性的模型,即

  • 土壤 pH 值

  • 有机碳%

  • 氮千克/公顷

  • 磷千克/公顷

  • 钾千克/公顷

  • 硫磺千克/公顷

我们可以忽略和去除铁和铜,因为它们没有表现出任何显著的相关性;事实上,对于我们的模型构建练习来说,它们可以忽略不计。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-9

每公顷产量和土壤 oH 变量的图表

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-8

每公顷产量和土壤 oH 变量散点图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-7

累积堆积面积图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-6

数值变量的堆积面积图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-5

数字变量的直方图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-4

数字变量的面积图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-3

水平箱线图可视化

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-2

垂直箱线图可视化

        Crop  Center  FarmID  landsize_in_ha  Crop_in_tonnes  Yield_per_ha  \
0  Sugarcane  Africa    1234            11.0          1235.3          112.3

   Soil_pH  Organic Carbon %  Nitrogen kg/ha  Phosphorus kg/ha  \
0     7.81              0.88           355.0              36.0

   Potassium kg/ha  Iron mg/kg  Copper mg/kg  Sulphur kg/ha
0            485.0        9.73          4.73  33.0
6314
(451, 14)
Index([u'Crop', u'Center', u'FarmID', u'landsize_in_ha', u'Crop_in_tonnes', u'Yield_per_ha', u'Soil_pH', u'Organic Carbon %', u'Nitrogen kg/ha', u'Phosphorus kg/ha', u'Potassium kg/ha', u'Iron mg/kg', u'Copper mg/kg', u'Sulphur kg/ha'],
      dtype='object')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 451 entries, 0 to 450
Data columns (total 14 columns):
Crop                451 non-null object
Center              451 non-null object
FarmID              451 non-null int64
landsize_in_ha      451 non-null float64
Crop_in_tonnes      451 non-null float64
Yield_per_ha        451 non-null float64
Soil_pH             451 non-null float64
Organic Carbon %    451 non-null float64
Nitrogen kg/ha      451 non-null float64
Phosphorus kg/ha    451 non-null float64
Potassium kg/ha     451 non-null float64
Iron mg/kg          374 non-null float64
Copper mg/kg        337 non-null float64
Sulphur kg/ha       451 non-null float64
dtypes: float64(11), int64(1), object(2)
memory usage: 49.4+ KB
Training set has 360 samples.
Testing set has 91 samples.
('Coefficients: \n', array([[-2.11198278,  0.02322665,  5.78698466,  3.69351487,  0.01497725,  0.17939622,  0.01021635,
         0.15396713]]))
mean squared error:  16.50
Variance score: 0.950297

Listing 9-2Output of Code from Listing 9-1

在图 9-2 到 9-8 中,我们可以看到 bloxplot 向我们展示了一个高度分布的 Crop _ in _ tonnes 值。避免看到这种情况的一种方法是对所有的数字变量应用缩放,就像我在我的书使用 Python 的机器学习应用中对医疗保健、零售和金融的案例研究解决方案所做的那样。直方图显示了变量的分布,我们看到除了铜和铁,大部分土壤养分都是右偏的。我们还注意到,Yield_per_ha 和 Soil_pH 的散点图通过 Python 代码df.plot.hexbin(x='Yield_per_ha', y="Soil_pH", gridsize=20)紧密相关。在完成通常的数据准备步骤后,通过将它分成名为cropyield的目标变量和具有所有其他特征的特征变量,然后我们将数据集分成训练和测试数据集,其中 360 个样本属于训练数据集,91 个样本属于测试数据集。之后,我们加载 linear_model Python 库,通过 Python 代码regr.fit(X_train,y_train) , y_pred= regr.predict(X_test)对训练和测试数据集执行线性回归算法。然后,在开始通过代码print("Variance score: %2f" % r2_score(y_test, y_pred))进行预测之前,我们查看我们从测试数据集获得的准确性分数,这给了我们 0.9502,或 95.02%的准确性。既然这很好,我们可以继续做预测。请记住,这是虚构的数据,所以我们能够达到一个良好的准确性水平。然而,在现实世界中,您可能需要运行更多的回归器或微调您的数据收集工作,以达到这样的准确性水平。

为了进行预测,使用的代码是predicted= regr.predict([[26,1500,6.8,0.9,367,32,490,35]]),这是 Aystsaga Agro 在另一个国家评估的具有以下特征的农业用地的价值:

landsize_in_ha      26
Crop_in_tonnes      1500
Soil_pH             6.8
Organic Carbon %    0.9
Nitrogen kg/ha      367
Phosphorus kg/ha    32
Potassium kg/ha     490
Sulphur kg/ha       35

Glanzo 想从模型中知道他们买下这片农田后可能的年产量。Python 程序给出的值是每公顷 75.2637 吨,如清单 9-3 所示。

predicted= regr.predict([[26,1500,6.8,0.9,367,32,490,35]])
print(predicted)
[[75.26373654]]

Listing 9-3Predicted Value by the Python Program

请记住,该计划没有考虑其他条件,如天气和种子品种,这些因素也会影响作物产量。建立这样一个数据集肯定是一个巨大的练习,超出了本书的范围。

摘要

我们现在已经到了本案例研究和本书的结尾。我非常喜欢为您带来这些基于物联网的解决方案来解决现代的实际商业问题,并尝试通过机器学习来解决这些问题。我希望你也喜欢向他们学习。请考虑在 www.pmauthor.com/raspbian 的论坛上留下反馈。

Logo

在这里,我们一起交流AI,学习AI,用AI改变世界。如有AI产品需求,可访问讯飞开放平台,www.xfyun.cn。

更多推荐