返回文章列表

PyTorch影像分類別資料準備與模型訓練

本文介紹如何使用 PyTorch 進行影像分類別,涵蓋資料準備、模型建構、訓練流程與模型儲存及載入。文章詳細說明使用 torchvision 進行資料集建立、轉換與載入,並以簡單神經網路為例,示範如何定義模型架構、選擇損失函式與最佳化器,以及使用 GPU

深度學習 影像處理

深度學習應用於影像分類別已成為主流趨勢,PyTorch 作為一個功能強大的深度學習框架,提供豐富的工具和函式庫,簡化了影像分類別任務的開發流程。本文將從資料準備開始,逐步講解如何使用 PyTorch 建立、訓練和佈署影像分類別模型。 torchvision 的 transforms 模組提供一系列影像轉換功能,例如調整大小、轉換為張量、標準化等,方便我們對影像資料進行預處理,以提高模型的訓練效率和準確度。ImageFolder 函式則可以根據資料夾結構自動載入影像和標籤,簡化資料集的建立過程。接著,我們使用 DataLoader 將資料集分批載入,並利用多執行緒提升載入效率,為模型訓練做好準備。在模型建構方面,我們以一個簡單的三層全連線網路為例,說明如何使用 PyTorch 定義模型架構,並選擇合適的啟用函式,例如 ReLU 和 Softmax。損失函式的選擇也至關重要,CrossEntropyLoss 函式適用於多類別分類別任務,並內建 Softmax 計算,簡化了程式碼。Adam 最佳化器則能自適應地調整學習率,在多數情況下都能取得良好的訓練效果。最後,我們示範如何使用訓練好的模型進行影像預測,以及如何儲存和載入模型引數,方便模型的佈署和復用。

影像分類別中的資料準備與載入

在進行影像分類別任務時,資料的準備與載入是至關重要的第一步。PyTorch 提供了一個強大的工具 torchvision,用於處理影像資料。本章將介紹如何使用 torchvision 來載入和預處理影像資料。

資料集的建立

首先,我們需要建立訓練、驗證和測試資料集。這些資料集應該包含標註好的影像資料,以便模型能夠學習和評估。

import torchvision
from torchvision import transforms

train_data_path = "./train/"
transforms = transforms.Compose([
    transforms.Resize(64),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

train_data = torchvision.datasets.ImageFolder(root=train_data_path, transform=transforms)

內容解密:

  1. transforms.Compose: 將多個影像轉換操作組合成一個單一的操作。這裡包括了調整影像大小、將影像轉換為張量以及對張量進行標準化。
  2. transforms.Resize(64): 將所有輸入的影像調整為 64x64 的大小,以統一輸入尺寸並提高處理效率。
  3. transforms.ToTensor(): 將影像資料轉換為 PyTorch 張量,以便於神經網路處理。
  4. transforms.Normalize: 對張量進行標準化,使用 ImageNet 資料集的均值和標準差。這有助於防止梯度爆炸問題,並提高模型的穩定性。

建立驗證和測試資料集

除了訓練資料集外,我們還需要建立驗證和測試資料集,以評估模型的泛化能力和最終效能。

val_data_path = "./val/"
val_data = torchvision.datasets.ImageFolder(root=val_data_path, transform=transforms)

test_data_path = "./test/"
test_data = torchvision.datasets.ImageFolder(root=test_data_path, transform=transforms)

內容解密:

  1. 重複使用 transforms: 對驗證和測試資料集使用與訓練資料集相同的轉換操作,以保持資料的一致性。
  2. ImageFolder 的使用: 自動根據目錄結構載入影像並進行標註。

資料載入器(Data Loaders)

建立好資料集後,我們需要使用 DataLoader 將資料載入到模型中進行訓練。

batch_size = 64
train_data_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size)
val_data_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size)
test_data_loader = torch.utils.data.DataLoader(test_data, batch_size=batch_size)

內容解密:

  1. batch_size: 設定每個批次載入的影像數量。這裡設定為 64,可以根據 GPU 記憶體大小進行調整。
  2. DataLoader 的功能: 自動將資料集分批載入,並提供多執行緒載入資料的功能,以提高效率。

簡單的神經網路模型

在完成資料準備後,我們可以開始建立一個簡單的神經網路模型來進行影像分類別。

簡單神經網路的架構

我們的初始模型將包含一個輸入層、一個隱藏層和一個輸出層。圖 2-3 說明瞭這個簡單的神經網路的架構。

此圖示說明瞭一個基本的神經網路結構,包括輸入層、隱藏層和輸出層之間的連線。

此圖示

內容解密:

  1. 輸入層: 處理輸入的影像張量。
  2. 隱藏層: 提供模型的非線性表達能力。
  3. 輸出層: 輸出分類別結果。

本章介紹瞭如何使用 PyTorch 和 torchvision 來準備和載入影像資料,並建立了一個簡單的神經網路模型。接下來的章節將探討如何訓練這個模型以及如何改進其效能。

建構與訓練神經網路的基礎

