基于TensorFlow的keras-applications图像分类技术的一般使用方式

简单的一批

可以跳过的部分

首先把图像分类技术应用到比赛中肯定有几个目标:

  1. 能正常由数据集训练出图像分类模型。
  2. 能够调用模型并获得不错的分类效果。
  3. 能画图, 能把图片放进论文里。

这三点TensorFlow做的都不错, 唯一不足的就是TensorFlow官方推荐语言是脚本语言Python, 脚本语言拿来写程序终究是会遇到性能上的硬伤, 而这会让本来性能就很一般的TensorFlow变得更加的不适合在Arm-Linux上运行。

所以这应该也是我最后一次应用TensorFlow到比赛的调用场景。(当然训练或许还是得用他, 比如给K210训练MoblieNet之类的。毕竟用脚本语言写训练脚本我觉得很合适。而且没有密集型计算, 密集型计算全是调用的C++和cuda。)

首先要确定需求, 由需求选择技术。
在我的实际测试中, 大致把需求分为以下几种:

  1. 在K210这种RAM较少但是算力中等的平台。
  2. 在普通Arm-Linux上这种RAM中等但是算力较差的平台(没错, 即使是RK3399这种Cortex A72在CPU条件下的神经网络算力也是远远不如K210的)。
  3. 在有cuda加速的设备上, 如Jetson-Linux和笔记本电脑。

对于第一种需求, 需要选择模型体积文件较小的神经网络, 比如 MoblieNet 图像分类网络 或者 Yolo-MobileNetV2Yolo-Fastest 图像识别网络。因为RAM不足, 所以会牺牲一点准确率。
对于第二种需求, 需要选择体积文件合适并且对算力要求较低的网络 比如在帧率要求不高的图像分类场合可以选择 Xception 提高准确率或者选择牺牲准确率提高帧率 比如 YOLO-Fastest
对于第三种需求, 则只有一个目的: 准确率。自然选择 YOLO v4 或者 Xception 等。

如题, 本篇博客主要针对于需求2 3的图像分类技术。

图像预处理

本篇文档所有代码在文章最后有开源链接, 穿插在文章中的代码只是负责讲解, 直接复制是跑不起来的。

我不太明白TensorFlow为什么这么多预处理的api都会自动拉伸图像改变他原来的长宽比。这样很明显会丢失图像细节和削弱图像特征。

所以不如自己先预处理一下提高准确度。因为有些目标物本来就不是长宽1:1的形状, 而手动裁剪太浪费时间, 所以我选择预先这样处理。
(事实证明我这样做对模型准确率有较明显的提升的效果, 同样的垃圾分类数据集, 不预处理只能达到93%的正确率, 预处理后到了97.69%)

我把长边缩放为512像素, 短边在随机位置填充随机纯色背景。
一般的模型所需要的图片大小一般都远小于512, 所以凑个整, 体积小了也方便保存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if(input_height > input_width):
# 如果高大于宽
# resize到最大边为width
output_width = int(input_width * size / input_height)
src = cv.resize(src,(output_width,size))

mask = np.zeros([size + 2, size + 2, 1], np.uint8)
background = np.zeros((size,size,input_channels), np.uint8)

cv.floodFill(background, mask, (0,0), (random.randint(0,255),random.randint(0,255),random.randint(0,255)),cv.FLOODFILL_MASK_ONLY)

randomcoor = random.randint(0,size - output_width)
np.copyto(background[0:size,randomcoor:randomcoor + output_width],src)
src = background


elif(input_height < input_width):
# 宽大于高
...

预处理Python脚本开源地址:
未来会开源到这个下面, 等我打完这个比赛闲下来整理一下吧。
https://github.com/h13-0/Tensorflow-Study-Note

至于dataset格式的话, 个人推荐采用TensorFlow官方的 ImageDataGenerator.flow_from_directory() 所需要的格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
dataset
├─Test
│ ├─Battery
│ ├─Brokenceramics
│ ├─Cans
│ ├─Cigarettebutts
│ ├─Drug
│ ├─Fruit
│ ├─Paper
│ ├─Tile
│ ├─Vegetables
│ └─Walterbottles
├─Train
│ ├─Battery
│ ├─Brokenceramics
│ ├─Cans
│ ├─Cigarettebutts
│ ├─Drug
│ ├─Fruit
│ ├─Paper
│ ├─Tile
│ ├─Vegetables
│ └─Walterbottles
└─Valid
├─Battery
├─Brokenceramics
├─Cans
├─Cigarettebutts
├─Drug
├─Fruit
├─Paper
├─Tile
├─Vegetables
└─Walterbottles

也就是dataset下分别为 Test Train Valid 文件夹。
这三个文件夹下再放入存有不同种类图片的文件夹。

开始训练

下载脚本:
https://github.com/h13-0/Tensorflow-Study-Note

