一文讀懂遷移學習和預訓練

轉載:https://zhuanlan.zhihu.com/p/27657264

引言

跟傳統的監督式機器學習算法相比,深度神經網絡目前最大的劣勢是什麼?

貴。

尤其是當我們在嘗試處理現實生活中諸如圖像識別、聲音辨識等實際問題的時候。一旦你的模型中包含一些隱藏層時,增添多一層隱藏層將會花費巨大的計算資源。

慶幸的是,有一種叫做“遷移學習”的方式,可以使我們在他人訓練過的模型基礎上進行小改動便可投入使用。在這篇文章中,我將會講述如何使用預訓練模型來加速解決問題的過程。

目錄

1. 什麼是遷移學習?

2. 什麼是預訓練模型?

3. 爲什麼我們使用預訓練模型?-結合生活實例

4. 我們可以怎樣運用預訓練模型?

提取特徵(extract features)

優化模型(fine tune the model)

5. 優化模型的方式

6. 在數字識別中使用預訓練模型

只針對輸出密集層(output dense layer)的重新訓練

凍結初始幾層網絡的權重因子

1. 什麼是遷移學習?

爲了對遷移學習產生一個直觀的認識,不妨拿老師與學生之間的關係做類比。

一位老師通常在ta所教授的領域有着多年豐富的經驗,在這些積累的基礎上,老師們能夠在課堂上教授給學生們該領域最簡明扼要的內容。這個過程可以看做是老手與新手之間的“信息轉移”。

這個過程在神經網絡中也適用。

我們知道,神經網絡需要用數據來訓練,它從數據中獲得信息,進而把它們轉換成相應的權重。這些權重能夠被提取出來,遷移到其他的神經網絡中,我們“遷移”了這些學來的特徵,就不需要從零開始訓練一個神經網絡了 。

現在,讓我們從自身進化的角度來討論這種遷移學習的重要性。這是Tim Urban最近在waitbutwhy.com上的一篇文章中提出的觀點。

Tim說,在語言發明之前,每一代人類都需要自身重新習得很多知識,這也是知識從上一代到下一代一增長緩慢的原因。

隨後,我們發明了語言,這爲知識在世代間的傳遞提供了載體,下圖是在語言發明後,同樣時間尺度下知識增長速度的示意圖。

是不是看起來很牛逼?而通過權重的傳遞來進行遷移學習和人類在世代交替中通過語言傳播知識,是一個道理。

2. 什麼是預訓練模型?

簡單來說,預訓練模型(pre-trained model)是前人爲了解決類似問題所創造出來的模型。你在解決問題的時候,不用從零開始訓練一個新模型,可以從在類似問題中訓練過的模型入手。

比如說,如果你想做一輛自動駕駛汽車,可以花數年時間從零開始構建一個性能優良的圖像識別算法,也可以從Google在ImageNet數據集上訓練得到的inception model(一個預訓練模型)起步,來識別圖像。

一個預訓練模型可能對於你的應用中並不是100%的準確對口,但是它可以爲你節省大量功夫。

接下來,我會舉個例子來說明。

3. 爲什麼我們要用預訓練模型?

上週我一直在嘗試解決Crowdanalytix platform上的一個問題:從手機圖片中分辨場景。

這是一個圖像分類的問題,訓練數據集中有4591張圖片,測試集中有1200張圖片。我們的任務是將圖片相應地分到16個類別中。在對圖片進行一些預處理後,我首先採用一個簡單的MLP(Multi-later Perceptron)模型,結構如下圖所示:

在對輸入圖片(224*224*3)平整化後,爲了簡化上述結構,我用了三個各含有500個神經元的隱藏層。在輸出層中,共有16個神經元對應着十六個類別。

我只能將訓練的準確率控制在6.8%,這是個很不理想的結果。我嘗試對隱藏層、隱層中神經元的數量以及drop out速率進行調整,但準確度都沒有太大的提升。而如果增加隱藏層和其中神經元的數量,每個週期的運行時間則會增加20s以上。(我的開發環境是12GB VRAM,Titan X GPU)

下面是我用上文所述結構的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。

跟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作爲特徵提取器。隨後這些特徵,會被傳遞到依據我們數據集訓練的dense layer上。輸出層同樣由與我們問題相對應的softmax層函數所取代。

在vgg16中,輸出層是一個擁有1000個類別的softmax層。我們把這層去掉,換上一層只有10個類別的softmax層。我們只訓練這些層,然後就進行數字識別的嘗試。

# importing required libraries

from keras.models import Sequential
from scipy.misc import imread
get_ipython().magic('matplotlib inline')
import matplotlib.pyplot as plt
import numpy as np
import keras
from keras.layers import Dense
import pandas as pd

from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
from keras.applications.vgg16 import decode_predictions
train=pd.read_csv("R/Data/Train/train.csv")
test=pd.read_csv("R/Data/test.csv")
train_path="R/Data/Train/Images/train/"
test_path="R/Data/Train/Images/test/"

from scipy.misc import imresize
# preparing the train dataset

train_img=[]
for i in range(len(train)):

    temp_img=image.load_img(train_path+train['filename'][i],target_size=(224,224))

    temp_img=image.img_to_array(temp_img)

    train_img.append(temp_img)

#converting train images to array and applying mean subtraction processing

train_img=np.array(train_img) 
train_img=preprocess_input(train_img)
# applying the same procedure with the test dataset

