預訓練模型遷移學習

摘要: 本文經過使用Keras及一個預訓練模型的實例,教你如何經過遷移學習快速簡便地解決圖像分類問題。

如何快速簡便地解決圖像分類問題呢?本文經過使用Keras及一個預訓練模型的實例,教你如何經過遷移學習來解決這個問題。服務器

深度學習正在迅速成爲人工智能應用開發的主要工具。在計算機視覺、天然語言處理和語音識別等領域都已有成功的案例。網絡

深度學習擅長解決的一個問題是圖像分類。圖像分類的目標是根據一組合理的類別對指定的圖片進行分類。從深度學習的角度來看,圖像分類問題能夠經過遷移學習的方法來解決。app

本文介紹瞭如何經過遷移學習來解決圖像分類的問題。本文中所提出的實現方式是基於Python語言的Keras。機器學習

本文結構:函數

1)遷移學習工具

2)卷積神經網絡性能

3)預訓練模型的複用學習

4)遷移學習過程測試

5)深度卷積神經網絡上的分類器大數據

6)示例

7)總結

一、遷移學習

遷移學習在計算機視覺領域中是一種很流行的方法,由於它能夠創建精確的模型,耗時更短。利用遷移學習,不是從零開始學習,而是從以前解決各類問題時學到的模式開始。這樣,你就能夠利用之前的學習成果(例如VGG、 Inception、MobileNet),避免從零開始。咱們把它看做是站在巨人的肩膀上。

在計算機視覺領域中,遷移學習一般是經過使用預訓練模型來表示的。預訓練模型是在大型基準數據集上訓練的模型,用於解決類似的問題。因爲訓練這種模型的計算成本較高,所以,導入已發佈的成果並使用相應的模型是比較常見的作法。

二、卷積神經網絡(CNN)

在遷移學習中,經常使用的幾個預訓練模型是基於大規模卷積神經網絡的(Voulodimos)。通常來講,CNN被證實擅長於解決計算機視覺方面的問題。它的高性能和易訓練的特色是最近幾年CNN流行的兩個主要緣由。

典型的CNN包括兩個部分:

(1)卷積基,由卷積層和池化層的堆棧組成。卷積基的主要目的是由圖像生成特徵。爲了直觀地解釋卷積層和池化層,請參閱Chollet 2017。

(2)分類器,一般是由多個全鏈接層組成的。分類器的主要目標是基於檢測到的特徵對圖像進行分類。全鏈接層是其中的神經元與前一層中的全部激活神經元徹底鏈接的層。

圖1顯示了一個基於CNN的模型體系結構。這是一個簡化的版本,事實上,這種類型的模型,其實際的體系結構比咱們在這裏說的要複雜得多。

圖1.基於CNN的模型體系結果

這些深度學習模型的一個重要方面是,它們能夠自動學習分層的特徵表示。這意味着由第一層計算的特徵是通用的,而且能夠在不一樣的問題域中重用,而由最後一層計算的特徵是較特殊的,而且依賴於所選擇的數據集和任務。根據Yosinski等人在2014年提出的「若是第一層的特徵是通用的而且最後一層的特徵是特殊的,那麼網絡中一定有一個從通常到特殊的轉變。所以,咱們的CNN的卷積基特別是它下面的層(那些更接近輸入的層)適用於通常特徵,而分類器部分和卷積基中一些較高的層適用於特殊的特徵。

三、預訓練模型的複用

當你根據本身的須要重用預訓練模型時,首先要刪除原始的分類器,而後添加一個適合的新分類器,最後必須根據如下的三種策略之一對模型進行微調:

(1)訓練整個模型 在這種狀況下,利用預訓練模型的體系結構,並根據數據集對其進行訓練。若是你從零開始學習模型,那麼就須要一個大數據集,和大量的計算資源。

