AI 遷移學習怎麼玩?手把手教你實戰遷移學習

本文將說明如何使用遷移學習來解決圖像分類問題,相關代碼示例將使用 Python + Keras 進行實現。


深度學習在最近幾年發展迅速,並且已經在人工只能領域佔據了極其重要的一席之地(LeCun,2015)。在業界,深度學習也應用廣泛,例如在計算機視覺、自然語言處理和語音識別等領域,深度學習都取得了不錯的效果。

在深度學習中,圖像分類(Rawat&Wang 2017)是一個經典問題。圖像分類的目標是根據一組特定圖片進行分類,一個非常常見的圖像分類例子就是是在識別貓和狗。

事實上,圖像分類還可以通過遷移學習來建模,並且,在最近一些圖像分類問題中取得良好表現的幾個解決方案(Krizhevsky等人2012,Simonyan&Zisserman 2014,He等人2016),都是基於遷移學習實現的。如果對論文感興趣,可以參考:Pan&Yang(2010)。

本文將著重介紹如何使用遷移學習進行圖像分類。看完本文後,相信你能夠快速輕鬆地解決任何圖像分類問題。

文章結構如下:

1. 遷移學習

2. 卷積神經網絡

3. 如何利用預訓練模型

4. 遷移學習流程

5. CNN 中的分類器

6. 代碼示例

7. 總結

8. 參考文獻



一、遷移學習

遷移學習是計算機視覺中的一種很受歡迎的方法,通過它我們可以花更少的時間建立更準確的模型(Rawat&Wang 2017)。遷移學習可以利用其他模型在學習其他模型已經訓練得到的模式和知識,並應用到當前你要解決的問題中。因此,遷移學習並不需要從頭開始學習和訓練模型,遷移學習的這一優秀特徵被稱為“站在巨人的肩膀上”。

進行遷移學習之前,我們需要基本的預訓練模型。在這裡,預訓練模型可以是一些公開的預訓練模型,這些模型通常基於一些公開的大型數據集進行建模,效果都還不錯。但是由於這些數據和模型的訓練目標和我們需要解決的問題不一樣,因此我們無法直接使用那些模型。當然,也不是什麼模型都能夠用於遷移學習,對於圖片分類的遷移學習任務,我們當然無法使用自然語言處理的預訓練模型,通常情況下,我們會選擇一些訓練目標類似的預訓練模型用於遷移學習。

二、卷積神經網絡

目前看來,遷移學習用到的大多數預訓練模型基本都是基於卷積神經網絡 (CNN)(Voulodimos等人,2018)訓練的。一個很重要的原因是,CNN在各種計算機視覺任務中都表現出色(Bengio 2009),並且入門門檻較低便於普及。

一個經典的 CNN 模型有兩部分:

1. 卷積+池化(Convolutional Base):由一堆卷積層和池化層組成。該層的主要目標是從原始數據中生成特徵。有關卷積和池化層的直觀解釋,請參閱Chollet(2017)。

2. 分類器(Classifier):通常由全連接層組成。分類器的主要目標是基於得到的特徵對圖像進行分類。

下圖展示了一個基本的 CNN 模型結構:


AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 1. 基本的 CNN 模型結構


深度學習模型的一個重要特點就是:它們可以自動學習分層特徵表示。這也就意味著:第一層計算的特徵是通用的,在不同的問題中它可以被重用,而最後一層計算的特徵是特定的,極大地取決於所選的數據集和任務。根據( Yosinski,2014)文中所說 :“如果深度模型的第一層特徵是通用的,最後一層特徵是特定的,那麼在網路中一定存在某一層,是從通用到特定的過渡”。對於 CNN 而言,靠近輸入的低層特徵通常是可以重用的特徵,而靠近輸出的分類器部分,則是一些和任務及數據相關的特定特徵。

三、如何利用預訓練模型

當你想要利用預訓練的模型時,首先要做的就是刪除原始分類器,然後添加合適的新分類器,最後再根據如下三種策略中的任意一種對模型進行微調:

1. 訓練整個模型。在這種情況下,新模型將需要數據進行端到端的訓練。為了有比較好的效果,通常需要一個大型數據集(以及大量的計算能力)進行整體建模。

