基于jupyter notebook的python编程-----猫狗数据集的阶段分类得到模型精度并进行数据集优化


本次博客内容已经属于目前热门的深度学习目标识别技术,请仔细学习每一行代码,理解训练、测试、数据增加、模型优化这些经典套路,接下来,就带小伙伴进行学习吧!

一、名词解释

1、什么是overfit(过拟合)?

1)、简单理解就是训练样本的得到的输出和期望输出基本一致,但是测试样本输出和测试样本的期望输出相差却很大 。
2)、为了得到一致假设而使假设变得过度复杂称为过拟合。想像某种学习算法产生了一个过拟合的分类器,这个分类器能够百分之百的正确分类样本数据(即再拿样本中的文档来给它,它绝对不会分错),但也就为了能够对样本完全正确的分类,使得它的构造如此精细复杂,规则如此严格,以至于任何与样本数据稍有不同的文档它全都认为不属于这个类别!

如果数据本身呈现二次型,故用一条二次曲线拟合会更好。但普通的PLS程序只提供线性方程供拟合之用。这就产生拟合不足即“欠拟合”现象,从而在预报时要造成偏差。如果我们用人工神经网络拟合,则因为三层人工神经网络拟合能力极强,有能力拟合任何函数。如果拟合彻底,就会连实验数据点分布不均匀,实验数据的误差等等“噪声”都按最小二乘判据拟合进数学模型。这当然也会造成预报的偏差。这就是“过拟合”的一个实例了

2、什么是数据增强?

1)、数据集增强主要是为了减少网络的过拟合现象,通过对训练图片进行变换可以得到泛化能力更强的网络,更好的适应应用场景。

数据增强也叫数据扩增,意思是在不实质性的增加数据的情况下,让有限的数据产生等价于更多数据的价值

2)、常用的数据增强方法有:

  • 旋转 | 反射变换(Rotation/reflection): 随机旋转图像一定角度; 改变图像内容的朝向;
  • 翻转变换(flip): 沿着水平或者垂直方向翻转图像;
  • 缩放变换(zoom): 按照一定的比例放大或者缩小图像;
  • 平移变换(shift): 在图像平面上对图像以一定方式进行平移;
  • 可以采用随机或人为定义的方式指定平移范围和平移步长, 沿水平或竖直方向进行平移. 改变图像内容的位置;
  • 尺度变换(scale): 对图像按照指定的尺度因子, 进行放大或缩小; 或者参照SIFT特征提取思想, 利用指定的尺度因子对图像滤波构造尺度空间. 改变图像内容的大小或模糊程度;
  • 对比度变换(contrast): 在图像的HSV颜色空间,改变饱和度S和V亮度分量,保持色调H不变. 对每个像素的S和V分量进行指数运算(指数因子在0.25到4之间), 增加光照变化;
  • 噪声扰动(noise): 对图像的每个像素RGB进行随机扰动, 常用的噪声模式是椒盐噪声和高斯噪声;
  • 颜色变化: 在图像通道上添加随机扰动。
  • 输入图像随机选择一块区域涂,黑参考《Random Erasing Data Augmentation》
    在这里插入图片描述

3、什么是猫狗数据集?

1)、猫狗数据集包括训练集和测试集,其中包括的是各种猫咪和狗狗的大量照片数据,用于进行阶段分类和求解训练模型的精度,其中大约有1万张猫狗图片数据!

二、猫狗数据集下载

1、猫狗数据集下载

1)、百度网盘资源链接
猫狗数据集下载请点击此处:网盘提取码:2xq4
2)、猫狗数据集的内容:
下载的zip文件解压后,会包括一个测试集和训练集,其中就包含了各种猫狗数据集,大约800多M,百度网盘下载速度可想而知,如下所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、数据集文件处理

1)、为了匹配本次博客,小伙伴需要将解压后的猫狗数据集放在新建的文件夹中,并重新命名为dogs-vs-cats,例如,林君学长放在E盘下:
在这里插入图片描述

