深度學習筆記09-使用VGG-16識別貓狗(Tensorflow)

前言


一、我的環境

  • 電腦系統: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)


fittrain_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 的用法:

  1. 靈活性train_on_batch 提供了比 fit 更靈活的訓練方式,它允許手動控制每個批次的訓練過程,這對於一些特定場景下的訓練很有用,例如當你想要對每個批次進行特殊處理時

  2. 細粒度控制:通過 train_on_batch,可以更加細緻地控制訓練過程,例如在每個批次後更新權重、計算梯度等,這可以進行更多自定義的操作

  3. 效率:相對於 fit 函數,train_on_batch 的效率更高,這是因為它僅僅執行單個批次的前向傳播和反向傳播,而不需要經過整個數據集的遍歷

  4. 掌握細節:通過使用 train_on_batch,你可以更好地了解訓練過程中的細節,例如每個批次的損失變化、準確率的變化等,這有助於你更好地調整模型和優化訓練過程

  5. 需求較高:相對於 fittrain_on_batch 對於使用者的需求更高,需要手動處理每個批次的數據輸入和標籤,並確保正確地更新模型參數

總之,train_on_batch 提供了一種更靈活、更細粒度的訓練方式,適用於一些特殊需求的訓練場景,然而,對於一般的訓練任務,fit 函數更加方便和易用

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值