基于谷歌最新网络模型EfficientNet,使用迁移学习对猫狗图像识别分类的实际案例应用

0 Introduction 引言

EfficientNet是Google谷歌的最新论文EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks中提出的全新神经网络模型(简称模型,下同)。

可以毫不夸张地说,此模型一出,对前面的那些模型们具有碾压效果。论文里作者在Github上开源了该网络模型训练好的源码,地址在这里,有兴趣的小伙伴们可以加以参考。在这篇论文中,作者系统地研究并验证了网络深度宽度分辨率之间的平衡可以发挥出更好的性能。由于本篇文章侧重于基于该模型的实际应用,将不对该论文展开过多的解读。本文参考的代码[1]已经在文末的参考部分列出。

1 迁移学习概述

迁移学习(Transfer Learning)是一种机器学习方法,就是把为任务 A 开发的已经训练好的模型参数作为初始点,重新迁移使用在为任务 B 开发模型中,来帮助新模型的训练[2]。这是百度百科关于迁移学习的解释,下面引用斯坦福大学的 CS231n(面向视觉识别的卷积神经网络)课程中关于迁移学习的定义[3]

In practice, very few people train an entire Convolutional Network 
from scratch (with random initialization), because it is relatively 
rare to have a dataset of sufficient size. Instead, it is common to 
pretrain a ConvNet on a very large dataset (e.g. ImageNet, which 
contains 1.2 million images with 1000 categories), and then use the 
ConvNet either as an initialization or a fixed feature extractor for 
the task of interest.

关于迁移学习的概念,大家仁者见仁智者见智,由于其并不是本篇文章的重点,在这里不再赘述。本节部分只介绍迁移学习的经典做法,即首先使用transfer learning对数据集进行训练,经过一定的Epoch批次之后,再改用fine tuning的方法,接着刚才训练过的模型即训练权重集继续进行训练,同时可以适当地降低learning rate学习率。之所以使用fine tuning,是因为这样做可以加速训练过程,详情可以在本文第四小节的内容中体会。理论讲解完毕,下面开始进入应用部分。

2 数据集的准备

正所谓兵马未动,粮草先行巧妇难为无米之炊,训练网络模型时候也一样,训练之前需要先把数据集给准备好,这里我们用到的是Kaggle比赛猫狗大战的数据集Kaggle Cats and Dogs Dataset,下载地址在这里(使用时请严格遵守数据集中附的MSR-LA - 3467.docx文件中提到的Microsoft Research Data License Agreement)。

下载好数据集后解压文件,会得到如下文件夹目录(其中以“- -”开头的是子目录):

kagglecatsanddogs_3367a
--PetImages
--MSR-LA - 3467.docx
--readme[1].txt

其中的PetImages文件夹就是猫狗图像了,每类各12500张,我们手动选取其中每类1000张图像用于训练,500张用于测试(这里可以根据个人情况酌量添加)。

3 使用Transfer leaning对数据集进行训练

本部分将介绍基于迁移学习的猫狗识别的training训练核心代码块,首先是引入EfficientNet:

from efficientnet import EfficientNetB0 as Net   # Import efficientnet and load the conv base model

接下来是Hyper parameters超参数的设置:

# Hyper parameters 超参数
batch_size = 16

# width = 150   # Error Detected
# height = 150   # B0的输入是 224*224*3
width = 224   # Last Modified: 2020.05.26
height = 224
epochs = 100
NUM_TRAIN = 2000
NUM_TEST = 1000
dropout_rate = 0.2
input_shape = (height, width, 3)

刚才下载解压好并手动选取放置后的数据集目录代码:

train_dir = './data/dog_vs_cat_small/train'
validation_dir = './data/dog_vs_cat_small/validation'

图像数据增强代码块:

train_datagen = ImageDataGenerator(          
      rescale=1./255,
      rotation_range=40,
      width_shift_range=0.2,
      height_shift_range=0.2,
      shear_range=0.2,
      zoom_range=0.2,
      horizontal_flip=True,
      fill_mode='nearest')

validation_datagen = ImageDataGenerator(
      rescale=1./255,
      rotation_range=40,
      width_shift_range=0.2,
      height_shift_range=0.2,
      shear_range=0.2,
      zoom_range=0.2,
      horizontal_flip=True,
      fill_mode='nearest')

分批读取数据集:

