使用TensorFlow和Keras,輕松搭建并訓(xùn)練你的第一個(gè)神經(jīng)網(wǎng)絡(luò)
AI技術(shù)發(fā)展迅猛,利用各種先進(jìn)的AI模型,可以打造聊天機(jī)器人、仿人機(jī)器人、自動(dòng)駕駛汽車等。AI已經(jīng)成為發(fā)展最快的技術(shù),而對(duì)象檢測(cè)和物體分類是最近的趨勢(shì)。
本文將介紹使用卷積神經(jīng)網(wǎng)絡(luò)從頭開(kāi)始構(gòu)建和訓(xùn)練一個(gè)圖像分類模型的完整步驟。本文將使用公開(kāi)的Cifar-10數(shù)據(jù)集來(lái)訓(xùn)練這個(gè)模型。這個(gè)數(shù)據(jù)集是獨(dú)一無(wú)二的,因?yàn)樗讼衿?、飛機(jī)、狗、貓等日常所見(jiàn)物體的圖像。通過(guò)對(duì)這些物體進(jìn)行神經(jīng)網(wǎng)絡(luò)訓(xùn)練,本文將開(kāi)發(fā)出智能系統(tǒng)來(lái)對(duì)現(xiàn)實(shí)世界中的這些東西進(jìn)行分類。它包含了6萬(wàn)多張32x32大小的10種不同類型的物體圖像。在本教程結(jié)束時(shí),你將擁有一個(gè)可以根據(jù)物體的視覺(jué)特征來(lái)判斷對(duì)象的模型。
圖1 數(shù)據(jù)集樣本圖像|圖片來(lái)自datasets.activeloop
本文將從頭開(kāi)始講述所有內(nèi)容,所以如果你還沒(méi)有學(xué)習(xí)過(guò)神經(jīng)網(wǎng)絡(luò)的實(shí)際實(shí)現(xiàn),也完全沒(méi)問(wèn)題。
以下是本教程的完整工作流程:
- 導(dǎo)入必要的庫(kù)
- 加載數(shù)據(jù)
- 數(shù)據(jù)的預(yù)處理
- 建立模型
- 評(píng)估模型的性能
圖2 完整的流程
導(dǎo)入必要的庫(kù)
首先必須安裝一些模塊才能開(kāi)始這個(gè)項(xiàng)目。本文將使用Google Colab,因?yàn)樗峁┟赓M(fèi)的GPU訓(xùn)練。
以下是安裝所需庫(kù)的命令:
$ pip install tensorflow, numpy, keras, sklearn, matplotlib
將庫(kù)導(dǎo)入到Python文件中。
from numpy import *
from pandas import *
import matplotlib.pyplot as plotter
# 將數(shù)據(jù)分成訓(xùn)練集和測(cè)試集。
from sklearn.model_selection import train_test_split
# 用來(lái)評(píng)估我們的訓(xùn)練模型的庫(kù)。
from sklearn.metrics import classification_report, confusion_matrix
import keras
# 加載我們的數(shù)據(jù)集。
from keras.datasets import cifar10
# 用于數(shù)據(jù)增量。
from keras.preprocessing.image import ImageDataGenerator
# 下面是一些用于訓(xùn)練卷積Nueral網(wǎng)絡(luò)的層。
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation
from keras.layers import Conv2D, MaxPooling2D, GlobalMaxPooling2D, Flatten
- Numpy:它用于對(duì)包含圖像的大型數(shù)據(jù)集進(jìn)行高效的數(shù)組計(jì)算。
- Tensorflow:它是一個(gè)由谷歌開(kāi)發(fā)的開(kāi)源機(jī)器學(xué)習(xí)庫(kù)。它提供了許多函數(shù)來(lái)建立大型和可擴(kuò)展的模型。
- Keras:另一個(gè)在TensorFlow之上運(yùn)行的高級(jí)神經(jīng)網(wǎng)絡(luò)API。
- Matplotlib:這個(gè)Python庫(kù)可以創(chuàng)建圖表,提供更好的數(shù)據(jù)可視化。
- Sklearn:它提供了對(duì)數(shù)據(jù)集執(zhí)行數(shù)據(jù)預(yù)處理和特征提取任務(wù)的功能。它包含內(nèi)置的函數(shù),可以找到模型的評(píng)估指標(biāo),如準(zhǔn)確率、精確度、誤報(bào)、漏報(bào)等。
現(xiàn)在,進(jìn)入數(shù)據(jù)加載的步驟。
加載數(shù)據(jù)
本節(jié)將加載數(shù)據(jù)集并執(zhí)行訓(xùn)練-測(cè)試數(shù)據(jù)的拆分。
加載和拆分?jǐn)?shù)據(jù):
# 類的數(shù)量
nc = 10
(training_data, training_label), (testing_data, testing_label) = cifar10.load_data()
(
(training_data),
(validation_data),
(training_label),
(validation_label),
) = train_test_split(training_data, training_label, test_size=0.2, random_state=42)
training_data = training_data.astype("float32")
testing_data = testing_data.astype("float32")
validation_data = validation_data.astype("float32")
cifar10數(shù)據(jù)集是直接從Keras數(shù)據(jù)集庫(kù)中加載的。并且這些數(shù)據(jù)也分為訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)。訓(xùn)練數(shù)據(jù)用于訓(xùn)練模型,以便它可以識(shí)別其中的模式。而測(cè)試數(shù)據(jù)對(duì)模型來(lái)說(shuō)是不可見(jiàn)的,它被用來(lái)檢查其性能,即相對(duì)于總的數(shù)據(jù)點(diǎn),有多少數(shù)據(jù)點(diǎn)被正確預(yù)測(cè)。
training_label包含了與training_data中的圖像對(duì)應(yīng)的標(biāo)簽。
然后使用內(nèi)置sklearn的train_test_split函數(shù)將訓(xùn)練數(shù)據(jù)再次拆分成驗(yàn)證數(shù)據(jù)。驗(yàn)證數(shù)據(jù)用于選擇和調(diào)整最終的模型。最后,所有的訓(xùn)練、測(cè)試和驗(yàn)證數(shù)據(jù)都轉(zhuǎn)換為32位的浮點(diǎn)數(shù)。
現(xiàn)在,數(shù)據(jù)集的加載已經(jīng)完成。在下一節(jié)中,本文將對(duì)其執(zhí)行一些預(yù)處理步驟。
數(shù)據(jù)的預(yù)處理
數(shù)據(jù)預(yù)處理是開(kāi)發(fā)機(jī)器學(xué)習(xí)模型時(shí)的第一步,也是最關(guān)鍵的一步。跟隨本文一起看看如何做到這一點(diǎn)。
# 歸一化
training_data /= 255
testing_data /= 255
validation_data /= 255
# 熱編碼
training_label = keras.utils.to_categorical(training_label, nc)
testing_label = keras.utils.to_categorical(testing_label, nc)
validation_label = keras.utils.to_categorical(validation_label, nc)
# 輸出數(shù)據(jù)集
print("Training: ", training_data.shape, len(training_label))
print("Validation: ", validation_data.shape, len(validation_label))
print("Testing: ", testing_data.shape, len(testing_label))
輸出:
Training: (40000, 32, 32, 3) 40000
Validation: (10000, 32, 32, 3) 10000
Testing: (10000, 32, 32, 3) 10000
該數(shù)據(jù)集包含10個(gè)類別的圖像,每個(gè)圖像的大小為32x32像素。每個(gè)像素都有一個(gè)0-255的值,我們需要在0-1之間對(duì)其進(jìn)行歸一化以簡(jiǎn)化計(jì)算過(guò)程。之后,我們將把分類標(biāo)簽轉(zhuǎn)換為單熱編碼標(biāo)簽。這樣做是為了將分類數(shù)據(jù)轉(zhuǎn)換為數(shù)值數(shù)據(jù),這樣我們就可以毫無(wú)問(wèn)題地應(yīng)用機(jī)器學(xué)習(xí)算法。
現(xiàn)在,進(jìn)入CNN模型的構(gòu)建。
建立CNN模型
CNN模型分三個(gè)階段工作。第一階段由卷積層組成,從圖像中提取相關(guān)特征。第二階段由池化層組成,用于降低圖像的尺寸。它也有助于減少模型的過(guò)度擬合。第三階段由密集層組成,將二維圖像轉(zhuǎn)換為一維數(shù)組。最后,這個(gè)數(shù)組被送入全連接層,進(jìn)行最后的預(yù)測(cè)。
以下是代碼:
model = Sequential()
model.add(
Conv2D(32, (3, 3), padding="same", activatinotallow="relu", input_shape=(32, 32, 3))
)
model.add(Conv2D(32, (3, 3), padding="same", activatinotallow="relu"))
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(64, (3, 3), padding="same", activatinotallow="relu"))
model.add(Conv2D(64, (3, 3), padding="same", activatinotallow="relu"))
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(96, (3, 3), padding="same", activatinotallow="relu"))
model.add(Conv2D(96, (3, 3), padding="same", activatinotallow="relu"))
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dropout(0.4))
model.add(Dense(256, activatinotallow="relu"))
model.add(Dropout(0.4))
model.add(Dense(128, activatinotallow="relu"))
model.add(Dropout(0.4))
model.add(Dense(nc, activatinotallow="softmax"))
本文應(yīng)用了三組圖層,每組包含兩個(gè)卷積層、一個(gè)最大池化層和一個(gè)丟棄層。Conv2D層接收input_shape為(32,32,3),必須與圖像的尺寸相同。
每個(gè)Conv2D層還需要一個(gè)激活函數(shù),即relu。激活函數(shù)是用于增加系統(tǒng)中的非線性。更簡(jiǎn)單地說(shuō),它決定神經(jīng)元是否需要根據(jù)某個(gè)閾值被激活。有許多類型的激活函數(shù),如ReLu、Tanh、Sigmoid、Softmax等,它們使用不同的算法來(lái)決定神經(jīng)元的激發(fā)。
之后,添加了平坦層和全連接層,在它們之間還有幾個(gè)Dropout層。Dropout層隨機(jī)地拒絕一些神經(jīng)元對(duì)網(wǎng)層的貢獻(xiàn)。它里面的參數(shù)定義了拒絕的程度。它主要用于避免過(guò)度擬合。
下面是一個(gè)CNN模型架構(gòu)的示例圖像。
圖3 Sampe CNN架構(gòu)|圖片來(lái)源:Researchgate
編譯模型
現(xiàn)在,本文將編譯和準(zhǔn)備訓(xùn)練的模型。
# 啟動(dòng)Adam優(yōu)化器
opt = keras.optimizers.Adam(lr=0.0001)
model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"])
# 獲得模型的摘要
model.summary()
輸出:
圖4 模型摘要
本文使用了學(xué)習(xí)率為0.0001的Adam優(yōu)化器。優(yōu)化器決定了模型的行為如何響應(yīng)損失函數(shù)的輸出而變化。學(xué)習(xí)率是訓(xùn)練期間更新權(quán)重的數(shù)量或步長(zhǎng)。它是一個(gè)可配置的超參數(shù),不能太小或太大。
擬合模型
現(xiàn)在,本文將把模型擬合到我們的訓(xùn)練數(shù)據(jù),并開(kāi)始訓(xùn)練過(guò)程。但在此之前,本文將使用圖像增強(qiáng)技術(shù)來(lái)增加樣本圖像的數(shù)量。
卷積神經(jīng)網(wǎng)絡(luò)中使用的圖像增強(qiáng)技術(shù)將增加訓(xùn)練圖像,而不需要新的圖像。它將通過(guò)在圖像中產(chǎn)生一定量的變化來(lái)復(fù)制圖像。它可以通過(guò)將圖像旋轉(zhuǎn)到一定程度、添加噪聲、水平或垂直翻轉(zhuǎn)等方式來(lái)實(shí)現(xiàn)。
augmentor = ImageDataGenerator(
width_shift_range=0.4,
height_shift_range=0.4,
horizontal_flip=False,
vertical_flip=True,
)
# 在augmentor中進(jìn)行擬合
augmentor.fit(training_data)
# 獲得歷史數(shù)據(jù)
history = model.fit(
augmentor.flow(training_data, training_label, batch_size=32),
epochs=100,
validation_data=(validation_data, validation_label),
)
輸出:
圖5 每個(gè)時(shí)期的準(zhǔn)確度和損失
ImageDataGenerator()函數(shù)用于創(chuàng)建增強(qiáng)的圖像。fit()用于擬合模型。它以訓(xùn)練和驗(yàn)證數(shù)據(jù)、Batch Size和Epochs的數(shù)量作為輸入。
Batch Size是在模型更新之前處理的樣本數(shù)量。一個(gè)關(guān)鍵的超參數(shù)必須大于等于1且小于等于樣本數(shù)。通常情況下,32或64被認(rèn)為是最好的Batch Size。
Epochs的數(shù)量代表了所有樣本在網(wǎng)絡(luò)的前向和后向都被單獨(dú)處理了多少次。100個(gè)epochs意味著整個(gè)數(shù)據(jù)集通過(guò)模型100次,模型本身運(yùn)行100次。
我們的模型已經(jīng)訓(xùn)練完畢,現(xiàn)在我們將評(píng)估它在測(cè)試集上的表現(xiàn)。
評(píng)估模型性能
本節(jié)將在測(cè)試集上檢查模型的準(zhǔn)確性和損失。此外,本文還將繪制訓(xùn)練和驗(yàn)證數(shù)據(jù)的準(zhǔn)確率與時(shí)間之間和損失與時(shí)間之間的關(guān)系圖。
model.evaluate(testing_data, testing_label)
輸出:
313/313 [==============================] - 2s 5ms/step - loss: 0.8554 - accuracy: 0.7545
[0.8554493188858032, 0.7545000195503235]
本文的模型達(dá)到了75.34%的準(zhǔn)確率,損失為0.8554。這個(gè)準(zhǔn)確率還可以提高,因?yàn)檫@不是一個(gè)最先進(jìn)的模型。本文用這個(gè)模型來(lái)解釋建立模型的過(guò)程和流程。CNN模型的準(zhǔn)確性取決于許多因素,如層的選擇、超參數(shù)的選擇、使用的數(shù)據(jù)集的類型等。
現(xiàn)在我們將繪制曲線來(lái)檢查模型中的過(guò)度擬合情況。
def acc_loss_curves(result, epochs):
acc = result.history["accuracy"]
# 獲得損失和準(zhǔn)確性
loss = result.history["loss"]
# 聲明損失和準(zhǔn)確度的值
val_acc = result.history["val_accuracy"]
val_loss = result.history["val_loss"]
# 繪制圖表
plotter.figure(figsize=(15, 5))
plotter.subplot(121)
plotter.plot(range(1, epochs), acc[1:], label="Train_acc")
plotter.plot(range(1, epochs), val_acc[1:], label="Val_acc")
# 給予繪圖的標(biāo)題
plotter.title("Accuracy over " + str(epochs) + " Epochs", size=15)
plotter.legend()
plotter.grid(True)
# 傳遞值122
plotter.subplot(122)
# 使用訓(xùn)練損失
plotter.plot(range(1, epochs), loss[1:], label="Train_loss")
plotter.plot(range(1, epochs), val_loss[1:], label="Val_loss")
# 使用 ephocs
plotter.title("Loss over " + str(epochs) + " Epochs", size=15)
plotter.legend()
# 傳遞真值
plotter.grid(True)
# 打印圖表
plotter.show()
acc_loss_curves(history, 100)
輸出:
圖6 準(zhǔn)確度和損失與歷時(shí)的關(guān)系
在本文的模型中,可以看到模型過(guò)度擬合測(cè)試數(shù)據(jù)集。(藍(lán)色)線表示訓(xùn)練精度,(橙色)線表示驗(yàn)證精度。訓(xùn)練精度持續(xù)提高,但驗(yàn)證誤差在20個(gè)歷時(shí)后惡化。
總結(jié)
本文展示了構(gòu)建和訓(xùn)練卷積神經(jīng)網(wǎng)絡(luò)的整個(gè)過(guò)程。最終得到了大約75%的準(zhǔn)確率。你可以使用超參數(shù)并使用不同的卷積層和池化層來(lái)提高準(zhǔn)確性。你也可以嘗試遷移學(xué)習(xí),它使用預(yù)先訓(xùn)練好的模型,如ResNet或VGGNet,并在某些情況下可以提供非常好的準(zhǔn)確性。