前言
- 🍨 本文為🔗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
# keras.layers,tensorflow 的子模塊,包含建構神經網路模型所需的各種層
# keras.models,tensorflow 的子模塊,包含建構神經網路模型的類和函數
from tensorflow.keras import layers,models
# 是 TensorFlow Keras 中的一個回調函數
# 用於在訓練過程中保存模型的檢查點(checkpoint)
# 檢查點是模型的權重或是整個模型在訓練期間的某個狀態的保存
from tensorflow.keras.callbacks import ModelCheckpoint
# 是 TensorFlow Keras 中的一個回調函數
# 用於提前停止訓練
# 如果在訓練過程中監視的指標(如驗證集損失)在一段連續的訓練週期內沒有改善,則停止訓練,以防止過度擬合
from tensorflow.keras.callbacks import EarlyStopping
三、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/T5,這邊要注意斜線的方向
data_dir = "T5/"
# 將路徑轉換成 pathlib.Path 對象,更易操作
data_dir = pathlib.Path(data_dir)
# 使用 glob 方法獲取指定目錄下所有以 '.jpg' 為副檔名的文件迭代器
# '*/*.jpg' 是一個通配符模式,表示所有直接位於子目錄中的以 .jpg 結尾的文件
# 第一個星號表示所有目錄
# 第二個星號表示所有檔名
image_count = len(list(data_dir.glob('*/*.jpg')))
# 印出圖片數量
print("圖片總數:",image_count)
# 開張圖來看看
roses = list(data_dir.glob('train/nike/*.jpg'))
img = PIL.Image.open(str(roses[0]))
plt.imshow(img)
# 關閉座標軸,即不顯示座標軸
plt.axis('off')
# 顯示圖片
plt.show()
五、數據預處理
# 設置批量大小,即每次訓練模型時輸入到模型中的圖像數量
# 在每次訓練跌代時,模型將同時處理32張圖像
# 批量大小的選擇會影響訓練速度和內存需求
batch_size = 32
# 圖像的高度,在加載圖像數據時,將所有的圖像調整為相同的高度,這裡設定為 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(
"T5/train/", # 指定包含圖像的目錄路徑
seed=123, # 設定亂數種子,確保每次運行程式碼時得到的數據集都是相同的,以保持實驗的可重複性
image_size=(img_height, img_width), # 指定圖像的大小
batch_size=batch_size) # 指定每個批次中包含的圖像樣本數量
# 創建測試集
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
"T5/test/",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
# class_names 是一個包含數據集中所有類別名稱的列表
class_names = train_ds.class_names
# 印出類別
print(class_names)
總共兩個品牌:adidas, nike
六、可視化數據
# 創建一個圖形對象,設置圖形大小寬度 20 英寸、高度 10 英寸
plt.figure(figsize=(20, 10))
# train_ds.take(1) 從訓練集中取出一個批次(batch)的數據
# 遍歷訓練數據集的第一個批次(batch)中的每張圖片
# images 是一個包含圖片數據的張量
# labels 是一個包含對應標籤的張量
for images, labels in train_ds.take(1):
# 遍歷當前批次(batch)的前 20 張圖
for i in range(20):
# 創建一個子圖,將圖形劃分成 5 行 10 列,當前子圖的索引為 i+1
ax = plt.subplot(5, 10, i + 1)
# 顯示第 i 張圖片
# images[i] 是一個張量,使用 .numpy() 方法將其轉換為 NumPy 數組,
# 再使用 .astype("uint8") 方法將其轉換為無符號整型 8 位整數類型
plt.imshow(images[i].numpy().astype("uint8"))
# 設置當前子圖的標題為當前圖片的類別名稱
# labels[i] 視當前圖片對應的標籤,通過索引 class_names 列表獲取對應的類別名稱
plt.title(class_names[labels[i]])
# 關閉座標軸的顯示
plt.axis("off")
# 顯示圖片
plt.show()
# 從 train_ds 中迭代獲取一個批次(batch)的圖像數據和標籤數據,印出他們的形狀
for image_batch, labels_batch in train_ds:
print('圖像數據的形狀:', image_batch.shape)
print('標籤數據的形狀:', labels_batch.shape)
break # 只印一批次(batch)即可
- image_batch.shape:印出圖像數據的形狀,通常是一個四維張量,形狀為 ( batch_size,image_height,image_width,num_channels) ,其中 batch_size 是批次大小,image_height 和 image_width 是圖像的高度和寬度,num_channels 是圖像的通道數,RGB 為 3
- labels_batch.shape:印出標籤數據的形狀,通常是一個二維張量,形狀為 ( batch_size,) ,其中 batch_size 與圖像數據的批次大小相同,每個元素代表一個圖像的標籤
這樣的輸出可以幫助確認訓練數據集中每個批次的數據形狀是否符合預期,以便進行後續的模型訓練
七、配置數據集
# AUTOTUNE 表示自動調整緩衝區大小以優化性能
AUTOTUNE = tf.data.AUTOTUNE
# 使用 cache() 方法將訓練集緩存到內存中,這樣可以加快數據加載速度
# 當模型多次迭代訓練數據集時,可以重複使用已經加載到內存中的數據,而不必重新從磁盤加載
# 使用 shuffle() 方法對訓練數據集進行洗牌操作,打亂數據集中的樣本順序
# 參數 1000 指定了洗牌時使用的緩衝區大小,即每次從數據集中隨機選擇的樣本數量
# 通過洗牌操作,可以增加模型的訓練效果,使模型更好的學習數據的分布
# 使用 prefetch() 方法預取數據,以便在訓練過程中盡可能的減少數據加載時間
# 此方法可以在訓練模型的同時異步加載數據,從而提高 GPU 的利用率,減少數據加載造成的訓練時間損失
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
# 對驗證集進行和訓練集相同的緩存操作
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
八、建構CNN網路
# 創建一個序列模型,這是一種線性堆疊模型,其中各個層按照他們被添加到模型中的順序來堆疊
model = models.Sequential([
# 創建了一個對圖像進行重新縮放的預處理層
# 將每個像素的數值除以 255,將像素值縮放到範圍 [0,1],即歸一化
# 輸入的 img_height 和 img_width 分別表示圖像的高度和寬度,3 表示圖像的通道數,通常為 RGB 彩色圖像
layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
# 創建一個卷積層
# 16:表示該卷積層使用了 16 個卷積核
# (3, 3):指定了每個卷積核的大小為 3x3
# 使用ReLU(Rectified Linear Unit)作為激活函數,它是一種常用的非線性函數,能夠使模型具有更好的學習能力
# 設置輸入形狀為(img_height, img_width, 3)
# 表示輸入圖像的高度為img_height像素,寬度為img_width像素,通道數為3(RGB 彩色圖像)
# 這個參數只需要在模型的第一層中指定,後續的層將自動推斷輸入形狀
layers.Conv2D(16, (3, 3), activation='relu', input_shape=(img_height, img_width, 3)),
# 創建一個平均池化層
# 將原始特徵圖中的每個 2x2 區域的數值取平均,從而實現尺寸的縮小和特徵的抽取
# (2, 2):指定了池化窗口的大小為 2x2 ,即將原始特徵圖劃分為 2x2 的子區域
# 這個池化操作對每個子區域內的數值進行平均,得到新的特徵圖
layers.AveragePooling2D((2, 2)),
# 創建第二個卷積層
# 32 個卷積核
# 每個卷積核的大小是 3x3
# 本層沒有指定輸入形狀(input_shape),因為這是模型中的中間層,將繼承前一層的輸出形狀作為輸入形狀
layers.Conv2D(32, (3, 3), activation='relu'),
# 創建第二個平均池化層
# 作用是對輸入的特徵圖進行下採樣,以減少特徵圖的空間尺寸,同時保留主要的特徵
# 指定池化窗口的大小為 2x2 ,這意味著對於每個 2x2 的區域,將計算其內像素的平均值作為輸出
layers.AveragePooling2D((2, 2)),
# 隨機失活層
# 在模型訓練過程中以指定的概率丟棄(關閉)一定比例的神經元,以防止過擬合
# 0.3 表示丟棄的神經元概率為 30%
# 意味著每次訓練迭代中,有 30% 的神經元會被隨機丟棄,以防止模型過度擬合訓練數據
layers.Dropout(0.3),
# 創建第三個卷積層
# 64個卷積核
# 每個卷積核的大小為 3x3
layers.Conv2D(64, (3, 3), activation='relu'),
# 隨機失活層
# 在模型訓練過程中以指定的概率丟棄(關閉)一定比例的神經元,以防止過擬合
# 0.3 表示丟棄的神經元概率為 30%
# 意味著每次訓練迭代中,有 30% 的神經元會被隨機丟棄,以防止模型過度擬合訓練數據
layers.Dropout(0.3),
# 展平層
# 將多維輸入數據平坦化成一維數組
# 將多維輸入數據(如卷積層的輸出)展平成一個一維數組,以便將其餵入全連接層
layers.Flatten(),
# 全連接層
# 包含 128 個神經元
# 使用 ReLU 激活函數
# 用於將之前卷積層和池化層提取的特徵進一步轉換和提取
layers.Dense(128, activation='relu'),
# 輸出層
# 包含了 num_classes 個神經元
# 其數量通常等於分類問題中的類別數量
# 這一層的激活函數通常沒有指定,因為它用於輸出每個類別的原始分數或概率值
layers.Dense(len(class_names))
])
# 印出模型結構
model.summary()
九、訓練模型
# 設置了初始學習率為 0.0001
initial_learning_rate = 0.0001
# 創建了一個指數衰減的學習率計劃
# 這個計劃將初始學習率(initial_learning_rate)以指定的速率(decay_rate)每隔一定的步驟(decay_steps)進行衰減
# staircase=True 表示使用階梯函數衰減,即每隔 decay_steps 步驟,學習率會按照指數衰減的方式進行更新
lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate,
decay_steps=10,
decay_rate=0.92,
staircase=True)
# 創建了一個 Adam 優化器,並將指數衰減的學習率計劃應用於該優化器中
optimizer = tf.keras.optimizers.Adam(learning_rate=lr_schedule)
# 將設置好的優化器應用於模型的編譯中
# 同時,設置了損失函數為稀疏分類交叉熵(Sparse Categorical Crossentropy),並且將準確率(accuracy)作為模型的評估指標
model.compile(optimizer=optimizer,
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
學習率對訓練神經網絡模型的性能和訓練速度具有重要影響,它既有優點也有缺點:
優點:
- 收斂速度快:較大的學習率可以使模型更快地收斂到局部最優解或全局最優解
- 跳出局部極小值:較大的學習率有助於模型跳出局部極小值,從而避免陷入局部最優解
缺點:
- 不穩定性:較大的學習率可能導致訓練過程不穩定,模型可能會在最優解附近震盪,甚至無法收斂
- 過度擬合:過大的學習率可能導致模型過度擬合訓練數據,無法泛化到測試數據
- 損失函數不收斂:較大的學習率可能使損失函數在訓練過程中無法收斂,產生不合理的訓練結果
總之,適當調整學習率大小可以平衡模型的訓練速度和性能,並確保模型收斂到良好的最優解,通常需要進行實驗和調參來確定最適合的學習率大小
# 指定訓練過程中的迭代次數,即訓練模型的輪數
epochs = 50
checkpointer = ModelCheckpoint(
filepath='best_model.h5', # 模型權重的保存路徑和文件名
monitor='val_accuracy', # 要監測的指標,這裡設置為 'val_accuracy',表示監測驗證集的準確率
verbose=1, # 控制輸出信息的詳細程度,設置為 1 表示輸出詳細信息
save_best_only=True, # 布林值,表示是否只保存在指標上表現最好的模型,設置為 True
save_weights_only=True) # 布林值,表示是否只保存模型的權重,設置為 True
earlystopper = EarlyStopping(
monitor='val_accuracy', # 要監測的指標,這裡也設置為 'val_accuracy',表示監測驗證集的準確率
min_delta=0.001, # 相對變化的閾值,如果監測指標的變化小於此值,則被認為沒有進步,訓練將停止
patience=20, # 訓練將在檢測到指標停止改善後的固定輪數後停止,以防止過度擬合
verbose=1) # 控制輸出信息的詳細程度,設置為 1 表示輸出詳細信息
-
ModelCheckpoint:這個回調函數用於保存模型在訓練過程中的最佳表現,它會監視指定的指標(如驗證集準確率或損失)並在每次訓練週期後檢查這些指標是否有改善,如果有改善,則保存模型的權重,這樣可以確保在整個訓練過程中始終保存最佳的模型
-
EarlyStopping:這個回調函數用於提前停止訓練,如果在訓練過程中監視的指標(如驗證集損失)在一段連續的訓練週期內沒有改善,則停止訓練,以防止過度擬合
這些回調函數通常與 model.fit() 方法一起使用,以監視訓練過程並根據需要保存模型或提前停止訓練
history = model.fit(
train_ds,
validation_data=val_ds, # 訓練集
epochs=epochs, # 指定訓練的迭代次數
callbacks=[checkpointer, earlystopper]) # 使用監測物件
Epoch 00021: early stopping 表示在第 21 個訓練時期(epoch)時,早停(early stopping)條件已被觸發,訓練被提前停止
這個消息意味著模型在進行 21 個訓練時期後,沒有觀察到驗證集性能的顯著改善,因此訓練提前終止以防止過度擬合
早停是一種常用的防止過度擬合的技術,它通過監控驗證集性能,在性能不再改善時停止訓練,從而避免模型在訓練集上表現過好而在新數據上泛化能力較差的問題
十、模型評估
# 從訓練歷史中提取準確率
# acc 是訓練集準確率
# val_acc 是驗證集準確率
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
# 從訓練歷史中提取損失
# loss 是訓練集損失
# val_loss 是驗證集損失
loss = history.history['loss']
val_loss = history.history['val_loss']
# 定義了迭代次數範圍
epochs_range = range(len(loss))
# 創建一個新的圖形,指定圖形的大小
plt.figure(figsize=(12, 4))
# 在圖形中創建 1 行 2 列的子圖,並選擇第一個子圖
plt.subplot(1, 2, 1)
# 繪製訓練集準確率隨迭代次數變化的曲線
plt.plot(epochs_range, acc, label='Training Accuracy')
# 繪製驗證集準確率隨迭代次數變化的曲線
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
# 添加圖例,位置位於右下角
plt.legend(loc='lower right')
# 添加子圖的標題
plt.title('Training and Validation Accuracy')
# 在圖形中創建 1 行 2 列的子圖,並選擇第二個子圖
plt.subplot(1, 2, 2)
# 繪製訓練集損失函數的曲線
plt.plot(epochs_range, loss, label='Training Loss')
# 繪製驗證集損失函數的曲線
plt.plot(epochs_range, val_loss, label='Validation Loss')
# 添加圖例,位置位於右上角
plt.legend(loc='upper right')
# 添加子圖的標題
plt.title('Training and Validation Loss')
# 顯示圖片
plt.show()
十一、預測
# 加載效果最好的模型權重
model.load_weights('best_model.h5')
# 選擇想預測的照片
img = PIL.Image.open("T5/test/nike/1.jpg")
# 將原始圖像調整為模型所需的大小
img = img.resize((img_height, img_width))
# 將 PIL 圖像對象轉換為 NumPy 數組(即將圖像像素轉換為數值表示)
# TensorFlow 中的圖像處理功能通常接受 NumPy 數組作為輸入
img_array = tf.keras.preprocessing.image.img_to_array(img)
# 將 NumPy 數組轉換為 TensorFlow 張量,同時添加一個額外的維度
# 添加到第一個維度,以便能夠將單個圖像作為單個樣本餵入模型進行預測。
img_array = tf.expand_dims(img_array, 0)
# 進行預測
# 產出一個包含預測概率的陣列
predictions = model.predict(img_array)
# 使用 np.argmax(predictions) 獲取概率最高的類別索引
# 利用 class_names 陣列找到對應的類別名稱
print("預測結果為:",class_names[np.argmax(predictions)])
結果正確
十二、總結
透過本次實作,學習到了如何使用 Early Stopping 技術
在深度學習模型的訓練過程中,通常使用 Early Stopping 技術來監控模型的性能並避免過度擬合,以下是我對 Early Stopping 的學習總結:
1. 防止過度擬合:Early Stopping 是一種防止深度學習模型在訓練過程中過度擬合的技術,它通過監控驗證集的性能來決定何時停止訓練,從而使模型在訓練集和測試集上都能表現良好
2. 提前終止訓練:當模型在驗證集上的性能不再改善時,Early Stopping 會提前終止訓練過程,這意味著模型不會持續訓練直到完全擬合訓練數據,從而避免了過度擬合的問題
3. 監控指標的選擇:在使用 Early Stopping 時,我們需要指定要監控的性能指標,通常是驗證集上的準確率或損失值,根據這些指標的變化,決定是否提前停止訓練
4. 設置停止條件:可以通過設置參數來指定 Early Stopping 的停止條件,如最小改善閾值、容忍的停止次數等,這些參數的設置可以根據具體的問題和模型進行調整
5. 避免過度訓練:使用 Early Stopping 技術有助於避免模型在訓練過程中過度訓練,從而提高了模型的泛化能力和效果
總之,Early Stopping 技術是深度學習訓練過程中的一種重要技術,能夠有效地提高模型的性能並避免過度擬合的問題,通過適當地監控模型的性能並及時停止訓練,可以獲得更加穩健和有效的深度學習模型