Datawhale干货

作者:张强,Datawhale成员

讲多了算法,如何真正将算法应用到产品领域?本文将带你从0用深度学习打造一个视觉AI的微型处理器。文章含完整代码,知识点相对独立,欢迎点赞收藏,跟着本文做完,你也可以做一个自己的嵌入式AI小产品!

属于AI人的硬核快乐

背景

随着硬件尤其是显卡性能升级,以及Pytorch,TensorFlow深度学习框架日趋完善,视觉AI算法在多个领域遍地开花,其中就包括嵌入式设备。这是一种微型处理器,它的关键单元就是内部小小的计算芯片。嵌入式设备和我们日常用的电脑相比体积小,只包含必要外设。一些针对特定任务的嵌入式设备往往不会运载我们常用的比如Windows、Linux系统,而是直接将代码烧录进去运行。

在嵌入式设备上尝试部署深度学习算法开始较早,1989年一家叫做ALVIVN的公司就将神经网络用在汽车上了。现如今,工程师们将其用在安防、机器人、自动驾驶等领域。因此,懂得如何设计、训练算法,又能将其部署到边缘硬件产品上,能帮我们实现许多产品的想法。

但是,视觉算法部署在产品中仍有许多难点,比如:(1)模型通常需要在CPU/GPU/NPU/FPGA等各种各样不同类型的平台上部署;(2)嵌入式算力/内存/存储空间都非常有限;跑在云端服务器上,需要实时联网又不很优雅;(3)模型训练时可能会使用不同的AI框架(比如Pytorch/TensorFlow等)、不同硬件(比如GPU、NPU),相互适配产生问题[1]。

因此笔者开始思考下列问题:

  • 有什么亲民价格的芯片能处理部署视觉AI算法?

  • 如何将深度学习算法部署到嵌入式设备上?

对第一个问题,在经过调研后,还真有这样的芯片,那就是嘉楠科技的K210芯片。一个芯片几十元,对应的开发板在某宝上两百多就可以买到。根据嘉楠官方的描述,K210具有双核 64-bit RISC-V RV64IMAFDC (RV64GC) CPU / 400MHz(可超频到600MHz),双精度 FPU,8MiB 64bit 片上 SRAM(6MiB通用SRAM+2MiB的AI专用SRAM)。关于这块芯片更详细的介绍可以参考[2] 。

市面上有许多搭载K210的开发板,笔者这里选了雅博一款功能较全的K210开发板,开始了嵌入式AI的折腾之路。

对于第二个问题,方法就多了,不同深度学习框架,不同硬件选型都决定着不同技术路线。基本路线可以为深度学习平台训练 -> 模型剪枝、量化 -> 参数转换 ->转换为硬件平台上能运行的模型

对深度学习平台选型,笔者决定选用当下最流行的Pytorch平台。最后一步往往取决于这个硬件的生态,如果没有相关生态支持,可能需要手写C语言代码加载参数运行。调研发现,K210有一个深度网络优化平台NNCASE,能加速深度模型部署。

调研过程中发现在这块板子上部署模型大多数都是从Keras、TensorFlow开始训练并最终部署,而研究者常用的Pytorch竟然没有教程,于是今天就尝试来讲一讲。

接下来,我们将从使用Pytorch训练手写体识别的例子开始,打通从训练到嵌入式平台部署的流程。

01 使用Pytorch训练分类网络模型

必要软件包安装

pip install tensorbay pillow torch torchvision numpy

数据集获取

一个AccessKey获取所有数据集。

我们使用一个开源数据集平台:https://gas.graviti.com ,这个网站汇总了AI开发者常见的公开数据集,调用其SDK就能直接在线训练,而且许多数据集直接在国内网络下连接直接使用,不需要到国外网站进行下载,还是非常方便的。

a. 打开本文对应数据集链接 https://gas.graviti.com/dataset/data-decorators/MNIST

b. 右上角注册登录

c. fork数据集

d. 点击网页上方开发者工具,获取使用SDK所需的AccessKey,获取到 AccessKey 后,将其存在项目根目录的gas_key.py里:

KEY = "<Your-Key>"

通过AccessKey可以上传数据、读取数据、使用数据,灵活对接模型开发和训练,与数据pipeline快速集成。