在前面的章節中,我們瞭解了全連線層(fully connected layer)的基本概念。全連線層中的每個節點都會影響下一層的所有節點,而每個連線都有一個權重(weight)來決定訊號的強度。這些權重會在訓練過程中被更新,通常是從隨機初始值開始。輸入資料透過網路時,可以透過矩陣乘法將該層的權重和偏差(bias)應用到輸入上。在輸入到下一層之前,結果會透過一個啟用函式(activation function),這是一種向系統中插入非線性的方法。

啟用函式

啟用函式看似複雜,但目前文獻中最常見的是ReLU(Rectified Linear Unit)。ReLU其實很簡單,它實作了max(0,x)的功能,也就是說,如果輸入是負數,輸出就是0;如果輸入是正數,輸出就是輸入本身。

另一個常見的啟用函式是softmax,它在數學上稍微複雜一些。softmax會產生一組介於0和1之間的值,並且這些值的總和為1(類別似機率)。它還會放大這些值之間的差異,使得某個值明顯高於其他值。在分類別網路的最後一層,softmax經常用來確保網路對輸入的類別做出明確的預測。

建立神經網路

在PyTorch中建立神經網路非常Pythonic。我們需要繼承torch.nn.Module類別並實作__init__forward方法:

class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc1 = nn.Linear(12288, 84)
        self.fc2 = nn.Linear(84, 50)
        self.fc3 = nn.Linear(50, 2)

    def forward(self, x):
        x = x.view(-1, 12288)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.softmax(self.fc3(x), dim=1)
        return x

simplenet = SimpleNet()

內容解密:

  1. __init__方法中,我們初始化了三個全連線層(nn.Linear),分別是fc1fc2fc3。這些層的輸入和輸出維度根據網路設計需要設定。
  2. forward方法描述了資料如何透過網路進行前向傳播。首先,我們使用view方法將輸入x轉換成一維張量,以便輸入到第一個全連線層。
  3. 然後,我們依次應用全連線層和啟用函式。最後,使用softmax函式輸出預測結果。

隱藏層中的神經元數量在這裡是任意設定的,但最後一層的輸出維度必須與我們的分類別類別數相匹配。在這個例子中,我們有兩類別(貓或魚),所以最後一層的輸出維度是2。

損失函式

損失函式是深度學習中的關鍵組成部分。PyTorch使用損失函式來決定如何更新網路以達到預期結果。對於多類別分類別任務,PyTorch提供了CrossEntropyLoss損失函式。這個損失函式內部已經包含了softmax操作,因此我們的forward方法可以簡化為:

def forward(self, x):
    x = x.view(-1, 12288)
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = self.fc3(x)
    return x

內容解密:

  1. 省略了最後一層的softmax啟用函式,因為CrossEntropyLoss已經包含了這一操作。
  2. 這樣簡化可以減少重複計算並提高效率。

最佳化器

訓練神經網路涉及將資料透過網路,使用損失函式計算預測與真實標籤之間的差異,然後根據這個差異更新網路的權重,以使損失函式的輸出儘可能小。這個更新過程由最佳化器(optimizer)負責。

最佳化器的作用可以透過一個簡單的比喻來理解:想像在一個曲面上滾動一個小球,使其最終停在最低點(最小值)。在這裡,小球代表我們的權重值,而曲面代表損失函式的值。透過調整權重,我們可以改變小球的位置,從而使損失函式的值變小。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title PyTorch影像分類別資料準備與模型訓練

package "機器學習流程" {
    package "資料處理" {
        component [資料收集] as collect
        component [資料清洗] as clean
        component [特徵工程] as feature
    }

    package "模型訓練" {
        component [模型選擇] as select
        component [超參數調優] as tune
        component [交叉驗證] as cv
    }

    package "評估部署" {
        component [模型評估] as eval
        component [模型部署] as deploy
        component [監控維護] as monitor
    }
}

collect --> clean : 原始資料
clean --> feature : 乾淨資料
feature --> select : 特徵向量
select --> tune : 基礎模型
tune --> cv : 最佳參數
cv --> eval : 訓練模型
eval --> deploy : 驗證模型
deploy --> monitor : 生產模型

note right of feature
  特徵工程包含:
  - 特徵選擇
  - 特徵轉換
  - 降維處理
end note

note right of eval
  評估指標:
  - 準確率/召回率
  - F1 Score
  - AUC-ROC
end note

@enduml

此圖示展示了訓練過程中權重更新的迴圈過程。

圖示解密:

  1. 從隨機初始權重開始。
  2. 計算當前權重下的損失。
  3. 根據損失計算梯度。
  4. 使用梯度更新權重。
  5. 重複步驟2至4,直到收斂。

透過這種方式,神經網路能夠學習如何從輸入資料中提取有用的特徵,並做出準確的預測。

建立 Adam 最佳化器與訓練神經網路

在深度學習中,最佳化器的選擇對於神經網路的訓練效果至關重要。PyTorch 提供了多種最佳化器,如 SGD、AdaGrad、RMSProp 和 Adam,其中 Adam 因其優秀的效能而被廣泛使用。

瞭解學習率與最佳化器