2. 訓練部分層,凍結剩餘層。前面提到,較低層是指一般特徵(與問題無關,通常特徵),而較高層是指特定特徵(取決於問題類型及數據)。因此,在這裡我們可以設定不同網絡層的訓練權重程度(凍結層在訓練期間不會改變)來實現部分層的訓練。一般情況下,如果你只有一個小數據集,但模型又有大量參數時,你需要保留更多的凍結層以避免過擬合。當然,如果你的數據集很大,你可以凍結較少的層,畢竟在大量數據集的情況下,過擬合通常不會發生。

3. 凍結卷積+池化層。這種方法是第二種辦法的一種極端操作。它的主要思想是保留卷積層+池化層,並使用其輸出來訓練分類器。預訓練模型中的特徵抽取方法將作為固定的特徵抽取方式,如果你的計算能力不足,數據集又很小,這種方法可以較好的解決你的問題。

下圖展示了這三種策略的區別


AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 2. 三種微調策略


三種策略中,策略3最為簡單,策略1和策略2則需要謹慎的設置學習率。學習率是一個超參,可以控制調整網絡的權重。通常情況下,當我們使用基於CNN的預訓練模型時,我們會選擇較小的學習率。因為過大的學習率會增加丟失先前知識的風險。理想情況下,假設預訓練模型本身訓練得當,保持較小的學習率將確保新模型不會過早或過多地扭曲 CNN 各層的權重。

四、遷移學習流程

工程實現中,遷移學習的整體流程可歸納如下:

1. 選擇預訓練模型。網上有很多可用的預訓練模型,並且不乏大公司發佈的效果良好的模型。例如,如果您正在使用Keras,在 github 上你可以找到系列模型,如:VGG(Simonyan和Zisserman 2014)、InceptionV3(Szegedy,2015)和ResNet5(He,2015)等。

2. 確定你的數據和任務所處的象限

。我畫了一個簡單的圖(圖3)來幫助大家確定自己的任務在所處的象限。該圖根據數據集的大小和數據集的相似性將遷移學習任務劃分到四個象限。

第一個象限代表:大數據集+與預訓練目標有較大不同;

第二個象限代表:大數據集+與預訓練目標相似;

第三個象限代表:小數據集+與預訓練目標有較大不同;

第四個象限代表:小數據集+與預訓練目標相似。

一般經驗是,如果每個類別的數據少於1000個,則認為數據集很小。 關於數據集相似性,我們則只有通過常識判斷。 比如,如果你的任務是識別貓和狗,ImageNet 將是一個類似的數據集,因為它有貓和狗的圖像。 但是,如果你的任務是識別癌細胞,則最好不要將 ImageNet 視為類似的數據集。


AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 3. 尺寸相似矩陣


3. 制定微調策略。前面我們已經提到了模型微調的幾種方法,並且在第二步中我們確定了遷移學習任務所處的象限,現在就讓我們根據圖4來確定遷移學習模型的微調策略。該圖中的象限與圖三中的象限一一對應。


AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 4. 微調策略決策


處於象限1時:由於你有大型數據集,因此可以全量訓練模型。雖然數據集與預訓練模型的數據集不相似,但在工程實踐中我們發現,使用預訓練模型初始化模型的架構和權重作為遷移模型的初始化架構和參數仍然是非常有用的。

處於象限2時:由於任務類型相似,所以我們可以凍結一部分輸入層。另外,由於又有一個大型數據集,所以我們可以儘可能多的訓練一些中間層和分類器,並且也不用擔心過擬合問題。所以我們選擇的策略是,訓練尾部的卷積+池化層、以及分類器。

處於象限3時:數據集較小,並且與預訓練模型的目標不同。這是最糟糕的情況了,由於數據集太小我們不能全量訓練,又由於訓練目標不同我們不得不盡可能多的訓練中間層。因此唯一的辦法就是如圖四中所示,訓練卷積+池化的中部及尾部層,並且還要訓練分類器。當然,如果有任何方法增加你的數據集,也是一種不錯的選擇。

