如果你不喜歡小貓和小狗,你可能不知道他們具體是哪一種品種,但是一般來(lái)說(shuō),你都能區(qū)分出這是貓還是狗,貓和狗的特征還是不一樣的,那我們?nèi)绾斡脵C(jī)器學(xué)習(xí)的方法訓(xùn)練一個(gè)網(wǎng)絡(luò)區(qū)分貓狗呢?
我們選用的是 Kaggle 的一個(gè)數(shù)據(jù)集(https://www.kaggle.com/c/dogs-vs-cats/data),用神經(jīng)網(wǎng)絡(luò)的方法進(jìn)行模型的訓(xùn)練。下載下來(lái)的數(shù)據(jù)集對(duì)于我們測(cè)試來(lái)說(shuō)數(shù)據(jù)有點(diǎn)大,這里面分別有 12500 個(gè)貓和狗的訓(xùn)練圖片,我們先來(lái)縮小一下訓(xùn)練集,然后再進(jìn)行模型的搭建和訓(xùn)練。我們的做法做法是貓和狗分別選擇 1000 個(gè)訓(xùn)練圖片,500 個(gè)驗(yàn)證集和 500 個(gè)測(cè)試集,我們可以手工完成這個(gè)工作,需要做的就是:
// 如下非可執(zhí)行代碼,含義非常清楚的表達(dá),最后會(huì)附上可執(zhí)行代碼
mkdir dog-vs-cats-small
cp dog-vs-cats/train/cat/pic-{0-999}.jpg dog-vs-cats-small/train/cat/
cp dog-vs-cats/train/dog/pic-{0-999}.jpg dog-vs-cats-small/train/dog/
cp dog-vs-cats/validation/cat/pic-{1000-1499}.jpg dog-vs-cats-small/validation/cat/
cp dog-vs-cats/validation/dog/pic-{1000-1499}.jpg dog-vs-cats-small/validation/dog/
cp dog-vs-cats/test/cat/pic-{1500-1999}.jpg dog-vs-cats-small/test/cat/
cp dog-vs-cats/test/dog/pic-{1500-1999}.jpg dog-vs-cats-small/test/dog/
從我們前面文章的經(jīng)驗(yàn)中,我們可以知道,這個(gè)卷積神經(jīng)網(wǎng)絡(luò)我們可以用 relu 激活的 Conv2D 層與 MaxPooling2D 層堆疊而成,與之前相比稍微需要修改就是網(wǎng)絡(luò)的大小,更大的網(wǎng)絡(luò)處理更多是數(shù)據(jù)。
卷積神經(jīng)網(wǎng)絡(luò)網(wǎng)絡(luò)的深度往往與特征圖的尺寸負(fù)相關(guān),越深的網(wǎng)絡(luò)每個(gè)特征圖的尺寸往往是越小的,我看到的數(shù)據(jù)往往是:深度 32-> 128,特征圖尺寸 150x150 -> 7x7。如下,這是我們構(gòu)架的網(wǎng)絡(luò):
優(yōu)化器依舊采用 RMSprop,學(xué)習(xí)率由默認(rèn)的 0.001 設(shè)置為 0.0001,后續(xù)我們也將 對(duì)不同的優(yōu)化器進(jìn)行介紹。由于需要輸出的結(jié)果是“貓 or 狗”,所以我們最后一層激活參數(shù)為 sigmoid,自然損失函數(shù)就為 binary_crossentropy 了,如此一來(lái),網(wǎng)絡(luò)就構(gòu)建好了,接下來(lái)就應(yīng)該喂給網(wǎng)絡(luò)數(shù)據(jù)了。
由于我們這里是一張又一張的圖片,jpg 格式,這可不是我們網(wǎng)絡(luò)所喜歡的格式,需要進(jìn)行處理,讀出圖片,將其解碼為 RGB 像素,再將 RGB 中的像素值轉(zhuǎn)換成浮點(diǎn)數(shù)進(jìn)行計(jì)算,又由于我們的網(wǎng)絡(luò)對(duì)于處理 0-1 之間的數(shù)效果更好,因此我們需要將像素值轉(zhuǎn)換區(qū)間,即從 0-255 轉(zhuǎn)換到 0-1,是不是覺(jué)得有點(diǎn)麻煩,確實(shí)!Keras 之所以說(shuō)是最容易上手的深度學(xué)習(xí)框架,就是因?yàn)樗瑯影堰@些繁瑣但是使用的工具內(nèi)置了,Image 包下的 ImageDataGenerator 就可以幫上大忙,這樣我們就可以得到 RGB 圖像與二進(jìn)制標(biāo)簽組成的批量。
接下來(lái),我們就要對(duì)數(shù)據(jù)進(jìn)行擬合了,fit_generator,上面的生成器也將傳給它,這樣,這一個(gè)網(wǎng)絡(luò)我們就建立完成了,可以進(jìn)行訓(xùn)練了,與前文一樣,我們?nèi)匀划?huà)出損失曲線和精度曲線。
訓(xùn)練精度逐漸接近百分之百,提醒我們注意過(guò)擬合的危險(xiǎn);訓(xùn)練精度在第五次(或六次)次后就維持在 70%左右不再上升了。
第五次或第十次后,驗(yàn)證損失就達(dá)到了最小值,嗯……,很顯然,過(guò)擬合了,我們需要降低過(guò)擬合。
出現(xiàn)過(guò)擬合的原因是學(xué)習(xí)樣本太少了,我們采用 **數(shù)據(jù)增強(qiáng) **來(lái)解決這個(gè)問(wèn)題。我們的做法就是在現(xiàn)有的訓(xùn)練數(shù)據(jù)中生成更多的訓(xùn)練數(shù)據(jù),就是增加一些隨機(jī)變換,這種隨機(jī)變化生成的圖片依然要保證是有效的。這樣模型在訓(xùn)練的時(shí)候就可以看到不同的更多的圖像了,這就使得訓(xùn)練出的模型泛化能力更好。怎么做呢,就可以把圖片進(jìn)行隨機(jī)的旋轉(zhuǎn),縮放,平移和翻轉(zhuǎn)等,ImageDataGenerator 提供了這樣的能力。同時(shí)在密集層之前添加一個(gè) Dropout 層,會(huì)更好的降低過(guò)擬合,如此一來(lái),看看結(jié)果:
可以看出來(lái),效果好了很多。訓(xùn)練精度至少可以到達(dá) 80%,再想大幅度提高精度,就需要一些其他的方法了,下一篇文章我們?cè)倭摹?/p>
老規(guī)矩,附上全部代碼:
#!/usr/bin/env python3
import os
import shutil
import time
import matplotlib.pyplot as plt
from keras import layers
from keras import models
from keras import optimizers
from keras.preprocessing.image import ImageDataGenerator
def make_small():
original_dataset_dir = '/Users/renyuzhuo/Desktop/cat/dogs-vs-cats/train'
base_dir = '/Users/renyuzhuo/Desktop/cat/dogs-vs-cats-small'
os.mkdir(base_dir)
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
def cat():
base_dir = '/Users/renyuzhuo/Desktop/cat/dogs-vs-cats-small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.summary()
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])
# train_datagen = ImageDataGenerator(rescale=1. / 255)
train_datagen = ImageDataGenerator(
rescale=1. / 255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
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_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=100,
validation_data=validation_generator,
validation_steps=50)
model.save('cats_and_dogs_small_2.h5')
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.show()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
if __name__ == "__main__":
time_start = time.time()
# make_small()
cat()
time_end = time.time()
print('Time Used: ', time_end - time_start)
首發(fā)自公眾號(hào):RAIS