跳到主要內容

Day 28 資料載入

Day 28 資料載入

引言

Day 27 我們認識了 autograd,理解神經網路怎麼自動算出梯度。但要在自己的資料上訓練模型,光有自動微分還不夠:我們得先把資料讀進來、做預處理、切成小批次送進模型。當資料量小的時候,把整份資料塞進記憶體直接用也沒問題;一旦資料筆數到達數萬甚至數十萬、每筆資料又是高解析度的影像時,就得改用 PyTorch 提供的標準化機制:Dataset 與 DataLoader。

這兩者屬於 torch.utils.data 模組,是訓練模型時最常打交道的一組工具。Dataset 負責「怎麼從磁碟或記憶體拿到單筆資料」,DataLoader 則負責「怎麼把單筆資料組成訓練用的批次」。兩者分工清楚,學會之後不管是處理 MNIST、CIFAR-10 還是公司內部的資料集,都能套用同樣的模式。今天會用三個小節,分別說明 Dataset、transforms 與 DataLoader 的用法。

Dataset:定義「如何取得單筆資料」

Dataset 是一個抽象類別,規定只要實作兩個方法,就能讓資料被 PyTorch 的訓練流程正確使用:__len__() 回傳資料集的總筆數,__getitem__(idx) 根據索引回傳第 idx 筆資料與對應的標籤。

我們先做一個最簡單的範例:用 5 筆假的特徵與標籤,示範如何把串列包裝成 Dataset。

import torch
from torch.utils.data import Dataset

class ToyDataset(Dataset):
    def __init__(self):
        # 5 筆特徵,每筆有 2 個數值
        self.features = torch.tensor(
            [[1.0, 2.0],
             [3.0, 4.0],
             [5.0, 6.0],
             [7.0, 8.0],
             [9.0, 10.0]]
        )
        # 對應的標籤(0 或 1)
        self.labels = torch.tensor([0, 1, 0, 1, 0])

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

    def __getitem__(self, idx):
        return self.features[idx], self.labels[idx]

dataset = ToyDataset()
print(len(dataset))           # 輸出:5
print(dataset[0])               # 輸出:(tensor([1., 2.]), tensor(0))
print(dataset[2])              # 輸出:(tensor([5., 6.]), tensor(0))

dataset[0] 直接用索引取值,這是因為 __getitem__ 的行為和 Python 的串列一樣;這個小細節讓我們可以很直覺地檢查資料內容,也可以搭配 for 迴圈走訪每一筆。

實務上,__getitem__ 才是真正「讀資料」的地方。常見的做法是在這裡從硬碟讀取單張影像(例如用 PIL 或 OpenCV),或是從大型 HDF5、Parquet 檔案中取出單筆。這樣做的好處是:訓練過程一次只需要一個 batch 的資料,可以大幅節省記憶體。

transforms:把原始資料轉成模型能吃的格式

影像資料在送進模型前通常需要預處理。最常見的幾個步驟是:調整尺寸、轉成張量、把像素值從 0–255 縮放到 0–1、正規化到特定範圍。PyTorch 把這些常用操作整理在 torchvision.transforms 模組,並用 Compose 串接成一條預處理管線。

下面示範如何把一張 32×32 的彩色影像,先調整成 28×28,再轉成張量與正規化。

import torch
from torchvision import transforms

# 建立預處理管線
transform = transforms.Compose([
    transforms.Resize((28, 28)),       # 縮放到 28x28
    transforms.ToTensor(),              # PIL Image / ndarray -> Tensor,並縮放到 [0, 1]
    transforms.Normalize(               # 用均值與標準差做正規化
        mean=[0.5, 0.5, 0.5],          # 三個通道
        std=[0.5, 0.5, 0.5]
    ),
])

# 假設有一張 PIL Image 影像
from PIL import Image
img = Image.new("RGB", (32, 32), color=(127, 127, 127))

x = transform(img)
print(x.shape)                         # 輸出:torch.Size([3, 28, 28])

Resize 接受目標尺寸,ToTensor 會把像素從整數範圍 0–255 轉成 0–1 的浮點數,並把維度從 HWC(高、寬、通道)改成 CHW(通道、高、寬),這是 PyTorch 期待的順序。Normalize 用給定的平均值與標準差做 Z-score 正規化,把像素再縮到接近 −1 到 1 的範圍。許多預訓練模型(如 ResNet)有官方建議的 mean/std,最好沿用。

transforms 的設計是「給 PIL Image 或 Tensor,輸出 Tensor」。當我們把 transform 物件傳給 Dataset 時,可以在 __getitem__ 裡套用:

class ImageDataset(Dataset):
    def __init__(self, image_paths, labels, transform=None):
        self.image_paths = image_paths
        self.labels = labels
        self.transform = transform

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

    def __getitem__(self, idx):
        img = Image.open(self.image_paths[idx]).convert("RGB")
        if self.transform is not None:
            img = self.transform(img)
        return img, self.labels[idx]

