迁移学习和finetune的区别及迁移学习代码实现
轉(zhuǎn)載:https://blog.csdn.net/gentelyang/article/details/77512565
一:區(qū)別
1:遷移學習是將已經(jīng)學習到的知識應用到其他領(lǐng)域,比如通用的語音模型遷移到某個人的語音模型上。
?????遷移學習就是將一個問題上訓練好的模型通過簡單的調(diào)整使其適用于一個新的問題。
????例如利用ImageNet數(shù)據(jù)集上訓練好的Inception-V3模型來解決一個新的圖像分類問題,可以保留訓練好的Inception-v3模型中所有卷積層的參數(shù),只是替換最后一層全連接層,在最后這一層全連接層之前的網(wǎng)絡層稱為瓶頸層。而將新的圖像通過訓練好的卷積神經(jīng)網(wǎng)絡直到瓶頸層的過程可以看成是對圖像進行特征提取的過程,瓶頸層輸出再通過一個單層的全連接層神經(jīng)網(wǎng)絡可以很好的區(qū)分類別,所以有理由相信將瓶頸層的輸出的節(jié)點向量可以被稱為任何圖像的更加精簡且表達能力更強的特征向量。所以可以直接利用這個訓練好的神經(jīng)網(wǎng)絡對圖像進行特征提取,然后再將提取得到特征向量作為輸入來訓練一個新的單層全連接網(wǎng)絡來處理分類問題。
????但是在數(shù)據(jù)量足夠的情況下,遷移學習的效果不如完全重新訓練,但是遷移學習所需要的訓練時間和訓練樣本要遠遠小于訓練完整的模型。
??????比如把已經(jīng)訓練好的模型的某一層的輸出拿出來,然后用一個svm、LR等分類,更好的去利用從某一層輸出的特征(也叫知識),這也還是遷移學習的思想,如下前三個是transfer learning經(jīng)常用到的方法。最后一個是finetune的思想。
把Alexnet里卷積層最后一層輸出的特征拿出來,然后直接用SVM分類。這是Transfer Learning,因為你用到了Alexnet中已經(jīng)學到了的“知識”。把Vggnet卷積層最后的輸出拿出來,用貝葉斯分類器分類。思想基本同上。甚至你可以把Alexnet、Vggnet的輸出拿出來進行組合,自己設(shè)計一個分類器分類。這個過程中你不僅用了Alexnet的“知識”,也用了Vggnet的“知識”。https://github.com/Gogul09/flower-recognition(此方法實現(xiàn)代碼)最后,你也可以直接使用fine-tune這種方法,在Alexnet的基礎(chǔ)上,重新加上全連接層,再去訓練網(wǎng)絡。
2:finetune(微調(diào)):例子:在Alexnet的基礎(chǔ)上,我們重新加上一個層再去訓練網(wǎng)絡,比如再加入一個全連接層,那就是先
固定前面的層,讓新加的fc層的loss值降低到一個很低的值,再調(diào)低學習率,放開所有層一塊去訓練這樣可以收斂到一個不錯的效果。
3:所以我個人認為遷移學習直接將現(xiàn)有的或者從現(xiàn)有的模型中提取出來的有用的東西應用的另一個領(lǐng)域,不在進行訓練之前的網(wǎng)絡部分,只需要訓練我們添加部分網(wǎng)絡的部分,將遷移過來的模型的某一層的輸出作為我們新增加網(wǎng)絡部分的輸入。
而finetune就是微調(diào),思想是:利用原有模型的參數(shù)信息,作為我們要訓練的新的模型的初始化參數(shù),這個新的模型可以和原來一樣也可以增添幾個層(進行適當?shù)恼{(diào)整)。
4:傳統(tǒng)的機器學習框架下,學習的任務是在給定充分訓練數(shù)據(jù)集的基礎(chǔ)上學習一個分類模型;然后利用這個學習到的模型來對測試文檔進行分類和預測。然而,我們看到機器學習算法在當前web挖掘應用領(lǐng)域存在一個關(guān)鍵問題:一些新出現(xiàn)的領(lǐng)域中的大量訓練數(shù)據(jù)非常難得到。web領(lǐng)域中大量新的數(shù)據(jù)不斷涌現(xiàn),從傳統(tǒng)的新聞,網(wǎng)頁,到圖片,再到博客,播客等。傳統(tǒng)的機器學習需要對每個領(lǐng)域都標定大量訓練數(shù)據(jù),這將會耗費大量的人力物力,而沒有大量的標注數(shù)據(jù),會使得很多與學習相關(guān)研究與應用無法開展,其次傳統(tǒng)的機器學習假設(shè)訓練數(shù)據(jù)與測試數(shù)據(jù)服從相同的數(shù)據(jù)分布。然而在很多情況下,這種相同分布不滿足,通常可能發(fā)生的情況如訓練數(shù)據(jù)過期。如果我們有大量的,在不同分布下的訓練數(shù)據(jù),完全丟棄這些數(shù)據(jù)也是非常浪費的,如何利用這些數(shù)據(jù)就是遷移學習主要解決的問題。
遷移學習可以從現(xiàn)有的數(shù)據(jù)中遷移知識,用來幫助將來的學習。
遷移學習的目標是將從一個環(huán)境中學到的知識用來幫助新環(huán)境中的學習任務,因此遷移學習不會想傳統(tǒng)機器學習那樣作同分布假設(shè)。
例子:一個會下象棋的人可以更容易的學會下圍棋。
遷移學習目前分為一下三個部分:同構(gòu)空間下基于實例的遷移學習;同構(gòu)空間下基于特征的遷移學習;異構(gòu)空間下的遷移學習。
基于實例的遷移學習有更強的知識遷移能力,基于特征的遷移學習具有更廣規(guī)范的知識遷移能力;異構(gòu)空間的遷移具有廣泛的學習與擴展能力。
遷移學習即一種學習對另一種學習的影響,它廣泛的存在于知識技能態(tài)度和行為的規(guī)范的學習中,任何一種學習都將受先驗知識的影響,只要有學習就有遷移,遷移是學習的繼續(xù)和鞏固,優(yōu)勢提高和深化學習的條件,學習與遷移不可分割。
二:遷移學習實例
為了能夠快速地訓練好自己的花朵圖片分類器,我們可以使用別人已經(jīng)訓練好的模型參數(shù),在此基礎(chǔ)之上訓練我們的模型。這個便屬于遷移學習。本文提供訓練數(shù)據(jù)集和代碼下載。?
原理:卷積神經(jīng)網(wǎng)絡模型總體上可以分為兩部分,前面的卷積層和后面的全連接層。卷積層的作用是圖片特征的提取,全連接層作用是特征的分類。我們的思路便是在inception-v3網(wǎng)絡模型上,修改全連接層,保留卷積層。卷積層的參數(shù)使用的是別人已經(jīng)訓練好的,全連接層的參數(shù)需要我們初始化并使用我們自己的數(shù)據(jù)來訓練和學習。
上面inception-v3模型圖紅色箭頭前面部分是卷積層,后面是全連接層。我們需要修改修改全連接層,同時把模型的最終輸出改為5。
由于這里使用了tensorflow框架,所以,我們需要獲取上圖紅色箭頭所在位置的張量BOTTLENECK_TENSOR_NAME(最后一個卷積層激活函數(shù)的輸出值,個數(shù)為2048)以及模型最開始的輸入數(shù)據(jù)的張量JPEG_DATA_TENSOR_NAME。獲取這兩個張量的作用是,圖片訓練數(shù)據(jù)通過JPEG_DATA_TENSOR_NAME張量輸入模型,通過BOTTLENECK_TENSOR_NAME張量獲取通過卷積層之后的圖片特征。
BOTTLENECK_TENSOR_SIZE = 2048
BOTTLENECK_TENSOR_NAME = 'pool_3/_reshape:0'
JPEG_DATA_TENSOR_NAME = 'DecodeJpeg/contents:0'
通過下面的代碼加載模型,同時獲取上面所述的兩個張量。
最后便是定義交叉熵損失函數(shù)。模型使用反向傳播訓練,而訓練的參數(shù)并不是模型的所有參數(shù),僅僅是全連接層的參數(shù),卷積層的參數(shù)是不變的。
定義交叉熵損失函數(shù)。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=ground_truth_input)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(cross_entropy_mean)
那么接下來的是如何給我們的模型輸入數(shù)據(jù)了,這里提供了幾個操作數(shù)據(jù)的函數(shù)。由于訓練數(shù)據(jù)集比較小,
先把所有的圖片通過JPEG_DATA_TENSOR_NAME張量輸入模型,然后獲取BOTTLENECK_TENSOR_NAME張量的值并保存到硬盤中。
在模型訓練的時候,從硬盤中讀取所保存的BOTTLENECK_TENSOR_NAME張量的值作為全連接層的輸入數(shù)據(jù)。因為一張圖片可能會被使用多次。
運行代碼在到時候再去看我的pycharm中的trasform這個項目。
這個代碼實現(xiàn)部分參考的是https://blog.csdn.net/liangyihuai/article/details/79219457這個博客的內(nèi)容
完整代碼
# coding=utf8
import glob
import os.path
import random
import numpy as np
import tensorflow as tf
from tensorflow.python.platform import gfile
BOTTLENECK_TENSOR_SIZE = 2048#最后一個卷積層激活函數(shù)輸出值,個數(shù)是2048個1×1的
BOTTLENECK_TENSOR_NAME = 'pool_3/_reshape:0'#張量獲取通過卷積層之后的圖片特征
JPEG_DATA_TENSOR_NAME = 'DecodeJpeg/contents:0'#模型開始的輸入數(shù)據(jù)的張量,張量輸入
MODEL_DIR = './inception_dec_2015'#inception模型的聞之
MODEL_FILE= 'tensorflow_inception_graph.pb'#模型文件的準確位置
CACHE_DIR = './bottleneck'#最后一個卷積層輸出的每一類的特征,這個要輸入到fc中然后進行分類用的。
INPUT_DATA = './flower_photos'
VALIDATION_PERCENTAGE = 10#
TEST_PERCENTAGE = 10#驗證集測試集都占10%
LEARNING_RATE = 0.01
STEPS = 4000
BATCH = 100
def create_image_lists(testing_percentage, validation_percentage):
? ? result = {}
? ? sub_dirs = [x[0] for x in os.walk(INPUT_DATA)]#os.walk()方法的作用是在目錄樹中游走輸出在目錄中的文件名,向上或者向下。
? ? is_root_dir = True
? ? for sub_dir in sub_dirs:
? ? ? ? if is_root_dir:
? ? ? ? ? ? is_root_dir = False
? ? ? ? ? ? continue
? ? ? ? extensions = ['jpg', 'jpeg', 'JPG', 'JPEG']
? ? ? ? file_list = []
? ? ? ? dir_name = os.path.basename(sub_dir)
? ? ? ? for extension in extensions:
? ? ? ? ? ? file_glob = os.path.join(INPUT_DATA, dir_name, '*.' + extension)
? ? ? ? ? ? file_list.extend(glob.glob(file_glob))
? ? ? ? if not file_list: continue
? ? ? ? label_name = dir_name.lower()
? ? ? ? #初始化
? ? ? ? training_images = []
? ? ? ? testing_images = []
? ? ? ? validation_images = []
? ? ? ? for file_name in file_list:
? ? ? ? ? ? base_name = os.path.basename(file_name)#這個只是取回去文件名,去掉其路徑
? ? ? ? ? ? #basename的作用是去掉目錄的路徑,只返回文件名,而dirname用于 去掉文件名,只返回目錄所在的路徑。os.split()的作用是返回路徑名和文件名的元組
? ? ? ? ? ? # 隨機劃分數(shù)據(jù)
? ? ? ? ? ? chance = np.random.randint(100)
? ? ? ? ? ? if chance < validation_percentage:
? ? ? ? ? ? ? ? validation_images.append(base_name)
? ? ? ? ? ? elif chance < (testing_percentage + validation_percentage):
? ? ? ? ? ? ? ? testing_images.append(base_name)
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? training_images.append(base_name)
? ? ? ? result[label_name] = {
? ? ? ? ? ? 'dir': dir_name,
? ? ? ? ? ? 'training': training_images,
? ? ? ? ? ? 'testing': testing_images,
? ? ? ? ? ? 'validation': validation_images,
? ? ? ? }
? ? return result
def get_image_path(image_lists, image_dir, label_name, index, category):
? ? label_lists = image_lists[label_name]
? ? category_list = label_lists[category]
? ? mod_index = index % len(category_list)
? ? base_name = category_list[mod_index]
? ? sub_dir = label_lists['dir']
? ? full_path = os.path.join(image_dir, sub_dir, base_name)
? ? return full_path
def get_bottleneck_path(image_lists, label_name, index, category):
? ? return get_image_path(image_lists, CACHE_DIR, label_name, index, category) + '.txt'
def run_bottleneck_on_image(sess, image_data, image_data_tensor, bottleneck_tensor):
? ? bottleneck_values = sess.run(bottleneck_tensor, {image_data_tensor: image_data})
? ? bottleneck_values = np.squeeze(bottleneck_values)
? ? return bottleneck_values
def get_or_create_bottleneck(sess, image_lists, label_name, index, category, jpeg_data_tensor, bottleneck_tensor):
? ? label_lists = image_lists[label_name]
? ? sub_dir = label_lists['dir']
? ? sub_dir_path = os.path.join(CACHE_DIR, sub_dir)
? ? if not os.path.exists(sub_dir_path): os.makedirs(sub_dir_path)
? ? bottleneck_path = get_bottleneck_path(image_lists, label_name, index, category)
? ? if not os.path.exists(bottleneck_path):
? ? ? ? image_path = get_image_path(image_lists, INPUT_DATA, label_name, index, category)
? ? ? ? image_data = gfile.FastGFile(image_path, 'rb').read()
? ? ? ? bottleneck_values = run_bottleneck_on_image(sess, image_data, jpeg_data_tensor, bottleneck_tensor)
? ? ? ? bottleneck_string = ','.join(str(x) for x in bottleneck_values)
? ? ? ? with open(bottleneck_path, 'w') as bottleneck_file:
? ? ? ? ? ? bottleneck_file.write(bottleneck_string)
? ? else:
? ? ? ? with open(bottleneck_path, 'r') as bottleneck_file:
? ? ? ? ? ? bottleneck_string = bottleneck_file.read()
? ? ? ? bottleneck_values = [float(x) for x in bottleneck_string.split(',')]
? ? return bottleneck_values
def get_random_cached_bottlenecks(sess, n_classes, image_lists, how_many, category, jpeg_data_tensor, bottleneck_tensor):
? ? bottlenecks = []
? ? ground_truths = []
? ? for _ in range(how_many):
? ? ? ? label_index = random.randrange(n_classes)
? ? ? ? label_name = list(image_lists.keys())[label_index]
? ? ? ? image_index = random.randrange(65536)
? ? ? ? bottleneck = get_or_create_bottleneck(
? ? ? ? ? ? sess, image_lists, label_name, image_index, category, jpeg_data_tensor, bottleneck_tensor)
? ? ? ? ground_truth = np.zeros(n_classes, dtype=np.float32)
? ? ? ? ground_truth[label_index] = 1.0
? ? ? ? bottlenecks.append(bottleneck)
? ? ? ? ground_truths.append(ground_truth)
? ? return bottlenecks, ground_truths
def get_test_bottlenecks(sess, image_lists, n_classes, jpeg_data_tensor, bottleneck_tensor):
? ? bottlenecks = []
? ? ground_truths = []
? ? label_name_list = list(image_lists.keys())
? ? for label_index, label_name in enumerate(label_name_list):
? ? ? ? category = 'testing'
? ? ? ? for index, unused_base_name in enumerate(image_lists[label_name][category]):
? ? ? ? ? ? bottleneck = get_or_create_bottleneck(sess, image_lists, label_name, index, category,jpeg_data_tensor, bottleneck_tensor)
? ? ? ? ? ? ground_truth = np.zeros(n_classes, dtype=np.float32)
? ? ? ? ? ? ground_truth[label_index] = 1.0
? ? ? ? ? ? bottlenecks.append(bottleneck)
? ? ? ? ? ? ground_truths.append(ground_truth)
? ? return bottlenecks, ground_truths
def main():
? ? image_lists = create_image_lists(TEST_PERCENTAGE, VALIDATION_PERCENTAGE)
? ? n_classes = len(image_lists.keys())
? ? # 讀取已經(jīng)訓練好的Inception-v3的模型
? ? with gfile.FastGFile(os.path.join(MODEL_DIR, MODEL_FILE), 'rb') as f:
? ? ? ? graph_def = tf.GraphDef()
? ? ? ? graph_def.ParseFromString(f.read())
? ? bottleneck_tensor, jpeg_data_tensor = tf.import_graph_def(
? ? ? ? graph_def, return_elements=[BOTTLENECK_TENSOR_NAME, JPEG_DATA_TENSOR_NAME])
? ? # 定義新的神經(jīng)網(wǎng)絡輸入
? ? bottleneck_input = tf.placeholder(tf.float32, [None, BOTTLENECK_TENSOR_SIZE], name='BottleneckInputPlaceholder')
? ? ground_truth_input = tf.placeholder(tf.float32, [None, n_classes], name='GroundTruthInput')
? ? # 定義一個權(quán)鏈接層
? ? with tf.name_scope('final_training_ops'):
? ? ? ? weights = tf.Variable(tf.truncated_normal([BOTTLENECK_TENSOR_SIZE, n_classes], stddev=0.001))
? ? ? ? biases = tf.Variable(tf.zeros([n_classes]))
? ? ? ? logits = tf.matmul(bottleneck_input, weights) + biases
? ? ? ? final_tensor = tf.nn.softmax(logits)
? ? # 定義交叉商損失函數(shù)
? ? cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=ground_truth_input)
? ? cross_entropy_mean = tf.reduce_mean(cross_entropy)
? ? train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(cross_entropy_mean)
? ? # 計算準確率
? ? with tf.name_scope('evaluation'):
? ? ? ? correct_prediction = tf.equal(tf.argmax(final_tensor, 1), tf.argmax(ground_truth_input, 1))
? ? ? ? #tf.argmax()的用法,tf.argmax(final_tensor,1)返回的是final_tensor中,值最大的一個值,1在這里是指的返回一個值
? ? ? ? #final_tensor返回的是概率的大小,返回的是概率的最大值作為最后的值
? ? ? ? #tf.equal()的用法是比較tf.argmax(final_tensor, 1)和 tf.argmax(ground_truth_input, 1)對應位置的值是否相等,
? ? ? ? #相等的時候返回true,否則返回false,然后統(tǒng)計true多占的比列就是最后的準確率。
? ? ? ? evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
? ? ? ? #tf.cast()的作用是改變數(shù)據(jù)類型的,改變correct_prediction的數(shù)據(jù)類型為float32
? ? ? ? #tf.reduce_mean()的用法,在tensor的某一維度上,計算元素的平均值,由于輸出的維度比原tensor降低了,所以也叫做降為。
? ? with tf.Session() as sess:
? ? ? ? sess.run(tf.global_variables_initializer())
? ? ? ? # 訓練過程
? ? ? ? for i in range(STEPS):
? ? ? ? ? ? train_bottlenecks, train_ground_truth = get_random_cached_bottlenecks(
? ? ? ? ? ? ? ? sess, n_classes, image_lists, BATCH, 'training', jpeg_data_tensor, bottleneck_tensor)
? ? ? ? ? ? sess.run(train_step,
? ? ? ? ? ? ? ? ? ? ?feed_dict={bottleneck_input: train_bottlenecks, ground_truth_input: train_ground_truth})
? ? ? ? ? ? #這里是train_bottlenecks,從磁盤讀入的張量值作為輸入向量,來訓練全鏈接層,
? ? ? ? ? ? if i % 100 == 0 or i + 1 == STEPS:
? ? ? ? ? ? ? ? validation_bottlenecks, validation_ground_truth = get_random_cached_bottlenecks(
? ? ? ? ? ? ? ? ? ? sess, n_classes, image_lists, BATCH, 'validation', jpeg_data_tensor, bottleneck_tensor)
? ? ? ? ? ? ? ? validation_accuracy = sess.run(evaluation_step, feed_dict={
? ? ? ? ? ? ? ? ? ? bottleneck_input: validation_bottlenecks, ground_truth_input: validation_ground_truth})
? ? ? ? ? ? ? ? print('Step %d: Validation accuracy on random sampled %d examples = %.1f%%' %
? ? ? ? ? ? ? ? ? ? ? (i, BATCH, validation_accuracy * 100))
? ? ? ? # 在最后的測試數(shù)據(jù)上測試正確率
? ? ? ? test_bottlenecks, test_ground_truth = get_test_bottlenecks(
? ? ? ? ? ? sess, image_lists, n_classes, jpeg_data_tensor, bottleneck_tensor)
? ? ? ? test_accuracy = sess.run(evaluation_step, feed_dict={
? ? ? ? ? ? bottleneck_input: test_bottlenecks, ground_truth_input: test_ground_truth})
? ? ? ? print('Final test accuracy = %.1f%%' % (test_accuracy * 100))
if __name__ == '__main__':#防止導入的包中的內(nèi)容,也就是import 后面的內(nèi)容也被運行
? ? main()
? ? #python是腳本語言,不像編譯語言一樣,先將程序編譯成二進制再運行 ,而是動態(tài)的逐行解釋運行,也就是從腳本的第一行開始運行,沒有統(tǒng)一的入口。
? ? #一個python源碼除了可以直接運行外,還可以最為模塊,也就是庫導入,不管是導入還是運行,最頂層的代碼都會被運行,python用縮進來區(qū)分代碼層次,而
? ? #實際上在導入的時候,有一部分代碼我們是不希望被運行的。
? ? #if __name__ =='main'就相當于程序的入口,python本身并沒有規(guī)定這莫寫,這只是一種編程習慣,由于模塊之間相互引用,不同模塊可能有這樣的定義
? ? #,而入口程序只能有一個,到底那一額入口程序被選中,這就取決于 __name__的值。
? ? #__name__可以清晰的反映一個模塊在包中的層次,其實,所謂模塊的在包中的層次。__name__是內(nèi)置變量,用于表示當前模塊的名字,同時還能反映一個
? ? #包的結(jié)構(gòu),如果模塊直接運行的,則代碼被運行,如果模塊被導入的,則代碼不能運行。
?
總結(jié)
以上是生活随笔為你收集整理的迁移学习和finetune的区别及迁移学习代码实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习之路---node.js(二)
- 下一篇: 年月日时间和64位时间的使用及相互转换