e. AccessKey写入后就可以写代码读取数据了,读取后可以使用一行代码自行下载,或者可以开启缓存功能,在读取过后会自动将数据存储到本地。将下载后的数据放在data文件夹下:

import numpy as np
from PIL import Imagefrom tensorbay import GAS
from tensorbay.dataset import Dataset
from tensorbay.dataset import Segmentdef read_gas_image(data):with data.open() as fp:image = Image.open(fp)return np.array(image)KEY = "用你的Key替换掉这个字符串"
# Authorize a GAS client.
gas = GAS(KEY)
# Get a dataset.
dataset = Dataset("MNIST", gas)# 开启下行语句在当前路径下的data目录缓存数据
# dataset.enable_cache("data")# List dataset segments.
segments = dataset.keys()
# Get a segment by name
segment = dataset["train"]
for data in segment:# 图片数据image = read_gas_image(data)# 标签数据label = data.label.classification.category

怎么把这个数据集集成到Pytorch里呢?官方文档也为此写了不少例子[4]。笔者尝试过觉得挺方便,在为不同任务训练嵌入式AI模型时,只需更换数据集的名字,就能下载,不用再打开浏览器、等待下载以及处理很久了。有了数据集之后,我们接下来用其训练一个分类任务模型。

深度网络模型选型

结合硬件特点设计网络。

在考虑硬件部署的任务时,网络的设计就要受到些许限制。

首先,大而深的模型是不行的,K210的RAM是6M,这意味着模型+程序都要烧到这个空间。当然我们可以放到内存卡中,但实时性要受影响。

其次,还要考虑AI编译器对特定算子的优化,以K210 NNCASE为例[3],其支持TFLite、Caffe、ONNX共三个平台的算子。

打开对应平台,能够到具体有哪些算子已经实现了低层优化。可以看到对ONNX算子优化还是比较多的。如果所选用的网络算子较新,抑或是模型太大,都要在本步多加思考与设计。

如果如果最后部署不成功,往往需要回到这一步考虑网络的设计。为了尽可能减少算子的使用,本文设计一个只基于卷积+ReLU+Pool的CNN:

代码文件名:models/net.py

