本文作者是 AI 工程的初學者,正透過《AI Engineering from Scratch》課程自學,並全程搭配 AI 輔助:一步步照著課程開發者的進度動手執行,卡關時就向 AI 提問、請它引導。這篇文章,是把作者與 AI 互動的完整過程與學習歷程,先交由 AI 記錄、統整成初稿,再由作者親自逐段審視、修改、優化而成。AI 負責整理,最終判斷與文字由作者把關。
寫下它有兩個用意:替後來的讀者鋪一條能照著走的路徑,也幫作者自己複習、把學過的東西沉澱下來。
這堂課在 AI 工程裡的位置
做一個 AI 應用,不管是 chatbot、RAG、還是 Agent,整條路大概是:資料 → 模型 → 應用。資料是最前面的,沒有資料什麼都做不了。
這堂課教的就是「資料的基礎建設」:怎麼拿到資料、怎麼存、怎麼切成訓練用的格式、怎麼管那些動不動幾百 MB 的大檔案。不涉及模型訓練,純粹是把原料管好。
這一課導覽框
- 課名:Phase 0 - Lesson 9 — Data Management
- 目標:學會用 Hugging Face
datasets載入資料集、轉換格式、切分 train/val/test,以及管理大型檔案- 時間:課程標約 45 分鐘,我邊理解邊動手大概花了一個半小時
- Mac 使用者:這一課沒有 Mac 特有的問題
- 完課驗證:跑
data_utils.py,看到All checks passed
安裝兩個套件
這堂課要裝兩個 Python 套件:
uv pip install datasets huggingface_hub
datasets 負責載入、轉換、切分資料集。huggingface_hub 負責下載模型檔案。兩個都是 Hugging Face 出的。
怎麼知道它們是 Python 套件?因為課程寫的是 pip install。看到 pip install 就知道是 Python 的東西,用 uv pip install 裝進 venv。看到 brew install 就是系統工具,看到 npm install 就是 Node.js 的。判斷方式就是看課程用什麼指令裝。
載入資料集:整包下載
from datasets import load_dataset
dataset = load_dataset("stanfordnlp/imdb")
print(dataset)
print(dataset["train"][0])
from datasets import load_dataset 是從 datasets 套件裡拿出 load_dataset 這個功能。不需要把整個套件的功能都拿出來,只拿你要用的。
load_dataset("stanfordnlp/imdb") 去 Hugging Face 平台下載 IMDB 電影評論資料集。stanfordnlp 是上傳者(史丹佛大學的 NLP 團隊),imdb 是資料集名稱。下載完會自動存到 ~/.cache/huggingface/datasets/,下次用同一個資料集直接從快取讀,不用重下。
dataset = ... 把下載回來的東西存進一個叫 dataset 的變數,之後用這個名字操作它。
print(dataset) 印出整體結構。IMDB 有三個切分:train(25,000 筆)、test(25,000 筆)、unsupervised(50,000 筆)。每一筆有兩個欄位:text(評論文字)和 label(0 = 負評,1 = 正評)。
dataset["train"][0] 是取出訓練集的第 0 筆(Python 從 0 開始數)。看到的就是一篇英文電影評論,加上它的標籤。
串流:不下載,一筆一筆讀
dataset = load_dataset("stanfordnlp/imdb", split="train", streaming=True)
for i, example in enumerate(dataset):
print(example["text"][:80])
if i >= 4:
break
streaming=True 開啟串流模式。資料不會下載到硬碟,而是一筆一筆從網路讀進來。
split="train" 是指定只要訓練集。IMDB 的作者在上傳時就已經把資料切好了 train / test / unsupervised 三份,這裡只是選其中一份來看。
for i, example in enumerate(dataset): 是一筆一筆取出來。i 是計數器(0, 1, 2…),example 是那筆資料。enumerate 的功能就是同時告訴你「第幾個」和「內容是什麼」。
example["text"][:80] 取這筆資料的 text 欄位,只看前 80 個字元。[:80] 是 Python 切片語法,「從頭到第 80 個字」。
if i >= 4: break 數到第 5 筆(從 0 開始,i=4 是第 5 筆)就停下來。不加這行它會一直讀下去。
整包下載跟串流的差別
結果一樣,都是拿到同一份資料。差別在怎麼拿。
整包下載:一次全部搬回家,存進快取。之後每次用都從本地讀,速度快。適合大部分情況。
串流:不下載,一筆一筆從網路讀。適合資料集大到硬碟放不下的時候(像整個維基百科、Common Crawl 爬蟲資料)。讀完就丟掉,記憶體用量固定。
這門課的資料集都不大,用整包下載就好。
四種資料格式
同一份資料可以用不同格式儲存。課程介紹了四種:
CSV
打開來長這樣:
text,label
"I loved this movie",1
"Terrible film",0
第一行是欄位名稱,之後每行是一筆資料,欄位之間用逗號隔開。用 Excel 打開就能看。人類最容易讀懂,但檔案大、電腦處理起來慢。
什麼時候會碰到:客戶給你資料、從網站匯出報表、Excel 另存新檔,通常都是 CSV。
JSON
打開來長這樣:
[
{"text": "I loved this movie", "label": 1},
{"text": "Terrible film", "label": 0}
]
每筆資料是一組 {key: value}。跟 CSV 最大的差別是它能巢狀,資料裡面還能包資料。檔案也大。
什麼時候會碰到:API 回傳的資料幾乎都是 JSON。之前呼叫 OpenAI API 收到的回應就是 JSON。
Parquet
打不開。它是二進位格式,不是給人類看的。
關鍵差異是列式儲存。CSV 是一行一行存的,Parquet 是一欄一欄存的。同一欄的資料類型相同(全是文字或全是數字),壓縮率高。如果你只需要某一欄,Parquet 可以只讀那一欄,不用把整份資料載進來。
什麼時候會碰到:訓練資料存檔、大型資料集(Hugging Face 上很多底層就是 Parquet)。
Arrow
你不會直接碰到它的檔案。Arrow 是記憶體裡的格式。load_dataset() 把資料載進 RAM 之後,底層就是 Arrow。datasets 套件之所以快,就是因為用了它。你不需要管它怎麼運作。
它們的分工
不是互相競爭,是各負責不同場景:
CSV / JSON 負責資料的「進」和「出」。別人給你的、你要交給別人的,通常是這兩種,因為人看得懂。
Parquet 負責資料的「存」。長期保存、省空間、快速讀取,用 Parquet。
Arrow 負責資料的「用」。程式跑起來的時候記憶體裡自動是 Arrow,不用操心。
一條典型的資料流:別人給你 CSV → 你轉成 Parquet 存著 → 程式載入時自動變 Arrow → 處理完存 Parquet → 要給別人時轉回 CSV 或 JSON。
動手轉檔
dataset = load_dataset("stanfordnlp/imdb", split="train")
dataset.to_csv("/tmp/imdb_train.csv")
dataset.to_json("/tmp/imdb_train.json")
dataset.to_parquet("/tmp/imdb_train.parquet")
to_csv()、to_json()、to_parquet() 就是把資料存成對應的格式。/tmp/ 是 Mac 的暫存資料夾。
然後用 os.path.getsize() 查檔案大小:
| 格式 | 大小 |
|---|---|
| CSV | 33,322,167 bytes |
| JSON | 33,880,437 bytes |
| Parquet | 20,516,856 bytes |
同一份資料,Parquet 比 CSV 小了 1.6 倍左右。
資料切分:train / val / test
為什麼要切三份
模型訓練的本質是:看一堆資料,找出規律,然後對沒看過的新資料做預測。
這裡有一個關鍵的認知:模型不是「懂」了什麼。它比較像一台自動分類機。你拿一萬顆水果倒進去,每顆上面貼了標籤(蘋果、橘子、香蕉)。機器不知道什麼是蘋果,但它會歸納出「紅色、圓的、有柄 → 標籤通常是蘋果」。跑完之後你丟一顆它沒見過的紅色圓形水果,它輸出「蘋果」。答對了。但它不是「認識」蘋果,只是學到了特徵跟標籤之間的關聯。你拿一顆紅色的球丟進去,它可能也說「蘋果」。
所以整件事從頭到尾在問的是「分得準不準」,不是「懂不懂」。
切分的三份各自的角色:
Train(訓練集,通常 80%):一萬顆有標籤的水果,讓機器歸納規律。
Validation(驗證集,通常 10%):另外留一千顆它沒看過的水果,訓練過程中拿出來測。分不好就調整機器的設定。
Test(測試集,通常 10%):再另外留一千顆全新的。訓練完全結束後才打開,只用一次,看最終成績。
為什麼 Validation 和 Test 要分開
因為你會根據 Validation 的成績反覆調整模型。調了很多次之後,模型間接適應了 Validation 的資料。所以你需要一份完全沒碰過的資料做最終判斷,那就是 Test。
seed:鎖住隨機
切分是隨機的,哪些筆資料分到哪一堆,每次跑可能不同。seed=42 把這個隨機過程鎖住。同一個 seed 每次跑出來結果一模一樣。
為什麼重要:可再現性。你今天跑出一個好結果,下週要跟同事分享,他用同一個 seed 就能得到完全相同的實驗條件。
動手切分
split = dataset.train_test_split(test_size=0.2, seed=42)
train_val = split["train"].train_test_split(test_size=0.125, seed=42)
train_ds = train_val["train"]
val_ds = train_val["test"]
test_ds = split["test"]
第一行把資料分成 80% 和 20%。第二行從那 80% 裡再切。test_size=0.125 是因為 80% x 12.5% = 10%,想要 Val 佔全部資料的 10%,但你是從 80% 裡面切,所以比例要算成 10% / 80% = 0.125。
結果:Train 17,500(70%)/ Val 2,500(10%)/ Test 5,000(20%)。
下載模型
from huggingface_hub import hf_hub_download, snapshot_download
model_path = hf_hub_download(
repo_id="sentence-transformers/all-MiniLM-L6-v2",
filename="config.json"
)
model_dir = snapshot_download("sentence-transformers/all-MiniLM-L6-v2")
from ... import hf_hub_download, snapshot_download 是一次從套件裡拿出兩個功能,跟分兩行寫效果一樣,只是比較簡潔。
hf_hub_download 從 Hugging Face 下載模型裡的一個檔案。這裡只拿了 config.json(模型的設定檔,記錄架構、大小、參數數量),612 bytes。
snapshot_download 下載整個模型,全部檔案,約 931 MB。
為什麼不一開始就下載整個?有時候你只是想先看看模型的設定檔,確認這個模型適不適合你的需求,確認了再下載完整模型。
下載的東西都存在 ~/.cache/huggingface/hub/。下次用同一個模型直接從快取讀。
這堂課只教你怎麼下載和管理模型。怎麼用它做推論、做語意搜尋,是後面 Phase 2 以後的事。
大檔管理
AI 專案跟一般程式專案的差別:會產生很大的檔案。模型權重幾百 MB 到幾十 GB,訓練資料集更大。這些東西不該放進 Git。
課程教了三種管理方式:
| 方式 | 做法 | 適合 |
|---|---|---|
.gitignore | 告訴 Git 忽略這些檔案 | 個人專案,資料可以重新下載 |
| Git LFS | Git 裡只存指標,大檔存在另一台伺服器 | 團隊共享模型權重 |
| DVC | 資料版本控制,搭配 S3 等雲端儲存 | 需要精確重現實驗的團隊 |
這門課用 .gitignore 就夠了。課程 repo 的 .gitignore 已經設好了 *.pt、*.bin、.env 等規則。Git LFS 和 DVC 知道有這個東西,以後碰到再學。
儲存模式
目前資料和模型都存在本地(~/.cache/huggingface/)。之後如果你租雲端 GPU 做訓練,那台機器跟你的 Mac 是不同電腦,需要把資料放在雲端儲存(S3、GCS),兩邊都能存取。這門課用本地就夠了。
驗證
跑課程提供的驗證腳本:
python phases/00-setup-and-tooling/09-data-management/code/data_utils.py
這個腳本把載入、串流、轉檔、切分、模型下載、快取檢查全部跑一遍。最後看到:
All checks passed. Your data pipeline is ready.
我的快取目錄已經有 27 個檔案、128.7 MB。
走到這裡才會知道的事
新版 datasets 套件要求完整的 namespace/name 格式。 課程原文寫的是 load_dataset("imdb") 和 load_dataset("glue", "mrpc"),但現在會報錯。要改成 load_dataset("stanfordnlp/imdb") 和 load_dataset("nyu-mll/glue", "mrpc")。錯誤訊息會告訴你 Repository id must be 'namespace/name',看到就知道要加上傳者的名字。
在 REPL 裡貼多行程式碼容易出問題。 像 for 迴圈這種多行結構,REPL 一次消化一行,貼上去的過程中縮排容易跑掉,結果報 IndentationError。存成 .py 檔再跑比較穩。
venv 裡用 python 不是 python3。 我在 venv 啟動的狀態下打 python3 data_test.py 失敗了,但 python data_test.py 可以。因為 venv 裡的執行檔叫 python,不一定有 python3 這個別名。以後在 venv 裡都打 python 就好。
Warning 說 unauthenticated requests 不是錯誤。 跑 load_dataset 的時候會看到一行提醒你沒登入 Hugging Face 帳號,下載速度可能比較慢。不影響功能。