[Day 28] Edge Impulse + BLE Sense实现影像分类(下)

=== 书接上回,[Day 27] Edge Impulse + BLE Sense实现影像分类(上) ===

迁移学习(Transfer Learning)

在前面有提及在增加「学习区块(Learning Block)」时,系统会推荐使用「迁移学习」。进到「迁移学习(Transfer Learning)」页面时,系统预设「训练次数(Training Cycles)」为20,「学习率(Learning Rate)」为0.0005,「资料扩增(Data Augmentation)」为不勾选。若勾选则会自动增加一些资料变化性让模型训练後能更强健。而预设模型会选用「MobileNetV2 96x96 0.35 dropout」。此时按下【Start Training】後,会得到一组很糟的训练结果,准确率只有50%,而损失则有1.06,更糟的是会发现右下角的装置效能,推论时间要7,231ms,且竟要使用346.8KB的RAM和574.4KB的FLASH(即模型参数区加模型推论程序码部份),明显超过了Arduino Nano 33 BLE Sense SRAM 256KB的限制(FLASH未超过1MB容量)。

所以此时需重新点击【Choose a different model】来重新选择较小的模型,如MobileNetV1 96x96 0.1(0.1代表Dropout比例),它只需使用53.2KB的RAM和101KB的ROM(等同於FLASH)。再重新按下【Start Training】後,可得推论时间423ms,使用RAM 66.0KB和FLASH 106.8KB,明显小了许多。而精确度问题,稍後再解释。完整步骤如图Fig. 28-1所示。

Edge Impulse模型训练(迁移学习)
Fig. 28-1 Edge Impulse模型训练(迁移学习)。(OmniXRI整理绘制, 2021/10/12)

以下简单列出目前内建的10种模型及RAM和ROM的使用量,由Table 28-1中可得知,BLE Sense开发板只能支援前3种模型。

Table 28-1 迁移学习模型使用资源表(OmniXRI整理制表, 2021/10/13)

Model Name RAM ROM BLE Sense
MobileNetV1 96x96 0.25 105.9K 301.6K O
MobileNetV1 96x96 0.2 83.1K 218.3K O
MobileNetV1 96x96 0.1 53.2K 101K O
MobileNetV2 96x96 0.35 296.8K 575.2K X
MobileNetV2 96x96 0.1 270.2K 212.3K X
MobileNetV2 96x96 0.05 265.3K 162.4K X
MobileNetV2 160x160 1.0 1.3M 2.6M X
MobileNetV2 160x160 0.75 1.3M 1.7M X
MobileNetV2 160x160 0.5 700.7K 982.4K X
MobileNetV2 160x160 0.35 683.3K 658.4K X

如果想要使用其它更小的模型,则必须切换到专家模式(Expert),手动方式定义模型,如你不是非常精通这些程序运作,建议不要随意更动。以下列出在专家模式下MobileNetV1 96x96 0.1时的模型源码,以供参考。

# MobileNetV1 0.1 (no final dense layer, 0.1 dropout) 专家模式模型原始程序

import math
from pathlib import Path
import tensorflow as tf
from tensorflow.keras import Model
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, InputLayer, Dropout, Conv1D, Flatten, Reshape, MaxPooling1D, BatchNormalization, Conv2D, GlobalMaxPooling2D, Lambda
from tensorflow.keras.optimizers import Adam, Adadelta
from tensorflow.keras.losses import categorical_crossentropy
sys.path.append('./resources/libraries')
import ei_tensorflow.training

WEIGHTS_PATH = './transfer-learning-weights/edgeimpulse/MobileNetV1.0_1.96x96.color.bsize_96.lr_0_05.epoch_66.val_accuracy_0.14.hdf5'

INPUT_SHAPE = (96, 96, 3)

base_model = tf.keras.applications.MobileNet(
    input_shape = INPUT_SHAPE,
    weights = WEIGHTS_PATH,
    alpha = 0.1
)

base_model.trainable = False

model = Sequential()
model.add(InputLayer(input_shape=INPUT_SHAPE, name='x_input'))
# Don't include the base model's top layers
last_layer_index = -6
model.add(Model(inputs=base_model.inputs, outputs=base_model.layers[last_layer_index].output))

model.add(Dropout(0.1))

model.add(Dense(classes, activation='softmax'))

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0005),
                loss='categorical_crossentropy',
                metrics=['accuracy'])

BATCH_SIZE = 32
train_dataset = train_dataset.batch(BATCH_SIZE, drop_remainder=False)
validation_dataset = validation_dataset.batch(BATCH_SIZE, drop_remainder=False)
callbacks.append(BatchLoggerCallback(BATCH_SIZE, train_sample_count))
model.fit(train_dataset, validation_data=validation_dataset, epochs=500, verbose=2, callbacks=callbacks)

