【嵌入式AI部署神经网络】STM32CubeIDE上部署神经网络之指纹识别(Pytorch)——篇一|环境搭建与模型初步部署篇

前言:本篇主要讲解搭建所需环境,以及基于pytorch框架在stm32cubeide上部署神经网络,部署神经网络到STM32单片机,本篇实现初步部署模型,没有加入训练集与验证集,将在第二篇加入。篇二详细讲解STM32CubeIDE上部署神经网络之指纹识别(Pytorch)的数据准备和模型训练过程等,进行实战,第二篇在本专栏查阅。

目录

1. 环境安装和配置

2. AI神经网络模型搭建

2.1 数据集介绍

2.2 网络模型

2.3 训练

3. STM32CubeIDE上进行模型转换与模型部署到单片机

4. STM32 CubeUDE上进行模型验证

5. 结果统计与分析


1. 环境安装和配置

本文介绍在STM32cubeIDE上部署AI模型,开发板型号STM32F429IGT6。

与AI加速器不同,ST支持神经网络计算是因为之前的芯片已经内置了DSP处理器,可以执行高精度浮点运算,正好可以拿来做神经网络计算。如何判断自己准备购买的板子适不适合做AI计算,最好也按以下步骤在CUBE-AI上模拟部署一遍,若模拟成功,所选开发板就是可以的。

STM32cubeIDE可直接在ST官网下载,下载链接

https://www.st.com/zh/development-tools/stm32cubeide.html

默认安装即可,不懂可自行上网查教程。

2. AI神经网络模型搭建

2.1 数据集介绍

针对tinyML开发了自己的指纹识别数据集,数据集和完整代码见文末下载链接。指纹识别数据集包含100个类别,大小为260*260,训练集30张,测试集5张。在测试时使用128*128与64*64的分辨率。

数据集在如下文件夹中

生成测试集的方法:

import os
import numpy as np
from PIL import Image
import torchvision.transforms as transforms

normalize = transforms.Normalize(mean=[0.5],std=[0.5])
test_transforms = transforms.Compose([
            # transforms.RandomResizedCrop(224),
            transforms.Resize(128),
            transforms.ToTensor(),
            normalize])

def prepare_eval_data(data_file, transform=None):

    datas = os.listdir(data_file)
    imgs=[]
    labels=[]
    for  img_path in datas:
        data = Image.open(data_file + '/' + img_path)  # 260*260*1
        label, _ = img_path.split('_')
        label = int(label) - 1
        label_ohot=np.zeros(100)
        label_ohot[label]=1
        # print(data.shape, label)

        data1 = transform(data)
        data2 = transform(data)
        data3 = transform(data)

        data = np.concatenate([data1, data2, data3], 0)

        labels.append(label_ohot)
        imgs.append(data)
    imgs = np.array(imgs)
    labels = np.array(labels)
    print(imgs.shape,labels.shape)
    return imgs,labels


if __name__ == '__main__':
    data_path = '/disks/disk2/dataset/fingerprint/'

    testx,testy=prepare_eval_data(data_path+'test',test_transforms )
    print(testx.shape,testy.shape)
    print(testy[0])

    np.save('fpr100*5_testx_128.npy', testx)
    np.save('fpr100*5_testy_128.npy', testy)

2.2 网络模型

  测试了多个轻量化神经网络模型,如压缩后的MobileNet v2:

