K210:kmodel避坑指南

模型就是一种数据结构,它储存了输入映射到输出的数学表示,换句话说,模型储存了一个神经网络的形状以及网络中的参数。通常,这个模型的数据可以被保存成文件,比如以.h5.tflite.kmodel为后缀等的文件,这些文件都是用来阐述某个模型的形状结构和参数,只是由不同软件使用。

由于K210这款芯片的用户群体相对较小,相应的资料与教程也大都并不完整,因此我在训练一个能够在K210上运行的模型(kmodel)的过程中踩了很多坑。为了帮助到像我一样可能正在因此而饱受困扰的初学者,所以我在这里尽我可能详细的介绍了从搭建模型模型转换最后应用模型的全过程,而由于我的应用项目为循迹车,故下面的介绍都以这个项目为例子。

运行环境

由于机器学习仍在不断地快速发展中,相关的软件工具也在不断地跟新迭代,而一些大版本的更新可能会导致先前的教程失效,因此在这里给出我目前所使用的各个相关工具的版本如下:

  • K210 开发板 MaixBit
    • MaixPy 固件版本 v0.6.2_73 minimum with kmodel v4 support
  • Windows
    • Python 3.8.3
    • TensorFlow/Keras 2.7.0
    • CUDA v11.5
    • cuDNN 8.3.1
  • Google Colab
    • Python 3.7.13
    • TensorFlow/Keras 2.8.0
  • NNCase v0.2.0 Beta4

这里需要特别指出的是目前最新的 MaixPy 固件仅支持 kmodel v3/v4,而如果使用最新的模型转换工具 NNCase 转换出来的模型则是 kmodel v5,而使用 kmodel v5 在 K210上 使用则会出现莫名其妙的错误,因此这里使用的是最后一个支持输出为 kmodel v4 的 NNCase v0.2.0 Beta4 版本。

搭建模型

约束条件

模型要能在 MaixPy 的程序里面被使用, 首先需要程序能够理解 .kmodel 这个格式的文件, 并且支持模型里面的算法,这样才能按照模型的描述将输入经过一些裂计算过程后得到输出。所以,重点就是支持模型里面的算法,这被称为算子。在 MaixPy 里面,已经集成了推导模型的代码,同时使用了 KPU 进行计算加速,使用时无需编写很多代码,只需要调用几个函数即可快速运行模型。但是由于成本、时间、功耗、体积、发热、应用领域定位等各种因素,KPU 的能力并不能像专业领域的强力 NPU 一样包含了每一种算子。

K210中的 KPU 实现了 卷积、批归一化、激活、池化 这 4 种基础操作的硬件加速, 但是它们不能分开单独使用,是一体的加速模块。所以, 在 KPU 上面推理模型,需满足以下几个要求。

内存限制

K210 有 6MB 通用 RAM 和 2MB KPU 专用 RAM,模型的输入和输出特征图存储在 2MB KPU RAM 中,权重和其他参数存储在 6MB 通用 RAM 中。但要注意的是,并不是所有 6MB RAM 都可以被模型使用,这 6MB 空间是由 MaixPy 固件、我们所编写的其他程序功能、模型本身以及模型在运算中所消耗的内存所共同使用的,因此选择使用较小的固件版本和尽可能在我们所编写的代码中减少对内存的消耗都有助于运行更大的网络模型,而更大的网络模型通常也就意味着更复杂的功能以及更高的准确率。

完全加速约束条件

  • 输入特征图小于等于 320×240 (宽x高) 同时输出特征图大于等于 4×4 (宽x高),通道数在 1 到 1024之间;
  • Same 对称 paddings (TensorFlow 在 stride=2 同时尺寸为偶数时使用非对称 paddings);
  • 普通 Conv2D 和 DepthwiseConv2D,卷积核为 1×1 或 3×3,stride 为 1 或 2;
  • 最大池化 MaxPool (2×2 或 4×4) 和 平均池化 AveragePool (2×2 或 4×4);
  • 任意逐元素激活函数 (ReLU, ReLU6, LeakyRelu, Sigmoid…), KPU 不支持 PReLU。

