现实场景中,我们可能获得的带标注的样本会比较小,因此可以利用一些机构提供的预训练网络进行图片分类,这种做法可以利用同行的研究成果,并极大缩短训练时间。以下是利用VGG16进行猫狗分类的一个例子。
导入keras的一些需要的库:
from keras.applications import VGG16
import os
import numpy as np
from keras.preprocessing import image
from keras.preprocessing.image import ImageDataGenerator
from keras import models
from keras import layers,Input
from keras import optimizers
import matplotlib.pyplot as plt
from keras.models import load_model,Model
import keras
设定工作目录,当然该目录下要放好继续训练的样本集、验证集和测试集图片:
base_dir='.\\cat_dog_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')
由于图片比较大,不太可能把所有图片一次装载入内存,因此设计一个迭代器,可以根据目录下的图片随机生成批量数据集,并且通过图片增强的方式增加样本数量,提升泛化能力。
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')
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')
test_generator = validation_datagen.flow_from_directory(test_dir,target_size=(150,150),batch_size=20,class_mode='binary')
利用VGG16的卷积层以及预训练好的参数,注意卷积层的参数先设定为不可训练。然后增加一个分类器,最后一级根据自己需要的分类选择dense的节点数量。
conv_base=VGG16(weights='imagenet',include_top=False)
conv_base.trainable=False
x_input = Input(shape=(150,150,3))
x = conv_base(x_input)
x = layers.Flatten()(x)
x = layers.Dense(512,activation='relu')(x)
x = layers.Dropout(0.5)(x)
feature_output = layers.Dense(1,activation='sigmoid')(x)
model = Model(x_input,feature_output)
model.summary()
设定回调函数,当满足条件时提前结束训练、改变学习率、保存最好的参数:
callbacks_list = [
#训练精度没下降,终止
keras.callbacks.EarlyStopping(
monitor='acc',
patience=10
),
#保存最好的模型
keras.callbacks.ModelCheckpoint(
filepath='cats_and_dogs_small_vgg_loss.h5',
monitor='val_loss',
save_weights_only=True,
save_best_only='True'),
keras.callbacks.ModelCheckpoint(
filepath='cats_and_dogs_small_vgg_acc.h5',
monitor='val_acc',
save_weights_only=True,
save_best_only='True'),
#减小学习率
keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',
factor=0.3,
patience = 5
)
]
编译模型,并开始训练分类器:
model.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])
history=model.fit_generator(train_generator,
steps_per_epoch=200,
epochs=200,
callbacks=callbacks_list,
validation_data=validation_generator,
validation_steps=50,
verbose=1)
Epoch 31/200
200/200 [==============================] - 97s 485ms/step - loss: 0.3003 - acc: 0.8742 - val_loss: 0.2566 - val_acc: 0.9040
Epoch 32/200
200/200 [==============================] - 97s 485ms/step - loss: 0.2858 - acc: 0.8817 - val_loss: 0.2565 - val_acc: 0.9040
Epoch 33/200
200/200 [==============================] - 97s 485ms/step - loss: 0.2903 - acc: 0.8742 - val_loss: 0.2568 - val_acc: 0.9030
Epoch 34/200
200/200 [==============================] - 97s 485ms/step - loss: 0.2799 - acc: 0.8812 - val_loss: 0.2567 - val_acc: 0.9030
Epoch 35/200
200/200 [==============================] - 97s 485ms/step - loss: 0.2804 - acc: 0.8782 - val_loss: 0.2569 - val_acc: 0.9030
训练结束时发现训练精度无法提升,直觉感觉应该是仅训练分类器是不够的,因为卷积层可以发现很对局部特征,而卷积层参数被固定,无法进行训练,因此下一步准备把最后几层的卷积层设定成可训练,重新编译模型开始训练。
#微调网络
#model.load_weights('cats_and_dogs_small_vgg.h5')
conv_base.trainable=True
set_trainable=False
for layer in conv_base.layers:
if layer.name=='block4_conv1':
set_trainable=True
if set_trainable==True:
layer.trainable=True
else:
layer.trainable=False
model.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=3e-5),metrics=['acc'])
history=model.fit_generator(train_generator,
steps_per_epoch=200,
epochs=200,
callbacks=callbacks_list,
validation_data=validation_generator,
validation_steps=50,
verbose=1)
将训练过程的验证精度和损失可视化
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']
epochs = range(1,len(loss_values)+1)
plt.plot(epochs,loss_values,'bo',label='Train_loss')
plt.plot(epochs,val_loss_values,'b',label='Validation_loss')
plt.title('Training and Validition loss')
plt.legend()
plt.figure()
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']
plt.plot(epochs,acc_values,'bo',label='Train_acc')
plt.plot(epochs,val_acc_values,'b',label='Validation_acc')
plt.legend()
plt.show()
最后导入最好的模型参数,在测试集上看看精度如何。
model.load_weights('cats_and_dogs_small_vgg_loss.h5')
test_loss1,test_acc1 = model.evaluate_generator(test_generator, steps=50)
print('test acc1: ',test_acc1)
model.load_weights('cats_and_dogs_small_vgg_acc.h5')
test_loss2,test_acc2 = model.evaluate_generator(test_generator, steps=50)
print('test acc2: ',test_acc2)
test acc1: 0.9439999902248383
test acc2: 0.9589999938011169
对比自己搭建的简单的卷积网络82%左右的测试精度,还是提升很大的。
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.Dropout(0.4))
model.add(layers.Dense(1,activation='sigmoid'))
model.summary()