最新版本TensorFlow训练模型TinyML部署到ESP32入门实操

最新版本TensorFlow训练模型TinyML入门实操

1.概述

这篇文章介绍微型嵌入式设备的机器学习TinyML,它们的特点就是将训练好的模型部署到单片机上运行。

2.TensorFlow深度学习原理

TensorFlow开源项目是由google研发的一个嵌入式机器学习工具,通过调用该工具的API可以实现训练模型,部署模型等。

下面介绍下使用 TensorFlow 训练模型的工作流程,掌握了流程后就可以进行实操,训练一个模型然后部署到单片机上运行。

  1. 选择一个目标:他决定了使用什么模型架构和收集什么数据
  2. 收集训练数据
  3. 设计模型架构
  4. 训练模型:给模型输入一组数据产生正确的输出
  5. 转换模型:我们训练的模型不能在MCU微控制器上运行,需要将他们转换为TensorFlowLite然后保存在硬盘上。
  6. 运行推断:将模型部署到微控制器上,通过测试数据判断它的运行质量。
  7. 评估和故障排除

3.TensorFlow深度学习实操

3.1.搭建开发环境

目标

今天要做的一个项目是训练一个可以对给定输入值X,然后预测输出正弦值Y的模型,通过输出的Y值控制微控制器的LED灯闪烁频率。

1.项目使用工具版本清单
工具包 版本
python 3.10
tensorflow 2.16.2
numpy 1.26.4
matplotlib 3.7.1
arduinoIDE 2.3.2
ESP32固件 3.0.3
ESP32开发板 ESP32WROOM32
EloquentTinyML 0.0.10
2.colab介绍

福利:如果不能访问colab平台,所有在colab平台操作的内容都可以在本地电脑操作,只需要有个python环境和代码编辑器即可。

colab平台是google提供的一个免费模型训练的云端计算机,他有两个作用可以写笔记,还可以运行代码训练模型。

注册一个google账号就会得到一个云端的硬盘,访问下面的地址进入硬盘。

https://drive.google.com/

在google云盘的首页点击我的云盘硬盘,然后点击上面的新建,选择新建文件夹

为新建的文件夹命名,例如TinyML ,在这个文件夹中存放机器学习的笔记。

双击tinyml文件夹,然后点击新建,选择google Colabratory

如果没有找google Colabratory,就点击下面的关联更多应用并添加colab

新创建的colab和我们平时用的jutyper一样,不但可以写笔记还可以运行代码。

左上角是笔记名称,可以修改。

在colab右上角点击连接google就会自动为我们分配一个云端的计算机运行我们的代码。

到这一步我们的colab平台就准备好了,下面就可以开始训练我们的第一个模型啦。

3.2.安装工具包

1.安装tensorflow

鼠标hover在输入框上边,就会出现添加文本代码 两个选项,点击文本就可以写笔记。

输入笔记内容

网上给出的教程都是安装的tensorflow2.0版本,该版本太老了,现在已经不能使用了,我们这里安装最新的版本。

在下一行代码框输入tensorflow,然后点击左边的三角图标就可以运行代码。

python 复制代码
!pip install tensorflow==2.17.0

查看安装包版本号,新建一行代码,输入下面的代码运行。

python 复制代码
# 查看 tensorflow版本
!pip show tensorflow
# 查看python版本
import platform
print(platform.python_version())
2.导入依赖包

新建一行文本,输入2.导入依赖包

新建一行代码,并运行下面的代码

python 复制代码
import tensorflow as tf
# Numpy is a math library
import numpy as np
# Matplotlib is a graphing library
import matplotlib.pyplot as plt
# math is Python's math library
import math

3.2.准备训练数据

嵌入式机器学习,通常训练数据来自于传感器采集的数据。这里我们用numpy生成我们要用到的训练数据。

1.生成数据

新建一行文本3.生成数据

新建一行代码,输入如下内容点击运行代码。

python 复制代码
# We'll generate this many sample datapoints/设置数据总量
SAMPLES = 1000

# Set a "seed" value, so we get the same random numbers each time we run this
# notebook. Any number can be used here.
SEED = 1337
np.random.seed(SEED)
tf.random.set_seed(SEED)

# Generate a uniformly distributed set of random numbers in the range from
# 0 to 2π, which covers a complete sine wave oscillation/设置生成数据格式
x_values = np.random.uniform(low=0, high=2*math.pi, size=SAMPLES)

# Shuffle the values to guarantee they're not in order/打乱数据的顺序
np.random.shuffle(x_values)

# Calculate the corresponding sine values/使用sin方法计算正弦值
y_values = np.sin(x_values)

# Add a small random number to each y value
y_values += 0.1 * np.random.randn(*y_values.shape)

# Plot our data/使用matplotlib绘制图形
plt.plot(x_values, y_values, 'b.')
plt.show()

运行上面的代码,生成数据图如下。

2.拆分数据

我们的数据会分为三个部分,训练数据、验证数据、测试数据。

为了评估模型的训练准确率,我们需要将模型预测的数据与实际数据进行比较,评估会发生在训练期间(称为验证)和训练之后(称为测试)。

为了确保有数据可以被用于评估,我们将在开始训练之前预留出一些数据。让我们保留20%数据用于验证,另外20%数据用于测试。剩余的60%数据用于训练模型,这是训练模型经典拆分比例。