這段程式示範裡的 __getitem__ 開啟硬碟上的影像檔、用 transform 做預處理、再回傳張量與標籤。如果之後想加資料增強(Data Augmentation),只要在 Compose 裡加 RandomHorizontalFlip() 這類方法即可,概念一致。

DataLoader:把資料組成批次送進模型

Dataset 解決了「怎麼拿單筆資料」,DataLoader 則負責把多筆資料組合成訓練用的批次(batch)。它還會幫我們處理 shuffle、多執行緒讀取、把多筆張量沿著第一個維度堆疊等瑣事。

延續前面的 ToyDataset,用 DataLoader 把它包起來:

from torch.utils.data import DataLoader

dataset = ToyDataset()
loader = DataLoader(dataset, batch_size=2, shuffle=True)

for batch_features, batch_labels in loader:
    print("特徵批次:", batch_features)
    print("標籤批次:", batch_labels)
    print("---")

# 輸出(順序會因 shuffle 而不同):
# 特徵批次:tensor([[3., 4.], [1., 2.]])
# 標籤批次:tensor([1, 0])
# ---
# 特徵批次:tensor([[5., 6.], [9., 10.]])
# 標籤批次:tensor([0, 0])
# ---
# 特徵批次:tensor([[7., 8.]])
# 標籤批次:tensor([1])

batch_size=2 表示每個 batch 包含 2 筆資料;shuffle=True 表示每個 epoch 開始前會把資料順序打亂,這對訓練的穩定性很有幫助。最後一批可能湊不到 batch_size 筆(例如上例 5 筆資料、batch_size=2 時,最後一批只有 1 筆),這是正常的。

在 GPU 上訓練時,DataLoader 還能配合 num_workers 用多個子行程同時讀資料,縮短 GPU 等待時間。常見的設定是 num_workers=2 或 num_workers=4,不過 Windows 在多行程讀取上有時候會遇到相容性問題,如果出現錯誤可以先設回 num_workers=0,再用其他方式加速。

loader = DataLoader(
    dataset,
    batch_size=64,
    shuffle=True,
    num_workers=2,    # 用 2 個子行程讀資料
    pin_memory=True,  # 若用 CUDA,啟用可加速 CPU->GPU 傳輸
)

pin_memory=True 會把張量放在「鎖定」的記憶體區域,加快之後搬到 GPU 的速度;不過只在 CUDA 環境有效,CPU 訓練時設成 False 也沒關係。把 Dataset、transforms 與 DataLoader 串起來,就是訓練深度學習模型的標準資料管線。

把三層串起來:完整的小範例

為了把觀念釐清,下面把 Dataset、transforms 與 DataLoader 一次串起來。雖然這個範例還是用合成資料,但只要把 __getitem__ 換成讀取硬碟影像的程式碼,就能直接套用到真實任務上。

import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms

class NumberDataset(Dataset):
    def __init__(self, n_samples=100, transform=None):
        self.features = torch.randn(n_samples, 3, 32, 32)   # 假裝是 3x32x32 影像
        self.labels = torch.randint(0, 10, (n_samples,))
        self.transform = transform

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

    def __getitem__(self, idx):
        x = self.features[idx]
        if self.transform is not None:
            x = self.transform(x)
        return x, self.labels[idx]

transform = transforms.Compose([
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
])

dataset = NumberDataset(transform=transform)
loader = DataLoader(dataset, batch_size=8, shuffle=True)

for batch_x, batch_y in loader:
    print(batch_x.shape, batch_y.shape)
    break

# 輸出:torch.Size([8, 3, 32, 32]) torch.Size([8])

這個範例說明了一個重點:DataLoader 會自動把同一批次的「資料張量」沿著第一個維度疊起來,把「標籤張量」也沿著第一個維度疊起來,我們不必自己寫 stack 程式碼。如果資料格式比較特殊(例如每筆資料長度不同的文字),可以額外傳入 collate_fn 自訂疊法,這部分在 Day 35 處理文字資料時會再提到。

結語

今天把訓練流程的「資料這一端」整理了一遍:Dataset 定義如何取得單筆資料與標籤,transforms.Compose 把預處理與資料增強串成一條管線,DataLoader 則把資料組成批次並交給模型。掌握這三層分工之後,不管資料是存在記憶體、硬碟上的影像、還是大型資料庫,都能用同樣的模式包進訓練流程。

明天,我們會把神經網路這一端補上。PyTorch 用 torch.nn.Module 把模型結構與參數統一管理,搭配 nn.Linear 與激活函式,就能搭出第一個全連接層構成的網路,並用前向傳播得到預測值。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...