建议一个项目用一个文件夹, 使用的时候把本文件夹拷贝一份即可。
先暂时不更改所使用的网络模型。
更改 Train.pyBase_Dir 为你的 dataset 路径,
然后创建在当前终端的路径(或项目路径)下创建 log weights 文件夹, 分别保存了 Tensorboard 数据 和 权重数据。
根据你的dataset修改 classes 以及 epochlearning_rate
根据你的电脑性能更改 batch_size

然后终端输入 python ./Train.py (建议使用终端打开, 注意在当前路径下是否有 logweights 文件夹)
或双击 Train.py (使用本方法则需要注意是否有 logweights 文件夹)

验证模型

下载脚本:
https://github.com/h13-0/Tensorflow-Study-Note

修改 Base_DirWeight_File_Path
然后运行。

画图

1
tensorboard --logdir #Your log dir here

然后根据提示访问网页
还是蛮好看的。

具体每张图的作用请右转百度。
我原本很想把 embedding 加进去, 可惜这玩意对运存要求太高了, 我就不加了。

就是这玩意儿

更换模型

假设我要切换为上一篇博客所讲的 InceptionResNet V2
找到这一行:

1
base_model = tf.keras.applications.Xception(weights="imagenet",include_top=False,input_shape=(299,299,3),pooling="avg")

改为:

1
base_model = tf.keras.applications.InceptionResNetV2(weights="imagenet",include_top=False,input_shape=(299,299,3),pooling="avg")

注意我修改了哪里!!!

然后在(在绝大多数模型中都不需要这样更改, 比如InceptionResNet V2, 觉得自己运气好的可以跳过)
https://github.com/keras-team/keras-applications/blob/master/keras_applications/
下找到你用的模型的脚本, 即:
https://github.com/keras-team/keras-applications/blob/master/keras_applications/inception_resnet_v2.py
(或者ide中跳转到 InceptionResNetV2 的函数定义, 或者在ide中按住 Ctrl 单击 InceptionResNetV2 )

找到类似于这样的语句(根据include_top判断和修改输出神经层的语句)

1
2
3
4
5
6
7
8
9
if include_top:
# Classification block
x = layers.GlobalAveragePooling2D(name='avg_pool')(x)
x = layers.Dense(classes, activation='softmax', name='predictions')(x)
else:
if pooling == 'avg':
x = layers.GlobalAveragePooling2D()(x)
elif pooling == 'max':
x = layers.GlobalMaxPooling2D()(x)

看看在 include_topTure 的时候比 False 的(avg池化)时候多了哪一层输出网络。
在本网络中, include_topTure 的时候在网络末端添加了

1
2
x = layers.GlobalAveragePooling2D(name='avg_pool')(x)
x = layers.Dense(classes, activation='softmax', name='predictions')(x)

include_topFalse 的(avg池化)时候网络末端为

1
x = layers.GlobalAveragePooling2D()(x)

多了一层神经元数量为 classesDense
那就把

1
outputs = layers.Dense(classes, activation='softmax')(base_model.output)

改为他多出来的网络即可(在较为重量级的网络下不需要更改, 但是轻量级的网络可能不一样)。
当然, 本脚本也没有针对轻量级网络做优化就是了。。。

这里

1
2
3
4
Xception
InceptionResNetV2
MobileNetV2
DenseNet

等, 是不需要修改的。
但是

1
2
3
MobileNet
MobileNetV3
EfficientNet

等, 是需要修改的。

最后, 核对官方建议分辨率是否为(299,299), 如不是, 请修改 Image_Shape

性能问题

调用时性能不足:

  1. 如果含有是含有nvidia的GPU的设备, 请在调用之前启用GPU加速

    1
    2
    3
    physical_devices = tf.config.experimental.list_physical_devices('GPU')
    for physical_device in physical_devices:
    tf.config.experimental.set_memory_growth(physical_device, True)

    或者试图同时在训练和验证的时候缩小输入神经网络的图片(不建议)

  2. 如果没有GPU加速, 可以尝试用ncnn或者mnn框架进行CPU演算, 比TensorFlow快得多。

训练时性能不足:

  1. 如果能启用cuda加速, 建议使用darknet框架代替TensorFlow。
  2. 去淘宝租GPU服务器。
  3. 加钱换GPU

据某些网友测试
cuda加速时速度:
Darknet > ncnn > pytorch > TensorFlow
Arm-CPU速度:
ncnn > Darknet > TensorFlow

至少我实测在Arm-CPU下跑 Yolo-Fastest 320*320 的时候
Darknet: 3000ms
ncnn: 450ms

还是有一定可信度的。
(并且cuda下训练 YOLO V4 的时候, Darknet比TensorFlow快很多)

Demo

https://github.com/h13-0/Garbage-Classification

TODO:

  1. 整理预处理脚本
  2. 整理训练脚本
  3. 整理验证脚本
  4. 上传视频