5.使用预训练的卷积训练神经网络
想要将深度学习应用于小型数据集上,一种非常有效的方法是使用预训练的网络
预训练网络是指之前在大型数据集上训练好的网络,比如在ImageNet上训练了一个网络(其类别主要是动物和日用品),然后将这个网络应用于其他识别的图片(比如家具上)。这种学到的特征在不同问题之间具有可移植性,是深度学习的重要优势,它是的深度学习对小数据问题也很有效
使用预训练的网络有两种方法:特征提取,微调模型
(1)特征提取
下面使用ImageNet数据集上预训练得到VGG16网络的卷积基用于猫狗图像中提取有趣的特征,然后在这些特征上训练一个猫狗分类器(只有卷积基是可以重复使用的,密集连接分类器是避免多次复用的,原因在于卷积基学到的表示可能更加通用,因此更适合重复使用)
VGG16等模块内置于keras.applications模块中
from keras.applications import VGG16
conv_base = VGG16(weights = 'imagenet',#制定模式初始化的权重点
include_top = False,#指定模型最后是否包含密集连接分类器
input_shape = (150,150,3))#指定输入网络中的图形张量的形状
conv_base.summary()
程序会自动下载VGG16模型
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_3 (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
_________________________________________________________________
文件自动下载到C盘下.keras/models文件夹下
可以看到最后提取的特征是(4,4,512)的形状,我们在这个特征上添加一个密集分类器
在数据集上运行卷积基,将输出保存成硬盘中的Numpy数组,然后用这个数据作为输入,输入到独立的密集连接分类器中,这种方法速度快,计算代价低,因为这样每次图像只用运行一次卷积基,卷积基是计算代价最高的,但是这种方法不能使用数据增强
先卷积运算,得到的特征保存下来,再输入到Dense层中分类:
from keras.applications import VGG16
conv_base = VGG16(weights = 'imagenet',#制定模式初始化的权重点
include_top = False,#指定模型最后是否包含密集连接分类器
input_shape = (150,150,3))#指定输入网络中的图形张量的形状
#conv_base.summary()
###不使用数据增强的快速特征提取
#使用预训练的卷积基提取特征
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
#在cats_and_dogs_small文件夹分别选中三个文件夹的路径,变量名可以直接表示路径了
base_dir = 'E:\\dataset\\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 = 20
#这是一个将图片,变成labels和features的函数,sample_count是图片数量,directory是文件夹名称
def extract_feature(directory,sample_count):
features = np.zeros(shape = (sample_count,4,4,512))
labels = np.zeros(shape = (sample_count))
#构造一个生成器,不断生成20个一批的大小为(150,150,3)的数据
generator = datagen.flow_from_directory(directory,
target_size = (150,150),
batch_size = batch_size,
class_mode = 'binary')
i = 0
#生成器不断生成inputs_batch,labels_batch就是20个一批的(150,150)大小的输入和labels
for inputs_batch,labels_batch in generator:
#将这些inputs_batch和labels_batch变成features和labels
features_batch = conv_base.predict(inputs_batch)#利用predict方法从这些图像中提取特征
features[i*batch_size:(i+1)*batch_size] = features_batch
labels[i*batch_size:(i+1)*batch_size] = labels_batch
i = i+1
if i*batch_size >= sample_count:
break #生成器会不断循环,必须在读取完所有图片之后终止循环
return features,labels
train_features,train_labels = extract_feature(train_dir,2000)#将train文件夹里面的2000张图片提取特征,还有labels
validation_features,validation_labels = extract_feature(validation_dir,1000)#将validation文件夹里1000张图片提取特征,还有labels
test_features,test_labels = extract_feature(test_dir,1000)#将test文件夹里面1000张图片提取特征,还有labels
思路就是:从生成器中得到inputs_batch,labels_batch
inputs_batch得到features_batch得到features
labels_batch得到labels
到目前为止,提取的特征为(图片数量,4,4,512)
我们要把它输入到密集分类器中,首先将其形状展平为(图片数量,8192)
#将特征展平为(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))
定义密集连接分类器,训练
#定义密集连接分类器(用dropout正则化),并训练
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))
绘制图形
#绘制结果
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,'r',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,'r',label = 'Validation loss')
plt.title('Training and Validation loss')
plt.legend()
plt.show()
训练精度进一步提高,可以达到90%了
特征提取还有另一种方法,就是先用Dense层来扩展已有的模型,就是直接端到端的运行整个模型,不用分两步,先提取特征,再分类,直接把分类器加到卷积层下面,构成一个大的模型,这种方法计算代价大,但是可以使用数据增强
(这个计算量太大,没有做图像数据的增强):
###1.在已经训练好的基网络上添加自定义网络
from keras.applications import VGG16
conv_base = VGG16(weights = 'imagenet',#制定模式初始化的权重点
include_top = False,#指定模型最后是否包含密集连接分类器
input_shape = (150,150,3))#指定输入网络中的图形张量的形状
from keras import models
from keras import layers
model = 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()
两层密集连接层前面有一个VGG16的model
然后需要冻结网络,VGG16的卷积基有14714688各参数,非常多,冻结过程中,冻结多个层,使训练过程权重不变,不然Dense层的权重将会传到前面的网络中,对之前学到的模型造成破坏
###2.冻结基网络
print('number of trainable weights:',len(model.trainable_weights))
conv_base.trainable = False
print('number of trainable weights:',len(model.trainable_weights))
#这样设置之后,只有Dense层的权重才会被训练
number of trainable weights: 30
number of trainable weights: 4
可以看到冻结之前有30个权重张量要训练,之后只有四个权重张量,每个Dense层两个(主权重矩阵,偏置向量)
###3.训练所添加的部分
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
import os
base_dir = 'E:\\dataset\\cats_and_dogs_small'
train_dir = os.path.join(base_dir,'train')
validation_dir = os.path.join(base_dir,'validation')
train_datagen = ImageDataGenerator(rescale = 1./255)
validation_datagen = ImageDataGenerator(rescale = 1./255)
train_generator = train_datagen.flow_from_directory(train_dir,
target_size = (150,150),
batch_size = 20,
class_mode = 'binary')
validation_generator = validation_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)
#绘制结果
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,'r',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,'r',label = 'Validation loss')
plt.title('Training and Validation loss')
plt.legend()
plt.show()
model.save('cats_and_dogs_small_freeze.h5')
这里准确度达到了90%了
如果做了数据增强:
#做数据增强
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')
将精确度为90%的模型用于预测之前预测错了的图片:
之前错认为是狗,现在可以正确识别是猫了:
(流泪。。)

