作者:张强,Datawhale成员

讲多了算法,如何真正将算法利用到产品畛域?本文将带你从0用深度学习打造一个视觉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获取所有数据集。

咱们应用一个开源数据集平台:gas.graviti.com ,这个网站汇总了AI开发者常见的公开数据集,调用其SDK就能间接在线训练,而且许多数据集间接在国内网络下连贯间接应用,还是十分不便的。

a. 关上本文对应数据集链接 https://gas.graviti.com/datas...
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 Image\\from tensorbay import GAS\from tensorbay.dataset import Dataset\from tensorbay.dataset import Segment\\def 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 transforms\\from tensorbay import GAS\from tensorbay.dataset import Dataset as TensorBayDataset\\\class 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 = transform\\    def __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_dataloader\\\def 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:\                break\                \\def test(model, device, test_loader):\    model.eval()\    test_loss = 0\    correct = 0\    with 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 loss\            pred = output.argmax(dim=1, keepdim=True)  # get the index of the max log-probability\            correct += 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 settings\    parser = 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 outputspython 1.train.py --save-model

运行后果:

Train Epoch: 1 [0/60000 (0%)]    Loss: 2.305400Train 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 Netimport torch.onnximport osdef parse_args():    import argparse    parser = 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 cpuOnnx 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 nncase\\\def parse_args():\    import argparse\    parser = 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]\\    # input\    inputs = []\    for _, e in enumerate(input_tensors):\        onnx_type = e.type.tensor_type\        input_dict = {}\        input_dict['name'] = e.name\        input_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, inputs    \\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), 'simplified.onnx')\    onnx.save_model(onnx_model, model_file)\    return model_file\    \\def read_model_file(model_file):\    with open(model_file, 'rb') as f:\        model_content = f.read()\    return model_content\\\def main():\    args = parse_args()\    model_file = args.input\    target = 'k210'\\    # onnx simplify\    model_file = onnx_simplify(model_file)\\    # compile_options\    compile_options = nncase.CompileOptions()\    compile_options.target = target\    compile_options.dump_ir = True\    compile_options.dump_asm = True\    compile_options.dump_dir = 'tmp'\\    # compiler\    compiler = nncase.Compiler(compile_options)\\    # import_options\    import_options = nncase.ImportOptions()\\    # import\    model_content = read_model_file(model_file)\    compiler.import_onnx(model_content, import_options)\\    # compile\    compiler.compile()\\    # kmodel\    kmodel = 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 .rdataWARN: Cannot find a decompiler for section .textSUMMARYINPUTS0       input.1 f32[1,1,28,28]OUTPUTS0       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)    # compile    compiler.compile()    # kmodel    kmodel = 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/shareUR...下载一个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, imageimport 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 inputsensor.set_hmirror(0)  # flip cameratask = kpu.load(0x300000)  # load model from flash address 0x200000a = kpu.set_outputs(task, 0, 10, 1, 1)sensor.run(1)while True:    img = sensor.snapshot()    lcd.display(img, oft=(0, 0))  # display large picture    img1 = img.to_grayscale(1)  # convert to gray    img2 = img1.resize(32, 32)  # resize to mnist input 32x32    a = img2.strech_char(1)  # preprocessing pictures, eliminate dark corner    lcd.display(img2, oft=(240, 32))  # display small 32x32 picture    a = img2.pix_to_ai();  # generate data for ai    fmap = kpu.forward(task, img2)  # run neural network model    plist = fmap[:]  # get result (10 digit's probability)    pmax = max(plist)  # get max probability    max_index = plist.index(pmax)  # get the digit    lcd.draw_string(224, 0, "%d: %.3f" % (max_index, pmax), lcd.WHITE, lcd.BLACK)

运行形式:
有多种形式,上面介绍两种,更多形式可参考:https://wiki.sipeed.com/soft/...

形式1:rshell
正如应用 linux 终端一样, 应用 rshell 的 cp 命令即可简略地复制文件到开发板

依照 rshell 我的项目主页的阐明装置好 rshell

sudo apt-get install python3-pipsudo pip3 install rshellrshell -p /dev/ttyUSB1 # 这里依据理论状况抉择串口

Copy

ls /flashcp ./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了

五、总结

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

  • 视觉模型的数据筹备、网络设计、训练
  • ONNX基本知识与ONNX的简化
  • 应用AI编译器NNCASE将ONNX转化为KModel
  • Micropython + KModel在K210上的部署

本文办法因为在Micropython上进行模型加载,在系统资源调用以及软件适配上会有许多限度。做成产品时倡议用C+K210 SDK开发。因为篇幅限度,下篇将摸索应用C语言对模型进行部署,欢送关注更新!因为集体能力无限,如有疑难以及勘误欢送和笔者进行交换:Github/QiangZiBro!

本文应用到的代码都托管在这个仓库里,大家能够自在查看:https://github.com/QiangZiBro...

参考资料(5-10是为读者筹备的参考学习材料)
[ 1 ] : 模型部署的场景、挑战和技术计划 https://zhuanlan.zhihu.com/p/...

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

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

[ 4 ] : How to integrate MNIST with Pytorch https://tensorbay-python-sdk....

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

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

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

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

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

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

[ 11 ] : https://mp.weixin.qq.com/s/OT...

[ 12 ] : https://github.com/QiangZiBro...

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

[ 14 ] : https://wiki.sipeed.com/soft/...

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

[ 16 ] : 编辑并执行文件 https://wiki.sipeed.com/soft/...

[ 17 ] : https://wiki.sipeed.com/soft/...

[ 18 ] : https://wiki.sipeed.com/soft/...

更多信息请拜访格物钛官网