最近在做基于TensorFlow的迁移学习,在网上找了一大堆资料,但大多数都是相似的,整理起来很费劲。现在终于把代码调通并验证过了,就抽空写个博客总结一下。由于能力有限,难免有不对之处,请大家多多指正!要是参考文献有遗落之处,请单独私信我,我会尽快改正!
下面,我将简单介绍一些迁移学习的基本概念,并给出基于TensorFlow的迁移学习的完整demo。
1 为什么要迁移学习
常规的机器学习方法一般假设训练样本和测试样本处在相同的特征空间或者源于相同的数据分布,在拥有足够多的带标注训练样本,常规的机器学习算法一般都能取得较好性能。然而,这在现实中却常常不可行(数据获取苦难,标注成本高)。例如我们要对一个任务进行分类,例如汉语语音情感识别,但是此任务中数据很少(标注的汉语情感语料很少,在迁移学习中称为目标域),然而又存在大量相关的训练数据(例如标注的英文情感数据,在迁移学习中称为源域),但是此训练数据与所需进行的分类任务中的测试数据特征分布不同(一个是中文的,一个是英文的)。在这种情况下如果可以采用合适的迁移学习方法则可以大大提高样本不充足的分类识别结果,也就是将知识迁移到新环境中的能力,这通常被称为迁移学习。
2 迁移学习
2.1 迁移学习的基本概念
迁移学习方法是将其他领域相关的知识运用到给定领域的任务上,即将其他领域的知识迁移到新的任务学习系统上。迁移学习主要记住两对概念:source(源)和target(目标),domain(域)和task(任务)。
Domain:特征空间X和边缘概率分布P(X)。当我们说域不同的时候可能说的是特征空间不同,也可能是特征空间相同但边缘概率分布不同。
Task:目标任务也包括两个部分,由标签空间Y和目标预测函数f组成。同理,当我们说task不同的时候,也得分两种情况,可能是标签空间不同,也可能是标签空间相同但目标预测函数不同。
Source和Target: 前者是用于训练模型的域/任务,后者是要用前者的模型对自己的数据进行预测/分类/聚类等机器学习任务的域/任务。
2.2 迁移什么
在一些学习任务中,有一些特征是个体所特有的,这些特征不可以迁移。而有些特征是在所有个体中都具有贡献的,这些可以迁移。
2.3 什么时候迁移
当source domain和target domain没什么关系或者不太相同的时候,迁移效果可能就不那么好了,甚至可能会比不迁移时候的表现要更差,这个就叫做负迁移。
可以看到,迁移学习的能力也是有限的,所以我们需要关注迁移学习的边界在哪里,比如用conditional Kolmogorov complexity去衡量tasks之间的相关性。
2.4 迁移学习的分类
2.4.1 从迁移场景看
根据源或者目标的域与任务之间的关系,迁移学习可以分解为以下几类:
学习方法 | 源域和目标域 | 源任务和目标任务 | |
常规机器学习方法 | 相同 | 相同 | |
迁移学习 | 归纳式迁移学习 | 相同 | 不同但相关 |
无监督迁移学习 | 不同但相关 | 不同但相关 | |
演绎式迁移学习 | 不同但相关 | 相同 |
2.4.2 从“迁移什么”来看
- 样本迁移
样本迁移一般是对样本进行加权,给比较重要的样本较大的权重,即在数据集(源域)中找到与目标域相似的数据,把这个数据放大好多倍,与目标域的数据进行匹配。如下图所示,找到例子3,然后加大它的权重,这样在训练的时候它所占的权重较大,预测也更加准确。
适用场景:这种方法比较适合与源数据与目标数据中部分带标签数据非常接近的情况。
- 特征迁移
在空间特征进行迁移,一般需要把源域和目标域的特征投影到同一个特征空间里进行,也就是通过源域和目标域之间的共同特征,然后利用观察所得的共同特征在不同层级进行自动迁移。
- 参数/模型迁移
将整个模型应用到目标域上,最常见的就是对预训练好的模型进行微调。比如利用上百万张的图片训练了一个VGG模型,当采用这个VGG模型进行声纹识别时,不再需要寻找这上百万张的图片来训练了,可以将原来图像识别的知识迁移到声纹中去,在目标域上只用几万张的语谱图进行微调就能够获得不错的性能。
- 关系迁移
最典型的关系迁移是社交网络和社会网络之间的迁移。
3. 基于TensorFlow的迁移学习
3.1 保存模型
首先,保存模型需要实例化一个tf.train.Saver:
saver = tf.train.Saver()
一般地,tensorflow默认保存5个模型,如果你想按自己的想法来保存模型,则可以使用max_to_keep和keep_checkpoint_every_n_ hours:
#saves a model every 2 hours and maximum 4 latest models are saved.
saver = tf.train.Saver(max_to_keep=4, keep_checkpoint_every_n_hours=2)
训练中定期调用saver.save()方法,向文件中写入当前模型中所有可训练变量的checkpoint文件:
saver.save(sess, FLAGS.log_dir, global_step = step)
#如果不想保存meta-graph时,使用
#saver.save(sess, FLAGS.log_dir, global_step = step)
一次saver.save()后可以在文件夹中看到新增的4个文件:
其中checkpoint文本文件,记录了模型文件的路径信息列表,.meta是二进制文件,保存了模型的计算图结构,而.index为一个string-string table,table的key为tensor名,value为BundleEntryProto,而.data保存了模型所有变量的值。
注意,如果我们在tf.train.Saver()中没有指定任何东西,它将保存所有的变量。如果,我们不想保存所有的变量,而只是一些变量。我们可以指定要保存的变量/集合。在创建tf.train。保护程序实例,我们将它传递给我们想要保存的变量的列表或字典。让我们来看一个例子:
import tensorflow as tf
w1 = tf.Variable(tf.random_normal(shape=[2]), name='w1')
w2 = tf.Variable(tf.random_normal(shape=[5]), name='w2')
saver = tf.train.Saver([w1,w2])
sess = tf.Session()
sess.run(tf.global_variables_initializer())
saver.save(sess, 'my_test_model',global_step=1000)
接下来给出一个基于卷积神经网络对Mnist分类的实例:
# -*- coding: utf-8 -*-
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.python.framework import graph_util
log_dir = './tensorboard'
mnist = input_data.read_data_sets(train_dir="./mnist_data",one_hot=True)
if tf.gfile.Exists(log_dir):
tf.gfile.DeleteRecursively(log_dir)
tf.gfile.MakeDirs(log_dir)
#定义输入数据mnist图片大小28*28*1=784,None表示batch_size
x = tf.placeholder(dtype=tf.float32,shape=[None,28*28],name="input")
#定义标签数据,mnist共10类
y_ = tf.placeholder(dtype=tf.float32,shape=[None,10],name="y_")
#将数据调整为二维数据,w*H*c---> 28*28*1,-1表示N张
image = tf.reshape(x,shape=[-1,28,28,1])
#第一层,卷积核={5*5*1*32},池化核={2*2*1,1*2*2*1}
w1 = tf.Variable(initial_value=tf.random_normal(shape=[5,5,1,32],stddev=0.1,dtype=tf.float32,name="w1"))
b1= tf.Variable(initial_value=tf.zeros(shape=[32]))
conv1 = tf.nn.conv2d(input=image,filter=w1,strides=[1,1,1,1],padding="SAME",name="conv1")
relu1 = tf.nn.relu(tf.nn.bias_add(conv1,b1),name="relu1")
pool1 = tf.nn.max_pool(value=relu1,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME")
#shape={None,14,14,32}
#第二层,卷积核={5*5*32*64},池化核={2*2*1,1*2*2*1}
w2 = tf.Variable(initial_value=tf.random_normal(shape=[5,5,32,64],stddev=0.1,dtype=tf.float32,name="w2"))
b2 = tf.Variable(initial_value=tf.zeros(shape=[64]))
conv2 = tf.nn.conv2d(input=pool1,filter=w2,strides=[1,1,1,1],padding="SAME")
relu2 = tf.nn.relu(tf.nn.bias_add(conv2,b2),name="relu2")
pool2 = tf.nn.max_pool(value=relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME",name="pool2")
#shape={None,7,7,64}
#FC1
w3 = tf.Variable(initial_value=tf.random_normal(shape=[7*7*64,1024],stddev=0.1,dtype=tf.float32,name="w3"))
b3 = tf.Variable(initial_value=tf.zeros(shape=[1024]))
#关键,进行reshape
input3 = tf.reshape(pool2,shape=[-1,7*7*64],name="input3")
fc1 = tf.nn.relu(tf.nn.bias_add(value=tf.matmul(input3,w3),bias=b3),name="fc1")
#shape={None,1024}
#FC2
w4 = tf.Variable(initial_value=tf.random_normal(shape=[1024,10],stddev=0.1,dtype=tf.float32,name="w4"))
b4 = tf.Variable(initial_value=tf.zeros(shape=[10]))
fc2 = tf.nn.bias_add(value=tf.matmul(fc1,w4),bias=b4,name="logit")
#shape={None,10}
#定义交叉熵损失
# 使用softmax将NN计算输出值表示为概率
y = tf.nn.softmax(fc2,name="out")
# 定义交叉熵损失函数
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=fc2,labels=y_)
loss = tf.reduce_mean(cross_entropy)
tf.summary.scalar('Cross_Entropy',loss)
#定义solver
train = tf.train.AdamOptimizer(learning_rate=0.0001).minimize(loss=loss)
for var in tf.trainable_variables():
print var
#train = tf.train.AdamOptimizer(learning_rate=0.0001).minimize(loss=loss)
#定义正确值,判断二者下标index是否相等
correct_predict = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
#定义如何计算准确率
accuracy = tf.reduce_mean(tf.cast(correct_predict,dtype=tf.float32),name="accuracy")
tf.summary.scalar('Training_ACC',accuracy)
#定义初始化op
merged = tf.summary.merge_all()
init = tf.global_variables_initializer()
saver = tf.train.Saver()
#训练NN
with tf.Session() as session:
session.run(fetches=init)
writer = tf.summary.FileWriter(log_dir,session.graph) #定义记录日志的位置
for i in range(0,500):
xs, ys = mnist.train.next_batch(100)
session.run(fetches=train,feed_dict={x:xs,y_:ys})
if i%10 == 0:
train_accuracy,summary = session.run(fetches=[accuracy,merged],feed_dict={x:xs,y_:ys})
writer.add_summary(summary,i)
print(i,"accuracy=",train_accuracy)
'''
#训练完成后,将网络中的权值转化为常量,形成常量graph,注意:需要x与label
constant_graph = graph_util.convert_variables_to_constants(sess=session,
input_graph_def=session.graph_def,
output_node_names=['out','y_','input'])
#将带权值的graph序列化,写成pb文件存储起来
with tf.gfile.FastGFile("lenet.pb", mode='wb') as f:
f.write(constant_graph.SerializeToString())
'''
saver.save(session,'./ckpt')
3.2 读取模型
一般地,训练时一般直接从checkpoint文件中恢复模型参数。而从checkpoint恢复模型时可直接从.meta里面读取模型的图,也可以直接重构模型的图结构。值得注意的是,如果从checkpoint里面恢复部分网络参数参与训练,则需要在需要恢复的网络结构后面紧接着saver = tf.train.Saver(),否者将会报错。
3.2.1 从checkpoint恢复模型参数,并读取meta文件恢复模型graph
# -*- coding: utf-8 -*-
import tensorflow as tf
import os
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(train_dir="./mnist_data",one_hot=True)
graph_dir = './ckpt.meta'
checkpoint_dir = './'
sess = tf.Session()
saver = tf.train.import_meta_graph(graph_dir)
#saver.restore(sess,tf.train.latest_checkpoint(checkpoint_dir))
x = tf.get_default_graph().get_tensor_by_name('input:0')
y_ = tf.get_default_graph().get_tensor_by_name('y_:0')
fc2 = tf.get_default_graph().get_tensor_by_name('logit:0')
y = tf.get_default_graph().get_tensor_by_name('out:0')
# 定义交叉熵损失函数
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=fc2,labels=y_)
loss = tf.reduce_mean(cross_entropy)
#定义solver
with tf.variable_scope("optimizer"):
train = tf.train.AdamOptimizer(learning_rate=0.0001).minimize(loss=loss)
#train = tf.train.GradientDescentOptimizer(learning_rate=0.0001).minimize(loss=loss)
#定义正确值,判断二者下标index是否相等
correct_predict = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
#定义如何计算准确率
accuracy = tf.reduce_mean(tf.cast(correct_predict,dtype=tf.float32),name="accuracy")
tensor_name_list = [tensor.name for tensor in tf.get_default_graph().as_graph_def().node]
for tensor_name in tensor_name_list:
print tensor_name, '\n'
for var in tf.trainable_variables():
print var
#定义初始化op
init = tf.global_variables_initializer()
sess.run(init)
saver.restore(sess,tf.train.latest_checkpoint(checkpoint_dir))
for i in range(0,1000):
xs, ys = mnist.train.next_batch(100)
sess.run(fetches=train,feed_dict={x:xs,y_:ys})
if i%10 == 0:
train_accuracy = sess.run(fetches=accuracy,feed_dict={x:xs,y_:ys})
print(i,"accuracy=",train_accuracy)
3.2.2 从checkpoint恢复模型参数,并自己搭建模型结构
# -*- coding: utf-8 -*-
from center_loss import get_center_loss
import tensorflow as tf
import os
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(train_dir="./mnist_data",one_hot=True)
graph_dir = './ckpt.meta'
checkpoint_dir = './'
#定义输入数据mnist图片大小28*28*1=784,None表示batch_size
x = tf.placeholder(dtype=tf.float32,shape=[None,28*28],name="input")
#定义标签数据,mnist共10类
y_ = tf.placeholder(dtype=tf.float32,shape=[None,10],name="y_")
#将数据调整为二维数据,w*H*c---> 28*28*1,-1表示N张
image = tf.reshape(x,shape=[-1,28,28,1])
#第一层,卷积核={5*5*1*32},池化核={2*2*1,1*2*2*1}
w1 = tf.Variable(initial_value=tf.random_normal(shape=[5,5,1,32],stddev=0.1,dtype=tf.float32,name="w1"))
b1= tf.Variable(initial_value=tf.zeros(shape=[32]))
conv1 = tf.nn.conv2d(input=image,filter=w1,strides=[1,1,1,1],padding="SAME",name="conv1")
relu1 = tf.nn.relu(tf.nn.bias_add(conv1,b1),name="relu1")
pool1 = tf.nn.max_pool(value=relu1,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME")
#shape={None,14,14,32}
#第二层,卷积核={5*5*32*64},池化核={2*2*1,1*2*2*1}
w2 = tf.Variable(initial_value=tf.random_normal(shape=[5,5,32,64],stddev=0.1,dtype=tf.float32,name="w2"))
b2 = tf.Variable(initial_value=tf.zeros(shape=[64]))
conv2 = tf.nn.conv2d(input=pool1,filter=w2,strides=[1,1,1,1],padding="SAME")
relu2 = tf.nn.relu(tf.nn.bias_add(conv2,b2),name="relu2")
pool2 = tf.nn.max_pool(value=relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME",name="pool2")
#shape={None,7,7,64}
#FC1
w3 = tf.Variable(initial_value=tf.random_normal(shape=[7*7*64,1024],stddev=0.1,dtype=tf.float32,name="w3"))
b3 = tf.Variable(initial_value=tf.zeros(shape=[1024]))
#关键,进行reshape
input3 = tf.reshape(pool2,shape=[-1,7*7*64],name="input3")
fc1 = tf.nn.relu(tf.nn.bias_add(value=tf.matmul(input3,w3),bias=b3),name="fc1")
#shape={None,1024}
#FC2
w4 = tf.Variable(initial_value=tf.random_normal(shape=[1024,10],stddev=0.1,dtype=tf.float32,name="w4"))
b4 = tf.Variable(initial_value=tf.zeros(shape=[10]))
fc2 = tf.nn.bias_add(value=tf.matmul(fc1,w4),bias=b4)
#shape={None,10}
#定义交叉熵损失
# 使用softmax将NN计算输出值表示为概率
y = tf.nn.softmax(fc2,name="out")
saver = tf.train.Saver()
#center loss
center_loss, centers, centers_update_op = get_center_loss(fc1,tf.argmax(y_,1) , 0.2, 10)
# 定义交叉熵损失函数
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=fc2,labels=y_)
loss = tf.reduce_mean(cross_entropy)
total_loss = loss + 0.001 * center_loss
#定义solver
#train = tf.train.AdamOptimizer(learning_rate=0.0001).minimize(loss=loss)
optimizer = tf.train.AdamOptimizer(learning_rate=0.000001)
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
operation = tf.group(update_ops,centers_update_op)
with tf.control_dependencies([operation]):
train = optimizer.minimize(total_loss)
#定义正确值,判断二者下标index是否相等
correct_predict = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
#定义如何计算准确率
accuracy = tf.reduce_mean(tf.cast(correct_predict,dtype=tf.float32),name="accuracy")
#定义初始化op
#saver = tf.train.Saver()
init = tf.global_variables_initializer()
#saver = tf.train.Saver()
#训练NN
with tf.Session() as sess:
sess.run(init)
saver.restore(sess,tf.train.latest_checkpoint(checkpoint_dir))
for i in range(0,20000):
xs, ys = mnist.train.next_batch(100)
sess.run(fetches=train,feed_dict={x:xs,y_:ys})
if i%10 == 0:
train_accuracy,t_loss,cn_loss,ce_loss = sess.run(fetches=[accuracy,total_loss,loss,center_loss],feed_dict={x:xs,y_:ys})
print "step = %d, accuracy=%.2f, total_loss = %.2f, cross_entropy loss = %.2f, center_loss = %.2f"%(i,train_accuracy,t_loss,cn_loss,ce_loss)