Keras的預訓練模型地址:https://github.com/fchollet/deep-learning-models/releases
一個稍微講究一點的辦法是,利用在大規模數據集上預訓練好的網絡。這樣的網絡在多數的計算機視覺問題上都能取得不錯的特征,利用這樣的特征可以讓我們獲得更高的准確率。
1,使用預訓練網絡的 bottleneck 特征:一分鍾達到90%的正確率
我們將使用VGG-16網絡,該網絡在 ImageNet數據集上進行訓練,這個模型我們之前提到過了。因為 ImageNet 數據集包含多種“貓”類和多種“狗”類,這個模型已經能夠學習與我們這個數據集相關的特征了。事實上,簡單的記錄原來網絡的輸出而不用 bottleneck特征就已經足夠把我們的問題解決的不錯了。不過我們這里講的方法對其他的類似問題有更好的推廣性,包括在ImageNet中沒有出現類別的分類問題。
所以我們這里的分類,不做貓狗二分類,我做的和上一節一樣的數據集,就是五分類的問題,依然對照Keras的中文文檔,借鑒貓狗二分類,做我們的五分類。
VGG的博文介紹可以參考我之前的博客:
tensorflow學習筆記——VGGNet
VGG-16 的網絡結構如下:
我們的方法是這樣的,我們將利用網絡的卷積層部分,把全連接以下的部分拋掉,然后在我們的訓練集和測試集上跑一遍,將得到的輸出(即“bottleneck feature”,網絡在全連接之前的最后一層激活的 feature map)記錄在兩個 numpy array 里面。然后我們基於記錄下來的特征訓練一個全連接層。
我們將這些特征保存為離線形式,而不是將我們的全連接模型直接加到網絡上並凍結之前的層參數進行訓練的原因是處於計算效率的考慮。運行VGG網絡的代價是非常高昂的,尤其是在CPU上運行,所以我們只想運行一次,這也是我們不進行數據提升的原因。
我們將不再贅述如何搭建 VGG-16 網絡了。這個在Keras的example里也可以找到,這個博文里面有Keras的關於VGG的examples:
深入學習Keras中Sequential模型及方法
下面讓我們看看如何記錄 bottleneck特征。
generator = datagen.flow_from_directory( 'data/train', target_size=(150, 150), batch_size=32, class_mode=None, shuffle=False ) # the predict_generator method returns the output of a model # given a generator that yields batches of numpy data bottleneck_feature_train = model.predict_generator(generator, 2000) # save the output as a Numpy array np.save(open('bottleneck_features_train.npy', 'w'), bottleneck_feature_train) generator = datagen.flow_from_directory( 'data/validation', target_size=(150, 150), batch_size=32, class_mode=None, shuffle=False ) bottleneck_feature_validaion = model.predict_generator(generator, 2000) # save the output as a Numpy array np.save(open('bottleneck_features_validation.npy', 'w'), bottleneck_feature_validaion)
記錄完畢后我們可以將數據載入,用於訓練我們的全連接網絡:
train_data = np.load(open('bottleneck_features_train.npy')) # the features were saved in order, so recreating the labels is easy train_labels = np.array([0] * 1000 + [1] * 1000) validation_data = np.load(open('bottleneck_features_validation.npy')) validation_labels = np.array([0] * 400 + [1] * 400) model = Sequential() model.add(Flatten(input_shape=train_data.shape[1:])) model.add(Dense(256, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(1, activation='sigmoid')) model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy']) model.fit(train_data, train_labels, nb_epoch=50, batch_size=32, validation_data=(validation_data, validation_labels)) model.sample_weights('bottleneck_fc_model.h5')
因為特征的size很小,模型在CPU上跑的也會很快,大概1s一個epoch,最后我們的准確率時90%~91%,這么好的結果多半歸功於預訓練的VGG網絡幫助我們提取特征。
Keras中文文檔好像有漏洞,導致內容不連貫,但是大概的代碼是沒有問題的,可是還是會報錯,所以我這里整理一下自己的筆記。
本節主要是通過已經訓練好的模型,把bottleneck特征抽取出來,然后滾到下一個“小”模型里面,也就是全連接層。
實施步驟:
- 1,把訓練好的模型的權重拿來,model;
- 2,運行,提取 bottleneck feature(網絡在全連接之前的最后一層激活的feature map 卷積——全連接層之間),單獨拿出來,並保存;
- 3,bottleneck層數據,之后 + dense 全連接層,進行 fine-tuning;
步驟1:導入預訓練權重與網絡框架
原作者博客地址:https://blog.keras.io/how-convolutional-neural-networks-see-the-world.html
WEIGHTS_PATH = '/keras/animal5/vgg16_weights_tf_dim_ordering_tf_kernels.h5' WEIGHTS_PATH_NO_TOP = '/keras/animal5/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5' from keras.applications.vgg16_matt import VGG16 model = VGG16(include_top=False, weights='imagenet')
其中 WEIGHTS_PATH_NO_TOP 就是去掉了全連接層,可以用它直接提取 bottleneck的特征。
注意上面的地址,是我導入的VGG16的官網h5模型,我將源碼修改了,把VGG16的官方h5下載下來運行的話,比較快。
步驟2:提取圖片的bottleneck特征
需要的步驟:載入圖片;灌入 pre_model的權重;得到 bottleneck feature
# 提取圖片中的 bottleneck 特征 ''' 步驟:1,載入圖片 2,灌入 pre_model 的權重 3,得到 bottleneck feature ''' from keras.models import Sequential from keras.layers import Conv2D, MaxPooling2D from keras.layers import Activation, Dropout, Flatten, Dense # 載入圖片 圖片生成器初始化 from keras.preprocessing.image import ImageDataGenerator import numpy as np from keras.applications.vgg16 import VGG16 model = VGG16(include_top=False, weights='imagenet', input_shape=(150, 150, 3)) print('load model ok') datagen = ImageDataGenerator(rescale=1./255) # 訓練集圖像生成器 train_generator = datagen.flow_from_directory( 'data/mytrain', target_size=(150, 150), batch_size=32, class_mode=None, shuffle=False ) # 驗證集圖像生成器 test_generator = datagen.flow_from_directory( 'data/mytest', target_size=(150, 150), batch_size=32, class_mode=None, shuffle=False ) print('increase image ok') # 灌入 pre_model 的權重 WEIGHTS_PATH = '' model.load_weights('data/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5') print('load pre model OK ') # 得到 bottleneck feature bottleneck_features_train = model.predict_generator(train_generator, 500) # 核心,steps是生成器要返回數據的輪數,每個epoch含有500張圖片,與model.fit(samples_per_epoch)相對 # 如果這樣的話,總共有 500*32個訓練樣本 np.save(open('bottleneck_features_train.npy', 'w'), bottleneck_features_train) bottleneck_features_validation = model.predict_generator(test_generator, 100) # 與model.fit(nb_val_samples)相對,一個epoch有800張圖片,驗證集 np.save(open('bottleneck_features_validation.npy', 'w'), bottleneck_features_validation)
注意1:
- class_mode: 此時為預測場景,制作數據階段,不用設置標簽,因為此時是按照順序產生;而在 train_generator 數據訓練之前的數據准備,則需要設置標簽。
- shuffle:此時為預測場景,制作數據集,不用打亂;但是在 model.fit 過程中需要打亂,表示是否在訓練過程中每個 epoch 前隨機打亂輸入樣本的順序。
注意2:上面會報錯:TypeError: write() argument must be str, not bytes
解決方法:
將: np.save(open('test.npy', 'w'), block4_pool_features) 修改為: np.save('test.npy', block4_pool_features)
注意3:在window上跑,會占滿內存,根本跑不動,在linux上也會報錯,比如臨時內存不足等。
解決方法(同時修改batch_size,將其變小):
import tensorflow as tf import os from keras.backend.tensorflow_backend import set_session os.environ["CUDA_VISIBLE_DEVICES"] = "1,2" config = tf.ConfigProto() config.gpu_options.per_process_gpu_memory_fraction = 0.3 set_session(tf.Session(config=config))
當batch_size 設置為32,會報錯:
還是內存問題,即使我設置三個GPU都沒用,但是設為16就可以了。真的是。。。。。
注意4:報錯:RuntimeError: can't start new thread
之前都沒問題,今天就出問題了,記下來,真惡心。
注意5:GPU服務器筆記 報錯:tensorflow.python.framework.errors_impl.InternalError: Dst tensor is not initialized.
這個網上說是GPU的內存不夠了,換一個空閑的GPU就可以跑了。
注意6:terminate called after throwing an instance of 'std::system_error' what(): Resource temporarily unavailable
注意7:Segmentation fault (core dumped)問題
解釋一下,注意4,5,6,7 是同一類錯誤,就是我的代碼本身是沒有任何問題的,只是我家的服務器出了問題,這個通常是指針錯誤引起的,而且這個錯誤會生成相應的 core file。
發生了core dump 之后,我們可以用 gdb 進行查看 core 文件的內容,以定位文件中引發 core dump的行:
gdb [exec file] [core file]
如:gdb ./test test.core 在進入gdb后, 用bt命令查看backtrace以檢查發生程序運行到哪里,來定位core dump的文件->行。
那么什么是core?
在使用半導體作為內存的材料錢,人類是利用線圈當做內存的材料(發明者為王安),線圈就叫做 core,用線圈做的內存就叫做 core memory。如今,半導體工業蓬勃發展,已經沒有人用 core memory 了,不過,在許多情況下,人們還是把記憶體叫做 core。
那么什么是 Core dump:
我們在開發(或使用)一個程序時,最怕的就是程序莫名其妙的dang掉,雖然系統沒事,但我們下次仍可能遇到相同的問題,於是這時操作系統就會把程序dang掉時的內存內容 dump出來(現在通常是寫在一個叫 core 的 file 里面),讓我們或是 debugger作為參考,這個動作就叫做 core dump。
那么core dump時會生成何種文件?
core dump 時,會生成諸如 core. 進程號的文件
利用上面,查看問題出在哪里,發現有人將服務器上的TensorFlow鎖住了,解鎖就好了,又或者是方便的話可以直接重啟服務器(可能大家都不會遇到后面幾個問題。。請略過)。
完整代碼:
#_*_coding:utf-8_*_ from keras.models import Sequential from keras.layers import Conv2D, MaxPooling2D from keras.layers import Activation, Dropout, Flatten, Dense from keras.preprocessing.image import ImageDataGenerator import numpy as np from keras.applications.vgg16 import VGG16 import os from keras.backend.tensorflow_backend import set_session import tensorflow as tf os.environ['CUDA_VISIBLE_DEVICES'] = '1,2,3' config = tf.ConfigProto() config.gpu_options.per_process_gpu_memory_fraction = 0.4 #config.gpu_options.allow_growth = True #sess = tf.Session(config=config) set_session(tf.Session(config=config)) model = VGG16(include_top=False, weights='imagenet', input_shape=(150, 150, 3)) print('load model ok') datagen = ImageDataGenerator(rescale=1./255) train_generator = datagen.flow_from_directory( '/data/lebron/data/mytrain', target_size=(150, 150), batch_size=4, class_mode=None, shuffle=False ) test_generator = datagen.flow_from_directory( '/data/lebron/data/mytest', target_size=(150, 150), batch_size=4, class_mode=None, shuffle=False ) print('increase image ok') model.load_weights('/data/lebron/data/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5') print('load pre model OK ') bottleneck_features_train = model.predict_generator(train_generator, 125) np.save('/data/lebron/bottleneck_features_train.npy', bottleneck_features_train) bottleneck_features_validation = model.predict_generator(test_generator, 25) np.save('/data/lebron/bottleneck_features_validation.npy', bottleneck_features_validation) print('game over')
注意上面的修改細節,后面會繼續學習,並展開敘述。
步驟3:fine-tuning “小” 網絡
主要步驟:
- 1,導入 bottleneck features 數據
- 2,設置標簽,並規范成 Keras默認格式
- 3,寫“小網絡” 的網絡結構
- 4,設置參數並訓練
# (1)導入bottleneck_features數據 train_data = np.load(open('bottleneck_features_train.npy')) # the features were saved in order, so recreating the labels is easy train_labels = np.array([0] * 100 + [1] * 100 + [2] * 100 + [3] * 100 + [4] * 96) # matt,打標簽 validation_data = np.load(open('bottleneck_features_validation.npy')) validation_labels = np.array([0] * 20 + [1] * 20 + [2] * 20 + [3] * 20 + [4] * 16) # matt,打標簽 # (2)設置標簽,並規范成Keras默認格式 train_labels = keras.utils.to_categorical(train_labels, 5) validation_labels = keras.utils.to_categorical(validation_labels, 5) # (3)寫“小網絡”的網絡結構 model = Sequential() #train_data.shape[1:] model.add(Flatten(input_shape=(4,4,512)))# 4*4*512 model.add(Dense(256, activation='relu')) model.add(Dropout(0.5)) #model.add(Dense(1, activation='sigmoid')) # 二分類 model.add(Dense(5, activation='softmax')) # matt,多分類 #model.add(Dense(1)) #model.add(Dense(5)) #model.add(Activation('softmax')) # (4)設置參數並訓練 model.compile(loss='categorical_crossentropy', # matt,多分類,不是binary_crossentropy optimizer='rmsprop', metrics=['accuracy']) model.fit(train_data, train_labels, nb_epoch=50, batch_size=16, validation_data=(validation_data, validation_labels)) model.save_weights('bottleneck_fc_model.h5')
結果如下:
Epoch 50/50 16/500 [..............................] - ETA: 0s - loss: 0.0704 - acc: 1.0000 48/500 [=>............................] - ETA: 0s - loss: 0.0248 - acc: 1.0000 80/500 [===>..........................] - ETA: 0s - loss: 0.0472 - acc: 0.9750 112/500 [=====>........................] - ETA: 0s - loss: 0.0397 - acc: 0.9821 144/500 [=======>......................] - ETA: 0s - loss: 0.0537 - acc: 0.9722 176/500 [=========>....................] - ETA: 0s - loss: 0.0567 - acc: 0.9716 208/500 [===========>..................] - ETA: 0s - loss: 0.0551 - acc: 0.9712 240/500 [=============>................] - ETA: 0s - loss: 0.0707 - acc: 0.9667 272/500 [===============>..............] - ETA: 0s - loss: 0.0676 - acc: 0.9706 304/500 [=================>............] - ETA: 0s - loss: 0.0620 - acc: 0.9737 336/500 [===================>..........] - ETA: 0s - loss: 0.0579 - acc: 0.9762 368/500 [=====================>........] - ETA: 0s - loss: 0.0574 - acc: 0.9783 400/500 [=======================>......] - ETA: 0s - loss: 0.0596 - acc: 0.9750 432/500 [========================>.....] - ETA: 0s - loss: 0.0595 - acc: 0.9745 464/500 [==========================>...] - ETA: 0s - loss: 0.0574 - acc: 0.9763 496/500 [============================>.] - ETA: 0s - loss: 0.0546 - acc: 0.9778 500/500 [==============================] - 1s 2ms/step - loss: 0.0563 - acc: 0.9760 - val_loss: 8.0778 - val_acc: 0.2700
准確率還是挺高的。
完整代碼:
# 提取圖片中的 bottleneck 特征 ''' 步驟:1,載入圖片 2,灌入 pre_model 的權重 3,得到 bottleneck feature ''' from keras.models import Sequential from keras.layers import Conv2D, MaxPooling2D from keras.layers import Activation, Dropout, Flatten, Dense # 載入圖片 圖片生成器初始化 from keras.preprocessing.image import ImageDataGenerator import numpy as np from keras.applications.vgg16 import VGG16 import keras def save_bottleneck_features(): model = VGG16(include_top=False, weights='imagenet', input_shape=(150, 150, 3)) print('load model ok') datagen = ImageDataGenerator(rescale=1./255) # 訓練集圖像生成器 train_generator = datagen.flow_from_directory( 'data/mytrain', target_size=(150, 150), batch_size=16, class_mode=None, shuffle=False ) # 驗證集圖像生成器 test_generator = datagen.flow_from_directory( 'data/mytest', target_size=(150, 150), batch_size=16, class_mode=None, shuffle=False ) print('increase image ok') # 灌入 pre_model 的權重 WEIGHTS_PATH = '' model.load_weights('data/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5') print('load pre model OK ') # 得到 bottleneck feature bottleneck_features_train = model.predict_generator(train_generator, 500) # 核心,steps是生成器要返回數據的輪數,每個epoch含有500張圖片,與model.fit(samples_per_epoch)相對 # 如果這樣的話,總共有 500*32個訓練樣本 np.save('bottleneck_features_train.npy', 'w', bottleneck_features_train) bottleneck_features_validation = model.predict_generator(test_generator, 100) # 與model.fit(nb_val_samples)相對,一個epoch有800張圖片,驗證集 np.save('bottleneck_features_validation.npy', 'w', bottleneck_features_validation) def train_fine_tune(): trainfile = 'data/model/train.npy' testfile = 'data/model/validation.npy' # (1) 導入 bottleneck features數據 train_data = np.load(trainfile) print(train_data.shape) # (8000, 4, 4, 512) # train_data = train_data.reshape(train_data.shape[0], 150, 150, 3) # the features were saved in order, so recreating the labels is easy train_labels = np.array( [0]*100 + [1]*100 + [2]*100 + [3]*100 + [4]*100 ) validation_data = np.load(testfile) print(validation_data.shape) # (1432, 4, 4, 512) validation_labels = np.array( [0]*20 + [1]*20 + [2]*20 + [3]*20 + [4]*20 ) # (2) 設置標簽,並規范成Keras默認格式 train_labels = keras.utils.to_categorical(train_labels, 5) validation_labels = keras.utils.to_categorical(validation_labels, 5) print(train_labels.shape, validation_labels.shape) # (8000, 5) (1432, 5) # (3) 寫“小網絡”的網絡結構 model = Sequential() # train_data.shape[1:] model.add(Flatten(input_shape=(4, 4, 512))) #4*4*512 model.add(Dense(256, activation='relu')) model.add(Dropout(0.5)) # model.add(Dense(1, activation='sigmoid')) # 二分類 model.add(Dense(5, activation='softmax')) # 多分類 # (4) 設置參數並訓練 model.compile(loss='categorical_crossentropy', # 兩分類是 binary_crossentropy optimizer='rmsprop', metrics=['accuracy']) model.fit(train_data, train_labels, nb_epoch=50, batch_size=16, validation_data=(validation_data, validation_labels)) model.save_weights('bottleneck_fc_model.h5') if __name__ == '__main__': # save_bottleneck_features() train_fine_tune() # print('over')
注意:這里有個大問題,就是會報錯:
為什么呢?我們的npy文件的尺寸,我們可以查看一下:
trainfile = 'data/model/bottleneck_features_train.npy' testfile = 'data/model/bottleneck_features_validation.npy' # (1) 導入 bottleneck features數據 train_data = np.load(trainfile) print(train_data.shape) validation_data = np.load(testfile) print(validation_data.shape) (8000, 4, 4, 512) (1432, 4, 4, 512)
根本就不是 500 100
修改方法:
# 得到 bottleneck feature bottleneck_features_train = model.predict_generator(train_generator, 500//batch_size) bottleneck_features_validation = model.predict_generator(test_generator, 100//batch_size)
具體原因,我還不知道為什么。(具體原因找到了,往后看)
再百度,找到一篇文章:
【詳解】keras + predict_generator + ImageDataGenerator.flow_from_ditectory 進行預測
其文章的大概意思呢,和我遇到的問題差不多,就是說在利用Keras進行預測分類,也就是運行下面的代碼:
bottleneck_features_train = model.predict_generator(train_generator, 500)
的時候,我們從文件夾直接讀取圖片(直接利用flow_from_directory ),並經過ImageDataGenerator進行增強。他遇到的坑也是 flow_from_directory 的batch_size的問題,如果這個batch_size設置不對,那么預測的結果與圖片不對應。
他的文章好像意思是:如果不知道batch_size 的設置,就請設置為1,如果設置為1,雖然可能會變慢,但是保證不會出錯,而他設置的1,是這樣的:
test_datagen = ImageDataGenerator(rescale=1./255) test_generator = test_datagen.flow_from_directory('/predict', target_size=(height, width), batch_size=1,class_mode='categorical', shuffle=False,)
然后生成器是這樣的:
val_generator.reset() pred = model.predict_generator(val_generator, verbose=1)
綜合起來,代碼是這樣的:
導入你的模型 導入你的參數 train_datagen = ImageDataGenerator(rescale=1./255, rotation_range=40, fill_mode='wrap') train_generator = train_datagen.flow_from_directory('new_images', target_size=(height, width), batch_size=96) val_datagen = ImageDataGenerator(rescale=1./255) test_generator = test_datagen.flow_from_directory('AgriculturalDisease_validationset/predict', target_size=(height, width),batch_size=1,class_mode='categorical', shuffle=False,) test_generator.reset() pred = model.predict_generator(test_generator, verbose=1) predicted_class_indices = np.argmax(pred, axis=1) labels = (train_generator.class_indices) label = dict((v,k) for k,v in labels.items()) # 建立代碼標簽與真實標簽的關系 predictions = [label[i] for i in predicted_class_indices] #建立預測結果和文件名之間的關系 filenames = test_generator.filenames for idx in range(len(filenames )): print('predict %d' % (int(predictions[idx]))) print('title %s' % filenames[idx]) print('')
也就是說,他在model.predict_generator(test_generator, verbose=1) 中,什么都沒有設置,我們嘗試一下。將預測代碼改為下面:
bottleneck_features_train = model.predict_generator(train_generator, verbose=1) bottleneck_features_validation = model.predict_generator(test_generator, verbose=1)
會報錯,我們將 train改回來,發現,也會報錯:
對於這個predict_generator函數,百度不了幾個,我真的是難受,痛定思痛,我覺得還是查看源碼比較好。
def predict_generator(self, generator, steps=None, max_queue_size=10, workers=1, use_multiprocessing=False, verbose=0): """Generates predictions for the input samples from a data generator. The generator should return the same kind of data as accepted by `predict_on_batch`. # Arguments generator: Generator yielding batches of input samples or an instance of Sequence (keras.utils.Sequence) object in order to avoid duplicate data when using multiprocessing. steps: Total number of steps (batches of samples) to yield from `generator` before stopping. Optional for `Sequence`: if unspecified, will use the `len(generator)` as a number of steps. max_queue_size: Maximum size for the generator queue. workers: Integer. Maximum number of processes to spin up when using process based threading. If unspecified, `workers` will default to 1. If 0, will execute the generator on the main thread. use_multiprocessing: If `True`, use process based threading. Note that because this implementation relies on multiprocessing, you should not pass non picklable arguments to the generator as they can't be passed easily to children processes. verbose: verbosity mode, 0 or 1. # Returns Numpy array(s) of predictions. # Raises ValueError: In case the generator yields data in an invalid format. """ return training_generator.predict_generator( self, generator, steps=steps, max_queue_size=max_queue_size, workers=workers, use_multiprocessing=use_multiprocessing, verbose=verbose)
此函數的意思是從數據生成器為輸入樣本生成預測。這個生成器應該返回與predict_on_batch 所接受的數據相同的數據類型,注意這個 predict_on_batch 就很有意思了,從我實踐中來看,這個有意思的說法,就是 說返回值在你輸入的predict_number 的基礎上乘以 batch_size,這樣才合理。
終於找到原因了,真的是開心哈,然后修改對應的 batch_size和predict_generator的參數,然后運行代碼,執行成功。
繼續找的規律,但是沒有找到。
2,在預訓練的網絡上 fine-tune
為了進一步提高之前的結果,我們可以試着fine-tune網絡的后面幾層,Fine-tune以一個預訓練好的網絡為基礎,在新的數據集上重新訓練一小部分權重。
在這個實驗中,fine-tune 分為三個步驟:
- 1,搭建VGG-16並載入權重
- 2,將之前定義的全連接網絡加在模型的頂部,並載入權重
- 3,凍結VGG-16網絡的一部分參數
- 4,模型訓練
注意:
- 為了進行 fine-tune,所有的層都應該以訓練好的權重為初始值,例如,你不能將隨機初始的全連接放在預訓練的卷積層之上,這是因為由隨機權重產生的大梯度將會破壞卷積層預訓練的權重。在我們的情形中,這就是為什么我們首先訓練頂層分類器,然后再基於它進行 fine-tune 的原因
- 我們選擇只fine-tune 最后的卷積塊,而不是整個網絡,這是為了防止過擬合。整個網絡具有巨大的熵容量,因此具有很高的過擬合傾向。由底層卷積模塊學習到的特征更加一般,更加不具有抽象性,因此我們要保持前兩個卷積塊(學習一般特征)不動,只 fine-tune 后面的卷積塊(學習特別的特征)
- fine-tune 應該在很低的學習率下進行,通常使用 SGD 優化而不是其他自適應學習率的優化算法,比如 RMSProp,這是為了保證更新的幅度保持在較低的程度,以免毀壞預訓練的特征。
2.1 下面均是Keras文檔的內容
代碼如下,首先在初始化好的VGG網絡上添加我們預訓練好的模型。
from keras.layers import Conv2D, Dense, Dropout, Flatten from keras.models import Sequential # build a classifier model to put on top of the convolution model top_model = Sequential() top_model.add(Flatten(input_shape=model.output_shape[1:])) top_model.add(Dense(256, activation='relu')) top_model.add(Dropout(0.5)) top_model.add(Dense(1, activation='sigmoid')) # note that it is neccessary to start with a fully-trained classifier # including the top classifier, in order to successfully to do fine-tuning top_model.load_weights(top_model_weights_path) # add the model on top of the convolutional base model.add(top_model)
然后將最后一個卷積塊前的卷積層參數凍結:
# 然后將最后一個卷積塊前的卷積層參數凍結 # set the first 25 layers (up to the last conv block) # to non-trainable (weights will not be updated) for layer in model.layers[: 25]: layer.trainable = False # compile the model with a SGD/momentum optimizer and a very slow learning rate model.compile(loss='binary_crossentropy', optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), metrics=['accuracy'])
然后以很低的學習率去訓練:
# 然后以很低的學習率學習 # prepare data augmentation configuration train_datagen = ImageDataGenerator(rescale=1. / 255) train_generator = train_datagen.flow_from_directory( validation_data_dir, target_size=(img_height, img_width), batch_size=32, class_mode='binary' ) validation_generator = test_datagen.flow_from_directory( validation_data_dir, target_size=(img_height, img_width), batch_size=32, class_mode='binary' ) # fine-tune the model model.fit_generator( train_generator, sample_per_epoch=nb_train_samples, nb_epochs=nb_epoch, validation_data=validation_generator, nb_val_samples=nb_validation_samples )
在五十個 peoch之后該方法的准確率達到 94% ,非常成功。
通過下面的方法你可以達到 95% 以上的正確率:
- 1,更加強烈的數據提升
- 2,更加強烈的dropout
- 3,使用L1和L2正則項(也成為權重衰減)
- 4,fine-tune更多的卷積塊(配合更大的正則)
2.2 我的修改
中文文檔是使用Sequential式那樣寫的,但是沒有找到對的權重:top_model_weights_path,如果不正確的權重文件會報錯:
ValueError: You are trying to load a weight file containing 16 layers into a model with 2 layers.
也就是說,看中文文檔,我們不知道model是什么,不知道 top_model_weights_path 是哪個,
當然看原作者的代碼,我們知道了這里的 model就是我們要微調的VGG model,而 top_model_weights_path 就是我們第一個訓練的 h5模型,也就是 bottleneck_fc_model.h5。
from keras.applications.vgg16 import VGG16 # 載入Model 權重 + 網絡 vgg_model = VGG16(weights='imagenet', include_top=False) # note that it is neccessary to start with a fully-trained classifier # including the top classifier, in order to successfully to do fine-tuning # top_model_weights_path 是上一個模型的權重 # 我們上個模型是將網絡的卷積層部分,把全連接以上的部分拋掉,然后在我們自己的訓練集 # 和測試集上跑一邊,將得到的輸出,記錄下來,然后基於輸出的特征,我們訓練一個全連接層 top_model_weights_path = 'bottleneck_fc_model.h5' top_model.load_weights(top_model_weights_path)
Keras的文檔中做的是貓狗兩分類,而我們這里做的是5分類,所以代碼還是需要改,將二分類改為多分類,
編譯模型的時候,如下:
生成數據的時候,還需要修改,代碼如下:
這樣編譯完,運行代碼,會報錯:
其中又遇到了Flatten() 層的問題,這是什么意思呢?網友的說法是,這一個層的意思是把 VGG16 網絡結構 + 權重的model數據輸出格式輸入給 Flatten() 進行降維,但是 model.output 輸出的格式是:(None,None,512),所以肯定會報錯。
注意,這里我們這樣做:
將 top_model.add(Flatten(input_shape=base_model.output_shape[1:])) 改為: vgg_model = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3))
這樣確保了,輸出的 print(vgg_model.output_shape[1:]) # (4, 4, 512)
然后會報錯如下:
這是什么意思呢? 就是說VGG16原來是Model式的,而我們使用 model.add() 是 Sequential式的,所以我們不這樣寫,修改報錯代碼如下:
# add the model on top of the convolutional base # vgg_model.add(top_model) # 上面代碼是需要將 身子 和 頭 組裝到一起,我們利用函數式組裝即可 vgg_model = Model(inputs=base_model.input, outputs=top_model(base_model.output))
完整的代碼如下:
from keras.preprocessing.image import ImageDataGenerator from keras import optimizers from keras.models import Sequential from keras.layers import Dropout, Flatten, Dense from keras.applications.vgg16 import VGG16 from keras.regularizers import l2 from keras.models import Model def build_model(): # 構建模型 # 載入Model 權重 + 網絡 base_model = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3)) # print(vgg_model.output_shape[1:]) # (4, 4, 512) # 網絡結構 # build a classifier model to put on top of the convolution model top_model = Sequential() top_model.add(Flatten(input_shape=base_model.output_shape[1:])) # top_model.add(Dense(256, activation='relu', kernel_regularizer=l2(0.001), )) top_model.add(Dense(256, activation='relu')) top_model.add(Dropout(0.5)) # top_model.add(Dense(1, activation='sigmoid')) top_model.add(Dense(5, activation='softmax')) # note that it is neccessary to start with a fully-trained classifier # including the top classifier, in order to successfully to do fine-tuning # top_model_weights_path 是上一個模型的權重 # 我們上個模型是將網絡的卷積層部分,把全連接以上的部分拋掉,然后在我們自己的訓練集 # 和測試集上跑一邊,將得到的輸出,記錄下來,然后基於輸出的特征,我們訓練一個全連接層 top_model.load_weights(top_model_weights_path) # add the model on top of the convolutional base # vgg_model.add(top_model) # 上面代碼是需要將 身子 和 頭 組裝到一起,我們利用函數式組裝即可 print(base_model.input, base_model.output) # (?, 150, 150, 3) (None, 4, 4, 512) vgg_model = Model(inputs=base_model.input, outputs=top_model(base_model.output)) # 然后將最后一個卷積塊前的卷積層參數凍結 # 普通的模型需要對所有層的weights進行訓練調整,但是此處我們只調整VGG16的后面幾個卷積層,所以前面的卷積層要凍結起來 for layer in vgg_model.layers[: 15]: # # :25 bug 15層之前都是不需訓練的 layer.trainable = False # compile the model with a SGD/momentum optimizer # vgg_model.compile(loss='binary_crossentropy', # optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), # metrics=['accuracy']) vgg_model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), # 使用一個非常小的lr來微調 metrics=['accuracy']) vgg_model.summary() return vgg_model def generate_data(): # 然后以很低的學習率去訓練 # this is the augmentation configuration we will use for training train_datagen = ImageDataGenerator( rescale=1. / 255, # 圖片像素值為0-255,此處都乘以1/255,調整到0-1之間 shear_range=0.2, # 斜切 zoom_range=0.2, # 方法縮小范圍 horizontal_flip=True # 水平翻轉 ) # this is the augmentation configuration we will use for testing test_datagen = ImageDataGenerator(rescale=1. / 255) # this is a generator that will read pictures found in subfolders of 'data/train' # and indefinitely generate batches of augmented image data train_generator = train_datagen.flow_from_directory( 'data/mytrain', target_size=(150, 150), batch_size=batch_size, class_mode='categorical' # if we use binary_crossentropy loss, we need binary labels ) # this is a similar generator, for validation data validation_generator = test_datagen.flow_from_directory( 'data/mytest', target_size=(150, 150), batch_size=batch_size, class_mode='categorical' ) # 開始用 train set 來微調模型的參數 print("start to fine-tune my model") vgg_model.fit_generator( train_generator, steps_per_epoch=nb_train_samples // batch_size, epochs=epochs, validation_data=validation_generator, validation_steps=nb_validation_samples // batch_size ) # vgg_model.save_weights('vgg_try.h5') if __name__ == '__main__': nb_train_samples = 500 nb_validation_samples = 100 epochs = 50 batch_size = 4 top_model_weights_path = 'bottleneck_fc_model.h5' vgg_model = build_model() generate_data()
結果如下,效果是非常的好:
最好用服務器跑,這樣快(可能說了句廢話。。)。
參考文獻:https://blog.csdn.net/sinat_26917383/article/details/72861152
貓狗兩分類參考文獻:https://blog.csdn.net/dugudaibo/article/details/78818247#commentBox
https://github.com/RayDean/DeepLearning/blob/master/FireAI_010_FineTuneMultiClass.ipynb