你知道吗?MCU也能做Machine learning (ML)

你知道吗?MCU也能做Machine learning (ML)


刚刚过去的2018年被称为“人工智能元年”,2019年人工智能将会有更大的发展,将会有更多的AI项目落地。随着单芯片计算力的不断增长,机器学习(ML)不再是云计算和高性能处理器的专利,边缘计算正在崛起!

边缘计算为AI提供了新的可能性,比如实时智能语音识别和实时人脸检测,其实时性、可靠性和隐私安全性是云计算无法相比的。

实战开始:使用CMSIS-NN在STM32F746G实现图像识别

1.准备工作

工欲善其事必先利其器,首先你需要相关的软件和硬件。
硬件

注意:本教程所有步骤都能非常容易的在其他开发板上实现。

软件

2.定义问题和模型

为了让你的边缘设备拥有更快的响应,更高的可靠性、安全性和更低的功耗,图像识别程序必须直接运行在你的设备上,而不是云端。

神经网络(NN)是机器学习(ML)算法中的一类,它在图像分类、目标识别、语音识别和自然语言处理应用中有着非常出色的表现。现在做流行的神经网络是多层感知机(MLP),卷积神经网络(CNN)和递归神经网络(RNN)。

在本教程中我们选择使用卷积神经网络(CNN),它来自于Caffe中对CIFAR-10图像数据集进行分类的一个示例,在这个示例中图像数据被输进卷积神经网络(CNN)进行分类,最后输出图像所属的类别。卷积神经网络(CNN)有不同的实现方式,下面是本教程选择的卷积神经网络:

模型的输入是一个32*32像素的彩色图片,它将被卷积神经网络(CNN)分为10类物体(猫、鹿、狗、马…)中的一类。分类任务的完成,依赖于神经网络的各个层:

  • 卷积层
  • 池化层
  • 激活函数
  • 全连接层
  • Softmax

提示:CIFAR-10是一个图像数据集,一共包含10类图像(飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车),其中每类图像包含60000幅32*32的彩色图像。

提示:如果有同学对卷积神经网络不了解,请在文末扫码关注小编,并在后台发送“计算机视觉”,即可获取参考资料。

在本教程中,我们将使用事先训练好的卷积神经网络模型,你可以到我们的GitHub仓库下载,如果你想训练自己的模型,请参考这个教程。下面就是本教程使用的卷积神经网络(CNN)模型的拓扑结构。

上面的拓扑图是使用Netscope工具生成的。

3.开发环境设置

  • 安装ARM Mbed CLI
sudo pip install mbed-cli
  • 创建工作空间
# In your <home> directory (cd ~)
mkdir CMSISNN_Webinar
cd CMSISNN_Webinar
# Create new Mbed project
# The --mbedlib flag picks Mbed-os version 2.0
mbed new cmsisnn_demo --mbedlib 

假如你是第一次使用Mbed工具,请安装相关依赖库

cd cmsisnn_demo/.temp/tools/
sudo pip install -r requirements.txt

# 也许你还需要安装这些
sudo pip install jsonschema
sudo pip install pyelftools
sudo apt-get install mercurial

4.构建基本的相机应用

  • 执行如下操作
cd ~/CMSISNN_Webinar/cmsisnn_demo
mbed add http://os.mbed.com/teams/ST/code/BSP_DISCO_F746NG/ 
mbed add http://os.mbed.com/teams/ST/code/LCD_DISCO_F746NG 
  • 获取Mbed稳定版本
# 打开mbed.bld文件,使用下面的链接替换文件中第一行的内容
https://mbed.org/users/mbed_official/code/mbed/builds/e95d10626187

  • 获取依赖库
mbed deploy
  • 获取相机应用示例
cd ~/CMSISNN_Webinar/
git clone https://github.com/ARM-software/ML-examples.git
  • 编译
cd cmsisnn_demo/
mbed compile -m DISCO_F746NG -t GCC_ARM --source . --source ../ML-examples/cmsisnn-cifar10/camera_demo/camera_app/
  • 图像采集测试

