iT邦幫忙

2024 iThome 鐵人賽

DAY 3
0
AI/ ML & Data

【AI筆記】30天從論文入門到 Pytorch 實戰系列 第 3

【AI筆記】30天從論文入門到 Pytorch 實戰:Pytorch 訓練流程全解析 Day 2

  • 分享至 

  • xImage
  •  

本篇將會使用 Pytorch 官方所撰寫的 code 進行流程介紹

Pytorch 訓練流程

流程

  1. Custom Dataset: 定義數據集,讀取圖片和標籤。
  2. DataLoader: 將 Dataset 分批次加載,並調用 Dataset getitem 方法獲取數據。
  3. Model 訓練: 使用加載的 Data 進行模型訓練,包括前向傳播、計算損失、反向傳播和更新參數。
  4. Model 評估: 在 Test Dataset 上評估模型性能,計算損失和準確率。

建立資料集

要先把資料整理成需要的格式,成對的(圖像, label),Dataloader 取資料的時候才不會拿錯。

MNIST Dataset

torchvision datasets 已經幫你打包好了,不用特別自己寫。
以下為資料內容

from torchvision import datasets, transforms

transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1305,), (0.3171,))
        ])
train_dataset = datasets.MNIST('./data', train=True, download=True,
                      transform=transform)
test_dataset = datasets.MNIST('./data', train=False,
                      transform=transform)
# 印出 Test Dataset 有幾筆資料
print(test_dataset.data.size())
print(test_dataset.targets.size())
# 印出第一筆資料的資料內容
print(test_dataset.data[0])
print(test_dataset.targets[0])
  • test dataset 中資料維度 torch.Size([10000, 28, 28]),10000筆圖片 1張圖片size為28*28
  • 第一筆資料 label 為 7
    有了圖片、label種類資訊就可以設計 Model。

Custom Dataset

如果是用 Custom Dataset 看流程圖會比較好理解,因為 MNIST 都打包好了比較難懂流程。
Custom Dataset: 需要自己整理抓取資料標籤、圖片的位址。
以下3個function是一定要有:

  • def init
  • def len
  • def getitem
class CustomImageDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, test_transform=None):
        self.img_labels = pd.read_csv(annotations_file)
        self.img_dir = img_dir
        self.transform = transform
        self.target_transform = target_transform

    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
        image = read_image(img_path)
        label = self.img_labels.iloc[idx, 1]
        if self.transform:
            image = self.transform(image)
        if self.test_transform:
            label = self.test_transform(label)
        return image, label

Train Transform 和 test transform 要分開寫,後續會講解。

DataLoader

訓練開始時,會 call Dataloader 去取資料,在程式碼中 train_loader會執行流程圖中黃色區塊,Dataloader再去找 Dataset 的 __getitem__ 取資料,Dataloader 會給一個 index,在 Dataset 的 list 中取 N 筆,因為batch size=64,所以 Dataloder 最後回傳的資料會是(batch_size, dim, W, H)=(64, 1, 28, 28)

Model

