遷移學習就是用別人已經訓練好的模型,如:Inception Model,Resnet Model等,把它當作Pre-trained Model,幫助咱們提取特徵。經常使用方法是去除Pre-trained Model的最後一層,按照本身的需求從新更改,而後用訓練集訓練。
由於Pre-trained Model可能已經使用過大量數據集,通過了長時間的訓練,因此咱們經過遷移學習可使用較少的數據集訓練就能夠得到相對不錯的結果。git
因爲項目中使用到Estimator,因此咱們再簡單介紹下Estimator。github
這裏引用下官網 Estimator的介紹。編程
咱們可使用「tf.keras.estimator.model_to_estimator」將keras轉換Estimator。這裏使用的數據集是Fashion-MNIST。安全
Fashion-MNIST數據標籤:服務器
數據導入:網絡
import os import time import tensorflow as tf import numpy as np import tensorflow.contrib as tcon (train_image,train_lables),(test_image,test_labels)=tf.keras.datasets.fashion_mnist.load_data() TRAINING_SIZE=len(train_image) TEST_SIZE=len(test_image) # 將像素值由0-255 轉爲0-1 之間 train_image=np.asarray(train_image,dtype=np.float32)/255 # 4維張量[batch_size,height,width,channels] train_image=train_image.reshape(shape=(TRAINING_SIZE,28,28,1)) test_image=np.asarray(test_image,dtype=np.float32)/255 test_image=test_image.reshape(shape=(TEST_SIZE,28,28,1))
使用tf.keras.utils.to_categorical將標籤轉爲獨熱編碼表示:app
# lables 轉爲 one_hot表示 # 類別數量 LABEL_DIMENSIONS=10 train_lables_onehot=tf.keras.utils.to_categorical( y=train_lables,num_classes=LABEL_DIMENSIONS ) test_labels_onehot=tf.keras.utils.to_categorical( y=test_labels,num_classes=LABEL_DIMENSIONS ) train_lables_onehot=train_lables_onehot.astype(np.float32) test_labels_onehot=test_labels_onehot.astype(np.float32)
建立Keras模型:機器學習
「」「 3層卷積層,2層池化層,最後展平添加全鏈接層使用softmax分類 」「」 inputs=tf.keras.Input(shape=(28,28,1)) conv_1=tf.keras.layers.Conv2D( filters=32, kernel_size=3, # relu激活函數在輸入值爲負值時,激活值爲0,此時可使用LeakyReLU activation=tf.nn.relu )(inputs) pool_1=tf.keras.layers.MaxPooling2D( pool_size=2, strides=2 )(conv_1) conv_2=tf.keras.layers.Conv2D( filters=64, kernel_size=3, activation=tf.nn.relu )(pool_1) pool_2=tf.keras.layers.MaxPooling2D( pool_size=2, strides=2 )(conv_2) conv_3=tf.keras.layers.Conv2D( filters=64, kernel_size=3, activation=tf.nn.relu )(pool_2) conv_flat=tf.keras.layers.Flatten()(conv_3) dense_64=tf.keras.layers.Dense( units=64, activation=tf.nn.relu )(conv_flat) predictions=tf.keras.layers.Dense( units=LABEL_DIMENSIONS, activation=tf.nn.softmax )(dense_64)
模型配置:分佈式
model=tf.keras.Model( inputs=inputs, outputs=predictions ) model.compile( loss='categorical_crossentropy', optimizer=tf.train.AdamOptimizer(learning_rate=0.001), metrics=['accuracy'] )
指定GPU數量,而後將keras轉爲Estimator,代碼以下:ide
NUM_GPUS=2 strategy=tcon.distribute.MirroredStrategy(num_gpus=NUM_GPUS) config=tf.estimator.RunConfig(train_distribute=strategy) estimator=tf.keras.estimator.model_to_estimator( keras_model=model,config=config )
前面說到過使用 Estimator 編寫應用時,您必須將數據輸入管道從模型中分離出來,因此,咱們先建立input function。使用prefetch將data預置緩衝區能夠加快數據讀取。由於下面的遷移訓練使用的數據集較大,因此在這裏有必要介紹下優化數據輸入管道的相關內容。
TensorFlow數據輸入管道是如下三個過程:
數據讀取:
一般,當CPU爲計算準備數據時,GPU/TPU處於閒置狀態;當GPU/TPU運行時,CPU處於閒置,顯然設備沒有被合理利用。
tf.data.Dataset.prefetch能夠將上述行爲並行實現,當GPU/TPU執行第N次訓練,此時讓CPU準備N+1次訓練使兩個操做重疊,從而利用設備空閒時間。
經過使用tf.contrib.data.parallel_interleave能夠並行從多個文件讀取數據,並行文件數有cycle_length指定。
數據轉換:
使用tf.data.Dataset.map對數據集中的數據進行處理,因爲數據獨立,因此能夠並行處理。此函數讀取的文件是含有肯定性順序,若是順序對訓練沒有影響,也能夠取消肯定性順序加快訓練。
def input_fn(images,labels,epochs,batch_size): ds=tf.data.Dataset.from_tensor_slices((images,labels)) # repeat值爲None或者-1時將無限制迭代 ds=ds.shuffle(500).repeat(epochs).batch(batch_size).prefetch(batch_size) return ds
# 用於計算迭代時間 class TimeHistory(tf.train.SessionRunHook): def begin(self): self.times = [] def before_run(self, run_context): self.iter_time_start = time.time() def after_run(self, run_context, run_values): self.times.append(time.time() - self.iter_time_start) time_hist = TimeHistory() BATCH_SIZE = 512 EPOCHS = 5 # lambda爲了填寫參數 estimator.train(lambda:input_fn(train_images, train_labels, epochs=EPOCHS, batch_size=BATCH_SIZE), hooks=[time_hist]) # 訓練時間 total_time = sum(time_hist.times) print(f"total time with {NUM_GPUS} GPU(s): {total_time} seconds") # 訓練數據量 avg_time_per_batch = np.mean(time_hist.times) print(f"{BATCH_SIZE*NUM_GPUS/avg_time_per_batch} images/second with {NUM_GPUS} GPU(s)")
結果如圖:
得益於Estimator數據輸入和模型的分離,評估方法很簡單。
estimator.evaluate(lambda:input_fn(test_images, test_labels, epochs=1, batch_size=BATCH_SIZE))
咱們使用Retinal OCT images數據集進行遷移訓練,數據標籤爲:NORMAL, CNV, DME DRUSEN,包含分辨率爲512*296,84495張照片。
數據讀取,設置input_fn:
labels = ['CNV', 'DME', 'DRUSEN', 'NORMAL'] train_folder = os.path.join('OCT2017', 'train', '**', '*.jpeg') test_folder = os.path.join('OCT2017', 'test', '**', '*.jpeg')
def input_fn(file_pattern, labels, image_size=(224,224), shuffle=False, batch_size=64, num_epochs=None, buffer_size=4096, prefetch_buffer_size=None): # 建立查找表,將string 轉爲 int 64ID table = tcon.lookup.index_table_from_tensor(mapping=tf.constant(labels)) num_classes = len(labels) def _map_func(filename): # sep = '/' label = tf.string_split([filename], delimiter=os.sep).values[-2] image = tf.image.decode_jpeg(tf.read_file(filename), channels=3) image = tf.image.convert_image_dtype(image, dtype=tf.float32) image = tf.image.resize_images(image, size=image_size) # tf.one_hot:根據輸入的depth返回one_hot張量 # indices = [0, 1, 2] # depth = 3 # tf.one_hot(indices, depth) return: # [[1., 0., 0.], # [0., 1., 0.], # [0., 0., 1.]] return (image, tf.one_hot(table.lookup(label), num_classes)) dataset = tf.data.Dataset.list_files(file_pattern, shuffle=shuffle) if num_epochs is not None and shuffle: dataset = dataset.apply( tcon.data.shuffle_and_repeat(buffer_size, num_epochs)) elif shuffle: dataset = dataset.shuffle(buffer_size) elif num_epochs is not None: dataset = dataset.repeat(num_epochs) dataset = dataset.apply( tcon.data.map_and_batch(map_func=_map_func, batch_size=batch_size, num_parallel_calls=os.cpu_count())) dataset = dataset.prefetch(buffer_size=prefetch_buffer_size) return dataset
經過keras使用預訓練的VGG16網絡,咱們重訓練最後5層:
# include_top:不包含最後3個全鏈接層 keras_vgg16 = tf.keras.applications.VGG16(input_shape=(224,224,3), include_top=False) output = keras_vgg16.output output = tf.keras.layers.Flatten()(output) prediction = tf.keras.layers.Dense(len(labels), activation=tf.nn.softmax)(output) model = tf.keras.Model(inputs=keras_vgg16.input, outputs=prediction) # 後5層不訓練 for layer in keras_vgg16.layers[:-4]: layer.trainable = False
從新訓練模型:
# 經過遷移學習獲得模型 model.compile(loss='categorical_crossentropy', # 使用默認學習率 optimizer=tf.train.AdamOptimizer(), metrics=['accuracy']) NUM_GPUS = 2 strategy = tf.contrib.distribute.MirroredStrategy(num_gpus=NUM_GPUS) config = tf.estimator.RunConfig(train_distribute=strategy) # 轉至estimator estimator = tf.keras.estimator.model_to_estimator(model, config=config) BATCH_SIZE = 64 EPOCHS = 1 estimator.train(input_fn=lambda:input_fn(train_folder, labels, shuffle=True, batch_size=BATCH_SIZE, buffer_size=2048, num_epochs=EPOCHS, prefetch_buffer_size=4), hooks=[time_hist]) # 模型評估: estimator.evaluate(input_fn=lambda:input_fn(test_folder, labels, shuffle=False, batch_size=BATCH_SIZE, buffer_size=1024, num_epochs=1))
如圖所示,VGG16有13個卷積層和3個全鏈接層。VGG16輸入爲[224,224,3],卷積核大小爲(3,3),池化大小爲(2,2)步長爲2。各層的詳細參數能夠查看VGG ILSVRC 16 layers由於圖片較大,這裏只給出部分截圖,詳情請點擊連接查看。
VGG16模型結構規整,簡單,經過幾個小卷積核(3,3)卷積層組合比大卷積核如(7,7)更好,由於多個(3,3)卷積比一個大的卷積擁有更多的非線性,更少的參數。此外,驗證了不斷加深的網絡結構能夠提高性能(卷積+卷積+卷積+池化,代替卷積+池化,這樣減小W的同時有能夠擬合更復雜的數據),不過VGG16參數量不少,佔用內存較大。
經過遷移學習咱們可使用較少的數據訓練出來一個相對不錯的模型,Estimator簡化了機器學習編程特別是在分佈式環境下。對於輸入數據較多的狀況咱們要從Extract,Transform,Load三方面考慮進行優化處理。固然,除了VGG16咱們還有不少選擇,如:Inception Model,Resnet Model。
代碼實現部分參考Kashif Rasul,在此表示感謝。