部分加速约束条件

  • 非对称 paddings 或 valid paddings 卷积, NNCase 会在其前后添加必要的 Pad 和 Crop(可理解为边框与裁切);
  • 普通 Conv2D 和 DepthwiseConv2D,卷积核为 1×1 或 3×3,但 stride 不是 1 或 2;
  • NNCase 会把它分解为 KPUConv2D 和一个 StridedSlice (可能还需要 Pad);
  • MatMul 算子, NNCase 会把它替换为一个 Pad (4×4) + KPUConv2D (1×1 卷积核) + Crop (1×1);
  • TransposeConv2D 算子, NNCase 会把它替换为一个 SpaceToBatch + KPUConv2D + BatchToSpace。

MobileNet 的使用

由于在我的项目中使用 MobileNet 作为模型架构,但原生的 MobileNet 并不能被 KPU 完全加速,为了使其满足 KPU 的加速条件需对其做做一些修改。这里给出了详细说明并给出了修改后的程序,但由于我所使用的 TensorFlow/Keras 的版本不同,其中一些模块已经发生变化,故我又在其基础上又进行了一些修改,以使其可以在我的环境中正常运行。修改后的问价可以在这里找到,将这整个文件目录与我们所编写的程序文件放在同一个目录下,便可像导入普通模块一样使用。具体操作如下,或可以在这里查看完整使用过程。

from keras_applications.mobilenet import MobileNet

关于归一化

在搭建模型时通常我们会在模型中的第一层对输入做归一化处理,但对于 kmodel 来说,如果其输入为 uint8 (例如 RGB888 图像) ,则不需要做归一化处理。但我们在对模型进行训练的过程中仍然需要对输入进行归一化处理,也就是说归一化操作不应在模型中进行,而应对输入的训练数据进行预处理,最后在使用训练并转换后得到的 kmodel 时只需直接将图像作为输入即可。

图像预处理过程如下:

def process_image(img):
    img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, (160, 160))
    img = np.asarray(img)
    img = img.astype('float32')
    img /= 255
    return img

更详细的使用介绍见 NNCase 常见问题

模型转换

由于 K210 仅支持 .kmodel 格式的模型文件,而我们直接训练出来的文件并不是这个格式,因此需要对训练后得到的模型文件进行转换。这里,我使用的是 Keras 完成的模型搭建以及训练过程,最终生成的模型文件为 .h5 格式,但勘智的模型转化工具 NNCase 并不支持从 .h5.kmodel 的直接转换,故下面以 .h5 为例,介绍了 .h5->.tflite->.kmodel 的全过程。

.h5 转为 .tflite

目前 NNCase 仅支持 TensorFlow Lite、Caffe、ONNX 这三种模型,因此需要先将 Keras 训练出来的 .h5 转换为 TensorFlow Lite 的 .tflite 格式。具体转换方式有如下两种,分别是使用 TensorFlow 的 Python API 和命令行工具两种方式,这两种方式均可,可以在这里查看详细介绍。

Python API

import TensorFlow as tf

model = tf.keras.models.load_model("model.h5")
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
with open("model.tflite", "wb") as f:
    f.write(tflite_model)

命令行工具

$ tflite_convert  --output_file=model.tflite --keras_model_file=model.h5

其中 model.h5 为 Keras 训练得到的模型文件,model.tflite 为转换后的模型文件。

需要注意的是,在转换过程中不可以使用类似下面这种 TensorFlow 的量化操作

converter.optimizations = [tf.lite.Optimize.DEFAULT]

否则在使用 NNCase 将 .tflite 模型转换为 .kmodel 模型的过程中会出现如下错误。

Fatal: Invalid tesnor type

更详细的使用介绍见 TensorFlow Lite 转换器

.tflite 转为 .kmodel