訓練時模型會 call forward 而x就是我們input的data。最後一層Linear輸出為類別的數量。若要在更詳細,可以去看其他人的教學。

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        ...
        self.fc1 = nn.Linear(9216, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        ...
        output = F.log_softmax(x, dim=1)
        return output

Training Model

讓我們詳細講解一下模型訓練的過程。

  • model.train():設置模型為訓練模式
  • for batch_idx, (data, target) in enumerate(train_loader):加載 data
  • data.to(device):移動到GPU/CPU
  • optimizer.zero_grad():每次反向傳播之前,我們需要清空優化器中的梯度。這是因為 PyTorch 中的梯度是累加的(即每次反向傳播時,梯度會累加到已存在的梯度上)。
  • output = model(data):前向傳播,獲得模型的輸出
  • loss = F.nll_loss(output, target):計算損失
  • loss.backward():計算損失相對於模型參數的梯度。
  • optimizer.step():使用計算出的梯度來更新模型的參數。這是通過優化器(如 SGD 或 Adam)實現的。
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
    data, target = data.to(device), target.to(device)
    optimizer.zero_grad()
    output = model(data)
    loss = F.nll_loss(output, target)
    loss.backward()
    optimizer.step()

Testing Model

  • with torch.no_grad()::禁用梯度計算,因為在評估模型時不需要進行反向傳播和參數更新,這樣可以節省內存和計算資源。
  • F.nll_loss(output, target, reduction='sum').item():計算模型輸出與真實標籤之間的損失,並將每個批次的損失累加起來。這裡使用的是負對數似然損失(Negative Log-Likelihood Loss),並將 reduction 設置為 'sum',以便累加每個批次的損失。 .item()取出torch裡面的數值。
  • output.argmax(dim=1, keepdim=True):獲取模型輸出中機率最大的類別索引。

Colab 程式碼

要自己把args改掉,不能使用args,使用到args的地方自行修掉。

# Training settings
batch_size = 64
test_batch_size = 1000
epochs = 14
lr = 1.0
gamma = 0.7
no_cuda = False
no_mps = False
dry_run = False
seed = 1
log_interval = 10
save_model = False

use_cuda = not no_cuda and torch.cuda.is_available()
use_mps = not no_mps and torch.backends.mps.is_available()

torch.manual_seed(seed)

小作業

上次使用的是Tensorflow的Keras進行簡單的手寫辨識模型訓練。
現在來實際做一下簡單的 loss function 改寫,原本只有使用到SparseCategoricalCrossentropy,但實際上我們訓練的時候不會只有使用到一個loss。

基本上 pytorch 和 keras 沒有太大的差異,在改寫 loss function 的方法都是一樣的。


請在上次的程式碼中的 loss 改成 L1 LossSparseCategoricalCrossentropyCategoricalCrossentropy相加。

  1. 撰寫 custom_loss function:
    首先,我們需要定義一個自訂的損失函數,將 L1 LossSparseCategoricalCrossentropyCategoricalCrossentropy 相加
    • 因為CategoricalCrossentropy他需要兩者維度相同,所以要先把 y_true 轉 one hot ,因為 y_true維度 (32,)y_pred 的維度是(32,10)
    • L1 loss 也是需要維度相同。
# 定義自訂的損失函數
from tensorflow.keras.utils import to_categorical # Import to_categorical
def custom_loss(y_true, y_pred):
    # SparseCategoricalCrossentropy 損失
    scce = SparseCategoricalCrossentropy()(y_true, y_pred)
    # TODO: CategoricalCrossentropy 損失: 轉onehot使用 to_categorical、CategoricalCrossentropy()
  
    # TODO: L1 損失: tf.reduce_mean(tf.abs(y_pred - y_true_onehot)) 
    
    # TODO: 結合3個損失
    
    return combined_loss
  1. 進行 model 訓練
    接下來,我們可以使用這個自訂的損失函數來訓練模型。上次你已經有一個簡單的手寫辨識模型,這裡是如何應用這個自訂損失函數?
import tensorflow as tf
# 匯入 Keras 內建的損失函數
from tensorflow.keras.losses import SparseCategoricalCrossentropy, CategoricalCrossentropy
# Import to_categorical
from tensorflow.keras.utils import to_categorical

# 建立一個簡單的神經網絡模型
model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),  # 將 28x28 的圖像展平成 784 的一維向量
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

# TODO: 編譯模型,使用自訂的損失函數
model.compile(optimizer='adam',
              loss='TODO',
              metrics=['accuracy'])

# 加載數據
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# 訓練模型
model.fit(x_train, y_train, epochs=5)

# 評估模型
model.evaluate(x_test, y_test)

如果不會的話,可以直接看最底下答案。

Reference

答案

import tensorflow as tf

# 匯入 Keras 內建的損失函數
from tensorflow.keras.losses import SparseCategoricalCrossentropy, CategoricalCrossentropy
# Import to_categorical
from tensorflow.keras.utils import to_categorical 

# 定義自訂的損失函數
def custom_loss(y_true, y_pred):
    # sparse_categorical_crossentropy 損失
    scce = SparseCategoricalCrossentropy()(y_true, y_pred)
    # 將 y_true 轉換成 one-hot 編碼,因為 y_true維度 (32,) 而 y_pred 的維度是(32,10)
    y_true_onehot = to_categorical(y_true, num_classes=10) 
    # cross_entropy 損失
    cross_entropy = CategoricalCrossentropy()(y_true_onehot, y_pred) # Use one-hot encoded labels
    # L1 loss
    l1_loss = tf.reduce_mean(tf.abs(y_pred - y_true_onehot)) # 取平均值
    # 結合3個損失
    combined_loss = scce + cross_entropy + l1_loss
    return combined_loss

# 建立一個簡單的神經網絡模型
model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),  # 將 28x28 的圖像展平成 784 的一維向量
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

# 編譯模型,使用自訂的損失函數
model.compile(optimizer='adam',
              loss=custom_loss,
              metrics=['accuracy'])

# 加載數據
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# 訓練模型
model.fit(x_train, y_train, epochs=5)

# 評估模型
model.evaluate(x_test, y_test)

答案


上一篇
【AI筆記】30天從論文入門到 Pytorch 實戰:環境搭建與 Colab 入門指南 Day 1
下一篇
【AI筆記】30天從論文入門到 Pytorch 實戰:資料集讀取技巧與最佳實踐 Day 3
系列文
【AI筆記】30天從論文入門到 Pytorch 實戰30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言