class MobileNetV2Slim(nn.Module):
    def __init__(self, num_classes=1000):
        super(MobileNetV2Slim,self).__init__()

        self.first_conv = Conv3x3BNReLU(3,4,2,groups=1)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        # self.layer1 = self.make_layer(in_channels=8, out_channels=4, stride=1, block_num=1)
        self.layer2 = self.make_layer(in_channels=4, out_channels=6, stride=1, block_num=1)
        self.layer3 = self.make_layer(in_channels=6, out_channels=8, stride=2, block_num=2)
        self.layer4 = self.make_layer(in_channels=8, out_channels=16, stride=2, block_num=3)
        self.layer5 = self.make_layer(in_channels=16, out_channels=24, stride=1, block_num=3)
        self.layer6 = self.make_layer(in_channels=24, out_channels=32, stride=2, block_num=3)
        self.layer7 = self.make_layer(in_channels=32, out_channels=64, stride=1, block_num=1)

        self.last_conv = Conv1x1BNReLU(64,128)
        self.avgpool = nn.AvgPool2d(kernel_size=2,stride=1)
        self.dropout = nn.Dropout(p=0.2)
        self.linear = nn.Linear(in_features=128,out_features=num_classes)

    def make_layer(self, in_channels, out_channels, stride, block_num):
        layers = []
        layers.append(InvertedResidual(in_channels, out_channels, stride))
        for i in range(1, block_num):
            layers.append(InvertedResidual(out_channels,out_channels,1))
        return nn.Sequential(*layers)

    def init_params(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear) or isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def forward(self, x):
        x = self.first_conv(x)
        x=self.maxpool(x)
        # x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.layer5(x)
        x = self.layer6(x)
        x = self.layer7(x)
        x = self.last_conv(x)
        # print(x.size())
        x = self.avgpool(x)
        # x = x.view(x.size(0),-1)
        x=x.reshape(int(x.size(0)), -1)
        x = self.dropout(x)
        out = self.linear(x)
        return out

2.3 训练

基于pytorch搭建模型进行训练,核心代码如下:


'''***********- trainer -*************'''
class trainer:
    def __init__(self, loss_f,loss_dv,loss_fn, model, optimizer, scheduler, config):
        self.loss_f = loss_f
        self.loss_dv = loss_dv
        self.loss_fn = loss_fn
        self.model = model
        self.optimizer = optimizer
        self.scheduler = scheduler
        self.config = config

    def batch_train(self, batch_imgs, batch_labels, epoch):
        predicted = self.model(batch_imgs)
        loss = self.myloss(predicted, batch_labels)
        predicted = softmax(predicted, dim=-1)
        del batch_imgs, batch_labels
        return loss, predicted

    def train_epoch(self, loader,epoch):
        self.model.train()
        tqdm_loader = tqdm(loader)
        # acc = Accuracy_score()
        losses = AverageMeter()
        top1 = AverageMeter()
        top5 = AverageMeter()

        print("\n************Training*************")
        for batch_idx, (imgs, labels) in enumerate(tqdm_loader):
            #print("data",imgs.size(), labels.size())#[128, 3, 32, 32]) torch.Size([128]
            imgs, labels=imgs.cuda(), labels.cuda()#permute(0,3,1,2).
            # print(self.optimizer.param_groups[0]['lr'])
            loss, predicted = self.batch_train(imgs, labels, epoch)
            losses.update(loss.item(), imgs.size(0))
            # print(predicted.size(),labels.size())

            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()
            # self.scheduler.step()

            err1, err5 = accuracy(predicted.data, labels, topk=(1, 5))
            top1.update(err1.item(), imgs.size(0))
            top5.update(err5.item(), imgs.size(0))

            tqdm_loader.set_description('Training: loss:{:.4}/{:.4} lr:{:.4} err1:{:.4} err5:{:.4}'.
                                        format(loss, losses.avg, self.optimizer.param_groups[0]['lr'],top1.avg, top5.avg))
            # if batch_idx%1==0:
            #     break
        return 100-top1.avg, losses.avg

    def valid_epoch(self, loader, epoch):
        self.model.eval()
        # acc = Accuracy_score()
        # tqdm_loader = tqdm(loader)
        losses = AverageMeter()
        top1 = AverageMeter()

        print("\n************Evaluation*************")
        for batch_idx, (imgs, labels) in enumerate(loader):
            with torch.no_grad():
                batch_imgs = imgs.cuda()#permute(0,3,1,2).
                batch_labels = labels.cuda()
                predicted= self.model(batch_imgs)
                loss = self.myloss(predicted, batch_labels).detach().cpu().numpy()
                loss = loss.mean()
                predicted = softmax(predicted, dim=-1)
                losses.update(loss.item(), imgs.size(0))

                err1, err5 = accuracy(predicted.data, batch_labels, topk=(1, 5))
                top1.update(err1.item(), imgs.size(0))

        return 100-top1.avg, losses.avg



    def myloss(self,predicted,labels):
        #print(predicted.size(),labels.size())#[128, 10]) torch.Size([128])
        loss = self.loss_f(predicted,labels,)
        # loss = loss1+loss2
        return loss

    def run(self, train_loder, val_loder,model_path):
        best_acc = 0
        start_epoch=0
        # model, optimizer, start_epoch=load_checkpoint(self.model,self.optimizer,model_path)
        for e in range(self.config.epochs):
            e=e+start_epoch+1
            print("------model:{}----Epoch: {}--------".format(self.config.model_name,e))
            self.scheduler.step(e)
            # torch.cuda.empty_cache()

            train_acc, train_loss = self.train_epoch(train_loder,e)
            val_acc, val_loss=self.valid_epoch(val_loder,e)
            #
            print("\nval_loss:{:.4f} | val_acc:{:.4f} | train_acc:{:.4f}".format(val_loss, val_acc,train_acc))

            if val_acc > best_acc:
                best_acc = val_acc
                print('Current Best (top-1 acc):',val_acc)
                #new_model = quant_dorefa.prepare(self.model, inplace=False, a_bits=8, w_bits=8)
                x = torch.rand(1, 3,data_config.input_size, data_config.input_size).float().cuda()
                save_path=data_config.MODEL_PATH+data_config.model_name+'_epoch{}_params.onnx'.format(e)
                torch.onnx.export(self.model, x, save_path, export_params=True, verbose=False,opset_version=10)
                # 支持Opset 7, 8, 9 and 10 of ONNX 1.6 is supported.
                print("saving model sucessful !",save_path)


        print('\nbest score:{}'.format(data_config.model_name))
        print("best accuracy:{:.4f}  ".format(best_acc))