處於象限4時:小數據集,但與預訓練模型的目標類似。這種情況下,你只需要重新訓練分類器即可,避免過擬合的同時又能確保你的目標得到了訓練。

五、CNN 中的分類器

前面我們提到,CNN 主要由卷積+池化層、分類器兩大部分構成。在第四節提到的遷移學習策略中,無論那種方案,分類器都是需要被重新訓練的部分。

因此,在本節中,我們將著重關注分類器。一些常見的分類器構造辦法是:

1. DNN 全鏈接:DNN 是一個很基本的神經網絡,通常由幾層全連接層構成,在末尾接上一層 softmax 激活層(Krizhevsky,2012,Simonyan和Zisserman,2014,Zeiler和Fergus,2014)。softmax層輸出數據在每個類別上的概率分佈,然後我們就可以根據概率來對數據進行分類。

2. 全局平均池化層:這是由 Lin 在 2013 年提出來的方法。這種方法中,我們不需要添加DNN全連接層,而是添加全局平均池化層,並直接將其輸出作為softmax激活層的輸入,從而得到分類結果。

3. SVM 支持向量機:線性支持向量機(SVM)是另一種常用的方法Tang(2013)。我們可以通過將卷積+池化得到的特徵作為輸入傳入 SVM,利用 SVM 進行分類,從而得到分類結果。

六、代碼示例

本節中,我將給出一些代碼示例。這些例子中,將比較每一種遷移學習中不同的分類器效果。這些代碼主要基於 Python + Keras 對圖像進行分類。

1. 準備數據

為了使這些數據能較快的在本地運行,我門將使用較小的原始數據集,從而保證計算能力有限的機器也能運行。

為了構建較小版本的數據集,我們可以調整Chollet(2017)提供的代碼(數據集為對貓和狗的圖片進行分類的數據集),如下所示:


# Create smaller dataset for Dogs vs. Cats
import os, 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)]
for fname 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)]
for fname 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)]
for fname 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)]
for fname 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)]
for fname 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)]
for fname 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)))


2. 從卷積+池化層中提取特徵

同樣的,我們將使用 Chollet(2017)提供的代碼,並對其中一些部分進行修改,實現如下所示:


# Extract features 

import os, shutil
from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(rescale=1./255)
batch_size = 32
def extract_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
for inputs_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
if i * 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)


3. 分類器

3.1 DNN 全連接

使用 Keras 實現一個 DNN 相當簡單,另外,根據斯坦佛的研究,我們將使用 Adam 作為優化器。代碼如下所示:


# Define model
from keras import models
from keras import layers
from keras 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))


在前兩步生成的數據集上運行該代碼,可以看到該分類器的學習曲線如下:


AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 5. 全連接層分類器在訓練集和驗證集上的準確率



AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 6. 全連接層分類器的損失函數變化情況


從上述各圖中可以看到:

- 驗證準確度約為0.85;

- 模型過擬合了,可以看到:訓練集和驗證集的損失曲線之間存在很大差距;

- 由於我們已經使用了 dropout 策略防止過擬合,因此目前我們應該增加數據集的大小以降低過擬合的程度;

3.2 全局平均池化

該方法與前一種方法的區別在於,我們不是添加一堆全連接層,而是添加全局平均池層,並將其輸出作為輸入提供給 sigmoid 激活層。

值得注意的是,我們使用的是一個sigmoid激活層而不是softmax激活層,這是根據 Lin 於 2013年發表的論文作出的決定。當然你也可以 softmax 激活層,試試看不同激活層對結果的影響。代碼如下所示:


# Define model
from keras import models
from keras import layers

from keras 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))


運行該代碼,模型的學習曲線如下所示:


AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 7. 全局平均池化層的準確率

AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 8. 全局平均池化層的損失函數曲線


從上面可以看出:

- 準確度與 DNN 方案的準確度差不多;

- 該模型在泛化能力上比 DNN 好,過擬合程度較低;

- 損失函數持續在下降,所以極有可能可以通過增加訓練次數來提升模型的準確率;

3.3 SVM 線性支持向量機

SVM 是一種傳統的機器學習方法,並且在很多領域都有不錯的效果。我們將使用 K 重交叉驗證來估計分類器的誤差。同時,由於將使用 K 重交叉驗證,我們可以將訓練集和驗證集放到一起,擴大我們的訓練數據用以訓練 SVM。

