卷积网络,也叫convent。
5.1 卷积神经网络简介
#5-1 实例化一个小型卷积神经网络
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3,3), activation ='relu', input_shape=(28,28,1)))#卷积神经网络接收形状为(image_height, image_width, image_channels)的输入张量,不包含批量维度
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(64,(3,3),activation='relu'))
#每个Conv2D和MaxPooling2D层输出都是一个形状为(height,width,channels)的3D张量,宽度和高度催着网络加深而变小,通道熟练个传入Conv2D层的第一个参数所控制(32或者64)
model.summary()
#5-2 在卷积神经网络上添加分类器
#在上面网络的输出张量(3,3,64),输入到一个密集连接分类器网络中,即Dense层的堆叠。
model.add(layers.Flatten())#分类器处理1D向量,因此当输入是3D张量时,首先要将其展平为1D
#在进入两个Dense层之前,形状(3,3,64)的输出被展平为形状(576,)的向量
model.add(layers.Dense(64, activation='relu'))#在上面添加Dense
model.add(layers.Dense(10, activation='softmax'))#使用带10个输出的softmax激活
model.summary()
#5-3 在MNIST图像上训练卷积神经网络
from keras.datasets import mnist
from keras.utils import to_categorical
(train_images, train_labels),(test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape(( 60000, 28, 28, 1))
train_images = train_images.astype('float32')/255
test_images = test_images.reshape(( 10000, 28, 28, 1))
test_images = test_images.astype('float32')/255
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy',metrics=['accuracy'] )
model.fit(train_images, train_labels, epochs=5, batch_size=64)
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(test_acc)
密集连接层和卷积层的根本区别在于,Dense层从输入特征空间学习到的是全局模式(涉及到所有像素的模式),而卷积层学习到的是局部模式,对于图像来说,学习到的就是在输入图像的二维小窗口中发现的模式。
包含两个空间轴(高度和宽度)和一个深度轴(通道轴)的3D张量,其卷积也叫特征图(feature map),(RGB深度就是3,黑白图深度就是1)。
卷积运算从输入特征图中提取图块,并对所有这些图块应用相同的变换,生成输出特征图(output feature map)。
该输出特征图依旧是一个3D张量,具有宽和高度,其深度可以任意取值,因为输出深度是层的参数,深度轴的不同通道不再像RGB输入那样代表特征颜色,而是代表过滤器(filter)过滤器对于输入数据的某一方面进行编码。
第一个卷积层输入(28,28,1)特征图,输出是(26,26,32)特征图,它在输入上计算32个过滤器。对于32个输出通道,每个通道包含一个26*26的数值网格,它是过滤器对输入的响应图(response map),表示这个过滤器模式在输入中不同位置的响应。特征图:深度轴的每个维度都是一个特征值(或过滤器),而2D张量output[:,:,n]是这个过滤器在输入上的响应的二维空间图,map
卷积关键参数
Keras的Conv2D层,这些参数都是向层传入的前几个参数:Conv2D(output_depth, (window_height, window_width))。
卷积工作原理:在3D输入特征图上滑动(slide)这些,3*3或 5*5的窗口,在每个可能的位置停止并提取周围特征的3D图块【形状为(window_height, window_width, input_depth)】。然后每个3D图块与学到的同一个权重矩阵[叫做卷积核(convolution kernel)]张量积,转换成形状为(output_depth,)的1D向量。
然后对所有这些向量进行空间重组,使其转换为形状为(height , width,output_depth)的3D输出特征图。输出特征图中的每个空间位置都对应于输入特征图中的相同位置。
输出宽度和高度于输入宽度和高度不同:
1、理解边界效应与填充
如果希望输出特征图的空间维度与输入相同,那么可以使用填充(padding)。填充是在输入特征图的每一遍添加适当数目的行和列,使得每个输入方块都能作为卷积窗口的中心。
Conv2D层,可以通过padding参数来设置填充,这个参数有两个取值:“valid”表示不使用填充。“same”表示“填充后输出的宽度和高度与输入相同”。padding参数默认值为“valid”
2、理解卷积步幅
两个连续窗口的距离是卷积的一个参数,叫做步幅,默认值为1。也可以使用步进卷积(strided convolution),即步幅大于1的卷积。
一般很少使用步幅,对特征图向进行下采样,通常使用最大池化(max-pooling)运算。
每个MaxPooling2D层之后,特征图的尺寸都会减半。最大池化的作用:对特征图进行下采样,与步进卷积类似。
最大池化是从输入特征图中提取窗口,并输出每个通道的最大值。它的概念与卷及类似,但是最大池化使用硬编码的max装量运算对局部图像进行变换,而不是使用学到的线性变换(卷积核)。最大池化与卷积核最大的不同之处在于,最大池化通常使用2*2的窗口和步幅2,其目的是将特征图下采样2倍,于此相对的是,卷积通常使用3*3窗口和步幅1.
#没有最大池化层的卷积基(convolutional base)
model_no_max_pool = models.Sequential()
model_no_max_pool.add(layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)))
model_no_max_pool.add(layers.Conv2D(64, (3,3), activation='relu'))
model_no_max_pool.add(layers.Conv2D(64, (3,3), activation='relu'))
model_no_max_pool.summary()
没有最大池化层:
使用下采样的原因,一是减少需要处理的特征图元素的个数,二是通过让连续卷积层的观察窗口越来越大(即窗口覆盖原始输入的比例越来越大),从而引入空间过滤器的层级结构。
特征中往往编码了某种模式或概念在特征图的不同位置是否存在(因此得名特征图),而观察不同特征的最大值而不是平均值能够给出更多的信息。
最合理的子采样策略首先生成密集的特征图(通过无步进的卷积),然后观察特征每个小图块的最大激活,而不是查看输入的稀疏窗口(通过步进卷积)或对输入图块取平均,因为后两种方法可能大致错过或淡化特征是否存在的信息。
在样本上训练一个简单的小型卷积神经网络,不做任何正则化,为模型目标设定一个基准,此时最主要的问题在于过拟合。
三种策略降低过拟合:
如果模型很小,并做了很好的正则化,同时任务非常简单,那么几百个样本可能就足够了。
卷积神经网学到的是局部的、平移不变特征,他对于感知问题可以高效的利用数据。
猫狗分类数据集合。https://www.kaggle.com/c/dogs-vs-cats/data
25000张猫狗图像(每个类别都有12500张),创建一个新的数据集:每个类别各1000个样本的训练集,每个类别各500个月样本的验证集和每个类别各500个样本的测试集。
import os, shutil
#原始数据集解压目录的路径
original_dataset_dir = 'E:/workspace/kaggle_original_data/train' #原始数据集解压目录的路径
base_dir = 'E:/workspace/kaggle_original_data/createDataSet'#保存较小数据集的目录
if not os.path.isdir(base_dir):
os.mkdir(base_dir)
train_dir = os.path.join(base_dir,'train')#划分后的训练目录
if not os.path.isdir(train_dir):
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir,'validation')#划分后的验证目录
if not os.path.isdir(validation_dir):
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir,'test')#划分后的测试目录
if not os.path.isdir(test_dir):
os.mkdir(test_dir)
train_cats_dir = os.path.join(train_dir, 'cats')#猫的训练图像目录
if not os.path.isdir(train_cats_dir):
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir,'dogs')#狗的训练图像目录
if not os.path.isdir(train_dogs_dir):
os.mkdir(train_dogs_dir)
validation_cats_dir = os.path.join(validation_dir,'cats')#猫的验证图像目录
if not os.path.isdir(validation_cats_dir):
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir,'dogs')#狗的验证图像目录
if not os.path.isdir(validation_dogs_dir):
os.mkdir(validation_dogs_dir)
test_cats_dir = os.path.join(test_dir,'cats')#猫的测试图像目录
if not os.path.isdir(test_cats_dir):
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')#狗的测试图像目录
if not os.path.isdir(test_dogs_dir):
os.mkdir(test_dogs_dir)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]#将前1000张猫的图像复制到train_cats_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src,dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000,1500)]#将接下来的500张猫的图像复制到validation_cats_dir
for fname in fnames:
src = os.path.join(original_dataset_dir,fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]#将接下来的500张猫的图像复制到test_cats_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]#将前1000张狗的图像复制到train_dogs_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]#将接下来500张狗的图像复制到validation_dogs_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src,dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1500,2000)]#将接下来的500张狗的图像复制到test_dogs_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
#检查一下看看每个分组(训练/验证/测试)中分别包含多少张图像。
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(test_cats_dir))
print('total test dog images:',len(test_dogs_dir))
复用相同的总体结构,即卷积神经网络由Conv2D层(使用relu激活)和MaxPooling2D层交替堆叠构成。
由于处理的是更大的图像和更复杂的问题,因此需要增大网络,即再增加一个Conv2D+MaxPooling2D的组合,既可以增大网络容量,也可以进一步减小特征图的尺寸,使其在连接Flatten层时尺寸不会太大。
网络中特征图的深度在逐渐增大(从32增大到128),而特征图的尺寸在逐渐减小。
二分类问题,因此网络最后一层使用的sigmoid激活的单体单元,这个单元将对某个类别的概率进行编码。
import os, shutil
#原始数据集解压目录的路径
original_dataset_dir = 'E:/workspace/kaggle_original_data/train' #原始数据集解压目录的路径
base_dir = 'E:/workspace/kaggle_original_data/createDataSet'#保存较小数据集的目录
if not os.path.isdir(base_dir):
os.mkdir(base_dir)
train_dir = os.path.join(base_dir,'train')#划分后的训练目录
if not os.path.isdir(train_dir):
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir,'validation')#划分后的验证目录
if not os.path.isdir(validation_dir):
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir,'test')#划分后的测试目录
if not os.path.isdir(test_dir):
os.mkdir(test_dir)
train_cats_dir = os.path.join(train_dir, 'cats')#猫的训练图像目录
if not os.path.isdir(train_cats_dir):
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir,'dogs')#狗的训练图像目录
if not os.path.isdir(train_dogs_dir):
os.mkdir(train_dogs_dir)
validation_cats_dir = os.path.join(validation_dir,'cats')#猫的验证图像目录
if not os.path.isdir(validation_cats_dir):
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir,'dogs')#狗的验证图像目录
if not os.path.isdir(validation_dogs_dir):
os.mkdir(validation_dogs_dir)
test_cats_dir = os.path.join(test_dir,'cats')#猫的测试图像目录
if not os.path.isdir(test_cats_dir):
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')#狗的测试图像目录
if not os.path.isdir(test_dogs_dir):
os.mkdir(test_dogs_dir)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]#将前1000张猫的图像复制到train_cats_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src,dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000,1500)]#将接下来的500张猫的图像复制到validation_cats_dir
for fname in fnames:
src = os.path.join(original_dataset_dir,fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]#将接下来的500张猫的图像复制到test_cats_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]#将前1000张狗的图像复制到train_dogs_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]#将接下来500张狗的图像复制到validation_dogs_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src,dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1500,2000)]#将接下来的500张狗的图像复制到test_dogs_dir
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
#检查一下看看每个分组(训练/验证/测试)中分别包含多少张图像。
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(test_cats_dir))
print('total test dog images:',len(test_dogs_dir))
# 代码清单 5-5 将猫狗分类的小型卷积神经网络实例化
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'))
model.summary()
#5-6 配置模型用于训练
from keras import optimizers
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])
将数据输入神经网络之前,应该将数据格式化为经过预处理的浮点数张量。
Python生成器(Python generator)是一个类似于迭代器的对象,一个可以和for in 运算符一起使用的对象。生成器使用yield运算符来构造的。
def generator():
i = 0
while True:
i += 1
yield i
for item in generator():
print(item)
if item > 4:
break
#5-7 使用ImageDataGenerator从目录中读取图像
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(rescale=1./255)#将所有图像乘以1/255缩放
test_datagen = ImageDataGenerator(rescale=1./255)#将所有图像乘以1/255缩放
traing_generator = train_datagen.flow_from_directory(train_dir, target_size=(150,150),batch_size=20,class_mode='binary')#将目标目录里面所有图像大小调整为150*150,因为使用了binary_crossentropy损失,所以需要用二进制标签
validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(150,150), batch_size=20, class_mode='binary')#生成了形状为(20,150,150,3)的RGB图像和形状为(20,)的二进制标签组成的批量,生成器会不断循环文件夹中的图像,因此需要定义终止break循环。
for data_batch, labels_batch in traing_generator:
print('data batch shape:', data_batch.shape)
print('labels batch shape:', labels_batch.shape)
break
利用生成器,让模型对数据进行拟合。使用fit_generator方法拟合的效果和fit相同。
#5-8 利用批量生成器拟合模型
history = model.fit_generator(traing_generator, steps_per_epoch=100, epochs=30,validation_data=validation_generator,validation_steps=50)
#在数据生成器上的效果和fit效果相同,
#param1:python生成器,可以不停的生成输出和目标组成的批量。
#steps_per_epoch要知道每一轮需要从生成器中抽取多少个批量(运行了steps_per_epoch次梯度下降),拟合过程将进入下一个轮次。
#epochs训练轮数
#validation_data验证数据生成器
#validation_steps从验证生成器中抽取多少个批次用于评估
#5-9 保存模型
model.save('cates_and_dogs_small_1.h5')
#5-10 绘制训练过程中的损失曲线和精度曲线
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='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuray')
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()
数据曾巧是从现有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加(augment)样本,目的是,模型在训练时不会两次查看完全相同的图像,让模型观测到更多数据,从而得到更好的泛化能力。
#5-11 利用ImageDataGenerator来设置数据增强
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')
#rotation_range是角度值(0~180范围内),表示图像随机旋转的角度范围。
#width_shift和height_shift是图像在水平或垂直方向上平移的范围(相对于总宽度和总高度的比例)
#shear_range是随机错切变换的角度
#zoom_range是图像随机放缩的范围
#horizontal_filp是随机降一半图像水平翻转,如果没有水平不对称的假设,这种做法是有意义的。
#fill_mode是用于填充新创建像素的方法,这些新像素可能来自于旋转或宽度/高度平移。
#5-12 显示几个随机增强后的训练图像
train_cats_dir = os.path.join(train_dir, 'cats')#猫的训练图像目录
from keras.preprocessing import image#图像预处理工具的模块
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]
img_path = fnames[3] #选择一张图像进行增强
img = image.load_img(img_path, target_size=(150,150))#读取图像并调整大小
x = image.img_to_array(img)#将其转换为形状为(150,150,3)的Numpy数组
x = x.reshape((1,) + x.shape)#将其形状改变为(1, 150, 150, 3)
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()
网络将不会两次看到同样的输入,无法生成新的信息,而是只能混合现有信息。不足以完全消除过拟合,为了进一步减低过拟合,需要向模型中添加一个Dropout层,添加到密集连接分类器之前。
#5-13 定义一个包含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.MaxPool2D((2,2)))
model.add(layers.Conv2D(128, (3,3), activation='relu'))
model.add(layers.MaxPool2D((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'])
#5-14 利用数据增强生成器训练卷积神经网络
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,)
test_datagen = ImageDataGenerator(rescale=1./255)#不能增强验证数据
train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150,150), batch_size= 32, class_mode='binary')#目标目录,将所有图像的大小调整为150*150,因为使用了binary_crossentropy损失,所以需要用二进制标签
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-15 保存模型
model.save('cats_and_dogs_small_2.h5')
预训练网络(pretrained network)。使用预训练网络有两种方法:
特征提取(feature extraction)和微调模型(fine-tuning)
特征提取是使用之前网络学习到的表示来从新样本中提取出有趣的特征。然后讲这些特征输入一个新的分类器,从头开始训练。
用于图像分类的卷积神经网络包含两部分:首先是一系列池化层和卷积层,最后一个是密集连接分类器。第一部分叫作模型的卷积基(convolutional base)。对于卷积神经网络而言,特征提取就是去除之前训练好的网络的卷积基,在上面运行新数据,然后在输出上面训练一个新的分类器。
卷积基学到的表示可能更加通用,因此更适合重复使用。卷积神经网络的特征图表示通用概念在图像中是否存在。
分类器学到的表示必然是针对模型训练的类别,其中仅包含某个类别出现在整张图像中的概率信息。
密集连接层的表示不再包含物体在输入图像中的位置信息。
密集连接层舍弃了空间的概念,而物体位置信息仍然由卷积特征图所描述。如果物体位置对于问题很重要,那么密集连接层的特征在很大程度上是无用的。
某个卷积层提取的表示的通用性(以及可重复性)屈居于该层在模型中的深度。模型中更靠近底部的层(先加入的层)提取的是局部的、高度通用的特征图(不如视觉边缘、颜色和纹理),而更靠近顶部的层(后加入的层)提取的是更加抽象的概念(比如猫耳朵或狗眼睛),因此你的新数据集与原始模型训练的数据集有很大差异,那么最好只是用模型的前几层来做特征提取而不是使用整个卷积基。
#5-16 将VGG16卷积基实例化
from keras.applications import VGG16
conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3))
#weights指定模型初始化的权重检查点
#include_top指定模型是否包含密集连接分类器。默认情况下该默认分类器对应于ImageNet的1000个类别,因为我们打算使用自己的密集连接分类器(只有两个类别:cat和dog),所以不需要包含它。
#input_shape输入到网络中的图像张量的形状,改参数可选,如果不传入这个参数,那么网络能够处理任意形状的输入。
conv_base.summary()
最后的特征图形状为(4,4,512),将在这个特征上添加一个密集连接分类器。
保存你的数据在conv_base中输出,然后将这些输出作为输入用于新的模型
1、不使用数据增强的快速特征提取
首先运行imageDataGenerator实例,将图像及其标签提取为Numpy数组,需要调用conv_base模型的predict方法来从这些图像中提取特征。
#5-17 使用预训的卷积基提取特征
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = 'E:/workspace/kaggle_original_data/createDataSet'
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_szie = 20
def extract_features(directory, sample_count):
features = np.zeros(shape=(sample_count, 4, 4, 512))#提取的特征形状(samples,4,4,512)
labels = np.zeros(shape=(sample_count))
generator = datagen.flow_from_directory(directory, target_size=(150, 150), batch_size=batch_szie,class_mode='binary')
i=0
for inputs_batch, labels_batch in generator:
features_batch = conv_base.predict(inputs_batch)
features[i * batch_szie : (i + 1) * batch_szie] = features_batch
labels[i * batch_szie :(i + 1) * batch_szie] = labels_batch
i +=1
if i * batch_szie>= sample_count:
break
return features, labels
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)
#输入到密集连接分类器中,首先将形状展平为(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))
#5-18 定义并训练密集连接分类器
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))
#5-19 绘制结果
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='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.legend()
plt.show()
2. 使用数据增强的特征提取
速度更慢,计算代价更高,但是在训练期间可以使用数据增强。扩展conv_base模型,然后输入数据上端到端的运行模型。
在编译和训练模型之前,一定要“冻结”卷积基,冻结(freeze)一个或多个层是指在训练过程中保持其权重不变。因为如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改。因为其上添加的Dense层时随机初始化的,所有非常大的权证更新将会在网络中传播,对之前学到的表示造成很大的破坏。
为了让修改生效,必须先编译模型。