test_img=[]
for i in range(len(test)):

    temp_img=image.load_img(test_path+test['filename'][i],target_size=(224,224))

    temp_img=image.img_to_array(temp_img)

    test_img.append(temp_img)

test_img=np.array(test_img) 
test_img=preprocess_input(test_img)

# loading VGG16 model weights
model = VGG16(weights='imagenet', include_top=False)
# Extracting features from the train dataset using the VGG16 pre-trained model

features_train=model.predict(train_img)
# Extracting features from the train dataset using the VGG16 pre-trained model

features_test=model.predict(test_img)

# flattening the layers to conform to MLP input

train_x=features_train.reshape(49000,25088)
# converting target variable to array

train_y=np.asarray(train['label'])
# performing one-hot encoding for the target variable

train_y=pd.get_dummies(train_y)
train_y=np.array(train_y)
# creating training and validation set

from sklearn.model_selection import train_test_split
X_train, X_valid, Y_train, Y_valid=train_test_split(train_x,train_y,test_size=0.3, random_state=42)

 

# creating a mlp model
from keras.layers import Dense, Activation
model=Sequential()

model.add(Dense(1000, input_dim=25088, activation='relu',kernel_initializer='uniform'))
keras.layers.core.Dropout(0.3, noise_shape=None, seed=None)

model.add(Dense(500,input_dim=1000,activation='sigmoid'))
keras.layers.core.Dropout(0.4, noise_shape=None, seed=None)

model.add(Dense(150,input_dim=500,activation='sigmoid'))
keras.layers.core.Dropout(0.2, noise_shape=None, seed=None)

model.add(Dense(units=10))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])

# fitting the model 

model.fit(X_train, Y_train, epochs=20, batch_size=128,validation_data=(X_valid,Y_valid))

凍結最初幾層網絡的權重

這裏我們將會把vgg16網絡的前8層進行凍結,然後對後面的網絡重新進行訓練。這麼做是因爲最初的幾層網絡捕獲的是曲線、邊緣這種普遍的特徵,這跟我們的問題是相關的。我們想要保證這些權重不變,讓網絡在學習過程中重點關注這個數據集特有的一些特徵,從而對後面的網絡進行調整。

from keras.models import Sequential
from scipy.misc import imread
get_ipython().magic('matplotlib inline')
import matplotlib.pyplot as plt
import numpy as np
import keras
from keras.layers import Dense
import pandas as pd

from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
from keras.applications.vgg16 import decode_predictions
from keras.utils.np_utils import to_categorical

from sklearn.preprocessing import LabelEncoder
from keras.models import Sequential
from keras.optimizers import SGD
from keras.layers import Input, Dense, Convolution2D, MaxPooling2D, AveragePooling2D, ZeroPadding2D, Dropout, Flatten, merge, Reshape, Activation

from sklearn.metrics import log_loss

train=pd.read_csv("R/Data/Train/train.csv")
test=pd.read_csv("R/Data/test.csv")
train_path="R/Data/Train/Images/train/"
test_path="R/Data/Train/Images/test/"

from scipy.misc import imresize

train_img=[]
for i in range(len(train)):

    temp_img=image.load_img(train_path+train['filename'][i],target_size=(224,224))

    temp_img=image.img_to_array(temp_img)

    train_img.append(temp_img)

train_img=np.array(train_img) 
train_img=preprocess_input(train_img)

test_img=[]
for i in range(len(test)):

temp_img=image.load_img(test_path+test['filename'][i],target_size=(224,224))

    temp_img=image.img_to_array(temp_img)

    test_img.append(temp_img)

test_img=np.array(test_img) 
test_img=preprocess_input(test_img)


from keras.models import Model

def vgg16_model(img_rows, img_cols, channel=1, num_classes=None):

    model = VGG16(weights='imagenet', include_top=True)

    model.layers.pop()

    model.outputs = [model.layers[-1].output]

    model.layers[-1].outbound_nodes = []

          x=Dense(num_classes, activation='softmax')(model.output)

    model=Model(model.input,x)

#To set the first 8 layers to non-trainable (weights will not be updated)

          for layer in model.layers[:8]:

       layer.trainable = False

# Learning rate is changed to 0.001
    sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)
    model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])

    return model

train_y=np.asarray(train['label'])

le = LabelEncoder()

train_y = le.fit_transform(train_y)

train_y=to_categorical(train_y)

train_y=np.array(train_y)

from sklearn.model_selection import train_test_split
X_train, X_valid, Y_train, Y_valid=train_test_split(train_img,train_y,test_size=0.2, random_state=42)

# Example to fine-tune on 3000 samples from Cifar10

img_rows, img_cols = 224, 224 # Resolution of inputs
channel = 3
num_classes = 10 
batch_size = 16 
nb_epoch = 10

# Load our model
model = vgg16_model(img_rows, img_cols, channel, num_classes)

model.summary()
# Start Fine-tuning
model.fit(X_train, Y_train,batch_size=batch_size,epochs=nb_epoch,shuffle=True,verbose=1,validation_data=(X_valid, Y_valid))

# Make predictions
predictions_valid = model.predict(X_valid, batch_size=batch_size, verbose=1)

# Cross-entropy loss score
score = log_loss(Y_valid, predictions_valid)

相關資源

原文:Transfer learning & The art of using Pre-trained Models in Deep Learning

VGG-16:<br>gist.github.com/baraldi

Keras庫中的ImageNet預訓練模型:<br>keras.io/applications/

手寫數字數據集MNIST:<br>MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章