3、需要的python库安装

1)、打开windows的终端cmd窗口,进行tensorflow库安装

pip install tensorflow

在这里插入图片描述
以上是tensorflow需要安装的东西,林君学长之前已经安装好了!
2)、keras库的安装

pip install keras

在这里插入图片描述
3)、在jupyter中查看tensorflow和keras是否安装配置完毕
(1)、打开jupyter,创建python3,导入keras,查看版本信息

import keras
keras.__version__

(2)、查看结果
在这里插入图片描述

三、猫狗数据集的两阶段分类实验(计算准确率、召回率、F1-score)

1、创建三个子集的新数据集

1)、下载猫狗数据集并解压缩后,我们将创建一个包含三个子集的新数据集:一个包含每个类1000个样本的训练集,一个包含每个类500个样本的验证集,最后一个包含每个类500个样本的测试集,通过如下代码可以实现该步骤:

import os, shutil
# The path to the directory where the original
# dataset was uncompressed
original_dataset_dir = 'E:\\dogs-vs-cats\\train\\train'
# The directory where we will
# store our smaller dataset
base_dir = 'E:\\dogs-vs-cats1'
os.mkdir(base_dir)
# Directories for our training,
# validation and test splits
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
# Directory with our training cat pictures
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
# Directory with our training dog pictures
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
# Directory with our validation cat pictures
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
# Directory with our validation dog pictures
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
# Directory with our validation cat pictures
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
# Directory with our validation dog pictures
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
# Copy first 1000 cat images to train_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    shutil.copyfile(src, dst)
# Copy next 500 cat images to validation_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_cats_dir, fname)
    shutil.copyfile(src, dst)
# Copy next 500 cat images to test_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_cats_dir, fname)
    shutil.copyfile(src, dst)    
# Copy first 1000 dog images to train_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_dogs_dir, fname)
    shutil.copyfile(src, dst)    
# Copy next 500 dog images to validation_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_dogs_dir, fname)
    shutil.copyfile(src, dst)    
# Copy next 500 dog images to test_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_dogs_dir, fname)
    shutil.copyfile(src, dst)

2)、打印新数据集的尺寸

print('total training cat images:', len(os.listdir(train_cats_dir)))
print('total training dog images:', len(os.listdir(train_dogs_dir)))
print('total validation cat images:', len(os.listdir(validation_cats_dir)))
print('total validation dog images:', len(os.listdir(validation_dogs_dir)))
print('total test cat images:', len(os.listdir(test_cats_dir)))
print('total test dog images:', len(os.listdir(test_dogs_dir)))

在这里插入图片描述

我们确实有2000张训练图像,1000张验证图像和1000张测试图像。在每一次分割中,来自每个类的样本数量都是相同的:这是一个平衡的二元分类问题,这意味着分类的准确性将是衡量成功的一个合适的标准

2、构建网络

在前面的示例中,我们已经为MNIST构建了一个小型卷积网,所以您应该熟悉它们。我们将重用相同的通用结构:我们的卷积网将是一个交替的Conv2D(激活relu)和MaxPooling2D层的堆栈。
然而,由于我们处理的是更大的图像和更复杂的问题,因此我们将使我们的网络相应地更大:它将有一个更多的Conv2D + MaxPooling2D阶段。这样既可以扩大网络的容量,又可以进一步缩小特征图的大小,这样当我们到达平坦层时,特征图就不会太大。在这里,由于我们从大小为150x150的输入开始(有点随意的选择),我们在Flatten层之前得到大小为7x7的feature map。

注意:feature map的深度在网络中逐渐增加(从32到128),而feature map的大小在减少(从148x148到7x7)。这是你会在几乎所有convnets中看到的模式。
由于我们解决的是一个二元分类问题,我们用一个单一单元(一个大小为1的稠密层)和一个s型激活来结束网络。这个单元将对网络正在查看一个类或另一个类的概率进行编码。
1)、构建小型卷积网络