新建一行文本,输入4.拆分数据

新建一行代码,输入如下内容,运行代码

python 复制代码
# We'll use 60% of our data for training and 20% for testing. The remaining 20%
# will be used for validation. Calculate the indices of each section.
TRAIN_SPLIT =  int(0.6 * SAMPLES)
TEST_SPLIT = int(0.2 * SAMPLES + TRAIN_SPLIT)
# 通过split方法拆分数据
x_train, x_validate, x_test = np.split(x_values, [TRAIN_SPLIT, TEST_SPLIT])
y_train, y_validate, y_test = np.split(y_values, [TRAIN_SPLIT, TEST_SPLIT])

plt.plot(x_train, y_train, 'b.', label="Train")
plt.plot(x_validate, y_validate, 'y.', label="Validate")
plt.plot(x_test, y_test, 'r.', label="Test")
plt.legend()
plt.show()

使用不同的颜色显示拆分后的数据

3.3.设计训练模型

新建一行文本输入5.创建模型

新建一行代码,输入内容如下

python 复制代码
from tensorflow.keras import layers
# 使用keras创建一个序列模型Sequential
model_2 = tf.keras.Sequential()

# First layer takes a scalar input and feeds it through 16 "neurons". The
# neurons decide whether to activate based on the 'relu' activation function.
# 第一层使用16个神经元;activation:激活函数,选择relu策略,他返回两个数中较大的值。
model_2.add(layers.Dense(16, activation='relu', input_shape=(1,)))

# The new second layer may help the network learn more complex representations
# 第二层神经元模型
model_2.add(layers.Dense(16, activation='relu'))

# Final layer is a single neuron, since we want to output a single value
#第三层神经元模型
model_2.add(layers.Dense(1))

# Compile the model using a standard optimizer and loss function for regression
# 设置训练过程中的参数 optimizer:优化器在训练期间用于调整网络对其输入进行建模的算法。
# loss:损失值在训练期间计算模型预测值与真实值之间的差距,mse代表均方误差(Mean Squared Error),
# 它是预测值与真实值之差的平方的平均值。
# metrics:用于评估模型性能的指标列表

model_2.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])

# Show a summary of the model
# 输出设计的模型架构摘要
model_2.summary()

运行结果显示了模型设计概要信息

3.4.训练模型

新建一行文本输入 6.训练模型

新建一行代码,输入启动训练的参数,然后运行代码开始训练模型。

python 复制代码
# 调用fit方法训练模型
# history_2:接收训练返回的数据
# x_train, y_train:训练数据x和y值
# epochs:训练次数
# batch_size:每次训练输入多少个数据,每一轮训练完成就会更新一次神经元模型,如果设置1就要更新600次,增加训练时间,
# 如果设置600,则每轮训练包含所有数据这样是快了,但是准确度降低了。因此设置为每论随机取16个数据训练。
# validation_data:验证数据,验证在训练过程中模型的预测和实际正确值是否一致。
history_2 = model_2.fit(x_train, y_train, epochs=600, batch_size=16,
                    validation_data=(x_validate, y_validate))

查看运算结果

  • loss:损失值越小越好,比较第一轮和最后一轮,观察损失值从大到小在变化,说明是好事。
  • mae:平均绝对误差,显示神经元网络模型预测值和训练数据中y值的平均差
  • val_loss: 验证数据损失值输出
  • val_mae: 验证数据平均误差

第一轮和最后一轮训练结果

3.5.分析运算结果

我们可以通过绘制数据图来了解模型训练的情况,掌握提高模型训练准确度的依据。

1.绘制训练损失和验证损失图

新建一行文本输入7.分析模型数据

新建一行代码,输入下面的代码。

python 复制代码
#绘制训练损失和验证损失图
# Draw a graph of the loss, which is the distance between
# the predicted and actual values during training and validation.
# 分别从历史数据中提取出损失数据loss和验证数据val_loss
loss = history_2.history['loss']
val_loss = history_2.history['val_loss']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'g.', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

从图中可以看出训练损失和验证损失值趋势都在减小,意味着模型正在改进并产生准确值。

2.绘制训练损失和验证损失详情图

通过减少查看训练轮次的次数,将训练数据放大,能够观察到训练的更多细节,发现模型训练的问题。

在colab中新建一行代码,输入下面的代码。

python 复制代码
# 跳过前100次,查看后面500次训练损失和验证损失详细数据
# Exclude the first few epochs so the graph is easier to read
#设置跳过的次数
SKIP = 100

plt.clf()

