深度學習筆記07-使用CNN識別好萊塢明星(Tensorflow)

前言

一、我的環境

二、準備套件

三、設定GPU

四、載入資料

五、數據預處理

六、可視化數據

七、配置數據集

八、建構CNN網路

九、訓練模型

十、模型評估

十一、預測

十二、總結


前言


一、我的環境

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

結果正確


十二、總結

 透過本次實作,學習到了獨熱編碼的使用方式:

  1. 處理類別型特徵:獨熱編碼通常用於處理類別型特徵,例如類別標籤或類別型變量

  2. 二進制向量表示:對於一個有N個不同類別的特徵,獨熱編碼會將每個類別映射為一個長度為N的二進制向量,其中只有一個元素為1,其餘元素都為0

  3. 適用於機器學習算法:獨熱編碼使得類別型特徵可以被機器學習算法有效地處理,因為大多數算法都需要數值型輸入

  4. 類別數量擴展:獨熱編碼會將類別數量擴展為特徵維度的大小,這可能會增加數據集的維度,對於大型數據集或高維度特徵空間,需要考慮這種擴展的影響

  5. 避免順序偏差:獨熱編碼不考慮類別之間的順序關係,每個類別都被編碼為獨立的向量,因此可以避免在數值表示中產生偏差

  6. 輸入特徵前處理:在應用獨熱編碼之前,需要確保特徵的類別型數據已經轉換為數值型格式,通常是將類別標籤映射為整數編碼

總之,獨熱編碼是一個重要的特徵處理技術,在處理類別型特徵時很常見,特別是在機器學習任務中,它的使用可以幫助我們克服類別型特徵在數值計算中的限制,使得算法能夠更好地理解和利用這些特徵

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值