跳到主要內容

Day 37 專案規劃與資料處理

Day 37 專案規劃與資料處理

引言

Day 32 到 Day 36 把我們帶進了影像、序列兩大類深度學習任務的核心模型,也實際訓練了 CNN 與 LSTM。不過模型只是整個機器學習專案的一部分,從「資料」到「可用的成果」之間,還有一段扎實的工程流程要走。從今天起的三篇,會把整個系列做一個收尾,用一個端到端的影像分類專案走完資料處理、模型設計、評估與部署。

這一篇的主題是「專案規劃與資料處理」。我們會先把任務定義清楚,建立一個可重現的專案結構,然後把資料從原始 CSV/資料夾,整理成可以直接餵給 DataLoader 的乾淨版本。這些步驟雖然不像模型設計那麼「炫」,卻決定了後續能不能順利往下走。實務上,有經驗的工程師花在資料處理的時間,往往比花在模型設計的時間還要多——因為模型可以靠經驗法則快速決定,資料卻藏著各種意想不到的坑。

定義任務與評估指標

在寫任何一行程式碼之前,先把任務寫下來:要做什麼、用什麼資料、用什麼指標衡量成功。這一步看起來理所當然,但很多人會跳過,直接進入寫模型階段,結果做了一堆事後才發現任務定義不清。

以這次的範例為例,我們要做的任務是「街景門牌號碼分類」,也就是著名的 SVHN(Street View House Numbers)資料集。我們要從 32×32 的彩色圖片中,預測中間那一個數字(0 到 9)的類別。這個任務的特性是:

  • 資料規模:訓練約 73,257 張、測試約 26,032 張,圖片大小固定為 32×32。
  • 類別數:10 類(數字 0 到 9),屬於平衡資料集。
  • 評估指標:以「分類正確率(Accuracy)」為主,因為類別分布均勻,Accuracy 就能反映模型能力。
  • 成功標準:在測試集達到 95% 以上的正確率,並確認模型沒有過擬合。

把這些寫下來後,後續的模型設計、調參、驗證都有了清楚的錨點,避免方向漂移。一個常見的小技巧是把任務定義寫成 README 的第一段,並在每次開新實驗前重新讀一次;當你發現自己已經在嘗試「看起來很帥但跟任務無關」的模型時,就能及時拉回來。

建立專案結構

把檔案與目錄規劃好,是專案能否順利協作的基礎。這個範例採用簡單但可擴充的結構:

svhn_project/
├── data/                # 原始與處理後的資料
├── notebooks/           # 探索用 notebook
├── src/
│   ├── data.py          # 資料載入與前處理
│   ├── model.py         # 模型定義
│   ├── train.py         # 訓練流程
│   ├── evaluate.py      # 評估與視覺化
│   └── export.py        # 匯出模型
├── configs/
│   └── default.yaml     # 超參數設定檔
├── checkpoints/         # 訓練過程中的權重
└── README.md            # 專案說明

這樣的結構把「資料、程式、設定、產出」分開,等專案變大時比較好維護。對個人小專案來說,src/ 下的四個檔案(data、model、train、evaluate)已經足夠涵蓋整個流程;規模再大一些,可以再切成 train/、infer/、utils/ 等子資料夾。

另外,建議一開始就把 configs/ 與 checkpoints/ 這兩個目錄放進 .gitignore:前者裝本地開發用的設定(例如資料路徑、API 金鑰),後者裝訓練產生的權重(檔案太大,不適合進版控)。權重如果要長期保留,可以另外放雲端硬碟或模型 registry(MLflow、DVC 等工具都支援)。

把資料處理模組化

把所有跟資料有關的程式集中在 src/data.py,後續不論是訓練、評估或部署,都只要呼叫同一支程式就能拿到一致的資料。下方是一個典型的骨架:

from torchvision import datasets, transforms
from torch.utils.data import DataLoader, random_split

# 統一的資料轉換流程
TRANSFORM_TRAIN = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.RandomHorizontalFlip(),
    transforms.ColorJitter(brightness=0.2, contrast=0.2),
    transforms.ToTensor(),
    transforms.Normalize((0.4377, 0.4438, 0.4728), (0.1980, 0.2010, 0.1970)),
])

TRANSFORM_EVAL = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize((0.4377, 0.4438, 0.4728), (0.1980, 0.2010, 0.1970)),
])

def get_dataloaders(data_dir, batch_size=128, val_ratio=0.1):
    full_train = datasets.SVHN(
        root=data_dir, split="train", download=True, transform=TRANSFORM_TRAIN
    )
    val_len = int(len(full_train) * val_ratio)
    train_len = len(full_train) - val_len
    train_set, val_set = random_split(full_train, [train_len, val_len])
    test_set = datasets.SVHN(
        root=data_dir, split="test", download=True, transform=TRANSFORM_EVAL
    )

    return (
        DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=2),
        DataLoader(val_set, batch_size=batch_size, shuffle=False, num_workers=2),
        DataLoader(test_set, batch_size=batch_size, shuffle=False, num_workers=2),
    )

