從零開始用Tensorflow構(gòu)建CNN
計算機(jī)視覺是當(dāng)今的熱門話題之一。它為涉及圖像的不同問題提供了穩(wěn)健的解決方案。在本文中,我們將使用Tensorflow從頭開始創(chuàng)建一個卷積神經(jīng)網(wǎng)絡(luò)(CNN)。然后,我們將使用它對貓和狗的圖像進(jìn)行分類,這是最流行的圖像分類數(shù)據(jù)集之一。
本文旨在幫助該領(lǐng)域的新手,尤其是Tensorflow,學(xué)習(xí)獲取數(shù)據(jù)、處理數(shù)據(jù)、建立模型、訓(xùn)練數(shù)據(jù)并最終使用數(shù)據(jù)進(jìn)行預(yù)測的基本原理。

什么是分類?
我們必須回答的第一個問題是什么是分類。我會盡可能簡短地介紹這些原則,并提供其他有價值的文章,更深入地介紹它們。
分類是為給定的輸入數(shù)據(jù)分配標(biāo)簽的過程。例如,我們可以對電子郵件是否為垃圾郵件進(jìn)行分類。在這種情況下,對于給定的圖像,我們將預(yù)測圖像是貓還是狗。
為了更詳細(xì)地解釋什么是分類以及最流行的算法,我建議通過中培IT學(xué)院機(jī)器學(xué)習(xí)、深度學(xué)習(xí)、計算機(jī)圖像處理及知識圖譜應(yīng)用與核心技術(shù)實(shí)戰(zhàn)課程進(jìn)行系統(tǒng)學(xué)習(xí)。
卷積神經(jīng)網(wǎng)絡(luò)(CNN)
以下關(guān)鍵概念是CNN(或ConvNet)。CNN是一類神經(jīng)網(wǎng)絡(luò),它將圖像作為輸入,對其應(yīng)用一系列操作并返回輸出。該輸出可以是概率、圖像類別的預(yù)測或另一個新圖像。這取決于網(wǎng)絡(luò)架構(gòu)和我們試圖解決的問題。
在這種情況下,我們將使用一個簡單的CNN來對狗或貓類中的輸入圖像進(jìn)行分類。它將返回給定圖像是狗或貓的概率。
為了更好地理解這種神經(jīng)網(wǎng)絡(luò)在圖像中執(zhí)行的操作,亦可參考中培IT學(xué)院機(jī)器學(xué)習(xí)、深度學(xué)習(xí)、計算機(jī)圖像處理及知識圖譜應(yīng)用與核心技術(shù)實(shí)戰(zhàn)課程。
建立我們的網(wǎng)絡(luò)
是時候開始工作并構(gòu)建我們的分類器了。正如我們之前所說,我們將使用Tensorflow和Keras,使用簡單的CNN架構(gòu)來制作貓/狗分類器。
加載數(shù)據(jù)集
我們將使用牛津IIIT寵物數(shù)據(jù)集,其中包含7000多張貓和狗的圖像。該數(shù)據(jù)集具有CC-BY-SA 4.0許可證,這意味著我們可以將數(shù)據(jù)共享和調(diào)整用于任何目的。
首先,我們將需要以下庫。
import pandas as pd
import glob
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
from keras.preprocessing.image import ImageDataGenerator
接下來,我們將加載數(shù)據(jù)集。你可以從Kaggle下載它,并將文件夾復(fù)制到你的工作目錄中。此外,您可以直接從數(shù)據(jù)集的網(wǎng)站獲取數(shù)據(jù)。
一旦我們有了數(shù)據(jù)集,它將包含一個名為“images”的文件夾,里面有所有寵物圖像。
每個圖像都將在文件名中包含其標(biāo)簽。正如我們在數(shù)據(jù)集的信息中看到的,數(shù)據(jù)被劃分為不同的品種。