class Net(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(1, 6, 5)self.relu1 = nn.ReLU()self.pool1 = nn.MaxPool2d(2)self.conv2 = nn.Conv2d(6, 16, 5)self.relu2 = nn.ReLU()self.pool2 = nn.MaxPool2d(2)self.conv3 = nn.Conv2d(16, 32, 4)self.relu3 = nn.ReLU()self.conv4 = nn.Conv2d(32, 64, 1)self.relu4 = nn.ReLU()self.conv5 = nn.Conv2d(64, 32, 1)self.relu5 = nn.ReLU()self.conv6 = nn.Conv2d(32, 10, 1)self.relu6 = nn.ReLU()def forward(self, x):y = self.conv1(x)y = self.relu1(y)y = self.pool1(y)y = self.conv2(y)y = self.relu2(y)y = self.pool2(y)y = self.conv3(y)y = self.relu3(y)y = self.conv4(y)y = self.relu4(y)y = self.conv5(y)y = self.relu6(y)y = self.conv6(y)y = self.relu6(y)y = y.view(y.shape[0], -1)return y

网络训练

设计好模型后,使用如下脚本进行训练。接下来脚本文件大致浏览一下,明白其中的工作原理即可。

代码文件名:1.train.py

注意将其中的ACCESS_KEY替成你自己的AccessKey。

from __future__ import print_function
import argparse
import torch
import torch.nn.functional as F
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
from models.net import Net
from PIL import Image
from torch.utils.data import DataLoader, Dataset
from torchvision import transformsfrom tensorbay import GAS
from tensorbay.dataset import Dataset as TensorBayDatasetclass MNISTSegment(Dataset):"""class for wrapping a MNIST segment."""def __init__(self, gas, segment_name, transform, cache=True):super().__init__()self.dataset = TensorBayDataset("MNIST", gas)if cache:self.dataset.enable_cache("data")self.segment = self.dataset[segment_name]self.category_to_index = self.dataset.catalog.classification.get_category_to_index()self.transform = transformdef __len__(self):return len(self.segment)def __getitem__(self, idx):data = self.segment[idx]with data.open() as fp:image_tensor = self.transform(Image.open(fp))return image_tensor, self.category_to_index[data.label.classification.category]def create_loader(key):to_tensor = transforms.ToTensor()normalization = transforms.Normalize(mean=[0.485], std=[0.229])my_transforms = transforms.Compose([to_tensor, normalization])train_segment = MNISTSegment(GAS(key), segment_name="train", transform=my_transforms)train_dataloader = DataLoader(train_segment, batch_size=4, shuffle=True, num_workers=0)test_segment = MNISTSegment(GAS(key), segment_name="test", transform=my_transforms)test_dataloader = DataLoader(test_segment, batch_size=4, shuffle=True, num_workers=0)return train_dataloader, test_dataloaderdef train(args, model, device, train_loader, optimizer, epoch):model.train()for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = F.cross_entropy(output, target)loss.backward()optimizer.step()if batch_idx % args.log_interval == 0:print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(epoch, batch_idx * len(data), len(train_loader.dataset),100. * batch_idx / len(train_loader), loss.item()))if args.dry_run:breakdef test(model, device, test_loader):model.eval()test_loss = 0correct = 0with torch.no_grad():for data, target in test_loader:data, target = data.to(device), target.to(device)output = model(data)test_loss += F.cross_entropy(output, target, reduction='sum').item()  # sum up batch losspred = output.argmax(dim=1, keepdim=True)  # get the index of the max log-probabilitycorrect += pred.eq(target.view_as(pred)).sum().item()test_loss /= len(test_loader.dataset)print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(test_loss, correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))def main():# Training settingsparser = argparse.ArgumentParser(description='PyTorch MNIST')parser.add_argument('--batch-size', type=int, default=64, metavar='N',help='input batch size for training (default: 64)')parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',help='input batch size for testing (default: 1000)')parser.add_argument('--epochs', type=int, default=14, metavar='N',help='number of epochs to train (default: 14)')parser.add_argument('--lr', type=float, default=1.0, metavar='LR',help='learning rate (default: 1.0)')parser.add_argument('--gamma', type=float, default=0.7, metavar='M',help='Learning rate step gamma (default: 0.7)')parser.add_argument('--no-cuda', action='store_true', default=False,help='disables CUDA training')parser.add_argument('--dry-run', action='store_true', default=False,help='quickly check a single pass')parser.add_argument('--seed', type=int, default=1, metavar='S',help='random seed (default: 1)')parser.add_argument('--log-interval', type=int, default=10, metavar='N',help='how many batches to wait before logging training status')parser.add_argument('--save-model', action='store_true', default=False,help='For Saving the current Model')args = parser.parse_args()use_cuda = not args.no_cuda and torch.cuda.is_available()torch.manual_seed(args.seed)device = torch.device("cuda" if use_cuda else "cpu")train_kwargs = {'batch_size': args.batch_size}test_kwargs = {'batch_size': args.test_batch_size}if use_cuda:cuda_kwargs = {'num_workers': 1,'pin_memory': True,'shuffle': True}train_kwargs.update(cuda_kwargs)test_kwargs.update(cuda_kwargs)ACCESS_KEY = 'Accesskey-4669e1203a6fa8291d5d7744ba313f91'train_loader, test_loader = create_loader(ACCESS_KEY)model = Net().to(device)optimizer = optim.Adadelta(model.parameters(), lr=args.lr)scheduler = StepLR(optimizer, step_size=1, gamma=args.gamma)for epoch in range(1, args.epochs + 1):train(args, model, device, train_loader, optimizer, epoch)test(model, device, test_loader)scheduler.step()if args.save_model:torch.save(model.state_dict(), "outputs/mnist.pt")if __name__ == '__main__':main()

运行方式:

打开终端

mkdir -p outputs
python 1.train.py --save-model

运行结果:

Train Epoch: 1 [0/60000 (0%)] Loss: 2.305400
Train Epoch: 1 [640/60000 (1%)] Loss: 1.359776
....

训练完毕,在outputs文件下有mnist.pt模型文件。

02 参数转换

Pytorch模型导出为ONNX

“各家都有一套语言,换到一个标准再说话”。