以下是將訓練集和驗證集合並的代碼:


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


為了優化 SVM,我們將對 SVM 的參數進行超參訓練。我們使用 scikit-learn 中的網格搜索進行超參訓練,代碼如下:


# Build model
import sklearn
from sklearn.cross_validation import train_test_split
from sklearn.grid_search import GridSearchCV
from sklearn.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)


實驗效果如下:


AI 遷移學習怎麼玩?手把手教你實戰遷移學習


圖 9. SVM分類器的準確率


從上圖可以看出:

- 模型的準確度約為0.86,與之前解決方案的準確性相差不大;

- 過擬合明顯:訓練準確率始終為 1.0。

- 模型的準確率應該應隨著訓練樣本的數量而增加。但是,上圖看起來並不會這樣。你可以改一下代碼試試,增加數據後,模型的準確率是否會增加。

七、總結

在本文中:

- 介紹了遷移學習、卷積神經網絡和預訓練模型等一些基本概念。

- 定義了基本的微調策略,以如何利用預訓練模型等流程。

- 描述了一種結構化方法,根據數據集的大小和相似性來決定應該使用哪種微調策略。

- 引入了三種不同的分類器。

- 對比三種不同的分類器,並給出了端到端的代碼示例。




八、參考文獻

1. Bengio, Y., 2009. Learning deep architectures for AI. Foundations and trends in Machine Learning, 2(1), pp.1–127.

2. Canziani, A., Paszke, A. and Culurciello, E., 2016. An analysis of deep neural network models for practical applications. arXiv preprint arXiv:1605.07678.

3. Chollet, F., 2015. Keras.

4. Chollet, F., 2017. Deep learning with python. Manning Publications Co..

5. Deng, J., Dong, W., Socher, R., Li, L.J., Li, K. and Fei-Fei, L., 2009, June. Imagenet: A large-scale hierarchical image database. In Computer Vision and Pattern Recognition, 2009. CVPR 2009. IEEE Conference on (pp. 248–255). Ieee.

6. He, K., Zhang, X., Ren, S. and Sun, J., 2016. Deep residual learning for image recognition. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 770–778).

7. Krizhevsky, A., Sutskever, I. and Hinton, G.E., 2012. Imagenet classification with deep convolutional neural networks. In Advances in neural information processing systems (pp. 1097–1105).

8. LeCun, Y., Bengio, Y. and Hinton, G., 2015. Deep learning. nature, 521(7553), p.436.

9. Lin, M., Chen, Q. and Yan, S., 2013. Network in network. arXiv preprint arXiv:1312.4400.

10. Pan, S.J. and Yang, Q., 2010. A survey on transfer learning. IEEE Transactions on knowledge and data engineering, 22(10), pp.1345–1359.

11. Rawat, W. and Wang, Z., 2017. Deep convolutional neural networks for image classification: A comprehensive review. Neural computation, 29(9), pp.2352–2449.

12. Simonyan, K. and Zisserman, A., 2014. Very deep convolutional networks for large-scale image recognition. arXiv preprint arXiv:1409.1556.

13. Szegedy, C., Vanhoucke, V., Ioffe, S., Shlens, J. and Wojna, Z., 2016. Rethinking the inception architecture for computer vision. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 2818–2826).

14. Tang, Y., 2013. Deep learning using linear support vector machines. arXiv preprint arXiv:1306.0239.

15. Voulodimos, A., Doulamis, N., Doulamis, A. and Protopapadakis, E., 2018. Deep learning for computer vision: A brief review. Computational intelligence and neuroscience, 2018.

16. Yosinski, J., Clune, J., Bengio, Y. and Lipson, H., 2014. How transferable are features in deep neural networks?. In Advances in neural information processing systems (pp. 3320–3328).

17. Zeiler, M.D. and Fergus, R., 2014, September. Visualizing and understanding convolutional networks. In European conference on computer vision (pp. 818–833). Springer, Cham.


本文由本頭條號獨家編譯,未經許可,不得轉載。


分享到:


相關文章: