National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 23

訓練神經網路

批次、學習率、過擬合,MLPClassifier 與 PyTorch 的對應
Interaction實作 30 minute(s)

Learning Objectives

  • 區分 epoch、批次與更新次數,算出一輪更新幾次
  • 用 MLPClassifier 訓練手寫數字分類,並和基準比較
  • 從損失曲線判斷學習率太大、太小與過擬合
  • 說明提前停止與正則化怎麼用,以及它們的限制
  • 把 MLPClassifier 的參數對應到 PyTorch 的寫法

從能算出梯度到訓練得起來

上一課已經能算出梯度。實際訓練還要決定幾件事,而且這些設定常常決定了模型學不學得起來:

  • 每次更新用幾筆資料(批次)?
  • 每次走多遠(學習率)?
  • 要訓練多久、什麼時候停(過擬合與提前停止)?

這一課不再手寫迴圈,改用 scikit-learn 的 MLPClassifier,在真實資料上看這些設定的影響;最後看同樣的東西用 PyTorch 怎麼寫。

這一課的資料是 sklearn.datasets.load_digits():真實的公開資料,來自 UCI 機器學習資料庫的手寫數字資料集(Optical Recognition of Handwritten Digits),是它測試集的副本。原始手寫圖片經過前處理,縮成 8×8 的格子,每格是 0 到 16 的整數,共 1797 筆,答案是 0 到 9。

逐步看:手寫數字資料長什麼樣

每張圖攤平成 64 個數字,一列一張圖。把其中一列還原成 8×8,就是一張圖。

epoch、批次與更新次數

上一課每次更新都用全部資料算梯度(全批次)。資料多時這樣太慢,實際上是每次取一小批:

名詞 意思
批次(batch) 一次算梯度用的資料,常見 32 到 256 筆
更新(update) 用一個批次的梯度更新一次權重
一輪(epoch) 全部訓練資料都看過一次
每輪更新次數 = 訓練筆數 ÷ 批次大小(有餘數就進位)

用小批次的梯度下降叫小批次梯度下降(mini-batch gradient descent)。每個批次的梯度只是估計值,所以損失會小幅跳動,但一輪可以更新很多次。

MLPClassifier 的主要預設值(scikit-learn 1.6):

參數 預設值 意思
hidden_layer_sizes (100,) 一個隱藏層、100 個神經元
activation 'relu' 隱藏層的活化函數
solver 'adam' 更新方法,會自動調整每個參數的步伐
learning_rate_init 0.001 學習率
batch_size 'auto' min(200, 訓練筆數)
max_iter 200 最多跑幾輪(用 sgd、adam 時是 epoch 數)
alpha 0.0001 L2 正則化強度
early_stopping False 不提前停止

類別超過兩個時,輸出層改成每個類別一個神經元,用 softmax 轉成總和為 1 的機率。

逐步看:MLPClassifier 分類手寫數字

先算基準:全猜最多的數字能對多少。再訓練一個 64-32-10 的網路,最後看損失曲線。

逐步看:批次大小的影響

為了只比較批次,這裡改用單純的 solver="sgd"(scikit-learn 預設帶 momentum=0.9)、學習率相同,三種批次都只跑 6 輪。partial_fit 每呼叫一次跑一輪。

學習率與過擬合

接下來用小工具看兩件最常遇到的事。資料是雜訊較多的月牙,只有一半拿來訓練,另一半是驗證資料(validation data):不參與訓練、用來觀察模型的表現與決定什麼時候停。

看損失曲線可以判斷很多事:

曲線長什麼樣 可能的原因 怎麼辦
下降得很慢 學習率太小 調大
上下亂跳、甚至上升 學習率太大 調小
一直很高、下不來 模型太簡單(欠擬合)或卡住 加神經元、換初始權重
訓練一直降,驗證先降後升 過擬合 提前停止、正則化、更多資料

互動練習:學習率、過擬合與提前停止

藍線是訓練損失,青線是驗證損失,金色虛線標出驗證損失最低的那一輪。改任何設定都會回到新的初始權重。任務有門檻值,訓練得不夠久就再多訓練一下。

過擬合的三種對策

小工具裡訓練損失幾乎降到 0、驗證損失卻一路上升:模型把訓練資料裡的雜訊也記住了。常用的對策:

對策 做法 在 MLPClassifier 裡
提前停止(early stopping) 切一部分訓練資料當驗證資料,驗證分數連續幾輪沒有進步就停,留下最好的權重 early_stopping=True,預設切 10%(validation_fraction),等 10 輪(n_iter_no_change)
L2 正則化 在損失加上權重平方的懲罰,權重比較不容易變得很大 alpha,越大懲罰越強
更多訓練資料 雜訊比較難被記住 不是參數,要真的去蒐集

這些對策有沒有用要實測,不是加了就會變好。下面在手寫數字上比較。

逐步看:哪一種對策比較有效

訓練資料故意只用 120 筆或 600 筆(其餘都是測試資料),網路較大(128 個神經元)。每一列比較「跑了幾輪」「訓練準確率」「測試準確率」。

怎麼解讀上面的結果

這裡的訓練準確率是用全部訓練資料算的;提前停止時有 10% 被切去當驗證資料,所以訓練時實際上看不到那些資料。