开放神经网络交换(Open Neural Network Exchange,简称ONNX)是微软和Facebook提出用来表示深度学习模型的开放格式。所谓开放就是ONNX定义了一组和环境,平台均无关的标准格式,来增强各种AI模型的可交互性。

换句话说,无论你使用何种训练框架训练模型(比如TensorFlow /Pytorch /OneFlow /Paddle),在训练完毕后你都可以将这些框架的模型统一转换为ONNX这种统一的格式进行存储。注意ONNX文件不仅仅存储了神经网络模型的权重,同时也存储了模型的结构信息以及网络中每一层的输入输出和一些其它的辅助信息。

ONNX开源在了[5],笔者也为大家准备了一些参考学习资料,可参考[5-10]进行学习。

Pytorch平台训练好模型后,使用下列脚本将模型转化为ONNX:

首先安装用到的包为:

pip install onnx coremltools onnx-simplifier

脚本名称:2.pt2onnx.py

from models.net import Net
import torch.onnx
import osdef parse_args():import argparseparser = argparse.ArgumentParser(description='PyTorch pt file to ONNX file')parser.add_argument('-i', '--input', type=str, required=True)return parser.parse_args()def main():args = parse_args()dummy_input = torch.randn(1, 1, 28, 28)model = Net()print("Loading state dict to cpu")model.load_state_dict(torch.load(args.input, map_location=torch.device('cpu')))name = os.path.join(os.path.dirname(args.input), os.path.basename(args.input).split('.')[0] + ".onnx")print("Onnx files at:", name)torch.onnx.export(model, dummy_input, name)if __name__ == '__main__':main()

运行方式:

python3 2.pt2onnx.py -i outputs/mnist.pt

运行结果:

Loading state dict to cpu
Onnx files at: outputs/mnist.onnx

运行完毕,在outputs文件可发现已将pt模型转为mnist.onnx模型文件。

ONNX模型转化成KModel

使用NNCASE将ONNX模型转为K210上能运行的KModel,由于NNCASE环境配置较为依赖环境,我们使用Docker完成环境配置,对于docker只需要安装并正确配置即可,不熟悉的可以参考我写的Docker教程[12]。

代码文件:3.onnx2kmodel.py

import os
import onnxsim
import onnx
import nncasedef parse_args():import argparseparser = argparse.ArgumentParser(description='ONNX file to KModel')parser.add_argument('-i', '--input', type=str, required=True)return parser.parse_args()def parse_model_input_output(model_file):onnx_model = onnx.load(model_file)input_all = [node.name for node in onnx_model.graph.input]input_initializer = [node.name for node in onnx_model.graph.initializer]input_names = list(set(input_all) - set(input_initializer))input_tensors = [node for node in onnx_model.graph.input if node.name in input_names]# inputinputs = []for _, e in enumerate(input_tensors):onnx_type = e.type.tensor_typeinput_dict = {}input_dict['name'] = e.nameinput_dict['dtype'] = onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[onnx_type.elem_type]input_dict['shape'] = [(i.dim_value if i.dim_value != 0 else d) for i, d in zip(onnx_type.shape.dim, [1, 3, 224, 224])]inputs.append(input_dict)return onnx_model, inputsdef onnx_simplify(model_file):onnx_model, inputs = parse_model_input_output(model_file)onnx_model = onnx.shape_inference.infer_shapes(onnx_model)input_shapes = {}for input in inputs:input_shapes[input['name']] = input['shape']onnx_model, check = onnxsim.simplify(onnx_model, input_shapes=input_shapes)assert check, "Simplified ONNX model could not be validated"model_file = os.path.join(os.path.dirname(model_file), 'simplified.onnx')onnx.save_model(onnx_model, model_file)return model_filedef read_model_file(model_file):with open(model_file, 'rb') as f:model_content = f.read()return model_contentdef main():args = parse_args()model_file = args.inputtarget = 'k210'# onnx simplifymodel_file = onnx_simplify(model_file)# compile_optionscompile_options = nncase.CompileOptions()compile_options.target = targetcompile_options.dump_ir = Truecompile_options.dump_asm = Truecompile_options.dump_dir = 'tmp'# compilercompiler = nncase.Compiler(compile_options)# import_optionsimport_options = nncase.ImportOptions()# importmodel_content = read_model_file(model_file)compiler.import_onnx(model_content, import_options)# compilecompiler.compile()# kmodelkmodel = compiler.gencode_tobytes()name = os.path.basename(model_file).split(".")[0]with open(f'{name}.kmodel', 'wb') as f:f.write(kmodel)if __name__ == '__main__':main()

