CanMV K230 第一个micropython案例(修正中)——单次Dense网络


这是不断进行迭代的版本,因此这个过程会比较长,帖子可能也会比较长

官网地址:https://www.canaan-creative.com/
在官网的开发者社区页面可以看到镜像、开发工具、在线文档等资料。
目前K230已经存在了几种不同的开发板

笔记计划在两款开发板中测试

笔记的主要内容:
CanMV K230 第一个micropython案例(修正版)——单层Dense网络
简单来说就是一个一次的线性函数,形式y=ax+b

开发环境如下:
系统:ubuntu20.04
环境:jupyter、tensorflow(生成原始模型)、k230sdk (用于转换模型,版本待定)
测试工具:CanMV-IDE(运行micropython)

k230部分的基本流程:在ubuntu安装k230sdk,用于对tensorflow模型进行转换(及后续的编译C++文件),将转换后的kmodel文件在CanMV-IDE中进行测试

省略ubuntu系统安装,及环境安装过程

在jupyter中导入tensorflow包并进行简单模型开发,需要进行拟合的函数目标为y=2x-1
代码如下

CanMV K230开发板使用案例
CanMV-IDE可以通过python变成开发,简化了开发过程,但还是涉及kmodel转换
主要流程为开发模型,模型转换及测试,模型部署.

CanMV K230开发板Micropython开发
模型转换

import tensorflow as tf 
import numpy as np
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
#拟合一个一次函数 Y=2X-1
Y = Dense(units=1, input_shape=[1])
model = Sequential([Y])
model.compile(optimizer='sgd', loss='mean_squared_error')
xs = np.array([-1.0, 0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
ys = np.array([-3.0, -1.0, 1.0, 3.0, 5.0, 7.0], dtype=float)
model.fit(xs, ys, epochs=500)
#显示测试结果及参数
print(model.predict([10.0])) #测试结果
print("Here is what I learned: {}".format(Y.get_weights()))

将模型进行保存或转换,目前的文档支持的格式为:
1)tflite
2)onnx
可能是由于开发主要集中在图像处理领域,输入的维度一般为4维,对于1维数据转换tflite可能存在动态形状的原因转换kmodel模型失败。因此推荐采用onnx格式。

对tensorflow模型转换的代码如下

import tensorflow as tf
import os
import onnx
#需要先使用model.save方法保存模型
model.save('model')
#调用tf2onnx将上一步保存的模型导出为ONNX
os.system("python3 -m tf2onnx.convert --saved-model model --output test1.onnx --opset 11")

转换完成需要进行校验,最好每个步骤后都进行校验

检测是否转换成功

import onnx
#载入模型
onnx_model = onnx.load("./test1.onnx")
check = onnx.checker.check_model(onnx_model)
print('Check: ', check) # 返回 Check:  None 为成功

可视化转换的模型结构与设计的结构是否相同,可以采用netron打开原始模型与onnx模型进行比对,以确认转换无误
例如转换后可能存在的输入输出维度异常问题

在netron上打开onnx模型,如下图所示,可以看到输入输出存在问题,需要采用下列代码进行更改

import onnx
onnx_model = onnx.load("./test1.onnx")
onnx_model.graph.input[0].type.tensor_type.shape.dim[0].dim_value = 1
onnx_model.graph.output[0].type.tensor_type.shape.dim[0].dim_value = 1
onnx.save(onnx_model, './test1_dim.onnx')

修正结果如图
以上完成了步骤1:原始模型开发

步骤2:原始模型转换——kmodel模型
kmodel模型可以理解为可以被没有配置tensorflow相关环境的硬件设备中运行的文件,同时可以实现原始模型的加速

此处需要采用的k230sdk开发工具,将准换onnx模型转换为kmodel模型

转换过程如下:
1)配置环境变量

import os
import sys
import subprocess

result = subprocess.run(["pip", "show", "nncase"], capture_output=True)

split_flag = "\n"
if sys.platform == "win32":
    split_flag = "\r\n"

location_s = [i for i in result.stdout.decode().split(split_flag) if i.startswith("Location:")]
location = location_s[0].split(": ")[1]

if "PATH" in os.environ:
    os.environ["PATH"] += os.pathsep + location
else:
    os.environ["PATH"] = location

2)定义转换参数

import nncase
import numpy as np
from nncase_base_func import *