from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

2)、了解征图的尺寸是如何随着每一层变化的

model.summary()

在这里插入图片描述
3)、对于编译步骤,我们将像往常一样使用RMSprop优化器。由于我们的网络是以一个单一的sigmoid单元结束的,所以我们将使用二元交叉矩阵作为我们的损失

from keras import optimizers

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

3、数据预处理

正如您现在所知道的,在将数据输入到我们的网络之前,应该将数据格式化为经过适当预处理的浮点张量。目前,我们的数据以JPEG文件的形式保存在硬盘上,因此将其导入网络的步骤大致如下:

  • 读取图片文件。
  • 解码JPEG内容到RBG像素网格。
  • 把它们转换成浮点张量。
  • 将像素值(从0到255)缩放到[0,1]区间(如您所知,神经网络更喜欢处理小的输入值)。

这看起来可能有点令人畏惧,但是谢天谢地,Keras有一些实用程序来自动处理这些步骤。Keras有一个包含图像处理辅助工具的模块,位于Keras
.preprocessing.image。特别是,它包含类ImageDataGenerator,它允许快速设置Python生成器,这些生成器可以自动地将磁盘上的图像文件转换为一批预处理的张量。这就是我们要用的。

1)、数据预处理

from keras.preprocessing.image import ImageDataGenerator

# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=20,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

在这里插入图片描述
2)、查看生成器输出

for data_batch, labels_batch in train_generator:
    print('data batch shape:', data_batch.shape)
    print('labels batch shape:', labels_batch.shape)
    break

在这里插入图片描述
我们可以看其中一个生成器的输出:它生成一批150x150的RGB图像(shape(20, 150, 150, 3))和二进制标签(shape(20,))。20是每批样品的数量(批次尺寸)。请注意,生成器会无限期地生成这些批:它只是在目标文件夹中出现的图像上无休止地循环。由于这个原因,我们需要在某一点中断迭代循环。
3)、使用生成器使我们的模型适合于数据

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50)

在这里插入图片描述
在这里插入图片描述
我们使用fit_generator方法来完成此操作,对于我们这样的数据生成器,它相当于fit方法。它期望Python生成器作为第一个参数,它将无限期地生成成批的输入和目标,就像我们的示例一样。因为数据是不断生成的,所以在宣告一个纪元结束之前,生成器需要知道示例从生成器中抽取多少样本。这就是steps_per_epoch参数的作用:在从生成器中绘制完steps_per_epoch批处理之后,即在运行完steps_per_epoch梯度下降步骤之后,拟合过程将转到下一个epoch。在我们的例子中,批次是20个样本大,所以在我们看到2000个样本的目标之前将需要100个批次。

在使用fit_generator时,可以传递validation_data参数,就像fit方法一样。重要的是,允许这个参数本身是一个数据生成器,但是它也可以是Numpy数组的元组。如果您传递一个生成器作为validation_data,那么这个生成器将会不断生成成批的验证数据,因此您还应该指定validation_steps参数,它告诉流程从验证生成器提取多少批来进行评估。

4)、保存我们的模型

model.save('cats_and_dogs_small_1.h5')

以上是当前路径下,小伙伴可以更改为自己的路径,比如“E:\cats_and_dogs_small_1.h5”
5)、在训练和验证数据上绘制模型的损失和准确性

import matplotlib.pyplot as plt
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()

在这里插入图片描述
这些图具有过拟合的特点。我们的训练精度随着时间线性增长,直到接近100%,而我们的验证精度停留在70-72%。我们的验证损失在5个epoch后达到最小,然后停止,而训练损失继续线性下降,直到接近0。
因为我们只有相对较少的训练样本(2000),过度拟合将是我们首要关心的问题。你已经知道了一些技术,可以帮助减轻过度拟合,如dropout和重量衰减(L2正则化)。现在我们将介绍一种新的方法,专门针对计算机视觉,在深度学习模型处理图像时几乎普遍使用:数据增强。

