[自然語言處理](https://app.notion.com/p/3e6fc631b03081b4a9e8f91f3411f61f) › [W4(10/1)](https://app.notion.com/p/3ecfc631b03081b8aa2cf4a97467ed41) › 09|PyTorch 教學(助教,2024 錄影)影片 [1:04:53–1:26:11](https://www.youtube.com/watch?v=sg22677pUEs&t=3893s)|投影片 pytorch_tutorial_NTHU_NLP p.51–62|上一章 [08 PyTorch:自動微分與訓練迴圈(0:40–1:04)](https://app.notion.com/p/3ecfc631b030817fa3aceb0a27afd016)|下一章 無
## 重點
- NLP 的模型和資料都太大,不能一次全部算,只能一批一批(batch)餵給模型;PyTorch 用 Dataset 存資料、DataLoader 負責分批和打包。
- 文字進模型前要先變成數字:每個字有編號,經過 embedding 層變成一串稠密的數字(向量),再交給 RNN 或 Transformer。訓練 RNN 時助教建議用 teacher forcing,因為比較穩定。
- BERT 是別人用大量文字預先訓練好的 Transformer,從 Hugging Face 下載後微調就能用;整句分類看開頭的 [CLS] 向量,每個字的分類看每個字自己的向量。
## Exam-ready
- **Batch sampling**: "As a result, we need to sample batches of data that can approximate the distribution of the original dataset."(pytorch_tutorial p.51)
- 中文:因為資料太大,我們要抽出一批一批的資料,讓每一批都能大致代表整個資料集的分布。白話:用一小把樣本代表一整袋。難字:sample(抽樣)、approximate(近似)、distribution(分布)。
- **Collate function**: "DataLoader input a tuple into the collate function."(pytorch_tutorial p.53)
- 中文:DataLoader 把湊好的一批資料包成一個 tuple,交給打包函式(collate function)處理。白話:一批資料先裝進同一個袋子,再交給打包員。難字:tuple(一組固定的資料)、collate(整理、彙整)。
- **RNN return value**: "torch.nn.RNN(input, hidden_size=768) return value: (output, h_n)"(pytorch_tutorial p.55)
- 中文:PyTorch 的 RNN 回傳兩樣東西:output 是每個 token 位置各自的輸出,h_n 是最後一個隱藏狀態。白話:一份逐字紀錄,加一份「讀完整句後的總結」。難字:return value(回傳值)、hidden state(隱藏狀態)。
- **Generative learning**: "If the model makes a mistake early on, that error will be used in later training, which makes training unstable."(pytorch_tutorial p.56)
- 中文:如果模型一開始就猜錯,這個錯誤會被拿去當後面訓練的上文,讓訓練變得不穩定。白話:第一步走錯,後面全跟著歪。難字:early on(一開始)、unstable(不穩定)。
- **Teacher forcing**: "Teacher forcing is more stable in training"(pytorch_tutorial p.57)【助教強調】(1:13:53)
- 中文:teacher forcing 在訓練時比較穩定。白話:每一步都給模型正確答案當上文,它就不會被自己前面的錯誤帶偏。難字:stable(穩定)。
- **Transformer vs RNN**: "Transformer-based models are more powerful and parallelable than RNN-based models."(pytorch_tutorial p.58)
- 中文:以 Transformer 為基礎的模型比 RNN 類的模型能力更強,也更能平行運算。白話:RNN 要一個字一個字排隊算,Transformer 可以全部字一起算。難字:parallelable(可平行處理的)。
- **Pretraining**: "Language models (LM) need pretraining to learn the underlying structure, patterns, and relationships in a language."(pytorch_tutorial p.58)
- 中文:語言模型需要先預訓練,才能學到一個語言底層的結構、規律和字與字之間的關係。白話:先讀很多書學會這門語言,再去做特定工作。難字:underlying(底層的)、patterns(規律)。
- **Hugging Face**: "Huggingface is an excellent platform for accessing and downloading pretrained model weights and datasets."(pytorch_tutorial p.58)
- 中文:Hugging Face 是一個很好用的平台,可以取得、下載別人預訓練好的模型權重和資料集。白話:模型界的應用程式商店。難字:platform(平台)、weights(權重,模型學到的數字)。
- **Tokenizer**: "Tokenizer is used to transform tokens to ids, and it can also pack the text batch into tensors"(pytorch_tutorial p.59)
- 中文:tokenizer 用來把一個個 token 換成編號,也能把一批文字直接打包成 tensor(PyTorch 的多維數字陣列)。白話:把句子翻成模型看得懂的號碼表。難字:transform(轉換)、pack(打包)。
- **[CLS] output (pooler output)**: "Also called pooler output in huggingface API"(pytorch_tutorial p.61)
- 中文:BERT 開頭 [CLS] 那個位置的輸出向量,在 Hugging Face 的介面裡也叫 pooler output,用來做整句分類。白話:整句話的代表。難字:pooler(彙整的)、API(程式介面)。
## [1:04:53](https://www.youtube.com/watch?v=sg22677pUEs&t=3893s) 為什麼 NLP 要分批訓練
前面的例子(多項式、線性迴歸)只有一兩個參數、幾筆資料,可以把全部資料一次丟進模型算 loss。NLP 模型的參數可能是幾百萬到幾十億,資料可能上千萬到上億筆(投影片 p.51:模型和資料可達數百 GB 甚至 TB),連放進 GPU 記憶體都不一定放得下。所以只能每次抽一批(batch)資料,用這一批代表整個資料集。batch 越大越能代表整體;只抽一筆,容易被那一筆的特性帶偏(bias)。
要先懂的詞:參數是模型裡可以調整的數字,訓練就是在調它們。GPU 是顯示卡,擅長同時做大量乘法,所以拿來訓練模型,但它的記憶體有限。loss 是「模型猜得多錯」的分數;梯度告訴我們參數往哪個方向調,loss 會下降;optimizer 照著梯度去更新參數(本週第 08 章學過:訓練迴圈就是算 loss → 算梯度 → 更新參數,見 [08 PyTorch:自動微分與訓練迴圈](https://app.notion.com/p/3ecfc631b030817fa3aceb0a27afd016))。
比喻:民調不會問全台灣每個人,而是抽一千人。抽一千人比只問一個人更能代表全體。
考試可能怎麼問:為什麼深度學習的 NLP 訓練要用 mini-batch,而不是一次用全部資料?
下面這張圖是「分批訓練」的一輪循環:
```mermaid
flowchart LR
A["上億筆的完整資料集"] --> B["抽出一批(batch)"]
B --> C["模型算出預測"]
C --> D["算 loss 與梯度"]
D --> E["optimizer 更新參數"]
E --> B
```
每一圈只看一小批,但跑很多圈後,模型等於看過了整個資料集。
下面的進階摺疊用實際數字算「跑完一輪要更新幾次參數」,並說明 batch 太小時,更新方向為什麼會忽左忽右。結論是:batch 越大,每次更新的方向越接近整個資料集的方向。
它到底怎麼運作?(進階,可跳過)
假設資料集有 10,000,000 句,batch size 設 32。跑完一輪(epoch,把全部資料看過一次)要更新 10,000,000 ÷ 32 = 312,500 次參數。每次更新只用 32 句算 loss,所以算得快、記憶體放得下。
batch size 改成 1 時,每次只看一句,梯度(loss 往哪個方向下降)會被那一句的特色左右,方向忽左忽右。batch 越大,32 句的平均越接近整個資料集的真實方向。
助教原話是什麼?
- 「如果小 batch 如果是一筆的話他有可能會有各種自己的 bias」(1:07:19)
## [1:07:41](https://www.youtube.com/watch?v=sg22677pUEs&t=4061s) Dataset 與 DataLoader
PyTorch 把「存資料」和「分批拿資料」拆成兩個工具。Dataset 是倉庫:初始化時把資料存起來、能用編號拿出第幾筆、能回報總共幾筆。DataLoader 是出貨員:拿到 Dataset 後,設定一批幾筆、用什麼函式打包、要不要打亂順序,之後用 for 迴圈一批一批取出來。
要先懂的詞:for 迴圈是程式裡「對每一個東西重複做同一件事」的寫法,這裡就是「每拿到一批,就訓練一次」。shuffle(打亂)是每一輪先洗牌再分批,避免模型記住資料的固定順序(我補充)。
比喻:Dataset 像圖書館書架,每本書有編號;DataLoader 像借書櫃台,一次幫你搬一疊、還可以先洗牌再搬。
考試可能怎麼問:Dataset 和 DataLoader 各負責什麼?為什麼要把兩者分開?
| 工具 | 負責什麼 | 要寫或要設定的東西 |
|---|---|---|
| Dataset | 存資料、按編號取出一筆 | 初始化(存資料)、取第幾筆(`__getitem__`)、總共幾筆(`__len__`) |
| DataLoader | 分批、打包、打亂 | 一批幾筆(batch_size)、打包函式(collate_fn)、要不要打亂(shuffle) |
拆開的好處是:換資料只要改 Dataset,換分批方式只要改 DataLoader,兩邊互不影響(我補充)。
下面的進階摺疊用三句話的小資料集走一遍:Dataset 怎麼回報筆數、怎麼按編號取出一句,DataLoader 怎麼把 3 句分成 2 批;最後附上投影片的程式骨架,只要認得長相,不考寫程式。
它到底怎麼運作?(進階,可跳過)
用一個三句的小資料集:「I love AI !」「I eat apple today」「Hello」。
- Dataset 的總筆數回報 3。
- 用編號 1 取出,拿到第二句「I eat apple today」(編號從 0 開始)。
- DataLoader 設 batch_size = 2、不打亂:第一批是第 0、1 句,第二批只剩第 2 句。所以一輪會拿到 2 批。
投影片 p.52 的骨架(只為了認得長相,不考寫程式):
```python
class myDataset(Dataset):
def __init__(self, split):
super().__init__()
self.data = ...
def __getitem__(self, index):
return self.data[index]
def __len__(self):
return len(self.data)
dl = DataLoader(myDataset(split=...), batch_size=..., collate_fn=get_batch, shuffle=...)
for batch in dl:
...
```
## [1:09:29](https://www.youtube.com/watch?v=sg22677pUEs&t=4169s) 一批資料怎麼被打包
資料流分兩層。第一層,Dataset 一筆一筆取出資料,這裡可以做「單筆」的前處理。第二層,湊滿一批後包成 tuple,交給 DataLoader 的打包函式(collate function),在「整批」的層次再處理,例如轉成 Tensor。for 迴圈拿到的,就是打包函式回傳的東西。
要先懂的詞:Tensor(張量)是 PyTorch 裝數字的多維表格,一串數字是一維、一張表是二維、好幾張表疊起來是三維;模型只吃 Tensor,不吃文字(本週第 06 章學過:Tensor 能計算、能記住計算過程好算梯度、能放上 GPU,見 [06 PyTorch:訓練主循環與 Tensor](https://app.notion.com/p/3ecfc631b03081579f0eeb810083a514))。tuple 是把幾樣東西綁成一組、之後不能再改的資料包;字典(dict)是「名稱 → 內容」的對照表,例如 data 對到句子、label 對到正確答案。
比喻:工廠先一顆一顆檢查蘋果(單筆前處理),再裝箱貼標籤(整批打包),出貨的是箱子不是單顆蘋果。
考試可能怎麼問:單筆前處理和整批處理分別該放在哪裡?為什麼補齊長度這種事要在 collate function 做?
這張投影片畫的就是上面兩層:上排是 Dataset 逐筆取出,下排是 collate function 把整批變成 Tensor。
[[IMG: C:\D槽\TAICA課程\_work\notes-v2\nlp-w4\img\brief_pytorch_tutorial_NTHU_NLP_p053.png | 文字資料先經 Dataset 逐筆取出,再由 collate function 把整批打包成 Tensor(pytorch_tutorial p.53)]]
圖上重點:(1)標題 Data Flow=資料流。(2)左上 Text data、Data type: str=原始文字資料,型別是字串。(3)`Dataset.__getitem__`、Preprocess each data=Dataset 逐筆取出,並做單筆前處理,得到一筆筆「data+label」(資料+答案)的字典。(4)DataLoader collate function、Processing data batch=打包函式處理整批資料;左下的 DataLoader input a tuple into the collate function=DataLoader 把一批包成 tuple 交給打包函式。(5)最右邊的方塊 Tensor=整批變成的數字表格。這張圖在講:文字從字串變成 Tensor,中間要經過「逐筆」和「整批」兩關。
左上是原始文字,右上是取出後的一筆筆字典(data+label),右下的方塊就是最後拿到的 Tensor。
下面的進階摺疊用第一批的兩句話走一遍打包過程。重點是:只有看到整批,才知道要把句子補到多長,所以補齊長度要在打包函式裡做。
它到底怎麼運作?(進階,可跳過)
接上一段的第一批:「I love AI !」和「I eat apple today」。
1. Dataset 逐筆取出,得到兩個字典,例如 {"data": "I love AI !", "label": 1}、{"data": "I eat apple today", "label": 0}。
2. DataLoader 把這兩個字典包成一個 tuple,交給 collate function。
3. collate function 看得到整批,才知道最長的句子有 4 個 token,於是把整批補到一樣長,轉成形狀 2×4 的 Tensor。
為什麼補齊要在這裡做:單看一筆資料時,不知道同一批其他句子多長;只有在整批的層次才知道要補到多長(我補充)。
助教原話是什麼?
- 「實際上取出的就是 collate function 的 return value」(1:10:46)
## [1:11:01](https://www.youtube.com/watch?v=sg22677pUEs&t=4261s) 文字在 RNN 裡怎麼流動
以「I love AI !」為例,每個 token 先查字典得到編號,這個編號等於一個 one-hot 稀疏向量(只有一格是 1,其他全是 0)。經過 embedding 層,變成例如 768 維的稠密向量(每格都有數值)。接著逐字送進 RNN,隱藏狀態(hidden state,記住前面讀過什麼的那串數字)一路往後傳。RNN 會輸出每個 token 的結果,外加最後一個隱藏狀態 h_n,可以交給後面的模組,例如解碼器。
要先懂的詞:token 是模型處理文字的最小單位,大多是一個字或一個符號。one-hot 和 embedding 本週第 03 章學過(one-hot 是只有一格是 1 的超長向量;word2vec 替每個字學出的一串數字就是 embedding,見 [03 稠密向量與 Word2Vec](https://app.notion.com/p/3ecfc631b030819f9a0df026aac33ae7))。「768 維」就是一串 768 個數字。RNN 是「照順序一個字一個字讀」的模型(第 1 週學過:老師點名 RNN/LSTM 雖舊但很重要,見 [05 預訓練模型改變 NLP 做法](https://app.notion.com/p/3e7fc631b03081c69424e055b260badc))。解碼器(decoder)是根據前面讀到的總結,把輸出一個字一個字寫出來的模組,例如翻譯時負責寫出譯文的那一半(我補充)。
比喻:one-hot 像學號,只告訴你「是誰」;embedding 像個人檔案,寫滿這個人的特徵。RNN 像傳話遊戲,每個人聽完前一個人的話,加上自己的字再傳下去。
考試可能怎麼問:為什麼要把 one-hot 向量換成 dense embedding?RNN 回傳的 output 和 h_n 差在哪?
下圖是文字變成向量的過程:
[[IMG: C:\D槽\TAICA課程\_work\notes-v2\nlp-w4\img\pytorch_tutorial_nthu_nlp_p054.png | one-hot 向量乘上 embedding 矩陣,變成 768 維的稠密向量,再送進隱藏層(pytorch_tutorial p.54)]]
圖上重點:(1)標題 Data Flow in RNN model=RNN 模型裡的資料流;上方前提是一批只有 1 句(batch_size = 1)、每個字用 768 個數字表示(hidden_dim = 768)、字典有 20000 個字。(2)One-hot vectors、Input tensor Shape = 1X6X20000(batch_size X seq_len X vocabs)=輸入是 one-hot 向量,形狀是「一批幾句 × 句子幾個 token × 字典大小」。(3)Embedding layer、A matrix、Shape = 20000X768(vocabs X hidden_dim)=embedding 層就是一張「字典大小 × 768」的數字表。(4)Dense vectors、Embeddings Shape = (1X6X768)=相乘後每個字變成 768 個有意義的小數,再送進 Hidden Layers(隱藏層,也就是 RNN 本體)。這張圖在講:字的編號怎麼變成模型能用的向量。
左邊每列只有一個 1;乘上中間的 embedding 矩陣後,右邊每列都是有意義的小數。
下面的進階摺疊追蹤每一步資料的形狀(幾句 × 幾個字 × 幾個數字)。結論是:one-hot 乘 embedding 矩陣其實就是查表,挑出那個字對應的那一列數字;RNN 最後交出每個字各自的輸出,加上一份讀完整句的總結 h_n。
它到底怎麼運作?(進階,可跳過)
設字典有 20,000 個字、hidden_dim = 768、batch size = 1。
1. 「I love AI !」4 個 token,排成 one-hot 輸入,形狀 1×4×20000。
2. embedding 矩陣形狀 20000×768。one-hot 乘矩陣,其實就是「挑出第幾列」:假設 love 的編號是 5,結果就是矩陣的第 5 列。所以 embedding 等於查表。
3. 輸出 1×4×768,每個 token 一個 768 維向量。
4. RNN 從 h0 出發:h1 由 h0 和 I 算出,h2 由 h1 和 love 算出,一直到 h4。回傳 (output, h_n):output 是 4 個位置各自的輸出(1×4×768),h_n 就是最後的 h4。
注意:投影片 p.54 的輸入寫成 1X6X20000,但這句只有 4 個 token,下一頁 p.55 也寫成 1X4X768;6 是 BERT 加上 [CLS]、[SEP] 後的長度(p.60),這裡應該照 4 理解(我補充)。
## [1:13:51](https://www.youtube.com/watch?v=sg22677pUEs&t=4431s) teacher forcing 與生成式訓練
訓練 RNN 生成句子有兩種餵法。生成式訓練(generative learning)讓模型自己一路生成,再拿結果去修正;如果第一個字就猜錯(正解是 I love AI !,模型生成 I eat apple .),後面每一步都會拿錯的上文去學,變得很奇怪又不穩定。teacher forcing 每一步都餵正確答案當上文(看 I 預測 love、看 I love 預測 AI),不會被自己的錯誤帶偏,所以更穩定。助教推薦 teacher forcing,而且它也比較好寫;用生成式訓練如果訓練得起來,也不會有大問題。
要先懂的詞:「生成句子」就是語言模型在做的事:看前面的字,猜下一個字(第 3 週學過:語言模型就是算「下一個字是什麼」的機率,見 [07 語言模型定義與困惑度](https://app.notion.com/p/3e6fc631b03081b4b99acb876fd0bb2d))。上文是指目前已經寫出來的前面幾個字。下面表格裡的 是 end of sentence,代表「句子到這裡結束」的特殊記號。
【助教強調】(1:13:53) 訓練 RNN 建議用 teacher forcing。注意:這是 2024 年錄影時對當年作業的建議,今年作業以今年公告為準。
比喻:學開車時,教練每次都把你拉回正確車道再讓你練下一段(teacher forcing);如果放你自己一路開,第一個彎轉錯,後面都在練怎麼在錯的路上開(生成式訓練)。
考試可能怎麼問:比較 generative learning 和 teacher forcing,為什麼 teacher forcing 訓練比較穩定?
| 第幾步 | 生成式訓練:模型看到的上文 → 要學的答案 | teacher forcing:模型看到的上文 → 要學的答案 |
|---|---|---|
| 1 | I → love | I → love |
| 2 | I eat → AI | I love → AI |
| 3 | I eat apple → ! | I love AI → ! |
| 4 | I eat apple ! → | I love AI ! → |
從第 2 步開始,左欄的上文是模型自己生成的錯字,要它在「I eat」後面接「AI」,這種對應本身就不合理;右欄的上文永遠是正解,沒有奇怪的組合。
下面的進階摺疊把上表改寫成機率式子。P(love | I) 讀作「看到 I 之後,下一個字是 love 的機率」;訓練就是把正解的這個機率調高。兩種方法調的都是這種機率,差別只在直線右邊的上文是正解,還是模型自己生成的字。
它到底怎麼運作?(進階,可跳過)
兩種方法最後優化的都是「給定上文,下一個字是正解的機率」,差別只在上文從哪來(投影片 p.56–57)。
- 生成式訓練優化:P(love | I)、P(AI | I eat)、P(! | I eat apple)、P( | I eat apple !)。
- teacher forcing 優化:P(love | I)、P(AI | I love)、P(! | I love AI)、P( | I love AI !)。
p.57 裡,teacher forcing 的模型四步自己猜的是 eat、you、!、,前兩步猜錯也沒關係,下一步輸入照樣換成正解 love、AI。錯誤不會累積。
(我補充)teacher forcing 的代價是:訓練時永遠有正解可看,真正使用時卻要靠自己的輸出接下去,兩者情境不同。這點助教沒講,知道就好。
助教原話是什麼?
- 「我在作業的講解當中有講過,就是 generative learning 跟 teacher forcing 之間的區別,那麼我們這邊會建議大家用 teacher forcing」(1:13:53)
- 「teacher forcing 它訓練起來是會更穩定的」(1:14:20)
- 「早期的預測結果的錯誤會被一步步去影響後面的預測結果」(1:16:01)
## [1:16:58](https://www.youtube.com/watch?v=sg22677pUEs&t=4618s) Transformer、BERT 與預訓練
Transformer 比 RNN 能力更強,而且可以平行運算。BERT 是 Google 以 Transformer 為架構、用大量沒有標註的文字預訓練好的模型,預訓練任務包括克漏字(遮住一個字讓模型猜)和判斷兩句話是不是前後句。預訓練要花很多硬體、時間和電費,所以一般人直接從 Hugging Face 下載。模型先學會語言,再微調(fine-tuning)到下游任務(真正要做的工作,例如情緒分類),可以大幅減少需要的標註資料。
要先懂的詞:平行運算是很多計算同時做,而不是排隊一個接一個做,GPU 正好擅長這種事。BERT 輸出的向量會隨上下文改變,同一個「蘋果」在「蘋果公司」和「蘋果派」裡的數字不同(本週第 04 章學過:上下文相關的詞向量,見 [04 上下文相關的詞向量](https://app.notion.com/p/3ecfc631b030814cb876f1cf87428b37))。預訓練模型怎麼改變 NLP 的做法,第 1 週也講過(見 [05 預訓練模型改變 NLP 做法](https://app.notion.com/p/3e7fc631b03081c69424e055b260badc))。
比喻:預訓練像先讀完國小到高中學會中文,微調像進公司學這份工作的術語。直接叫一個不識字的人做客服(直接從零訓練下游任務)就「太遙遠」了。
考試可能怎麼問:為什麼要先 pretraining 再 fine-tuning?這樣做對標註資料的需求有什麼影響?
下圖是「預訓練 → 微調」的路線:
```mermaid
flowchart LR
A["大量沒標註的文字"] --> B["預訓練:克漏字、判斷下一句"]
B --> C["BERT 學會語言結構"]
C --> D["放上 Hugging Face"]
D --> E["下載後微調"]
F["少量有標註的下游資料"] --> E
E --> G["完成分類等下游任務"]
```
最貴的預訓練別人已經做好,我們只需要準備少量標註資料,做最後一段微調。
要先懂什麼?
- 有標註/沒標註:有標註是每筆資料都有人工給的答案(例如這句是正面還是負面);沒標註就只是一大堆文字。沒標註的文字到處都有,標註資料要花錢請人做。
- 克漏字之所以能用沒標註的資料訓練,是因為答案就是被遮住的那個字,不需要人工標(我補充)。
- 下游任務:預訓練之後,真正想讓模型做的具體工作。
助教原話是什麼?
- 「所以我們會通過 pretraining 先讓他學會這門語言」(1:19:13)
## [1:19:27](https://www.youtube.com/watch?v=sg22677pUEs&t=4767s) tokenizer 與載入模型
tokenizer 一行就能做完文字前處理:把句子切成 token、換成編號、把短句補零到一樣長(padding)、把太長的句子截掉(truncation),最後直接打包成 PyTorch Tensor。它回傳的東西可以當字典用,預設放在 CPU 上,要用 GPU 得自己搬過去。模型則用 AutoModel 載入權重,載入後就是一個普通的 nn.Module(PyTorch 的模型元件),照前幾章的方式使用。
要先懂的詞:CPU 是電腦的主處理器,GPU 是顯示卡;資料和模型要放在同一個地方才能一起計算,所以要把 tokenizer 的結果搬到 GPU(本週第 06 章學過:Tensor 有一個 device 屬性,記錄它放在 CPU 還是 GPU)。nn.Module 是 PyTorch 所有模型的共同骨架,分成「準備零件」和「資料怎麼流過零件(forward)」兩部分(本週第 07 章學過,見 [07 PyTorch:用模組搭模型](https://app.notion.com/p/3ecfc631b03081d0869df37240433e19))。權重就是模型學到的那些數字;載入權重,等於把別人訓練好的數字直接搬進來。
比喻:tokenizer 像排隊入場的工作人員,幫每個人發號碼牌、矮的人墊箱子、太高的請他蹲下,讓所有人排成整齊的方陣。
考試可能怎麼問:為什麼一批句子要做 padding 和 truncation?
下表用兩句話示範 padding(數字是假設的編號):
| 句子 | 切成 token 並換成編號 | padding 後(補到 4 格) |
|---|---|---|
| I love AI | [12, 85, 307] | [12, 85, 307, 0] |
| I eat apple today | [12, 640, 911, 52] | [12, 640, 911, 52] |
補完之後兩句一樣長,才能疊成一個 2×4 的 Tensor。如果設最大長度 3,第二句就會被截成 [12, 640, 911]。
下面的進階摺疊是投影片上的三行程式,只做三件事:載入 tokenizer、把一批句子變成數字表格、載入 BERT。只要認得,不考寫法。
它到底怎麼運作?(進階,可跳過)
投影片 p.59 的三行(認得就好):
```python
tokenizer = T.AutoTokenizer.from_pretrained("google-bert/bert-base-uncased", cache_dir="./cache/")
data = tokenizer.batch_encode_plus(text, padding=True, truncation=True, return_tensors="pt")
model = T.AutoModel.from_pretrained("google-bert/bert-base-uncased", cache_dir="./cache/")
```
- 第一、三行:載入 tokenizer 和 BERT(架構+權重),引號裡是模型名稱,cache_dir 是本機存放資料夾。
- 第二行:return_tensors="pt" 代表回傳 PyTorch Tensor;回傳的 BatchEncoding 可當字典用,在 CPU 記憶體。
(我補充)真正的 BERT tokenizer 還會在前後加上 [CLS] 和 [SEP],下一段會看到。
助教原話是什麼?
- 「他其實相當於就是一個 torch.nn.Module」(1:22:05)
## [1:22:25](https://www.youtube.com/watch?v=sg22677pUEs&t=4945s) BERT 的資料流與分類用法
BERT 不像 RNN 一個字一個字處理,而是所有 token 同時經過多層 attention 與前饋網路,輸出的向量和輸入一一對應。句子前後會加上兩個特殊 token:開頭的 [CLS] 代表整句話,結尾的 [SEP] 表示句子結束。要做整句分類,就取 [CLS] 的向量,接一層線性層算出各類別分數;要做每個字的分類(例如 NER,命名實體辨識,找出人名地名),就把每個 token 的向量各接一層線性層。
要先懂的詞:attention(注意力)讓每個字直接去看句子裡所有其他字,決定該多參考誰,所以不用像 RNN 那樣排隊一個個讀;Transformer 的細節之後的課會講。前饋網路(feed-forward network,下圖寫成 FFN)是最普通的神經網路層:輸入乘上權重再加總。線性層就是一層這種「乘權重再加總」的計算(本週第 07 章學過:Linear 層),這裡用它把 768 個數字變成每個類別一個分數。下圖的 logits 是還沒換成機率的原始分數,數字越大代表越像那一類。
注意:助教口頭把 BERT 叫做「auto encoder」,投影片 p.60–61 寫的是「Transformer Auto-encoder」。這裡指的是編碼器式(encoder-only)的 Transformer,不是一般說的「把輸入壓縮再還原」的自編碼器。
比喻:[CLS] 像班長,聽完全班意見後代表全班發言(整句分類問班長就好);token 分類則是每個同學各自回答自己的問題。
考試可能怎麼問:用 BERT 做 sequence classification 和 token classification,分別要拿哪些輸出向量?
[[IMG: C:\D槽\TAICA課程\_work\notes-v2\nlp-w4\img\pytorch_tutorial_nthu_nlp_p061.png | 上方:取 CLS 向量接線性層做整句分類;下方:每個 token 向量各接線性層做 token 分類(pytorch_tutorial p.61)]]
圖上重點:(1)標題 Data Flow in Transformer Auto-encoder=Transformer 編碼器(BERT)裡的資料流,前提同上一張圖(一批 1 句、768 個數字、字典 20000 字)。(2)Hidden Layers → Output logits Shape = (1X6X768)=經過隱藏層後,[CLS]、I、love、AI、!、[SEP] 六個位置各有一串 768 個數字。(3)For sequence classification=整句分類:只取 [CLS] 那一列([CLS] logits,shape 1X1X768);旁邊的 Also called pooler output in huggingface API=在 Hugging Face 裡也叫 pooler output。(4)For token classification=每個字的分類:取中間四個字那四列(Tokens logits,1X4X768)。(5)FFN (assume there are 2 classes) Shape = 768X2(hidden_dim X classes)=把 768 個數字變成 2 個類別分數的一層網路,最右邊的 [0.1, 0.6] 等就是各類分數。這張圖在講:同一份 BERT 輸出,整句分類和逐字分類各拿哪幾列。
上方路線只用第一列([CLS]),得到一組兩類分數;下方路線用中間四列(I love AI !),每個字各得到一組兩類分數。
最後助教預告,之後的助教課會再講 Transformer 的細節和其他好用的工具。注意:2024 年的作業會要求從 Hugging Face 下載模型、自己寫模型做分類或生成,這是 2024 年的說法,不代表今年作業。
下面的進階摺疊把上圖的數字一步步走一遍。最後算出來的兩個數字是兩個類別的分數,哪個大就判成哪一類;它也說明 BERT 為什麼比 RNN 快:六個位置同時算。
它到底怎麼運作?(進階,可跳過)
沿用「I love AI !」,加上 [CLS]、[SEP] 共 6 個 token,hidden_dim = 768,假設 2 個類別。
1. 輸入 one-hot 1×6×20000,乘 embedding 矩陣 20000×768,得到 1×6×768(p.60)。
2. 經過多層 attention+前饋網路,輸出仍是 1×6×768,6 個位置一一對應。
3. 整句分類:取 [CLS] 那列(1×1×768),乘上 768×2 的線性層,得到 [0.1, 0.6],第二類分數較高,判為第二類。
4. token 分類:取 I、love、AI、! 四列(1×4×768),乘同樣形狀的 768×2,得到四組分數,例如 [0.7, 0.0]、[0.9, -0.2]、[0.1, 0.75]、[-0.1, 0.7]:前兩個字判第一類,後兩個判第二類。
(我補充)嚴格說,Hugging Face 的 pooler output 是 [CLS] 向量再過一層線性層加 tanh,不是原封不動的 [CLS]。
和 RNN 對照:RNN 要從 h0 一路算到 h4 才拿得到最後結果;BERT 六個位置同時算,所以能平行化(呼應 p.58)。
助教原話是什麼?
- 「auto encoder 他是所有的 token 平行的往前處理」(1:22:54)
- 「在之後的作業當中我們會要求大家能夠是構建一個比方說從 hugging face 上面下載模型」(1:25:05)
## Self-check
Q1. Why does the tutorial recommend teacher forcing over generative learning when training an RNN?(中文:為什麼訓練 RNN 時建議用 teacher forcing,而不是生成式訓練?)
**Answer**: In generative learning the model feeds its own predictions back as context, so an early mistake (e.g., "I eat" instead of "I love") is used in later training steps and creates strange targets such as P(AI | I eat), which makes training unstable. Teacher forcing always feeds the ground-truth prefix, so every step optimizes a sensible target and training is more stable (and easier to implement).
中文:生成式訓練會把模型自己生成的字當作下一步的上文。一開始猜錯(例如生成「I eat」而不是「I love」),這個錯誤就會被拿去訓練後面每一步,出現「看到 I eat 要接 AI」這種不合理的目標,所以訓練不穩定。teacher forcing 每一步都給正確答案當上文,每個要學的目標都合理,訓練比較穩定,而且也比較好寫。
Q2. Why do NLP models need to be trained on mini-batches, and why is a larger batch often preferred?(中文:為什麼 NLP 模型要用分批訓練?為什麼常說 batch 大一點比較好?)
**Answer**: NLP models and datasets are huge (millions to billions of parameters, tens of millions of samples), so the whole dataset cannot be fed to the model at once. We sample batches that approximate the distribution of the original dataset. A larger batch represents the dataset better, while a batch of one sample can be biased by that single example.
中文:NLP 的模型參數可能上億、資料可能上千萬筆,不可能一次全部丟進模型計算,所以每次抽一批,用這一批代表整個資料集的分布。batch 越大,越接近整個資料集的樣子;如果一批只有一筆,結果容易被那一筆的特性帶偏。
Q3. Explain why pretraining followed by fine-tuning reduces the amount of labeled data needed.(中文:說明為什麼「先預訓練、再微調」可以減少需要的標註資料。)
**Answer**: During pretraining, a model such as BERT learns the structure, patterns, and relationships of a language from large unlabeled text using tasks like masked word prediction and next sentence prediction. Since the model already understands the language, fine-tuning only needs to adapt it to the downstream task, which requires far less labeled data than training from scratch.
中文:預訓練時,BERT 這類模型用大量沒標註的文字,透過克漏字、判斷下一句這類任務,學會語言的結構、規律和字詞關係。模型已經懂這門語言了,微調時只要把它調整到特定任務上,所以需要的標註資料比從零開始訓練少很多。
Q4. Compare how BERT's outputs are used for sequence classification and token classification.(中文:比較 BERT 的輸出在整句分類和每個字的分類中怎麼使用。)
**Answer**: BERT processes all tokens in parallel and outputs one vector per input token. For sequence classification, we take only the [CLS] vector (the pooler output), which represents the whole sentence, and pass it through a linear layer to get class scores. For token classification such as NER, we pass every token's vector through a linear layer to get a label for each token.
中文:BERT 會同時處理所有 token,每個輸入 token 都有一個對應的輸出向量。整句分類只拿開頭 [CLS] 的向量(Hugging Face 裡叫 pooler output),它代表整句話,接一層線性層算出各類別分數。每個字的分類(例如 NER)則是把每個 token 的向量各自接線性層,替每個字給一個類別。
這週讀完了。回到週頁:[W4(10/1)](https://app.notion.com/p/3ecfc631b03081b8aa2cf4a97467ed41)