plt.plot(epochs[SKIP:], loss[SKIP:], 'g.', label='Training loss')
plt.plot(epochs[SKIP:], val_loss[SKIP:], 'b.', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

从这个图中可以看出损失减小的很平缓,最小损失在0.15,这个值还是高。另外验证损失值高于训练损失值,可能出现了过拟合现象。

3.绘制平均绝对误差

为了更好的了解模型的表现,绘制更多的数据进行分析。例如绘制平均绝对误差看下情况。

在colab中新建一行代码,输入下面的代码。

python 复制代码
#使用上面相同轮次,绘制下面的图形。
plt.clf()

# Draw a graph of mean absolute error, which is another way of
# measuring the amount of error in the prediction.
mae = history_2.history['mae']
val_mae = history_2.history['val_mae']

plt.plot(epochs[SKIP:], mae[SKIP:], 'g.', label='Training MAE')
plt.plot(epochs[SKIP:], val_mae[SKIP:], 'b.', label='Validation MAE')
plt.title('Training and validation mean absolute error')
plt.xlabel('Epochs')
plt.ylabel('MAE')
plt.legend()
plt.show()

这个图中可以看出,训练的误差低于验证的误差。可能出现了过拟合,对训练数据学习过度,无法对新数据做出有效的预测。平均绝对误差值非常高,由于我们期望输出范围在-1~+1之间,因此0.31误差离精确建模正弦波很远。

4.绘制训练数据的预期值和期望值

我们通过绘制神经网络模型在训练过程中训练数据的预测值和预期值情况,了解他识别精确度情况。

在colab中新建一行代码,输入下面的代码。

python 复制代码
# 训练数据的预测值和期望值
# Make predictions based on our test dataset
predictions = model_2.predict(x_test)

# Graph the predictions against the actual values
plt.clf()
plt.title('Comparison of predictions and actual values')
plt.plot(x_test, y_test, 'b.', label='Actual')
plt.plot(x_test, predictions, 'r.', label='Predicted')
plt.legend()
plt.show()

通过调用predict方法,将训练数据中所有x值进行推断,返回一个预测数组。将数组中的值与训练集中实际的y值绘制在一张图上。

这张图表明,模型只能粗略的拟合数据,实际值不能完全匹配我们的预期值

通过对模型训练结果分析,我们得出这次训练的结果不是很好,需要扩大模型(增加模型的层数)或者调整训练数据,以及调整训练参数等方式改善训练结果。

3.6.测试模型

当模型训练的精确度达到我们的要求后,就可以使用测试数据测试下它的实际运行情况。

我们预留了20%的数据用于测试模型

在colab中新建一行代码,输入下面的代码。

python 复制代码
# Calculate and print the loss on our test dataset 
#使用测试数据调用evaluate方法计算损失值和平均绝对误差值,
loss = model_2.evaluate(x_test, y_test)

# Make predictions based on our test dataset
#使用测试数据计算预测值
predictions = model_2.predict(x_test)

# Graph the predictions against the actual values
plt.clf()

plt.title('Comparison of predictions and actual values')
# 绘制测试数据直接结果
plt.plot(x_test, y_test, 'b.', label='Actual')
# 绘制测试数据预测结果
plt.plot(x_test, predictions, 'r.', label='Predicted')
plt.legend()
plt.show()

从图中可以看出损失值为0.1639,这个值高于验证损失0.1577,平均绝对误差0.3213和验证的平均绝对误差一样。

图中的实际值和预测值不在一条线上,说明和训练模型的结果相同,不能精确的识别出预期值。

这个测试结果和训练结果没有太大差异,说明测试结果和训练结果基本吻合。

3.7.转换模型(导出模型)

我们使用TensorFlow训练的模型不能直接部署到微控制器上,需要将它转换为更小的模型。TensorFlow Lite转换器由两个部分构成

  • 转换器
    • 将tensorflow模型转换成一种特殊的更节省空间的文件格式,以便在小型微控制器上运行。
  • 解释器
    • 在微控制器上运行转换后的TensorFlow Lite模型
1.转化模型

使用tensorflowLite 转化器将模型转化为适合微控制器运行的小模型,它除了转化还可以对模型进行优化,使它的体积更小,这个优化是不会降低模型精度的。 为了验证使用优化压缩模型是否会影响模型识别精度,下面的代码创建了两个版本。 普通转化和使用了优化策略的转化。

在colab中新建一行代码,输入下面的代码。

运行下面的代码得到转化后的模型

python 复制代码
# Convert the model to the TensorFlow Lite format without quantization
# 只将模型转化,不做优化。
converter = tf.lite.TFLiteConverter.from_keras_model(model_2)
tflite_model = converter.convert()

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

# Convert the model to the TensorFlow Lite format with quantization
# 对模型转化并使用了优化压缩模型体积
converter = tf.lite.TFLiteConverter.from_keras_model(model_2)
# Indicate that we want to perform the default optimizations,
# which includes quantization
# 使用优化器
converter.optimizations = [tf.lite.Optimize.DEFAULT]
# Define a generator function that provides our test data's x values
# as a representative dataset, and tell the converter to use it
def representative_dataset_generator():
  for value in x_test:
    # Each scalar value must be inside of a 2D array that is wrapped in a list
    yield [np.array(value, dtype=np.float32, ndmin=2)]
converter.representative_dataset = representative_dataset_generator
# Convert the model
tflite_model = converter.convert()

# Save the model to disk
open("sine_model_quantized.tflite", "wb").write(tflite_model)
2.验证普通转化和优化器转化后模型识别精度差距

由于TensorFlowLite解释器主要是用来提高效率的,所以它的使用就比较复杂,需要经过如下几个步骤

  • 实例化一个Interpreter对象
  • 调用一些为模型分配内存的方法
  • 将输入写入输入张量
  • 调用模型
  • 读取输出张量的输出

在colab中新建一行代码,输入下面的代码。运行代码

python 复制代码
# Instantiate an interpreter for each model
sine_model = tf.lite.Interpreter('sine_model.tflite')
sine_model_quantized = tf.lite.Interpreter('sine_model_quantized.tflite')

# Allocate memory for each model
sine_model.allocate_tensors()
sine_model_quantized.allocate_tensors()

# Get indexes of the input and output tensors
sine_model_input_index = sine_model.get_input_details()[0]["index"]
sine_model_output_index = sine_model.get_output_details()[0]["index"]
sine_model_quantized_input_index = sine_model_quantized.get_input_details()[0]["index"]
sine_model_quantized_output_index = sine_model_quantized.get_output_details()[0]["index"]

# Create arrays to store the results
sine_model_predictions = []
sine_model_quantized_predictions = []

# Run each model's interpreter for each value and store the results in arrays
for x_value in x_test:
  # Create a 2D tensor wrapping the current x value
  x_value_tensor = tf.convert_to_tensor([[x_value]], dtype=np.float32)
  # Write the value to the input tensor
  sine_model.set_tensor(sine_model_input_index, x_value_tensor)
  # Run inference
  sine_model.invoke()
  # Read the prediction from the output tensor
  sine_model_predictions.append(
      sine_model.get_tensor(sine_model_output_index)[0])
  # Do the same for the quantized model
  sine_model_quantized.set_tensor(sine_model_quantized_input_index, x_value_tensor)
  sine_model_quantized.invoke()
  sine_model_quantized_predictions.append(
      sine_model_quantized.get_tensor(sine_model_quantized_output_index)[0])


# See how they line up with the data
plt.clf()
plt.title('Comparison of various models against actual values')
plt.plot(x_test, y_test, 'bo', label='Actual')
plt.plot(x_test, predictions, 'ro', label='Original predictions')
plt.plot(x_test, sine_model_predictions, 'bx', label='Lite predictions')
plt.plot(x_test, sine_model_quantized_predictions, 'gx', label='Lite quantized predictions')
plt.legend()
plt.show()

从图中可以看出无论是原始模型、转换模型、优化转换模型,几乎没有区别。普通的转化和使用优化的转化模型预测值基本是吻合的,说明使用优化方式转换模型没有带来精度的损失。

3.8.标准化模型

我们转化后的模型还不能直接部署到微控制器上,因为它使用的是python语言,我们使用工具将它C语言源文件,才可以部署。

使用xxd工具可以将模型转化为c语言文件。

c 复制代码
# Install xxd if it is not available
!apt-get -qq install xxd
# Save the file as a C source file
# 将模型格式化为C语言文件
!xxd -i sine_model_quantized.tflite > sine_model_quantized.cc
# Print the source file
# 查看C语言文件模型内容
!cat sine_model_quantized.cc

格式化结果如下

他是一个数组文件

在colab左侧工具栏点击文件夹,就可以看到创建的模型文件。在这里可以下载模型文件。

4.部署项目

我们的目标是将训练好的模型部署到esp32开发板上,然后根据模型的输出控制LED灯的亮和灭.

在微控制器上运行模型有两个支持的库

  • EloquentTinyML库

    它是eloquentarduino开发的,https://github.com/eloquentarduino

    这个Arduino库简化使用Arduino IDE将Tensorflow Lite for Micro控制器模型部署到Arduino板的过程,操作起来非常的简单。

  • TensorFlowLite_ESP32库

    它是TensorFlow官方项目下TensorFlowLite解释器运行模型,里面的类比较多操作较为复杂。

4.1.安装依赖库

我们选择简单方式来运行我们的模型,首先搜索EloquentTinyML库选择0.0.10版本并安装它

4.2.下载模型文件

使用前面介绍的方法将模型转为.h文件,运行代码如下。然后将模型文件下载到本地。

c 复制代码
# 将模型格式化为C语言的.h文件
!xxd -i sine_model_quantized.tflite > sine_model.h
# Print the source file
# 查看C语言文件模型内容
!cat sine_model.h

下载到本地模型文件sine_model.h的内容如下:

c 复制代码
unsigned char sine_model_tflite[] = {
  0x20, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x00, 0x00,
  0x14, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00,
  0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00,
  0x1c, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00,
  0x00, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x2c, 0x0e, 0x00, 0x00,
  0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  0xae, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
  0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76,
  0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x68, 0xff, 0xff, 0xff,
  0x0b, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x30, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x86, 0xfc, 0xff, 0xff,
  0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x6b, 0x65, 0x72, 0x61,
  0x73, 0x5f, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00,
  0x03, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0xb8, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x4e, 0x56,
  0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44,
  0x41, 0x54, 0x41, 0x00, 0xdc, 0xff, 0xff, 0xff, 0x0e, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e, 0x5f,
  0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73,
  0x69, 0x6f, 0x6e, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00,
  0x08, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x75, 0x6e, 0x74,
  0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00,
  0x10, 0x00, 0x00, 0x00, 0xf0, 0x02, 0x00, 0x00, 0xe8, 0x02, 0x00, 0x00,
  0xd4, 0x02, 0x00, 0x00, 0xac, 0x02, 0x00, 0x00, 0x5c, 0x02, 0x00, 0x00,
  0x4c, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00,
  0xd4, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00,
  0xbc, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00,
  0x74, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6a, 0xfd, 0xff, 0xff,
  0x04, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x0e, 0x00, 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00,
  0x08, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0xeb, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x18, 0x00,
  0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
  0x81, 0x33, 0x62, 0xc9, 0x4d, 0x63, 0x8a, 0x73, 0x02, 0x00, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
  0x32, 0x2e, 0x31, 0x37, 0x2e, 0x30, 0x00, 0x00, 0xd6, 0xfd, 0xff, 0xff,
  0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0xf2, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  0x31, 0x2e, 0x31, 0x34, 0x2e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xf4, 0xf3, 0xff, 0xff, 0xf8, 0xf3, 0xff, 0xff,
  0xfc, 0xf3, 0xff, 0xff, 0x00, 0xf4, 0xff, 0xff, 0x04, 0xf4, 0xff, 0xff,
  0x22, 0xfe, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  0x7f, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0x7f, 0x7f, 0x81, 0x81, 0x7f, 0x7f,
  0x7f, 0x7f, 0x7f, 0x81, 0x3e, 0xfe, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
  0x40, 0x00, 0x00, 0x00, 0x3a, 0xe6, 0xff, 0xff, 0xc9, 0x0b, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x1b, 0xef, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
  0xa4, 0xe6, 0xff, 0xff, 0x47, 0x07, 0x00, 0x00, 0x8b, 0xe6, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x77, 0xff, 0xff,
  0xed, 0xe5, 0xff, 0xff, 0x9e, 0xe6, 0xff, 0xff, 0x9c, 0xe6, 0xff, 0xff,
  0xd5, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xfe, 0xff, 0xff,
  0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7f, 0x95, 0xdd, 0x2e,
  0xb8, 0xd3, 0x9d, 0x05, 0x42, 0xfe, 0xff, 0x21, 0x91, 0x9b, 0x79, 0x64,
  0x7f, 0x9a, 0x44, 0xb1, 0x3d, 0xad, 0xa9, 0x20, 0xac, 0x8c, 0xe0, 0xe6,
  0xca, 0x84, 0x9f, 0xe9, 0x3a, 0xbb, 0xe8, 0x81, 0xa7, 0xd6, 0x0b, 0xb8,
  0xe1, 0xd5, 0xcc, 0x38, 0xfd, 0x13, 0x46, 0xac, 0x8a, 0x45, 0xdc, 0xd9,
  0x44, 0x15, 0x57, 0xb9, 0x24, 0x5d, 0x58, 0x13, 0x81, 0x0d, 0x09, 0x06,
  0x5b, 0x7f, 0x1a, 0xce, 0x9f, 0x9d, 0x42, 0x39, 0xb2, 0xcc, 0x0d, 0x5b,
  0xc0, 0xad, 0xe9, 0x53, 0x40, 0x8d, 0x35, 0x91, 0x42, 0xba, 0x29, 0xbc,
  0x36, 0xa5, 0x38, 0xd5, 0xe8, 0xf0, 0x88, 0x81, 0x67, 0x3e, 0xc7, 0x5f,
  0xa9, 0x24, 0x81, 0x8f, 0xe2, 0x0c, 0x13, 0x0a, 0x52, 0x9e, 0xe9, 0x7b,
  0x1f, 0x3d, 0x46, 0x58, 0xdd, 0x7f, 0xf5, 0x2c, 0x52, 0xf6, 0x0e, 0x5a,
  0x42, 0xb2, 0x22, 0xdf, 0xde, 0x87, 0x0b, 0x04, 0xb3, 0x7f, 0x41, 0x88,
  0xca, 0x4d, 0x47, 0x89, 0xc0, 0x42, 0xf7, 0xc1, 0x7d, 0xd7, 0x32, 0xb6,
  0x49, 0x3c, 0x66, 0x3b, 0x66, 0xf5, 0x25, 0x7f, 0xec, 0x76, 0x69, 0xec,
  0x8a, 0x81, 0xa6, 0x8e, 0x0c, 0x3f, 0x0b, 0xc8, 0xca, 0x6f, 0x9a, 0x78,
  0x3d, 0xe6, 0x24, 0xb8, 0x97, 0xb7, 0xfc, 0xef, 0xf2, 0xd6, 0xcb, 0x2e,
  0xb6, 0x2f, 0xd1, 0x65, 0x6b, 0xe6, 0x0c, 0x7f, 0xd0, 0x15, 0x0c, 0x21,
  0x00, 0xd1, 0xfe, 0xe3, 0xcc, 0x0c, 0x4e, 0xf3, 0xcf, 0xeb, 0x81, 0xf0,
  0xd6, 0x19, 0x57, 0x0f, 0x85, 0x32, 0xc6, 0x68, 0xb8, 0x7f, 0xe3, 0x96,
  0x51, 0xa8, 0xaf, 0x37, 0x81, 0xdc, 0xde, 0x89, 0x6d, 0xce, 0xf3, 0x02,
  0x20, 0x33, 0x04, 0x66, 0x66, 0xb0, 0x06, 0xbc, 0xbb, 0xe1, 0x91, 0xef,
  0x77, 0x7f, 0x2b, 0x34, 0x1e, 0x90, 0xdc, 0xd2, 0x88, 0x90, 0xa0, 0x20,
  0x96, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
  0xd3, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xcf, 0xfc, 0xff, 0xff,
  0x3c, 0x1a, 0x00, 0x00, 0xec, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0xe4, 0xfe, 0xff, 0xff, 0x83, 0xea, 0xff, 0xff, 0x42, 0xff, 0xff, 0xff,
  0xf3, 0xe7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00,
  0x88, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xe2, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
  0x10, 0x00, 0x00, 0x00, 0xf0, 0xd5, 0x27, 0x17, 0x27, 0xde, 0x1a, 0xd5,
  0x31, 0xdd, 0x18, 0x23, 0x81, 0xed, 0xf4, 0xe0, 0x00, 0x00, 0x06, 0x00,
  0x08, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0x04, 0x0e, 0x00, 0x00, 0xfc, 0xf5, 0xff, 0xff,
  0x00, 0xf6, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0x4c, 0x49, 0x52,
  0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,
  0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00,
  0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
  0x40, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x48, 0x01, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
  0x05, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00,
  0x6c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00,
  0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x92, 0xff, 0xff, 0xff,
  0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x00,
  0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb0, 0xf6, 0xff, 0xff,
  0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
  0x09, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0xc6, 0xff, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
  0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0xb6, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
  0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,
  0x1a, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x0b, 0x00, 0x04, 0x00,
  0x0e, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
  0x1c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
  0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x0c, 0x00, 0x00, 0x00, 0x48, 0x08, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00,
  0x40, 0x07, 0x00, 0x00, 0x2c, 0x06, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00,
  0xfc, 0x03, 0x00, 0x00, 0xe0, 0x02, 0x00, 0x00, 0x6c, 0x02, 0x00, 0x00,
  0xa0, 0x01, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0xfe, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
  0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
  0x0c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
  0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xe8, 0xf7, 0xff, 0xff,
  0x1b, 0x00, 0x00, 0x00, 0x53, 0x74, 0x61, 0x74, 0x65, 0x66, 0x75, 0x6c,
  0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x43,
  0x61, 0x6c, 0x6c, 0x5f, 0x31, 0x3a, 0x30, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0xfe, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
  0x40, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
  0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
  0x01, 0x00, 0x00, 0x00, 0xd4, 0xf8, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
  0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x74, 0x91, 0xff, 0x3b, 0x1c, 0x00, 0x00, 0x00, 0x53, 0x74, 0x61, 0x74,
  0x65, 0x66, 0x75, 0x6c, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f,
  0x6e, 0x65, 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x5f, 0x31, 0x3a, 0x30, 0x31,
  0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x96, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
  0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x90, 0x00, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00,
  0x54, 0xf9, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x90, 0x3a, 0x3c,
  0x54, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69,
  0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31,
  0x5f, 0x32, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x3b, 0x73, 0x65,
  0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64,
  0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, 0x5f, 0x32, 0x2f, 0x52, 0x65, 0x6c,
  0x75, 0x3b, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
  0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, 0x5f, 0x32,
  0x2f, 0x41, 0x64, 0x64, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4e, 0xff, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
  0x40, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
  0x88, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
  0x10, 0x00, 0x00, 0x00, 0x0c, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
  0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x62, 0xb1, 0x81, 0x3c, 0x4e, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75,
  0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e,
  0x73, 0x65, 0x5f, 0x31, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x3b,
  0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31,
  0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, 0x2f, 0x52, 0x65, 0x6c,
  0x75, 0x3b, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
  0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, 0x2f, 0x41,
  0x64, 0x64, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x20, 0x00, 0x1c, 0x00,
  0x1b, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x07, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x48, 0x00, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,
  0xd4, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc9, 0x80, 0xc9, 0x3c,
  0x0c, 0x00, 0x00, 0x00, 0x74, 0x66, 0x6c, 0x2e, 0x71, 0x75, 0x61, 0x6e,
  0x74, 0x69, 0x7a, 0x65, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4a, 0xfb, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00,
  0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf4, 0x00, 0x00, 0x00,
  0x34, 0xfb, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00,
  0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xb5, 0x5b, 0x89, 0x3a,
  0xfc, 0x3e, 0x67, 0x3b, 0x55, 0x6e, 0x4d, 0x3a, 0xda, 0x5a, 0x49, 0x3b,
  0x5d, 0x53, 0x97, 0x3b, 0x60, 0x4d, 0xb2, 0x3a, 0x54, 0x2a, 0xf6, 0x3a,
  0x03, 0x6c, 0x17, 0x3b, 0x18, 0x5e, 0x2e, 0x3b, 0x36, 0xd5, 0x4e, 0x3a,
  0xf1, 0x6c, 0xb5, 0x37, 0x0e, 0xb3, 0xe7, 0x3a, 0x9f, 0x2d, 0xa8, 0x3a,
  0xb7, 0x69, 0x46, 0x3b, 0x74, 0x17, 0x9a, 0x3b, 0x71, 0xf9, 0x68, 0x3b,
  0x12, 0x00, 0x00, 0x00, 0x74, 0x66, 0x6c, 0x2e, 0x70, 0x73, 0x65, 0x75,
  0x64, 0x6f, 0x5f, 0x71, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x35, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x62, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00,
  0xe4, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
  0xf0, 0x00, 0x00, 0x00, 0x4c, 0xfc, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
  0x88, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x36, 0x3c, 0xd8, 0x37,
  0xc9, 0x04, 0xb6, 0x38, 0xf9, 0xb2, 0xa1, 0x37, 0xa1, 0x7d, 0x9e, 0x38,
  0x29, 0x39, 0xee, 0x38, 0x73, 0x58, 0x0c, 0x38, 0x12, 0xc3, 0x41, 0x38,
  0xf7, 0x5f, 0x6e, 0x38, 0x99, 0x3f, 0x89, 0x38, 0x74, 0xcd, 0xa2, 0x37,
  0xce, 0xcd, 0x0e, 0x35, 0x25, 0x60, 0x36, 0x38, 0x6d, 0x60, 0x04, 0x38,
  0xd1, 0x2c, 0x9c, 0x38, 0xde, 0x93, 0xf2, 0x38, 0x0d, 0x61, 0xb7, 0x38,
  0x12, 0x00, 0x00, 0x00, 0x74, 0x66, 0x6c, 0x2e, 0x70, 0x73, 0x65, 0x75,
  0x64, 0x6f, 0x5f, 0x71, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x34, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x72, 0xfd, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00,
  0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf4, 0x00, 0x00, 0x00,
  0x5c, 0xfd, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00,
  0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xcb, 0x98, 0x3e, 0x3b,
  0xa2, 0x2a, 0x51, 0x3b, 0x71, 0x35, 0x9a, 0x3b, 0xd1, 0x6d, 0x73, 0x3b,
  0x79, 0xc9, 0x3f, 0x3b, 0xfe, 0x6d, 0x5a, 0x3b, 0xc6, 0xad, 0x58, 0x3b,
  0xb7, 0x66, 0x89, 0x3b, 0x7e, 0x67, 0x4e, 0x3b, 0x56, 0x04, 0x74, 0x3b,
  0x67, 0x3f, 0x44, 0x3b, 0xb4, 0xd7, 0x5d, 0x3b, 0x0c, 0xad, 0x07, 0x3c,
  0x11, 0xde, 0x5e, 0x3b, 0x63, 0x13, 0x58, 0x3b, 0xe3, 0xea, 0x5d, 0x3b,
  0x12, 0x00, 0x00, 0x00, 0x74, 0x66, 0x6c, 0x2e, 0x70, 0x73, 0x65, 0x75,
  0x64, 0x6f, 0x5f, 0x71, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x33, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  0x8a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00,
  0xe4, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
  0xf0, 0x00, 0x00, 0x00, 0x74, 0xfe, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
  0x88, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1e, 0x1e, 0x41, 0x38,
  0xd4, 0xee, 0x53, 0x38, 0x90, 0x3f, 0x9c, 0x38, 0x05, 0xa6, 0x76, 0x38,
  0xd3, 0x52, 0x42, 0x38, 0x8d, 0x51, 0x5d, 0x38, 0x68, 0x8b, 0x5b, 0x38,
  0xee, 0x37, 0x8b, 0x38, 0x56, 0x22, 0x51, 0x38, 0x87, 0x3e, 0x77, 0x38,
  0xdc, 0xd7, 0x46, 0x38, 0xd2, 0xc6, 0x60, 0x38, 0x6b, 0x78, 0x09, 0x39,
  0xa7, 0xd0, 0x61, 0x38, 0xfa, 0xee, 0x5a, 0x38, 0x41, 0xda, 0x60, 0x38,
  0x12, 0x00, 0x00, 0x00, 0x74, 0x66, 0x6c, 0x2e, 0x70, 0x73, 0x65, 0x75,
  0x64, 0x6f, 0x5f, 0x71, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x32, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x9a, 0xff, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
  0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x40, 0x00, 0x00, 0x00,
  0x84, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x34, 0xcf, 0x28, 0x3c,
  0x12, 0x00, 0x00, 0x00, 0x74, 0x66, 0x6c, 0x2e, 0x70, 0x73, 0x65, 0x75,
  0x64, 0x6f, 0x5f, 0x71, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x31, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x16, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x17, 0x00, 0x10, 0x00,
  0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
  0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00,
  0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
  0x4c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0xfd, 0x0b, 0xf6, 0x38, 0x11, 0x00, 0x00, 0x00, 0x74, 0x66, 0x6c, 0x2e,
  0x70, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x5f, 0x71, 0x63, 0x6f, 0x6e, 0x73,
  0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x16, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x00, 0x00, 0x14, 0x00,
  0x10, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x07, 0x00,
  0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00,
  0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x3c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
  0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,
  0x1e, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x5f,
  0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6b, 0x65, 0x72, 0x61,
  0x73, 0x5f, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x3a, 0x30, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x03, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x00, 0x10, 0x00,
  0x0f, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
  0x09, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
  0x0c, 0x00, 0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
  0x0c, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72
};
unsigned int sine_model_tflite_len = 3768;

4.3.开发应用程序(控制LED灯逻辑)

1.在ArduinoIDE软件新建一个文件helloworld_tinyml 将下面的程序复制到该文件中保存该文件。

2.将下载的sine_model.h模型文件移动到helloworld_tinyml 所在的目录下,这样运行主程序时候就能读取到该模型的文件内容。

c 复制代码
#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"

// 设置ESP32 灯pwm参数
#define PIN 2
#define BASE_FREQ 5000
#define LEDC_TIMER_12_BIT 12

// 设置Eloquent参数
#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 3 * 1024

// 实例化对象ml
Eloquent::TinyML::TfLite<NUMBER_OF_INPUTS, NUMBER_OF_OUTPUTS, TENSOR_ARENA_SIZE> ml;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  // LED灯pwm参数
  ledcAttach(PIN, BASE_FREQ, LEDC_TIMER_12_BIT);
  // 启动模型
  ml.begin(sine_model_tflite);
}