(2)訓練一些層而凍結其它的層較低層適用的是通用特徵(獨立問題),而較高層適用的是特殊特徵。這裏,咱們經過選擇要調整的網絡的權重來處理這兩種狀況。一般,若是有一個較小的數據集和大量的參數,你會凍結更多的層,以免過分擬合。相比之下,若是數據集很大,而且參數的數量不多,那麼能夠經過給新任務訓練更多的層來完善模型,由於過分擬合不是問題了。

(3)凍結卷積基這種狀況適用於訓練/凍結平衡的極端狀況。其主要思想是將卷積基保持在原始形式,而後使用其輸出提供給分類器。把你正在使用的預訓練模型做爲固定的特徵提取途徑,若是缺乏計算資源,而且數據集很小,或者預訓練模型解決了你正要解決的問題,那麼這就頗有用。

圖2以圖表的形式說明了這三種策略。

與策略3的這種直接簡單的應用不一樣,策略1和策略2要求你當心謹慎地應對卷積部分中使用的學習率。學習率是一個超參數,它控制你調整網絡權重的大小。當你正在使用基於CNN的預訓練模型時,使用一個低學習率是比較明智的,由於使用高學習率會增長失去以前所積累知識的風險。假設預訓練模型通過了比較好的訓練,保持低學習率將確保你不會過早和過分地調整CNN權重。

四、遷移學習過程

從實踐的角度來看, 整個遷移學習過程能夠歸納以下:

(1)選擇預訓練模型.從大量的預訓練模型,能夠選擇一個適合你要解決的問題的。若是你正在使用Keras,能夠當即使用一系列模型,例如VGG、InceptionV3和ResNet5。點擊<u style="box-sizing: border-box;">這裏</u>你能夠看到Keras上全部的模型

(2)根據大小類似性矩陣進行分類.在圖3中你用矩陣來控制選擇,這個矩陣是根據數據集的大小,以及預訓練模型被訓練以後的數據集的類似性,來對計算機視覺問題進行分類的。根據經驗,若是每一個類的圖像少於1000個,則認爲是小數據集。就數據集的類似性而言,常識佔了上風。例如,若是是識別貓和狗,那麼ImageNet將是一個相似的數據集,由於它有貓和狗的圖像。然而,若是是識別癌細胞,ImageNet就不行了。

(3)微調模型.這裏可使用大小和類似性矩陣來指導你的選擇,而後參考前面提到的重用預訓練模型的三個策略。請見圖4。

象限1.大數據集,但不一樣於預訓練模型的數據集。這種狀況將會讓你使用策略1。由於有一個大數據集,你就能夠從零開始訓練一個模型。儘管數據集不一樣,但在實踐中,利用模型的體系結構和權重,經過預訓練模型對初始化模型仍然是頗有幫助的;

象限2.大數據集與相似於預訓練模型的數據集。在這裏任何選項都有效,可能最有效的是策略2。因爲咱們有一個大數據集,過分擬合不該該是個問題,因此咱們能夠儘量多地學習。然而,因爲數據集是類似的,咱們能夠經過利用之前的知識來節省大量的訓練工做。所以,僅對卷積基的分類器和頂層進行訓練就足夠了。

象限3.小數據集,不一樣於預訓練模型的數據集,這裏惟一適合的就是策略2。很難在訓練和凍結的層數之間平衡。若是你涉及的太深刻,那麼模型就會過分擬合;若是你僅停留在模型的表面,那麼你就不會學到任何有用的東西。也許,你須要比象限2更深刻,而且須要考慮數據加強技術。

象限4.小數據集,但相似於預訓練模型的數據集。你只需刪除最後一個全鏈接層(輸出層),而且執行預訓練模型做爲固定的特徵提取器,而後使用結果特徵來訓練新的分類器。

圖3和圖4. 尺寸類似性矩陣(上)和微調預訓練模型的決策圖(下)

五、深度卷積神經網絡上的分類器

如前所述,由基於預訓CNN的遷移學習方法獲得的圖像分類模型一般由如下兩部分組成:

(1)卷積基,實現特徵提取;

