(本文所使用的Python库和版本号: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, Keras 2.1.6, Tensorflow 1.9.0)html
上一篇文章咱们用本身定义的模型来解决了二分类问题,在20个回合的训练以后获得了大约74%的准确率,一方面是咱们的epoch过小的缘由,另一方面也是因为模型太简单,结构简单,故而不能作太复杂的事情,那么怎么提高预测的准确率了?一个有效的方法就是迁移学习。git
迁移学习其本质就是移花接木:将其余大数据集(好比ImageNet等)上获得的网络结构及其weights应用到一个新的项目中来,好比此处的猫狗二分类问题。固然,ImageNet中确定有猫和狗这两个类别,能够说此处的小数据集是ImageNet的一个子集,可是,对于和ImageNet彻底没有任何关系的其余数据集,迁移学习也有必定的效果,固然,对于两个数据集的相关性比较差的数据集,使用迁移学习可能效果不太好。github
具体作法是:使用一个成熟的网络结构(好比此处用VGG16)和参数,把它的全链接层所有都去掉,只保留卷积层,这些卷积层能够当作是图片的特征提取器(获得的特征被称为bottleneck features),而全链接层是分类器,对这些图片的特征进行有效分类。对于新项目,咱们要分类的类别数目并非ImageNet的1000类,而是好比此处的2类。故而分类器对咱们毫无用处,咱们须要建立和训练本身的分类器。以下为VGG16网络的结构:网络
其中的Conv block 1-5 都是卷积层和池化层,组成了图片的特征提取器,然后面的Flatten和Dense组成了分类器。app
此处咱们将Conv block 1-5的结构和参数都移接过来,在组装上本身的分类器便可。性能
在训练时,咱们能够先我上一篇博文同样,创建图片数据流,将图片数据流导入到VGG16模型中提取特征,而后将这些特征送入到自定义的分类器中训练,优化自定义分类器的参数,可是这种方式训练速度很慢,此处咱们用VGG16的卷积层统一提取全部图片的特征,将这些特征保存,而后直接加载特征来训练,加载数字比加载图片要快的多,故而训练也快得多。学习
我这篇博文主要参考了:keras系列︱图像多分类训练与利用bottleneck features进行微调(三),这篇博文也是参考的Building powerful image classification models using very little data,但我发现这两篇博文有不少地方的代码跑不起来,主要缘由多是Keras或Tensorflow升级形成的,因此我作了一些必要的修改。测试
首先使用预训练好的模型VGG16来提取train set和test set图片的特征,而后将这些特征保存,这些特征实际上就是numpy.ndarray,故而能够保存为数字,而后加载这些数字来训练。大数据
# 此处的训练集和测试集并非原始图片的train set和test set,而是用VGG16对图片提取的特征,这些特征组成新的train set和test set
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dropout, Flatten, Dense
from keras import applications
def save_bottlebeck_features():
datagen = ImageDataGenerator(rescale=1. / 255) # 不需图片加强
# build the VGG16 network
model = applications.VGG16(include_top=False, weights='imagenet')
# 使用imagenet的weights做为VGG16的初始weights,因为只是特征提取,故而只取前面的卷积层而不须要DenseLayer,故而include_top=False
generator = datagen.flow_from_directory( # 产生train set
train_data_dir,
target_size=(IMG_W, IMG_H),
batch_size=batch_size,
class_mode=None,
shuffle=False) # 必须为False,不然顺序打乱以后,和后面的label对应不上。
bottleneck_features_train = model.predict_generator(
generator, train_samples_num // batch_size) # 若是是32,这个除法获得的是62,抛弃了小数,故而获得1984个sample
np.save('E:\PyProjects\DataSet\FireAI\DeepLearning\FireAI006/bottleneck_features_train.npy', bottleneck_features_train)
print('bottleneck features of train set is saved.')
generator = datagen.flow_from_directory(
val_data_dir,
target_size=(IMG_W, IMG_H),
batch_size=batch_size,
class_mode=None,
shuffle=False)
bottleneck_features_validation = model.predict_generator(
generator, val_samples_num // batch_size)
np.save('E:\PyProjects\DataSet\FireAI\DeepLearning\FireAI006/bottleneck_features_val.npy',bottleneck_features_validation)
print('bottleneck features of test set is saved.')
复制代码
通过上面的代码,trainset图片集的特征被保存到E:\PyProjects\DataSet\FireAI\DeepLearning\FireAI006/bottleneck_features_train.npy文件中,而test set的特征也被保存到../bottleneck_features_val.npy中。优化
很显然,此处咱们并不要提取图片的各类特征,前面的VGG16已经帮咱们作完了这件事,因此咱们只须要对这些特征进行分类便可,因此至关于咱们只创建一个分类器模型就能够。
用keras创建一个简单的二分类模型,以下:
def my_model():
''' 自定义一个模型,该模型仅仅至关于一个分类器,只包含有全链接层,对提取的特征进行分类便可 :return: '''
# 模型的结构
model = Sequential()
model.add(Flatten(input_shape=train_data.shape[1:])) # 将全部data进行flatten
model.add(Dense(256, activation='relu')) # 256个全链接单元
model.add(Dropout(0.5)) # dropout正则
model.add(Dense(1, activation='sigmoid')) # 此处定义的模型只有后面的全链接层,因为是本项目特殊的,故而须要自定义
# 模型的配置
model.compile(optimizer='rmsprop',
loss='binary_crossentropy', metrics=['accuracy']) # model的optimizer等
return model
复制代码
模型虽然创建好了,但咱们要训练里面的参数,使用刚刚VGG16提取的特征来进行训练:
# 只须要训练分类器模型便可,不须要训练特征提取器
train_data = np.load('E:\PyProjects\DataSet\FireAI\DeepLearning\FireAI006/bottleneck_features_train.npy') # 加载训练图片集的全部图片的VGG16-notop特征
train_labels = np.array(
[0] * int((train_samples_num / 2)) + [1] * int((train_samples_num / 2)))
# label是1000个cat,1000个dog,因为此处VGG16特征提取时是按照顺序,故而[0]表示cat,1表示dog
validation_data = np.load('E:\PyProjects\DataSet\FireAI\DeepLearning\FireAI006/bottleneck_features_val.npy')
validation_labels = np.array(
[0] * int((val_samples_num / 2)) + [1] * int((val_samples_num / 2)))
# 构建分类器模型
clf_model=my_model()
history_ft = clf_model.fit(train_data, train_labels,
epochs=epochs,
batch_size=batch_size,
validation_data=(validation_data, validation_labels))
复制代码
-------------------------------------输---------出--------------------------------
Train on 2000 samples, validate on 800 samples Epoch 1/20 2000/2000 [==============================] - 6s 3ms/step - loss: 0.8426 - acc: 0.7455 - val_loss: 0.4280 - val_acc: 0.8063 Epoch 2/20 2000/2000 [==============================] - 5s 3ms/step - loss: 0.3928 - acc: 0.8365 - val_loss: 0.3078 - val_acc: 0.8675 Epoch 3/20 2000/2000 [==============================] - 5s 3ms/step - loss: 0.3144 - acc: 0.8720 - val_loss: 0.4106 - val_acc: 0.8588
.......
Epoch 18/20 2000/2000 [==============================] - 5s 3ms/step - loss: 0.0479 - acc: 0.9830 - val_loss: 0.5380 - val_acc: 0.9025 Epoch 19/20 2000/2000 [==============================] - 5s 3ms/step - loss: 0.0600 - acc: 0.9775 - val_loss: 0.5357 - val_acc: 0.8988 Epoch 20/20 2000/2000 [==============================] - 5s 3ms/step - loss: 0.0551 - acc: 0.9810 - val_loss: 0.6057 - val_acc: 0.8825
--------------------------------------------完-------------------------------------
将训练过程当中的loss和acc绘图以下:
很显然,在第5个epoch以后,train set和test set出现了很明显的分离,代表后面出现了比较强烈的过拟合,可是在test set上的准确率仍然有90%左右。
能够看出,相对上一篇文章咱们本身定义的三层卷积层+两层全链接层的网络结构,用VGG16网络结构的方法获得的准确率更高一些,并且训练所须要的时间也更少。
注意一点:此处咱们并无训练VGG16中的任何参数,而仅仅训练本身定义的分类器模型中的参数。
########################小**********结###############################
1,迁移学习就是使用已经存在的模型及其参数,使用该模型来提取图片的特征,而后构建本身的分类器,对这些特征进行分类便可。
2,此处咱们并无训练已存在模型的结构和参数,仅仅是训练自定义的分类器,若是要训练已存在模型的参数,那就是微调(Fine-tune)的范畴了
#################################################################
注:本部分代码已经所有上传到(个人github)上,欢迎下载。