将生成的.bin文件直接拖拽到你的开发板存储中。

提示:Mbed开发板通过USB连接到PC后,会生成一个盘符,将固件复制进去即可更新程序,不了解Mbed的同学请自行百度

如果以上步骤成功执行,那么你将看到如下画面:

5.转化预训练的Caffe模型

为了使卷积神经网络模型能够在ARM Cortex-M微控制器上运行,我们必须要对Caffe模型进行优化。

5.1模型量化(Quantize the model)

请使用Arm’s quantization script脚本将Caffe模型的权重和激活函数从32位浮点型数据转换为8位整型格式。这不会减小神经网络的大小,但是可以避免复杂的浮点运算。此脚本文件将会找出最优的8位定点数的表示方式,以确保在测试数据集上的最小精度损失。此脚本的执行输出是一个序列化的Python pickle(.pkl)文件,它包含神经网络模型,量化的权重和激活函数,以及每一层的量化格式。

执行如下命令即可生成量化模型:

# Run command in the ML-examples/cmsisnn-cifar10 directory
cd ~/CMSISNN_Webinar/ML-examples/cmsisnn-cifar10 
# Note: To enable GPU for quantization sweeps, use '--gpu' argument
python nn_quantizer.py --model models/cifar10_m7_train_test.prototxt --weights models/cifar10_m7_iter_300000.caffemodel.h5 --save models/cifar10_m7.pkl

5.2转换模型

现在你已经拥有了一个优化的模型,下面我们将这个模型转换为一个C++文件,然后你可以将这个C++程序集成到你的相机应用项目中。


# Run command in the ML-examples/cmsisnn-cifar10 directory
python code_gen.py --model models/cifar10_m7.pkl --out_dir code/m7

这个脚本的执行将获取到量化参数和网络连接图,并且将生成由NN函数调用组成的代码。

6.构建图像识别应用

最后一步,我们将在图像采集应用中集成神经网络。

6.1包含nn.h

为了实现这一步,我们需要对文件ML-examples/cmsisnn-cifar10/camera_demo/camera_app/camera_app.cpp做一些修改。

# In ML-examples/cmsisnn-cifar10/camera_demo/camera_app/
# Rename the camera application 
mv camera_app.cpp camera_with_nn.cpp

  • 首先,我们将之前通过code_gen.py脚本生成的代码包含进camera_with_nn.cpp文件
#include "nn.h"

6.2添加分类能力

在开始之前我们需要定义一些变量。

  1. 标签变量-用字符串标识最终的输出结果
  2. 输出数据数组-用于存储神经网络的输出
const char* cifar10_label[] = {"Plane", "Car", "Bird", "Cat", "Deer", "Dog", "Frog", "Horse", "Ship", "Truck"};
q7_t output_data[10]; //10-classes

我们还需要一个函数去获取softmax输出的最优预测值:

int get_top_prediction(q7_t* predictions)
{
  int max_ind = 0;
  int max_val = -128;
  for(int i=0;i<10;i++) {
    if(max_val < predictions[i]) {
      max_val = predictions[i];
      max_ind = i;
    }
  }
  return max_ind;
}

6.3修改camera_with_nn.cpp文件中的main函数

现在是时候添加神经网络函数调用了,此函数是之前我们通过code_gen.py脚本生成的。

// run neural network 
run_nn((q7_t*)resized_buffer, output_data);
// Softmax: to get predictions
arm_softmax_q7(output_data,IP1_OUT_DIM,output_data);

我们还需要标识出图片属于哪个类别的可能性最大,通过下面的函数实现:

int top_ind = get_top_prediction(output_data);

最后,添加以下代码测试输出,打印预测结果和可信度。

sprintf(lcd_output_string,"  Prediction: %s       ",cifar10_label[top_ind]);
lcd.DisplayStringAt(0, LINE(8), (uint8_t *)lcd_output_string, LEFT_MODE);
sprintf(lcd_output_string,"  Confidence: %.1f%%   ",(output_data[top_ind]/127.0)*100.0);
lcd.DisplayStringAt(0, LINE(9), (uint8_t *)lcd_output_string, LEFT_MODE);

