预训练模型迁移学习

摘要: 本文经过使用Keras及一个预训练模型的实例,教你如何经过迁移学习快速简便地解决图像分类问题。python

摘要:如何快速简便地解决图像分类问题呢?本文经过使用Keras及一个预训练模型的实例,教你如何经过迁移学习来解决这个问题。数据库

 

深度学习正在迅速成为人工智能应用开发的主要工具。在计算机视觉、天然语言处理和语音识别等领域都已有成功的案例。服务器

深度学习擅长解决的一个问题是图像分类。图像分类的目标是根据一组合理的类别对指定的图片进行分类。从深度学习的角度来看,图像分类问题能够经过迁移学习的方法来解决。网络

本文介绍了如何经过迁移学习来解决图像分类的问题。本文中所提出的实现方式是基于Python语言的Keras。app

本文结构:机器学习

1)迁移学习函数

2)卷积神经网络工具

3)预训练模型的复用性能

4)迁移学习过程学习

5)深度卷积神经网络上的分类器

6)示例

7)总结

一、迁移学习

迁移学习在计算机视觉领域中是一种很流行的方法,由于它能够创建精确的模型,耗时更短。利用迁移学习,不是从零开始学习,而是从以前解决各类问题时学到的模式开始。这样,你就能够利用之前的学习成果(例如VGG、 Inception、MobileNet),避免从零开始。咱们把它看做是站在巨人的肩膀上。

在计算机视觉领域中,迁移学习一般是经过使用预训练模型来表示的。预训练模型是在大型基准数据集上训练的模型,用于解决类似的问题。因为训练这种模型的计算成本较高,所以,导入已发布的成果并使用相应的模型是比较常见的作法。

二、卷积神经网络(CNN)

在迁移学习中,经常使用的几个预训练模型是基于大规模卷积神经网络的(Voulodimos)。通常来讲,CNN被证实擅长于解决计算机视觉方面的问题。它的高性能和易训练的特色是最近几年CNN流行的两个主要缘由。

典型的CNN包括两个部分:

(1)卷积基,由卷积层和池化层的堆栈组成。卷积基的主要目的是由图像生成特征。为了直观地解释卷积层和池化层,请参阅Chollet 2017。

(2)分类器,一般是由多个全链接层组成的。分类器的主要目标是基于检测到的特征对图像进行分类。全链接层是其中的神经元与前一层中的全部激活神经元彻底链接的层。

图1显示了一个基于CNN的模型体系结构。这是一个简化的版本,事实上,这种类型的模型,其实际的体系结构比咱们在这里说的要复杂得多。

 

图1.基于CNN的模型体系结果

这些深度学习模型的一个重要方面是,它们能够自动学习分层的特征表示。这意味着由第一层计算的特征是通用的,而且能够在不一样的问题域中重用,而由最后一层计算的特征是较特殊的,而且依赖于所选择的数据集和任务。根据Yosinski等人在2014年提出的“若是第一层的特征是通用的而且最后一层的特征是特殊的,那么网络中一定有一个从通常到特殊的转变。所以,咱们的CNN的卷积基特别是它下面的层(那些更接近输入的层)适用于通常特征,而分类器部分和卷积基中一些较高的层适用于特殊的特征。

三、预训练模型的复用

当你根据本身的须要重用预训练模型时,首先要删除原始的分类器,而后添加一个适合的新分类器,最后必须根据如下的三种策略之一对模型进行微调:

(1)训练整个模型 在这种状况下,利用预训练模型的体系结构,并根据数据集对其进行训练。若是你从零开始学习模型,那么就须要一个大数据集,和大量的计算资源。

(2)训练一些层而冻结其它的层 较低层适用的是通用特征(独立问题),而较高层适用的是特殊特征。这里,咱们经过选择要调整的网络的权重来处理这两种状况。一般,若是有一个较小的数据集和大量的参数,你会冻结更多的层,以免过分拟合。相比之下,若是数据集很大,而且参数的数量不多,那么能够经过给新任务训练更多的层来完善模型,由于过分拟合不是问题了。

(3)冻结卷积基 这种状况适用于训练/冻结平衡的极端状况。其主要思想是将卷积基保持在原始形式,而后使用其输出提供给分类器。把你正在使用的预训练模型做为固定的特征提取途径,若是缺乏计算资源,而且数据集很小,或者预训练模型解决了你正要解决的问题,那么这就颇有用。

图2以图表的形式说明了这三种策略。

 

