本次所用数据来自ImageNet,使用预训练好的数据来预测一个新的数据集:猫狗图片分类。这里,使用VGG模型,这个模型内置在Keras中,直接导入就可以了。

from keras.applications import VGG16conv_base = VGG16(weights='imagenet',include_top=False,input_shape=(150, 150, 3))

说一下这三个参数:

  • weights:指定模型初始化权重检查点
  • include_top:指定模型最后是否包含密集连接分类器。默认情况下,这个密集连接分类器对应于ImageNet的1000个类别。因为我们打算使用自己的分类器(只有两个类别:cat和dog),所以不用包含。
  • input_shape:输入到网络中的图像张量(可选参数),如果不传入这个参数,那么网络可以处理任意形状的输入

看一下VGG16网络的详细构架:

conv_base.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         (None, 150, 150, 3)       0
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 150, 150, 64)      1792
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 150, 150, 64)      36928
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 75, 75, 64)        0
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 75, 75, 128)       73856
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 75, 75, 128)       147584
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 37, 37, 128)       0
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 37, 37, 256)       295168
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 37, 37, 256)       590080
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 37, 37, 256)       590080
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 18, 18, 256)       0
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 18, 18, 512)       1180160
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 18, 18, 512)       2359808
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 18, 18, 512)       2359808
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 9, 9, 512)         0
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 9, 9, 512)         2359808
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 9, 9, 512)         2359808
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 9, 9, 512)         2359808
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0

最后这个特征图形状为(4, 4, 512),我们在这个特征上面添加一个密集连接分类器。


不使用数据增强的快速特征提取(计算代价低)

首先,运行ImageDataGenerator实例,将图像及其标签提取为Numpy数组,调用conv_base模型的predict方法从这些图像的中提取特征。

import os
import numpy as np
from keras.preprocessing.image import ImageDataGeneratorbase_dir = '/Users/fchollet/Downloads/cats_and_dogs_small'train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20def extract_features(directory, sample_count):features = np.zeros(shape=(sample_count, 4, 4, 512))labels = np.zeros(shape=(sample_count))generator = datagen.flow_from_directory(directory,target_size=(150, 150),batch_size=batch_size,class_mode='binary')i = 0for inputs_batch, labels_batch in generator:features_batch = conv_base.predict(inputs_batch)features[i * batch_size : (i + 1) * batch_size] = features_batchlabels[i * batch_size : (i + 1) * batch_size] = labels_batchi += 1if i * batch_size >= sample_count:break  # 这些生成器在循环中不断生成数据,所以你必须在读完所有图像之后终止循环return features, labelstrain_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

目前,提取的特征形状为(samples, 4, 4, 512),我们要将其输入到密集连接分类器中去,所以必须首先对其形状展平为(samples ,8192)

train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))

下面定义一个密集连接分类器,并在刚刚保存好的数据和标签上训练分类器:

from keras import models
from keras import layers
from keras import optimizersmodel = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))model.compile(optimizer=optimizers.RMSprop(lr=2e-5),loss='binary_crossentropy',metrics=['acc'])history = model.fit(train_features, train_labels,epochs=30,batch_size=20,validation_data=(validation_features, validation_labels))

训练速度非常快,因为只需要处理两个Dense层。下面看一下训练过程中的损失曲线和精度曲线:

import matplotlib.pyplot as pltacc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()plt.figure()plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()plt.show()



从图中可以看出,验证精度达到了约90%,比之前从一开始就训练小型模型效果要好很多,但是从图中也可以看出,虽然dropout比率比较大,但模型从一开始就出现了过拟合。这是因为本方法中没有使用数据增强,而数据增强对防止小型图片数据集过拟合非常重要。


使用数据增强的特征提取(计算代价高)

这种方法速度更慢,计算代价更高,但是可以在训练期间使用数据增强。这种方法是:扩展conv_base模型,然后在输入数据上端到端的运行模型。(这种方法计算代价很高,必须在GPU上运行)

承接我们之前定义的网络模型

from keras import models
from keras import layersmodel = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
vgg16 (Model)                (None, 4, 4, 512)         14714688
_________________________________________________________________
flatten_1 (Flatten)          (None, 8192)              0
_________________________________________________________________
dense_3 (Dense)              (None, 256)               2097408
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 257
=================================================================
Total params: 16,812,353
Trainable params: 16,812,353
Non-trainable params: 0

我们可以看到,VGG16的卷积基一共有14714688个参数,其上添加的分类器一共有200万个参数,非常多。

在编译和训练模型之前,需要冻结卷积基。冻结一个或多个层是指在训练过程中保持其权重不变。如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改。因为其上添加的Dense是随机初始化的,所以非常打的权重更新会在网络中进行传播,对之前学到的表示造成很大破坏。

在Keras中,冻结网络的方法是将其trainable属性设置为False

print('This is the number of trainable weights ''before freezing the conv base:', len(model.trainable_weights))

This is the number of trainable weights before freezing the conv base: 30

conv_base.trainable = False
print('This is the number of trainable weights ''after freezing the conv base:', len(model.trainable_weights))

This is the number of trainable weights after freezing the conv base: 4