print('')
print('Initial training done.', flush=True)

# How many epochs we will fine tune the model
FINE_TUNE_EPOCHS = 10
# What percentage of the base model's layers we will fine tune
FINE_TUNE_PERCENTAGE = 65

print('Fine-tuning best model for {} epochs...'.format(FINE_TUNE_EPOCHS), flush=True)
# Load best model from initial training
model = ei_tensorflow.training.load_best_model(BEST_MODEL_PATH)

# Determine which layer to begin fine tuning at
model_layer_count = len(model.layers)
fine_tune_from = math.ceil(model_layer_count * ((100 - FINE_TUNE_PERCENTAGE) / 100))

# Allow the entire base model to be trained
model.trainable = True
# Freeze all the layers before the 'fine_tune_from' layer
for layer in model.layers[:fine_tune_from]:
    layer.trainable = False

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.000045),
                loss='categorical_crossentropy',
                metrics=['accuracy'])

model.fit(train_dataset,
                epochs=FINE_TUNE_EPOCHS,
                verbose=2,
                validation_data=validation_dataset,
                callbacks=callbacks)

在刚才第一次训练後得到的结果非常差,所以最简单的方式就是先把训练次数加高,学习率暂不变动,保持在0.0005,如图Fig. 28-1的下半部图示。当训练次数提高到100次时,精确度提升到70%,损失0.80。当再提高训练次数到200次时,精确度已提升到80%,而损失已降至0.68。可是当训练次数再提高至500次时,精确度已难以提升,但损失仍有略微下降至0.58,还是有所改善。这里精确度无法再提升还有一个主因是样本数过少导致,只需增加样本数量及多样性就能有所改善。

影像分类(Classification (Keras))

在前面建立「学习区块(Learning Block)」时,曾说明系统预设是推荐「迁移学习(Transfer Learning)」选项,但其实也可选「影像分类(Classification (Keras))」,再按下【Save Impulse】按钮後,左侧选单就会出现「NN Classification」取代原有的「Transfer Learning」。而这个选项就是一般层数较少的「卷积神经网路(Convolutional Neural Network, CNN)」。但请注意学习区块只须建立一种就好,不要同时建两种。

把页面切到「NN Classification」後,预设的「训练次数」为10,「学习率」为0.0005,输入层特徵数为27,648(即96x96像素x3通道之总数),输出层为3个分类,隐藏层为2个2D卷积池化层(2D conv/pool)加上一个展平层(Flatten)和随机丢弃层(Dropout, rate 0.25)所组成。其中第1卷积层预设卷积核3x3,32个特徵图(Filter,Feature Map),而第2层则为16个特徵图。

为了比较,这里把「训练次数」先调成20,当按下【Start Training】按钮後,就可以开始训练模型。第一次得到的结果,不算太理想,精确率只有70%,损失0.88,看起来好像比迁移学习好(?)。但注意一下画面左下角的RAM使用量,竟已高达364.0KB,明显超过BLE Sense开发板的SRAM 256KB,这样就算完成训练也无法布署到开发板上。所以要将游标移到第1卷积池化层「2D conv/pool layer」按钮上方,待出现4个小按钮,点下编辑滤波器(Filter,就是特徵图数量),将32改成16。同样地把第2卷积池化层的滤波器数量改为8。再重新按下【Start Training】重新训练一次,这次会得到一个比较好的结果精确率只有90%,损失0.63,且RAM的使用量已降到183.2KB,满足布署条件。但这里大家可能会发现一个奇怪的问题,怎麽参数变少了反而得到较好的结果。其实这只是一个假像,只是碰巧这几笔资料满足模型,有点过拟合的味道。

接着为了和「迁移学习」作一些比较,这里分别调整「训练次数」为50, 100,200来求得精确率和损失。而眼尖的人大概有注意到训练100次和200次的精确度和损失已相同,表示已收歛,再增加次数也无法改善,需从其它方向来改善。完整的操作步骤如图Fig. 28-2所示。

Edge Impulse模型训练(影像分类)
Fig. 28-2 Edge Impulse模型训练(影像分类)。(OmniXRI整理绘制, 2021/10/12)

如果想要使用其它更小的模型,则必须切换到专家模式(Expert),手动方式定义模型,如你不是非常精通这些程序运作,建议不要随意更动。以下列出在专家模式下NN Classifier (CNN)的模型源码,以供参考。

# NN Classifier 专家模式模型原始程序

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, InputLayer, Dropout, Conv1D, Conv2D, Flatten, Reshape, MaxPooling1D, MaxPooling2D, BatchNormalization, TimeDistributed
from tensorflow.keras.optimizers import Adam