与策略3的这种直接简单的应用不一样,策略1和策略2要求你当心谨慎地应对卷积部分中使用的学习率。学习率是一个超参数,它控制你调整网络权重的大小。当你正在使用基于CNN的预训练模型时,使用一个低学习率是比较明智的,由于使用高学习率会增长失去以前所积累知识的风险。假设预训练模型通过了比较好的训练,保持低学习率将确保你不会过早和过分地调整CNN权重。

四、迁移学习过程

从实践的角度来看, 整个迁移学习过程能够归纳以下:

(1)选择预训练模型.从大量的预训练模型,能够选择一个适合你要解决的问题的。若是你正在使用Keras,能够当即使用一系列模型,例如VGG、InceptionV3和ResNet5。点击这里你能够看到Keras上全部的模型

(2)根据大小类似性矩阵进行分类.在图3中你用矩阵来控制选择,这个矩阵是根据数据集的大小,以及预训练模型被训练以后的数据集的类似性,来对计算机视觉问题进行分类的。根据经验,若是每一个类的图像少于1000个,则认为是小数据集。就数据集的类似性而言,常识占了上风。例如,若是是识别猫和狗,那么ImageNet将是一个相似的数据集,由于它有猫和狗的图像。然而,若是是识别癌细胞,ImageNet就不行了。

(3)微调模型.这里可使用大小和类似性矩阵来指导你的选择,而后参考前面提到的重用预训练模型的三个策略。请见图4。

象限1.大数据集,但不一样于预训练模型的数据集。这种状况将会让你使用策略1。由于有一个大数据集,你就能够从零开始训练一个模型。尽管数据集不一样,但在实践中,利用模型的体系结构和权重,经过预训练模型对初始化模型仍然是颇有帮助的;

象限2.大数据集与相似于预训练模型的数据集。在这里任何选项都有效,可能最有效的是策略2。因为咱们有一个大数据集,过分拟合不该该是个问题,因此咱们能够尽量多地学习。然而,因为数据集是类似的,咱们能够经过利用之前的知识来节省大量的训练工做。所以,仅对卷积基的分类器和顶层进行训练就足够了。

象限3.小数据集,不一样于预训练模型的数据集,这里惟一适合的就是策略2。很难在训练和冻结的层数之间平衡。若是你涉及的太深刻,那么模型就会过分拟合;若是你仅停留在模型的表面,那么你就不会学到任何有用的东西。也许,你须要比象限2更深刻,而且须要考虑数据加强技术。

象限4.小数据集,但相似于预训练模型的数据集。你只需删除最后一个全链接层(输出层),而且执行预训练模型做为固定的特征提取器,而后使用结果特征来训练新的分类器。

 

图3和图4. 尺寸类似性矩阵(左)和微调预训练模型的决策图(右)

五、深度卷积神经网络上的分类器

如前所述,由基于预训CNN的迁移学习方法获得的图像分类模型一般由如下两部分组成:

(1)卷积基,实现特征提取;

(2)分类器,经过卷积基提取的特征对输入图像进行分类;

因为在本节中咱们主要关注分类器部分,因此必须首先说明能够遵循不一样的方法来构建分类器:

(1)全链接层.对于图像分类问题,标准的方法是使用一组下面是softmax激活层的全链接层。softmax激活层输出每一个可能的类标签上的几率分布,而后只须要根据最可能的类对图像进行分类。

(2)全局平均池化层.Lin在2013年提出了一种基于全局平均池化的方法。在这个方法中,咱们没有在卷积基最上面添加全链接层,而是添加了一个全局平均池化层,并将其输出直接提供给softmax激活层。

(3)线性支持向量机.线性支持向量机(SVM)是另外一种可能的方法。根据Tang在2013年所说的,能够利用卷积基在提取的特征上经过训练线性SVM分类器来提升分类精确度。SVM方法的优缺点细节能够在下文中找到。

6. 示例

6.1 准备数据

在例子中,咱们将使用原始数据集的小版本,这能够更快地运行模型,适用于那些计算能力有限的任务。

为了构建小版本数据集,咱们可使用Chollet 2017所提供的代码。

