迁移学习和finetune的区别及迁移学习代码实现
轉(zhuǎn)載:https://blog.csdn.net/gentelyang/article/details/77512565
一:區(qū)別
1:遷移學(xué)習(xí)是將已經(jīng)學(xué)習(xí)到的知識(shí)應(yīng)用到其他領(lǐng)域,比如通用的語音模型遷移到某個(gè)人的語音模型上。
?????遷移學(xué)習(xí)就是將一個(gè)問題上訓(xùn)練好的模型通過簡單的調(diào)整使其適用于一個(gè)新的問題。
????例如利用ImageNet數(shù)據(jù)集上訓(xùn)練好的Inception-V3模型來解決一個(gè)新的圖像分類問題,可以保留訓(xùn)練好的Inception-v3模型中所有卷積層的參數(shù),只是替換最后一層全連接層,在最后這一層全連接層之前的網(wǎng)絡(luò)層稱為瓶頸層。而將新的圖像通過訓(xùn)練好的卷積神經(jīng)網(wǎng)絡(luò)直到瓶頸層的過程可以看成是對圖像進(jìn)行特征提取的過程,瓶頸層輸出再通過一個(gè)單層的全連接層神經(jīng)網(wǎng)絡(luò)可以很好的區(qū)分類別,所以有理由相信將瓶頸層的輸出的節(jié)點(diǎn)向量可以被稱為任何圖像的更加精簡且表達(dá)能力更強(qiáng)的特征向量。所以可以直接利用這個(gè)訓(xùn)練好的神經(jīng)網(wǎng)絡(luò)對圖像進(jìn)行特征提取,然后再將提取得到特征向量作為輸入來訓(xùn)練一個(gè)新的單層全連接網(wǎng)絡(luò)來處理分類問題。
????但是在數(shù)據(jù)量足夠的情況下,遷移學(xué)習(xí)的效果不如完全重新訓(xùn)練,但是遷移學(xué)習(xí)所需要的訓(xùn)練時(shí)間和訓(xùn)練樣本要遠(yuǎn)遠(yuǎn)小于訓(xùn)練完整的模型。
??????比如把已經(jīng)訓(xùn)練好的模型的某一層的輸出拿出來,然后用一個(gè)svm、LR等分類,更好的去利用從某一層輸出的特征(也叫知識(shí)),這也還是遷移學(xué)習(xí)的思想,如下前三個(gè)是transfer learning經(jīng)常用到的方法。最后一個(gè)是finetune的思想。
把Alexnet里卷積層最后一層輸出的特征拿出來,然后直接用SVM分類。這是Transfer Learning,因?yàn)槟阌玫搅薃lexnet中已經(jīng)學(xué)到了的“知識(shí)”。把Vggnet卷積層最后的輸出拿出來,用貝葉斯分類器分類。思想基本同上。甚至你可以把Alexnet、Vggnet的輸出拿出來進(jìn)行組合,自己設(shè)計(jì)一個(gè)分類器分類。這個(gè)過程中你不僅用了Alexnet的“知識(shí)”,也用了Vggnet的“知識(shí)”。https://github.com/Gogul09/flower-recognition(此方法實(shí)現(xiàn)代碼)最后,你也可以直接使用fine-tune這種方法,在Alexnet的基礎(chǔ)上,重新加上全連接層,再去訓(xùn)練網(wǎng)絡(luò)。
2:finetune(微調(diào)):例子:在Alexnet的基礎(chǔ)上,我們重新加上一個(gè)層再去訓(xùn)練網(wǎng)絡(luò),比如再加入一個(gè)全連接層,那就是先
固定前面的層,讓新加的fc層的loss值降低到一個(gè)很低的值,再調(diào)低學(xué)習(xí)率,放開所有層一塊去訓(xùn)練這樣可以收斂到一個(gè)不錯(cuò)的效果。
3:所以我個(gè)人認(rèn)為遷移學(xué)習(xí)直接將現(xiàn)有的或者從現(xiàn)有的模型中提取出來的有用的東西應(yīng)用的另一個(gè)領(lǐng)域,不在進(jìn)行訓(xùn)練之前的網(wǎng)絡(luò)部分,只需要訓(xùn)練我們添加部分網(wǎng)絡(luò)的部分,將遷移過來的模型的某一層的輸出作為我們新增加網(wǎng)絡(luò)部分的輸入。
而finetune就是微調(diào),思想是:利用原有模型的參數(shù)信息,作為我們要訓(xùn)練的新的模型的初始化參數(shù),這個(gè)新的模型可以和原來一樣也可以增添幾個(gè)層(進(jìn)行適當(dāng)?shù)恼{(diào)整)。
4:傳統(tǒng)的機(jī)器學(xué)習(xí)框架下,學(xué)習(xí)的任務(wù)是在給定充分訓(xùn)練數(shù)據(jù)集的基礎(chǔ)上學(xué)習(xí)一個(gè)分類模型;然后利用這個(gè)學(xué)習(xí)到的模型來對測試文檔進(jìn)行分類和預(yù)測。然而,我們看到機(jī)器學(xué)習(xí)算法在當(dāng)前web挖掘應(yīng)用領(lǐng)域存在一個(gè)關(guān)鍵問題:一些新出現(xiàn)的領(lǐng)域中的大量訓(xùn)練數(shù)據(jù)非常難得到。web領(lǐng)域中大量新的數(shù)據(jù)不斷涌現(xiàn),從傳統(tǒng)的新聞,網(wǎng)頁,到圖片,再到博客,播客等。傳統(tǒng)的機(jī)器學(xué)習(xí)需要對每個(gè)領(lǐng)域都標(biāo)定大量訓(xùn)練數(shù)據(jù),這將會(huì)耗費(fèi)大量的人力物力,而沒有大量的標(biāo)注數(shù)據(jù),會(huì)使得很多與學(xué)習(xí)相關(guān)研究與應(yīng)用無法開展,其次傳統(tǒng)的機(jī)器學(xué)習(xí)假設(shè)訓(xùn)練數(shù)據(jù)與測試數(shù)據(jù)服從相同的數(shù)據(jù)分布。然而在很多情況下,這種相同分布不滿足,通常可能發(fā)生的情況如訓(xùn)練數(shù)據(jù)過期。如果我們有大量的,在不同分布下的訓(xùn)練數(shù)據(jù),完全丟棄這些數(shù)據(jù)也是非常浪費(fèi)的,如何利用這些數(shù)據(jù)就是遷移學(xué)習(xí)主要解決的問題。
遷移學(xué)習(xí)可以從現(xiàn)有的數(shù)據(jù)中遷移知識(shí),用來幫助將來的學(xué)習(xí)。
遷移學(xué)習(xí)的目標(biāo)是將從一個(gè)環(huán)境中學(xué)到的知識(shí)用來幫助新環(huán)境中的學(xué)習(xí)任務(wù),因此遷移學(xué)習(xí)不會(huì)想傳統(tǒng)機(jī)器學(xué)習(xí)那樣作同分布假設(shè)。
例子:一個(gè)會(huì)下象棋的人可以更容易的學(xué)會(huì)下圍棋。
遷移學(xué)習(xí)目前分為一下三個(gè)部分:同構(gòu)空間下基于實(shí)例的遷移學(xué)習(xí);同構(gòu)空間下基于特征的遷移學(xué)習(xí);異構(gòu)空間下的遷移學(xué)習(xí)。
基于實(shí)例的遷移學(xué)習(xí)有更強(qiáng)的知識(shí)遷移能力,基于特征的遷移學(xué)習(xí)具有更廣規(guī)范的知識(shí)遷移能力;異構(gòu)空間的遷移具有廣泛的學(xué)習(xí)與擴(kuò)展能力。
遷移學(xué)習(xí)即一種學(xué)習(xí)對另一種學(xué)習(xí)的影響,它廣泛的存在于知識(shí)技能態(tài)度和行為的規(guī)范的學(xué)習(xí)中,任何一種學(xué)習(xí)都將受先驗(yàn)知識(shí)的影響,只要有學(xué)習(xí)就有遷移,遷移是學(xué)習(xí)的繼續(xù)和鞏固,優(yōu)勢提高和深化學(xué)習(xí)的條件,學(xué)習(xí)與遷移不可分割。
二:遷移學(xué)習(xí)實(shí)例
為了能夠快速地訓(xùn)練好自己的花朵圖片分類器,我們可以使用別人已經(jīng)訓(xùn)練好的模型參數(shù),在此基礎(chǔ)之上訓(xùn)練我們的模型。這個(gè)便屬于遷移學(xué)習(xí)。本文提供訓(xùn)練數(shù)據(jù)集和代碼下載。?
原理:卷積神經(jīng)網(wǎng)絡(luò)模型總體上可以分為兩部分,前面的卷積層和后面的全連接層。卷積層的作用是圖片特征的提取,全連接層作用是特征的分類。我們的思路便是在inception-v3網(wǎng)絡(luò)模型上,修改全連接層,保留卷積層。卷積層的參數(shù)使用的是別人已經(jīng)訓(xùn)練好的,全連接層的參數(shù)需要我們初始化并使用我們自己的數(shù)據(jù)來訓(xùn)練和學(xué)習(xí)。
上面inception-v3模型圖紅色箭頭前面部分是卷積層,后面是全連接層。我們需要修改修改全連接層,同時(shí)把模型的最終輸出改為5。
由于這里使用了tensorflow框架,所以,我們需要獲取上圖紅色箭頭所在位置的張量BOTTLENECK_TENSOR_NAME(最后一個(gè)卷積層激活函數(shù)的輸出值,個(gè)數(shù)為2048)以及模型最開始的輸入數(shù)據(jù)的張量JPEG_DATA_TENSOR_NAME。獲取這兩個(gè)張量的作用是,圖片訓(xùn)練數(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í)獲取上面所述的兩個(gè)張量。
最后便是定義交叉熵?fù)p失函數(shù)。模型使用反向傳播訓(xùn)練,而訓(xùn)練的參數(shù)并不是模型的所有參數(shù),僅僅是全連接層的參數(shù),卷積層的參數(shù)是不變的。
定義交叉熵?fù)p失函數(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ù)了,這里提供了幾個(gè)操作數(shù)據(jù)的函數(shù)。由于訓(xùn)練數(shù)據(jù)集比較小,
先把所有的圖片通過JPEG_DATA_TENSOR_NAME張量輸入模型,然后獲取BOTTLENECK_TENSOR_NAME張量的值并保存到硬盤中。
在模型訓(xùn)練的時(shí)候,從硬盤中讀取所保存的BOTTLENECK_TENSOR_NAME張量的值作為全連接層的輸入數(shù)據(jù)。因?yàn)橐粡垐D片可能會(huì)被使用多次。
運(yùn)行代碼在到時(shí)候再去看我的pycharm中的trasform這個(gè)項(xiàng)目。
這個(gè)代碼實(shí)現(xiàn)部分參考的是https://blog.csdn.net/liangyihuai/article/details/79219457這個(gè)博客的內(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#最后一個(gè)卷積層激活函數(shù)輸出值,個(gè)數(shù)是2048個(gè)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'#模型文件的準(zhǔn)確位置
CACHE_DIR = './bottleneck'#最后一個(gè)卷積層輸出的每一類的特征,這個(gè)要輸入到fc中然后進(jìn)行分類用的。
INPUT_DATA = './flower_photos'
VALIDATION_PERCENTAGE = 10#
TEST_PERCENTAGE = 10#驗(yàn)證集測試集都占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)#這個(gè)只是取回去文件名,去掉其路徑
? ? ? ? ? ? #basename的作用是去掉目錄的路徑,只返回文件名,而dirname用于 去掉文件名,只返回目錄所在的路徑。os.split()的作用是返回路徑名和文件名的元組
? ? ? ? ? ? # 隨機(jī)劃分?jǐn)?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)訓(xùn)練好的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)絡(luò)輸入
? ? bottleneck_input = tf.placeholder(tf.float32, [None, BOTTLENECK_TENSOR_SIZE], name='BottleneckInputPlaceholder')
? ? ground_truth_input = tf.placeholder(tf.float32, [None, n_classes], name='GroundTruthInput')
? ? # 定義一個(gè)權(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)
? ? # 計(jì)算準(zhǔn)確率
? ? 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中,值最大的一個(gè)值,1在這里是指的返回一個(gè)值
? ? ? ? #final_tensor返回的是概率的大小,返回的是概率的最大值作為最后的值
? ? ? ? #tf.equal()的用法是比較tf.argmax(final_tensor, 1)和 tf.argmax(ground_truth_input, 1)對應(yīng)位置的值是否相等,
? ? ? ? #相等的時(shí)候返回true,否則返回false,然后統(tǒng)計(jì)true多占的比列就是最后的準(zhǔn)確率。
? ? ? ? evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
? ? ? ? #tf.cast()的作用是改變數(shù)據(jù)類型的,改變correct_prediction的數(shù)據(jù)類型為float32
? ? ? ? #tf.reduce_mean()的用法,在tensor的某一維度上,計(jì)算元素的平均值,由于輸出的維度比原tensor降低了,所以也叫做降為。
? ? with tf.Session() as sess:
? ? ? ? sess.run(tf.global_variables_initializer())
? ? ? ? # 訓(xùn)練過程
? ? ? ? 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,從磁盤讀入的張量值作為輸入向量,來訓(xùn)練全鏈接層,
? ? ? ? ? ? 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__':#防止導(dǎo)入的包中的內(nèi)容,也就是import 后面的內(nèi)容也被運(yùn)行
? ? main()
? ? #python是腳本語言,不像編譯語言一樣,先將程序編譯成二進(jìn)制再運(yùn)行 ,而是動(dòng)態(tài)的逐行解釋運(yùn)行,也就是從腳本的第一行開始運(yùn)行,沒有統(tǒng)一的入口。
? ? #一個(gè)python源碼除了可以直接運(yùn)行外,還可以最為模塊,也就是庫導(dǎo)入,不管是導(dǎo)入還是運(yùn)行,最頂層的代碼都會(huì)被運(yùn)行,python用縮進(jìn)來區(qū)分代碼層次,而
? ? #實(shí)際上在導(dǎo)入的時(shí)候,有一部分代碼我們是不希望被運(yùn)行的。
? ? #if __name__ =='main'就相當(dāng)于程序的入口,python本身并沒有規(guī)定這莫寫,這只是一種編程習(xí)慣,由于模塊之間相互引用,不同模塊可能有這樣的定義
? ? #,而入口程序只能有一個(gè),到底那一額入口程序被選中,這就取決于 __name__的值。
? ? #__name__可以清晰的反映一個(gè)模塊在包中的層次,其實(shí),所謂模塊的在包中的層次。__name__是內(nèi)置變量,用于表示當(dāng)前模塊的名字,同時(shí)還能反映一個(gè)
? ? #包的結(jié)構(gòu),如果模塊直接運(yùn)行的,則代碼被運(yùn)行,如果模塊被導(dǎo)入的,則代碼不能運(yùn)行。
?
總結(jié)
以上是生活随笔為你收集整理的迁移学习和finetune的区别及迁移学习代码实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习之路---node.js(二)
- 下一篇: 年月日时间和64位时间的使用及相互转换