当前位置:首页 > AI技术 > 正文内容

使用 ESP

admin2周前 (03-24)AI技术24

针对该分类问题,我们使用了

Kaggle 手势识别数据集

中的一个开源数据集。原始数据集包括 10 个类别,我们只使用了其中 6 个。这些类别更容易识别,且日常生活中更有用,如下表所示。我们的数据集较原数据集还有一处关于图像大小的区别,在原始数据集中,图像的大小为 (240, 640),但为了方便起见,我们将数据集的大小调整为 (96, 96)。本文中使用的数据集可以在

1.2 测试/训练分离

我们需要将数据集分为测试和训练数据集。这些数据集是我们原始

的子集,训练数据集用于训练模型、测试数据集用于测试模型的性能。校准数据集在

阶段用于校准,您可以从训练集和测试集中抽选一部分作为校准数据集。生成以上数据集的过程是相同的,我们使用了 train_test_split 以实现此目标。

from sklearn.model_selection import train_test_split

ts = 0.3 # Percentage of images that we want to use for testing.

X_train, X_test1, y_train, y_test1 = train_test_split(X, y, test_size=ts, random_state=42)

X_test, X_cal, y_test, y_cal = train_test_split(X_test1, y_test1, test_size=ts, random_state=42)

,了解关于 train_test_split 的更多细节

如果您需要转载本教程,您可以在此

获得数据,并在您的工作环境中开放数据。

import pickle

with open('X_test.pkl', 'rb') as file:

X_test = pickle.load(file)

with open('y_test.pkl', 'rb') as file:

y_test = pickle.load(file)

with open('X_train.pkl', 'rb') as file:

X_train = pickle.load(file)

with open('y_train.pkl', 'rb') as file:

y_train = pickle.load(file)

我们为此分类问题创建了一个基本的卷积神经网络 (Convolution Neural Network, CNN)。它由 3 个卷积层组成,然后是最大池和全连接层,输出层有 6 个神经元。您可以点击

,了解创建 CNN 的更多内容。以下是用于建立 CNN 的代码。

import tensorflow as tf

from tensorflow import keras

from keras.models import Sequential

from keras.layers.convolutional import Conv2D, MaxPooling2D

from keras.layers import Dense, Flatten, Dropout

print(tf.__version__)

model = Sequential()

model.add(Conv2D(32, (5, 5), activation='relu', input_shape=(96, 96, 1)))

model.add(MaxPooling2D((2, 2)))

model.add(Dropout(0.2))

model.add(Conv2D(64, (3, 3), activation='relu'))

model.add(MaxPooling2D((2, 2)))

model.add(Dropout(0.2))

model.add(Conv2D(64, (3, 3), activation='relu'))

model.add(MaxPooling2D((2, 2)))

model.add(Flatten())

model.add(Dense(128, activation='relu'))

model.add(Dense(6, activation='softmax'))

model.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=['accuracy'])

model.summary()

插图 1:CNN 模型

该模型运行了 5 个 epochs,最终准确率为 99% 左右。

history=model.fit(X_train, y_train, epochs=5, batch_size=64, verbose=1, validation_data=(X_test, y_test))

将训练好的模型保存为分层数据格式 (.h5)。您可以点击

,了解关于如何保存 Keras 模型的更多内容。

model.save('handrecognition_model.h5')

ESP-DL 使用开放式神经网络交换 (ONXX) 格式的模型。您可以点击

,了解 ONXX 是如何工作的。为了与 ESP-DL 兼容,请使用下方代码将训练的 .h5 格式的模型转换为 ONXX 格式。

model = tf.keras.models.load_model("/content/handrecognition_model.h5")

tf.saved_model.save(model, "tmp_model")

!python -m tf2onnx.convert --saved-model tmp_model --output "handrecognition_model.onnx"

!zip -r /content/tmp_model.zip /content/tmp_model

最后,下载 H5 格式的模型、ONNX 格式的模型和模型检查点,以供将来使用。

from google.colab import files

files.download("/content/handrecognition_model.h5")

files.download("/content/handrecognition_model.onnx")

files.download("/content/tmp_model.zip")

2. ESP-DL 格式

当模型的 ONNX 格式准备就绪,您可以按照以下步骤将您的模型转换为 ESP-DL 格式。

IDE 进行 ESP-DL 格式转换。

2.1 格式转换要求

首先,您需要成功搭建环境,并安装正确的模块版本,否则将出现错误。您可以点击

,阅读关于 ESP-DL 格式转换要求的更多信息。

Python == 3.7

Numba == 0.53.1

pip install Numba==0.53.1

ONNX == 1.9.0

pip install ONNX==1.9.0

ONNX Runtime == 1.7.0

pip install ONNXRuntime==1.7.0

ONNX Optimizer == 0.2.6

pip install ONNXOptimizer==0.2.6

