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

news2024/10/7 16:15:22

前言:本篇主要讲解搭建所需环境,以及基于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报告:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP之内置web服务器

1. 前言 PHP从5.4开始,就提供了一个内置的web服务器。 这个主要是用来做本地的开发测试用的,不能用于线上环境。 将PHP的安装路径配置到电脑的系统环境变量Path中,下图是win7,win10中会看的更清楚 2. 进入项目目录,执…

OpenHarmony南向开发案例:【 智能家居中控】

应用场景简介 智能家居。 今天打造的这一款全新智能家庭控制系统,凸显应用在智能控制和用户体验的特点,开创国内智能家居系统体验新局面。新的系统主要应用在鸿蒙生态。 工程版本 系统版本/API版本:OpenHarmony SDK API 8IDE版本&#xf…

unity cinemachine相机 (案例 跟随角色移动)

安装相机包 打开包管理工具 在 unity registry 搜索cinemachine 会在maincamera中生成一个组件cinemachineBrain 只能通过虚拟相机操控 主相机 虚拟相机的参数 案例 1.固定相机效果 位置 在固定的地方 默认的模式 2.相机跟随人物效果 焦距设置 20 跟随设置 把playere…

【LeetCode热题100】【多维动态规划】不同路径

题目链接:62. 不同路径 - 力扣(LeetCode) 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记…

Django模型继承之Meta继承

在Django模型继承中,当一个抽象基类被设计完成后,它会将该基类中定义的Meta内部类以属性的形式提供给子类。另外,如果子类未定义自己的Meta类,那么它就会默认继承抽象基类的Meta类。 关于Meta类的继承,大致总结如下&a…

Ubuntu20.04安装redis5.0.7

redis下载命令: wget https://download.redis.io/releases/redis-5.0.7.tar.gz 解压到 opt目录下 tar -zxvf redis-5.0.7.tar.gz -C /opt apt install -y gcc # 安装gccapt install make # 安装make 后面执行make一直报错 make报错后清除: make …

机器学习(XgBoost)预测顶和底

之前的文章中,我们对中证1000指数进行了顶和底的标注。这一篇我们将利用这份标注数据,实现机器学习预测顶和底,并探讨一些机器学习的原理。 我们选取的特征非常简单–上影线和WR(William’s R)的一个变种。选取这两个…

环境配置——Windows平台配置VScode运行环境为远程服务器或虚拟机

1. 远程机需要先安装SSH服务,命令如下 sudo apt install openssh-server 2. 安装好后需要开启SSH服务: sudo service sshd start 3. 查看SSH服务是否有被开启: sudo systemctl status sshd.service 4. 本地Windows需要生成密钥将公钥放…

毕业撒花 流感服务小程序的设计与实现

目录 1.1 总体页面设计 1.1.1 用户首页 1.1.2 新闻页面 1.1.3 我的页面 1.1.5 管理员登陆页面 1.1.6 管理员首页 1.2 用户模块 1.2.1 体检预约功能 1.2.2 体检报告功能 1.2.4 流感数据可视化功能 1.2.5 知识科普功能 1.2.6 疾病判断功能 1.2.7 出示个人就诊码功能 …

(五)AB测试及两个案例 学习简要笔记 #统计学 #CDA学习打卡

目录 一. AB测试简介 1)假设检验的一般步骤 2)基于假设检验的AB测试步骤 二. 案例1:使用基于均值的假设检验进行AB测试 1)原始数据 2)提出原假设H0和备择假设H1 3)使用均值之差的t检验,计…

计算机网络3——数据链路层3以太网的MAC层

文章目录 一、MAC 层的硬件地址1、介绍2、注意点3、定制标准 二、MAC 帧的格式1、结构2、工作原理3、其他 一、MAC 层的硬件地址 1、介绍 在局域网中,硬件地址又称为物理地址或 MAC地址(因为这种地址用在MAC帧中)。 大家知道,在所有计算机系统的设计中…

MySQL从入门到高级 --- 2.DDL基本操作

文章目录 第二章:2.基本操作 - DDL2.1 数据库的常用操作创建数据库选择要操作的数据库删除数据库修改数据库编码 2.2 表结构的常用操作创建表格式查看当前数据库的所有表名称查看指定某个表的创建语句查看表结构删除表 2.3 修改表结构添加列修改列名和类型删除列修改…

在Spring boot中指定随机可用的端口

​ 正常情况下每个spring boot启动都有固定的端口,也就是8080,如果启动多个项目,很容易出现端口冲突,那么怎么解决这个问题呢? 解决方案1: random 随机端口 ​ 在spring boot中,可以通过${ran…

20240424 每日一题:2385. 感染二叉树需要的总时间

题目简介: 这个问题描述了一个情景:给定一棵二叉树和一个起始节点值,起始节点被感染后,感染将从该节点开始向其相邻节点传播。每分钟,如果一个节点此前还没有感染,并且它与一个已感染节点相邻,…

vue项目打包时因为图片问题报错

执行 npm run build命令打包项目时报错,看起来是图片的问题: package.json里面image-webpack-loader的版本是^7.0.1 解决方案: 1、先卸载 npm uninstall image-webpack-loader 2、用cnpm重新安装 cnpm install image-webpack-loader --save…

《ElementPlus 与 ElementUI 差异集合》el-select 差异点,如:高、宽、body插入等

宽度 Element UI 父元素不限制宽度时,默认有个宽度 207px; 父元素有固定宽度时,以父元素宽度为准; Element Plus 父元素不限制宽度时,默认100%; 父元素有固定宽度时,以父元素宽度为准&#x…

百度网盘SVIP超级会员试用1天领取活动地址2024最新

百度网盘SVIP超级会员是百度网盘提供的一种高级会员服务,用户开通后可以享受多项特权和服务。以下是对百度网盘SVIP超级会员的详细介绍: 一、会员特权 百度网盘SVIP超级会员享有众多特权,包括但不限于: 容量套餐:SV…

Linux系统安全:从面临的攻击和风险到安全加固、安全维护策略(文末有福利)

1. Linux面临的攻击与风险 1.1. Linux系统架构 Linux系统架构解读: 用户之间隔离内核态与用户态之间隔离用户进程一般以低权限用户运行系统服务一般以特权服务运行用户态通过系统调用进入内核态内核对系统资源进行管理和分配 1.2. Linux系统常见安全威胁 1.2.1.…

uniapp项目中禁止横屏 ,app不要自动旋转 -,保持竖屏,uniapp取消重力感应

uniapp项目中禁止横屏 ,app不要自动旋转 -,保持竖屏,uniapp取消重力感应 1.适用于移动端,安卓和IOS,当即使手机打开了自动旋转的按钮,设置如下的代码后,页面依旧保持竖屏。 步骤一&#xff1a…

Git和Github绑定

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…