您現在的位置是:首頁 > 垂釣
想正確識別貓狗?理論+例項+程式碼一文搞定
- 由 讀芯術 發表于 垂釣
- 2021-11-02
貓狗分類精度最高多少
深度學習正迅速成為人工智慧應用的關鍵工具。例如,在計算機視覺、自然語言處理和語音識別等領域,深度學習已經取得顯著的成果。因此,人們對深度學習的興趣也越來越濃厚。
深度學習中最突出的問題之一是影象分類。影象分類的目的是根據潛在的類別對特定的影象進行分類。影象分類的一個經典示例是在一組影象中識別貓和狗。
從深度學習的角度來看,影象分類問題可以透過遷移學習來解決。實際上,影象分類中幾項最新的研究成果都是基於遷移學習方案得出的。
本文將介紹如何在影象分類問題中實施遷移學習解決方案。本文提供的實施方案基於使用Python程式語言的Keras(Chollet 2015)。透過實施此方案,你將能夠快速輕鬆地解決任何影象分類問題。
目錄
1。 遷移學習
2。 卷積神經網路
3。 再利用預訓練模型
4。 遷移學習過程
5。 深度卷積神經網路上的分類器
6。 例項
7。 總結
1. 遷移學習
遷移學習是計算機視覺中一種備受歡迎的學習方法,透過遷移學習,我們可以在省時的基礎上建立準確的模型。進行遷移學習時,你可以從解決不同問題時遇到的已知的模式開始學習,而不需要從頭開始。這樣你可以利用以前學到的知識,避免從頭開始。遷移學習可視作沙特爾所說的“站在巨人的肩膀上”的深度學習版本。
在計算機視覺中,遷移學習通常透過使用預訓練模型來實現。預訓練模型指在大型基準資料集上進行問題解決訓練的模型,而用於訓練的問題與我們實際想要解決的問題類似。由於訓練這類模型的計算量與計算成本低,更常見的應用是在已出版的文獻中匯入和使用這類模型。
2. 卷積神經網路
用於遷移學習的幾種預訓練模型都是基於大型卷積神經網路(CNN)的。總的來說,CNN在各種計算機視覺任務中表現出色。其具備的高效能且易於訓練是近年來CNN流行的兩個主要因素。
一個典型的CNN 由兩部分組成:
1。 卷積基底,由一堆卷積層和池化層組成。卷積基的主要目的是從影象中生成特徵。
2。 分類器,通常由全連線層組成。分類器的主要作用是根據檢測到的特徵對影象進行分類。全連線層指神經元與前一層中的所有啟用完全連線的層。
圖1是基於CNN的模型結構。注意,這是一個簡化的版本,它符合本文的目的。實際中,這類模型結構比我們這裡列舉的更復雜。
圖1 基於CNN的模型結構
這些深度學習模型的一個重要特徵是它們可以自動學習分層特徵表示。這意味著由第一層計算的特徵是通用的,並且可以在不同的問題域中再次使用,而由最後一層計算的特徵是特定的,並且取決於所選擇的資料集和任務。有專家認為:“如果第一層特徵是通用的,而最後一層特徵是特定的,那麼在網路的某個地方必須要從一般特徵過渡到特定特徵。”
因此,CNN的卷積基,尤其是其較低的層(更接近輸入的層),指的是一般特徵;而CNN的分類器部分和卷積基中的一些較高層則指特殊特徵。
3. 再利用預訓練模型
當你根據自己的需要再利用預訓練模型時,你首先要刪除原始的分類器,然後新增符合你需要的新分類器,最後必須根據以下三種策略中的一種對模型進行微調:
1。 訓練整個模型。在這種情況下,你將用到預訓練模型的體系結構,並根據你的資料集對其進行訓練。由於你將從頭開始學習模型,因此你需要一個大資料集(以及大量的計算能力)。
2。 訓練一部分層,其他層保持凍結狀態。如前面所說,較低層指的是一般特性(與問題無關),而較高層指的是特定特性(與問題有關)。這裡,我們透過需要調整的網路權重(凍結層在訓練期間不會改變)來實現這種二分法。通常,如果你有一個小的資料集和大量的引數,你需要留下更多的凍結層以避免過度擬合。相反,如果資料集很大,引數的數量很小,你可以對更多的層進行新任務訓練來改進模型,因為這種情況下不會出現過度擬合。
3。 凍結卷積基。這種情況與訓練權衡或凍結權衡的極端情況相對應。其主要思想是使卷積基保持其原始形式,然後將其輸出提供給分類器。當你將預訓練模型作為固定的特徵提取機制時,如果你缺乏計算能力、資料集很小,和/或預訓練模型解決了你想要解決的問題,那麼此方法將很有用。
圖2是這三種策略的流程圖:
圖2 微調策略
不同於操作直接的策略3,使用策略1和策略2時,你需要注意卷積部分中使用的學習率。學習率是一個超引數,它控制你對網路權重的調整程度。當你使用基於CNN的預訓練模型時,最好保持較低學習率,因為高學習率會增加失去之前獲取的知識的風險。假設預訓練模型經過了良好的訓練(這樣假設是完全合理的),保持低學習率將確保你不會過早和過量地扭曲CNN權重。
4.遷移學習過程
從實際角度來看,整個遷移過程可歸納如下:
1。 選擇一個預訓練模型。你可以從各種可用的預訓練模型中選擇一種適合你的模型。例如,如果你使用的是Keras,那麼你可以立即訪問如VGG、InceptionV3 , 以及 ResNet5 這類模型。
2。 根據大小-相似性矩陣對問題進行分類。圖3中的“矩陣”影響著你的選擇。這個矩陣根據資料集的大小以及與訓練預訓練模型時所用的的資料集的相似性對計算機視覺問題進行分類。根據經驗法則,如果一個數據集的每個類中影象少於1000個,則認為該資料集很小。而資料集的相似性可以根據常識判斷出。例如,如果你的任務是識別貓和狗,則ImageNet是一個相似的資料集,因為它能識別貓和狗的影象。然而,如果你的任務是識別癌細胞,則ImageNet不能被視為相似的資料集。
3。 微調模型。在這裡,你可以使用大小-相似性矩陣來輔助選擇,然後參考前面提到的關於再利用預訓練模型的三種選擇。圖4是以下文字的影象總結。
象限
1:大資料集,但與預訓練模型的資料集不同。在這種情況下,需要採取策略1。由於你有一個大的資料集,所以可以從頭開始訓練模型,並執行你想要的任何操作。儘管資料集有所不同,但在實踐中,透過使用體系結構和權重,初始化一個預訓練模型的方法仍然是有用的。
象限2
:大資料集並類似於預訓練模型的資料集。這時候任何選項都適用,但很有可能最有效的選擇是策略2。由於資料集很大,因而不會出現過度擬合,所以我們想學多少就學多少。然而,由於資料集是相似的,我們可以透過利用以前的知識省去大量的工作。因此,我們只需要訓練分類器和卷積基頂層就足夠了。
象限3
:小資料集並不同於預訓練模型的資料集。這類資料集必將造成計算機視覺問題。一切都對你不利。此時抱怨沒有用,唯一的希望就是選擇策略2。你很難平衡需要訓練和凍結的層數。如果你研究得過於深入,你的模型可能會發生過擬合現象,如果你停留在模型的淺層,你又學不到任何有用的東西。也許,與象限2相比,你需要更進一步,並且需要運用資料增強技術。
象限4
:小型資料集,但類似於預訓練模型的資料集。當我問尤達大師對此的看法時,他告訴我:“對於這類資料集,策略3是最好的選擇”。我雖不瞭解他,但我不會低估他的“原力”。因此,我選擇策略3。你只需要移除最後一個全連線層(輸出層),將預訓練模型作為固定的特徵提取器執行,然後使用先前所得的特徵來訓練新的分類器。
圖3和圖4 大小-相似性矩陣(左)和用於微調預訓練模型的決策圖(右)
5.深度卷積神經網路上的分類器
如前所述,基於預訓練卷積神經網路的遷移學習方法產生的影象分類模型通常由兩部分組成:
1。 卷積基,用於執行特徵提取。
2。 分類器,根據卷積基提取的特徵對輸入影象進行分類。
由於在本節中我們主要關注分類器部分,所以首先我們必須說明構建分類器的方法有多種,其中最常用的是:
1。 全連線層。處理影象分類問題,最佳方法是使用一堆全連線層,然後再用Softmax啟用層。Softmax層輸出在每個可能的類標籤上的機率分佈,然後我們只需要根據最可能的類對影象進行分類。
2。 全域性平均池化。有人提出了一種基於全域性平均池化的方法。使用這種方法,我們不需要在卷積基上新增全連線層,而是新增全域性平均池層並將其輸出直接饋送到softmax啟用層。
3。 線性支援向量機。線性支援向量機(SVM)是另一種可用於構建分類器的方法。 我們可以透過訓練線性SVM分類器對卷積基提取的特徵進行分類來提高分類精度。本文也會進一步詳細介紹SVM方法的優缺點。
6.例項
在本例中,我們將探究如何將每個分類器應用於影象分類的遷移學習解決方案中。“在深度卷積神經網路上比較不同分類器的效能仍然需要進一步研究,從而形成一個有趣的研究方向”。因此,觀察每個分類器在標準影象分類問題中的表現將會很有趣。
6。1。準備資料
在本例中,我們將使用一個較小版本的原始資料集。這將利於我們更快地執行模型,且對於計算能力有限的人(比如我)來說也是一件好事。
為了構建較小版本的資料集,我們可以對以前的程式碼進行改編,如程式碼1所示:
# 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)))
程式碼1 給貓狗大戰構建較小資料集
6。2。從卷積基中提取特徵
卷積基將用於提取特徵。這些特徵將被輸入到我們想要訓練的分類器中,以便我們識別影象中是否有狗或貓。
同樣,我們再次對程式碼進行改編。詳情請看程式碼2:
# 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)
程式碼2 卷積基中提取的特徵
6。3。分類器
6.3.1.全連線層
我們提出的第一個解決方案基於全連線層。該分類器中添加了一堆全連線層,這些層由從卷積基礎中提取的特徵提給。
為了簡單(和快速),我們將使用Chollet(2018)提出的解決方案,並對其稍加修改。
程式碼3是此方案使用的程式碼,圖5和圖6是其學習曲線。
# 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))
程式碼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
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))
程式碼4 全域性平均池化解決方案
圖7 全域性平均池化解決方案的精確性
圖8 全域性平均池化解決方案的損失函式
對結果的簡要概述:
1。 驗證精度與由全連線層解決方案得到的驗證精度相似。
2。 此模型不發生之前出現的過度擬合現象。
3。 當模型停止訓練時,損失函式仍然在減小。也許可以透過增加epoch的數量來改進模型。
6.3.3.線性支援向量機
這裡,我們將根據卷積基提取的特徵訓練一個線性支援向量機(SVM)分類器。
對於此分類器的訓練,傳統的機器學習方法是首選。因此,我們將使用K-折交叉驗證法來估計分類器的誤差。使用K-折交叉驗證法時,我們可以將訓練集和驗證集連線起來,以擴充套件訓練資料(與前面的情況一樣,測試集保持不動)。程式碼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
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)
程式碼6 線性SVM解決方案
圖9 線性SVM解決方案的精確性
對結果的簡要概述:
1。 模型的精度為0。86左右,與之前的解決方案相近。
2。 角落處產生過度擬合。此外,訓練精度總是1。0,這不常見,可視為過度擬合的標誌。
3。 模型的精度應隨著訓練樣本數量的增加而增加。然而,事實並非如此,這可能是因為過度擬合。當資料集增加時,模型將如何反應呢?這是一個有趣的問題。
7. 總結
本文中,我們:
1。 介紹了遷移學習、卷積神經網路和預訓練模型的概念。
2。 定義了重新調整預訓練模型的基本微調策略。
3。 描述了基於資料集的大小和相似性來決定應該使用哪種微調策略的結構化方法。
4。 列舉了三種可用於從卷積基提取的特徵頂部的分類器。
5。 為本文列舉的三個分類器分別提供端到端的分類示例圖。
編譯組:王玲、韋振琛
相關連結:
https://www。kdnuggets。com/2018/12/solve-image-classification-problem-quickly-easily。html
如需轉載,請後臺留言,遵守轉載規範