边缘部署:从OTA更新到模型部署的全面指南
1. 边缘部署概述
在单台计算机上执行机器学习和操作(MLOps)就颇具挑战性,而要在数千台计算机上进行模型的训练、部署和维护,其复杂性更是令人望而却步。不过,借助容器化和持续集成/持续部署(CI/CD)管道等工具,能够降低这种复杂性。我们将探讨如何以安全、可更新且针对现有硬件进行优化的方式部署模型。
2. OTA更新MCU
2.1 OTA更新的重要性和方法
OTA更新对于部署安全更新、添加新功能以及更新模型至关重要。有两种不同的OTA更新技术:
-
自定义程序方式
:构建一个自定义程序,理想情况下在与要更新的主程序不同的程序或线程上运行。该软件将新固件下载到闪存中,注册并启动新固件。若新固件启动失败,自定义软件可启动正常工作的版本。通常需要预留一半的闪存用于OTA更新。
-
Azure IoT Edge方式
:使用Azure IoT Edge等系统更新设备上的Docker容器。这需要设备运行完整的操作系统,如Raspbian、Ubuntu或Windows。但大多数物联网设备缺乏支持IoT Edge所需的计算能力。
2.2 准备工作
我们将使用ESP32为小型MCU设备进行OTA更新,采用IDF框架进行编程。Espressif IoT开发框架(ESP - IDF)是一种低级编程框架,与Arduino框架相比,预构建组件较少,但速度更快,更适合工业应用。开发时使用带有PlatformIO扩展的VS Code,创建项目的步骤如下:
1. 访问PlatformIO主页,选择“+ New Project”。
2. 添加项目名称,选择开发板和开发框架,这里使用NodeMCU - 32S作为开发板。
3. 将根目录下的empty.c重命名为main.c,开始编码。
2.3 实现步骤
- 导入必要的库 :
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "cJSON.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "wifi_functions.h"
- 设置固件版本、证书和缓冲区大小 :
#define FIRMWARE_VERSION 0.1
#define UPDATE_JSON_URL "https://microshak.com/esp32/firmware.json"
extern const char server_cert_pem_start[] asm("_binary_certs_pem_start");
extern const char server_cert_pem_end[] asm("_binary_certs_pem_end");
char rcv_buffer[200];
- 创建HTTP事件处理程序 :
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
switch(evt->event_id) {
case HTTP_EVENT_ERROR:
break;
case HTTP_EVENT_ON_CONNECTED:
break;
case HTTP_EVENT_HEADER_SENT:
break;
case HTTP_EVENT_ON_HEADER:
break;
case HTTP_EVENT_ON_DATA:
if (!esp_http_client_is_chunked_response(evt->client)){
strncpy(rcv_buffer, (char*)evt->data, evt->data_len);
}
break;
case HTTP_EVENT_ON_FINISH:
break;
case HTTP_EVENT_DISCONNECTED:
break;
}
return ESP_OK;
}
- 创建无限循环(忽略机器学习算法) :
void ml_task(void *pvParameter)
{
while(1)
{
//ML on this thread
}
}
- 检查OTA更新 :
void check_update_task(void *pvParameter)
{
while(1)
{
printf("Looking for a new firmware...\n");
esp_http_client_config_t config =
{
.url = UPDATE_JSON_URL,
.event_handler = _http_event_handler,
};
esp_http_client_handle_t client =
esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if(err == ESP_OK) {
cJSON *json = cJSON_Parse(rcv_buffer);
if(json == NULL) printf("downloaded file is not a valid json, aborting...\n");
else {
cJSON *version = cJSON_GetObjectItemCaseSensitive(json, "version");
cJSON *file = cJSON_GetObjectItemCaseSensitive(json, "file");
if(!cJSON_IsNumber(version)) printf("unable to read new version, aborting...\n");
else {
double new_version = version->valuedouble;
if(new_version > FIRMWARE_VERSION) {
printf("current firmware version (%.1f) is lower than the available one (%.1f), upgrading...\n", FIRMWARE_VERSION, new_version);
if(cJSON_IsString(file) && (file->valuestring != NULL))
{
printf("downloading and installing new firmware(%s)...\n", file->valuestring);
esp_http_client_config_t ota_client_config =
{
.url = file->valuestring,
.cert_pem = server_cert_pem_start,
};
esp_err_t ret = esp_https_ota(&ota_client_config);
if (ret == ESP_OK)
{
printf("OTA OK, restarting...\n");
esp_restart();
}
else
{
printf("OTA failed...\n");
}
}
else printf("unable to read the new file name, aborting...\n");
}
else printf("current firmware version (%.1f) is greater or equal to the available one (%.1f), nothing to do...\n", FIRMWARE_VERSION, new_version);
}
}
}
else printf("unable to download the json file, aborting...\n");
esp_http_client_cleanup(client);
printf("\n");
vTaskDelay(60000 / portTICK_PERIOD_MS);
}
}
- 初始化Wi - Fi :
static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch(event->event_id)
{
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
esp_wifi_connect();
break;
default:
break;
}
return ESP_OK;
}
void wifi_initialise(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
wifi_event_group = xEventGroupCreate();
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
wifi_config_t wifi_config = {
.sta = {
.ssid = "mynetwork",
.password = "mywifipassword",
},
};
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
}
void wifi_wait_connected()
{
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true,
portMAX_DELAY);
}
- 在主循环中启动Wi - Fi并创建任务 :
void app_main() {
printf("HTTPS OTA, firmware %.1f\n\n", FIRMWARE_VERSION);
wifi_initialise();
wifi_wait_connected();
printf("Connected to wifi network\n");
xTaskCreate(&ml_task, "ml_task", configMINIMAL_STACK_SIZE, NULL,
5, NULL);
xTaskCreate(&check_update_task, "check_update_task", 8192, NULL,
5, NULL);
}
2.4 工作原理
该程序有三个任务:
1. 设置并确保连接到Wi - Fi,在建立连接之前不执行其他操作。
2. 程序使用Free RTOS作为实时操作系统,允许线程独立执行,从而拥有两个非阻塞线程。第一个线程执行机器学习任务,第二个线程执行更新任务。更新任务可以不那么频繁地轮询Web服务器。
2.5 额外说明
OTA更新器需要一个清单来检查当前版本并找到要下载的文件,以下是一个清单的.json文件示例:
{
"version":1.2,
"file":"https://microshak.com/firmware/otaml1_2.bin"
}
大多数硅设备制造商(如ESP32或STM32)已经解决了OTA更新问题,通常会提供示例代码帮助快速启动项目。
3. 使用IoT Edge部署模块
3.1 IoT Edge的作用
将模型部署到边缘存在风险。如果更新导致整个设备群出现故障,可能会造成不可挽回的损失。而IoT Edge通过使用Docker技术,专门解决了在物联网设备上运行多个程序的问题。例如,采矿设备需要进行地理围栏操作、设备故障预测的机器学习以及自动驾驶汽车的强化学习等,其中任何一个程序的更新都不会影响其他模块。
3.2 准备工作
需要一个Azure IoT Hub和一个Azure容器注册表,还需要安装了Azure IoT扩展的Visual Studio Code(VS Code)和一个Raspberry Pi。主要步骤如下:
1.
设置Raspberry Pi
:
- 允许SSH连接:在Raspberry Pi上,进入“Menu | Preferences | Raspberry Pi Configuration”,点击“Interfaces”并启用SSH。
- 获取IP地址:在终端窗口中输入“hostname -I”,获取Raspberry Pi的IP地址。
- 连接到Raspberry Pi:在VS Code中安装SSH插件,使用“Connect to SSH”按钮,根据设备IP地址和密码连接到Raspberry Pi。连接成功后,可在设备上创建新项目。
- 安装IoT Edge代理:按照https://docs.microsoft.com/en-us/azure/iot - edge/how - to - install - iot - edge - linux的说明进行安装。
2.
代码设置
:
- 创建新的IoT Edge项目:打开Visual Studio,安装Azure IoT Edge扩展和Docker扩展。使用“Ctrl + Shift + P”打开命令窗口,输入“Azure IoT Edge:”,选择“Azure IoT Edge: New IoT Edge Solution”。
- 按照向导命名项目并添加模块。项目可以有多个执行不同任务的模块,这些模块可以用不同语言编写,也可以使用Azure机器学习服务集成该平台上的预构建模型。这里我们创建一个自定义Python模块。
- VS Code的代码生成器会创建一个main.py文件,其中包含一个从IoT Hub接收消息并回显的起始模板。
3.3 实现步骤
- 在main.py文件中导入必要的库 :
import time
import os
import sys
import asyncio
from six.moves import input
import threading
from azure.iot.device.aio import IoTHubModuleClient
from azure.iot.device import Message
import uuid
- 创建ML代码的存根 :
def MLCode():
# You bispoke ML code here
return True
- 创建消息发送函数 :
async def send_d2c_message(module_client):
while True:
msg = Message("test machine learning ")
msg.message_id = uuid.uuid4()
msg.custom_properties["MachineLearningBasedAlert"] = MLCode()
await module_client.send_message_to_output(msg, "output1")
- 创建消息接收函数 :
def stdin_listener():
while True:
try:
selection = input("Press Q to quit\n")
if selection == "Q" or selection == "q":
print("Quitting...")
break
except:
time.sleep(10)
- 启动消息发送线程和消息接收线程 :
async def main():
try:
module_client = IoTHubModuleClient.create_from_edge_environment()
await module_client.connect()
listeners = asyncio.gather(send_d2c_message(module_client))
loop = asyncio.get_event_loop()
user_finished = loop.run_in_executor(None, stdin_listener)
# Wait for user to indicate they are done listening for messages
await user_finished
# Cancel listening
listeners.cancel()
# Finally, disconnect
await module_client.disconnect()
except Exception as e:
print ( "Unexpected error %s " % e )
raise
- 设置标准Python主程序入口点 :
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
3.4 工作原理
在这个过程中,我们学习了如何为开发边缘模块准备设备和开发环境。IoT Edge的编码范式是接收消息、执行操作并发送消息。在代码中,我们将这些操作分离成不同的任务,这些任务可以独立运行。使用asyncio库实现Python的多线程,方便在不同时间循环中执行不同操作。代码准备好后,可以构建Docker容器并部署到安装了边缘模块的其他设备或整个设备群。
3.5 额外说明
添加代码到设备后,需要在设备架构上本地构建代码。确保设备镜像正常工作后,可将其上传到容器注册表。具体操作如下:
1. 在Visual Studio项目中,右键单击module.json文件,可选择本地构建或构建并推送到容器注册表。
2. 右键单击deployment.template.json文件,选择“Generate IoT Edge Deployment Manifest”,VS Code会生成一个包含deployment.arm32.json文件的config文件夹。
3. 右键单击deployment.arm32.json文件,可选择部署到单个设备或设备群。部署更新后,可在门户中查看更新情况。如果部署更新了设备孪生,可以使用它查询整个设备群的部署状态。
4. 使用TensorFlow.js将计算卸载到Web
4.1 卸载计算的原因
物联网失败的一个重要驱动因素是成本。设备通常以较低的固定价格出售,但设备制造商可能会有持续的成本。将一些机器学习计算卸载到访问数据的设备或应用程序上,可以降低这些持续成本。我们将使用TensorFlow.js将昂贵的计算卸载到查看网页的人的浏览器中。
4.2 准备工作
基于之前的相关实验,使用MLflow实验检索模型,将其转换为可在前端使用TensorFlow.js运行的模型。具体步骤如下:
1. 安装TensorFlow.js:运行“pip install tensorflowjs”。
2. 转换模型:找到从MLflow工件下载的模型(即保存的Keras模型),运行以下命令:
tensorflowjs_converter --input_format=keras model.h5 tfjs_model
其中,model.h5是从预测性维护数据集中保存的Keras LSTM模型,tfjs_model是模型将被放置的文件夹。
3. 打开Visual Studio,编写两个文件:一个HTML文件和一个JavaScript文件。创建好文件后,可使用GitHub仓库中的webserver.py文件在本地运行,在浏览器中访问http://localhost:8080/index.html。仓库中还有一个data.json文件,代表向网页返回数据的Web服务。
4.3 实现步骤
- 在index.js文件中添加GetData函数 :
async function GetData(){
$.get( "/data.json", function( data ) {
$( "#data" ).text( data["dat"] );
predict(data["dat"])
});
}
- 创建预测函数 :
async function predict(dat)
{
const model = await tf.loadLayersModel('/tfjs_model/model.json');
console.log(model)
dat = tf.tensor3d(dat, [1, 50, 25] )
dat[0] = null
console.log(dat)
var pred = model.predict( dat)
const values = pred.dataSync();
let result = "Needs Maintenance"
if(values[0] < .8)
result = "Does not need Maintenance"
$('#needed').html(result )
}
- 创建index.html文件调用js文件 :(此处原文档未给出完整代码,可根据实际情况补充)
通过以上步骤,我们可以将机器学习计算卸载到Web浏览器中,降低物联网设备的成本。
总结
本文详细介绍了边缘部署的多个方面,包括OTA更新MCU、使用IoT Edge部署模块以及使用TensorFlow.js将计算卸载到Web等内容。通过这些技术和方法,可以更安全、高效地在边缘设备上部署和更新模型,同时降低成本。这些方法在物联网、工业设备等多个领域具有广泛的应用前景。
下面是一个简单的mermaid流程图,展示了OTA更新MCU的主要流程:
graph TD;
A[开始] --> B[初始化Wi-Fi];
B --> C[检查OTA更新];
C --> D{版本是否不同};
D -- 是 --> E[下载并安装新固件];
E --> F[重启设备];
D -- 否 --> G[等待一段时间后再次检查];
G --> C;
同时,为了更清晰地展示各个部分的步骤,我们可以用表格进行总结:
| 操作内容 | 步骤 |
| ---- | ---- |
| OTA更新MCU | 1. 导入必要库;2. 设置固件版本等;3. 创建HTTP事件处理程序;4. 创建无限循环;5. 检查OTA更新;6. 初始化Wi-Fi;7. 在主循环中启动任务 |
| 使用IoT Edge部署模块 | 1. 设置Raspberry Pi;2. 代码设置;3. 编写main.py文件中的代码;4. 构建和部署代码 |
| 使用TensorFlow.js将计算卸载到Web | 1. 安装和转换模型;2. 编写index.js和index.html文件 |
5. 移动模型部署
5.1 移动模型部署的意义
在当今的物联网和移动应用场景中,将模型部署到移动设备上具有重要意义。移动设备如智能手机、平板电脑等具有广泛的使用场景,能够实时处理数据并提供个性化的服务。通过在移动设备上部署模型,可以减少对云端的依赖,提高响应速度,同时保护用户数据的隐私。
5.2 准备工作
虽然文档中未详细提及移动模型部署的具体准备工作,但通常需要以下几个方面:
1.
选择合适的移动平台
:如Android或iOS。
2.
安装开发环境
:对于Android,需要安装Android Studio;对于iOS,需要安装Xcode。
3.
选择合适的模型格式
:常见的有TensorFlow Lite等,它专门为移动和嵌入式设备设计,具有较小的模型大小和较低的计算资源需求。
5.3 实现步骤
由于缺乏详细的代码示例,大致的实现步骤如下:
1.
模型转换
:将训练好的模型转换为适合移动设备的格式,如使用TensorFlow Lite转换器将TensorFlow模型转换为.tflite文件。
tflite_convert --output_file=model.tflite --saved_model_dir=path/to/saved_model
-
集成到移动应用
:
- Android :在Android项目中添加TensorFlow Lite依赖,然后在代码中加载和运行模型。
import org.tensorflow.lite.Interpreter;
// 加载模型
try (Interpreter tflite = new Interpreter(new FileInputStream(new File("path/to/model.tflite")).getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()))) {
// 准备输入数据
float[][] input = new float[1][inputSize];
// 运行模型
float[][] output = new float[1][outputSize];
tflite.run(input, output);
} catch (IOException e) {
e.printStackTrace();
}
- **iOS**:在Xcode项目中添加TensorFlow Lite框架,使用Swift或Objective - C代码加载和运行模型。
import TensorFlowLite
// 加载模型
let modelPath = Bundle.main.path(forResource: "model", ofType: "tflite")!
let interpreter = try! Interpreter(modelPath: modelPath)
// 准备输入数据
let inputTensor = try! interpreter.input(at: 0)
let inputData = ... // 填充输入数据
try! interpreter.resizeInput(at: 0, to: [1, inputSize])
try! interpreter.copy(inputData, toInputAt: 0)
// 运行模型
try! interpreter.invoke()
// 获取输出数据
let outputTensor = try! interpreter.output(at: 0)
let outputData = outputTensor.data
5.4 工作原理
移动模型部署的核心是将训练好的模型转换为适合移动设备的格式,并在移动设备上加载和运行。模型转换过程会对模型进行优化,减少模型大小和计算量。在移动设备上,通过相应的API加载模型,将输入数据传递给模型进行推理,最后获取输出结果。
6. 使用设备孪生维护设备群
6.1 设备孪生的概念
设备孪生是一种虚拟的设备表示,它包含了设备的状态、配置和其他相关信息。通过设备孪生,可以远程管理和监控设备群,实现设备的统一配置和状态同步。
6.2 工作原理
设备孪生通常由两部分组成:
1.
报告属性
:设备向云端报告自己的状态和配置信息。
2.
所需属性
:云端向设备发送配置信息和指令。
设备和云端通过设备孪生进行双向通信,确保设备的状态和配置与云端一致。
6.3 实现步骤
- 创建设备孪生 :在Azure IoT Hub中创建设备孪生,为每个设备分配一个唯一的标识符。
- 设备端实现 :在设备端代码中,定期向云端报告设备的状态信息,并监听云端发送的所需属性更新。
from azure.iot.device import IoTHubDeviceClient, Message
import json
# 连接到IoT Hub
device_client = IoTHubDeviceClient.create_from_connection_string("your_connection_string")
device_client.connect()
# 报告属性
reported_properties = {
"status": "online",
"temperature": 25
}
device_client.patch_twin_reported_properties(reported_properties)
# 监听所需属性更新
def twin_patch_handler(patch):
print("Received desired properties update: {}".format(patch))
# 处理所需属性更新
# ...
device_client.on_twin_desired_properties_patch_received = twin_patch_handler
while True:
pass
- 云端管理 :在云端通过Azure IoT Hub的API或控制台,查看设备的报告属性,发送所需属性更新。
6.4 优势
使用设备孪生可以实现对设备群的集中管理和监控,提高设备的可维护性和可靠性。同时,通过设备孪生可以快速部署配置更新,减少人工干预。
7. 雾计算实现分布式机器学习
7.1 雾计算的概念
雾计算是一种介于云计算和边缘计算之间的计算模式,它将计算和数据存储靠近数据源,减少数据传输延迟。在分布式机器学习中,雾计算可以将模型训练和推理任务分布到多个边缘设备和雾节点上,提高计算效率和数据隐私。
7.2 工作原理
雾计算的工作原理如下:
1.
数据采集
:边缘设备采集数据。
2.
数据处理
:边缘设备对数据进行初步处理,并将处理后的数据发送到雾节点。
3.
模型训练和推理
:雾节点和边缘设备协同进行模型训练和推理,减少对云端的依赖。
4.
结果反馈
:将处理结果反馈给边缘设备或云端。
7.3 实现步骤
- 架构设计 :设计雾计算架构,确定边缘设备、雾节点和云端的角色和通信方式。
- 数据分发 :将数据分发到边缘设备和雾节点上。
- 模型训练 :在边缘设备和雾节点上进行模型训练,可以使用联邦学习等技术,确保数据隐私。
- 模型聚合 :将各个节点的模型参数进行聚合,得到全局模型。
- 模型部署 :将聚合后的模型部署到边缘设备和雾节点上。
7.4 优势
雾计算实现分布式机器学习具有以下优势:
1.
降低数据传输延迟
:减少数据在网络中的传输时间,提高响应速度。
2.
保护数据隐私
:数据在本地处理,减少了数据泄露的风险。
3.
提高计算效率
:利用多个节点的计算资源,加速模型训练和推理过程。
总结
本文全面介绍了边缘部署的多个关键方面,包括OTA更新MCU、使用IoT Edge部署模块、使用TensorFlow.js将计算卸载到Web、移动模型部署、使用设备孪生维护设备群以及雾计算实现分布式机器学习。通过这些技术和方法,可以更安全、高效地在边缘设备上部署和更新模型,降低成本,提高系统的可靠性和可维护性。
为了更清晰地展示各个部分的关联,下面是一个mermaid流程图:
graph LR;
A[OTA更新MCU] --> B[使用IoT Edge部署模块];
B --> C[使用TensorFlow.js将计算卸载到Web];
C --> D[移动模型部署];
D --> E[使用设备孪生维护设备群];
E --> F[雾计算实现分布式机器学习];
F --> G[整体边缘部署系统];
同时,我们用表格对各个部分的关键信息进行总结:
| 技术名称 | 关键作用 | 主要步骤 |
| ---- | ---- | ---- |
| OTA更新MCU | 实现MCU设备的远程固件更新 | 导入库、设置参数、创建处理程序、检查更新、初始化Wi-Fi、启动任务 |
| 使用IoT Edge部署模块 | 安全高效地在边缘设备上部署多个程序 | 设置设备、代码设置、编写代码、构建和部署 |
| 使用TensorFlow.js将计算卸载到Web | 降低物联网设备成本 | 安装转换模型、编写文件 |
| 移动模型部署 | 实现模型在移动设备上的运行 | 转换模型、集成到移动应用 |
| 使用设备孪生维护设备群 | 集中管理和监控设备群 | 创建设备孪生、设备端实现、云端管理 |
| 雾计算实现分布式机器学习 | 提高计算效率和数据隐私 | 架构设计、数据分发、模型训练、聚合和部署 |
超级会员免费看
473

被折叠的 条评论
为什么被折叠?



