使用Python和Arduino在ESP32上预测正弦函数

使用Python和Arduino在ESP32上预测正弦函数

引言

在嵌入式系统中,使用机器学习进行预测和推理的需求越来越普遍。在本文中,我们将介绍如何使用Python和Arduino配合,在ESP32开发板上预测正弦函数。我们将使用Python训练一个神经网络模型,并将其转换为TensorFlow Lite格式,然后将模型加载到ESP32上进行推理。

简介

本教程旨在帮助您了解如何使用Python和Arduino在ESP32上进行机器学习推理。我们将训练一个能够预测正弦函数的神经网络模型,并将其转换为TensorFlow Lite格式,然后使用Arduino加载模型,并在ESP32上进行预测。这对于嵌入式系统中的智能传感器、边缘计算等应用非常有用。

1. 训练模型

首先,我们需要编写Python代码来训练神经网络模型。在代码中,我们使用NumPy生成正弦函数的样本数据,并使用TensorFlow库构建和训练模型。

代码中需要用到tensorflow库,可通过以下命令安装

pip install tensorflow

python代码

import math
import numpy as np
import tensorflow as tf
from tensorflow.keras import layers


def get_model():
    SAMPLES = 1000
    np.random.seed(1337)
    x_values = np.random.uniform(low=0, high=2 * math.pi, size=SAMPLES)
    # shuffle and add noise
    np.random.shuffle(x_values)
    y_values = np.sin(x_values)
    y_values += 0.1 * np.random.randn(*y_values.shape)

    # split into train, validation, test
    TRAIN_SPLIT = int(0.6 * SAMPLES)
    TEST_SPLIT = int(0.2 * SAMPLES + TRAIN_SPLIT)
    x_train, x_test, x_validate = np.split(x_values, [TRAIN_SPLIT, TEST_SPLIT])
    y_train, y_test, y_validate = np.split(y_values, [TRAIN_SPLIT, TEST_SPLIT])

    # create a NN with 2 layers of 16 neurons
    model = tf.keras.Sequential()
    model.add(layers.Dense(16, activation='relu', input_shape=(1, )))
    model.add(layers.Dense(16, activation='relu'))
    model.add(layers.Dense(1))
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
    model.fit(x_train,
              y_train,
              epochs=200,
              batch_size=16,
              validation_data=(x_validate, y_validate))
    return model


# Function: Convert some hex value into an array for C programming
def hex_to_c_array(hex_data, var_name):

    c_str = ''

    # Create header guard
    c_str += '#ifndef ' + var_name.upper() + '_H\n'
    c_str += '#define ' + var_name.upper() + '_H\n\n'

    # Add array length at top of file
    c_str += '\nunsigned int ' + var_name + '_len = ' + str(
        len(hex_data)) + ';\n'

    # Declare C variable
    c_str += 'unsigned char ' + var_name + '[] = {'
    hex_array = []
    for i, val in enumerate(hex_data):

        # Construct string from hex
        hex_str = format(val, '#04x')

        # Add formatting so each line stays within 80 characters
        if (i + 1) < len(hex_data):
            hex_str += ','
        if (i + 1) % 12 == 0:
            hex_str += '\n '
        hex_array.append(hex_str)

    # Add closing brace
    c_str += '\n ' + format(' '.join(hex_array)) + '\n};\n\n'

    # Close out header guard
    c_str += '#endif //' + var_name.upper() + '_H'

    return c_str


if __name__ == '__main__':
    model = get_model()
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
    tflite_model = converter.convert()

    # Save the model to disk
    open("sine_model.tflite", "wb").write(tflite_model)

    # Write TFLite model to a C source (or header) file
    c_model_name = 'sine_model_c'
    with open(c_model_name + '.h', 'w') as file:
        file.write(hex_to_c_array(tflite_model, c_model_name))

运行代码后,会在当前目录生成一个名为sine_model.tflite的模型文件

代码解释

代码的功能是创建一个用于预测正弦函数的神经网络模型,并将该模型转换为TensorFlow Lite格式并保存到磁盘上。

下面是代码的详细解释:

  • 导入必要的库:
import math
import numpy as np
import tensorflow as tf
from tensorflow.keras import layers
  • 定义一个函数get_model(),用于创建和训练神经网络模型,并返回该模型:
