遷移學習, 用現成網路,跑自己資料: 保留已有網路除輸出層以外其它層的權重, 改變已有網路的輸出層的輸出class 個數. 以已有網路權值為基礎, 訓練自己的網路,
以keras 2.1.5 / VGG16Net為例. 匯入必要的庫
from keras.preprocessing.image import ImageDataGeneratorfrom keras import optimizersfrom keras.models import Sequentialfrom keras.layers import Dropout, Flatten, Densefrom keras import Modelfrom keras import initializersfrom keras.callbacks import ModelCheckpoint, EarlyStoppingfrom keras.applications.vgg16 import VGG16
設定輸入圖片augmentation方法
這裡對於train/test data只設定rescale, 即圖片矩陣整體除以某個數值.
# prepare data augmentation configurationtrain_datagen = ImageDataGenerator( rescale=1. / 255,# shear_range=0.2,# zoom_range=0.2,# horizontal_flip=True )test_datagen = ImageDataGenerator(rescale=1. / 255)
設定圖片尺寸
因為是保留VGG16所有網路結構(除輸出層), 因此輸入必須和VGGnet要求的一致.
# the input is the same as original networkinput_shape = (224,224,3)
設定圖片路徑
設定圖片路徑, directory參數對應類別根目錄, directory下的各子目錄對應各個類別的圖片. 子目錄檔案夾名即為class name. 讀入的類別順序按照類別目錄的字母排序而定.
train_generator = train_datagen.flow_from_directory( directory = './data/train/', target_size = input_shape[:-1], color_mode = 'rgb', classes = None, class_mode = 'categorical', batch_size = 10, shuffle = True)test_generator = test_datagen.flow_from_directory( directory = './data/test/', target_size = input_shape[:-1], batch_size = 10, class_mode = 'categorical')
載入VGG16 網路
input_shape : 圖片輸入尺寸
include_top = True, 保留全串連層.
classes = 10: 類別數目(我們的類別數目)
weights = None: 不載入任何網路
# build the VGG16 network, 載入VGG16網路, 改變輸出層的類別數目.# include_top = True, load the whole network# set the new output classes to 10# weights = None, load no weights base_model = VGG16(input_shape = input_shape, include_top = True, classes = 10, weights = None )print('Model loaded.')
改變最後一層的名稱
base_model.layers[-1].name = 'pred'
查看最後一層的初始化方法
base_model.layers[-1].kernel_initializer.get_config()
將會得到:
{'distribution': 'uniform', 'mode': 'fan_avg', 'scale': 1.0, 'seed': None}
改變最後一層的權值初始化方法
base_model.layers[-1].kernel_initializer = initializers.glorot_normal()
載入VGG16 在imagenet訓練得到的權重, 一定要按照 by_name = True的方式
按照layer的名稱載入權重(名稱不對應的層級將不會載入權重), 這就是為什麼我們一定要改變最後一層的名稱了. 因為唯有如此,
這一步載入權重,將會載入除了最後一層的所有層的權重.
base_model.load_weights('./vgg16_weights_tf_dim_ordering_tf_kernels.h5', by_name = True)
compile 網路
# compile the model with a SGD/momentum optimizer# and a very slow learning rate.sgd = optimizers.SGD(lr=0.01, decay=1e-4, momentum=0.9, nesterov=True)base_model.compile(loss = 'categorical_crossentropy', optimizer = sgd, metrics=['accuracy'])
開始訓練
訓練過程中自動儲存權重,並按要求停止訓練.
# fine-tune the modelcheck = ModelCheckpoint('./', monitor='val_loss', verbose=0, save_best_only=False, save_weights_only=False, mode='auto', period=1)stop = EarlyStopping(monitor='val_loss', min_delta=0, patience=0, verbose=0, mode='auto')base_model.fit_generator( generator = train_generator, epochs = 5, verbose = 1, validation_data = test_generator, shuffle = True, callbacks = [check, stop] )
儲存網路
model.save_weights('fine_tuned_net.h5')