背景
在上一篇文章我们引用了VGG16模型,通过删除其全连接层,再加上自己的全连接层进行训练,达到了90%的正确率。但是还有一种方法,就是微调模型,这个方法配合特征提取可以达到一个更好的高度。
什么是微调模型呢?就是通过解冻VGG16最后的几层,通过与训练好的全连接层联合训练的方式,来达到我们的目的,也就是对已经训练好的网络进行微调。
为什么只解冻后边几层呢?如果了解过卷积神经网络的话,我们可以知道,网络越是靠后,它提取出来的特征越是笼统,越是靠前,它提取的特征越散,总结来说修改哪里其实都是能提高正确率的,但是,修改前边的网络性价比很低,相比于修改后边的网络,效果不是那么显著,而且我觉得牵一发而动全身,前边的网络都改了后边的怎么能不改呢。
数据准备
猫狗识别的数据,kaggle比赛
训练集2000张
验证集1000张
步骤
- 在已经训练好的网络上添加自定义网络。(特征提取)
- 冻结基网络
- 训练所添加的部分
- 解冻基网络的一些层
- 联合训练解冻的这些层和添加的部分
在猫狗识别(三)中,我们实现了前三个步骤,使用VGG16网络+自己写一个全连接层,冻结VGG16网络后进行训练,训练好后将VGG16的卷积块5解冻,调小学习率,再进行训练。
代码
代码的改动不多,主要分为下边几个部分:
卷积块5的解冻:
conv_base.trainable = True
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
添加层:
model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(connection_layer)
中间加了一个扁平层,是因为卷积基输出的是一个多维数组,而我们训练全连接层的时候,设置的输入为44512的一维数组。所以转化一下就好了。
调小学习率
平滑图表
因为最终的输出曲线幅度过大,所以为了方便读取,所以通过一个函数使其平滑。
函数如下:
def smooth_curve(points, factor=0.6):
smooth_points = []
for point in points:
if smooth_points:
previous = smooth_points[-1]
smooth_points.append(previous * factor + point * (1 - factor))
else:
smooth_points.append(point)
return smooth_points
其中factor表示平滑度,越高越平滑,取值为[0,1]
整体代码如下
from keras.applications import VGG16
conv_base = VGG16(
weights='imagenet',#指定模型初始化检查点
include_top=False,#指定模型最后是否包含密集连接器
input_shape=(150,150,3)#指定输入到网络中图像的形状
)
#设置VGG16第五卷积块解冻,使其可训练
conv_base.trainable = True
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
connection_layer = load_model('./cats_and_dogs.h5')
connection_layer.trainable = True
import os
#指定地址
base_dir = 'F:\DeepLearn\cat_and_dog'
train_dir = os.path.join(base_dir,'train')
test_dir = os.path.join(base_dir,'test')
validation_dir = os.path.join(base_dir,'check')
#配置模型并训练,将之前特征提取训练好的模型和VGG16联合训练
from keras import layers
from keras import models
model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(connection_layer)
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
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')
test_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 = 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=1e-5),#因为只是微调模型,所以调小学习率
metrics=['acc'])
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=40,
validation_data=validation_generator,
validation_steps=50)
#保存模型
model.save('cats_and_dogs_small.h5')
#画出结果
import matplotlib.pyplot as plt
#查看变量,发现history.history中就只有这四个值,分别是准确度,验证集准确度,损失,验证集损失
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)
#画两个图,分别是正确率和损失
#平滑函数,输入一组数,输出平滑函数
#原理:当前数 = 前一个数 * factor + 当前数 * factor
#(ps:平滑是平滑了,但是我个人觉得factor不能调的太大,不然就失去图表本身的意义了)
def smooth_curve(points, factor=0.6):
smooth_points = []
for point in points:
if smooth_points:
previous = smooth_points[-1]
smooth_points.append(previous * factor + point * (1 - factor))
else:
smooth_points.append(point)
return smooth_points
#正确率
plt.figure(1)
plt.plot(epochs, smooth_curve(acc), 'bo', label='Training acc')
plt.plot(epochs, smooth_curve(val_acc), 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.savefig('acc2.png')
plt.show()
#损失
plt.figure(2)
plt.plot(epochs, smooth_curve(loss), 'bo', label='Training loss')
plt.plot(epochs, smooth_curve(val_loss), 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.savefig('loss2.png')
plt.show()
输出结果
这里用的是训练三十批次,输出的平滑度设置的是0.5,和程序中的不一样,想要的到好的结果,需要经常调整参数