def get_model():
  • 设置样本数量和随机种子:
    SAMPLES = 1000
    np.random.seed(1337)
  • 生成x值,并进行洗牌和添加噪声:
    x_values = np.random.uniform(low=0, high=2*math.pi, size=SAMPLES)
    np.random.shuffle(x_values)
  • 生成对应的y值(正弦函数的值)并添加噪声:
    y_values = np.sin(x_values)
    y_values += 0.1 * np.random.randn(*y_values.shape)
  • 将数据分割为训练集、验证集和测试集:
    TRAIN_SPLIT = int(0.6 * SAMPLES)
    TEST_SPLIT = int(0.2 * SAMPLES + TRAIN_SPLIT)
    x_train, x_test, x_validate = np.split(x_values, [TRAIN_SPLIT, TEST_SPLIT])
    y_train, y_test, y_validate = np.split(y_values, [TRAIN_SPLIT, TEST_SPLIT])
  • 创建一个包含两个具有16个神经元的隐藏层的神经网络模型:
    model = tf.keras.Sequential()
    model.add(layers.Dense(16, activation='relu', input_shape=(1,)))
    model.add(layers.Dense(16, activation='relu'))
    model.add(layers.Dense(1))
  • 编译模型,指定优化器、损失函数和评估指标:
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
  • 使用训练集数据训练模型:
    model.fit(x_train, y_train, epochs=200, batch_size=16, validation_data=(x_validate, y_validate))
  • 返回训练好的模型:
    return model
  • 调用get_model()函数获取训练好的模型:
model = get_model()
  • 创建一个TFLite转换器并将模型转换为TensorFlow Lite格式:
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
tflite_model = converter.convert()
  • 将转换后的模型保存到本地:

open("sine_model.tflite", "wb").write(tflite_model)

总结来说,这段代码的作用是创建一个用于预测正弦函数的神经网络模型,通过训练模型来拟合正弦函数的数据,并将训练好的模型转换为TensorFlow Lite格式并保存到磁盘上,以便在资源受限的设备上进行部署和推理。

2. 转换模型 (可选)

xxd命令行转换

在将模型转换为TensorFlow Lite格式后,我们可以使用xxd命令将.tflite文件转换为C矩阵头文件。这样做的好处是,我们可以将模型以C数组的形式嵌入到Arduino代码中,方便加载和使用。

2023-7-2 :在上一步中的python代码中,已添加python转化模型为c数组的功能,因此此步骤可选

请按照以下步骤执行转换:

  1. 打开终端或命令提示符,并导航到存储sine_model.tflite文件的目录。
  2. 运行以下命令将模型转换为C矩阵头文件:
xxd -i sine_model.tflite > sine_model.h

xxd是一个Linux命令,Windows安装了git就有,可使用everything自行搜索xxd.exe,将其所在目录添加到环境变量中即可使用xxd命令。

image-20230701134314757

  1. 这将生成一个名为sine_model.h的文件,其中包含模型的C数组表示形式。
  2. 将生成的sine_model.h文件复制到Arduino项目的目录中。

python转换

除了使用xxd工具转换以外,还可以通过python进行转换

def hex_to_c_array(hex_data, var_name):
    c_str = ''
    # Create header guard
    c_str += '#ifndef ' + var_name.upper() + '_H\n'
    c_str += '#define ' + var_name.upper() + '_H\n\n'
    # Add array length at top of file
    c_str += '\nunsigned int ' + var_name + '_len = ' + str(
        len(hex_data)) + ';\n'
    # Declare C variable
    c_str += 'unsigned char ' + var_name + '[] = {'
    hex_array = []
    for i, val in enumerate(hex_data):
        # Construct string from hex
        hex_str = format(val, '#04x')
        # Add formatting so each line stays within 80 characters
        if (i + 1) < len(hex_data):
            hex_str += ','
        if (i + 1) % 12 == 0:
            hex_str += '\n '
        hex_array.append(hex_str)
    # Add closing brace
    c_str += '\n ' + format(' '.join(hex_array)) + '\n};\n\n'
    # Close out header guard
    c_str += '#endif //' + var_name.upper() + '_H'
    return c_str


# 将模型转换为C数组文件
open("model.h", "w").write(hex_to_c_array(tflite_model, "model_name"))

3. 使用模型

现在,我们将转向Arduino部分,将训练好的模型加载到ESP32上,并进行正弦函数的预测。