模型训练后得到***.onnx模型、训练和测试数据,这是我们后续步骤要用到的。

3. STM32CubeIDE上进行模型转换与模型部署到单片机

    步骤2得到了torch神经网络框架训练好的“***.onnx”模型文件,下一步需要把该AI模型转换成C程序代码,并嵌入到整个项目工程中。整个项目工程包括硬件代码可以直接通过 STM32CubeIDE工具生成。

具体步骤如下:

打开安装好的STM32CubeIDE软件。

新建,stm32 project。

搜索,选择目标开发板型号STM32F429IGT6,点击next。

填写项目名,点击finish。

点击Yes 。

点击I hava read ....   ;然后点击Finish。

等待初始化完成,左边是IDE编译器中是自动生成的底板代码,右边是CUBEMX软件界面,可对开发板硬件资源进行配置。

 下载X-CUBE-AI,需在software packs点manage software packs找到X-CUBE-AI下载。

 

选取ai开发需要支持的软件包cue-ai,在software packs点select components。


 

在X-CUBE-AI行下core勾选,application选validation,双核开发板建议选M7系列,点OK完成

回到引脚配置页面,最下方就是上面导入的CUBE-AI工具包,点击进入配置,选中上方两个√号,然后在configration下方platform setting 选择并配置串口,用来进行数据交互的

回到cube-ai,选择添加网络

根据训练产生的文件进行配置:选择模型文件、压缩比、加载验证数据

点击分析:工具会根据模型与模型参数推断出模型占用(ROM、RAM),进而判断神经网络能否部署到开发板。

若出现以下报错 

错误信息建议通过将注册表中的LongPathsEnabled键设置为1来启用长路径。

以下是具体操作步骤:

  1. 通过在Windows搜索栏中键入“regedit”并按Enter来打开注册表编辑器。
  2. 导航至以下键:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem
  3. 在右侧窗格上右键单击,然后选择“新建” -> “DWORD (32位) 值”。
  4. 将新值命名为LongPathsEnabled
  5. 双击LongPathsEnabled并将其值设置为1
  6. 单击“确定”以保存更改。
  7. 关闭注册表编辑器,然后尝试重新运行命令。

网络分析运行成功

根据ROM、RAM判断出模型可部署 

 点击,validation on desktop 在pc上进行模型验证。(也可以实现原模型与转换后模型的对比。)

在前面步骤基础上,点时钟配置栏,系统会自动进行时钟配置

点项目管理栏,进行配置