7.CMSIS-NN

为了充分利用微控制器的能力,接下来我们将使用CMSIS-NN优化库。CMSIS-NN是专门为ARM Cortex-M处理器内核设计的一套高效的神经网络库,它将Cortex-M处理器内核的性能发挥到极致。基于CMSIS-NN的神经网络接口,在运行时实现了4.6倍的性能提升,在能量充足的情况下实现了4.9倍的性能提升。

为了在你的代码中使用CMSIS-NN,首先你需要获取他:

cd ~/CMSISNN_Webinar
git clone https://github.com/ARM-software/CMSIS_5.git

下面的这些函数将被nn.cpp文件中的run_nn(input, output)函数调用,它们被定义在 CMSIS-NN 库中, 示例如下:

arm_convolve_HWC_q7_RGB(input_data, CONV1_IN_DIM, CONV1_IN_CH, conv1_wt, CONV1_OUT_CH, CONV1_KER_DIM, CONV1_PAD, CONV1_STRIDE, conv1_bias, CONV1_BIAS_LSHIFT, CONV1_OUT_RSHIFT, buffer1, CONV1_OUT_DIM, (q15_t*)col_buffer, NULL);
arm_maxpool_q7_HWC(buffer1, POOL1_IN_DIM, POOL1_IN_CH, POOL1_KER_DIM, POOL1_PAD, POOL1_STRIDE, POOL1_OUT_DIM, col_buffer, buffer2);
arm_relu_q7(buffer2, RELU1_OUT_DIM*RELU1_OUT_DIM*RELU1_OUT_CH);

现在我们已经优化了模型,并且完整构建了图像识别应用,是时候进行最后一步了!

8.将图像识别程序部署到开发板上

  • 编译我们创建的图像识别程序
cd cmsisnn_demo/ 
mbed compile -m DISCO_F746NG -t GCC_ARM --source .  --source ../ML-examples/cmsisnn-cifar10/code/m7 --source ../ML-examples/cmsisnn-cifar10/camera_demo/camera_app/ --source ../CMSIS_5/CMSIS/NN/Include --source ../CMSIS_5/CMSIS/NN/Source --source ../CMSIS_5/CMSIS/DSP/Include --source ../CMSIS_5/CMSIS/DSP/Source --source ../CMSIS_5/CMSIS/Core/Include -j8 

将生成的.bin文件,放进你的开发板中运行。

运行结果如下:

更多精彩资讯,请扫码关注

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MCU控制RTL时,一般需要使用到SPI或I2C总线来进行通信。这里以SPI为例来介绍相关的驱动代码。 1. 首先需要初始化SPI总线,包括设置SPI的工作模式、数据位数、CPOL、CPHA等参数,并使能SPI总线。 ```c void spi_init(void) { // 初始化SPI总线 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; SPI_Init(SPI1, &SPI_InitStructure); // 使能SPI总线 SPI_Cmd(SPI1, ENABLE); } ``` 2. 然后需要编写发送数据的函数,这里以发送一个字节为例。 ```c void spi_write_byte(uint8_t data) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // 等待发送缓冲区为空 SPI_I2S_SendData(SPI1, data); // 发送数据 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); // 等待接收缓冲区非空 SPI_I2S_ReceiveData(SPI1); // 清除接收缓冲区 } ``` 3. 最后编写读取数据的函数,同样以读取一个字节为例。 ```c uint8_t spi_read_byte(void) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // 等待发送缓冲区为空 SPI_I2S_SendData(SPI1, 0x00); // 发送数据 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); // 等待接收缓冲区非空 return SPI_I2S_ReceiveData(SPI1); // 返回接收到的数据 } ``` 这样就可以通过MCU控制RTL芯片了。当然,具体的驱动代码还需要根据具体的芯片型号来进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值