在訓練神經網路時,我們需要調整模型的權重以最小化損失函式。學習率(learning rate)決定了每次權重更新的步伐大小。過大的學習率可能導致模型在訓練過程中震盪,而過小的學習率則可能使訓練過程過慢。

Adam 最佳化器透過為每個引數自適應學習率來改進傳統的 SGD 方法。它維護了一個指數衰減的梯度列表和梯度的平方,並使用這些資訊來縮放全域性學習率。實證結果表明,Adam 在大多數深度學習任務中表現優於其他最佳化器。

建立 Adam 最佳化器

使用 PyTorch 建立 Adam 最佳化器非常簡單,只需呼叫 optim.Adam() 並傳入需要更新的網路權重和學習率即可:

import torch.optim as optim

# 假設 simplenet 是我們的神經網路模型
optimizer = optim.Adam(simplenet.parameters(), lr=0.001)

訓練迴圈

完整的訓練迴圈結合了資料載入、模型預測、損失計算、梯度計算和權重更新等步驟。以下是 PyTorch 中的典型訓練迴圈:

for epoch in range(epochs):
    for batch in train_loader:
        optimizer.zero_grad()
        input, target = batch
        output = model(input)
        loss = loss_fn(output, target)
        loss.backward()
        optimizer.step()

內容解密:

  1. optimizer.zero_grad():清除之前的梯度,避免梯度累積。
  2. input, target = batch:從資料載入器中取得輸入資料和對應標籤。
  3. output = model(input):將輸入資料傳入模型進行預測。
  4. loss = loss_fn(output, target):計算預測輸出與真實標籤之間的損失。
  5. loss.backward():計算損失函式相對於模型引數的梯度。
  6. optimizer.step():根據計算出的梯度更新模型引數。

加速訓練:使用 GPU

為了加速訓練,可以將模型和資料移動到 GPU 上。使用 PyTorch 的 to() 方法可以輕鬆實作這一點:

if torch.cuda.is_available():
    device = torch.device("cuda")
else:
    device = torch.device("cpu")

model.to(device)

內容解密:

  1. torch.cuda.is_available():檢查系統是否可用 GPU。
  2. model.to(device):將模型移動到指定的裝置(GPU 或 CPU)上。

結合所有步驟

最終,我們可以將上述步驟結合起來,建立一個通用的訓練函式,用於訓練神經網路模型:

def train(model, optimizer, loss_fn, train_loader, val_loader,
          epochs=20, device="cpu"):
    for epoch in range(epochs):
        training_loss = 0.0
        valid_loss = 0.0
        model.train()
        # 省略其他實作細節...

內容解密:

  1. model.train():將模型設定為訓練模式。
  2. training_lossvalid_loss:用於跟蹤訓練和驗證過程中的損失。

透過這種方式,我們可以靈活地訓練不同的神經網路模型,並根據需要調整超引數,如學習率和批次大小,以獲得最佳的訓練效果。

使用PyTorch進行影像分類別的完整流程

在上一段落中,我們完成了神經網路模型的訓練,接下來將探討如何使用該模型進行預測以及如何儲存和載入模型引數。

進行預測

在訓練完成後,我們可以使用模型對新的影像進行分類別。以下是一個簡單的Python程式碼範例,展示如何載入一張影像並使用我們的神經網路模型進行預測:

from PIL import Image
labels = ['cat', 'fish']
img = Image.open(FILENAME)
img = transforms(img)
img = img.unsqueeze(0)
prediction = simplenet(img)
prediction = prediction.argmax()
print(labels[prediction])

程式碼解析:

  1. 載入影像:使用Image.open(FILENAME)從檔案系統中載入影像。
  2. 轉換影像:重用之前建立的轉換流程transforms,將影像轉換為神經網路所需的格式。
  3. 擴充維度:由於模型需要批次輸入,使用unsqueeze(0)在張量的最前面新增一個新的維度,以模擬批次大小為1的輸入。
  4. 進行預測:將處理好的影像輸入模型simplenet,獲得預測結果。
  5. 取得預測類別:使用argmax()函式找出具有最高機率的類別索引,並對應到labels列表中的類別名稱。

儲存和載入模型

在訓練過程中或訓練完成後,我們可能需要儲存模型的引數以便未來使用。PyTorch提供了torch.save()torch.load()方法來實作模型的儲存和載入。

儲存模型

torch.save(simplenet, "/tmp/simplenet")

或者,更常見的是儲存模型的state_dict

torch.save(model.state_dict(), PATH)

載入模型

載入整個模型:

simplenet = torch.load("/tmp/simplenet")

或者,先例項化模型,然後載入state_dict

simplenet = SimpleNet()
simplenet_state_dict = torch.load("/tmp/simplenet")
simplenet.load_state_dict(simplenet_state_dict)

儲存模型的優點

儲存state_dict比儲存整個模型更靈活,尤其是在模型的結構發生變化時,可以透過設定strict=False來載入部分匹配的引數。

圖示說明:

此圖示展示了使用PyTorch進行影像分類別的主要步驟,從資料準備到模型訓練、評估、儲存、載入,直至最終的預測。每一環節都是深度學習流程中不可或缺的一部分。