如此设置之后,只有添加的两个Dense层的权重才会被训练,总共有4个权重张量,每层2个(主权重矩阵和偏置向量),注意的是,如果想修改权重属性trainable,那么应该修改好属性之后再编译模型。

下面,我们可以训练模型了,并使用数据增强的办法:

from keras.preprocessing.image import ImageDataGeneratortrain_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')# Note that the validation data should not be augmented!
test_datagen = ImageDataGenerator(rescale=1./255)train_generator = train_datagen.flow_from_directory(# This is the target directorytrain_dir,# All images will be resized to 150x150target_size=(150, 150),batch_size=20,# Since we use binary_crossentropy loss, we need binary labelsclass_mode='binary')validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(150, 150),batch_size=20,class_mode='binary')model.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=2e-5),metrics=['acc'])history = model.fit_generator(train_generator,steps_per_epoch=100,epochs=30,validation_data=validation_generator,validation_steps=50,verbose=2)
model.save('cats_and_dogs_small_3.h5')

我们再来看看验证精度:

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()plt.figure()plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()plt.show()



验证精度到了将近96%,而且减少了过拟合。


微调模型

我们下面使用模型微调,进一步提高模型的性能。模型微调的步骤如下:

  • (1)在已经训练好的基网络(base network)上添加自定义网络
  • (2)冻结基网络
  • (3)训练所添加的部分
  • (4)解冻基网络的一些层
  • (5)联合训练解冻的这些层和添加的部分

在做特征提取的时候已经完成了前三个步骤。我们继续第四个步骤,先解冻conv_base,然后冻结其中的部分层。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         (None, 150, 150, 3)       0
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 150, 150, 64)      1792
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 150, 150, 64)      36928
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 75, 75, 64)        0
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 75, 75, 128)       73856
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 75, 75, 128)       147584
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 37, 37, 128)       0
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 37, 37, 256)       295168
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 37, 37, 256)       590080
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 37, 37, 256)       590080
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 18, 18, 256)       0
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 18, 18, 512)       1180160
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 18, 18, 512)       2359808
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 18, 18, 512)       2359808
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 9, 9, 512)         0
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 9, 9, 512)         2359808
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 9, 9, 512)         2359808
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 9, 9, 512)         2359808
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0

再回顾一下这些层,我们将微调最后三个卷积层,也就是说,知道block4_pool的所有层都应该被冻结,后面三层来进行训练。

要知道,训练的参数越多,过拟合的风险越大。卷积基有1500万个参数,所以你在小型数据集上训练这么多参数是有风险的。因此,这种情况下最好的策略是仅微调卷积基最后三两层。

conv_base.trainable = Trueset_trainable = False
for layer in conv_base.layers:if layer.name == 'block5_conv1':set_trainable = Trueif set_trainable:layer.trainable = Trueelse:layer.trainable = False

现在可以微调网络了,我们将使用学习率非常小的RMSProp优化器来实现。之所以让学习率很小,是因为对于微调网络的三层表示,我们希望其变化范围不要太大,太大的权重可能会破坏这些表示。

model.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-5),metrics=['acc'])history = model.fit_generator(train_generator,steps_per_epoch=100,epochs=100,validation_data=validation_generator,validation_steps=50)
model.save('cats_and_dogs_small_4.h5')

下面,绘制曲线看看效果:

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()plt.figure()plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()plt.show()



这些曲线看起来包含噪音。为了让图像更具有可读性,可以让每个损失精度替换为指数移动平均,从而让曲线变得更加平滑,下面用一个简单实用函数来实现:

def smooth_curve(points, factor=0.8):smoothed_points = []for point in points:if smoothed_points:previous = smoothed_points[-1]smoothed_points.append(previous * factor + point * (1 - factor))else:smoothed_points.append(point)return smoothed_pointsplt.plot(epochs,smooth_curve(acc), 'bo', label='Smoothed training acc')
plt.plot(epochs,smooth_curve(val_acc), 'b', label='Smoothed validation acc')
plt.title('Training and validation accuracy')
plt.legend()plt.figure()plt.plot(epochs,smooth_curve(loss), 'bo', label='Smoothed training loss')
plt.plot(epochs,smooth_curve(val_loss), 'b', label='Smoothed validation loss')
plt.title('Training and validation loss')
plt.legend()plt.show()


通过指数移动平均,验证曲线变得更清楚了。可以看到,精度提高了1%,约从96%提高到了97%。

下面,在测试集上评估一下这个模型

test_generator = test_datagen.flow_from_directory(test_dir,target_size=(150, 150),batch_size=20,class_mode='binary')test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)

Found 1000 images belonging to 2 classes.
test acc: 0.967999992371

得到了差不多97%的测试精度,在关于这个数据集的原始Kaggle竞赛中,这个结果是最佳结果之一。
值得注意的是,我们只是用了一小部分训练数据(约10%)就得到了这个结果。训练20000个样本和训练2000个样本还是有很大差别的。

更多精彩内容,欢迎关注我的微信公众号:数据瞎分析

