热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一文看懂迁移学习:怎样用预训练模型搞定深度神经网络?

引言跟传统的监督式机器学习算法相比,深度神经网络目前最大的劣势是什么?贵。尤其是当我们在尝试处理现实生活中诸如图像识别、声音辨识等实际问题的时候。
引言

跟传统的监督式机器学习算法相比,深度神经网络目前最大的劣势是什么?

贵。

尤其是当我们在尝试处理现实生活中诸如图像识别、声音辨识等实际问题的时候。一旦你的模型中包含一些隐藏层时,增添多一层隐藏层将会花费巨大的计算资源。

庆幸的是,有一种叫做“迁移学习”的方式,可以使我们在他人训练过的模型基础上进行小改动便可投入使用。在这篇文章中,我将会讲述如何使用预训练模型来加速解决问题的过程。

注:这篇文章默认读者对于神经网络和深度学习有着一定的了解,如果你不了解深度学习,那么我强烈建议你先了解一下深度学习的基础概念:

目录

1. 什么是迁移学习?

2. 什么是预训练模型?

3. 为什么我们使用预训练模型?-结合生活实例

4. 我们可以怎样运用预训练模型?

 提取特征(extract features)

 优化模型(fine tune the model)

5. 优化模型的方式

6. 在数字识别中使用预训练模型

 只针对输出密集层(output dense layer)的重新训练

 冻结初始几层网络的权重因子

1. 什么是迁移学习?

为了对迁移学习产生一个直观的认识,不妨拿老师与学生之间的关系做类比。

一位老师通常在ta所教授的领域有着多年丰富的经验,在这些积累的基础上,老师们能够在课堂上教授给学生们该领域最简明扼要的内容。这个过程可以看做是老手与新手之间的“信息转移”。

 

这个过程在神经网络中也适用。

 

我们知道,神经网络需要用数据来训练,它从数据中获得信息,进而把它们转换成相应的权重。这些权重能够被提取出来,迁移到其他的神经网络中,我们“迁移”了这些学来的特征,就不需要从零开始训练一个神经网络了 。

现在,让我们从自身进化的角度来讨论这种迁移学习的重要性。这是Tim Urban最近在http://waitbutwhy.com上的一篇文章中提出的观点。

Tim说,在语言发明之前,每一代人类都需要自身重新习得很多知识,这也是知识从上一代到下一代一增长缓慢的原因。

 

随后,我们发明了语言,这为知识在世代间的传递提供了载体,下图是在语言发明后,同样时间尺度下知识增长速度的示意图。

 

 

是不是看起来很牛逼?而通过权重的传递来进行迁移学习和人类在世代交替中通过语言传播知识,是一个道理。

 

2. 什么是预训练模型?

简单来说,预训练模型(pre-trained model)是前人为了解决类似问题所创造出来的模型。你在解决问题的时候,不用从零开始训练一个新模型,可以从在类似问题中训练过的模型入手。

比如说,如果你想做一辆自动驾驶汽车,可以花数年时间从零开始构建一个性能优良的图像识别算法,也可以从Google在ImageNet数据集上训练得到的inception model(一个预训练模型)起步,来识别图像。

一个预训练模型可能对于你的应用中并不是100%的准确对口,但是它可以为你节省大量功夫。

接下来,我会举个例子来说明。

3. 为什么我们要用预训练模型?

上周我一直在尝试解决Crowdanalytix platform上的一个问题:从手机图片中分辨场景。

这是一个图像分类的问题,训练数据集中有4591张图片,测试集中有1200张图片。我们的任务是将图片相应地分到16个类别中。在对图片进行一些预处理后,我首先采用一个简单的MLP(Multi-later Perceptron)模型,结构如下图所示:

 

在对输入图片(224*224*3)平整化后,为了简化上述结构,我用了三个各含有500个神经元的隐藏层。在输出层中,共有16个神经元对应着十六个类别。

 

我只能将训练的准确率控制在6.8%,这是个很不理想的结果。我尝试对隐藏层、隐层中神经元的数量以及drop out速率进行调整,但准确度都没有太大的提升。而如果增加隐藏层和其中神经元的数量,每个周期的运行时间则会增加20s以上。(我的开发环境是12GB VRAM,Titan X GPU)

下面是我用上文所述结构的MLP模型训练输出的结果。

 

可以看出,除非指数级地增加训练时长,MLP模型无法提供给我更好的结果。因此,我转而采用CNN(卷积神经网络),看看他们在这个数据集上的表现,以及是否能够提高训练的准确度。

 

CNN的结构如下:

 