# Create smaller dataset for Dogs vs. Cats importos, shutil original_dataset_dir = '/Users/macbook/dogs_cats_dataset/train/' base_dir = '/Users/macbook/book/dogs_cats/data' if not os.path.exists(base_dir): os.mkdir(base_dir) # Create directories train_dir = os.path.join(base_dir,'train') if not os.path.exists(train_dir): os.mkdir(train_dir) validation_dir = os.path.join(base_dir,'validation') if not os.path.exists(validation_dir): os.mkdir(validation_dir) test_dir = os.path.join(base_dir,'test') if not os.path.exists(test_dir): os.mkdir(test_dir) train_cats_dir = os.path.join(train_dir,'cats') if not os.path.exists(train_cats_dir): os.mkdir(train_cats_dir) train_dogs_dir = os.path.join(train_dir,'dogs') if not os.path.exists(train_dogs_dir): os.mkdir(train_dogs_dir) validation_cats_dir = os.path.join(validation_dir,'cats') if not os.path.exists(validation_cats_dir): os.mkdir(validation_cats_dir) validation_dogs_dir = os.path.join(validation_dir, 'dogs') if not os.path.exists(validation_dogs_dir): os.mkdir(validation_dogs_dir) test_cats_dir = os.path.join(test_dir, 'cats') if not os.path.exists(test_cats_dir): os.mkdir(test_cats_dir) test_dogs_dir = os.path.join(test_dir, 'dogs') if not os.path.exists(test_dogs_dir): os.mkdir(test_dogs_dir) # Copy first 1000 cat images to train_cats_dir fnames = ['cat.{}.jpg'.format(i) for i in range(100)] forfname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(train_cats_dir, fname) shutil.copyfile(src, dst) # Copy next 500 cat images to validation_cats_dir fnames = ['cat.{}.jpg'.format(i) for i in range(200, 250)] forfname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(validation_cats_dir, fname) shutil.copyfile(src, dst) # Copy next 500 cat images to test_cats_dir fnames = ['cat.{}.jpg'.format(i) for i in range(250,300)] forfname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(test_cats_dir, fname) shutil.copyfile(src, dst) # Copy first 1000 dog images to train_dogs_dir fnames = ['dog.{}.jpg'.format(i) for i in range(100)] forfname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(train_dogs_dir, fname) shutil.copyfile(src, dst) # Copy next 500 dog images to validation_dogs_dir fnames = ['dog.{}.jpg'.format(i) for i in range(200,250)] forfname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(validation_dogs_dir, fname) shutil.copyfile(src, dst) # Copy next 500 dog images to test_dogs_dir fnames = ['dog.{}.jpg'.format(i) for i in range(250,300)] forfname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(test_dogs_dir, fname) shutil.copyfile(src, dst) # Sanity checks 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(os.listdir(test_cats_dir))) print('total test dog images:', len(os.listdir(test_dogs_dir)))

6.2 从卷积基中提取特征

卷积基将用于提取特征,这些特征将为咱们想要训练的分类器提供输入,以便可以识别图像中是否有狗或猫。

再次把Chollet 2017提供的代码修改了一下,请看代码2。

# Extract features importos, shutil fromkeras.preprocessing.image import ImageDataGenerator datagen = ImageDataGenerator(rescale=1./255) batch_size = 32 defextract_features(directory, sample_count): features = np.zeros(shape=(sample_count, 7, 7, 512)) # Must be equal to the output of the convolutional base labels = np.zeros(shape=(sample_count)) # Preprocess data generator = datagen.flow_from_directory(directory, target_size=(img_width,img_height), batch_size = batch_size, class_mode='binary') # Pass data through convolutional base i = 0 forinputs_batch, labels_batch in generator: features_batch = conv_base.predict(inputs_batch) features[i * batch_size: (i + 1) * batch_size] = features_batch labels[i * batch_size: (i + 1) * batch_size] = labels_batch i += 1 ifi * batch_size>= sample_count: break return features, labels train_features, train_labels = extract_features(train_dir, train_size) # Agree with our small dataset size validation_features, validation_labels = extract_features(validation_dir, validation_size) test_features, test_labels = extract_features(test_dir, test_size)

代码2.从卷积基中提取特征。

6.3分类器

6.3.1全链接层

咱们提出的第一个解决方案是基于全链接层的。在分类器中添加一组全链接层,把从卷积基中提取的特征做为它们的输入。

为了保持简单和高效,咱们将使用Chollet2018上的解决方案,并稍做修改,特别地使用Adam优化器来代替RMSProp。

代码3显示了相关的代码,而图5和图6表示了学习曲线。

# Define model fromkeras import models fromkeras import layers fromkeras import optimizers epochs = 100 model = models.Sequential() model.add(layers.Flatten(input_shape=(7,7,512))) model.add(layers.Dense(256, activation='relu', input_dim=(7*7*512))) model.add(layers.Dropout(0.5)) model.add(layers.Dense(1, activation='sigmoid')) model.summary() # Compile model model.compile(optimizer=optimizers.Adam(), loss='binary_crossentropy', metrics=['acc']) # Train model history = model.fit(train_features, train_labels, epochs=epochs, batch_size=batch_size, validation_data=(validation_features, validation_labels))