环境

示例程序具体分以下几个步骤:

  1. 引入库和头文件 在Arduino IDE中,我们需要引入EloquentTinyML库和模型头文件。这些库提供了在ESP32上加载和运行TensorFlow Lite模型的功能。

    这里我是通过arduinolibraries网站检索并下载EloquentTinyML-0.0.10.zip

    image-20230701135916161

  2. 定义模型和参数 我们需要定义模型的输入和输出数量,以及Tensor Arena的大小。这些参数决定了模型的输入输出维度和所需的内存空间。

  3. 设置和初始化模型 在Arduino的setup()函数中,我们初始化串口通信,并使用EloquentTinyML库的begin()函数加载模型。

  4. 循环预测 在loop()函数中,我们使用随机生成的x值计算实际的正弦函数值,并使用模型预测对应的y值。我们将实际值和预测值通过串口打印出来,并延迟一段时间后重复这个过程。

示例esp32代码

#include <Arduino.h>

// https://github.com/eloquentarduino/EloquentTinyML  Version:0.0.10
#include <EloquentTinyML.h>
// sine_model.h contains the array you exported from the previous step with xxd or tinymlgen
#include "sine_model.h"

#define NUMBER_OF_INPUTS 1
#define NUMBER_OF_OUTPUTS 1
// in future projects you may need to tweek this value: it's a trial and error process
#define TENSOR_ARENA_SIZE 2 * 1024

Eloquent::TinyML::TfLite<NUMBER_OF_INPUTS, NUMBER_OF_OUTPUTS, TENSOR_ARENA_SIZE> ml;

void setup()
{
  Serial.begin(115200);
  ml.begin(sine_model_tflite);
}

void loop()
{
  // pick up a random x and predict its sine
  float x = 3.14 * random(100) / 100;
  float y = sin(x);
  float input[1] = {x};
  float predicted = ml.predict(input);

  Serial.print("sin(");
  Serial.print(x);
  Serial.print(") = ");
  Serial.print(y);
  Serial.print("\t predicted: ");
  Serial.println(predicted);
  delay(100);
}

代码解释

这段代码是ESP32的Arduino代码,用于加载和运行之前在Python中训练并转换为TensorFlow Lite格式的模型,以进行正弦函数预测。

以下是代码的详细解释:

  1. 引入必要的库和头文件:
#include <Arduino.h>
#include <EloquentTinyML.h>
#include "sine_model.h"
  1. 定义输入和输出的数量:
#define NUMBER_OF_INPUTS 1
#define NUMBER_OF_OUTPUTS 1
  1. 定义Tensor Arena的大小,用于分配内存空间给模型:
#define TENSOR_ARENA_SIZE 2 * 1024
  1. 创建TinyML对象并设置模型数据:
Eloquent::TinyML::TfLite<NUMBER_OF_INPUTS, NUMBER_OF_OUTPUTS, TENSOR_ARENA_SIZE> ml;
  1. setup()函数中初始化串口通信和TinyML模型:
void setup()
{
  Serial.begin(115200);
  ml.begin(sine_model_tflite);
}
  1. loop()函数中进行循环预测:
void loop()
{
  // 随机选择一个x值并预测其正弦值
  float x = 3.14 * random(100) / 100;
  float y = sin(x);
  float input[1] = {x};
  float predicted = ml.predict(input);

  Serial.print("sin(");
  Serial.print(x);
  Serial.print(") = ");
  Serial.print(y);
  Serial.print("\t predicted: ");
  Serial.println(predicted);
  delay(100);
}

在每次循环中,它会随机选择一个x值,计算该x值的正弦值,并将其作为实际值y进行打印。然后,它将x值作为输入传递给TinyML模型的predict()函数,并将预测值打印出来。之后,它会延迟100毫秒,然后重复这个过程。

通过这段代码,ESP32可以利用之前训练好的TensorFlow Lite模型来进行正弦函数的预测,并将实际值和预测值输出到串口供观察和调试。

示例效果

image-20230701140532085

代码运行在合宙的esp32c3 ,可见预测结果在一定程度还是挺准的,以上就是使用Python和Arduino在ESP32上预测正弦函数的流程。

posted @ 2023-07-01 14:17  Dapenson  阅读(159)  评论(3编辑  收藏  举报