引自:http://blog.csdn.net/sinat_26917383/article/details/72982230
之前在博客《keras系列︱圖像多分類訓練與利用bottleneck features進行微調(三)》一直在倒騰VGG16的fine-tuning,然后因為其中的Flatten層一直沒有真的實現最后一個模塊的fine-tuning。
看到github上有一份InceptionV3的fine-tuning並且可以實現。我看到的keras微調的方式分為以下兩種:
fine-tuning方式一:使用預訓練網絡的bottleneck特征
fine-tuning方式二:要調整權重,並訓練
.
整個流程分為以下幾個步驟:
- 一、定義函數以及加載模塊
- 二、數據准備
- 三、 fine-tuning方式一:使用預訓練網絡的bottleneck特征
- 四、fine-tuning方式二:要調整權重,並訓練
- 五、畫圖函數
- 六、預測
.
Keras系列:
1、keras系列︱Sequential與Model模型、keras基本結構功能(一)
2、keras系列︱Application中五款已訓練模型、VGG16框架(Sequential式、Model式)解讀(二)
3、keras系列︱圖像多分類訓練與利用bottleneck features進行微調(三)
4、keras系列︱人臉表情分類與識別:opencv人臉檢測+Keras情緒分類(四)
5、keras系列︱遷移學習:利用InceptionV3進行fine-tuning及預測、完整案例(五)
一、定義函數以及加載模塊
其中的get_nb_files函數為得到文件數量,個數。
其中,from keras.applications.inception_v3_matt import InceptionV3中,我有自己改,不然就會每次都從網上下載。
import os import sys import glob import argparse import matplotlib.pyplot as plt from keras import __version__ # from keras.applications.inception_v3 import InceptionV3, preprocess_input from keras.applications.inception_v3_matt import InceptionV3, preprocess_input from keras.models import Model from keras.layers import Dense, GlobalAveragePooling2D from keras.preprocessing.image import ImageDataGenerator from keras.optimizers import SGD def get_nb_files(directory): """Get number of files by searching directory recursively""" if not os.path.exists(directory): return 0 cnt = 0 for r, dirs, files in os.walk(directory): for dr in dirs: cnt += len(glob.glob(os.path.join(r, dr + "/*"))) return cnt # get_nb_files('/home/ubuntu/keras/animal5/train')
.
二、數據准備
數據放在不同的文件夾下即可,很方便。本文實踐的數據是我上次博客的數據《keras系列︱圖像多分類訓練與利用bottleneck features進行微調(三)》的第二節。
同時原作者在.flow_from_directory函數中,好像漏寫了class_mode=’categorical’,一開始導致最后訓練的時候,val_acc一直在0.2徘徊。
# 數據准備 IM_WIDTH, IM_HEIGHT = 299, 299 #InceptionV3指定的圖片尺寸 FC_SIZE = 1024 # 全連接層的節點個數 NB_IV3_LAYERS_TO_FREEZE = 172 # 凍結層的數量 train_dir = '/home/ubuntu/keras/animal5/train' # 訓練集數據 val_dir = '/home/ubuntu/keras/animal5/validation' # 驗證集數據 nb_classes= 5 nb_epoch = 3 batch_size = 16 nb_train_samples = get_nb_files(train_dir) # 訓練樣本個數 nb_classes = len(glob.glob(train_dir + "/*")) # 分類數 nb_val_samples = get_nb_files(val_dir) #驗證集樣本個數 nb_epoch = int(nb_epoch) # epoch數量 batch_size = int(batch_size) # 圖片生成器 train_datagen = ImageDataGenerator( preprocessing_function=preprocess_input, rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True ) test_datagen = ImageDataGenerator( preprocessing_function=preprocess_input, rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True ) # 訓練數據與測試數據 train_generator = train_datagen.flow_from_directory( train_dir, target_size=(IM_WIDTH, IM_HEIGHT), batch_size=batch_size,class_mode='categorical') validation_generator = test_datagen.flow_from_directory( val_dir, target_size=(IM_WIDTH, IM_HEIGHT), batch_size=batch_size,class_mode='categorical')
.
三、 fine-tuning方式一:使用預訓練網絡的bottleneck特征
# 添加新層 def add_new_last_layer(base_model, nb_classes): """ 添加最后的層 輸入 base_model和分類數量 輸出 新的keras的model """ x = base_model.output x = GlobalAveragePooling2D()(x) x = Dense(FC_SIZE, activation='relu')(x) #new FC layer, random init predictions = Dense(nb_classes, activation='softmax')(x) #new softmax layer model = Model(input=base_model.input, output=predictions) return model # 凍上base_model所有層,這樣就可以正確獲得bottleneck特征 def setup_to_transfer_learn(model, base_model): """Freeze all layers and compile the model""" for layer in base_model.layers: layer.trainable = False model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy']) # 定義網絡框架 base_model = InceptionV3(weights='imagenet', include_top=False) # 預先要下載no_top模型 model = add_new_last_layer(base_model, nb_classes) # 從基本no_top模型上添加新層 setup_to_transfer_learn(model, base_model) # 凍結base_model所有層 # 模式一訓練 history_tl = model.fit_generator( train_generator, nb_epoch=nb_epoch, samples_per_epoch=nb_train_samples, validation_data=validation_generator, nb_val_samples=nb_val_samples, class_weight='auto')
訓練時候報錯:
main:15: UserWarning: Update your Model
call to the Keras 2 API: Model(outputs=Tensor("de..., inputs=Tensor("in...)
沒有關系。
有過這樣的報錯 :
Epoch 1/3
25/25 [==============================] - 37s - loss: 12.6398 - acc: 0.1900 - val_loss: 12.8990 - val_acc: 0.1997
Epoch 2/3
25/25 [==============================] - 34s - loss: 12.8945 - acc: 0.2000 - val_loss: 12.8718 - val_acc: 0.2014
Epoch 3/3
25/25 [==============================] - 34s - loss: 12.8945 - acc: 0.2000 - val_loss: 12.8877 - val_acc: 0.2004
一般是我在設置錯了分類時候會出現的情況
出錯在:之前少加了class_mode
train_generator = train_datagen.flow_from_directory(
train_dir,
target_size=(IM_WIDTH, IM_HEIGHT),
batch_size=batch_size,class_mode=’categorical’)
.
四、fine-tuning方式二:要調整權重,並訓練
那么fine-tuning模式一與模式二區別在於,模式一凍結了base_model所有層,只有新添加的層才能訓練,調整權重。但是模式二,在base_model內開放了一部分。
類似的可以看到官方VGG16的兩種模式的區別:
第一張圖就是模式一,凍結了base_model層;第二張圖就是模式二,base_model一些層都開放了。
# 凍上NB_IV3_LAYERS之前的層 def setup_to_finetune(model): """Freeze the bottom NB_IV3_LAYERS and retrain the remaining top layers. note: NB_IV3_LAYERS corresponds to the top 2 inception blocks in the inceptionv3 arch Args: model: keras model """ for layer in model.layers[:NB_IV3_LAYERS_TO_FREEZE]: layer.trainable = False for layer in model.layers[NB_IV3_LAYERS_TO_FREEZE:]: layer.trainable = True model.compile(optimizer=SGD(lr=0.0001, momentum=0.9), loss='categorical_crossentropy', metrics=['accuracy']) # 設置網絡結構 setup_to_finetune(model) # 模式二訓練 history_ft = model.fit_generator( train_generator, samples_per_epoch=nb_train_samples, nb_epoch=nb_epoch, validation_data=validation_generator, nb_val_samples=nb_val_samples, class_weight='auto') # 模型保存 model.save(args.output_model_file)
# 正確 Epoch 1/3 25/25 [==============================] - 38s - loss: 0.0589 - acc: 0.9850 - val_loss: 0.0084 - val_acc: 0.9986 Epoch 2/3 25/25 [==============================] - 37s - loss: 0.0196 - acc: 0.9925 - val_loss: 0.0150 - val_acc: 0.9965 Epoch 3/3 25/25 [==============================] - 37s - loss: 0.0349 - acc: 0.9875 - val_loss: 0.0101 - val_acc: 0.9979
.
五、畫圖函數
# 畫圖 def plot_training(history): acc = history.history['acc'] val_acc = history.history['val_acc'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(len(acc)) plt.plot(epochs, acc, 'r.') plt.plot(epochs, val_acc, 'r') plt.title('Training and validation accuracy') plt.figure() plt.plot(epochs, loss, 'r.') plt.plot(epochs, val_loss, 'r-') plt.title('Training and validation loss') plt.show() # 訓練的acc_loss圖 plot_training(history_ft)
.
六、預測
# 定義層 import sys import argparse import numpy as np from PIL import Image import requests from io import BytesIO import matplotlib.pyplot as plt from keras.preprocessing import image from keras.models import load_model from keras.applications.inception_v3 import preprocess_input # 狂階圖片指定尺寸 target_size = (229, 229) #fixed size for InceptionV3 architecture # 預測函數 # 輸入:model,圖片,目標尺寸 # 輸出:預測predict def predict(model, img, target_size): """Run model prediction on image Args: model: keras model img: PIL format image target_size: (w,h) tuple Returns: list of predicted labels and their probabilities """ if img.size != target_size: img = img.resize(target_size) x = image.img_to_array(img) x = np.expand_dims(x, axis=0) x = preprocess_input(x) preds = model.predict(x) return preds[0] # 畫圖函數 # 預測之后畫圖,這里默認是貓狗,當然可以修改label labels = ("cat", "dog") def plot_preds(image, preds,labels): """Displays image and the top-n predicted probabilities in a bar graph Args: image: PIL image preds: list of predicted labels and their probabilities """ plt.imshow(image) plt.axis('off') plt.figure() plt.barh([0, 1], preds, alpha=0.5) plt.yticks([0, 1], labels) plt.xlabel('Probability') plt.xlim(0,1.01) plt.tight_layout() plt.show() # 載入模型 model = load_model(args.model) # 本地圖片 img = Image.open(image) preds = predict(model, img, target_size) plot_preds(img, preds) # 圖片URL response = requests.get(image_url) img = Image.open(BytesIO(response.content)) preds = predict(model, img, target_size) plot_preds(img, preds)
延伸一:fine-tuning時如何加載No_top的權重
場景:你要用自己的訓練完的模型,作為下一個模型初始化的權重,譬如inceptionv3中的no_top版本。
如果你需要加載權重到不同的網絡結構(有些層一樣)中,例如fine-tune或transfer-learning,你可以通過層名字來加載模型:
model.load_weights(‘my_model_weights.h5’, by_name=True)
例如:
假如原模型為:
model = Sequential()
model.add(Dense(2, input_dim=3, name="dense_1")) model.add(Dense(3, name="dense_2")) ... model.save_weights(fname)
# new model model = Sequential() model.add(Dense(2, input_dim=3, name="dense_1")) # will be loaded model.add(Dense(10, name="new_dense")) # will not be loaded # load weights from first model; will only affect the first layer, dense_1. model.load_weights(fname, by_name=True)