def compile_kmodel(model_path, dump_path, calib_data):
    """
    Set compile options and ptq options.
    Compile kmodel.
    Dump the compile-time result to 'compile_options.dump_dir'
    """
    print("\n----------   compile    ----------")
    print("Simplify...")
    model_file = model_simplify(model_path)

    print("Set options...")
    # import_options
    import_options = nncase.ImportOptions()

    ############################################
    # The code below, you need to modify to fit your model.
    # You can find more details about these options in docs/USAGE_v2.md.
    ############################################
    # compile_options
    compile_options = nncase.CompileOptions()
    compile_options.target = "k230" #"cpu"
    compile_options.dump_ir = False  # if False, will not dump the compile-time result.
    compile_options.dump_asm = True
    compile_options.dump_dir = dump_path
    compile_options.input_file = ""

    # preprocess args   不采用预处理过程,应用与原始数据与训练模型输入之间的差异
    compile_options.preprocess = False#True
    if compile_options.preprocess:
        compile_options.input_type = "float32" # "uint8" "float32"
        compile_options.input_shape = [1,1]
        compile_options.input_range = [-1,4]
        compile_options.input_layout = "" # "NHWC"
        compile_options.swapRB = False
        compile_options.mean = [1.5]
        compile_options.std = [1.7]
        compile_options.letterbox_value = 0
        compile_options.output_layout = "" # "NHWC"

    # quantize options
    ptq_options = nncase.PTQTensorOptions()
    ptq_options.quant_type = "uint8"    # datatype : "float32", "int8", "int16"
    ptq_options.w_quant_type = "uint8"  # datatype : "float32", "int8", "int16"
    ptq_options.calibrate_method = "NoClip" # "Kld"
    ptq_options.finetune_weights_method = "NoFineTuneWeights"
    ptq_options.dump_quant_error = True   # False 输出模型推断的误差结果,评估转换情况
    ptq_options.dump_quant_error_symmetric_for_signed = True  #False

    # mix quantize options
    # more details in docs/MixQuant.md
    ptq_options.quant_scheme = ""
    ptq_options.quant_scheme_strict_mode = False
    ptq_options.export_quant_scheme = False
    ptq_options.export_weight_range_by_channel = False
    ############################################

    ptq_options.samples_count = len(calib_data[0])
    ptq_options.set_tensor_data(calib_data)

    print("Compiling...")
    compiler = nncase.Compiler(compile_options)
    # import
    model_content = read_model_file(model_file)
    if model_path.split(".")[-1] == "onnx":
        compiler.import_onnx(model_content, import_options)
    elif model_path.split(".")[-1] == "tflite":
        compiler.import_tflite(model_content, import_options)

    compiler.use_ptq(ptq_options)

    # compile
    compiler.compile()
    kmodel = compiler.gencode_tobytes()

    kmodel_path = os.path.join(dump_path, "test1.kmodel")
    with open(kmodel_path, 'wb') as f:
        f.write(kmodel)
    print("----------------end-----------------")
    return kmodel_path

3)模型转换

# compile kmodel single input  动态过程需要预先设置,
model_path = "./test1_dim.onnx"
dump_path = "./test1_onnx"
# sample_count is 2
calib_data = [[100*np.random.rand(1, 1).astype(np.float32), 100*np.random.rand(1, 1).astype(np.float32)]]  # 代表数据集
kmodel_path = compile_kmodel(model_path, dump_path, calib_data)

4)模型测试

# run kmodel(simulate)
import os

kmodel_path = "./test1_onnx/test1.kmodel"

input_data=10*np.random.rand(1, 1).astype(np.float32)
print(input_data)
result = run_kmodel(kmodel_path, input_data)
# 输出被限制在代表数据集内 代表数据集的限制
print(result)

以上完成了步骤2:原始模型转换

步骤3:开发板测试

将所需要的文件传输到开发板中的SD中
方式1:scp命令
方式2:读卡器或者PC的sd卡接口拷贝文件
将文件放置在自己知道的路径中,例如
/sdcard/app/tests/k230_test/test1.kmodel

插入SD并打开CanMV-IDE设置自动连接

在CanMV-IDE中编写测试模型的代码,整体流程如下:
1)导入必要的库
2)设置输入
3)开启kpu载入kmodel模型
4)测试模型的输入输出是否符合设计要求
5)运行模型并测试结果
6)释放设备

整体编写代码如下

import nncase_runtime as nn
import ulab.numpy as np
# 输入数据
a=np.array([22])
# 开启kpu
kpu = nn.kpu()
# 载入kmodel模型
kpu.load_kmodel("/sdcard/app/tests/k230_test/test1.kmodel")

print("inputs info:")
for i in range(kpu.inputs_size()):
    print(kpu.inputs_desc(i))

print("outputs info:")
for i in range(kpu.outputs_size()):
    print(kpu.outputs_desc(i))

input_data = np.frombuffer(a,dtype=np.float)
print(input_data.shape)
input_data = input_data.reshape((1,1))
print(input_data.shape)

kpu.set_input_tensor(0, nn.from_numpy(input_data))

# run kmodel
kpu.run()

print('input  is : ',a[0])
# get output
for i in range(kpu.outputs_size()):
    result = kpu.get_output_tensor(i)
    result = result.to_numpy()
    print('output is : ',result[0][0])

del kpu
del input_data
del result

CanMV-IDE测试结果如下图所示

图中显示在结果与函数接近30×2-1=59,与设计的结果相近。

存在的问题:
但300、100时其结果为192.625,当数据降为90时正常,采用的是量化模型,并量化过程中设置了输入输出的限制,同时较少的校正数据集也限制了模型的输入输出范围。
尝试的解决方法:扩大范围,否则当应用的实际场景中则可能会存在问题

关于校正数据集的设置后续会展开讨论

后续,对基础模型进行扩展,例如手写数字的识别

以上为简单的一个模型尝试过程,欢迎讨论,后续会补充一些细节及扩展,例如传感器输入数据进行推断,如adc采集或温度传感器等采集数据进行推断等。

CanMV K230开发板使用感受
python开发较为简单,便于模型的快速验证,但实现的功能有限,感觉C++的学习还是有必要的。同时工具链的使用还不熟练,官方的文档等也有待加强,嘻嘻



扫描二维码,在手机上阅读

理解数字信号处理系列计划——基于CanMV K230修正版

canmv k230自学开发笔记

评 论