运行方式:

为简化部署方式,在部署好NNCASE的Docker镜像中直接运行Python脚本。首先用Docker拉取NNCASE镜像,再进入到镜像中运行Python代码

# 拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/kendryte/nncase:latest
# 进入到容器内部
docker run -it --rm -v `pwd`:/mnt -w /mnt registry.cn-hangzhou.aliyuncs.com/kendryte/nncase:latest /bin/bash -c "/bin/bash"
# 运行
python3 3.onnx2kmodel.py -i outputs/mnist.onnx

运行结果:

1. Import graph...
2. Optimize target independent...
3. Optimize target dependent...
5. Optimize target dependent after quantization...
6. Optimize modules...
7.1. Merge module regions...
7.2. Optimize buffer fusion...
7.3. Optimize target dependent after buffer fusion...
8. Generate code...
WARN: Cannot find a decompiler for section .rdata
WARN: Cannot find a decompiler for section .textSUMMARY
INPUTS
0       input.1 f32[1,1,28,28]
OUTPUTS
0       18      f32[1,10]MEMORY USAGES
.input     3.06 KB      (3136 B)
.output   40.00 B       (40 B)
.data    313.00 KB      (320512 B)
MODEL      4.58 MB      (4802240 B)
TOTAL      4.89 MB      (5125928 B)

运行完毕,在outputs文件下有mnist.kmodel文件,这个文件。

代码讲解

(1)首先使用onnx-simplifier简化ONNX模型

为什么要简化?这是因为在训练完深度学习的pytorch或者tensorflow模型后,有时候需要把模型转成 onnx,但是很多时候,很多节点比如cast节点,Identity 这些节点可能都不需要,我们需要进行简化[11],这样会方便我们后续在嵌入式平台部署。onnx-simplifier的开源地址见[9]。

主要代码为

def onnx_simplify(model_file):onnx_model, inputs = parse_model_input_output(model_file)onnx_model = onnx.shape_inference.infer_shapes(onnx_model)input_shapes = {}for input in inputs:input_shapes[input['name']] = input['shape']onnx_model, check = onnxsim.simplify(onnx_model, input_shapes=input_shapes)assert check, "Simplified ONNX model could not be validated"model_file = os.path.join(os.path.dirname(model_file), 'outputs/simplified.onnx')onnx.save_model(onnx_model, model_file)return model_file

(2)使用NNCASE转换ONNX参数,核心代码为

model_content = read_model_file(model_file)compiler.import_onnx(model_content, import_options)# compilecompiler.compile()# kmodelkmodel = compiler.gencode_tobytes()

这一步时,离部署到嵌入式不远了,那么我们继续来看K210的部分。

03 K210开发环境

开发这个K210的姿势总结如下有三种:(1)使用Micropython固件开发 (2)使用standalone SDK 进行开发 (3)使用FreeRTOS进行开发。

K210是支持好几种编程环境的,从最基本的cmake命令行开发环境 ,到IDE开发环境,到Python脚本式开发环境都支持,这几种开发方式没有优劣之分,有的人喜欢用命令行+vim,有的人喜欢IDE图形界面,也有的人根本不关心编译环境,觉得人生苦短只想写Python。

一般来说越基础的开发方式自由度越大,比如C语言+官方库,能充分发挥出芯片的各种外设功能,但同时开发难度比较高,过程很繁琐;越顶层的开发方式比如写脚本,虽然十分地便捷,甚至连下载程序的过程都不需要了,但是程序功能的实现极度依赖于MicroPython的API更新,且很多高级系统功能无法使用[2]。

为降低大家在开发中的不友好度,本文介绍第一种开发方法,以后有机会可以介绍使用C SDK直接进行开发。不管用什么系统都能进行K210的开发,我们需要完成下列内容的准备:

(1)将K210通过USB连入你的电脑