4、使用数据增强

过度拟合是由于可供学习的样本太少,使我们无法训练一个模型来泛化到新的数据。给定无限的数据,我们的模型将暴露于手头数据分布的每一个可能方面:我们永远不会过度拟合。数据增强采用的方法是从现有的训练样本中生成更多的训练数据,方法是通过一系列随机变换来“增强”样本,从而产生看上去可信的图像。我们的目标是在训练时,我们的模型不会两次看到完全相同的图像。这有助于将模型暴露于数据的更多方面,并更好地泛化。
在Keras中,这可以通过配置一系列随机转换来完成,这些转换将对ImageDataGenerator实例所读取的图像执行。让我们以一个例子开始:
1)、图像数据生成器增强数据

datagen = ImageDataGenerator(
      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')

这些只是可用的选项中的一部分(更多信息,请参阅Keras文档)。刚刚写的参数含义如下:

  • rotation_range是一个角度值(0-180),在这个范围内可以随机旋转图片。
  • width_shift和height_shift是范围(作为总宽度或高度的一部分),在其中可以随机地垂直或水平地转换图片。
  • shear_range用于随机应用剪切转换。
  • zoom_range用于在图片内部随机缩放。
  • horizontal_flip是用于水平随机翻转一半的图像——当没有假设水平不对称时(例如真实世界的图片)。
  • fill_mode是用于填充新创建像素的策略,它可以在旋转或宽度/高度移动之后出现。

2)、查看增强后的图像

# This is module with image preprocessing utilities
from keras.preprocessing import image
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]
# We pick one image to "augment"
img_path = fnames[3]
# Read the image and resize it
img = image.load_img(img_path, target_size=(150, 150))
# Convert it to a Numpy array with shape (150, 150, 3)
x = image.img_to_array(img)
# Reshape it to (1, 150, 150, 3)
x = x.reshape((1,) + x.shape)
# The .flow() command below generates batches of randomly transformed images.
# It will loop indefinitely, so we need to `break` the loop at some point!
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break
plt.show()

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
如果我们使用这种数据增加配置训练一个新的网络,我们的网络将永远不会看到两次相同的输入。然而,它看到的输入仍然是高度相关的,因为它们来自少量的原始图像——我们不能产生新的信息,我们只能混合现有的信息。因此,这可能还不足以完全消除过度拟合。
3)、为了进一步对抗过拟合,我们还将在我们的模型中增加一个Dropout层,就在密集连接分类器之前:

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

4)、用数据增强和退出来训练我们的网络:

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,)
# 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 directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=32,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')
history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)

在这里插入图片描述
在这里插入图片描述
5)、保存我们的模型——将在convnet可视化部分使用它

model.save('cats_and_dogs_small_2.h5')

保存在当前路径下,也可以保存到其他路径下哦
6)、画出结果

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()

在这里插入图片描述
由于数据的增加和遗漏,我们不再过度拟合:训练曲线相当紧密地跟踪验证曲线。我们现在能够达到82%的精度,相对于非正则化模型有15%的改进。
通过进一步利用正则化技术和调整网络参数(比如每个卷积层的滤波器数量,或者网络中的层数),我们可能能够获得更好的精度,可能达到86-87%。

然而,仅仅通过从零开始训练我们自己的卷积神经网络来达到更高的高度是非常困难的,因为我们可以使用的数据太少了。作为提高这个问题准确性的下一步,我们必须利用一个预先训练好的模型,这将是接下来两部分的重点。

四、优化并提高猫狗图像分类模型精度

在我们构造卷积网络时,一开始先是好几层卷积层和Max Pooling层,然后会调用Flatten()把他们输出的多维向量压扁后,传入到普通层