(2)分類器,經過卷積基提取的特徵對輸入圖像進行分類;

因爲在本節中咱們主要關注分類器部分,因此必須首先說明能夠遵循不一樣的方法來構建分類器:

(1)全鏈接層.對於圖像分類問題,標準的方法是使用一組下面是softmax激活層的全鏈接層。softmax激活層輸出每一個可能的類標籤上的機率分佈,而後只須要根據最可能的類對圖像進行分類。

(2)全局平均池化層.Lin在2013年提出了一種基於全局平均池化的方法。在這個方法中,咱們沒有在卷積基最上面添加全鏈接層,而是添加了一個全局平均池化層,並將其輸出直接提供給softmax激活層。

(3)線性支持向量機.線性支持向量機(SVM)是另外一種可能的方法。根據Tang在2013年所說的,能夠利用卷積基在提取的特徵上經過訓練線性SVM分類器來提升分類精確度。SVM方法的優缺點細節能夠在下文中找到。

6. 示例

6.1 準備數據

在例子中,咱們將使用原始數據集的小版本,這能夠更快地運行模型,適用於那些計算能力有限的任務。

爲了構建小版本數據集,咱們可使用Chollet 2017所提供的代碼。

# Create smaller dataset for Dogs vs. Cats
importos, shutil

    original_dataset_dir = '/Users/macbook/dogs_cats_dataset/train/'

    base_dir = '/Users/macbook/book/dogs_cats/data'
    if not os.path.exists(base_dir):
    os.mkdir(base_dir)

    # Create directories
    train_dir = os.path.join(base_dir,'train')
    if not os.path.exists(train_dir):
    os.mkdir(train_dir)
    validation_dir = os.path.join(base_dir,'validation')
    if not os.path.exists(validation_dir):
    os.mkdir(validation_dir)
    test_dir = os.path.join(base_dir,'test')
    if not os.path.exists(test_dir):
    os.mkdir(test_dir)

    train_cats_dir = os.path.join(train_dir,'cats')
    if not os.path.exists(train_cats_dir):
    os.mkdir(train_cats_dir)

    train_dogs_dir = os.path.join(train_dir,'dogs')
    if not os.path.exists(train_dogs_dir):
    os.mkdir(train_dogs_dir)

    validation_cats_dir = os.path.join(validation_dir,'cats')
    if not os.path.exists(validation_cats_dir):
    os.mkdir(validation_cats_dir)

    validation_dogs_dir = os.path.join(validation_dir, 'dogs')
    if not os.path.exists(validation_dogs_dir):
    os.mkdir(validation_dogs_dir)

    test_cats_dir = os.path.join(test_dir, 'cats')     
    if not os.path.exists(test_cats_dir):
    os.mkdir(test_cats_dir)

    test_dogs_dir = os.path.join(test_dir, 'dogs')
    if not os.path.exists(test_dogs_dir):
    os.mkdir(test_dogs_dir)

    # Copy first 1000 cat images to train_cats_dir
    fnames = ['cat.{}.jpg'.format(i) for i in range(100)]
    forfname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    shutil.copyfile(src, dst)

    # Copy next 500 cat images to validation_cats_dir
    fnames = ['cat.{}.jpg'.format(i) for i in range(200, 250)]
    forfname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_cats_dir, fname)
    shutil.copyfile(src, dst)

    # Copy next 500 cat images to test_cats_dir
    fnames = ['cat.{}.jpg'.format(i) for i in range(250,300)]
    forfname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_cats_dir, fname)
    shutil.copyfile(src, dst)

    # Copy first 1000 dog images to train_dogs_dir
    fnames = ['dog.{}.jpg'.format(i) for i in range(100)]
    forfname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_dogs_dir, fname)
    shutil.copyfile(src, dst)

    # Copy next 500 dog images to validation_dogs_dir
    fnames = ['dog.{}.jpg'.format(i) for i in range(200,250)]
    forfname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_dogs_dir, fname)
    shutil.copyfile(src, dst)

    # Copy next 500 dog images to test_dogs_dir
    fnames = ['dog.{}.jpg'.format(i) for i in range(250,300)]
    forfname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_dogs_dir, fname)
    shutil.copyfile(src, dst)

    # Sanity checks
    print('total training cat images:', len(os.listdir(train_cats_dir)))
    print('total training dog images:', len(os.listdir(train_dogs_dir)))
    print('total validation cat images:', len(os.listdir(validation_cats_dir)))
    print('total validation dog images:', len(os.listdir(validation_dogs_dir)))
    print('total test cat images:', len(os.listdir(test_cats_dir)))
    print('total test dog images:', len(os.listdir(test_dogs_dir)))