(2)CH340驱动已安装

(3)cmake

(4)kflash或kflash GUI,一个烧录程序用以将编译好的.bin文件烧录到硬件中。前者命令行,后者图形界面

(5)Micropython固件

第一步 下载Micropython固件。到https://dl.sipeed.com/shareURL/MAIX/MaixPy/release/master 下载一个bin文件,这里笔者使用的是minimum_with_kmodel_v4_support

wget https://dl.sipeed.com/fileList/MAIX/MaixPy/release/master/maixpy_v0.6.2_72_g22a8555b5/maixpy_v0.6.2_72_g22a8555b5_minimum_with_kmodel_v4_support.bin

第二步 查看K210的串口号。以笔者使用的MacOS为例:

ls /dev/cu.usbserial-*
# /dev/cu.usbserial-14330

第三步 烧录。

使用命令行进行烧录示例:

kflash  -p /dev/cu.usbserial-14330 -b 115200 -t maixpy_v0.6.2_72_g22a8555b5_minimum_with_kmodel_v4_support.bin

笔者比较懒,不想每次指定串口号,所以直接用/dev/cu.usbserial-* 。如果你电脑只有一个以/dev/cu.usbserial 开头的串口,那就不用指定,直接用我这种方法:

kflash  -p /dev/cu.usbserial-* -b 115200 -t *.bin

到这里,如果没有问题的话,说明你已经成功在K210上部署了。怎么使用其进行编程呢?笔者建议读完[16],更多信息可以从参考Micropython的文档[14]和Github[15]。

04 硬件平台部署

KModel制作好,接下来需要把KModel部署到硬件 。有两种方式,下面分别介绍,任选一种即可。

方法一:将Model烧录到Flash中

打开Kflash GUI,配置如下烧录到0x300000地址

方法二:将KModel放在SD卡中

直接将KModel放在SD卡中即可,注意TF卡需要是MBR 分区 FAT32 格式[17]。如果不是这个格式,是加载不到SD卡的。在Mac中,可以打开磁盘管理工具–>选择对应磁盘–>抹掉–>格式–>MS-DOS(FAT)直接一步格式化。

最终运行

代码文件:4.k210_main.py

import sensor, lcd, image
import KPU as kpulcd.init(invert=True)
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))  # set to 224x224 input
sensor.set_hmirror(0)  # flip camera
task = kpu.load(0x300000)  # load model from flash address 0x200000
a = kpu.set_outputs(task, 0, 10, 1, 1)
sensor.run(1)
while True:img = sensor.snapshot()lcd.display(img, oft=(0, 0))  # display large pictureimg1 = img.to_grayscale(1)  # convert to grayimg2 = img1.resize(32, 32)  # resize to mnist input 32x32a = img2.strech_char(1)  # preprocessing pictures, eliminate dark cornerlcd.display(img2, oft=(240, 32))  # display small 32x32 picturea = img2.pix_to_ai();  # generate data for aifmap = kpu.forward(task, img2)  # run neural network modelplist = fmap[:]  # get result (10 digit's probability)pmax = max(plist)  # get max probabilitymax_index = plist.index(pmax)  # get the digitlcd.draw_string(224, 0, "%d: %.3f" % (max_index, pmax), lcd.WHITE, lcd.BLACK)

运行方式:

有多种方式,下面介绍两种,更多方式可参考:https://wiki.sipeed.com/soft/maixpy/zh/get_started/get_started_upload_script.html

方式1:rshell

正如使用 linux 终端一样, 使用 rshell 的 cp 命令即可简单地复制文件到开发板

按照 rshell 项目主页的说明安装好 rshell

sudo apt-get install python3-pip
sudo pip3 install rshell
rshell -p /dev/ttyUSB1 # 这里根据实际情况选择串口

Copy

ls /flash
cp ./test.py /flash/ #复制电脑当前目录的文件 test.py

方式2:SD 卡自动拷贝到 Flash 文件系统

为了方便将 SD 卡的内容拷贝到 Flash 文件系统, 只需要将要拷贝到 Flash 文件系统的文件重命名为cover.boot.py 或者cover.main.py, 然后放到SD卡根目录, 开发板断电插入SD卡,然后开发板上电, 程序就会自动将这两个文件拷贝到/flash/boot.py或者/flash/main.py,这样就算后面取出了SD卡,程序已经在 /flash/boot.py或者/flash/main.py