在IDE编译器中,选择project,选择生成代码

生成代码后,右键工程,选择build project

修改代码,等待编译完成后,若没有错误则下一步。

重新编译后,右下侧可以看到占用的flash与RAM,表明模型是否成功部署,

右键工程名运行。

配置,默认即可

烧录完成

至此,模型部署完成,下面开始进行板上模型验证。

4. STM32 CubeUDE上进行模型验证

基于上述步骤,下面进行模型验证,验证AI模型在目标板上的运行情况。验证原理如下:

现在tinyML解决方案厂商都在做AutoML,就是为方便初学者不需要掌握专门的硬件知识就能够自动部署到MCU开发板。因此开发者只需要按照指定的格式准备好数据,validate on target 就是将准备好的数据通过串口传输到RAM内存中,一般是神经网络指定的输入缓冲块,经神经网络后再将结果通过串口传回前端程序显示。

运行前,不要忘记连接开发板,以及检测端口号

双击.ioc文件,会自动返回cubeMX页面,与validate on desktop相同的配置进行validate on target

自动即可

 validation on target结果如下

重新 validation on desktop和validation on target,结果是一样的,

参数分析

运行时间

5. 结果统计与分析

结果分析如下:

当前暂时没有加入测试与验证数据集,将在第二篇加入。

更多详细信息需参考txt报告:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/568400.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于研发过程改进的质量度量模型

随着企业规模和产品项目的不断扩张,全面、精准、高效地保障产品质量成为组织的核心挑战。为了应对这一挑战,企业应寻求采用数字化和智能化的研发过程管理方案,以实现对研发活动的精细化量化控制,并利用数据分析工具深入洞察产品质…

Interpreter 解释器

意图 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 结构 AbstractExpression声明一个程序的解释操作,这个接口为抽象语法树中所有结点所共享。TerminalExpression实现与文法…

【IR 论文】Query2doc — 使用 LLM 做 Query Expansion 来提高信息检索能力

论文:Query2doc: Query Expansion with Large Language Models ⭐⭐⭐⭐⭐ Microsoft Research, EMNLP 2023 文章目录 背景介绍Query2doc 论文速读实现细节实验结果和分析总结分析 背景介绍 信息检索(Information Retrieval,IR)指…

谷歌收录工具有什么好用的?

如果是想促进谷歌的收录,其实能用的手段无非就两个,谷歌GSC以及爬虫池 谷歌gsc就不用说了,作为谷歌官方提供的工具,他能提供最准确的数据,并且可以提交每天更新的链接,进而促进收录,只要你的页面…

【unity】三维数学应用(计算线和面的交点)

【unity】三维数学应用(计算线和面的交点) 实现方法有多种,下面介绍一种简单的方法。利用一个点指向面上任意点的向量,到该面法线的投影长度相同的基本原理,结合相似三角形既可以求出交点。 原理 如下图 GD组成的线段…

Docker搭建Maven仓库Nexus

文章目录 一、简介二、Docker部署三、仓库配置四、用户使用Maven五、管理Docker镜像 一、简介 Nexus Repository Manager(简称Nexus)是一个强大的仓库管理器。 Nexus3支持maven、docker、npm、yum、apt等多种仓库的管理。 建立了 Maven 私服后&#xf…

大小端解释以及如何使用程序判断IDE的存储模式

今天让我们来了解一下大小端的概念吧 什么是大小端? 大端(存储)模式:指的是数据的低位保存在内存的高地址处,而数据的高位则保存在内存的低地址处。 小端(存储)模式:指的是数据的低位…

jvm中的垃圾回收器

Jvm中的垃圾回收器 在jvm中,实现了多种垃圾收集器, 包括: 1.串行垃圾收集器 2.并行垃圾收集器 3.CMS(并发)垃圾收集器 4.G1垃圾收集器 1.串行垃圾回收器 效率低,使用较少 2.并行垃圾回收器 3.并发垃圾回…

InternLM2-lesson3作业+笔记

茴香豆 https://www.bilibili.com/video/BV1QA4m1F7t4/?vd_source902e3124d4683c41b103f1d1322401fa 一、笔记 RAG RAG(Retrieval Augmented Generation)是一种结合了检索(Retrieval)和生成(Generation)的技术,旨在通过利用外部知识库来增强大预言模型的性能。…