上面的結果不代表提前停止沒有用,而是說明:

  • 它要犧牲一部分資料當驗證資料,資料少時這個代價很明顯;
  • 驗證資料太少時分數雜訊大,n_iter_no_change 太小就會太早停;
  • 這個資料集、這個網路本身過擬合得不嚴重,對策能幫上的本來就不多。小工具裡雜訊大的月牙,就看得到明顯的效果。

而且這只是切一次的結果。實際比較設定時要用第 14 課的交叉驗證。

同樣的東西用 PyTorch 怎麼寫

MLPClassifier 只適合小型的全連接網路,也不能用 GPU。要做影像、語音或更大的模型,一般改用 PyTorch 這類深度學習框架。PyTorch 不能在這個頁面的瀏覽器環境執行,下面這段只是對照,不會在這裡跑,要自己裝在電腦或 Colab 上試。

import torch
from torch import nn
from torch.utils.data import DataLoader, TensorDataset

# X_train、y_train 同上面的手寫數字資料
X_t = torch.tensor(X_train, dtype=torch.float32)
y_t = torch.tensor(y_train, dtype=torch.long)
loader = DataLoader(TensorDataset(X_t, y_t), batch_size=200, shuffle=True)

model = nn.Sequential(nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 10))
loss_fn = nn.CrossEntropyLoss()      # 輸入是沒有經過 softmax 的分數(logits)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

for epoch in range(100):
    model.train()
    for xb, yb in loader:
        loss = loss_fn(model(xb), yb)    # 前向傳播
        loss.backward()                  # 反向傳播:算出所有梯度
        optimizer.step()                 # 更新權重
        optimizer.zero_grad()            # 清掉梯度,否則下一次會累加上去

model.eval()
with torch.no_grad():                    # 預測時不需要梯度
    pred = model(torch.tensor(X_test, dtype=torch.float32)).argmax(dim=1)

差別是 PyTorch 把訓練迴圈交給你自己寫,你也就能看到前兩課做的事:

這幾課學的 MLPClassifier PyTorch
網路結構 hidden_layer_sizes=(32,)、activation='relu' nn.Sequential(nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 10))
前向傳播 fit 裡面做 model(xb)
損失 自動(log-loss) nn.CrossEntropyLoss()
反向傳播 fit 裡面做 loss.backward()
更新方法與學習率 solver='adam'、learning_rate_init=0.001 torch.optim.Adam(model.parameters(), lr=0.001)
批次 batch_size=200 DataLoader(..., batch_size=200, shuffle=True)
訓練幾輪 max_iter 外層 for epoch 迴圈
預測 predict model.eval()、torch.no_grad()、argmax

注意 PyTorch 的提前停止、驗證資料都要自己寫,下面的實作題就是這段邏輯。

實作:寫提前停止的判斷

完成兩個函數:

  • early_stop(val_losses, patience):val_losses 是每一輪的驗證損失(第 0 輪開始)。一路記下損失最低的一輪;如果目前這一輪距離最低的那一輪已經有 patience 輪,而且這一輪沒有更低,就在這一輪停。回傳 (最好的一輪, 停下來的一輪)。一直沒觸發時,停在最後一輪。
  • updates_per_epoch(n, batch_size):一輪更新幾次(有餘數要進位)。
你的輸出
預期輸出

小測驗:訓練神經網路

1.

訓練資料 1000 筆,批次大小 100,跑 5 輪。總共更新幾次權重?下面程式會印出什麼?

n, batch, epochs = 1000, 100, 5
print((n // batch) * epochs)
2.

損失曲線上下亂跳,有時還比一開始高。最先該試什麼?

3.

訓練準確率 1.0、測試準確率 0.89,測試準確率遠高於基準。下面哪一句最恰當?

4.

MLPClassifier(solver="adam") 的 max_iter=200 是什麼意思?

5.

PyTorch 的訓練迴圈裡,哪一行計算出所有參數的梯度?

6.

上面的實驗裡,600 筆資料開提前停止,測試準確率比不開低。最合理的解讀是?

第 1 次作答

重點整理

  • epoch=全部資料看一次;批次=一次更新用的資料;每輪更新次數=訓練筆數 ÷ 批次大小(進位)。
  • MLPClassifier 預設一層 100 個神經元、ReLU、Adam、學習率 0.001、最多 200 輪;同樣要做尺度處理、要和基準比較、要看測試資料。
  • 學習率太小下降很慢,太大亂跳甚至發散;看損失曲線判斷。
  • 過擬合時訓練損失降、驗證損失升。對策有提前停止、L2 正則化與更多資料,有沒有用要實測;在手寫數字的實驗裡,增加資料的效果最明顯。
  • 提前停止要用驗證資料決定,不能用測試資料。
  • PyTorch 把訓練迴圈交給你寫:model(xb)、loss.backward()、optimizer.step()、optimizer.zero_grad(),正好對應前兩課的前向、反向與更新。
  • 下一課是專題:把整門課用在校園單車的借用量預測,從切分、特徵、模型比較一路做到評估報告。
這一課完成了!
Next lesson:專題:校園單車借用量
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。