.tflite.kmodel 的转换过程由勘智提供的转换工具 NNCase 完成,并且出于上文所说的原因这里使用 NNCase 的 0.2.0 Beta4 版本。由于 NNCase 在转换过程中会对模型进行量化,简单来说就是通过将模型中的浮点型权重参数变为整型参数以达到缩减模型大小的目的,因此需要提供量化校准集来量化模型,校准集通常就是从训练集中选择几百到上千个数据存放到一个目录中,校准集数据文件可以随意命名,NNCase 会自动调用 OpenCV 读取目录下的所有数据。下面分别为在 Linux 环境下以及在 Windows 环境下的转换命令,其中 model.tflite 为 TensorFlow Lite 模型文件,model.kmodel 为转换后的 kmodel 文件,calibrate 为校准集目录名。

Linux

$ chmod +x ./ncc
$ ./ncc compile model.tflite model.kmodel -i tflite -o kmodel -t k210 --dataset calibrate

Windows

> ncc.exe compile model.tflite model.kmodel -i tflite -o kmodel -t k210 --dataset calibrate

需要注意的是编译模型时,NNCase 将打印运行模型所需的工作内存。通常模型会加载到 6MB RAM,因此总的主内存使用量是工作内存 + 模型的大小。

更详细的使用介绍见 NNCase 使用说明

应用模型

经过搭建、训练、转换过后的 kmodel 模型文件就可以被实际应用在项目中去,具体使用 kmodel 的方式有两种,最直接的便是在 K210 上实际运行,但也可以在 PC 上模拟运行以方便对模型进行调试,下面将分别介绍这两种使用方式。

PC

使用 NNCase 的 infer 命令可以运行 kmodel,NNCase 会将模型的输出张量按 NCHW 布局保存到 .bin 文件,具体 NCHW 是什么这里不进行介绍,简单来说就是对数据储存顺序的一种规定。在 Linux 环境和 Windows 环境下的使用方式如下,其中 model.kmodel 为 kmodel 模型文件,kmodel_infer 为使用 kmodel 对输入数据推理得到的 .bin 文件储存目录,calibrate 为需要进行推理的输入数据,其中的数据应与模型实际需要的输入相同,例如在这里模型的输入大小为 160X160,因此 calibrate 中的图像数据都为经过预处理缩放为 160X160 后的图像数据。

Linux

$ ./ncc infer model.kmodel kmodel_infer --dataset calibrate

Windows

> ncc.exe infer model.kmodel kmodel_infer --dataset calibrate

最后,对于使用 kmodel 推理得到的 .bin 文件可以使用 numpy 进行读取,读取方式如下,其中 index kmodel_infer/{}.bin 共同组成需要读取的文件名。

def kmodel_evaluator(index):
    result = np.fromfile('kmodel_infer/{}.bin'.format(index), dtype=np.float32)
    return result[0]

需要注意的是 numpy 读取的返回值 result 为 numpy 数组,而 result[0] 才是我们所需要的推理结果;并且对于浮点数的推理结果,需在使用 numpy 读取时指定读取类型为 dtype=np.float32

更详细的使用介绍见 NNCase 使用说明

K210

使用 K210 对模型进行推理运算便相对简单,加载模型后便可利用其对输入数据进行推理得到结果。不过仍有几个细节需要注意,首先对于 NNCase 转换得到的 kmodel v4,在加载模型后需要使用 kpu.set_outputs() 指定模型的输出形状,v3 模型则不需要;然后如果不是直接使用摄像头拍摄得到的图像,而是对其进行了一些变换,则需要在变换后使用 pix_to_ai() 同步图像缓冲区。具体使用流程如下,其中 kmodel 文件 model.kmodel 储存在 SD 卡根目录下。

import sensor, image
import KPU as kpu

sensor_init()                         # 摄像头初始化函数,这里不再赘述

model = kpu.load("/sd/model.kmodel")
kpu.set_outputs(model, 0, 1, 1, 1)

img = sensor.snapshot()
img = img.resize(160, 160)
img.pix_to_ai()                       # 同步 RGB888 内存块
fmap = kpu.forword(model, img)        # 得到特征图
plist = fmap[:]

print('kmodel infer result:', plist[0])

更详细的使用介绍见 MaixPy API 手册:KPU

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