ctfshow web入门 web180--web185

web180 import requests import recom re.compile("admin") def repisTrue(char):url f"http://自己环境的网址/api/?id1%27and%27{char}%27%27{char}&page1&limit10"res requests.get(url)w com.search(res.text)if w is not None:return T…

windows系统下python开发工具安装

一. 简介 前一篇文章学习了安装 python解释器,文章如下: windows系统下python解释器安装-CSDN博客 本文来学习如何下载安装 python开发工具 PyCharm。 二. python开发工具 PyCharm下载安装 1. PyCharm官网 PyCharm开发工具 PyCharm为 python代码…

自制Apache-Doris 2.0.4镜像Docker部署一Fe和一Be集群及遇到的问题解决

自制Apache-Doris 2.0.4镜像Docker部署一Fe和一Be集群及遇到的问题解决 文章目录 1.前言2.doris是什么?2.1简介2.2介绍2.3使用场景2.4架构 3.官网4.构建部署4.1 构建环境4.2 doris2.0.4的fe和be镜像构建4.2.1 fe2.0.4镜像构建脚本4.2.2 be2.0.4镜像构建4.2.3 启动脚…

Linux 操作系统的引导过程

Linux系统开机引导过程: 开机自检 检测硬件设备,找到能够引导系统的设备,比如硬盘MBR引导 运行MBR扇区里的主引导程序GRUB启动GRUB菜单 系统读取GRUB配置文件(/boot/grub2/grub.cfg)获取内核的设置和…

centos7+mysql57安装以及初始化

1、下载安装yum官方mysql源: http://repo.mysql.com/ ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/061472a86e9e4548b76d4603d4614568.png rpm -ivh mysql57-community-release-el7.rpm2、yum安装mysql服务 yum install -y mysql-community-server…

深兰科技入选2024全国“人工智能+”行动创新案例TOP100

近日,中科院《互联网周刊》联合eNET研究院、德本咨询、中国社会科学院信息化研究中心共同发布了《2024全国“人工智能”行动创新案例TOP100》榜单。经评委会层层遴选,深兰科技专为洛阳市打造的“工业智能化洛阳中心”项目成功入围该榜单。一同入围的还包…

Threejs 相机定位聚焦

网上大部分讲功能的时候,讲的太复杂了,或者就是讲不清,今天我就给大家简单简洁的描述下这个聚焦定位咋做如下: 扫码关注下: 聚焦定位只要就是设置相机的位置,和更新相机的朝向,就如一个人站在这…

2024三掌柜赠书活动第二十四期:containerd原理剖析与实战

目录 前言 Containerd的架构 Containerd的工作流程 Containerd的实战演示 关于《containerd原理剖析与实战》 编辑推荐 内容简介 作者简介 图书目录 书中前言/序言 《containerd原理剖析与实战》全书速览 结束语 前言 作为开发者,对于编程语言并不陌生&…

Atlas Vector Search:借助语义搜索和 AI 针对任何类型的数据构建智能应用

Atlas Vector Search已正式上线! Vector Search(向量搜索)现在支持生产工作负载,开发者可以继续构建由语义搜索和生成式人工智能驱动的智能应用,同时通过 Search Node(搜索节点)优化资源消耗并…

Android—— log的记忆

一、关键log 1.Java的 backtrace(堆栈log) 上述是一个空指针异常,问题出现在sgtc.settings,所以属于客户UI问题。 2.WindowManager(管理屏幕上的窗口和视图层次结构) 3.ActivityManager(管理应用程序生命周期和任务栈) 4.wifi操作 (1) 连接wifi&#…

2024深圳杯数学建模挑战赛C题:编译器版本的识别问题思路代码成品论文分析

更新完整代码和成品完整论文 《2024深圳杯&东三省数学建模思路代码成品论文》↓↓↓ https://www.yuque.com/u42168770/qv6z0d/zx70edxvbv7rheu7?singleDoc# 问题重述 深圳杯数学建模挑战赛2024C题:编译器版本的识别问题 作为一种重要的工具,电子…