預訓練模型遷移學習

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

------

雙11 10本技術書籍(Python、Java、架構、Android等書籍)等你拿!留言就有機會得!

-------

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

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

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

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

本文結構:

1)遷移學習

2)卷積神經網路

3)預訓練模型的復用

4)遷移學習過程

5)深度卷積神經網路上的分類器

6)示例

7)總結

1、遷移學習

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

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

2、卷積神經網路(CNN)

在遷移學習中,常用的幾個預訓練模型是基於大規模卷積神經網路的(Voulodimos)。一般來說,CNN被證明擅長於解決計算機視覺方面的問題。它的高性能和易訓練的特點是最近幾年CNN流行的兩個主要原因。

典型的CNN包括兩個部分:

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

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

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

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

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

3、預訓練模型的復用

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

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

(2)訓練一些層而凍結其它的層較低層適用的是通用特徵(獨立問題),而較高層適用的是特殊特徵。這裡,我們通過選擇要調整的網路的權重來處理這兩種情況。通常,如果有一個較小的數據集和大量的參數,你會凍結更多的層,以避免過度擬合。相比之下,如果數據集很大,並且參數的數量很少,那麼可以通過給新任務訓練更多的層來完善模型,因為過度擬合不是問題了。

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

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

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

4、遷移學習過程

從實踐的角度來看, 整個遷移學習過程可以概括如下:

(1)選擇預訓練模型.從大量的預訓練模型,可以選擇一個適合你要解決的問題的。如果你正在使用Keras,可以立即使用一系列模型,例如VGG、InceptionV3和ResNet5。點擊<u stylex="box-sizing: border-box;">這裡</u>你可以看到Keras上所有的模型

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

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

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

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

象限3.小數據集,不同於預訓練模型的數據集,這裡唯一適合的就是策略2。很難在訓練和凍結的層數之間平衡。如果你涉及的太深入,那麼模型就會過度擬合;如果你僅停留在模型的表面,那麼你就不會學到任何有用的東西。也許,你需要比象限2更深入,並且需要考慮數據增強技術。

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

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

5、深度卷積神經網路上的分類器

如前所述,由基於預訓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萬等你瓜分!

馬上一鍵開團贏紅包: click.aliyun.com/m/1000

以上為譯文,由阿里云云棲社區組織翻譯。

譯文鏈接

文章原標題《Transfer learning from pre-trained models》

作者:Pedro Marcelino

譯者:奧特曼,審校:袁虎。

文章為簡譯,更為詳細的內容,請查看原文.

更多技術乾貨敬請關注云棲社區知乎機構號:阿里云云棲社區 - 知乎

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


推薦閱讀:

TAG:機器學習 | 深度學習(DeepLearning) | 模型 |