垃圾分类

基于Keras迁移学习

Posted by Gavin on October 25, 2019

日暮酒醒人已远

满天风雨下西楼

前言

迁移学习

人工智能大作业,响应上海市号召,垃圾分类,我比较懒,不想从头到尾自己搭建模型了,因此使用了Keras中的预训练模型进行迁移学习。我们知道神经网络很强,可以找到传统CV方法无法关注到的特征,以此进行图像识别和分类效果很好,因此诞生了一系列经典模型,比如VGG16ResNetInceptionV3MobileNet等等。但是从头到尾自己搭建和调试这些模型显然不太现实,这些都是又大又重的模型,仿佛庞然大物,训练代价高昂。因此产生了迁移学习的思路,除去这些模型的最终输出层,它们的中高层输出都是很好的特征提取,利用这些输出当作一个新任务的特征,节省大量从头训练的时间,岂不美哉?因此以MobileNet为例,进行迁移学习。

MobileNet

MobileNet是Google提出的可以运行于移动端的一个瘦身模型,体量小,效率高。其结构如下:

其创新之处在于,普通的卷积过程要对图像的每个通道都进行考虑,而MobileNet对不同通道的输入使用不同的卷积核进行操作,将普通卷积拆分为DepthwisePointwise两个过程,Depthwise负责使用不同卷积核提取每个通道的特征,Pointwise则对每个点进行传统卷积,提取点特征。其优化点在于,减少了特征数,比如想将一个3通道图片扩展为256个特征向量:

  • 传统卷积过程(使用3*3*256的卷机核):3*(3*3*256)=6912
  • DW卷积:3*(3*3*1)+3*(1*1*256)=795

大大降低特征数目,减轻了计算压力


实验

数据集

来自GitHub的垃圾分类数据集,该数据集包含了2507个生活垃圾图片,数据集的创建者将垃圾分为了6个类别,分别是class、cardboard、metal、paper、plastic、trash。
随机查看数据:

迁移MobileNet

反正MobileNet很强了,我就不对图像进行预处理提取特征了,交给MobileNet(大雾,本人偷懒,大家别学我)。利用Keras的预训练模型进行新模型构建,删除MobileNet的top输出层,将剩余层作为新模型的输入特征,同时训练新构建的顶层时,锁住MobileNet的各层参数,不让其再参与训练,否则就达不到节省时间、偷懒的目的了。

base_model = mobilenet_v2.MobileNetV2(input_shape=(84,512,3),weights='imagenet', include_top=False)
feature = GlobalAveragePooling2D(name='GAP')(base_model.output)
init_feature = Dropout(rate=dropout_rate)(feature)
outputs = Dense(6, activation='softmax',name='Pre')(init_feature)

model = Model(inputs = base_nodel.input,outputs = outputs)

for layer in base_model.layers:
    layer.trainable = False

model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])

构建完成,跑一跑却发现了一个巨大的问题:

Train on 50 samples, validate on 50 samples
Epoch 1/100
50/50 [==============================] - 1s 21ms/step - loss: 0.6806 - acc: 0.5400 - val_loss: 1.6767 - val_acc: 0.5000
Epoch 2/100
50/50 [==============================] - 0s 8ms/step - loss: 0.6061 - acc: 0.6400 - val_loss: 1.8632 - val_acc: 0.5000
Epoch 3/100
50/50 [==============================] - 0s 9ms/step - loss: 0.5088 - acc: 0.9000 - val_loss: 

训练集的loss一直在变小,acc也在飙升,但是测试集loss却越来越大,这是为啥?
探寻资料后,发现是Keras中Batch Normalization实现的问题,即便你锁住了baseModel,其中的BN层依然会更新自己的权重,因此我们不能将MobileNet的各层作为新模型结构的一部分,而应该将MobileNet整体当作一层使用,即MobileNet不参与训练,只相当于一个静态函数。改造写法如下:

Inp = Input((384,512,3))
base_model = mobilenet_v2.MobileNetV2(input_shape=(84,512,3),weights='imagenet', include_top=False)
x = base_model(Inp)
feature = GlobalAveragePooling2D(name='GAP')(x)
init_feature = Dropout(rate=dropout_rate)(feature)
outputs = Dense(6, activation='softmax',name='Pre')(init_feature)

model = Model(inputs = Inp,outputs = outputs)

for layer in base_model.layers:
    layer.trainable = False

model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])

再跑一下试试:

Epoch 1/5
70/70 [==============================] - 141s 2s/step - loss: 1.6760 - acc: 0.8268 - val_loss: 1.4670 - val_acc: 0.8929
Epoch 2/5
70/70 [==============================] - 133s 2s/step - loss: 1.5560 - acc: 0.8571 - val_loss: 1.5144 - val_acc: 0.8265
Epoch 3/5
70/70 [==============================] - 132s 2s/step - loss: 1.4673 - acc: 0.8701 - val_loss: 1.3820 - val_acc: 0.8721
Epoch 4/5
70/70 [==============================] - 129s 2s/step - loss: 1.4461 - acc: 0.8572 - val_loss: 1.3840 - val_acc: 0.8584
Epoch 5/5
70/70 [==============================] - 126s 2s/step - loss: 1.3771 - acc: 0.8630 - val_loss: 1.2560 - val_acc: 0.9132

Loss: 1.57, Accuracy: 85.66%

nice~


参考

  1. BN问题
  2. Keras预训练模型
  3. MobileNet论文