表格 2:所需的模块和特定版本

接下来,您需要下载 ESP-DL,并从 GitHub 仓库克隆

git clone --recursive https://github.com/espressif/esp-dl.git

为了运行 ESP-DL 提供的优化器,以 Window 系统为例,我们需要找到并将以下文件放入 pychram - IDE 的工作目录中。

calibrator.pyd

calibrator_acc.pyd

evaluator.pyd

optimizer.py

中生成的校准数据集和在

中保存的 ONNX 格式模型放在一起。您的工作目录应该是这样的。

您可以按照下面的步骤生成优化后的模型和量化参数。

from optimizer import *

from calibrator import *

from evaluator import *

2.2.2 加载 ONNX 模型

onnx_model = onnx.load("handrecognition_model.onnx")

2.2.3 优化 ONNX 模型

optimized_model_path = optimize_fp_model("handrecognition_model.onnx")

2.2.4 加载校准数据集

with open('X_cal.pkl', 'rb') as f:

(test_images) = pickle.load(f)

with open('y_cal.pkl', 'rb') as f:

(test_labels) = pickle.load(f)

calib_dataset = test_images[0:1800:20]

pickle_file_path = 'handrecognition_calib.pickle'

model_proto = onnx.load(optimized_model_path)

print('Generating the quantization table:')

calib = Calibrator('int16', 'per-tensor', 'minmax')

# calib = Calibrator('int8', 'per-channel', 'minmax')

calib.set_providers(['CPUExecutionProvider'])

# Obtain the quantization parameter

calib.generate_quantization_table(model_proto,calib_dataset, pickle_file_path)

# Generate the coefficient files for esp32s3

calib.export_coefficient_to_cpp(model_proto, pickle_file_path, 'esp32s3', '.', 'handrecognition_coefficient', True)

如果一切正常,这时您可以在路径中生成两个扩展名为 .cpp 和 .hpp 的文件,如下图。

注:稍后您还将用到这个输出结果,建议先截图保存。

插图 4:优化模型的输出结果

这一步并不是模型格式转化的必要步骤,如您希望评估优化后模型的性能,您可以使用以下代码。

print('Evaluating the performance on esp32s3:')

eva = Evaluator('int16', 'per-tensor', 'esp32s3')

eva.set_providers(['CPUExecutionProvider'])

eva.generate_quantized_model(model_proto, pickle_file_path)

output_names = [n.name for n in model_proto.graph.output]

providers = ['CPUExecutionProvider']

m = rt.InferenceSession(optimized_model_path, providers=providers)

batch_size = 64

batch_num = int(len(test_images) / batch_size)

fp_res = 0

input_name = m.get_inputs()[0].name

for i in range(batch_num):

# int8_model

[outputs, _] = eva.evalaute_quantized_model(test_images[i * batch_size:(i + 1) * batch_size], False)

res = res + sum(np.argmax(outputs[0], axis=1) == test_labels[i * batch_size:(i + 1) * batch_size])

# floating-point model

fp_outputs = m.run(output_names, {input_name: test_images[i * batch_size:(i + 1) * batch_size].astype(np.float32)})

fp_res = fp_res + sum(np.argmax(fp_outputs[0], axis=1) == test_labels[i * batch_size:(i + 1) * batch_size])

print('accuracy of int8 model is: %f' % (res / len(test_images)))

print('accuracy of fp32 model is: %f' % (fp_res / len(test_images)))

,了解更多关于 ESP-DL API 的信息。

模型部署是最后的关键步骤。在这里,我们将在

微控制器上运行模型并得到结果。

Visual Studio Code

在 ESP32-S3 上部署模型。

3.1 ESP-IDF 项目的层次结构

首先,您需要在 VSCode 中创建一个基于 ESP-IDF 标准的新项目。您可以观看

,了解如何基于 ESP32 系列芯片创建 VSCode 项目。

中生成的 .cpp 和 .hpp 文件复制到当前的工作目录中。

再次,请您将所有依赖的组件添加到工作目录下的组件文件夹中。

最后,sdkconfig 文件是

ESP-WHO 示例

中的默认文件,您可以在

项目目录如下图所示:

├── CMakeLists.txt

├── components

│ ├── esp-dl

├── dependencies.lock

│ ├── app_main.cpp

│ └── CMakeLists.txt

│ ├── handrecognition_coefficient.cpp

│ ├── handrecognition_coefficient.hpp

│ └── model_define.hpp

├── partitions.csv

├── sdkconfig

├── sdkconfig.defaults

├── sdkconfig.defaults.esp32

├── sdkconfig.defaults.esp32s2

└── sdkconfig.defaults.esp32s3

注:ESP-WHO 不是本教程必须的项目。

我们将在 “model_define.hpp” 文件中定义模型,您可以依照下面的步骤进行操作。