代码3.全链接层解决方案

 

图5.全链接层方案的准确性

 

图6.全链接层方案的损失

结果简述:

(1)验证精确度约为0.85,对于给定数据集的大小,这个结果是很是不错的。(2)这个模型过分拟合,在训练曲线和验证曲线之间有很大的差距。

(3)因为咱们已经使用了dropout,因此应该增大数据集来改善结果。

6.3.2全局平均池化层

这种状况与以前的不一样之处在于,咱们将添加一个全局平均池化层,并将其输出提供到一个sigmoid激活层,而不是添加一组全链接层。

请注意,咱们所说的是sigmoid激活层,而不是SoftMax激活层。咱们正在改成sigmoid激活,由于在Keras中,为了执行二进制分类,应该使用sigmoid激活和binary_crossentropy做为损失。

代码4是构建分类器的代码。图7和图8表示所得结果的学习曲线。

# Define model fromkeras import models fromkeras import layers fromkeras import optimizers epochs = 100 model = models.Sequential() model.add(layers.GlobalAveragePooling2D(input_shape=(7,7,512))) model.add(layers.Dense(1, activation='sigmoid')) model.summary() # Compile model model.compile(optimizer=optimizers.Adam(), loss='binary_crossentropy', metrics=['acc']) # Train model history = model.fit(train_features, train_labels, epochs=epochs, batch_size=batch_size, validation_data=(validation_features, validation_labels))

代码4.全局平均池化解决方案

 

图7.全局平均池化层方案的准确性

 

图8.全局平均池化方案的损失

结果简述:

(1)验证精确度与全链接层方案的相似;

(2)模型不像之前那样过分拟合;

(3)当模型中止训练时,损失函数的结果仍在减少,大概能够经过增长周期数来完善模型;

6.3.3 线性支持向量机

在这种状况下,咱们将利用卷积基提取的特征来训练一个线性支持向量机(SVM)的分类器。

为了训练这种分类器,可使用传统的机器学习方式。所以,咱们将使用k-fold cross-validation来估算分类器的偏差。因为将使用k-fold cross-validation,咱们能够将训练集和验证集链接起来以扩大训练数据(像前面同样保持测试集不变)。

代码5显示了如何联系数据。

# Concatenate training and validation sets svm_features = np.concatenate((train_features, validation_features)) svm_labels = np.concatenate((train_labels, validation_labels))

代码 5. 数据链接

最后,咱们必需要知道SVM分类器有一个超参数,它是偏差项的惩罚参数C。为了优化这个超参数,咱们将使用穷举的方法进行网格搜索。

代码6表示用于构建该分类器的代码,而图9表示了学习曲线。

# Build model importsklearn fromsklearn.cross_validation import train_test_split fromsklearn.grid_search import GridSearchCV fromsklearn.svm import LinearSVC X_train, y_train = svm_features.reshape(300,7*7*512), svm_labels param = [{ "C": [0.01, 0.1, 1, 10, 100] }] svm = LinearSVC(penalty='l2', loss='squared_hinge') # As in Tang (2013) clf = GridSearchCV(svm, param, cv=10) clf.fit(X_train, y_train)
 

代码6. 线性 SVM解决方案.

 

图9.线性支持向量机方案的精确度

结果简述:

(1)模型的精确度在0.86左右,相似于前一个方案;

(2)过分拟合即将出现。此外,训练精确度始终是1,这是不正常的,能够解释为过分拟合的现象;

(3)模型的精确度应随着训练样本数量的增长而提升。然而,这彷佛并无出现,这多是因为过分拟合的缘由。有趣的是,当数据集增长的时候,模型将会如何反应。

7. 总结

· 本文提出了迁移学习、卷积神经网络和预训练模型的概念;

· 定义了基本的微调策略来从新调整预训练模型;

· 描述了一种基于数据集的大小和类似度来决定应该使用哪一种微调策略的结构化方法;

· 介绍了三种不一样的分类器,可用于经过卷积基提取的特征上面;

· 为文中阐述的三个分类器中的任意一个都提供了关于图像分类的端到端的例子;

阿里云双十一1折拼团活动:已满6人,都是最低折扣了

【满6人】1核2G云服务器99.5元一年298.5元三年 2核4G云服务器545元一年 1227元三年

【满6人】1核1G MySQL数据库 119.5元一年

【满6人】3000条国内短信包 60元每6月

参团地址:http://click.aliyun.com/m/1000020293/

原文连接

相关文章
相关标签/搜索