train_generator = train_datagen.flow_from_directory(
        train_dir,
        target_size=(height, width),
        batch_size=batch_size,
        class_mode='categorical')

validation_generator = validation_datagen.flow_from_directory(
        validation_dir,
        target_size=(height, width),
        batch_size=batch_size,
        class_mode='categorical')

以下是本节内容的核心部分,即使用EfficientNet进行迁移学习的代码块:

conv_base = Net(weights='imagenet', include_top=False, input_shape=input_shape)
model = models.Sequential()
model.add(conv_base)
model.add(layers.GlobalMaxPooling2D(name="gap"))
if dropout_rate > 0:
    model.add(layers.Dropout(dropout_rate, name="dropout_out"))
model.add(layers.Dense(2, activation='softmax', name="fc_out")) 

# 输出网络模型参数
# model.summary()  

# 冻结卷积层不参与训练
conv_base.trainable = False

model.compile(loss='categorical_crossentropy', optimizer=optimizers.RMSprop(lr=2e-5),
              metrics=['acc'])

一切准备就绪,下面开始训练网络模型,并将训练好后的网络模型文件保存,输出Training和Validation的accuracy及loss图:

history_tl = model.fit_generator(       
      train_generator,
      steps_per_epoch= NUM_TRAIN //batch_size,
      epochs=epochs,
      validation_data=validation_generator,
      validation_steps= NUM_TEST //batch_size,
      verbose=1,
      use_multiprocessing=True,
      workers=4
)
# 训练后的模型文件保存目录
model.save('./output_model_file/my_model.h5')


