由於很多時候我們在一個新的網絡中只會用到一個已訓練模型的部分參數,即遷移學習。
那麼,如何加載已訓練模型的部分參數到當前網絡。
一、當前網絡加載已訓練模型相同name scope的變量
方法1. 手動構建與預訓練一樣的部分圖
將需要fine tune的變量的name scope命名爲與模型中的name scope相同,然後使用如下代碼將模型參數加載到當前網絡。
tf.train.Saver([var for var in tf.global_variables() if var.name.startswith('train')]) \
.restore(sess,' D:\\tuxiang\\hhh\\my_test_model-1')
注意:相同的name scope的變量的值是一樣的,因此加載模型的參數就會覆蓋已初始化的參數,實現fine tune的目的。
二、當前網絡與已訓練模型的name scope不一致的情況
例如:將訓練模型name scope ‘a’的值賦給當前網絡的name scope ‘m/b/t’。
方法1:改寫已保存模型的name scope,使其與目標變量的name scope一致;或者將需要加載到當前網絡的參數選取出來寫入一個新的模型,然後直接加載就ok.
name scope改寫代碼,也可進行name scope刪選,剔除不需要加載的變量。
import os
import tensorflow as tf
import numpy as np
# 新模型的存儲地址
new_checkpoint_path = 'D:\\tuxiang\\hhh\\h\\'
# 舊模型的存儲地址
checkpoint_path = 'D:\\tuxiang\\hhh\\my_test_model-1'
# 添加的name scope
add_prefix = 'main/'
if not os.path.exists(new_checkpoint_path):
os.makedirs(new_checkpoint_path)
with tf.Session() as sess:
new_var_list = [] # 新建一個空列表存儲更新後的Variable變量
for var_name, _ in tf.contrib.framework.list_variables(checkpoint_path): # 得到checkpoint文件中所有的參數(名字,形狀)元組
var = tf.contrib.framework.load_variable(checkpoint_path, var_name) # 得到上述參數的值
# var_name爲變量的name scope,是一個字符串,可以進行改寫
# var 是該name scope對應的值
print(var_name,var)
new_name = var_name
new_name = add_prefix + new_name # 在這裏加入了名稱前綴,大家可以自由地作修改
# 除了修改參數名稱,還可以修改參數值(var)
print('Renaming %s to %s.' % (var_name, new_name))
renamed_var = tf.Variable(var, name=new_name) # 使用加入前綴的新名稱重新構造了參數
new_var_list.append(renamed_var) # 把賦予新名稱的參數加入空列表
print('starting to write new checkpoint !')
saver = tf.train.Saver(var_list=new_var_list) # 構造一個保存器
sess.run(tf.global_variables_initializer()) # 初始化一下參數(這一步必做)
model_name = 'deeplab_resnet_altered' # 構造一個保存的模型名稱
checkpoint_path = os.path.join(new_checkpoint_path, model_name) # 構造一下保存路徑
saver.save(sess, checkpoint_path) # 直接進行保存
print("done !")
方法2:直接通過tf.train.import_meta_graph()和saver.restore()將模型的所有參數加載到當前圖中,然後再使用 sess.run(name scope)和sess.run(tf.assign())取出模型所在的name scope的數值賦給網絡中需要fine tune的變量.
使用此方法會將預訓練模型的所有參數和圖加載進來並在保存的時候與當前網絡一起保存,使參數更加龐大,必須在訓練結束後定義需要保存的變量,避免保存所有參數。
with tf.variable_scope('train/x'):
w1 = tf.get_variable('w1', shape = [2])
w2 = tf.get_variable( name='w2',shape=[2])
w3 = tf.get_variable( name='w3',shape=[2])
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run('train/x/w3:0'))
saver =tf.train.import_meta_graph('D:\\tuxiang\\hhh\\my_test_model-1.meta')
saver.restore(sess,'D:\\tuxiang\\hhh\\my_test_model-1')
data = sess.run('train/w1:0')
print(data)
sess.run(tf.assign(w1,data))
print(sess.run('train/x/w1:0'))
保存的參數爲:
train/w1 [-0.22390778 1.227742 ]
train/w2 [-0.8072208 -0.2457586 -1.0661628 0.65901166 0.4100374 ]
train/x/w1 [-0.22390778 1.227742 ]
train/x/w2 [ 0.9039186 -0.33169258]
train/x/w3 [-0.88651645 1.0805331 ]
通過在聲明tf.train.Saver類時可以提供一個變量列表來指定需要保存或加載的變量可解決上述問題。
new idea
其實,此方法可以直接通過以下代碼來獲得所有參數名稱,然後選取我們需要fine tune的參數名稱並提取相應的值,通過sess.run(tf.assign())的方法便可fine tune,由於沒導入預訓練模型的圖,所以不存在上述問題。
import os
import tensorflow as tf
import numpy as np
checkpoint_path = 'D:\\tuxiang\\hhh\\hh1\\my_test_model'
for var_name, _ in tf.contrib.framework.list_variables(checkpoint_path):
# 得到checkpoint文件中所有的參數(名字,形狀)元組
var = tf.contrib.framework.load_variable(checkpoint_path, var_name) # 得到上述參數的值
print(var_name,var)
三、固定加載進來的參數
對於某些fine tune的參數,如果希望將這些參數固定住,即不訓練,可以通過在定義變量的時候設置trainable=False;也可以在訓練過程中的minimize()中添加需要訓練的參數,則未添加的參數會固定住。
用var_list = tf.contrib.framework.get_variables(scope_name)獲取指定scope_name下的變量,
然後optimizer.minimize()時傳入指定var_list即可。
train_op = tf.train.GradientDescentOptimizer.minimize(loss,var_list=var_list)
參考文獻:
1.tensorflow 選擇性fine-tune(微調)
2.輸出TensorFlow 模型的變量名和對應值
3.tensorflow從已經訓練好的模型中,恢復(指定)權重(構建新變量、網絡)並繼續訓練(finetuning)
4.Tensorflow Finetune方法