layer | parameters |
---|---|
Conv2d | kernel={3x3,1x1} stride={1,2} padding=same * |
DepthwiseConv2d | kernel={3x3,1x1} stride={1,2} padding=same * |
FullyConnected | 全连接操作。将输入张量拉伸成一个一维向量,然后与权重矩阵进行点积,最后加上偏差向量。 |
Add | 按位加法操作。将两个输入张量的相应元素相加。 |
MaxPool2d | 二维最大池化操作。对输入张量的每个子区域执行最大值池化,并返回包含每个子区域的最大值的新张量。 |
AveragePool2d | 二维平均池化操作。对输入张量的每个子区域执行平均值池化,并返回包含每个子区域的平均值的新张量。 |
GlobalAveragePool2d | 全局平均池化操作。对输入张量的所有元素求平均值,并返回一个形状为 (1, 1, C) 的张量,其中 C是输入张量的通道数。 |
BatchNormalization | 批量归一化操作。在训练时,对于输入张量的每个通道计算均值和方差,然后使用这些值对输入进行归一化;在预测时,使用固定的均值和方差对输入进行归一化。 |
BiasAdd | 将偏差向量加到输入张量上。 |
Relu | 修正线性单元 (ReLU) 激活函数。将输入张量的所有小于 0 的元素设为 0。 |
Relu6 | 修正线性单元 (ReLU6) 激活函数。将输入张量的所有小于 0 的元素设为 0,将所有大于 6 的元素设为 6。 |
LeakyRelu | 带有小正斜率的修正线性单元 (Leaky ReLU) 激活函数。将输入张量的所有小于 0 的元素乘以一个小于 1 的正斜率。 |
Concatenation | 按给定轴连接多个张量。 |
L2Normalization | 二范数归一化操作。对输入张量的所有元素求二范数,然后将输入张量的所有元素除以二范数。 |
Sigmoid | 对输入张量的所有元素应用 Sigmoid 函数。 |
Softmax | 对输入张量的所有元素应用 Softmax 函数。 |
Flatten | 展平张量。将输入张量拉伸成一个一维向量。 |
ResizeNearestNeighbor | 使用最近邻插值法对输入张量进行重新调整大小。 |
image.to_grayscale([copy=False])
将图像转换为灰度图像。
os.listdir()
,查看当前目录下的文件。
os.chdir()
切换当前目录到文件的目录,比如 os.chdir("/flash")
os.remove(path)
删除文件。
ncc compile mnist_float.tflite mnist_float.kmodel -i tflite -o kmodel -t k210 --inference-type float
使用nncase的0.2版本,将.tflite文件转为.kmodel格式。
ncc -i tflite -o k210model --inference-type float mnist_float.tflite mnist.kmodel
使用nncase的0.1版本进行模型格式转换。
1、使用IDE将文件保存到板子
Tool
中使用发送文件
保存为同名文件;Tool
中使用保存为boot.py
,下次板子开机上电时自动执行。2、执行板子上的文件
例如,板子上有个文件名为hello.py
,下面的代码将执行它。
with open("hello.py") as f:
exec(f.read())
3、实现开机自启
开机会自动先执行boot.py
,然后执行main.py
(如果检测到SD卡则执行SD卡里的)。
“对同一个外设或者引脚重复映射”是什么意思?
我需要做板级配置吗?
很奇怪,为什么只有我手机充电的那根数据线可以连的上板子?
仍然不太清楚“外设”和“引脚”的关系。
一个人读文档发现不一致的地方时是真的难受,而且也找不着解释。例如我使用 k f l a s h _ g u i kflash\_gui kflash_gui烧录模型时,就没有设置烧录地址的地方,而文档中第一张图里面有地址,后面的图又没有地址了。
用IDE运行官方的人脸识别时,一段时间后就会报错,据说是因为运行内存不够,可以用终端试试。但IDE里的终端我没用明白,我嵌在代码里的print语句正常输出,说明程序确实运行了,但却没有显示画面。而且,为什么会运行一段时间后才显示内存不足呢?是否和脸的数量有关?
RGB565是什么?
摄像头水平镜像是干什么?
import time
导入的包和文档中的 utime
是一样的吗?
这一段代码是不是不能脱离try-finally
结构?因为前面是while(True)
?
为什么插上板子时,我电脑上会增加两个串口COM5和COM6,但是只有COM5能用?那COM6是什么?
如何删除板子上的文件呢?
下载.kmodel
在0x300000
位置后,就连不上板子了,不知道是不是把固件挤掉了。而重新下载.bin
固件时,会下载失败。
解决:将开发板设置从“Sipeed Mainxduino"改为“自动选择”后,就好了。但是我记得我之前都是用的“Sipeed Mainxduino"选项。
试试运行一个maixpy官方提供的模型。
使用MaixHub训练的模型可以在开发板上正常运行,不错不错。在IDE上时只能运行一小会儿就会报错,使用串口终端时又无法显示图像。后来王鹤野建议说打印直接打印结果,我尝试发现确实可以。连串口时板子是在运行的,仅仅是没有图像显示而已。这次尝试的是一个击败K的小模型,训练时验证集精度达到了79.5%,但在尝试中它对盘装菜识别效果较好,其它的尝试识别都失败了。
后续可以进行的尝试:
.kmodel
的格式。但是Paddle模型保存的时候都是两个文件,一个参数文件一个模型文件,可以转成一个.kmodel
文件吗?尝试了将 paddle 框架训练的模型使用 paddle2onnx
转换为 onnx
格式,然后使用 nncase
转换为 kmodel
格式。
但是得到的 kmodel
模型无法使用。在载入模型的那一步 kpu.load(model_addr)
就会直接结束,而且连板子和maixpy IDE的连接都会断开。
仍然是转换出的 kmodel
无法在板子上加载的问题。据我目前了解,可能是它已经停止对 nncase
转换工具的支持了。参考:【K210踩坑】pytorch模型转kmodel,Dock上使用。(最终未实现) 。
真的烦,别人做什么我就只能用什么,为什么我不会自己写一个模型转换工具呢?
使用nncase v0.2.0转出的模型需要手动设置输出层形状。不知道是不是我使用方法不对,我的部分代码如下:
model_addr = "/flash/iris.kmodel"
task = kpu.load(model_addr)
success = kpu.set_outputs(task, 0, 12, 1, 1)
print('success: ', success)
但它总是显示:
w*h*ch!=12
[MAIXPY]kpu: set_outputs arg value error: w,c,ch size not match output size
success: False
可nncase v0.1.0转出的iris.kmodel的输出就是:
{"fmap": "data"=0x80380e70, "size"=12, "index": 1, "w": 12, "h": 1, "ch": 1, "typecode": f}
不知道出了什么问题,那我干脆就用v0.1.0版本就好了?
在尝试使用nncase的0.1和0.2对kmodel进行转换时都失败了,它们都不支持SHAPE操作。总体0.2版本的算子支持比0.1版本要丰富许多,比如0.2可以支持Reshape操作。
删除含有SHAPE操作的层后,转换又失败了。
Fatal: Invalid tensor type
,可以参考Cannot compile .tflite model (Fatal: Invalid tensor type) · Issue #420 · kendryte/nncase (github.com) 。当我取消tensorflow lite中的量化后,
0.1版仍然失败,据chatGPT所说,是因为转换器无法理解模型文件中的某些内容,我没有继续探究这个问题。
1-4对比文档发现,应该是不支持Reshape算子导致。
0.2版成功得到了 .kmodel
的模型。
现在,让我们尝试将刚刚得到的模型载入板子。
好家伙,使用IDE传文件又传不进去了,一直显示发送并保存中,请等待...
。此时IDE就一直卡在传送那里,无法断开与板子的连接,却可以打开串口终端;将IDE关闭后,依然有程序在后台运行。
好像是特定文件传不进去,我尝试传另一个 .kmodel
文件时成功了。模型文件从Colab训练后下载后得到,Build a handwritten digit classifier app with TensorFlow Lite 。
终于成了!一个手写数字识别的模型成功在板子上被加载,并正常产生了输出。
关于报错kpu:check img format err!
,看很多帖子都说分辨率不正确。在串口终端运行文件时多了一条报错[MAIXPY]kpu: pix_ai is NULL
,查了帖子后发现是内存中图像的修改与kpu中图像的修改不会自动同步的问题,需要每次修改图像后手动同步:.pix_to_ai()
,记得将替换为自己图像变量的变量名。
参考:why pix_to_ai still return none? · Issue #264 · sipeed/MaixPy (github.com) 。
关于 kpu.set_outputs()
总是失败。是啊,是要设置 outputs
,我总莫名想象成了 inputs
,制度第n次看到w*h*ch!=40
之类的报错提示与输入的形状风牛马不相及(输入是分辨率为28*28的图像),我才突然恍然大悟。
关于模型部署,MaixPy文档的这一部分中可能有些有用的参考:部署模型到 Maix-I(M1) K210 系列开发板 - Sipeed Wiki 。
猜测:输入需要进行预处理。训练时使用了img = img / 255.0
,但是在Maixpy中我不知道如何达成这一步操作。
我尝试了 image.div()
接口,但发现 /255
时就相当于数学中 /1
,/125
就相当于数学中 /0.5
这种。也就是说,我无法使用这个接口让图像中的像素值变小。
接着我使用了如下代码进行测试:
import sensor, lcd, image
import KPU as kpu
# 测试图像列表
img_names = ['num0', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8', 'num9']
# 载入模型
model_addr = 0x300000
task = kpu.load(model_addr)
# 批量测试
for i in range(10):
img = image.Image("/flash/" + img_names[i] + ".jpg")
img = img.resize(28, 28)
img = img.to_grayscale(copy=False)
img.pix_to_ai()
success = kpu.set_outputs(task, 0, 1, 1, 10)
fmap = kpu.forward(task, img)
plist=fmap[:]
pmax=max(plist)
max_index=plist.index(pmax)
print('\ni = ', i)
print(plist)
print(max_index)
对于用于测试的10张图片,输出的 plist
都是:
(3.402823e+38, -3.402823e+38, 3.402823e+38, -3.402823e+38, 3.402823e+38, 3.402823e+38, -3.402823e+38, 3.402823e+38, 3.402823e+38, 3.402823e+38)
很容易发现它只有两个值:3.402823e+38 和 -3.402823e+38,看起来像是溢出了。是不是没有预处理的原因呢?
想对image对象进行/255
的预处理,结果发现它没有小数,可以使用image.morph()
对每个像素进行除法操作,但是得到的结果只能是0~255。
发现列表和元组都支持小数和除法操作。
有没有什么方便的办法将图像转换为列表或元组呢?运行模型的时候是否支持使用列表或元组作为输入?转换速度如何?
发现fmap = kpu.forward(task, img)
中 img 可以是一维的列表。
测试10张手写数字的图片,模型都正确预测。哦耶!
下面的代码实现了对手写数字图片文件的预测:
import sensor, image, time
import KPU as kpu
def to_list_1(img):
return [x/255 for x in img[:]]
# 载入图片
img = image.Image("/flash/num0.jpg")
img = img.resize(28, 28)
img = img.to_grayscale(copy=False)
print()
print(img)
img = to_list_1(img)
print(img[:10])
# 载入模型
model_addr = 0x300000
task = kpu.load(model_addr)
# 推理
success = kpu.set_outputs(task, 0, 1, 1, 10)
print(success)
fmap = kpu.forward(task, img)
print(fmap)
# 处理模型输出
plist=fmap[:]
pmax=max(plist)
max_index=plist.index(pmax)
print(plist)
print(max_index)
print()
通过板子摄像头识别的效果要稍差一些,但是通常还是可以产生正确的输出;使用的是mnist测试集中的图片。
对于自己手写的数字,效果比较糟糕。
拖动、缩放图片,可能导致预测结果变化。
猜测:训练集中的数据场景比较单一,又没有经过数据增强。
可以发现两种情况下直方图的差异较大,左为自己手写,右为mnist数据集中的图片。
目前问题:对一张图片的预测时间太长,例如在手写数字识别的例子中为0.7秒。
尝试将float参数类型的mnist分类模型使用nncase-0.2量化为uint8类型,成功。
目的:提升推理速度。nncase文档中提到使用uint8可以获得k210的kpu加速。
效果:模型推理时间从700ms左右减为40ms左右;从摄像头获得输入,可以获得正确的预测结果。
疑惑:为什么量化需要输入数据集作为参数呢?
使用的编译参数为
ncc compile mnist_float.tflite mnist_float.kmodel -i tflite -o kmodel -t k210 --inference-type uint8 --dataset ./dataset_mnist --input-std 1 --input-mean 0
ncc compile cifar10-2.tflite cifar10-2.kmodel -i tflite -o kmodel -t k210 --inference-type uint8 --dataset ./dataset_cifar10 --input-std 1 --input-mean 0 --dump-weights-range --weights-quantize-threshold 64.000000
不进行量化(建议量化,因为摄像头输入本身是uint8):
ncc compile cifar10.tflite cifar10.kmodel -i tflite -o kmodel -t k210 --inference-type float
以moblienet-v2网络在mnist上训练了一个模型,然后转为tflite,分析了网络使用算子的支持情况。
mobilenet-v2网络代码来源:MobileNet V2 网络结构的原理与 Tensorflow2.0 实现 。
下表为mobilenet-v2中的算子支持情况:
算子 | 支持 |
---|---|
Conv2D | ✅ |
Relu6 | ✅ |
DepthwiseConv2D | ✅ |
Add | ✅ |
Mean | ✅ |
Shape | ❌ |
StridedSlice | ✅ |
Pack | ❌ |
Reshape | ✅ |
Softmax | ✅ |
修改网络后,开始尝试tflite转kmodel。
bug:
Fatal: Invalid dataset, should contain one file at least
,但文件夹明明非空啊。发现:文件夹的名字打错了,修正后得到了kmodel。
问题:转换过程中显示
WARN: Conv2D_19 Fallback to float conv2d due to weights divergence
,暂时不知有什么影响。
连接板子运行时,加载模型失败。
bug:ValueError: [MAIXPY]kpu: load error:2003, ERR_KMODEL_FORMAT: layer_header.body_size <= 0
参考:Fallback to float conv2d due to weight divergence · Issue #164 · kendryte/nncase (github.com) 。
bug2:使用Netron查看网络结构时显示Error loading kmodel. Unsupported version ‘4’ layer ‘strided_slice’.
发现:若不进行量化,可以在Netron正常查看结构,但仍加载时仍然有ValueError。
猜测:是否网络中单层体积太大?记得maixpy文档中说单层不能超过2MB(不知道是指参数量、输出特征图,还是什么)。使用
model.summary()
查看网络各层,最大的一层Param为410880。不知道每个参数占多大的空间,我们先假定为8字节,于是这层占用内存为 410880 ∗ 8 / 102 4 2 = 3.13 M B 410880*8/1024^2=3.13MB 410880∗8/10242=3.13MB 。但这样就有一个矛盾,.tflite
模型文件的实际大小为8.47MB,如果我按上面的计算方法,将所有层参数的参数大小加起来,会远超这个数值。疑惑:卷积层的参数量是怎么计算的?前一层输出尺寸为(None, 1, 1, 320),本身输出为(None, 1, 1, 1280)。于是 ( 320 + 1 ) ∗ 1280 = 410880 (320 + 1) * 1280 = 410880 (320+1)∗1280=410880 。但这不应该是全连接层的计算方法吗?我理解中卷积层应该 P a r a m = f i l t e r ∗ k e r n e l Param = filter * kernel Param=filter∗kernel 。
对比Params与模型文件实际体积。
结果:模型实际大小与Params大小是可以对上的,参数应该是以float32存储。我把“字节”与“位”搞混了,应该是一个字节为8位。
疑惑:为什么nncase量化后,模型文件体积还是没有显著减小?例如tfilte文件大小为436KB,量化后的kmodel大小仍有380KB。但从float32到uint8,大小理应变为四分之一。
看tensorflow的视频,卷积层参数量如何计算。
结果: P a r a m = C o u t ∗ ( W ∗ H ∗ C i n + 1 ) Param=C_{out} * (W * H * C_{in} + 1) Param=Cout∗(W∗H∗Cin+1) 。输出特征图的通道数决定过滤器的个数,输入特征图的通道数决定每个过滤器的通道数。
重新训练mobilenet_v2。
发现:使用batch_size=128和使用batch_size=32时每个step的耗时差不多,于是整体使用batch_size=128时准确率上升更快。然而训练准确率达到92%时,测试准确率却只有70%。
使用batch_size=32时,也出现了过拟合。训练5个epoch时准确率从78%到了81%,但是测试准确率却突然从73%掉到了58%。再训练5个epoch后训练准确率为84%,测试准确率又回到了74%。
问题:过拟合真的是噩梦。
发现2:转换为kmodel后的体积发生了变化。tflite是8.47MB没有变,上次kmodel是8.44MB,这次kmodel是6.31MB。使用Netron依然打不开模型,板子上也依然加载不了。
问题2:据chatGPT和mainxpy群里的“小老鼠”所说,转换模型时的
WARN: Conv2D_19 Fallback to float conv2d due to weights divergence
没有关系。那导致加载模型时ValueError的原因是什么呢?发现3:因为使用的cifar10数据集,图片分辨率为(32, 32),所以网络中出现了(1, 1)分辨率的图片被(3, 3)卷积核卷积的情况,可能是它导致了问题。
改用图片分辨率为(224, 224)的数据集训练mobilenet_v2 。
进度:卡在了调整数据集中图片大小这一步,找了个猫狗数据集,数据集中图片的分辨率是不固定的。虽然opencv的resize操作可以调整图片大小,但是我不知道怎么把这个操作插入到tensorflow训练模型的流水线中去。
贪图一些API的方便,却又不熟悉这些API的使用。
解决方案:继续学习tensorflow课程视频,了解对自己数据集的处理流程。
miblenet_v2在horse-or-human数据集上训练失败。
发现:层
tf.keras.layers.BatchNormalization()
似乎会导致问题,多次使用该层容易导致像是过拟合的现象。下面是五层卷积的简单模型,插入了三层BatchNormalization,产生的训练数据:
Epoch 1/5 16/16 [=====] - 7s 345ms/step - loss: 2.6593 - acc: 0.7812 - val_loss: 0.6854 - val_acc: 0.7617 Epoch 2/5 16/16 [=====] - 5s 305ms/step - loss: 0.0719 - acc: 0.9793 - val_loss: 0.7076 - val_acc: 0.4570 Epoch 3/5 16/16 [=====] - 5s 320ms/step - loss: 0.3488 - acc: 0.8887 - val_loss: 0.6957 - val_acc: 0.4688 Epoch 4/5 16/16 [=====] - 5s 321ms/step - loss: 0.0114 - acc: 0.9980 - val_loss: 0.6909 - val_acc: 0.5000 Epoch 5/5 16/16 [=====] - 6s 373ms/step - loss: 0.2425 - acc: 0.9503 - val_loss: 0.8048 - val_acc: 0.5000
使用mobilenet_v2时,现象类似,train_acc可以在90%以上,val_acc恒为50%整。
令人不解的是,当我使用训练集数据进行评估,acc是在50%附近浮动,那为何训练时会显示那么高的准确率?
model.evaluate(train_generator) 129/129 [==============================] - 10s 77ms/step - loss: 0.8571 - acc: 0.4869
删除mobilenet_v2中的所有BatchNormalization层,模型却无法收敛了,train_acc在50%附件徘徊。
使用的mobilenet_v2模型代码来自MobileNet V2 网络结构的原理与 Tensorflow2.0 实现 。
想法:换个数据集会不会不一样?毕竟mobilenet_v2之前在mnist数据集上是可以正常训练的。
猜测:1、我的mobilenet_v2代码有问题,与官方的不同;2、数据集不合适。
方案:1、调用
model.summary()
查看keras中官方mobienet_v2的模型结构,与它对齐;2、改用自己的4分类垃圾数据集训练。
对齐mobilenet_v2模型结构 。
步骤1:先试试keras中的mobilenet_v2能不能正常训练;
结果:可以。
步骤2:对比两个模型的结构,以及训练时准确率;
发现:相对官方模型的train_acc随epoch稳定上升,我的模型train_acc显得不稳定。仔细对比模型结构,发现我模型尾部多了个卷积层。
# 例 - 调用keras官方mobilenet_v2 tf.keras.applications.mobilenet_v2.MobileNetV2(input_shape=(224,224,3), weights=None, classes=4)
在原来CSDN找到的代码中,该卷积层与Reshape层搭配,发挥全连接层的作用。因为nncase-v0.2.0不支持Reshape,我就把Reshape删了,补了全连接层Dense,但是我没有把那个卷积层删掉。于是,相当于多了一层。
# 原代码中的用法 x = tf.keras.layers.GlobalAveragePooling2D()(x) x = tf.keras.layers.Reshape((1,1,1280))(x) x = tf.keras.layers.Conv2D(classes, (1,1), padding='same')(x) x = tf.keras.layers.Reshape((classes,))(x) x = tf.keras.layers.Activation('softmax')(x) # 正确修改方式 x = tf.keras.layers.GlobalAveragePooling2D(keepdims=True)(x) x = tf.keras.layers.Flatten()(x) x = tf.keras.layers.Dense(classes)(x) x = tf.keras.layers.Activation('softmax')(x)
改成上述“正确修改方式“后,训练效果与官方模型接近。
”革命尚未成功,同志仍需努力“。