# model architecture
model = Sequential()
model.add(Conv2D(16, kernel_size=3, activation='relu', kernel_constraint=tf.keras.constraints.MaxNorm(1), padding='same'))
model.add(MaxPooling2D(pool_size=2, strides=2, padding='same'))
model.add(Conv2D(8, kernel_size=3, activation='relu', kernel_constraint=tf.keras.constraints.MaxNorm(1), padding='same'))
model.add(MaxPooling2D(pool_size=2, strides=2, padding='same'))
model.add(Flatten())
model.add(Dropout(0.25))
model.add(Dense(classes, activation='softmax', name='y_pred'))

# this controls the learning rate
opt = Adam(lr=0.0005, beta_1=0.9, beta_2=0.999)
# this controls the batch size, or you can manipulate the tf.data.Dataset objects yourself
BATCH_SIZE = 32
train_dataset = train_dataset.batch(BATCH_SIZE, drop_remainder=False)
validation_dataset = validation_dataset.batch(BATCH_SIZE, drop_remainder=False)
callbacks.append(BatchLoggerCallback(BATCH_SIZE, train_sample_count))

# train the neural network
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
model.fit(train_dataset, epochs=20, validation_data=validation_dataset, verbose=2, callbacks=callbacks)

模型训练结果比较

有了上述二种模型的训练结果,其推论时间及使用资源可参考Tabel 28-2所示。在布署到实际开发板前,可以利用Edge Impulse的「Model Testing」页面来测试一下先前保留的测试集资料的推论结果。进到页面後,只需按下【Classify All】就能得到精确度和混淆矩阵,还会显示出不确定(Uncertain)的资料比例。系统预设「置信度门槛(Confidence thresholds)为0.6」,当低於这个门槛会被归在不确定的范围,当这个比例过高时,可能就会重新检讨资料集的建立及模型设计方向。这里因为测试资料集数量太小(3分类各4笔,共12笔),所以精确度仅供参考。

Table 28-2 模型使用资源比表(OmniXRI整理制表, 2021/10/13)

Model Name Inference Time(ms) Peak RAM Usage Flash Usage
MobileNet V1 96x96 0.1 423ms 66KB 106.8KB
Classification (CNN) 1,188ms 183.2KB 46.5KB

在图Fig. 28-3中,分别使用已训练500次的迁移学习MobileNetV1模型和训练200次(已收歛)的卷积神经网路(CNN)影像分类模型来测试。测试时分别给予不同的置信度门槛 0.6, 0.5和0.4。从图表中可得知当门槛越低时,不确定的资料比例就会下降,推论精确度也会略微提升。而两种模型的表现各有优劣,可能要使用更大的测试集来验证会更准确一些。

Edge Impulse模型训练结果比较
Fig. 28-3 Edge Impulse模型训练结果比较。(OmniXRI整理绘制, 2021/10/12)

另外虽然最後模型还是可以布署到BLE Sense上进行推论,但这里未能使用真正的摄影机模组取像,少了点味道,所以就先不介绍布署和执行後的结果,希望後续拿到摄像头後再来撰写相关实测心得。

小结

影像的辨识比起声音或感测器辨识来说不仅资料维度增加至二维,同时输入特徵及训练的参数的数量也呈指数爆增,训练时间更是漫长,想要得到好的辨识结果更有许多AI相关知识要学习。目前各种tinyML的开发板对於影像类应用还是有点不足,期待未来有更好的算法、更强的算力及更便宜的解决方案问世,这样才有机会让tinyML普及到生活各种智能应用上。

参考连结

Edge Impulse Tutorials - Adding sight to your sensors说明文件
Edge Impulse Development Board - Arduino Nano 33 BLE Sense with OV7675 camera add-on说明文件


<<:  图的拓扑排序 - DAY 29

>>:  DAY28 - 来试试看 line notify吧

资产剥离(divestiture)

首先考虑范围内的资产更为有效,因为业务中断,知识产权泄漏和数据隐私不合规是范围内资产所产生的影响或...

Day 30 完结

大家好~~本篇为最後的一篇 谢谢大家,在这30天中非常的艰辛,虽然可能没有到非常好,但是希望给予初学...

新新新手阅读 Angular 文件 - pathMatch(3) - Day29

本文内容 本文内容为阅读与 Angular 的 pathMatch:full 和 redirectT...

Principal Component Analysis (PCA)

昨天在研究MDS的时候顺便把PCA也复习了一下,所以今天来把它相关的原理补上(非常推荐观看refer...

Day 5. Hashicorp Nomad: How to configure one service advertise multiple ports

Hashicorp Nomad: How to configure one service adve...