文本翻譯自The Keras Blog。翻譯的目的主要是放慢本身學習的速度,也許對其餘人也會有幫助。如下爲譯文。html
在這個教程中,咱們會展現一些簡單但有效的方法,使你能夠用不多的訓練樣本——每一個待識別的類別只有幾百張到幾千張圖片——來構建強大的圖片分類模型。python
咱們會使用以下的方法:git
這會讓咱們涉及如下的Keras 特性:github
fit_generator
使用Python數據生成器來訓練Keras模型ImageDataGenerator
實時的圖像加強注意:全部的示例都在2017年3月14日更新到了 Keras 2.0 API。運行這些代碼須要Keras版本號大於等於2.0.0。算法
咱們從以下設定開始:api
data/
train/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
validation/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
複製代碼
爲了獲取幾百到幾千張你感興趣的分類的照片,你能夠經過 Flickr API下載特定標籤的圖片,它的協議比較友好。數組
在咱們的例子中,使用兩類圖片,它們來自 Kaggle:1000只貓和1000條狗(儘管原數據集有12,500只貓和12,500條狗,咱們只使用每類最靠前的1000張)。再每一個類別取400張圖片做爲驗證集來評估模型。bash
這就只有不多的圖片可供學習了,對一個圖片分類問題而言這很不容易。這是一個有挑戰的機器學習問題,但也很符合現實狀況:在現實世界中,只獲取小規模的數據集也是很昂貴的甚至是幾乎不可能的(好比醫療學習)。從有限的數據得到最大的產出是一個合格的數據科學家的關鍵技能。網絡
這個問題有多難呢?在Kaggle 開始貓狗分類競賽(一共25,000張圖片)兩年多後,出現了以下的評論:機器學習
在一個多年前的非正式調查中,計算機圖像專家認爲當前技術若是沒有的的巨大進步,一個準確率高於60%的分類器都幾乎是不可能的。做爲參考,60%準確率的分類器將12-image HIP的猜想機率從1/4096提升到1/459。而如今的材料認爲在這個任務上機器分類的正確率能夠高達80%。
在競賽結果中,頂級選手使用現代的深度學習技術,準確率能夠高達98%。在咱們的例子中,咱們限制只用8%的數據集,問題會更難。
我常常聽見這種說法「深度學習只有在你有龐大的數據時纔有用」。雖然不是全錯,但這種說法必定程度上是有誤導做用的。固然,深度學習有從數據中自動學習特徵的能力,一般只有在可用的數據量很大時纔有可能——特別是當輸入樣本的維度很高時,好比圖片。然而,卷積神經網絡網絡——深度學習的重要算法之一——是針對大多數感知問題(好比圖片分類)的最佳模型,即便只有不多的數據可供學習也是如此。在一個小的圖片數據集上從頭訓練一個卷積神經網絡仍然能夠得到不錯的結果,並且不須要手動設計特徵。卷積神經網絡足夠好了,它們是解決這類問題的正確工具。
可是更進一步,深度學習模型自然地適合更改應用場景:正如你將在本文中看到的,你能夠將一個在大型數據集上訓練的圖片分類或語音識別模型,稍作修改後應用到徹底不一樣的問題上。尤爲是在計算機圖形領域,不少預訓練的模型(一般在ImageNet 數據集上)是公開可下載的,你可使用它們在不多的數據上構建強大的圖像模型。
爲了更好的利用有限的樣本,你須要使用一系列的隨機變換來加強圖片,這樣模型就不會再重複使用徹底相同的圖片。這能夠防止過擬合,模型能更好的泛化。
在Keras 中這能夠經過keras.preprocessing.image.ImageDataGenerator
類來實現。這個類提供以下功能:
.flow(data, labels)
或flow_from_directory(directory)
來實例化批量加強圖片的生成器。這些生成器隨便可以做爲fit_generator
,``evaluate_generator和
predict_generator`等Keras 模型方法的輸入。示例以下:
from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
複製代碼
這裏只是一部分可配置項(更多的請參考文檔。咱們看一下上述可配置項的含義:
rotation_range
是0~180的度數值,表示圖片旋轉的範圍width_shift
和height_shift
是寬度、長度的百分比,表明圖片平移的範圍rescale
表示在其它處理以前對圖片的乘數。咱們的原始圖片是由0~255之間的RGB值構成的,這種值模型處理起來太大了(在典型的學習率下),因此須要經過縮放因子1/255把值縮放到0~1之間shear_range
表示隨機應用錯切zoom_range
表示圖片內的隨機縮放horizontal_flip
表示隨機地水平翻轉圖片——當沒有水平不對稱的假設時(例如,真實世界的圖片)。fill_mode
表示在旋轉或水平/垂直平移後,對新像素的填充策略如今咱們使用這個工具在臨時文件夾中生成一些圖片,直觀感覺一下這些加強策略的效果,爲了圖片可顯示,這裏不使用數值縮放因子。
from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
img = load_img('data/train/cats/cat.0.jpg') #這是一個 PIL 圖片
x = img_to_array(img) # 這是一個 Numpy 數組,形狀是 (3, 150, 150)
x = x.reshape((1,) + x.shape) # 這是一個 Numpy 數組,形狀是 (1, 3, 150, 150)
# 下面的 .flow() 命令生成一批隨機變換的圖片
# 而後保存到 `preview/` 目錄
i = 0
for batch in datagen.flow(x, batch_size=1,
save_to_dir='preview', save_prefix='cat', save_format='jpeg'):
i += 1
if i > 20:
break # 不然生成器會一直循環下去
複製代碼
下面就是數據加強策略獲得的圖片。
卷積網絡是解決圖片分類問題的合適工具,咱們先嚐試訓練一個做爲基準線。因爲樣本不多,因此首先關注的問題是過擬合。過擬合發生於模型見過的樣本太少,而不能泛化到新的數據,也就是說,模型開始使用一些不相關的特徵來作預測。好比一我的只見過3張伐木工人的圖片,和3張水手的圖片,這些圖片中只有一個伐木工人是戴帽子的,你可能會想戴帽子是伐木工人的特徵而不是水手的,這樣就會是一個糟糕的伐木工人/水手分類器。
數據加強是克服過擬合的方法之一,可是僅僅數據加強還不夠,由於加強的樣本之間是高度相關的。克服過擬合的關鍵是模型的熵容量——模型能夠存儲多少信息。模型能存儲的信息越多,就有更大的潛力作到精確,它能利用更多的特徵,可是有存儲不相關特徵的風險。而模型只能存儲不多的信息時,則能聚焦到數據中找到的顯著特徵,這些特徵更多是真正問題相關的,能更好的泛化。
在這個例子中,咱們使用一個只有少數幾層、少數幾個過濾器的小卷積網絡,同時使用數據加強和dropout。Dropout 也能減輕過擬合,它能夠防止一個層兩次看到一樣的模式,所以扮演了相似數據加強的角色(能夠理解爲數據加強和dropout都能破環數據中的隨機相關性)。
下面的代碼是咱們的第一個模型,簡單的3層卷積網絡,卷積使用ReLU激活器,每層卷積緊接一個最大池化層。這和LeCun 在1990年代發表的圖像分類模型結構很類似,除了當時沒有使用ReLU激活器。
本實驗的完整代碼能夠在這裏找到。
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(3, 150, 150)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
#目前爲止模型的輸出維度 (height, width, features)
複製代碼
在此之上,使用兩個全鏈接層。最後一層是單個單元,使用sigmoid 激活器,它很是適合二分類問題,相應的使用binary_crossentropy
來訓練模型。
model.add(Flatten()) # 3維數據特徵轉化爲1維
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='rmsprop',
metrics=['accuracy'])
複製代碼
如今開始準備數據,使用.flow_from_directory()
根據jpgs圖片和相應的目錄,生成批量的圖片數據和相應的標籤。
batch_size = 16
# 用於訓練的圖片加強配置
train_datagen = ImageDataGenerator(
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True)
# 用於測試的圖片加強配置:只設定rescale
test_datagen = ImageDataGenerator(rescale=1./255)
# 生成器從'data/train' 文件夾中讀取數據, 能夠無限生成加強的圖片數據
train_generator = train_datagen.flow_from_directory(
'data/train', # 目標文件夾
target_size=(150, 150), # 全部圖片尺寸重設爲 150x150
batch_size=batch_size,
class_mode='binary') # binary_crossentropy 損失函數須要二值標籤
# 一個相似的生成器,用於驗證集數據
validation_generator = test_datagen.flow_from_directory(
'data/validation',
target_size=(150, 150),
batch_size=batch_size,
class_mode='binary')
複製代碼
如今可使用生成器來訓練模型了。每一個輪次訓練在GPU上須要20~30s,在CPU上須要300~400s。因此若是不是很着急,在CPU上運行是可行的。
model.fit_generator(
train_generator,
steps_per_epoch=2000 // batch_size,
epochs=50,
validation_data=validation_generator,
validation_steps=800 // batch_size)
model.save_weights('first_try.h5') # 記得在訓練後和訓練中適時保存模型
複製代碼
這種方法在50輪次訓練後,驗證集準確率約爲0.79~0.81(50次是隨意設定的——由於模型很小,又用了比較強的dropout,不太容易會過擬合)。若是在Kaggle 剛發佈這個競賽的時候,咱們的結果已是最好的——並且只用了8%的數據,也沒有優化網絡結構和超參數。實際上在Kaggle 的競賽中這個模型能排進前100(一共215個競賽者)。因此估計至少有115個競賽者沒有用深度學習。
更優雅的方法是利用在大數據集上的預訓練網絡。這些網絡已經學到了在不少計算機圖像問題上都有用的特徵,相比那些只依賴問題自己可得到的數據的模型,利用這些特徵能夠達到更高的準確率。
咱們使用VGG16 模型,它是在ImageNet 數據集上預訓練的——本博客以前討論過。因爲ImageNet 的1000個分類中包括幾個「貓」分類(波斯貓、暹羅貓等)和不少「狗」分類,這個模型已經學到不少和咱們的分類問題相關的特徵。實際上,僅僅須要記錄數據通過模型後的softmax 預測值就能夠很好的解決貓狗分類問題,都不須要使用bottleneck 特徵。不過爲了讓方法能有更普遍的適應性,包括那些在ImageNet 中沒有出現的分類,咱們仍是使用bottleneck 特徵。
下面是VGG16 的結構:
咱們的策略以下:只使用這個模型的卷積部分,即全鏈接以前的全部層。而後在咱們的數據上運行這個模型,使用兩個numpy 數組記錄全部的輸出(即VGG16的bottleneck 特徵:全鏈接層以前的最後一個激活層)。而後在這些記錄的基礎上,訓練一個小的全鏈接網絡。
咱們之因此離線記錄這些特徵,而不是直接在凍結的卷積層之上增長全鏈接層訓練,是由於這樣計算更高效。運行VGG16 模型很費時,尤爲是使用CPU時,並且咱們只但願計算一次。不過注意這樣就不能用數據加強了。
能夠在這裏找到完整的代碼,從Github獲取預訓練權重。這裏再也不討論模型怎樣構造和加載——在不少Keras 的示例中已經討論過了。不過須要看看如何使用圖片生成器記錄bottleneck 特徵:
batch_size = 16
generator = datagen.flow_from_directory(
'data/train',
target_size=(150, 150),
batch_size=batch_size,
class_mode=None, # 這個生成器不產生標籤
shuffle=False) # 保持數據順序,先是1000只貓,而後是1000條狗
# 給定一個生成器yields批量的numpy數據,predict_generator會返回模型的輸出
bottleneck_features_train = model.predict_generator(generator, 2000)
# 保存這些輸出到 numpy數組
np.save(open('bottleneck_features_train.npy', 'w'), bottleneck_features_train)
generator = datagen.flow_from_directory(
'data/validation',
target_size=(150, 150),
batch_size=batch_size,
class_mode=None,
shuffle=False)
bottleneck_features_validation = model.predict_generator(generator, 800)
np.save(open('bottleneck_features_validation.npy', 'w'), bottleneck_features_validation)
複製代碼
而後使用保存的數據訓練一個小的全鏈接網絡:
train_data = np.load(open('bottleneck_features_train.npy'))
# 因爲特徵是按貓狗的順序產生的,全部構造標籤很簡單
train_labels = np.array([0] * 1000 + [1] * 1000)
validation_data = np.load(open('bottleneck_features_validation.npy'))
validation_labels = np.array([0] * 400 + [1] * 400)
model = Sequential()
model.add(Flatten(input_shape=train_data.shape[1:]))
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
model.fit(train_data, train_labels,
epochs=50,
batch_size=batch_size,
validation_data=(validation_data, validation_labels))
model.save_weights('bottleneck_fc_model.h5')
複製代碼
因爲網絡很小,因此在CPU上訓練也很快(每秒1個輪次):
Train on 2000 samples, validate on 800 samples
Epoch 1/50
2000/2000 [==============================] - 1s - loss: 0.8932 - acc: 0.7345 - val_loss: 0.2664 - val_acc: 0.8862
Epoch 2/50
2000/2000 [==============================] - 1s - loss: 0.3556 - acc: 0.8460 - val_loss: 0.4704 - val_acc: 0.7725
...
Epoch 47/50
2000/2000 [==============================] - 1s - loss: 0.0063 - acc: 0.9990 - val_loss: 0.8230 - val_acc: 0.9125
Epoch 48/50
2000/2000 [==============================] - 1s - loss: 0.0144 - acc: 0.9960 - val_loss: 0.8204 - val_acc: 0.9075
Epoch 49/50
2000/2000 [==============================] - 1s - loss: 0.0102 - acc: 0.9960 - val_loss: 0.8334 - val_acc: 0.9038
Epoch 50/50
2000/2000 [==============================] - 1s - loss: 0.0040 - acc: 0.9985 - val_loss: 0.8556 - val_acc: 0.9075
複製代碼
準確率達到0.90~0.91,至關不錯了。這是由於基礎模型已經在標記過貓狗的樣本數據上訓練過了。
爲了繼續改進前面的結果,咱們能夠微調VGG16模型最後的卷積模塊以及分類器。微調意味着從訓練過的網絡開始,在新的數據集上使用很小的權重更新來從新訓練,本例能夠分爲3步:
注意:
能夠在這裏得到完整的試驗代碼。
初始化VGG16 網絡加載權重後,增長咱們前面訓練的全鏈接層:
# 在卷積層上增長分類器
top_model = Sequential()
top_model.add(Flatten(input_shape=model.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(1, activation='sigmoid'))
# 注意爲了微調訓練,全鏈接分類器也應該訓練過
# classifier, including the top classifier,
# in order to successfully do fine-tuning
top_model.load_weights(top_model_weights_path)
# 添加到卷積層之上
model.add(top_model)
複製代碼
最後開始使用很小的學習率,微調模型:
batch_size = 16
# 準備數據加強
train_datagen = ImageDataGenerator(
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
train_data_dir,
target_size=(img_height, img_width),
batch_size=batch_size,
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_data_dir,
target_size=(img_height, img_width),
batch_size=batch_size,
class_mode='binary')
# 微調模型
model.fit_generator(
train_generator,
steps_per_epoch=nb_train_samples // batch_size,
epochs=epochs,
validation_data=validation_generator,
validation_steps=nb_validation_samples // batch_size)
複製代碼
50輪次訓練後,本方法能夠到達0.94的準確率。很大的進步!
用下面的方法能夠嘗試爭取準確率達到0.95:
本文到此結束!回顧一下,各部分試驗的代碼以下:
若是關於這篇文章討論的話題你有任何評論或建議能夠到Twitter。