垃圾分类模型想上maixpy

文章目录

  • maixpy笔记
    • Something
      • nnc-0.1算子支持
      • 可能用到的API
    • Commands
    • Skills
    • Problems
    • 12-13
    • 12-27
    • 12-28
    • 12-29
    • 12-31
    • 1-1
      • code:10图测试
      • code:正确预测
    • 1-3
    • 1-4
    • 1-5
    • 1-8
    • 1-9

maixpy笔记

Something

  • 上下拉。应该就是强制高、低电平,可以避免不确定的状态。
  • 模型区没有文件系统,模型之间烧录在指定地址。
  • 文件系统可以有效帮助我们管理存储介质,例如磨损均衡
  • 理论上只要单层使用内存不超过 2MB, 整体模型可以无限大,只不过要牺牲一点运算速度。
  • SRAM与Flash 。其中SRAM掉电易失,容量小,而速度快。

nnc-0.1算子支持

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 使用最近邻插值法对输入张量进行重新调整大小。

可能用到的API

  • image.to_grayscale([copy=False])

    将图像转换为灰度图像。

Commands

  • 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版本进行模型格式转换。

Skills

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卡里的)。

Problems

  • “对同一个外设或者引脚重复映射”是什么意思?

  • 我需要做板级配置吗?

  • 很奇怪,为什么只有我手机充电的那根数据线可以连的上板子?

  • 仍然不太清楚“外设”和“引脚”的关系。

  • 一个人读文档发现不一致的地方时是真的难受,而且也找不着解释。例如我使用 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是什么?

  • 如何删除板子上的文件呢?

  • 下载.kmodel0x300000位置后,就连不上板子了,不知道是不是把固件挤掉了。而重新下载.bin固件时,会下载失败。

    解决:将开发板设置从“Sipeed Mainxduino"改为“自动选择”后,就好了。但是我记得我之前都是用的“Sipeed Mainxduino"选项。

12-13

试试运行一个maixpy官方提供的模型。

使用MaixHub训练的模型可以在开发板上正常运行,不错不错。在IDE上时只能运行一小会儿就会报错,使用串口终端时又无法显示图像。后来王鹤野建议说打印直接打印结果,我尝试发现确实可以。连串口时板子是在运行的,仅仅是没有图像显示而已。这次尝试的是一个击败K的小模型,训练时验证集精度达到了79.5%,但在尝试中它对盘装菜识别效果较好,其它的尝试识别都失败了。

后续可以进行的尝试:

  • 在Maixhub上训练一个更大的模型,看在串口终端连接下它可以支持多大模型的运行。
  • 将Paddle的模型转换为.kmodel的格式。但是Paddle模型保存的时候都是两个文件,一个参数文件一个模型文件,可以转成一个.kmodel文件吗?

12-27

尝试了将 paddle 框架训练的模型使用 paddle2onnx 转换为 onnx 格式,然后使用 nncase 转换为 kmodel 格式。

但是得到的 kmodel 模型无法使用。在载入模型的那一步 kpu.load(model_addr) 就会直接结束,而且连板子和maixpy IDE的连接都会断开。

12-28

仍然是转换出的 kmodel 无法在板子上加载的问题。据我目前了解,可能是它已经停止对 nncase 转换工具的支持了。参考:【K210踩坑】pytorch模型转kmodel,Dock上使用。(最终未实现) 。

真的烦,别人做什么我就只能用什么,为什么我不会自己写一个模型转换工具呢?

12-29

使用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版本就好了?

12-31

在尝试使用nncase的0.1和0.2对kmodel进行转换时都失败了,它们都不支持SHAPE操作。总体0.2版本的算子支持比0.1版本要丰富许多,比如0.2可以支持Reshape操作。

删除含有SHAPE操作的层后,转换又失败了。

  • 0.1版猜测是不支持我在tensorflow lite中量化后的得到的unit8参数格式。
  • 0.2版则显示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 。

  • 改用kflash_gui可以成功烧录进板子。

终于成了!一个手写数字识别的模型成功在板子上被加载,并正常产生了输出。

  • 关于报错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的图像),我才突然恍然大悟。

1-1

关于模型部署,MaixPy文档的这一部分中可能有些有用的参考:部署模型到 Maix-I(M1) K210 系列开发板 - Sipeed Wiki 。

  • 实际用数字图片进行测试时,手写数字识别的模型无法产生正确的输出。

猜测:输入需要进行预处理。训练时使用了img = img / 255.0 ,但是在Maixpy中我不知道如何达成这一步操作。

我尝试了 image.div() 接口,但发现 /255 时就相当于数学中 /1/125 就相当于数学中 /0.5 这种。也就是说,我无法使用这个接口让图像中的像素值变小。

接着我使用了如下代码进行测试:

code:10图测试

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张手写数字的图片,模型都正确预测。哦耶!

下面的代码实现了对手写数字图片文件的预测:

code:正确预测

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秒。

1-3

尝试将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

1-4

以moblienet-v2网络在mnist上训练了一个模型,然后转为tflite,分析了网络使用算子的支持情况

mobilenet-v2网络代码来源:MobileNet V2 网络结构的原理与 Tensorflow2.0 实现 。

下表为mobilenet-v2中的算子支持情况

算子 支持
Conv2D
Relu6
DepthwiseConv2D
Add
Mean
Shape
StridedSlice
Pack
Reshape
Softmax

修改网络后,开始尝试tflite转kmodel

bugFatal: 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 4108808/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=filterkernel

1-5

对比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(WHCin+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课程视频,了解对自己数据集的处理流程。

1-8

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分类垃圾数据集训练。

1-9

对齐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)

改成上述“正确修改方式“后,训练效果与官方模型接近。


”革命尚未成功,同志仍需努力“。

你可能感兴趣的:(学习笔记,深度学习,分类,单片机,计算机视觉)