前言
- 🍨 本文為🔗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/T6,這邊要注意斜線的方向
data_dir = "T6/"
# 將路徑轉換成 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('Angelina Jolie/*.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(
# 表示包含圖像資料集的目錄的路徑
data_dir,
# 指定要從資料集中分離出作為驗證集的部分比例
# 例如,如果設置為 0.1,則將使用資料集的 10% 作為驗證集,而其餘 90% 將用於訓練
validation_split=0.1,
# 設置為 "training",表示創建訓練子集
subset="training",
# 指定如何處理標籤
# 設置為 "categorical",表示標籤將以獨熱編碼的形式返回,每個類別有一個元素為 1,其餘元素為 0 的向量
label_mode = "categorical",
# 設置隨機種子以便能夠複現結果
seed=123,
# 表示輸入圖像的大小,通常以像素為單位
image_size=(img_height, img_width),
# 表示每個批次的樣本數量
batch_size=batch_size)
# 創建測試集
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
data_dir,
validation_split=0.1,
subset="validation",
label_mode = "categorical",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
# class_names 是一個包含數據集中所有類別名稱的列表
class_names = train_ds.class_names
# 印出類別
print(class_names)
共有 17 個明星類別
獨熱編碼(One-Hot Encoding)是一種將類別型特徵轉換為二元向量的方法,常用於機器學習中對標籤(或類別)的表示,當一個類別特徵有多個類別時,獨熱編碼將每個類別映射為一個二元向量,其中該類別所在的位置為 1,其餘位置為 0
例如,假設有三個類別:"狗"、"貓" 和 "鳥",對於獨熱編碼,我們可以用以下方式表示這三個類別:
- "狗":[1, 0, 0]
- "貓":[0, 1, 0]
- "鳥":[0, 0, 1]
這樣,每個類別都被表示為一個與類別數量相同的向量,並且只有該類別所在位置的值為 1,其他位置的值都為 0,這種表示方法使得模型能夠更容易地理解和處理類別型特徵
在機器學習中,獨熱編碼常用於分類問題中的標籤表示,例如,在多類別分類問題中,如果有 10 個類別,則每個標籤將使用 10 維的獨熱向量表示
六、可視化數據
# 創建一個圖形對象,設置圖形大小寬度 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"))
# 設置當前子圖的標題為當前圖片的類別名稱
# np.argmax(labels[i]) 是用來從標籤向量 labels[i] 中找出值最大的索引,這代表了該標籤的類別
# labels[i] 是獨熱編碼的形式,那麼 np.argmax(labels[i]) 將返回一個介於 0 到類別數量減 1 之間的整數,代表該標籤所屬的類別
# 透過 class_names[np.argmax(labels[i])] 來取得該類別的名稱,並將其設置為圖表的標題
plt.title(class_names[np.argmax(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)即可
圖像數據的形狀:(32, 224, 224, 3)
- 第一個數字 32 表示這個數據集中有 32 張圖像
- 接著的兩個數字 (224, 224) 表示每個圖像的高度和寬度,即圖像的尺寸為 224x224 像素
- 最後一個數字 3 表示圖像的通道數,這裡是 3,代表 RGB 顏色通道,意味著每個像素由紅、綠、藍三個通道組成
標籤數據的形狀:(32, 17)
- 第一個數字 32 表示這個數據集中有 32 個樣本(或者說 32 張圖像)
- 第二個數字 17 表示每個樣本的標籤向量的長度,即每個樣本有 17 個元素
七、配置數據集
# 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網路
# 表示模型的輸出類別數量為 17 ,在這種情況下,模型的輸出層具有 17 個神經元,每個神經元對應一個類別
num_classes = 17
# 創建一個序列模型,這是一種線性堆疊模型,其中各個層按照他們被添加到模型中的順序來堆疊
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.5 表示丟棄的神經元概率為 50%
# 意味著每次訓練迭代中,有 50% 的神經元會被隨機丟棄,以防止模型過度擬合訓練數據
layers.Dropout(0.5),
# 創建第三個卷積層
# 64個卷積核
# 每個卷積核的大小為 3x3
layers.Conv2D(64, (3, 3), activation='relu'),
# 創建第三個平均池化層
# 作用是對輸入的特徵圖進行下採樣,以減少特徵圖的空間尺寸,同時保留主要的特徵
# 指定池化窗口的大小為 2x2 ,這意味著對於每個 2x2 的區域,將計算其內像素的平均值作為輸出
layers.AveragePooling2D((2, 2)),
# 隨機失活層
# 在模型訓練過程中以指定的概率丟棄(關閉)一定比例的神經元,以防止過擬合
# 0.5 表示丟棄的神經元概率為 50%
# 意味著每次訓練迭代中,有 50% 的神經元會被隨機丟棄,以防止模型過度擬合訓練數據
layers.Dropout(0.5),
# 創建第四個卷積層
# 128個卷積核
# 每個卷積核的大小為 3x3
layers.Conv2D(128, (3, 3), activation='relu'),
# 隨機失活層
# 在模型訓練過程中以指定的概率丟棄(關閉)一定比例的神經元,以防止過擬合
# 0.5 表示丟棄的神經元概率為 50%
# 意味著每次訓練迭代中,有 50% 的神經元會被隨機丟棄,以防止模型過度擬合訓練數據
layers.Dropout(0.5),
# 展平層
# 將多維輸入數據平坦化成一維數組
# 將多維輸入數據(如卷積層的輸出)展平成一個一維數組,以便將其餵入全連接層
layers.Flatten(),
# 全連接層
# 包含 128 個神經元
# 使用 ReLU 激活函數
# 用於將之前卷積層和池化層提取的特徵進一步轉換和提取
layers.Dense(128, activation='relu'),
# 輸出層
# 包含了 num_classes 個神經元
# 其數量通常等於分類問題中的類別數量
# 這一層的激活函數通常沒有指定,因為它用於輸出每個類別的原始分數或概率值
layers.Dense(len(num_classes))
])
# 印出模型結構
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=60,
decay_rate=0.96,
staircase=True)
# 創建了一個 Adam 優化器,並將指數衰減的學習率計劃應用於該優化器中
optimizer = tf.keras.optimizers.Adam(learning_rate=lr_schedule)
# 將設置好的優化器應用於模型的編譯中
# 因為是獨熱編碼,所以使用 Categorical Crossentropy
# 同時,設置了損失函數是分類交叉熵(Categorical Crossentropy),並且將準確率(accuracy)作為模型的評估指標
# 設置 from_logits=True 表示模型的輸出是經過 softmax 處理的 logits(未經過 softmax 的機率分佈),因此損失函數將自動進行 softmax 操作
model.compile(optimizer=optimizer,
loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
# 指定訓練過程中的迭代次數,即訓練模型的輪數
epochs = 100
# 保存最加模型參數
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 表示輸出詳細信息
history = model.fit(
train_ds,
validation_data=val_ds, # 訓練集
epochs=epochs, # 指定訓練的迭代次數
callbacks=[checkpointer, earlystopper]) # 使用監測物件
最佳模型參數在第 27 輪出現了
表示模型在第 47 個訓練周期時觸發了早期停止機制,這意味著在這個周期之後,模型的性能沒有顯著改善,因此訓練被提前停止了
十、模型評估
# 從訓練歷史中提取準確率
# 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("T6/Brad Pitt/003_7a6b2156.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)])
結果正確
十二、總結
透過本次實作,學習到了獨熱編碼的使用方式:
-
處理類別型特徵:獨熱編碼通常用於處理類別型特徵,例如類別標籤或類別型變量
-
二進制向量表示:對於一個有N個不同類別的特徵,獨熱編碼會將每個類別映射為一個長度為N的二進制向量,其中只有一個元素為1,其餘元素都為0
-
適用於機器學習算法:獨熱編碼使得類別型特徵可以被機器學習算法有效地處理,因為大多數算法都需要數值型輸入
-
類別數量擴展:獨熱編碼會將類別數量擴展為特徵維度的大小,這可能會增加數據集的維度,對於大型數據集或高維度特徵空間,需要考慮這種擴展的影響
-
避免順序偏差:獨熱編碼不考慮類別之間的順序關係,每個類別都被編碼為獨立的向量,因此可以避免在數值表示中產生偏差
-
輸入特徵前處理:在應用獨熱編碼之前,需要確保特徵的類別型數據已經轉換為數值型格式,通常是將類別標籤映射為整數編碼
總之,獨熱編碼是一個重要的特徵處理技術,在處理類別型特徵時很常見,特別是在機器學習任務中,它的使用可以幫助我們克服類別型特徵在數值計算中的限制,使得算法能夠更好地理解和利用這些特徵