首先导入所有相关的库。接下来您需要知道模型的具体结构,您可以使用开源工具

结束时优化生成的 ONNX 模型。您可以在

#pragma once

#include

#include "dl_layer_model.hpp"

#include "dl_layer_base.hpp"

#include "dl_layer_max_pool2d.hpp"

#include "dl_layer_conv2d.hpp"

#include "dl_layer_reshape.hpp"

#include "dl_layer_softmax.hpp"

#include "handrecognition_coefficient.hpp"

using namespace dl;

using namespace layer;

using namespace handrecognition_coefficient;

接下来是定义每个层。

我们一般不认为输入是一个独立的层,在这里我们没有对其作定义。

除了输出层,所有层都定义为私有层。

在建立模型时,请按照前面

中定义的顺序放置各层。

class HANDRECOGNITION : public Model

Conv2D l1;

MaxPool2D l2;

Conv2D l3;

MaxPool2D l4;

Conv2D l5;

MaxPool2D l6;

Reshape l7;

Conv2D l8;

Conv2D l9;

Softmax l10; // output layer

3.2.3 初始化层

在定义了各层之后,我们需要初始化每个层的权重、偏置激活函数和形状。我们可以逐层进行检查。

在详述细节前,让我们先在 Netron 里打开模型,导入该模型的目的是获得一些初始化的参数。

插图 5:优化后的模型视图

图中的第一层是 reshape 层(输入没有被作为单独的层)。这一层的输入是 (96, 96, 1),顺序是 (H, W, C),输出的形状是 (1, 96, 96),顺序变为了 (C, H, W)。在之前 1.3 节的训练代码中,我们没有加入 reshape 层,但优化后的 ONNX 模型出现了 reshape 层,这是因为 ONNX 要求的运算(例如卷积层)张量顺序为 (C, H, W),在 1.6 节模型转化所述的过程中会添加 reshape 层来满足要求。我们的训练平台与 ESP-DL 要求的顺序是一致的,为 (H, W, C),与输入的顺序一致,因此不需要在部署的代码里添加 reshape 层,您可以

点击此 GitHub 链接

对于二维卷积层,我们可以从前面

末尾生成的 .hpp 文件中获得该层的过滤器、偏置项和激活函数的名称。但是对于指数,我们需要检查

对于最大池化层 (max-pooling layer),我们可以使用与建立模型时相同的参数,请参见本文的

。另一种了解参数和层的方法是使用开源工具

结束时优化生成的 ONNX 模型。

对于密集层或全连接层,我们使用了二维卷积模块。我们可以从前面

末尾生成的 .hpp 文件中获得该层的过滤器、偏置项和激活函数的名称。但是对于指数,我们需要检查

输出层是一个 SoftMax 层权重,其名称可以从

HANDRECOGNITION () :

l1(Conv2D(-8, get_statefulpartitionedcall_sequential_1_conv2d_3_biasadd_filter(), get_statefulpartitionedcall_sequential_1_conv2d_3_biasadd_bias(), get_statefulpartitionedcall_sequential_1_conv2d_3_biasadd_activation(), PADDING_VALID, {}, 1,1, "l1")),

l2(MaxPool2D({2,2},PADDING_VALID, {}, 2, 2, "l2")),

l3(Conv2D(-9, get_statefulpartitionedcall_sequential_1_conv2d_4_biasadd_filter(), get_statefulpartitionedcall_sequential_1_conv2d_4_biasadd_bias(), get_statefulpartitionedcall_sequential_1_conv2d_4_biasadd_activation(), PADDING_VALID,{}, 1,1, "l3")),

l4(MaxPool2D({2,2},PADDING_VALID,{}, 2, 2, "l4")),

l5(Conv2D(-9, get_statefulpartitionedcall_sequential_1_conv2d_5_biasadd_filter(), get_statefulpartitionedcall_sequential_1_conv2d_5_biasadd_bias(), get_statefulpartitionedcall_sequential_1_conv2d_5_biasadd_activation(), PADDING_VALID,{}, 1,1, "l5")),

l6(MaxPool2D({2,2},PADDING_VALID,{}, 2, 2, "l6")),

l7(Reshape({1,1,6400},"l7_reshape")),

l8(Conv2D(-9, get_fused_gemm_0_filter(), get_fused_gemm_0_bias(), get_fused_gemm_0_activation(), PADDING_VALID, {}, 1, 1, "l8")),

l9(Conv2D(-9, get_fused_gemm_1_filter(), get_fused_gemm_1_bias(), NULL, PADDING_VALID,{}, 1,1, "l9")),

l10(Softmax(-14,"l10")){}

下一步是建立每个层。请

,了解构建每层的构建函数。

void build(Tensor &input)

this->l1.build(input);

this->l2.build(this->l1.get_output());

this->l3.build(this->l2.get_output());