使用预训练的卷积神经网络(猫狗图片分类)相关推荐

  1. python狗图像识别_TensorFlow卷积神经网络之使用训练好的模型识别猫狗图片

    本文是Python通过TensorFlow卷积神经网络实现猫狗识别的姊妹篇,是加载上一篇训练好的模型,进行猫狗识别 本文逻辑: 我从网上下载了十几张猫和狗的图片,用于检验我们训练好的模型. 处理我们下 ...

  2. Python深度学习(使用预训练的卷积神经网络)--学习笔记(十一)

    5.3 使用预训练的卷积神经网络 想要将深度学习应用于小型图像数据集,一种常用且非常高效的方法是使用预训练网络.预训练网络(pretrained network)是一个保存好的网络,之前已经在大型数据 ...

  3. PyTorch搭建预训练AlexNet、DenseNet、ResNet、VGG实现猫狗图片分类

    目录 前言 AlexNet DensNet ResNet VGG 前言 在之前的文章中,利用一个简单的三层CNN猫狗图片分类,正确率不高,详见: CNN简单实战:PyTorch搭建CNN对猫狗图片进行 ...

  4. 11.CNN实现真实猫狗图片分类

    CNN实现真实猫狗图片分类 个人认为,和上一节的mnist数据集里面的手写数字图片不同之处就是,真实的图片更加复杂,像素点更多.因此在对应的图片预处理方面会稍微麻烦一些.但是这个例子能让我们可以处理自 ...

  5. 体验AI乐趣:基于AI Gallery的二分类猫狗图片分类小数据集自动学习

    摘要:直接使用AI Gallery里面现有的数据集进行自动学习训练,很简单和方便,节约时间,不用自己去训练了,AI Gallery 里面有很多类似的有趣数据集,也非常好玩,大家一起试试吧. 本文分享自 ...

  6. python 图片比较 猫_TensorFlow卷积神经网络之使用训练好的模型识别猫狗图片

    本文逻辑: 我从网上下载了十几张猫和狗的图片,用于检验我们训练好的模型. 处理我们下载的图片 加载模型 将图片输入模型进行检验 代码如下: #coding=utf-8 import tensorflo ...

  7. 卷积神经网络——猫狗分类

    目录 一.搭建环境,完成猫狗分类 一)安装TensorFlow和Keras 1.Anaconda中安装 2.cmd中安装 二)猫狗分类实验 1.先制作数据集 2.卷积神经网络CNN 三)附加问题 二. ...

  8. 用卷积神经网络实现猫狗图片分类

    该例程使用数据集来源于 kaggle cat_VS _dog 数据集中的一部分, 用卷积神经网络实现猫狗图片二分类,例程序比较简单,就不多解释了,代码中会有相应的注释,直接上代码: import nu ...

  9. CNN之从头训练一个猫狗图片分类模型

    猫狗图片下载地址: 链接:https://pan.baidu.com/s/1l1AnBgkAAEhh0vI5_loWKw 提取码:2xq4 说明:大概有816M大小,分为train和test,trai ...

最新文章

  1. 替代TabActivity,底部菜单主框架搭建
  2. 【Linux 操作系统】 Secure CRT 终端配置 -- 配置语法高亮 光标 和 字体
  3. LeetCode - 141. 环形链表
  4. 自定义vue-cli生成项目模板配置(1)
  5. react项目_如何从零开始创建React项目(三种方式)
  6. linux ipset 流量,linux中ipset命令的使用方法详解
  7. cordova指定版本_Cordova/Ionic构建android Gradle错误:支持的最小Gradle版本是2.14.1。当前版本是2.13...
  8. 【iCore3 双核心板_ uC/OS-III】例程二:任务的建立与删除
  9. JQuery EasyUI Layout 在from布局自适应窗口大小
  10. 剑指offer(C++)-JZ33:二叉搜索树的后序遍历序列(数据结构-树)
  11. 22. Element lang 属性
  12. PHP常用方法(不定时更新)
  13. 服务器音频文件缓存,音频文件如何缓存到本地,和播放缓存到本地的音频文件...
  14. windows 启动参数
  15. 听刘万祥老师讲“风险矩阵分析图”
  16. 2021年9月电子学会Python等级考试试卷(四级)考题解析
  17. 淘宝店铺店名起、分类如何定【太原网络营销师】教你
  18. 《低风险投资之路》书中的精髓:低风险不等于低收益。
  19. x / k向上取整转换为向下取整
  20. 信创办公–基于WPS的PPT最佳实践系列 (将幻灯片组织成节的形式)

热门文章

  1. python mysql 编码方式,Python3编码与mysql编码介绍
  2. Kubernetes Docker 容器网络终极之战(十四)
  3. 使用Spring Data Redis操作Redis(集群版)
  4. 使用Denyhost来阻止恶意连接SSH的IP
  5. 利用tinyproxy在Linux上搭建HTTP Proxy Server
  6. springcloud-06-feign的使用
  7. 使用 Node.js、Express、AngularJS 和 MongoDB 构建一个Web程序
  8. 数据库生成T4模版在代码生成中的应用心得
  9. CentOS-6.4-x86_64 下安装mysql5.6
  10. java例程练习(批量修改文件后缀名)