void loop() {
  // 生成随机测试数据
  // pick up a random x and predict its sine
  float x = 3.14 * random(100) / 100;
  // 通过sin函数对输入值输出正确的预期值y
  float y = sin(x);
  // 将x值输入模型得到预测值,这个值就是执行模型后输出的值
  float input[1] = { x };
  float predicted = ml.predict(input);

  // 下面是设置pwm浮动算法,根据模型输出的结果改变LED灯亮度
  // Calculate the brightness of the LED such that y=-1 is fully off
  // and y=1 is fully on. The LED's brightness can range from 0-255.
  int brightness = (int)(127.5f * (predicted + 1));
  // Set the brightness of the LED. If the specified pin does not support PWM,
  // this will result in the LED being on when y > 127, off otherwise.
  //analogWrite(led, brightness);

  uint32_t duty = (8191 / 255) * min(brightness, 255);
  ledcWrite(PIN, duty);

  // 输出
  Serial.print("duty(");
  Serial.print(duty);
  Serial.println(")");
  Serial.print("sin(");
  Serial.print(x);
  Serial.print(") = ");
  Serial.print(y);
  Serial.print("\t predicted: ");
  Serial.println(predicted);
  delay(200);
}

4.4.主程序代码介绍

1.引入必要的库和头文件
c 复制代码
#include <Arduino.h>
#include <EloquentTinyML.h>
#include "sine_model.h"
2.定义运行模型输入和输出参数
c 复制代码
# 定义输入和输出的数量
#define NUMBER_OF_INPUTS 1
#define NUMBER_OF_OUTPUTS 1