6.2從卷積基中提取特徵

卷積基將用於提取特徵,這些特徵將爲咱們想要訓練的分類器提供輸入,以便可以識別圖像中是否有狗或貓。

再次把Chollet 2017提供的代碼修改了一下,請看代碼2。

# Extract features
importos, shutil
fromkeras.preprocessing.image import ImageDataGenerator

datagen = ImageDataGenerator(rescale=1./255)
batch_size = 32

defextract_features(directory, sample_count):
features = np.zeros(shape=(sample_count, 7, 7, 512))  # Must be equal to the output of the convolutional base
labels = np.zeros(shape=(sample_count))
    # Preprocess data
generator = datagen.flow_from_directory(directory,
target_size=(img_width,img_height),
batch_size = batch_size,
class_mode='binary')
    # Pass data through convolutional base
i = 0
forinputs_batch, labels_batch in generator:
features_batch = conv_base.predict(inputs_batch)
features[i * batch_size: (i + 1) * batch_size] = features_batch
labels[i * batch_size: (i + 1) * batch_size] = labels_batch
i += 1
ifi * batch_size>= sample_count:
break
return features, labels

train_features, train_labels = extract_features(train_dir, train_size)  # Agree with our small dataset size
validation_features, validation_labels = extract_features(validation_dir, validation_size)
test_features, test_labels = extract_features(test_dir, test_size)

代碼2.從卷積基中提取特徵。

6.3分類器

6.3.1全鏈接層

咱們提出的第一個解決方案是基於全鏈接層的。在分類器中添加一組全鏈接層,把從卷積基中提取的特徵做爲它們的輸入。

爲了保持簡單和高效,咱們將使用Chollet2018上的解決方案,並稍做修改,特別地使用Adam優化器來代替RMSProp。

代碼3顯示了相關的代碼,而圖5和圖6表示了學習曲線。

# Define model
fromkeras import models
    fromkeras import layers
    fromkeras import optimizers

    epochs = 100

    model = models.Sequential()
    model.add(layers.Flatten(input_shape=(7,7,512)))
    model.add(layers.Dense(256, activation='relu', input_dim=(7*7*512)))
    model.add(layers.Dropout(0.5))
    model.add(layers.Dense(1, activation='sigmoid'))
    model.summary()

    # Compile model
    model.compile(optimizer=optimizers.Adam(),
    loss='binary_crossentropy',
    metrics=['acc'])

    # Train model
    history = model.fit(train_features, train_labels,
    epochs=epochs,
    batch_size=batch_size, 
    validation_data=(validation_features, validation_labels))

代碼3.全鏈接層解決方案

圖5.全鏈接層方案的準確性

圖6.全鏈接層方案的損失

結果簡述:

(1)驗證精確度約爲0.85,對於給定數據集的大小,這個結果是很是不錯的。(2)這個模型過分擬合,在訓練曲線和驗證曲線之間有很大的差距。

(3)因爲咱們已經使用了dropout,因此應該增大數據集來改善結果。

6.3.2全局平均池化層

這種狀況與以前的不一樣之處在於,咱們將添加一個全局平均池化層,並將其輸出提供到一個sigmoid激活層,而不是添加一組全鏈接層。