有12種貓和25種狗。我們將遵循的策略是使用所有的狗品種作為狗的圖像,并使用所有的貓品種作為貓的圖像。我們不會考慮品種,因?yàn)槲覀兿虢⒁粋€二進(jìn)制分類器。
讓我們看看我們有多少數(shù)據(jù)。、
print('There are {} images in the dataset'.format(len(glob.glob('images/*.jpg'))))
正如我們所看到的,總共有7390張圖片?,F(xiàn)在是時候擺脫這些品種了。我們將創(chuàng)建兩個列表,一個包含所有狗的照片,另一個包含貓的所有圖像。
CATS = ['Abyssinian', 'Bengal', 'Birman', 'Bombay', 'British_Shorthair', 'Egyptian_Mau', 'Maine_Coon', 'Persian', 'Ragdoll', 'Russian_Blue', 'Siamese', 'Sphynx']
cats_images = []
dogs_images = []
for img in glob.glob('images/*.jpg'):
if any(cat in img for cat in CATS):
cats_images.append(img)
else:
dogs_images.append(img)
現(xiàn)在我們可以看看我們有多少動物的圖像。
print('There are {} images of cats'.format(len(cats_images)))
print('There are {} images of dogs'.format(len(dogs_images)))
該數(shù)據(jù)集有2400只貓和4990只狗。
數(shù)據(jù)分區(qū)
對于我們的網(wǎng)絡(luò),我們希望使用三個不同的集合:
訓(xùn)練:用于訓(xùn)練模型的圖像。
驗(yàn)證:在訓(xùn)練過程中測試模型的圖像。
測試:在模型完成訓(xùn)練后,對其進(jìn)行測試的圖像。
我們將使用70%的圖像進(jìn)行訓(xùn)練,10%用于驗(yàn)證,剩下的20%用于測試。
由于我們只有兩個圖像列表(狗和貓),我們需要將這些列表劃分為不同的集合。我們將用熊貓來做到這一點(diǎn)。
首先,我們對數(shù)據(jù)進(jìn)行混洗,并將其分為三組,其對應(yīng)的數(shù)據(jù)分布為70/10/20。
#shuffle the lists
np.random.shuffle(cats_images)
np.random.shuffle(dogs_images)
#split the data into train, validation and test sets
train_d, val_d, test_d = np.split(dogs_images, [int(len(dogs_images)*0.7), int(len(dogs_images)*0.8)])
train_c, val_c, test_c = np.split(cats_images, [int(len(cats_images)*0.7), int(len(cats_images)*0.8)])
接下來,我們必須使用panda創(chuàng)建數(shù)據(jù)幀。
train_dog_df = pd.DataFrame({'image':train_d, 'label':'dog'})
val_dog_df = pd.DataFrame({'image':val_d, 'label':'dog'})
test_dog_df = pd.DataFrame({'image':test_d, 'label':'dog'})
train_cat_df = pd.DataFrame({'image':train_c, 'label':'cat'})
val_cat_df = pd.DataFrame({'image':val_c, 'label':'cat'})
test_cat_df = pd.DataFrame({'image':test_c, 'label':'cat'})
它們將只有兩列,一列帶有圖像名稱,另一列帶有標(biāo)簽“cat”或“dog”。
最后,我們可以連接數(shù)據(jù)幀。
train_df = pd.concat([train_dog_df, train_cat_df])
val_df = pd.concat([val_dog_df, val_cat_df])
test_df = pd.concat([test_dog_df, test_cat_df])
print('There are {} images for training'.format(len(train_df)))
print('There are {} images for validation'.format(len(val_df)))
print('There are {} images for testing'.format(len(test_df)))
我們已經(jīng)從數(shù)據(jù)集中完美的創(chuàng)建了三個分區(qū)。
數(shù)據(jù)預(yù)處理
以下步驟是對所有圖像進(jìn)行預(yù)處理。我們希望它們具有相同的維度(因?yàn)镃NN需要具有相同維度的所有輸入數(shù)據(jù)),并且它們的像素被歸一化。這樣做的原因是為了更快地訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò)模型。
我們將使用Keras中的ImageDataGenerator類。
BATCH_SIZE = 32
IMG_HEIGHT = 224
IMG_WIDTH = 224
#create the ImageDataGenerator object and rescale the images
trainGenerator = ImageDataGenerator(rescale=1./255.)
valGenerator = ImageDataGenerator(rescale=1./255.)
testGenerator = ImageDataGenerator(rescale=1./255.)
#convert them into a dataset
trainDataset = trainGenerator.flow_from_dataframe(
dataframe=train_df,
class_mode="binary",
x_col="image",
y_col="label",
batch_size=BATCH_SIZE,
seed=42,
shuffle=True,
target_size=(IMG_HEIGHT,IMG_WIDTH) #set the height and width of the images
)
valDataset = valGenerator.flow_from_dataframe(
dataframe=val_df,
class_mode='binary',
x_col="image",
y_col="label",
batch_size=BATCH_SIZE,
seed=42,
shuffle=True,
target_size=(IMG_HEIGHT,IMG_WIDTH)
)
testDataset = testGenerator.flow_from_dataframe(
dataframe=test_df,
class_mode='binary',
x_col="image",
y_col="label",
batch_size=BATCH_SIZE,
seed=42,
shuffle=True,
target_size=(IMG_HEIGHT,IMG_WIDTH)
)
我們剛剛創(chuàng)建了3個新的數(shù)據(jù)集,每個數(shù)據(jù)集都有經(jīng)過預(yù)處理的圖像。此外,我們應(yīng)用shuffle屬性來隨機(jī)重組圖像的順序,并使用batch_size將圖像連接到32個元素的組中。這意味著我們將一次向CNN提供32張圖片。每一步中的圖像越少,模型學(xué)習(xí)得越好,但完成訓(xùn)練過程需要更長的時間。這是一個我們可以用來檢查性能如何隨它而變化的參數(shù)。
可視化
如果我們想檢查數(shù)據(jù)集的結(jié)果,我們可以進(jìn)行以下編譯。
images, labels = next(iter(testDataset))
print('Batch shape: ', images.shape)
print('Label shape: ', labels.shape)
我們有32張帶有相關(guān)標(biāo)簽的圖片。此外,我們可以繪制這些圖像(例如,第四個圖像)。
plt.imshow(images[3])
print('Label: ', labels[3])