# 定义Tensor Arena的大小,用于分配内存空间给模型,这个值需要根据具体开发板内存大小进行尝试调整
#define TENSOR_ARENA_SIZE 3 * 1024

# 创建TinyML对象并设置模型数据
Eloquent::TinyML::TfLite<NUMBER_OF_INPUTS, NUMBER_OF_OUTPUTS, TENSOR_ARENA_SIZE> ml;
3.初始化模型
c 复制代码
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  // LED灯pwm参数
  ledcAttach(PIN, BASE_FREQ, LEDC_TIMER_12_BIT);
  // 启动模型,输入的参数必须是模型文件中数组的名称,否则会找不到模型的入口。
  ml.begin(sine_model_tflite);
}
4.在loop()函数中运行模型控制LED灯亮灭

生成测试数据

c 复制代码
// 生成随机测试数据
// pick up a random x and predict its sine
float x = 3.14 * random(100) / 100;
// 通过sin函数对输入值输出正确的预期值y
float y = sin(x);

运行模型输出预测结果

c 复制代码
// 将x值输入模型得到预测值,这个值就是执行模型后输出的值
  float input[1] = { x };
  float predicted = ml.predict(input);

使用pwm方式控制灯的亮度

c 复制代码
  //使用模型输出的预测值作为条件改变pwm值
  int brightness = (int)(127.5f * (predicted + 1));
  uint32_t duty = (8191 / 255) * min(brightness, 255);
  ledcWrite(PIN, duty);