請注意,咱們所說的是sigmoid激活層,而不是SoftMax激活層。咱們正在改成sigmoid激活,由於在Keras中,爲了執行二進制分類,應該使用sigmoid激活和binary_crossentropy做爲損失。

代碼4是構建分類器的代碼。圖7和圖8表示所得結果的學習曲線。

# Define model
fromkeras import models
fromkeras import layers
fromkeras import optimizers

epochs = 100

model = models.Sequential()
model.add(layers.GlobalAveragePooling2D(input_shape=(7,7,512)))
model.add(layers.Dense(1, activation='sigmoid'))
model.summary()

# Compile model
model.compile(optimizer=optimizers.Adam(),
loss='binary_crossentropy',
metrics=['acc'])

# Train model
history = model.fit(train_features, train_labels,
epochs=epochs,
batch_size=batch_size, 
validation_data=(validation_features, validation_labels))

代碼4.全局平均池化解決方案

圖7.全局平均池化層方案的準確性

圖8.全局平均池化方案的損失

結果簡述:

(1)驗證精確度與全鏈接層方案的相似;

(2)模型不像之前那樣過分擬合;

(3)當模型中止訓練時,損失函數的結果仍在減少,大概能夠經過增長週期數來完善模型;

6.3.3 線性支持向量機

在這種狀況下,咱們將利用卷積基提取的特徵來訓練一個線性支持向量機(SVM)的分類器。

爲了訓練這種分類器,可使用傳統的機器學習方式。所以,咱們將使用k-fold cross-validation來估算分類器的偏差。因爲將使用k-fold cross-validation,咱們能夠將訓練集和驗證集鏈接起來以擴大訓練數據(像前面同樣保持測試集不變)。

代碼5顯示瞭如何聯繫數據。

# Concatenate training and validation sets
svm_features = np.concatenate((train_features, validation_features))
svm_labels = np.concatenate((train_labels, validation_labels))

代碼 5. 數據鏈接

最後,咱們必需要知道SVM分類器有一個超參數,它是偏差項的懲罰參數C。爲了優化這個超參數,咱們將使用窮舉的方法進行網格搜索。

代碼6表示用於構建該分類器的代碼,而圖9表示了學習曲線。

# Build model
importsklearn
fromsklearn.cross_validation import train_test_split
fromsklearn.grid_search import GridSearchCV
fromsklearn.svm import LinearSVC

X_train, y_train = svm_features.reshape(300,7*7*512), svm_labels

param = [{
          "C": [0.01, 0.1, 1, 10, 100]
         }]

svm = LinearSVC(penalty='l2', loss='squared_hinge')  # As in Tang (2013)
clf = GridSearchCV(svm, param, cv=10)
clf.fit(X_train, y_train)

代碼6. 線性 SVM解決方案.

圖9.線性支持向量機方案的精確度

結果簡述:

(1)模型的精確度在0.86左右,相似於前一個方案;

(2)過分擬合即將出現。此外,訓練精確度始終是1,這是不正常的,能夠解釋爲過分擬合的現象;

(3)模型的精確度應隨着訓練樣本數量的增長而提升。然而,這彷佛並無出現,這多是因爲過分擬合的緣由。有趣的是,當數據集增長的時候,模型將會如何反應。

7. 總結

  • 本文提出了遷移學習、卷積神經網絡和預訓練模型的概念;

* 定義了基本的微調策略來從新調整預訓練模型;

  • 描述了一種基於數據集的大小和類似度來決定應該使用哪一種微調策略的結構化方法;
  • 介紹了三種不一樣的分類器,可用於經過卷積基提取的特徵上面;
  • 爲文中闡述的三個分類器中的任意一個都提供了關於圖像分類的端到端的例子;

雲服務器99元拼團購!拉新還可贏現金紅包!300萬等你瓜分!
立刻一鍵開團贏紅包: http://click.aliyun.com/m/100...


本文做者:【方向】

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索