看看部署到K210的识别效果:

五、总结

本文是笔者折腾嵌入式AI的一篇实践,涵盖以下内容:

  • 视觉模型的数据准备、网络设计、训练

  • ONNX基本知识与ONNX的简化

  • 使用AI编译器NNCASE将ONNX转化为KModel

  • Micropython + KModel在K210上的部署

本文方法由于在Micropython上进行模型加载,在系统资源调用以及软件适配上会有许多限制。做成产品时建议用C+K210 SDK开发。由于篇幅限制,下篇将探索使用C语言对模型进行部署,欢迎关注更新!由于个人能力有限,如有疑问以及勘误欢迎和笔者进行交流:Github/QiangZiBro!

本文使用到的代码都托管在这个仓库里,大家可以自由查看:https://github.com/QiangZiBro/pytorch-k210.git

参考资料(5-10是为读者准备的参考学习资料)

[ 1 ] : 模型部署的场景、挑战和技术方案 https://zhuanlan.zhihu.com/p/387575188

[ 2 ] : 嵌入式AI从入门到放肆【K210篇】-- 硬件与环境 https://zhuanlan.zhihu.com/p/81969854

[ 3 ] : encase https://github.com/kendryte/nncase

[ 4 ] : How to integrate MNIST with Pytorch    https://tensorbay-python-sdk.graviti.com/en/stable/integrations/pytorch.html

[ 5 ] : ONNX Github https://github.com/onnx/onnx

[ 6 ] : ONNX学习笔记 https://zhuanlan.zhihu.com/p/346511883

[ 7 ] : ONNX 教程 https://github.com/onnx/tutorials

[ 8 ] : ONNX 预训练SOTA模型  https://github.com/onnx/models

[ 9 ] : ONNX简化器 https://github.com/daquexian/onnx-simplifier

[ 10 ] :  nncase Github  https://github.com/kendryte/nncase

[ 11 ] : https://mp.weixin.qq.com/s/OTUSDSqGTgTJ6-KA-o0rQw

[ 12 ] : https://github.com/QiangZiBro/learn-docker-in-a-smart-way

[ 13 ] : 训练好的深度学习模型原来这样部署的!https://mp.weixin.qq.com/s/tqSmFcR-aQjDhaEyQBzeUA

[ 14 ] : https://wiki.sipeed.com/soft/maixpy/zh/index.html

[ 15 ] : https://github.com/sipeed/MaixPy

[ 16 ] : 编辑并执行文件 https://wiki.sipeed.com/soft/maixpy/zh/get_started/get_started_edit_file.html

[ 17 ] : https://wiki.sipeed.com/soft/maixpy/zh/others/maixpy_faq.html#Micro-SD-%E5%8D%A1%E8%AF%BB%E5%8F%96%E4%B8%8D%E5%88%B0

[ 18 ] : https://wiki.sipeed.com/soft/maixpy/zh/get_started/get_started_upload_script.html

点击阅读原文直接获取数据集

