前言
- 🍨 本文為🔗365天深度學習訓練營 中的學習紀錄博客
- 🍖 原作者:K同学啊 | 接輔導、項目定制
一、我的環境
-
電腦系統:Windows 10
-
顯卡:NVIDIA Quadro P620
-
語言環境:Python 3.7.0
-
開發工具:Sublime Text,Command Line(CMD)
-
深度學習環境:Tensorflow 2.5.0
二、準備套件
# 提供一些與操作系統交互的功能,例如文件路徑操作等
import os
# 用於圖像處理,例如打開、操作、保存圖像文件
import PIL
# 用於處理文件路徑的模塊,提供一種更加直觀和面向對象的操作文件路徑方式
import pathlib
# 用於繪圖,可以創建各種類型的圖表和圖形
import matplotlib.pyplot as plt
# 數值計算庫,用於處理大型多維數組和矩陣的
import numpy as np
# 開源的機器學習框架
import tensorflow as tf
# 導入 keras 模塊,為 tensorflow 的高級 API 之一,操作起來更加簡單、易用
from tensorflow import keras
# layers模組包含了各種類型的神經網絡層
# models模組包含了用於定義神經網絡模型的類
# Input類用於定義模型的輸入
from tensorflow.keras import layers, models, Input
# 用於定義自定義的神經網絡模型
from tensorflow.keras.models import Model
# 導入Keras API中的一些常用神經網絡層
# 包括卷積層(Conv2D)、池化層(MaxPooling2D)、全連接層(Dense)、展平層(Flatten)、失活層(Dropout)
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout
# 供了一個名為 tqdm 的進度條,可以在迭代過程中顯示進度,讓用戶了解運行的進度
# 它是一個很常用的進度條庫,對於長時間運行的程式非常有用
from tqdm import tqdm
# 將 Keras 的後端函數庫引入為 K
# Keras 的後端函數庫提供了一系列與計算圖和張量操作相關的功能,
# 例如張量的數學運算、梯度計算等。通常,我們可以通過 K. 來訪問這些函數和類
import tensorflow.keras.backend as K
三、設定GPU
# 列出系統中的GPU裝置列表
gpus = tf.config.list_physical_devices("GPU")
# 如果有GPU
if gpus:
# 挑選第一個 GPU
gpu0 = gpus[0]
# 僅在需要的時候分配記憶體
tf.config.experimental.set_memory_growth(gpu0, True)
# 將 GPU0 設置為 TensorFlow 中可見的唯一 GPU ,將運算限制在特定的 GPU 上
tf.config.set_visible_devices([gpu0],"GPU")
四、載入資料
# 設定數據目錄的相對路徑,也可以使用絕對路徑
# D:/AI/ai_note/T8,這邊要注意斜線的方向
data_dir = "T8/"
# 將路徑轉換成 pathlib.Path 對象,更易操作
data_dir = pathlib.Path(data_dir)
# 使用 glob 方法獲取指定目錄下所有以 '.png' 為副檔名的文件迭代器
# '*/*.png 是一個通配符模式,表示所有直接位於子目錄中的以 .png 結尾的文件
# 第一個星號表示所有目錄
# 第二個星號表示所有檔名
image_count = len(list(data_dir.glob('*/*.jpg')))
# 印出圖片數量
print("圖片總數:",image_count)
# 開張圖來看看
dog = list(data_dir.glob('dog/*.jpg'))
img = PIL.Image.open(str(dog[0]))
plt.imshow(img)
# 關閉座標軸,即不顯示座標軸
plt.axis('off')
# 顯示圖片
plt.show()
五、數據預處理
# 設置批量大小,即每次訓練模型時輸入到模型中的圖像數量
# 在每次訓練跌代時,模型將同時處理8張圖像
# 批量大小的選擇會影響訓練速度和內存需求
batch_size = 8
# 圖像的高度,在加載圖像數據時,將所有的圖像調整為相同的高度,這裡設定為 224 像素
img_height = 224
# 圖像的寬度,在加載圖像數據時,將所有的圖像調整為相同的寬度,這裡設定為 224 像素
img_width = 224
# 創建訓練集
# 使用 tf.keras.preprocessing.image_dataset_from_directory 函數從目錄中創建一個圖像數據集
# 會得到一個 tf.data.Dataset 對象,其中包含指定目錄中圖像的數據集,以及相應的標籤
# 返回的這個數據集可以直接用於模型的訓練和驗證
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
# 表示包含圖像資料集的目錄的路徑
data_dir,
# 指定要從資料集中分離出作為驗證集的部分比例
# 例如,如果設置為 0.2,則將使用資料集的 20% 作為驗證集,而其餘 80% 將用於訓練
validation_split=0.2,
# 設置為 "training",表示創建訓練子集
subset="training",
# 設置隨機種子以便能夠複現結果
seed=12,
# 表示輸入圖像的大小,通常以像素為單位
image_size=(img_height, img_width),
# 表示每個批次的樣本數量
batch_size=batch_size)
# 創建驗證集
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset="validation",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
# class_names 是一個包含數據集中所有類別名稱的列表
class_names = train_ds.class_names
# 印出類別
print(class_names)
六、檢查數據
# 從 train_ds 中迭代獲取一個批次(batch)的圖像數據和標籤數據,印出他們的形狀
for image_batch, labels_batch in train_ds:
print('圖像數據的形狀:', image_batch.shape)
print('標籤數據的形狀:', labels_batch.shape)
break # 只印一批次(batch)即可
七、數據預處理
# 設置一個參數 AUTOTUNE,它的值是 TensorFlow 中的一個特殊常量,用於自動調整處理數據的程式碼以優化性能
AUTOTUNE = tf.data.AUTOTUNE
# 用於對圖像和其對應的標籤進行預處理
def preprocess_image(image,label):
# 將圖像數據進行歸一化處理,將像素值範圍從 0 到 255 轉換為 0 到 1 之間的浮點數。然後將處理後的圖像和原始的標籤返回
return (image/255.0,label)
# 將訓練數據集 train_ds 中的每個圖像和標籤應用 preprocess_image 函數進行預處理
# num_parallel_calls 參數告訴 TensorFlow 在處理數據時可以同時進行的並行處理數量
train_ds = train_ds.map(preprocess_image, num_parallel_calls=AUTOTUNE)
# 對驗證數據集 val_ds 進行相同的預處理
val_ds = val_ds.map(preprocess_image, num_parallel_calls=AUTOTUNE)
# 對訓練數據集進行緩存、洗牌和提前加載
# cache() 函數將數據緩存到記憶體中以提高訓練效率,
# shuffle(1000) 函數將數據洗牌以增加隨機性,
# prefetch(buffer_size=AUTOTUNE) 函數則是在訓練時提前加載數據以減少等待時間
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
# 對驗證數據集進行相同的緩存和提前加載操作
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
八、可視化數據
# 創建了一個新的圖形窗口,並設置了其大小為 15x10
plt.figure(figsize=(15, 10))
# train_ds.take(1) 從訓練集中取出一個批次(batch)的數據
# 遍歷訓練數據集的第一個批次(batch)中的每張圖片
# images 是一個包含圖片數據的張量
# labels 是一個包含對應標籤的張量
for images, labels in train_ds.take(1):
# 遍歷了這個批次中的前 8 張圖片
for i in range(8):
# 創建了一個子圖,將圖片放在一個 5x8 的網格中的適當位置
# i + 1 是因為子圖的索引是從 1 開始的,而不是從 0 開始的
ax = plt.subplot(5, 8, i + 1)
# 顯示了第 i 張圖片,images[i] 包含了這個批次中的第 i 張圖片的數據
plt.imshow(images[i])
# 設置了圖片的標題,標題是該圖片對應的標籤,labels[i] 是第 i 張圖片的標籤,class_names 是一個列表或數組,包含了所有可能的標籤名稱
plt.title(class_names[labels[i]])
# 關閉了坐標軸,這樣圖片就不會顯示坐標軸了
plt.axis("off")
# 顯示圖片
plt.show()
八、建構 VGG-16
# 自建 VGG-16
# nb_classes 表示输出类别的数量
# input_shape 表示输入图像的形状
def VGG16(nb_classes, input_shape):
input_tensor = Input(shape=input_shape)
# 第一個卷積塊
# 創建一個卷積層
# 64:表示該卷積層使用了 64 個卷積核
# (3, 3):指定了每個卷積核的大小為 3x3
# 使用ReLU(Rectified Linear Unit)作為激活函數,它是一種常用的非線性函數,能夠使模型具有更好的學習能力
# padding='same' 表示使用零填充,以便輸入和輸出的尺寸相同
# name='block1_conv1',用於給該層命名以便識別
# 輸入 input_tensor,表示輸入圖像數據
x = Conv2D(64, (3,3), activation='relu', padding='same',name='block1_conv1')(input_tensor)
# 創建和上一層一樣的卷積層
x = Conv2D(64, (3,3), activation='relu', padding='same',name='block1_conv2')(x)
# 創建一個最大池化層
# 池化窗口大小是 (2,2),池化步幅也是 (2,2)
# 作用是對輸入特徵圖進行降採樣,減少特徵圖的尺寸,同時保留最重要的特徵
x = MaxPooling2D((2,2), strides=(2,2), name = 'block1_pool')(x)
# 第二個卷積塊
# 層內容同上,把內容調整成 128 個卷積核
x = Conv2D(128, (3,3), activation='relu', padding='same',name='block2_conv1')(x)
x = Conv2D(128, (3,3), activation='relu', padding='same',name='block2_conv2')(x)
x = MaxPooling2D((2,2), strides=(2,2), name = 'block2_pool')(x)
# 第三個卷積塊
# 層內容同上,把內容調整成 256 個卷積核
x = Conv2D(256, (3,3), activation='relu', padding='same',name='block3_conv1')(x)
x = Conv2D(256, (3,3), activation='relu', padding='same',name='block3_conv2')(x)
x = Conv2D(256, (3,3), activation='relu', padding='same',name='block3_conv3')(x)
x = MaxPooling2D((2,2), strides=(2,2), name = 'block3_pool')(x)
# 第四個卷積塊
# 層內容同上,把內容調整成 512 個卷積核
x = Conv2D(512, (3,3), activation='relu', padding='same',name='block4_conv1')(x)
x = Conv2D(512, (3,3), activation='relu', padding='same',name='block4_conv2')(x)
x = Conv2D(512, (3,3), activation='relu', padding='same',name='block4_conv3')(x)
x = MaxPooling2D((2,2), strides=(2,2), name = 'block4_pool')(x)
# 第五個卷積塊
# 層內容同上,把內容調整成 512 個卷積核
x = Conv2D(512, (3,3), activation='relu', padding='same',name='block5_conv1')(x)
x = Conv2D(512, (3,3), activation='relu', padding='same',name='block5_conv2')(x)
x = Conv2D(512, (3,3), activation='relu', padding='same',name='block5_conv3')(x)
x = MaxPooling2D((2,2), strides=(2,2), name = 'block5_pool')(x)
# 展平層
# 將多維輸入數據平坦化成一維數組
# 將多維輸入數據(如卷積層的輸出)展平成一個一維數組,以便將其餵入全連接層
x = Flatten()(x)
# 全連接層
# 包含 4096 個神經元
# 使用 ReLU 激活函數
# 用於將之前卷積層和池化層提取的特徵進一步轉換和提取
# 4096 表示全連接層(Dense layer)的神經元數量,也就是該層的輸出維度
# 這個數字可以隨意設置,但通常根據具體的任務需求和模型架構來決定
# 較大的神經元數量可能會增加模型的容量,但也可能導致過度擬合(overfitting)
# 較小的神經元數量則可能導致模型無法充分擷取數據的特徵
# 因此,選擇適當的神經元數量需要根據具體情況進行調整和嘗試
x = Dense(4096, activation='relu', name='fc1')(x)
# 全連接層,設定同上
x = Dense(4096, activation='relu', name='fc2')(x)
# 輸出層
# 包含了 num_classes 個神經元
# 其數量通常等於分類問題中的類別數量
# 這一層的激活函數通常沒有指定,因為它用於輸出每個類別的原始分數或概率值
output_tensor = Dense(nb_classes, activation='softmax', name='predictions')(x)
model = Model(input_tensor, output_tensor)
return model
# 調用自建的 VGG-16 模型
model=VGG16(len(class_names), (img_width, img_height, 3))
# 印出模型結構
model.summary()
九、訓練模型
# 指定了優化器為 Adam
# Adam 是一種常用的優化算法,
# 它結合了動量梯度下降和 RMSProp 策略,
# 可以有效地更新模型的權重以最小化損失函數
model.compile(optimizer="adam",
# 指定了損失函數為稀疏分類交叉熵。
# 在進行多類別分類任務時,稀疏分類交叉熵通常用於計算模型預測與真實標籤之間的差異,並作為模型優化的目標
loss ='sparse_categorical_crossentropy',
# 指定了評估指標為準確率
# 在模型訓練過程中,準確率用於衡量模型對訓練數據的預測準確度,它是一個常用的模型評估指標
metrics =['accuracy'])
# 設置迭代的次數,即訓練週期的數量
epochs = 10
# 設置初始的學習率
lr = 1e-4
# 用於存儲每個訓練/驗證週期的損失和準確率
history_train_loss = []
history_train_accuracy = []
history_val_loss = []
history_val_accuracy = []
# 迭代訓練模型 epochs 次
for epoch in range(epochs):
train_total = len(train_ds)
val_total = len(val_ds)
# 使用 tqdm 庫創建一個進度條,
# 設置迭代總數 total 為訓練數據集的大小,
# desc 參數為進度條的標籤,
# mininterval 參數為進度條的最小更新間隔,
# ncols 參數設置進度條的最大寬度
with tqdm(total=train_total, desc=f'Epoch {epoch + 1}/{epochs}',mininterval=1,ncols=100) as pbar:
# 在每個訓練週期內,調整學習率,並將新的學習率設置到模型的優化器中
lr = lr*0.92
K.set_value(model.optimizer.lr, lr)
# 迭代訓練數據集中的每個圖像和標籤
for image,label in train_ds:
# 返回訓練的損失和準確率
history = model.train_on_batch(image,label)
train_loss = history[0]
train_accuracy = history[1]
pbar.set_postfix({"loss": "%.4f"%train_loss,
"accuracy":"%.4f"%train_accuracy,
"lr": K.get_value(model.optimizer.lr)})
# 將當前的訓練損失和準確率顯示在進度條上,並更新進度條的狀態
pbar.update(1)
history_train_loss.append(train_loss)
history_train_accuracy.append(train_accuracy)
print('開始驗證!')
# 創建一個驗證進度條
with tqdm(total=val_total, desc=f'Epoch {epoch + 1}/{epochs}',mininterval=0.3,ncols=100) as pbar:
for image,label in val_ds:
history = model.test_on_batch(image,label)
val_loss = history[0]
val_accuracy = history[1]
# 將當前的驗證損失和準確率顯示在進度條上,並更新進度條的狀態
pbar.set_postfix({"loss": "%.4f"%val_loss,
"accuracy":"%.4f"%val_accuracy})
pbar.update(1)
# 將每個驗證週期的損失和準確率添加到相應的列表中
history_val_loss.append(val_loss)
history_val_accuracy.append(val_accuracy)
# 打印出當前驗證的損失和準確率
print('結束驗證!')
print("驗證loss為:%.4f"%val_loss)
print("驗證準確為:%.4f"%val_accuracy)
fit
和 train_on_batch
都是 TensorFlow 中用於訓練模型的方法,但它們之間有一些重要的區別:
-
fit:
fit
是 Keras 中用於訓練模型的高級方法,通常會在整個數據集上進行多個訓練迭代,即完整的訓練過程中會將整個數據集多次輸入模型進行訓練。- 它通常使用了一些高級功能,如損失函數的計算、梯度計算、自動反向傳播和參數更新。
fit
方法需要將整個數據集加載到內存中,對於大型數據集可能會導致內存耗盡。
-
train_on_batch:
train_on_batch
是 Keras 中的低級方法,用於手動控制模型訓練的過程,通常用於特殊需求或自定義訓練循環。- 它允許你以批次的方式控制模型訓練的進程,即每次只使用一個小批量的數據進行訓練。
- 通常用於需要更靈活控制訓練過程的情況,例如在訓練過程中手動更改學習率、使用不同的損失函數或監視訓練過程中的中間結果。
總的來說,如果你只是進行普通的模型訓練,並且不需要對訓練過程進行特別的控制,則應該使用 fit
方法。如果你需要更靈活地控制訓練過程,或者需要以自定義的方式處理每個訓練批次,則可以使用 train_on_batch
方法
十、模型評估
# 繪製模型訓練過程中的訓練準確度、驗證準確度、訓練損失和驗證損失的變化趨勢圖
epochs_range = range(epochs)
plt.figure(figsize=(12, 4))
# 在畫布上創建一個子圖,1 行 2 列,目前處理第 1 個子圖
plt.subplot(1, 2, 1)
# 繪製訓練準確度曲線,epochs_range 是 x 軸上的訓練時期範圍,history_train_accuracy 是對應時期的訓練準確度值
plt.plot(epochs_range, history_train_accuracy, label='Training Accuracy')
# 繪製驗證準確度曲線,history_val_accuracy 是對應時期的驗證準確度值
plt.plot(epochs_range, history_val_accuracy, label='Validation Accuracy')
# 在圖中添加圖例,顯示訓練準確度和驗證準確度的對應關係
plt.legend(loc='lower right')
# 設置子圖的標題,顯示訓練和驗證準確度的變化
plt.title('Training and Validation Accuracy')
# 繪製訓練和驗證損失的曲線
plt.subplot(1, 2, 2)
plt.plot(epochs_range, history_train_loss, label='Training Loss')
plt.plot(epochs_range, history_val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
十一、預測
# 建一個畫布,設置其尺寸為寬度 18 個單位,高度 3 個單位
plt.figure(figsize=(18, 3))
# 設置整個畫布的標題為 "預測結果展示"
plt.suptitle("預測結果展示")
# 遍歷驗證數據集中的第一個批次(batch)
for images, labels in val_ds.take(1):
# 對於批次中的每個圖像進行處理
for i in range(8):
# 在畫布上創建一個子圖,1 行 8 列,當前處理第 i+1 個子圖
ax = plt.subplot(1,8, i + 1)
# 顯示第 i 個圖像,images[i] 是一個 TensorFlow 張量,.numpy() 方法將其轉換為 NumPy 數組以供顯示
plt.imshow(images[i].numpy())
# 將圖像擴展為一個批次(batch),即在其前面添加一個維度
img_array = tf.expand_dims(images[i], 0)
# 使用模型對擴展後的圖像進行預測,得到預測結果
predictions = model.predict(img_array)
# 根據預測結果獲取對應的類別名稱,並將其設置為子圖的標題
plt.title(class_names[np.argmax(predictions)])
# 關閉子圖的坐標軸顯示
plt.axis("off")
# 顯示整個畫布,呈現所有的子圖
plt.show()
準確率很低,須重新訓練
十二、總結
透過本次實作,我學習到 train_on_batch
的用法:
-
靈活性:
train_on_batch
提供了比fit
更靈活的訓練方式,它允許手動控制每個批次的訓練過程,這對於一些特定場景下的訓練很有用,例如當你想要對每個批次進行特殊處理時 -
細粒度控制:通過
train_on_batch
,可以更加細緻地控制訓練過程,例如在每個批次後更新權重、計算梯度等,這可以進行更多自定義的操作 -
效率:相對於
fit
函數,train_on_batch
的效率更高,這是因為它僅僅執行單個批次的前向傳播和反向傳播,而不需要經過整個數據集的遍歷 -
掌握細節:通過使用
train_on_batch
,你可以更好地了解訓練過程中的細節,例如每個批次的損失變化、準確率的變化等,這有助於你更好地調整模型和優化訓練過程 -
需求較高:相對於
fit
,train_on_batch
對於使用者的需求更高,需要手動處理每個批次的數據輸入和標籤,並確保正確地更新模型參數
總之,train_on_batch
提供了一種更靈活、更細粒度的訓練方式,適用於一些特殊需求的訓練場景,然而,對於一般的訓練任務,fit
函數更加方便和易用