CanMV K230使用经验-初稿
CanMV K230开发板简介
K230宣传了很久,K230的开发板目前还处于缺货的状态,价格在250左右,CanMV K230 在官网已经更新了资料,包括镜像和教程。
官网地址:https://www.canaan-creative.com/
在官网的开发者社区页面可以看到镜像、开发工具、在线文档等资料。
开发板图
其余信息可以查阅官网信息
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
l0 = Dense(units=1, input_shape=[1])
model = Sequential([l0])
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(l0.get_weights()))
在训练完模型后需要转换为tflite或onnx格式文件,推荐转换为onnx文件,tflite文件在输入维度少时可能会存在动态形状难以转换为kmodel模型。
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 为成功
还需要调整输入维度,因为转换为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')
修正结果如图
准换onnx为kmodel模型,便于部署在k230开发板上,转换代码如下:
配置环境变量
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
定义转换参数
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
模型转换
# 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)
模型测试
# 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)
CanMV K230开发板python(完善)
将转换后的test1.kmodel拷贝到sd卡中,可以采用读卡器读取,将文件放置在/sdcard/app/tests/k230_test/test1.kmodel位置。
链接canmv ide
新建文件并编写代码
import nncase_runtime as nn
import ulab.numpy as np
a=np.array([22])
kpu = nn.kpu()
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
测试结果如下图所示
图中显示在结果与函数接近30×2-1=59,但300、100时其结果为192.625,当数据降为90时正常,可能的原因是在转换模型时校正数据集时,限制了模型的范围,因此校正数据集的设置很重要,虽然这是一个很小的模型,但转换后的量化导致推断出现问题,可以尝试扩大数据集的参数范围。
以上为简单的一个模型尝试过程,欢迎讨论,后续会补充一些细节及扩展,例如传感器输入数据进行推断,如adc采集或温度传感器等采集数据进行推断等。
CanMV K230开发板使用感受
python开发较为简单,便于模型的快速验证,但实现的功能有限,感觉C++的学习还是有必要的。同时工具链的使用还不熟练,官方的文档等也有待加强,嘻嘻