我使用了3个卷积的模块,每个模块由以下部分组成:

 

  • 32个5*5的filter
  • 线性整流函数(ReLU)作为激活函数
  • 4*4的最大值池化层

 

最后一个卷积模块输出的结果经过平整化后会被传递到一个拥有64的神经元的隐藏层上,随后通过一个drop out rate = 0.5处理后传递到输出层。

最终训练的结果记录如下:

 

准确率15.75%,尽管与MLP模型相比有所提升,但每个周期的运行时间也增加了。

 

而更重要的是,数据集中最大类别所含图片数量约占总数17.6%左右。

只要把所有的图片都归到最大的类别,我们就能够得到比MLP、CNN训练出来的模型更好的结果(ノへ ̄、)。

此外,增加更多的卷积模块也会大大增加训练时长。

于是,我转而去采用预训练模型,这样我不需要重新训练我的整个结构,只需要针对其中的几层进行训练即可。

因此,我采用了在ImageNet数据集上预先训练好的VGG16模型,这个模型可以在Keras库中找到。

模型的结构如下所示:

 

在VGG16结构的基础上,我只将softmax层的1000个输出改为16个,从而适应我们这个问题的情景,随后重新训练了dense layer。

 

跟MLP和CNN相比,这个结构的准确率能够达到70%。同时,使用VGG16最大的好处是大大减少了训练时间,只需要针对dense layer进行训练,所需时间基本可以忽略。

4.怎样使用预训练模型?

当在训练经网络的时候我们的目标是什么?我们希望网络能够在多次正向反向迭代的过程中,找到合适的权重。

通过使用之前在大数据集上经过训练的预训练模型,我们可以直接使用相应的结构和权重,将它们应用到我们正在面对的问题上。这被称作是“迁移学习”,即将预训练的模型“迁移”到我们正在应对的特定问题中。

在选择预训练模型的时候你需要非常仔细,如果你的问题与预训练模型训练情景下有很大的出入,那么模型所得到的预测结果将会非常不准确。

举例来说,如果把一个原本用于语音识别的模型用来做用户识别,那结果肯定是不理想的。

幸运的是,Keras库中有许多这类预训练的结构。

ImageNet数据集已经被广泛用作训练集,因为它规模足够大(包括120万张图片),有助于训练普适模型。ImageNet的训练目标,是将所有的图片正确地划分到1000个分类条目下。这1000个分类基本上都来源于我们的日常生活,比如说猫猫狗狗的种类,各种家庭用品,日常通勤工具等等。

在迁移学习中,这些预训练的网络对于ImageNet数据集外的图片也表现出了很好的泛化性能。

既然预训练模型已经训练得很好,我们就不会在短时间内去修改过多的权重,在迁移学习中用到它的时候,往往只是进行微调(fine tune)。

在修改模型的过程中,我们通过会采用比一般训练模型更低的学习速率。

5. 微调模型的方法

特征提取

我们可以将预训练模型当做特征提取装置来使用。具体的做法是,将输出层去掉,然后将剩下的整个网络当做一个固定的特征提取机,从而应用到新的数据集中。

采用预训练模型的结构

我们还可以采用预训练模型的结构,但先将所有的权重随机化,然后依据自己的数据集进行训练。

训练特定层,冻结其他层

另一种使用预训练模型的方法是对它进行部分的训练。具体的做法是,将模型起始的一些层的权重保持不变,重新训练后面的层,得到新的权重。在这个过程中,我们可以多次进行尝试,从而能够依据结果找到frozen layers和retrain layers之间的最佳搭配。

如何使用与训练模型,是由数据集大小和新旧数据集(预训练的数据集和我们要解决的数据集)之间数据的相似度来决定的。

下图表展示了在各种情况下应该如何使用预训练模型:

 

场景一:数据集小,数据相似度高(与pre-trained model的训练数据相比而言)

 

在这种情况下,因为数据与预训练模型的训练数据相似度很高,因此我们不需要重新训练模型。我们只需要将输出层改制成符合问题情境下的结构就好。

我们使用预处理模型作为模式提取器。

比如说我们使用在ImageNet上训练的模型来辨认一组新照片中的小猫小狗。在这里,需要被辨认的图片与ImageNet库中的图片类似,但是我们的输出结果中只需要两项——猫或者狗。

在这个例子中,我们需要做的就是把dense layer和最终softmax layer的输出从1000个类别改为2个类别。

场景二:数据集小,数据相似度不高

在这种情况下,我们可以冻结预训练模型中的前k个层中的权重,然后重新训练后面的n-k个层,当然最后一层也需要根据相应的输出格式来进行修改。

