前言
距离上次更公众号已经有一段时间了,寒假到开学这段时间都没有更新,笔者在这跟大家说声抱歉。这个学期可能会更新一些有关深度学习的文章,尽量保持一周一更,也希望大家监督。话不多说,开始正题。
目录
1.迁移学习的概念
2.为什么要迁移学习
3.迁移学习的分类
4.迁移学习的方法
5.关于迁移学习的思考和优化
6.基于VGG关于迁移学习的一个实例
迁移学习的概念
迁移学习是属于机器学习的一种研究领域。它专注于存储已有问题的解决模型,并将其利用在其他不同但相关问题上,正如人类可以将一个领域学习到的知识和经验,应用到其他相似的领域中去一样,机器同样也能做到。
为什么要迁移学习
传统的机器学习/数据挖掘只有在训练集数据和测试集数据都来自同一个feature space(特征空间)和统一分布的时候才运行的比较好,这意味着每一次换了数据都要重新训练模型,太麻烦了。比如:
(1)从数据类型/内容上看,对于新的数据集,获取新的训练数据很贵也很难。
(2)从时间维度上看,有些数据集很容易过期,即不同时期的数据分布也会不同。
迁移学习的分类
在讲分类之前,我先介绍两个概念,方便等会读者的理解。
domain(域)和task(任务),source(源)和target(目标),然后给它们进行自由组合。
domain:包括两部分:1.feature space(特征空间);2.probability(概率)。所以当我们说domain不同的时候,就得分两种情况。可能是feature space不同,也可能是feature space一样但probability不同。
task:包括两部分:1. label space(标记空间);2.objective predictive function(目标预测函数)。同理,当我们说task不同的时候,就得分两种情况。可能是label space不同,也可能是label space一样但function不同。
source和target就不用说了,前者是用于训练模型的域/任务,后者是要用前者的模型对自己的数据进行预测/分类/聚类等机器学习任务的域/任务。
一、从迁移的内容来分类。
(1)Instance-based TL(样本迁移)
尽管source domain数据不可以整个直接被用到target domain里,但是在source domain中还是找到一些可以重新被用到target domain中的数据。对它们调整权重,使它能与target domain中的数据匹配之后可以进行迁移。盗一张图,比如在这个例子中就是找到例子3,然后加重它的权值,这样在预测的时候它所占权重较大,预测也可以更准确。
instance reweighting(样本重新调整权重)和importance sampling(重要性采样)是instance-based TL里主要用到的两项技术。
(2)Feature-representation-transfer(特征迁移)
找到一些好的有代表性的特征,通过特征变换把source domain和target domain的特征变换到同样的空间,使得这个空间中source domain和target domain的数据具有相同的分布,然后进行传统的机器学习就可以了。
(3)Parameter-transfer(参数/模型迁移)
假设source tasks和target tasks之间共享一些参数,或者共享模型hyperparameters(超参数)的先验分布。这样把原来的模型迁移到新的domain时,也可以达到不错的精度。
(4)Relational-knowledge-transfer(关系迁移)
把相似的关系进行迁移,比如生物病毒传播到计算机病毒传播的迁移,比如师生关系到上司下属关系的迁移。
二、从迁移的场景来分类
(1)Inductive TL(归纳式迁移学习)
source和target的domain可能一样或不一样,task不一样;target domain的labeled数据可得,source domain不一定可得。所以呢,根据source domain的labeled数据可以再细分为两类:
multitask learning(多任务学习):source domain的labeled数据可得。
self-taught learning(自学习):source domain的labeled数据不可得。
(2)Transductive TL(直推式迁移学习)
source和target的task一样,domain不一样;source domain的labeled数据可得,target domain的不可得。注意我们提过,domain不一样意味着两种可能:feature space不一样,或者feature space一样而probability不一样。而后一种情况和domain adaptation(域适配)息息相关。这里也可以根据domain和task的个数分为两个情况:
Domain Adaptation(域适配):不同的domains single task
Sample Selection Bias(样本选择偏差)/Covariance Shift(协方差转变):single domain single task
(3)Unsupervised TL(无监督迁移学习)
source和target的domain和task都不一样;source domain和target domain的labeled数据都不可得。
综上,这几个方法差别主要是:(1)source和domain之间,domain是否相同,task是否相同;(2)source domain和target domain的labeled数据是否可以得到。
迁移学习的方法
迁移学习的方法有许多,通过source target domain task不同情况的组合都有不同的方法,在这里我就简述一下一般迁移学习的方法流程。
我们知道在做深度网络的时候,一开始网络学的是general(一般)的特征,之后才越来越细化,越来越specific(具体)。那么到底怎么衡量一层是general和specific的呢?这种转变到底是突然在某一层发生的,还是慢慢渐变式地发生的呢?这种转变是在哪个部分发生的,开始、中间、还是最后一层?研究这些问题,是因为这些问题对研究迁移效果很有帮助,因为我们进行迁移,本质就是要找出source和domain里的共同点,所以要在general层面上进行迁移。因此,找出哪一层是general的,哪一层是specific的,也就显得至关重要了。
一般的迁移学习是这样的:训练好一个网络(我们称它为base network)→把它的前n层复制到target network的前n层→target network剩下的其他层随机初始化→开始训练target task.。其中,在做backpropogate(反向传播)的时候,有两种方法可以选择:
(1)把迁移过来的这前n层frozen(冻结)起来,即在训练target task的时候,不改变这n层的值;
(2)不冻结这前n层,而是会不断调整它们的值,称为fine-tune(微调)。这个主要取决于target数据集的大小和前n层的参数个数,如果target数据集很小,而参数个数很多,为了防止overfitting(过拟合),通常采用frozen方法;反之,采用fine-tune。
关于迁移学习的思考和优化
我们假设:
A和B:两个task,可以分割为两个相似的数据集(random A/B splits)和不相似的数据集(man-made and natural)
设定:ImageNet分类类别为1000个,可以分割为两个分别有500个类别的数据集,也可以分割为分别有645000个样本的数据集。
层数总共为8层,为了更好地举例子,我们对上面所说的“前n层”里面的n取n=3,把这个第n层称为example layer。
base A:A上训练的神经网络
base B:B上训练的神经网络
transfer A3B:前3层从base A里面的前3层得到,且前3层是frozen的;后5层随机初始化,且在B上进行训练。重点来了,如果A3B和baseB的效果差不多,说明第3层对B来说仍然是general的;如果差多了,那第3层就是specific to A的,不适合迁移到B;
selffer B3B :和B3B一样,只不过它不用frozen前3层,而是会学习所有层数,即它是fine tune的;
transfer A3B :和A3B一样,只不过它不用frozen前3层,而是会学习所有层数,即它是fine tune的。
用一张图来展示:
思考优化:
一、深度网络里不同层次迁移效果,展示了不使用fine-tune时,迁移效果下降的原因可能有两个:
(1)特征本身specificity,就比如现在已经到了网络稍深一点的层次了,网络学的已经是specific的特征,这时候去迁移效果会不好。解决方法就是选出general的层,进行迁移;
(2)一个特征之间是co-adapted(耦合)的网络分割了。这一个我的理解可能是和frozen相关,就是本来网络里的特征是耦合的、紧密联系的,但是因为我们把前n层frozen了,相当于把网络割成了两部分,这样可能会导致效果不好。解决方法就是fine tune。
二、相似数据集之间的迁移效果优于不同数据集之间的迁移效果。
三、训练网络时,使用迁移的weights(权重)去初始化的效果会比随机初始化的效果要好,无论是在相似的数据集上迁移还是在不相似的数据集上迁移。
四、无论使用多少层的迁移特征对网络进行初始化,在fine tune之后效果都会变得很好。
一个以VGG为背景的迁移学习的例子
我先稍微介绍一下VGG:VGG 是视觉领域竞赛 ILSVRC 在 2014 年的获胜模型,以 7.3% 的错误率在 ImageNet 数据集上大幅刷新了前一年 11.7% 的世界纪录。VGG16 基本上继承了 AlexNet 深的思想,并且发扬光大,做到了更深。AlexNet 只用到了 8 层网络,而 VGG 的两个版本分别是 16 层网络版和 19 层网络版。在接下来的例子中,我会采用稍微简单的一些的 VGG16,他和 VGG19 有几乎完全一样的准确度,但是运算起来更快一些。
VGG16在1000个类别中训练过,我提取了VGG前面的卷积尺化层,重新组建了后面的全连接层,让它做一些和原来不太相干的事情。我从网上下载了将近1000张猫和老虎的照片,然后伪造了一些猫和老虎长度的数据,最后让迁移后的网络分辨出猫和老虎的长度。
猫和老虎照片如下:
猫和老虎体长的数据:
另外我们还要下载一个VGG16的模型,是一个.npy文件,是一个numpy对象,笔者是上github下载的。
准备好数据,我们就可以开始进行迁移VGG了。
我保留了VGG前面的卷积尺化层,只是把后面的全连接层给拆了,改成可被train的两层,输出一个数字,这个数字代表这只猫或者老虎的长度。(也就是采取冻结而非微调的方式)
ps:代码有部分涉及到VGG16源码,在此不作过多解读,有空专门写一篇读VGG代码的文章。
代码语言:javascript复制class Vgg16:
……
conv5_1 = self.conv_layer(pool4, "conv5_1")
conv5_2 = self.conv_layer(conv5_1, "conv5_2")
conv5_3 = self.conv_layer(conv5_2, "conv5_3")
pool5 = self.max_pool(conv5_3, 'pool5')
# detach original VGG fc layers and
# reconstruct your own fc layers serve for your own purpose
self.flatten = tf.reshape(pool5, [-1, 7*7*512])
self.fc6 = tf.layers.dense(self.flatten, 256, tf.nn.relu, name='fc6')
self.out = tf.layers.dense(self.fc6, 1, name='out')
self.sess = tf.Session()
if restore_from:
saver = tf.train.Saver()
saver.restore(self.sess, restore_from)
else: # training graph
self.loss = tf.losses.mean_squared_error(labels=self.tfy, predictions=self.out)
self.train_op = tf.train.RMSPropOptimizer(0.001).minimize(self.loss)
self.sess.run(tf.global_variables_initializer())
def max_pool(self, bottom, name):
return tf.nn.max_pool(bottom, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name=name)
def conv_layer(self, bottom, name):
with tf.variable_scope(name): # CNN's filter is constant, NOT Variable that can be trained(前面几层都是从文件读取,无法被训练)
conv = tf.nn.conv2d(bottom, self.data_dict[name][0], [1, 1, 1, 1], padding='SAME')
lout = tf.nn.relu(tf.nn.bias_add(conv, self.data_dict[name][1]))
return lout
在 self.flatten 之前的 layers, 都是不能被 train 的. 而 tf.layers.dense() 建立的 layers 是可以被 train 的. 到时候我们 train 好了, 再定义一个 Saver 来保存由 tf.layers.dense() 建立的 parameters.
代码语言:javascript复制 def save(self, path='./for_transfer_learning/model/transfer_learn'):
saver = tf.train.Saver()
saver.save(self.sess, path, write_meta_graph=False)
接着就可以开始训练了
因为我们有了训练好的VGG16,我们就可以把VGG16的卷积层想象成一个feature extractor来提取或压缩图片中的特征。这其实和 Autoencoder 中的 encoder 类似,用这些提取的特征来训练后面的我们自己编写的全连接层。因为我这里采取的是冻结的方式,也就是只需要训练自己编写的全连接层,所以我只训练了100次,如果你采取微调的方式,那训练100次是远远不够的。
代码语言:javascript复制def train():
……
vgg = Vgg16(vgg16_npy_path='./for_transfer_learning/vgg16.npy')
print('Net built')
for i in range(100):
b_idx = np.random.randint(0, len(xs), 6)
train_loss = vgg.train(xs[b_idx], ys[b_idx])
print(i, 'train loss: ', train_loss)
vgg.save('./for_transfer_learning/model/transfer_learn')
训练好之后我们就可以开始测试了,我输入了一张猫,一张老虎的图,训练好的网络给了我他的答案:
这样一个小的迁移学习的例子就完成了,最后附上全部代码
代码语言:javascript复制from urllib.request import urlretrieve
import os
import numpy as np
import tensorflow as tf
import skimage.io
import skimage.transform
import matplotlib.pyplot as plt
def load_img(path):
img = skimage.io.imread(path)
img = img / 255.0
# print "Original Image Shape: ", img.shape
# we crop image from center
short_edge = min(img.shape[:2])
yy = int((img.shape[0] - short_edge) / 2)
xx = int((img.shape[1] - short_edge) / 2)
crop_img = img[yy: yy short_edge, xx: xx short_edge]
# resize to 224, 224
resized_img = skimage.transform.resize(crop_img, (224, 224))[None, :, :, :] # shape [1, 224, 224, 3]
return resized_img
def load_data():
imgs = {'tiger': [], 'kittycat': []}
for k in imgs.keys():
dir = './for_transfer_learning/data/' k
for file in os.listdir(dir):
if not file.lower().endswith('.jpg'):
continue
try:
resized_img = load_img(os.path.join(dir, file))
except OSError:
continue
imgs[k].append(resized_img) # [1, height, width, depth] * n
if len(imgs[k]) == 400: # only use 400 imgs to reduce my memory load
break
# fake length data for tiger and cat
tigers_y = np.maximum(20, np.random.randn(len(imgs['tiger']), 1) * 30 100)
cat_y = np.maximum(10, np.random.randn(len(imgs['kittycat']), 1) * 8 40)
return imgs['tiger'], imgs['kittycat'], tigers_y, cat_y
class Vgg16:
vgg_mean = [103.939, 116.779, 123.68]
def __init__(self, vgg16_npy_path=None, restore_from=None):
# pre-trained parameters
try:
self.data_dict = np.load(vgg16_npy_path, encoding='latin1').item()#遍历其内键值对,导入模型参数
except FileNotFoundError:
print('Please download VGG16 parameters from here https://mega.nz/#!YU1FWJrA!O1ywiCS2IiOlUCtCpI6HTJOMrneN-Qdv3ywQP5poecMnOr from my Baidu Cloud: https://pan.baidu.com/s/1Spps1Wy0bvrQHH2IMkRfpg')
self.tfx = tf.placeholder(tf.float32, [None, 224, 224, 3])
self.tfy = tf.placeholder(tf.float32, [None, 1])
# Convert RGB to BGR
red, green, blue = tf.split(axis=3, num_or_size_splits=3, value=self.tfx * 255.0)
bgr = tf.concat(axis=3, values=[
blue - self.vgg_mean[0],
green - self.vgg_mean[1],
red - self.vgg_mean[2],
])# 逐样本减去每个通道的像素平均值,这种操作可以移除图像的平均亮度值,该方法常用在灰度图像上
# pre-trained VGG layers are fixed in fine-tune
conv1_1 = self.conv_layer(bgr, "conv1_1")
conv1_2 = self.conv_layer(conv1_1, "conv1_2")
pool1 = self.max_pool(conv1_2, 'pool1')
conv2_1 = self.conv_layer(pool1, "conv2_1")
conv2_2 = self.conv_layer(conv2_1, "conv2_2")
pool2 = self.max_pool(conv2_2, 'pool2')
conv3_1 = self.conv_layer(pool2, "conv3_1")
conv3_2 = self.conv_layer(conv3_1, "conv3_2")
conv3_3 = self.conv_layer(conv3_2, "conv3_3")
pool3 = self.max_pool(conv3_3, 'pool3')
conv4_1 = self.conv_layer(pool3, "conv4_1")
conv4_2 = self.conv_layer(conv4_1, "conv4_2")
conv4_3 = self.conv_layer(conv4_2, "conv4_3")
pool4 = self.max_pool(conv4_3, 'pool4')
conv5_1 = self.conv_layer(pool4, "conv5_1")
conv5_2 = self.conv_layer(conv5_1, "conv5_2")
conv5_3 = self.conv_layer(conv5_2, "conv5_3")
pool5 = self.max_pool(conv5_3, 'pool5')
# detach original VGG fc layers and
# reconstruct your own fc layers serve for your own purpose
self.flatten = tf.reshape(pool5, [-1, 7*7*512])
self.fc6 = tf.layers.dense(self.flatten, 256, tf.nn.relu, name='fc6')
self.out = tf.layers.dense(self.fc6, 1, name='out')
self.sess = tf.Session()
if restore_from:
saver = tf.train.Saver()
saver.restore(self.sess, restore_from)
else: # training graph
self.loss = tf.losses.mean_squared_error(labels=self.tfy, predictions=self.out)
self.train_op = tf.train.RMSPropOptimizer(0.001).minimize(self.loss)
self.sess.run(tf.global_variables_initializer())
def max_pool(self, bottom, name):
return tf.nn.max_pool(bottom, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name=name)
def conv_layer(self, bottom, name):
with tf.variable_scope(name): # CNN's filter is constant, NOT Variable that can be trained(前面几层都是从文件读取,无法被训练)
conv = tf.nn.conv2d(bottom, self.data_dict[name][0], [1, 1, 1, 1], padding='SAME')
lout = tf.nn.relu(tf.nn.bias_add(conv, self.data_dict[name][1]))
return lout
def train(self, x, y):
loss, _ = self.sess.run([self.loss, self.train_op], {self.tfx: x, self.tfy: y})
return loss
def predict(self, paths):
fig, axs = plt.subplots(1, 2)
for i, path in enumerate(paths):
x = load_img(path)
length = self.sess.run(self.out, {self.tfx: x})
axs[i].imshow(x[0])
axs[i].set_title('Len: %.1f cm' % length)
axs[i].set_xticks(()); axs[i].set_yticks(())
plt.show()
def save(self, path='./for_transfer_learning/model/transfer_learn'):
saver = tf.train.Saver()
saver.save(self.sess, path, write_meta_graph=False)
def train():
tigers_x, cats_x, tigers_y, cats_y = load_data()
# plot fake length distribution
plt.hist(tigers_y, bins=20, label='Tigers')
plt.hist(cats_y, bins=10, label='Cats')
plt.legend()
plt.xlabel('length')
plt.show()
xs = np.concatenate(tigers_x cats_x, axis=0)
ys = np.concatenate((tigers_y, cats_y), axis=0)
vgg = Vgg16(vgg16_npy_path='./for_transfer_learning/vgg16.npy')
print('Net built')
for i in range(100):
b_idx = np.random.randint(0, len(xs), 6)
train_loss = vgg.train(xs[b_idx], ys[b_idx])
print(i, 'train loss: ', train_loss)
vgg.save('./for_transfer_learning/model/transfer_learn') # save learned fc layers
def eval():
vgg = Vgg16(vgg16_npy_path='./for_transfer_learning/vgg16.npy',
restore_from='./for_transfer_learning/model/transfer_learn')
vgg.predict(
['./for_transfer_learning/data/kittycat/000129037.jpg', './for_transfer_learning/data/tiger/391412.jpg'])
if __name__ == '__main__':
# download()
#train()
eval()
参考:
1.https://ieeexplore.ieee.org/abstract/document/5288526/
2.http://yosinski.com/media/papers/Yosinski__2014__NIPS__How_Transferable_with_Supp.pdf
3.https://me.csdn.net/vvnzhang2095