结果出来并没有书上的96%那么好。
(2)模型微调
另一种方法是模型微调,微调是指,冻结几层和新增加几层,新增加的是全连接分类器,冻结的是卷积层的前面几层
冻结VGG16的卷积基是为了能够在上面训练一个随机初始化的分类器,训练好了分类器之后,才能将上面的卷积层解冻,然后联合训练这所有的层
步骤:
1.在已经训练好的基网络(base network)上添加自定义网络
2.冻结基网络
3.训练所添加的部分
4.解冻及网络的冻结部分
5.联合训练解冻的这些层和天机的部分
前面三个步骤与特征提取是一样的,接下来需要微调最后三个卷积层
#4.冻结直到‘block5_conv1’的所有层
from keras import layers
from keras import optimizers
set_trainable = False
for layer in conv_base.layers:
if layer.name == 'block5_conv1':
set_trainable = True
if set_trainable == True:
layer.trainable = True
else:
layer.trainable = False
#导入前三部训练好的模型
from keras.models import load_model
model = load_model('cats_and_dogs_small_freeze.h5')
#5.模型微调
model.compile(loss = 'binary_crossentropy',
optimizer = optimizers.RMSprop(lr = 1e-5),
metrics = ['acc'])
history = model.fit_generator(train_generator,
steps_per_epoch = 100,
epochs = 30,
validation_data = validation_generator,
validation_steps = 50)
#绘制图像
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,'r',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,'r',label = 'Validation loss')
plt.title('Training and Validation loss')
plt.legend()
plt.show()
model.save('cats_and_dogs_small_freeze_weitiao.h5')
ResourceExhaustedError!
内存耗尽了。。。