因为数据的相似度不高,重新训练的过程就变得非常关键。而新数据集大小的不足,则是通过冻结预训练模型的前k层进行弥补。

场景三:数据集大,数据相似度不高

在这种情况下,因为我们有一个很大的数据集,所以神经网络的训练过程将会比较有效率。然而,因为实际数据与预训练模型的训练数据之间存在很大差异,采用预训练模型将不会是一种高效的方式。

因此最好的方法还是将预处理模型中的权重全都初始化后在新数据集的基础上重头开始训练。

场景四:数据集大,数据相似度高

这就是最理想的情况,采用预训练模型会变得非常高效。最好的运用方式是保持模型原有的结构和初始权重不变,随后在新数据集的基础上重新训练。

6. 在手写数字识别中使用预训练模型

现在,让我们尝试来用预训练模型去解决一个简单的问题。

我曾经使用vgg16作为预训练的模型结构,并把它应用到手写数字识别上。

让我们先来看看这个问题对应着之前四种场景中的哪一种。我们的训练集(MNIST)有大约60,000张左右的手写数字图片,这样的数据集显然是偏小的。所以这个问题应该属于场景一或场景二。

我们可以尝试把两种对应的方法都用一下,看看最终的效果。

只重新训练输出层 & dense layer

这里我们采用vgg16作为特征提取器。随后这些特征,会被传递到依据我们数据集训练的dense layer上。输出层同样由与我们问题相对应的softmax层函数所取代。

在vgg16中,输出层是一个拥有1000个类别的softmax层。我们把这层去掉,换上一层只有10个类别的softmax层。我们只训练这些层,然后就进行数字识别的尝试。


# importing required librariesfrom keras.models import Sequential
from scipy.misc import imread
get_ipython().magic('matplotlib inline')
import matplotlib.pyplot as plt
import numpy as np
import keras
from keras.layers import Dense
import pandas as pdfrom keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
from keras.applications.vgg16 import decode_predictions
train=pd.read_csv("R/Data/Train/train.csv")
test=pd.read_csv("R/Data/test.csv")
train_path="R/Data/Train/Images/train/"
test_path="R/Data/Train/Images/test/"from scipy.misc import imresize
# preparing the train datasettrain_img=[]
for i in range(len(train)):temp_img=image.load_img(train_path+train['filename'][i],target_size=(224,224))temp_img=image.img_to_array(temp_img)train_img.append(temp_img)#converting train images to array and applying mean subtraction processingtrain_img=np.array(train_img)
train_img=preprocess_input(train_img)
# applying the same procedure with the test datasettest_img=[]
for i in range(len(test)):temp_img=image.load_img(test_path+test['filename'][i],target_size=(224,224))temp_img=image.img_to_array(temp_img)test_img.append(temp_img)test_img=np.array(test_img)
test_img=preprocess_input(test_img)# loading VGG16 model weights
model = VGG16(weights='imagenet', include_top=False)
# Extracting features from the train dataset using the VGG16 pre-trained modelfeatures_train=model.predict(train_img)
# Extracting features from the train dataset using the VGG16 pre-trained modelfeatures_test=model.predict(test_img)# flattening the layers to conform to MLP inputtrain_x=features_train.reshape(49000,25088)
# converting target variable to arraytrain_y=np.asarray(train['label'])
# performing one-hot encoding for the target variabletrain_y=pd.get_dummies(train_y)
train_y=np.array(train_y)
# creating training and validation setfrom sklearn.model_selection import train_test_split
X_train, X_valid, Y_train, Y_valid=train_test_split(train_x,train_y,test_size=0.3, random_state=42)# creating a mlp model
from keras.layers import Dense, Activation
model=Sequential()model.add(Dense(1000, input_dim=25088, activation='relu',kernel_initializer='uniform'))
keras.layers.core.Dropout(0.3, noise_shape=None, seed=None)model.add(Dense(500,input_dim=1000,activation='sigmoid'))
keras.layers.core.Dropout(0.4, noise_shape=None, seed=None)model.add(Dense(150,input_dim=500,activation='sigmoid'))
keras.layers.core.Dropout(0.2, noise_shape=None, seed=None)model.add(Dense(units=10))
model.add(Activation('softmax'))model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])# fitting the model model.fit(X_train, Y_train, epochs=20, batch_size=128,validation_data=(X_valid,Y_valid))

冻结最初几层网络的权重

这里我们将会把vgg16网络的前8层进行冻结,然后对后面的网络重新进行训练。这么做是因为最初的几层网络捕获的是曲线、边缘这种普遍的特征,这跟我们的问题是相关的。我们想要保证这些权重不变,让网络在学习过程中重点关注这个数据集特有的一些特征,从而对后面的网络进行调整。