把訓練用與評估用的 transforms 分開是關鍵:訓練可以加資料增強(翻轉、調亮度),評估必須保持一致的可重現性,否則指標會變得難以解讀。Normalize 的平均值與標準差是 SVHN 這個資料集本身的統計值,先用對的值能讓模型更快收斂。

實務上也可以考慮把 TRANSFORM_TRAIN 與 TRANSFORM_EVAL 寫成設定檔的一部份(例如 configs/default.yaml),這樣同一支程式就能在不同任務間快速切換,而不必改程式碼。對個人小專案來說直接寫在 data.py 裡也夠用,但如果你的程式會在多個任務間共用,設定檔會是更好的設計。

驗證集切分與隨機種子

上面 random_split 的寫法很方便,但它每次執行的切分結果都不一樣。這在小型實驗還好,但在正式實驗中很容易造成「同一個模型,這次跑 95%、下次跑 94.5%」的困擾。固定隨機種子是最低成本的解法。

import torch
import random
import numpy as np

def set_seed(seed: int = 42) -> None:
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed_all(seed)
        torch.backends.cudnn.deterministic = True
        torch.backends.cudnn.benchmark = False

def get_dataloaders(data_dir, batch_size=128, val_ratio=0.1, seed=42):
    set_seed(seed)
    full_train = datasets.SVHN(
        root=data_dir, split="train", download=True, transform=TRANSFORM_TRAIN
    )
    val_len = int(len(full_train) * val_ratio)
    train_len = len(full_train) - val_len
    generator = torch.Generator().manual_seed(seed)
    train_set, val_set = random_split(full_train, [train_len, val_len], generator=generator)
    test_set = datasets.SVHN(
        root=data_dir, split="test", download=True, transform=TRANSFORM_EVAL
    )
    return (
        DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=2),
        DataLoader(val_set, batch_size=batch_size, shuffle=False, num_workers=2),
        DataLoader(test_set, batch_size=batch_size, shuffle=False, num_workers=2),
    )

把 set_seed 放在最前面,並讓 random_split 使用帶種子的 generator,是 PyTorch 專案裡常見的做法。配合 cudnn.deterministic = True 與 cudnn.benchmark = False,可以讓 GPU 上的運算也接近可重現。不過要提醒:完全可重現有時會犧牲一點效能,正式上線的程式不一定要這麼嚴格,但在實驗與除錯階段非常有幫助。

資料探索:先看一眼資料

資料處理的最後一步,建議在進入訓練前先用 matplotlib 畫幾張圖,確認資料沒有怪東西(例如全黑、標籤錯置)。這個動作只要 5 分鐘,卻能省下之後好幾小時的除錯時間。

import matplotlib.pyplot as plt
from torchvision import datasets

def preview_samples(data_dir, n=8):
    ds = datasets.SVHN(root=data_dir, split="train", download=True)
    fig, axes = plt.subplots(1, n, figsize=(12, 2))
    for i, ax in enumerate(axes):
        img, label = ds[i]
        ax.imshow(img)
        ax.set_title(f"label={label}")
        ax.axis("off")
    plt.tight_layout()
    plt.savefig("checkpoints/sample_preview.png", dpi=120)
    print("已存到 checkpoints/sample_preview.png")

# preview_samples("data")

把這個函式放在 notebooks/eda.ipynb 或 src/data.py 都可。它的目的是在「資料長相」與「程式碼預期」之間建立連結:當你預期資料是 32×32 的彩色影像,實際看到的卻是 64×64 的灰階,就可以馬上回頭修 transforms,不用等到訓練結果離譜才發現。

另一個值得養成的習慣,是把資料統計值(平均值、標準差、類別分布)寫進 README 或資料說明檔。下次換到類似的影像任務時,可以參考這份資料,決定要用什麼樣的 Normalize、需不需要做類別加權。SVHN 之所以選 (0.4377, 0.4438, 0.4728) 與 (0.1980, 0.2010, 0.1970),就是根據該資料集 73,257 張訓練影像計算出來的全域統計值,這樣的設定通常比「全用 0.5」更貼近實際分布,能讓模型更快收斂。

結語

這一篇把「任務定義、專案結構、資料模組化、隨機種子、資料探索」這五件事串起來。雖然表面上看不出模型準確率提升,但它們是後續三天(模型設計、評估、部署)的基礎。當專案結構與資料流都已經穩定之後,後續的實驗會跑得順暢很多。

在真實團隊中,資料工程師會把這部分做得更重:建立資料版本(Data Version Control)、寫資料驗證腳本、設置資料品質監控。但在個人或小型團隊的學習階段,先把上面這幾個關鍵動作做好,就足以支撐一個完整的深度學習專案。把這些當作肌肉記憶養成,後面不管遇到什麼任務都能少踩坑。

明天,我們會在這個骨架上設計模型架構、挑選超參數,並說明訓練過程中該怎麼根據驗證曲線調整學習率。

留言

這個網誌中的熱門文章

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 建構深度學習模型。 開發者與研究人員 :想更深入了...