VGG16預訓練學習筆記
1.遷移學習
2.預訓練模型
3.使用預訓練模型
4.運用預訓練模型
? 提取特徵(extractfeatures)
? 優化模型(finetune the model)
5.優化模型的方式
6.在數字識別中使用預訓練模型
? 只針對輸出密集層(outputdense layer)的重新訓練
? 凍結初始幾層網路的權重因子
1什麼是遷移學習?
我們知道,神經網路需要用資料來訓練,它從資料中獲得資訊,進而把它們轉換成相應的權重。這些權重能夠被提取出來,遷移到其他的神經網路中,我們“遷移”了這些學來的特徵,就不需要從零開始訓練一個神經網路了。
現在,讓我們從自身進化的角度來討論這種遷移學習的重要性。這是Tim Urban最近在waitbutwhy.com上的一篇文章中提出的觀點。
2. 什麼是預訓練模型?
簡單來說,預訓練模型(pre-trained model)是前人為了解決類似問題所創造出來的模型。你在解決問題的時候,不用從零開始訓練一個新模型,可以從在類似問題中訓練過的模型入手。
比如說,如果你想做一輛自動駕駛汽車,可以花數年時間從零開始構建一個性能優良的影象識別演算法,也可以從Google在ImageNet資料集上訓練得到的inception model(一個預訓練模型)起步,來識別影象。
一個預訓練模型可能對於你的應用中並不是100%
接下來,我會舉個例子來說明。
3. 為什麼我們要用預訓練模型?
上週我一直在嘗試解決Crowdanalytix platform上的一個問題:從手機圖片中分辨場景。
這是一個影象分類的問題,訓練資料集中有4591張圖片,測試集中有1200張圖片。我們的任務是將圖片相應地分到16個類別中。在對圖片進行一些預處理後,我首先採用一個簡單的MLP(Multi-laterPerceptron)模型,結構如下圖所示:
在對輸入圖片(224*224*3)平整化後,為了簡化上述結構,我用了三個各含有500個神經元的隱藏層。在輸出層中,共有16個神經元對應著十六個類別。
我只能將訓練的準確率控制在6.8%
下面是我用上文所述結構的MLP模型訓練輸出的結果。
可以看出,除非指數級地增加訓練時長,MLP模型無法提供給我更好的結果。因此,我轉而採用CNN(卷積神經網路),看看他們在這個資料集上的表現,以及是否能夠提高訓練的準確度。
CNN的結構如下:
我使用了3個卷積的模組,每個模組由以下部分組成:
· 32個5*5的filter
· 線性整流函式(ReLU)作為啟用函式
· 4*4的最大值池化層
最後一個卷積模組輸出的結果經過平整化後會被傳遞到一個擁有64的神經元的隱藏層上,隨後通過一個drop out rate = 0.5處理後傳遞到輸出層。
最終訓練的結果記錄如下:
準確率15.75%,儘管與MLP模型相比有所提升,但每個週期的執行時間也增加了。
而更重要的是,資料集中最大類別所含圖片數量約佔總數17.6%左右。
只要把所有的圖片都歸到最大的類別,我們就能夠得到比MLP、CNN訓練出來的模型更好的結果(ノへ ̄、)。
此外,增加更多的卷積模組也會大大增加訓練時長。
於是,我轉而去採用預訓練模型,這樣我不需要重新訓練我的整個結構,只需要針對其中的幾層進行訓練即可。
因此,我採用了在ImageNet資料集上預先訓練好的VGG16模型,這個模型可以在Keras庫中找到。
模型的結構如下所示:
在VGG16結構的基礎上,我只將softmax層的1000個輸出改為16個,從而適應我們這個問題的情景,隨後重新訓練了dense layer。
匯入必要的庫
from keras.modelsimport Sequential
from keras.layers.coreimport Flatten, Dense, Dropout
from keras.layers.convolutionalimport Convolution2D, MaxPooling2D, ZeroPadding2D
from keras.optimizersimport SGD
import cv2, numpyas np
· 1
· 2
· 3
· 4
· 5
- 1
- 2
- 3
- 4
- 5
UsingTheano backend.
D:\Anaconda\lib\site-packages\theano-0.8.0.dev0-py2.7.egg\theano\tensor\signal\downsample.py:5:UserWarning: downsample module has been moved to the pool module.
warnings.warn("downsample module hasbeen moved to the pool module.")
使用keras建立vgg16模型
def VGG_16(weights_path=None):
model = Sequential()
model.add(ZeroPadding2D((1,1),input_shape=(3,224,224)))
model.add(Convolution2D(64,3, 3, activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(64,3, 3, activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(128,3, 3, activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(128,3, 3, activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(256,3, 3, activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(256,3, 3, activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(256,3, 3, activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(512,3, 3, activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(512,3, 3, activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(512,3, 3, activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(512,3, 3, activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(512,3, 3, activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(512,3, 3, activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(Flatten())
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1000, activation='softmax'))
if weights_path:
model.load_weights(weights_path)
return model
另一段程式碼中:
model = VGG_16('vgg16_weights.h5')
sgd = SGD(lr=0.1, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(optimizer=sgd, loss='categorical_crossentropy')
現在我們開始來預測了
首先寫一個方法來載入並處理圖片
def load_image(imageurl):
im = cv2.resize(cv2.imread(imageurl),(224,224)).astype(np.float32)
im[:,:,0] -=103.939
im[:,:,1] -=116.779
im[:,:,2] -=123.68
im = im.transpose((2,0,1))
im = np.expand_dims(im,axis=0)
return im
·
讀取vgg16的類別檔案
f = open('synset_words.txt','r')
lines = f.readlines()
f.close()
·
def predict(url):
im = load_image(url)
pre = np.argmax(model.predict(im))
print lines[pre]
%pylab inline
Populatingthe interactive namespace from numpy and matplotlib
from IPython.displayimport Image
Image('cat1.jpg')
跟MLP和CNN相比,這個結構的準確率能夠達到70%。同時,使用VGG16最大的好處是大大減少了訓練時間,只需要針對dense layer進行訓練,所需時間基本可以忽略。
4.怎樣使用預訓練模型?
當在訓練經網路的時候我們的目標是什麼?我們希望網路能夠在多次正向反向迭代的過程中,找到合適的權重。
通過使用之前在大資料集上經過訓練的預訓練模型,我們可以直接使用相應的結構和權重,將它們應用到我們正在面對的問題上。這被稱作是“遷移學習”,即將預訓練的模型“遷移”到我們正在應對的特定問題中。
在選擇預訓練模型的時候你需要非常仔細,如果你的問題與預訓練模型訓練情景下有很大的出入,那麼模型所得到的預測結果將會非常不準確。
舉例來說,如果把一個原本用於語音識別的模型用來做使用者識別,那結果肯定是不理想的。
幸運的是,Keras庫中有許多這類預訓練的結構。
ImageNet資料集已經被廣泛用作訓練集,因為它規模足夠大(包括120萬張圖片),有助於訓練普適模型。ImageNet的訓練目標,是將所有的圖片正確地劃分到1000個分類條目下。這1000個分類基本上都來源於我們的日常生活,比如說貓貓狗狗的種類,各種家庭用品,日常通勤工具等等。
在遷移學習中,這些預訓練的網路對於ImageNet資料集外的圖片也表現出了很好的泛化效能。
既然預訓練模型已經訓練得很好,我們就不會在短時間內去修改過多的權重,在遷移學習中用到它的時候,往往只是進行微調(fine tune)。
在修改模型的過程中,我們通過會採用比一般訓練模型更低的學習速率。
5.微調模型的方法
特徵提取
我們可以將預訓練模型當做特徵提取裝置來使用。具體的做法是,將輸出層去掉,然後將剩下的整個網路當做一個固定的特徵提取機,從而應用到新的資料集中。
採用預訓練模型的結構
我們還可以採用預訓練模型的結構,但先將所有的權重隨機化,然後依據自己的資料集進行訓練。
訓練特定層,凍結其他層
另一種使用預訓練模型的方法是對它進行部分的訓練。具體的做法是,將模型起始的一些層的權重保持不變,重新訓練後面的層,得到新的權重。在這個過程中,我們可以多次進行嘗試,從而能夠依據結果找到frozen layers和retrain layers之間的最佳搭配。
如何使用與訓練模型,是由資料集大小和新舊資料集(預訓練的資料集和我們要解決的資料集)之間資料的相似度來決定的。
下圖表展示了在各種情況下應該如何使用預訓練模型:
場景一:資料集小,資料相似度高(與pre-trained model的訓練資料相比而言)
在這種情況下,因為資料與預訓練模型的訓練資料相似度很高,因此我們不需要重新訓練模型。我們只需要將輸出層改制成符合問題情境下的結構就好。
我們使用預處理模型作為模式提取器。
比如說我們使用在ImageNet上訓練的模型來辨認一組新照片中的小貓小狗。在這裡,需要被辨認的圖片與ImageNet庫中的圖片類似,但是我們的輸出結果中只需要兩項——貓或者狗。
在這個例子中,我們需要做的就是把dense layer和最終softmax layer的輸出從1000個類別改為2個類別。
場景二:資料集小,資料相似度不高
在這種情況下,我們可以凍結預訓練模型中的前k個層中的權重,然後重新訓練後面的n-k個層,當然最後一層也需要根據相應的輸出格式來進行修改。
因為資料的相似度不高,重新訓練的過程就變得非常關鍵。而新資料集大小的不足,則是通過凍結預訓練模型的前k層進行彌補。
場景三:資料集大,資料相似度不高
在這種情況下,因為我們有一個很大的資料集,所以神經網路的訓練過程將會比較有效率。然而,因為實際資料與預訓練模型的訓練資料之間存在很大差異,採用預訓練模型將不會是一種高效的方式。
因此最好的方法還是將預處理模型中的權重全都初始化後在新資料集的基礎上重頭開始訓練。
場景四:資料集大,資料相似度高
這就是最理想的情況,採用預訓練模型會變得非常高效。最好的運用方式是保持模型原有的結構和初始權重不變,隨後在新資料集的基礎上重新訓練。
6. 在手寫數字識別中使用預訓練模型
現在,讓我們嘗試來用預訓練模型去解決一個簡單的問題。
我曾經使用vgg16作為預訓練的模型結構,並把它應用到手寫數字識別上。
讓我們先來看看這個問題對應著之前四種場景中的哪一種。我們的訓練集(MNIST)有大約60,000張左右的手寫數字圖片,這樣的資料集顯然是偏小的。所以這個問題應該屬於場景一或場景二。
我們可以嘗試把兩種對應的方法都用一下,看看最終的效果。
只重新訓練輸出層 & dense layer
這裡我們採用vgg16作為特徵提取器。隨後這些特徵,會被傳遞到依據我們資料集訓練的denselayer上。輸出層同樣由與我們問題相對應的softmax層函式所取代。
在vgg16中,輸出層是一個擁有1000個類別的softmax層。我們把這層去掉,換上一層只有10個類別的softmax層。我們只訓練這些層,然後就進行數字識別的嘗試。
凍結最初幾層網路的權重
這裡我們將會把vgg16網路的前8層進行凍結,然後對後面的網路重新進行訓練。這麼做是因為最初的幾層網路捕獲的是曲線、邊緣這種普遍的特徵,這跟我們的問題是相關的。我們想要保證這些權重不變,讓網路在學習過程中重點關注這個資料集特有的一些特徵,從而對後面的網路進行調整。