def plot_training(history):

    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']

    epochs_x = range(len(acc))

    plt.plot(epochs_x, acc, 'bo', label='Training acc')
    plt.plot(epochs_x, val_acc, 'b', label='Validation acc')
    plt.title('Training and validation accuracy')
    plt.legend()

    plt.figure()

    plt.plot(epochs_x, loss, 'bo', label='Training loss')
    plt.plot(epochs_x, val_loss, 'b', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()

    plt.show()

# 用于训练后输出Training和Validation的accuracy及loss图
plot_training(history_tl)

Training文件执行后的训练过程如下:

Using TensorFlow backend.
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Epoch 1/100

  1/125 [..............................] - ETA: 22:03 - loss: 4.5333 - acc: 0.5000
  2/125 [..............................] - ETA: 11:00 - loss: 4.7284 - acc: 0.4375
  3/125 [..............................] - ETA: 7:18 - loss: 4.5324 - acc: 0.4792 
  4/125 [..............................] - ETA: 5:28 - loss: 4.2386 - acc: 0.4531
  5/125 [>.............................] - ETA: 4:21 - loss: 3.9124 - acc: 0.4625
  6/125 [>.............................] - ETA: 3:37 - loss: 4.0239 - acc: 0.4688
  7/125 [>.............................] - ETA: 3:05 - loss: 3.7774 - acc: 0.4911
  8/125 [>.............................] - ETA: 2:42 - loss: 3.8022 - acc: 0.5078
  9/125 [=>............................] - ETA: 2:23 - loss: 3.8040 - acc: 0.4792
 10/125 [=>............................] - ETA: 2:08 - loss: 3.7879 - acc: 0.4750
 11/125 [=>............................] - ETA: 1:56 - loss: 3.6966 - acc: 0.4943
 ...
123/125 [============================>.] - ETA: 0s - loss: 2.5113 - acc: 0.4553
124/125 [============================>.] - ETA: 0s - loss: 2.5085 - acc: 0.4556
125/125 [==============================] - 38s 305ms/step - loss: 2.5123 - acc: 0.4555 - val_loss: 1.8039 - val_acc: 0.4315
Epoch 2/100

  1/125 [..............................] - ETA: 7s - loss: 2.0313 - acc: 0.3750
  2/125 [..............................] - ETA: 7s - loss: 2.1477 - acc: 0.4062
  3/125 [..............................] - ETA: 7s - loss: 2.2250 - acc: 0.3542
  4/125 [..............................] - ETA: 7s - loss: 2.0626 - acc: 0.3906
  5/125 [>.............................] - ETA: 7s - loss: 2.1242 - acc: 0.3875
 ...

我们可以看到,首先使用transfer learning方法对数据集进行训练的过程中,刚开始的accuracy准确率并不是很高,从0.5左右开始逐步提升,而且经过100个Epoch之后才上升到了0.75附近。不要慌,下个部分我们将介绍到如何利用fine tuning方法在本节训练模型的基础上对模型继续进行训练。我们先来看使用transfer learning方法经过100个Epoch的训练好之后的结果。

使用transfer learning方法经过100个Epoch的训练之后,会在指定目录下生成模型文件,并显示Training和Validation的accuracy及loss图如下:
Training and validation accuracy
Training and validation accuracy

4 使用Fine tuning载入上节训练过的模型继续对数据集进行训练

首先是载入上节训练后保存的模型文件,即训练权重集:

model.load_weights('./output_model_file/my_model.h5')

然后是基于Fine tuning的策略,对网络模型进行修改和编译,重新搭建新的模型:

# Fine tuning网络模型的最后几层   
# 设置 'multiply_16' 及接下来的几层为可训练状态
conv_base.trainable = True

set_trainable = False

for layer in conv_base.layers:
    if layer.name == 'multiply_16':
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False

模型搭建好之后,我们接着上节的网络模型继续进行200个Epoch的训练:

Using TensorFlow backend.
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Epoch 1/200

  1/125 [..............................] - ETA: 6:21 - loss: 0.5286 - acc: 0.7500
  4/125 [..............................] - ETA: 1:34 - loss: 0.5933 - acc: 0.8281
  7/125 [>.............................] - ETA: 53s - loss: 0.4986 - acc: 0.8482 
 10/125 [=>............................] - ETA: 37s - loss: 0.4308 - acc: 0.8625
 13/125 [==>...........................] - ETA: 28s - loss: 0.4212 - acc: 0.8654
 15/125 [==>...........................] - ETA: 25s - loss: 0.4004 - acc: 0.8667
 16/125 [==>...........................] - ETA: 24s - loss: 0.4175 - acc: 0.8633
 17/125 [===>..........................] - ETA: 23s - loss: 0.4007 - acc: 0.8676
 ...
124/125 [============================>.] - ETA: 0s - loss: 0.5657 - acc: 0.8201
125/125 [==============================] - 23s 184ms/step - loss: 0.5710 - acc: 0.8195 - val_loss: 0.6552 - val_acc: 0.7540
Epoch 2/200

  1/125 [..............................] - ETA: 1s - loss: 0.1782 - acc: 0.9375
  4/125 [..............................] - ETA: 2s - loss: 0.4303 - acc: 0.8125
  7/125 [>.............................] - ETA: 2s - loss: 0.5291 - acc: 0.8036
 10/125 [=>............................] - ETA: 2s - loss: 0.6332 - acc: 0.7750
 13/125 [==>...........................] - ETA: 2s - loss: 0.5831 - acc: 0.7885
 14/125 [==>...........................] - ETA: 3s - loss: 0.6281 - acc: 0.7812
 ...

通过输出我们可以看到,接着上节保存的模型进行训练的时候,准确率直接是从0.75开始的,这也是情理之中的事情,毕竟是使用fine tuning方法进行的训练,我们来看下200个Epoch之后输出的Training和Validation的accuracy及loss图:
Training和Validation的accuracy图
Training和Validation的loss图
通过观察Training和Validation的accuracy及loss图,我们可以看到,经过200个Epoch之后,准确率已经由原来的0.75提升到了0.80附近,这不能不算得上是一件值得高兴的事情。

5 使用训练好的模型文件来进行对图像的识别

模型训练好之后,我们就可以使用该模型用于猫狗图像的识别分类预测任务了。本节中我们将对比使用fine tuning之后生成的模型和未进行fine tuning,在transfer learning后直接进行分类预测的准确率结果。

5.1 未进行fine tuning,使用transfer learning后生成的模型直接进行分类预测

首先是载入经过transfer learning训练后保存的模型文件:

# 载入模型
model = load_model('./output_model_file/my_model.h5')

定义图像分类的预测函数:

def predict_image(img_path):
    # 读入要识别的图像并修改为要求的尺寸大小
    img = image.load_img(img_path, target_size=(height, width))
    # 将图像转化为Numpy数组格式
    x = image.img_to_array(img)
    x = x.reshape((1,) + x.shape)
    x /= 255.
    result = model.predict([x])[0][0]
    if result > 0.5:
        animal = "cat"
    else:
        animal = "dog"
        result = 1 - result
    return animal, result

将要进行识别的图像路径:

img = './12499.jpg'

用于输出识别图像结果的代码语句:

print(predict_image(img))

以下是图像识别的类别输出及对应的概率:

Using TensorFlow backend.
...
('dog', 0.9990355644840747)

可以看到,使用训练后的模型,可以成功地识别出该图像所属的类别。

5.2 fine tuning之后,继续transfer learning生成的模型来进行分类预测

同样的,先载入经过transfer learning和fine tuning之后生成的模型文件:

# 载入模型
# model = load_model('./output_model_file/my_model.h5')
model = load_model('./models/cats_and_dogs_small.h5')

用于图像分类的预测函数不变,仍然识别刚才那张名为12499.jpg的照片,这时的预测结果及其准确率如下:

Using TensorFlow backend.
...
('dog', 0.9998197872046148)

可以看到,虽然两种情况下的识别准确性是同等的,都能成功地识别出其所属的类别,但是在fine tuning之后,图像识别的准确率提升了0.00078左右。

6 Supplementary Contents 补充内容

以上案例的演示是Windows下进行的,如有Linux下运行的小伙伴,第一次执行training训练文件时候可能会出现如下情况:

[**@** EfficientNet]# python training.py
Using TensorFlow backend.
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.

Downloading data from https://github.com/qubvel/efficientnet/releases/download/v0.0.1/efficientnet-b0_imagenet_1000_notop.h5
  122880/16717576 [..............................] - ETA: 18:11Traceback (most recent call last):
  File "training.py", line 85, in <module>
    conv_base = Net(weights='imagenet', include_top=False, input_shape=input_shape)
  File "/**/efficientnet/model.py", line 400, in EfficientNetB0
    weights=weights, classes=classes, pooling=pooling)
  File "/**/efficientnet/model.py", line 391, in _get_model_by_name
    md5_hash=weights['md5'],
  File "/usr/local/python3/lib/python3.6/site-packages/tensorflow/python/keras/utils/data_utils.py", line 248, in get_file
    urlretrieve(origin, fpath, dl_progress)
  File "/usr/local/python3/lib/python3.6/urllib/request.py", line 277, in urlretrieve
    block = fp.read(bs)
  File "/usr/local/python3/lib/python3.6/http/client.py", line 449, in read
    n = self.readinto(b)
  File "/usr/local/python3/lib/python3.6/http/client.py", line 493, in readinto
    n = self.fp.readinto(b)
  File "/usr/local/python3/lib/python3.6/socket.py", line 586, in readinto
    return self._sock.recv_into(b)
  File "/usr/local/python3/lib/python3.6/ssl.py", line 1012, in recv_into
    return self.read(nbytes, buffer)
  File "/usr/local/python3/lib/python3.6/ssl.py", line 874, in read
    return self._sslobj.read(len, buffer)
  File "/usr/local/python3/lib/python3.6/ssl.py", line 631, in read
    v = self._sslobj.read(len, buffer)