標(biāo)簽為1表示狗,0表示貓。
模型
既然我們已經(jīng)準(zhǔn)備好了所有的數(shù)據(jù),現(xiàn)在是時候構(gòu)建模型了。
model = keras.Sequential([
keras.layers.InputLayer(input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
keras.layers.Conv2D(64, (3, 3), activation='relu'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(128, (3, 3), activation='relu'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(256, (3, 3), activation='relu'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(512, (3, 3), activation='relu'),
keras.layers.GlobalAveragePooling2D(),
keras.layers.Dense(1, activation='sigmoid')
])
這是一個簡單的CNN模型,具有四個卷積層和一個由具有1個輸出神經(jīng)元的密集層組成的輸出層。
讓我們進(jìn)入訓(xùn)練階段。我們需要編譯并擬合模型。我們將使用二進(jìn)制交叉熵作為損失函數(shù),因?yàn)槲覀兪褂玫氖菐в姓麛?shù)標(biāo)簽的類(0表示cat,1表示dog)。優(yōu)化器將是adam算法,我們將使用準(zhǔn)確性作為過程的度量。
我們將對網(wǎng)絡(luò)進(jìn)行15代的培訓(xùn)。
epochs=15
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(trainDataset, epochs=epochs, validation_data=(valDataset))
現(xiàn)在我們已經(jīng)訓(xùn)練了我們的第一個CNN!我們可以在訓(xùn)練過程中查看網(wǎng)絡(luò)的準(zhǔn)確性和損失值。
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend(['Training', 'Validation'])
plt.show()

最后一步是評估這個模型。為此,我們將使用測試數(shù)據(jù)集。
loss, acc = model.evaluate(testDataset)
print('Loss:', loss)
print('Accuracy:', acc)
該模型的準(zhǔn)確率為80%。
你已經(jīng)訓(xùn)練出了你的第一個卷積神經(jīng)網(wǎng)絡(luò),祝賀你!現(xiàn)在我們已經(jīng)訓(xùn)練了我們的新模型,我們可以用它來預(yù)測貓和狗的真實(shí)圖像。這是我們可以使用我們的模型進(jìn)行預(yù)測的部分。這些圖像以前在任何過程階段都不應(yīng)該被看到。
預(yù)測
預(yù)測階段是與現(xiàn)實(shí)世界問題最相似的場景,一旦模型經(jīng)過訓(xùn)練,它就必須對看不見的數(shù)據(jù)進(jìn)行分類。
在我們的例子中,我們將為下面的圖像預(yù)測類。

對于人類來說,這顯然是一只可愛的貓的形象,但讓我們看看我們的模型對此有什么看法。
def preprocess(image):
img_resize = tf.image.resize(image, [IMG_HEIGHT, IMG_WIDTH])
img_norm = img_resize / 255
return img_norm
img = plt.imread('pexels-cat-predict.jpg')
img = tf.reshape(img, (-1, IMG_HEIGHT, IMG_HEIGHT, 3))
img = preprocess(img)
model.predict(img)
在這種情況下,我們必須將圖像重塑為網(wǎng)絡(luò)的輸入格式,并將其標(biāo)準(zhǔn)化,就像我們對所有數(shù)據(jù)集的圖像所做的那樣。
然后我們把它交給模型,輸出為0.08。該輸出可以在0到1之間變化,因?yàn)榫W(wǎng)絡(luò)的輸出層具有s形激活函數(shù)。由于我們的類對于貓是0,對于狗是1,所以我們的模型同意這個圖像是貓的。
本文介紹了一個簡單的方法,從零開始CNN。它的表現(xiàn)不錯,但仍有很大的改進(jìn)空間。在這里,我向大家推薦中培IT學(xué)院機(jī)器學(xué)習(xí)、深度學(xué)習(xí)、計算機(jī)圖像處理及知識圖譜應(yīng)用與核心技術(shù)實(shí)戰(zhàn)課程,課程涉及了深度學(xué)習(xí)、機(jī)器學(xué)習(xí)、神經(jīng)網(wǎng)絡(luò)、圖像識別、知識圖譜等人工智能領(lǐng)域的核心技術(shù)知識,將會給學(xué)員帶來較大收獲。