this->l4.build(this->l3.get_output());

this->l5.build(this->l4.get_output());

this->l6.build(this->l5.get_output());

this->l7.build(this->l6.get_output());

this->l8.build(this->l7.get_output());

this->l9.build(this->l8.get_output());

this->l10.build(this->l9.get_output());

最后,我们需要将这些层连接起来,并通过调用函数逐一调用它们。请

,了解每层的调用函数。

void call(Tensor &input)

this->l1.call(input);

input.free_element();

this->l2.call(this->l1.get_output());

this->l1.get_output().free_element();

this->l3.call(this->l2.get_output());

this->l2.get_output().free_element();

this->l4.call(this->l3.get_output());

this->l3.get_output().free_element();

this->l5.call(this->l4.get_output());

this->l4.get_output().free_element();

this->l6.call(this->l5.get_output());

this->l5.get_output().free_element();

this->l7.call(this->l6.get_output());

this->l6.get_output().free_element();

this->l8.call(this->l7.get_output());

this->l7.get_output().free_element();

this->l9.call(this->l8.get_output());

this->l8.get_output().free_element();

this->l10.call(this->l9.get_output());

this->l9.get_output().free_element();

在模型建立后,需要给定输入并运行模型来进行推理。您可以将生成的输入内容放在 “app_main.cpp”文件里,然后在

#include

#include

#include "esp_system.h"

#include "freertos/FreeRTOS.h"

#include "freertos/task.h"

#include "dl_tool.hpp"

#include "model_define.hpp"

3.3.2 输入声明

我们训练好的模型的输入大小为 (96, 96, 1),详情请见

。input_exponent 可以从

生成的输出中获得其指数值。您可以把输入/测试图片的像素写在这里。

int input_height = 96;

int input_width = 96;

int input_channel = 1;

int input_exponent = -7;

__attribute__((aligned(16))) int16_t example_element[] = {

//add your input/test image pixels

3.3.3 设置输入参数

每个输入的像素将根据上面声明的 input_exponent 进行调整。

extern "C" void app_main(void)

Tensor input;

input.set_element((int16_t *)example_element).set_exponent(input_exponent).set_shape({input_height,input_width,input_channel}).set_auto_free(false);

3.3.4. 调用模型

通过调用 forward 函数并传入输入来调用模型。延迟时间 (Latency) 用于计算 ESP32-S3 运行神经网络的时间。

HANDRECOGNITION model;

dl::tool::Latency latency;

latency.start();

model.forward(input);

latency.end();

latency.print("\nSIGN", "forward");

3.3.5. 监测输出

输出来自公共层,例如 l10,您可以在终端打印结果。

float *score = model.l10.get_output().get_element_ptr();

float max_score = score[0];

int max_index = 0;

for (size_t i = 0; i < 6; i++)

printf("%f, ", score[i]*100);

if (score[i] > max_score)

max_score = score[i];

max_index = i;

printf("\n");

switch (max_index)

printf("Palm: 0");

printf("I: 1");

printf("Thumb: 2");

printf("Index: 3");

printf("ok: 4");

printf("C: 5");

printf("No result");

printf("\n");

插图 6 显示了输出结果,在 ESP32-S3 上,模型的延迟时间约为 0.7 秒,每个神经元的输出和最后的预测结果都能够显示出来。

ESP32-S3-EYE

开发板设计一个模型,它可以实时捕捉图像并进行手势识别。未来您可以在

此 GitHub 页面

相关文章

什么是LLM?看这一篇就够了!

一、全套AGI大模型学习路线 AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能! 二、640套AI大模型报告合集 这套包含640份报告的合集,涵盖了AI大...

【DL】2023年你应该知道的 10 大深度学习算法

3. 循环神经网络 (RNN) 4. 生成对抗网络 (GAN) 5. 径向基函数网络 (RBFN) 6. 多层感知器 (MLP) 7. 自组织图 (SOM)...

一文讲清神经网络、BP神经网络、深度学习的关系

人工神经网络中的顶级代表。往往说《神经网络》就是指《BP神经网络》。 大家研究着各种神经网络,研究得不亦乐乎, 来了两个家伙Romelhart 和Mcclelland,...

什么是人工智能 ?

您可以使用 ML 训练 AI,使其精确、快速地执行任务。这可以通过自动化员工感到吃力或厌烦的业务部分来提高运营效率。同样,您可以使用 AI 自动化来腾出员工资源,用于更复杂和更具创造性的工作。...

前端开发高级应用:MuleRun如何连接Slack通知 MuleRun消息推送集成配置步骤实战案例|Duuu笔记

若MuleRun无法向Slack推送通知,需依次配置Incoming Webhook或Bot Token、在MuleRun中设置对应通知目标参数,并通过最小化任务测试验证;常见失败原因包括凭据错误、权...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。