1、构建卷积网络

1)、下面代码就是我们前几节做过的卷积网络,它的结构正如我们刚才描述的那样

from keras import layers
from keras import models
from keras import optimizers
model = models.Sequential()
#输入图片大小是150*150 3表示图片像素用(R,G,B)表示
model.add(layers.Conv2D(32, (3,3), activation='relu', input_shape=(150 , 150, 3)))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4),
             metrics=['acc'])
model.summary()

在这里插入图片描述

2、构建VGG16网络

现在要借用的的VGG16网络,其结构与上面差不多,只不过它的Conv2D和MaxPooling层要比我们上面做的多得多而已。在我们借用别人训练好的网络时,往往要去掉Flatten()后面的网络层,因为那些网络层与别人构造网络时的具体应用场景相关,他们的应用场景与我们肯定不同,我们要借用的是Flatten上面那些由卷积层和Max Pooling层输出的结果,这些结果蕴含着对训练图片本质的认知,这才是我们想要的,去掉Flatten后面的神经层,换上我们自己的神经层,这个行为就叫特征抽取,具体流程如下图:
在这里插入图片描述
1)、初始化一个VGG16网络实例

from keras.applications import VGG16
conv_base = VGG16(weights = 'imagenet', include_top = False, input_shape=(150, 150, 3))
conv_base.summary()
  • weight参数告诉程序将网络的卷积层和max pooling层对应的参数传递过来,并将它们初始化成对应的网络层次
  • include_top表示是否也要把Flatten()后面的网络层也下载过来,VGG16对应的这层网络用来将图片划分到1000个不同类别中,由于我们只用来区分猫狗两个类别,因此我们去掉它这一层
  • input_shape告诉网络,我们输入图片的大小是150*150像素,每个像素由[R, G, B]三个值表示

2)、首次运行时候,会自动从对应网站下载h5格式文件
在这里插入图片描述
上面下载很慢,而且还有可能在中途挂掉,因此建议将网址复制到手机上面,然后通过手机下载,一下就下载好了,手机下载好之后,上传到电脑,然后放到当前jupyter目录下,最后,将上面代码修改为如下:

from keras.applications import VGG16
conv_base = VGG16(weights = 'vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5', include_top = False, input_shape=(150, 150, 3))
conv_base.summary()

执行结果如下所示:
在这里插入图片描述
VGG16的网络结构与我们前面做的网络差不多,只不过它的层次要比我们多不少。最后的(None, 4, 4, 512)表示它将输出44的矩阵,而这些矩阵有512层,或者你也可以看成它将输出一个44的矩阵,而矩阵每个元素是包含512个值的向量

3、将猫狗数据集传递给神经网络

1)、将步骤三产生的新建的猫狗数据集传递给神经网络,让它把图片的隐含信息给抽取出来

import os 
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = 'E:\\dogs-vs-cats1'
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 = 20
def 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 = 0
    for inputs_batch, labels_batch in generator:
        #把图片输入VGG16卷积层,让它把图片信息抽取出来
        features_batch = conv_base.predict(inputs_batch)
        #feature_batch 是 4*4*512结构
        features[i * batch_size : (i + 1)*batch_size] = features_batch
        labels[i * batch_size : (i+1)*batch_size] = labels_batch
        i += 1
        if i * batch_size >= sample_count :
            #for in 在generator上的循环是无止境的,因此我们必须主动break掉
            break
        return features , labels
#extract_features 返回数据格式为(samples, 4, 4, 512)
train_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)

在这里插入图片描述
上面代码利用VGG16的卷积层把图片的特征抽取出来
2)、把抽取的特征输入到我们自己的神经层中进行分类

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 optimizers
#构造我们自己的网络层对输出数据进行分类
model = 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))

在这里插入图片描述
在这里插入图片描述
由于我们不需要训练卷积层,因此上面代码运行会很快
3)、画出训练结果和校验结果