from keras.models import Sequential
from scipy.misc import imread
get_ipython().magic('matplotlib inline')
import matplotlib.pyplot as plt
import numpy as np
import keras
from keras.layers import Dense
import pandas as pdfrom keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
from keras.applications.vgg16 import decode_predictions
from keras.utils.np_utils import to_categoricalfrom sklearn.preprocessing import LabelEncoder
from keras.models import Sequential
from keras.optimizers import SGD
from keras.layers import Input, Dense, Convolution2D, MaxPooling2D, AveragePooling2D, ZeroPadding2D, Dropout, Flatten, merge, Reshape, Activationfrom sklearn.metrics import log_losstrain=pd.read_csv("R/Data/Train/train.csv")
test=pd.read_csv("R/Data/test.csv")
train_path="R/Data/Train/Images/train/"
test_path="R/Data/Train/Images/test/"from scipy.misc import imresizetrain_img=[]
for i in range(len(train)):temp_img=image.load_img(train_path+train['filename'][i],target_size=(224,224))temp_img=image.img_to_array(temp_img)train_img.append(temp_img)train_img=np.array(train_img)
train_img=preprocess_input(train_img)test_img=[]
for i in range(len(test)):temp_img=image.load_img(test_path+test['filename'][i],target_size=(224,224))temp_img=image.img_to_array(temp_img)test_img.append(temp_img)test_img=np.array(test_img)
test_img=preprocess_input(test_img)from keras.models import Modeldef vgg16_model(img_rows, img_cols, channel=1, num_classes=None):model = VGG16(weights='imagenet', include_top=True)model.layers.pop()model.outputs = [model.layers[-1].output]model.layers[-1].outbound_nodes = []x=Dense(num_classes, activation='softmax')(model.output)model=Model(model.input,x)#To set the first 8 layers to non-trainable (weights will not be updated)for layer in model.layers[:8]:layer.trainable = False# Learning rate is changed to 0.001sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])return modeltrain_y=np.asarray(train['label'])le = LabelEncoder()train_y = le.fit_transform(train_y)train_y=to_categorical(train_y)train_y=np.array(train_y)from sklearn.model_selection import train_test_split
X_train, X_valid, Y_train, Y_valid=train_test_split(train_img,train_y,test_size=0.2, random_state=42)# Example to fine-tune on 3000 samples from Cifar10img_rows, img_cols = 224, 224 # Resolution of inputs
channel = 3
num_classes = 10
batch_size = 16
nb_epoch = 10# Load our model
model = vgg16_model(img_rows, img_cols, channel, num_classes)model.summary()
# Start Fine-tuning
model.fit(X_train, Y_train,batch_size=batch_size,epochs=nb_epoch,shuffle=True,verbose=1,validation_data=(X_valid, Y_valid))# Make predictions
predictions_valid = model.predict(X_valid, batch_size=batch_size, verbose=1)# Cross-entropy loss score
score = log_loss(Y_valid, predictions_valid)

相关资源

原文:
Transfer learning & The art of using Pre-trained Models in Deep Learning

VGG-16:
https://gist.github.com/baraldilorenzo/07d7802847aaad0a35d3

Keras库中的ImageNet预训练模型:
https://keras.io/applications/

手写数字数据集MNIST:
MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

【完】


推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 如何使用Python从工程图图像中提取底部的方法?
    本文介绍了使用Python从工程图图像中提取底部的方法。首先将输入图片转换为灰度图像,并进行高斯模糊和阈值处理。然后通过填充潜在的轮廓以及使用轮廓逼近和矩形核进行过滤,去除非矩形轮廓。最后通过查找轮廓并使用轮廓近似、宽高比和轮廓区域进行过滤,隔离所需的底部轮廓,并使用Numpy切片提取底部模板部分。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • 深度学习中的Vision Transformer (ViT)详解
    本文详细介绍了深度学习中的Vision Transformer (ViT)方法。首先介绍了相关工作和ViT的基本原理,包括图像块嵌入、可学习的嵌入、位置嵌入和Transformer编码器等。接着讨论了ViT的张量维度变化、归纳偏置与混合架构、微调及更高分辨率等方面。最后给出了实验结果和相关代码的链接。本文的研究表明,对于CV任务,直接应用纯Transformer架构于图像块序列是可行的,无需依赖于卷积网络。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记
    本文介绍了大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记,包括outputFormat接口实现类、自定义outputFormat步骤和案例。案例中将包含nty的日志输出到nty.log文件,其他日志输出到other.log文件。同时提供了一些相关网址供参考。 ... [详细]
author-avatar
DSSDD
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有