目录
一、引言
(一)Overfit(过拟合)含义
1.简单理解就是训练样本的得到的输出和期望输出基本一致,但是测试样本输出和测试样本的期望输出相差却很大 。
2.为了得到一致假设而使假设变得过度复杂称为过拟合。想像某种学习算法产生了一个过拟合的分类器,这个分类器能够百分之百的正确分类样本数据(即再拿样本中的文档来给它,它绝对不会分错),但也就为了能够对样本完全正确的分类,使得它的构造如此精细复杂,规则如此严格,以至于任何与样本数据稍有不同的文档它全都认为不属于这个类别!
如果数据本身呈现二次型,故用一条二次曲线拟合会更好。但普通的PLS程序只提供线性方程供拟合之用。这就产生拟合不足即“欠拟合”现象,从而在预报时要造成偏差。如果我们用人工神经网络拟合,则因为三层人工神经网络拟合能力极强,有能力拟合任何函数。如果拟合彻底,就会连实验数据点分布不均匀,实验数据的误差等等“噪声”都按最小二乘判据拟合进数学模型。这当然也会造成预报的偏差。这就是“过拟合”的一个实例了
(二)数据增强
1、什么是数据增强
数据增强主要用来防止过拟合,用于dataset较小的时候。
之前对神经网络有过了解的人都知道,虽然一个两层网络在理论上可以拟合所有的分布,但是并不容易学习得到。因此在实际中,我们通常会增加神经网络的深度和广度,从而让神经网络的学习能力增强,便于拟合训练数据的分布情况。在卷积神经网络中,有人实验得到,深度比广度更重要。
然而随着神经网络的加深,需要学习的参数也会随之增加,这样就会更容易导致过拟合,当数据集较小的时候,过多的参数会拟合数据集的所有特点,而非数据之间的共性。那什么是过拟合呢,之前的博客有提到,指的就是神经网络可以高度拟合训练数据的分布情况,但是对于测试数据来说准确率很低,缺乏泛化能力。
因此在这种情况下,为了防止过拟合现象,数据增强应运而生。当然除了数据增强,还有正则项/dropout等方式可以防止过拟合。
2、常见的数据增强方法
- 随机旋转:随机旋转一般情况下是对输入图像随机旋转[0,360)。
- 随机裁剪:随机裁剪是对输入图像随机切割掉一部分。
- 色彩抖动:色彩抖动指的是在颜色空间如RGB中,每个通道随机抖动一定的程度。在实际的使用中,该方法不常用,在很多场景下反而会使实验结果变差。
- 高斯噪声:是指在图像中随机加入少量的噪声。该方法对防止过拟合比较有效,这会让神经网络不能拟合输入图像的所有特征。
- 水平翻转
- 竖直翻转
随机裁剪/随机旋转/水平反转/竖直反转都是为了增加图像的多样性。并且在某些算法中,如faster RCNN中,自带了图像的翻转。
在实验中我们发现,一个小数据集通过数据增强方法后,loss和accuracy反而都增加了。这可能对于初学者来说比较困惑,因为同样的网络结构可以拟合一个较大的数据集,却不能拟合一个小的数据集。有人给出了解释说,因为经过了数据增强后,dataset更容易学习了,所以虽然迭代次数一致,但是大的数据集更容易学习到收敛,小的数据集学的要慢一些。如果增加迭代次数,两者都将达到一个很高的拟合程度。还有人说,这是因为加入了正则项的原因,导致小的数据集不能过拟合了。
在训练时,我们还发现,当batchsize不变时,经过了数据增强后的数据集容易造成更大的波动。这主要是因为,如果数据增强是把1张图片变成5张,batchsize都为5,那么在validation的时候,小数据集每个batchsize的5张图片都不同,因此全部错误的概率很低,但是经过了数据增强后的数据集,有很大可能5张图片来自于同一张或同两张原始图片,因此可能要对都对,要错都错,这也就是为什么会波动很大的原因。因此,或许我们可以对经过数据增强后的数据集训练的batchsize也增大同样的倍数。
二、相关数据下载及TensorFlow、Keras、Jupyter Notebook库的搭建
(一)搭建配置TensorFlow、Keras、Jupyter Notebook
1、打开Anaconda Prompt,安装tensorflow库
conda --version //检查Anaconda是否成功安装(如果成功会显示版本号)
conda create -n tensorflow pip python=3.6 //创建名为tensorflow的conda环境 注意:这里需要将Python版本改成自己的版本号
activate tensorflow //激活TensorFlow
激活成功显示如下:
安装TensorFlow的纯CPU版本
pip install --ignore-installed --upgrade tensorflow
可能会报错:E:\MyDownloads\Anaconda3\Anaconda3\envs\tensorflow\Scripts\pip-script.py”, line 6, in from pip._internal.cli.main import mainModuleNotFoundError: No module named ‘pip._internal.cli.main’
解决方法:
easy_install pip
如果出现以下问题:
那就参考一下这位朋友的解决方法吧:https://blog.csdn.net/qq_30722795/article/details/103231442
安装好之后退出tensorflow环境
deactivate
查看tensorflow的环境是否已经被成功添加
conda info --envs
2、安装keras库
pip install keras
3、打开Anaconda Navigator安装jupyter notebook
第一次打开需要install,因为我是下载好的,所以这里是可以直接launch的,这里install的时候最好用手机热点下载。
如果下载的时候弹错error窗口的话,需要为 Anaconda 配置国内镜像源。
如果给 Anaconda 配置了环境变量,可以直接在 cmd 窗口中执行;如果没有配置环境变量,则在 Anaconda Prompt 中执行,我这里没有配置环境变量,在Anaconda Prompt 中执行如下命令,为 Anaconda 配置清华大学镜像源。
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --set show_channel_urls yes
然后下载应该就没有什么问题了。
(二)Kaggle狗猫数据集下载
猫狗数据集下载链接:https://pan.baidu.com/share/init?surl=l1AnBgkAAEhh0vI5_loWKw
提取码:2xq4
三、猫狗数据集原始数据直接训练和数据增强后训练
(一)创建三个子集的新数据集
1、打开jupyter,创建python3,导入keras,查看版本信息
import keras
keras.__version__
2、下载猫狗数据集并解压缩后,我们将创建一个包含三个子集的新数据集:一个包含每个类1000个样本的训练集,一个包含每个类500个样本的验证集,最后一个包含每个类500个样本的测试集,通过如下代码可以实现该步骤:
import os, shutil
# The path to the directory where the original
# dataset was uncompressed
original_dataset_dir = 'C:\\Users\\asus\\Desktop\\人工智能与机器学习\\卷积神经网络图像分类\\train'
# The directory where we will
# store our smaller dataset
base_dir = 'C:\\Users\\asus\\Desktop\\人工智能与机器学习\\卷积神经网络图像分类\\cat_and_dog_small'
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)
3、打印新数据集的尺寸
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张测试图像。在每一次分割中,来自每个类的样本数量都是相同的:这是一个平衡的二元分类问题,这意味着分类的准确性将是衡量成功的一个合适的标准。
(二)构建小型卷积网络
在前面的示例中,我们已经为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优化器。由于我们用一个单一的乙状结肠单元结束我们的网络,我们将使用二进制交叉熵作为我们的损失(作为提醒,查看第4章第5节中的表格,了解在各种情况下使用什么损失函数)
from keras import optimizers
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
(三)数据预处理
正如我们现在所知道的,在将数据输入到我们的网络之前,应该将数据格式化为经过适当预处理的浮点张量。目前,我们的数据以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('C:\\Users\\asus\\Desktop\\人工智能与机器学习\\卷积神经网络图像分类\\cats_and_dog_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正则化)。现在我们将介绍一种新的方法,专门针对计算机视觉,在深度学习模型处理图像时几乎普遍使用:数据增强。
(四)数据增强
过度拟合是由于可供学习的样本太少,使我们无法训练一个模型来泛化到新的数据。给定无限的数据,我们的模型将暴露于手头数据分布的每一个可能方面:我们永远不会过度拟合。数据增强采用的方法是从现有的训练样本中生成更多的训练数据,方法是通过一系列随机变换来“增强”样本,从而产生看上去可信的图像。我们的目标是在训练时,我们的模型不会两次看到完全相同的图像。这有助于将模型暴露于数据的更多方面,并更好地泛化。
在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('C:\\Users\\asus\\Desktop\\人工智能与机器学习\\卷积神经网络图像分类\\cat_and_dog_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、构建网络
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()
(二)构建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个值的向量
(三)将猫狗数据集传递给神经网络
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)
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()
(四)参数调优
从上面可以看出,经过一百多万张图片训练的网络,其识别效果就要比我们用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)
开始训练:
这里训练的时间会偏长,请耐心等待…
以上就是狗猫数据集完成原始数据直接训练和数据增强后训练全部内容了,如有错误请帮忙指正,谢谢大家~