import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label = 'Train_acc')
plt.plot(epochs, val_acc, 'b', label = 'Validation acc')
plt.title('Trainning 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()

在这里插入图片描述

4、参数调优

从上面可以看出,经过一百多万张图片训练的网络,其识别效果就要比我们用4000张图片训练的网络要好很多,网络对图片的校验正确率达到了99%以上,同时对训练数据和校验数据的损失估计完全是一模一样的。

上面的方法叫特征提取,还有一种方法叫参数调优。特征提取时,我们把图片输入VGG16的卷积层,让他直接帮我们把图片中的特征提取出来,我们并没有通过自己的图片去训练更改VGG16的卷积层,参数调优的做法在于,我们会有限度的通过自己的数据去训练VGG16提供的卷积层,于是让其能从我们的图片中学习到相关信息。我们从VGG16模型中获取了它六层卷积层,我们在调优时,让这六层卷积层中的最高2层也去学习我们的图片,于是最高两层的链路权重参数会根据我们的图片性质而更改,基本情况如下:
在这里插入图片描述
1)、参数调优步骤
参数调优的步骤如下:

  1. 将我们自己的网络层添加到VGG16的卷积层之上。
  2. 固定VGG16的卷积层保持不变。
  3. 用数据训练我们自己添加的网络层
  4. 将VGG16的卷积层最高两层放开
  5. 用数据同时训练放开的那两层卷积层和我们自己添加的网络层

2)、通过代码理解上面步骤所要描述的意思

model = models.Sequential()
#将VGG16的卷积层直接添加到我们的网络
model.add(conv_base)
#添加我们自己的网络层
model.add(layers.Flatten())
model.add(layers.Dense(256, activation = 'relu'))
model.add(layers.Dense(1, activation = 'sigmoid'))
model.summary()

在这里插入图片描述
上图可以看出,VGG16的卷积层已经有一千多万个参数了!用个人电脑单个CPU是不可能对这个模型进行训练的!但我们可以训练它的其中一部分
3)、把它最高三层与我们自己的网络层结合在一起训练,同时冻结最低四层

conv_base.trainable = True
set_trainable = False
#一旦读取到'block5_conv1'时,意味着来到卷积网络的最高三层
#可以使用conv_base.summary()来查看卷积层的信息
for layer in conv_base.layers:
    if layer.name == 'block5_conv1':
        set_trainable = True
    if set_trainable:
        #当trainable == True 意味着该网络层可以更改,要不然该网络层会被冻结,不能修改
        layer.trainable = True
    else:
        layer.trainable = False

上面的代码将会把卷积层进行部分冻结
4)、数据传入网络,训练给定的卷积层和我们自己的网络层

#把图片数据读取进来
test_datagen = ImageDataGenerator(rescale = 1. / 255)
train_generator = test_datagen.flow_from_directory(train_dir, target_size = (150, 150), batch_size = 20,
                                                   class_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(2e-5),
             metrics = ['acc'])

history = model.fit_generator(train_generator, steps_per_epoch = 100, epochs = 30, 
                              validation_data = validation_generator,
                              validation_steps = 50)

在这里插入图片描述
由于训练的时间实在太长,林君学长放弃了,有兴趣的小伙伴可以自己将模型训练出来然后通过上面讲到的保存方法将训练模型保存下来哦,这里就只展示未训练完的图吧!
以上就是本次博客的全部内容啦,希望通过对本次博客的阅读,可以帮助小伙伴深入了解猫狗数据集的阶段分类,同时,通过python代码学习模型精度求解,通过代码了解原理,是程序员的必备技能啦!
遇到问题的小伙伴记得留言评论哦,林君学长看到会为大家进行解答的,这个学长不太冷

陈一月的又一天编程岁月^ _ ^

猜你喜欢

转载自blog.csdn.net/qq_42451251/article/details/106657643