我用深度学习做个视觉AI微型处理器!相关推荐

  1. 劲爆!有人不用深度学习做强AI?道翰天琼认知智能机器人平台API接口大脑为您揭秘

    劲爆!有人不用深度学习做强AI?道翰天琼认知智能机器人平台API接口大脑为您揭秘 认知智能是计算机科学的一个分支科学,是智能科学发展的高级阶段,它以人类认知体系为基础,以模仿人类核心能力为目标,以信息 ...

  2. 上海交大发布「人类行为理解引擎」:深度学习+符号推理,AI逐帧理解大片中每个动作...

    博雯 发自 凹非寺 量子位 | 公众号 QbitAI 看图看片,对现在的AI来说早已不是什么难事. 不过让AI分析视频中的人类动作时,传统基于目标检测的方法会碰到一个挑战: 静态物体的模式与行为动作的 ...

  3. 【百家稷学】深度学习与嵌入式平台AI实践(北京交通大学实训)

    继续咱们百家稷学专题,本次是有三AI在北京交通大学进行的暑期课程教学.百家稷学专题的目标,是走进100所高校和企业进行学习与分享. 分享主题 本次分享是在北京交通大学计算机与信息技术学院进行,主题是& ...

  4. Yoshua Bengio首次中国演讲:深度学习通往人类水平AI的挑战

    11 月 7 日,Yoshua Bengio 受邀来到北京参加第二十届「二十一世纪的计算」国际学术研讨会.会上以及随后受邀前往清华时,他给出了题为「深度学习通往人类水平 AI 的挑战」(Challen ...

  5. TorchFusion 是一个深度学习框架,主要用于 AI 系统加速研究和开发

    TorchFusion 是一个深度学习框架,主要用于 AI 系统加速研究和开发. TorchFusion 基于 PyTorch 并且完全兼容纯 PyTorch 和其他 PyTorch 软件包,它供了一 ...

  6. 如何用深度学习做自然语言处理?这里有份最佳实践清单

    如何用深度学习做自然语言处理?这里有份最佳实践清单 By 机器之心2017年7月26日 14:16 对于如何使用深度学习进行自然语言处理,本文作者 Sebastian Ruder 给出了一份详细的最佳 ...

  7. Amazon 首席科学家李沐亲授「深度学习」,2019 AI ProCon震撼来袭!(日程出炉)...

    2019年9月5-7日,面向AI技术人的年度盛会-- 2019 AI开发者大会 AI ProCon,火热来袭!  继2018 年由CSDN成功举办AI 开发者大会一年之后,全球AI市场正发生着巨大的变 ...

  8. 【干货】怎样用深度学习做语音识别

    [新智元导读]吴恩达曾经预测当语音识别的准确率从95%上升到99%时,语音识别将会成为人类与计算机交互的新方式.归功于深度学习,这4%的准确率的提升使得语音识别从难以实际应用的技术变成有无限的应用潜力 ...

  9. 用深度学习做命名实体识别(五)-模型使用

    通过本文,你将了解如何基于训练好的模型,来编写一个rest风格的命名实体提取接口,传入一个句子,接口会提取出句子中的人名.地址.组织.公司.产品.时间信息并返回. 核心模块entity_extract ...

最新文章

  1. Deep Manta:单目图像下2d到3d由粗到精的多任务网络车辆分析
  2. 聚类热图怎么按自己的意愿调整分支的顺序?
  3. laravel 5.1 Model 属性详解
  4. python docx 设置字体_python docx 中文字体设置的操作方法
  5. Windows界面编程-背景图片、透明特效使用
  6. 【转】The test form is only available for requests from the local machine 解决方法
  7. 专访20年技术老兵云郎:16年峰回路,每一步都是更好的沉淀
  8. sis地址获取器_TencentOS tiny深度源码分析(2)—— 调度器
  9. 《量子计算机简史》--摘
  10. [算法练习] 把字符串转换成整数
  11. ECHAR学习-Part1 文字特效
  12. 中兴WCDMA模块 Linux拨号流程
  13. 计算机课ppt插入图片,ppt2010官方基础教程:插入照片-powerpoint技巧-电脑技巧收藏家...
  14. 使用Excel时计算剩余时间
  15. 北京信息科技大学第十二届程序设计竞赛暨ACM选拔赛
  16. 第一个将Palette Mode引入VVC,阿里云在JVET会议上引起关注
  17. 视频教程-沐风老师3DMAX室内建模挤出法详解-3Dmax
  18. windows下mysql8初始化
  19. PPIO边缘云聚焦音视频底层技术,探索元宇宙“登月工程”
  20. 计算机科学应用论文题目,比较好写的计算机科学与应用论文题目 计算机科学与应用论文题目怎么取...

热门文章

  1. Python什么是状态机,状态机的简单应用
  2. 天气API接口大全【转】
  3. 自然语言处理在eBay的技术实践
  4. 张兴华:云端架构助力企业快速成长
  5. Google AudioSet-谷歌语音数据集如何解析
  6. 计算机word软件学后的总结,计算机office教学总结范文
  7. Python用turtle画爱心丘比特之剑
  8. CVPR 2017 paper
  9. 吉首大学第八届“新星杯”大学生程序设计大赛部分解题报告
  10. 怎么把ubuntu系统从英文修改为中文界面