ConnectionResetError: [Errno 104] Connection reset by peer

之所以会报这个错误,是因为首次执行training训练文件的时候,程序会自动从网上下载EfficientNet模型文件,但由于网速的限制,可能会出现下载中断的情况,报出ConnectionResetError的错误。针对这个情况,我们可以预先将要用到的EfficientNet网络模型离线下载,然后将该模型放置到指定的路径下,这样以后执行training训练文件的时候就不会每次都先下载网络模型了,而是直接可以使用我们预先放置好的网络模型来进行训练。至于如何离线下载网络模型并上传,以及如何找到网络模型上传的指定目录,已经详细地记录在了我的这篇博文里,点击这里即可跳转到这篇博文进行阅读

♣️ 项目完整代码(托管于Github)

项目的完整Github代码(完善和维护中)

写到这里,差不多本文就要结束了。之所以会选择写这篇文章,是因为本人发现,谷歌的EfficientNet模型已经提出将近有小半年的时间了,可是在CSDN上面竟然没有一个可供参考的EfficientNet模型用于图像分类的完整案例,于是乎本人花了将近一周的时间咬着牙痛苦地做出了这个案例。如有需要,后续将会给出本案例对应的Github完整代码 Github代码已经公开,大家按需所取,如果有问题可以在下方留言区留言交流。如果我的这篇文章帮助到了你,那我也会感到很高兴,一个人能走多远,在于与谁同行

7 References 参考

[1] 参考代码
[2] 百度百科关于迁移学习的解释
[3] 斯坦福大学的 CS231n课程关于迁移学习的定义

  • 18
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值