输出结果

c 复制代码
  // 输出信息
  Serial.print("duty(");
  Serial.print(duty);
  Serial.println(")");
  Serial.print("sin(");
  Serial.print(x);
  Serial.print(") = ");
  Serial.print(y);
  Serial.print("\t predicted: ");
  Serial.println(predicted);
  delay(200);
}

5.上传程序到ESP32

将ESP32开发板和电脑连接然后上传程序,运行结果如下。

从运行的结果可以看出模型运行结果和sin函数计算处的值差距很大,说明模型训练的时候精度不够。

通过模型运行,控制LED灯实现了闪烁功能。

相关推荐
MessiGo7 分钟前
Python 爬虫 (1)基础 | 基础操作
开发语言·python
肥猪猪爸31 分钟前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
LZXCyrus1 小时前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm
Enougme1 小时前
Appium常用的使用方法(一)
python·appium
懷淰メ1 小时前
PyQt飞机大战游戏(附下载地址)
开发语言·python·qt·游戏·pyqt·游戏开发·pyqt5
我感觉。1 小时前
【机器学习chp4】特征工程
人工智能·机器学习·主成分分析·特征工程
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
YRr YRr1 小时前
深度学习神经网络中的优化器的使用
人工智能·深度学习·神经网络
DieYoung_Alive1 小时前
一篇文章了解机器学习(下)
人工智能·机器学习
夏沫的梦1 小时前
生成式AI对产业的影响与冲击
人工智能·aigc