iPAS AI 奈米級拆解程式題解說・中級科目三 115-01
Question 48 · TensorFlow / Keras · matplotlib

訓練曲線怎麼讀:
訓練 0.81、驗證 0.72,這個差距代表什麼

第 46~48 題是同一支 CIFAR-10 程式:第 46 題準備資料、第 47 題建模型,第 48 題只給了一張訓練結果的圖。考卷沒印出「訓練」和「畫圖」那幾行,這頁先把它們補出來,一行一行講清楚 model.fit() 交回來的 history 是什麼、圖上的每一個點從哪裡來;再把四個選項說的情況各真的訓練一次,親眼看學習率太低、批次太大、低度擬合、過擬合的曲線各長什麼樣子。

TensorFlow 2.xKeras 3matplotlib compilefitHistoryhistory.historyplt.plot 過擬合零基礎可讀

讀完這頁,你會知道

  1. model.compile() 和 model.fit() 各做什麼;fit 交回的 History 物件、它的 history 字典、字典裡的 list 各是什麼
  2. epoch、batch、step 的關係:5 萬張圖、每批 32 張,一個 epoch 要更新 1,563 次
  3. plt.plot() 怎麼把 list 畫成線、'bo-' 是什麼意思,以及為什麼題目說的「Epochs=6」其實是第 7 輪
  4. 訓練曲線和驗證曲線怎麼讀:過擬合、低度擬合、學習率太低、批次太大各長什麼樣子(全部實際訓練過)
  5. 為什麼前幾輪的驗證準確率,反而比訓練準確率高
§0

題目

請依據 CIFAR-10 影像分類程式與下圖作答: CIFAR-10 包含 10 類、32×32 彩色影像,每張影像有 RGB 三個通道。 參考下圖繪圖結果,當 Epochs=6 時,訓練曲線值(0.81)明顯大於驗證曲線值(0.72),下列何者為最可能的原因?
第 48 題原卷附圖:標題 Training and Validation Accuracy for CIFAR-10 with CNN 的折線圖,橫軸 Epochs 從 0 到 9,縱軸 Accuracy。藍色圓點線是 Train Accuracy,從 0.47 一路升到 0.84;紅色圓點線是 Validation Accuracy,從 0.63 升到 0.80 上下,在橫軸 6 的位置掉到 0.72,之後又回到 0.80 左右
原卷附圖(考卷只給了這張圖;產生它的程式在 §3 補出來)

先憑直覺選一個,讀完再回來對照:

先抓題目的三個關鍵字

① 「請依據 CIFAR-10 影像分類程式」:這是題組的第 3 題,和第 46 題(資料前處理)、第 47 題(建模型)是同一支程式;這張圖就是拿第 47 題的模型訓練 10 輪的結果。② 「Epochs=6」:圖的橫軸從 0 開始,所以標著 6 的那個點其實是第 7 輪(§1 觀念 3 會講為什麼)。③ 「訓練 0.81 明顯大於驗證 0.72」:模型在「練過的資料」上表現比「沒練過的資料」好,問的是這個落差最可能的原因。

考卷沒印出來的程式

第 46 題的程式已經 import matplotlib.pyplot as plt、準備好 x_train 等四份資料;第 47 題做好了 model。從「模型」到「這張圖」,中間一定還有三件事:設定訓練方式(compile)→ 訓練(fit)→ 把成績畫出來(plot)。§3 依照圖上的標題、圖例文字、藍紅圓點線、灰色虛線格線,把這段程式重建出來;這是最常見的寫法,考卷原本的程式細節(例如 batch_size)可能不同,但不影響本題的判斷。

§1

先補 Python 基本功:讀懂這 16 行需要的 6 個觀念

這節只講這段程式真的會用到的語法,例子全部取自本題。中間有兩支動畫:先用「練習卷和模擬考」看懂兩條線在比什麼,最後打開記憶體,看 fit 交回來的東西真正長什麼樣子;每支動畫前後都有文字說明。

① 回傳值觀念 1
compile vs fit
→
② 練習卷和模擬考動畫 1
兩條線在比什麼
→
③ dict・list・畫圖觀念 2~6
→
④ 記憶體實況動畫 2
從 fit 到那張圖

第 3 支動畫(四個選項的曲線,各真的訓練一次)放在 §4,和學習曲線判讀器一起看。

觀念 1|呼叫方法會有「回傳值」:compile 什麼都不交回,fit 交回一份成績紀錄

model 是第 47 題照 Sequential 設計圖做出來的物件。model.compile(...)、model.fit(...) 是在叫這個物件做事(呼叫它的方法)。做完事,方法可以「交回」一個東西,叫回傳值;寫成 名字 = 物件.方法(...),就是把交回來的東西貼上名牌:

呼叫它做了什麼交回什麼(實測)
model.compile(...)把「怎麼訓練」的設定記在 model 身上:損失函數、優化器、要報告的分數None(什麼都沒有)
history = model.fit(...)真的開始訓練,邊訓練邊記成績一個 History 物件(成績紀錄本),貼上名牌 history

所以 compile 那行前面不寫 變數 =;fit 那行前面寫 history =,之後才拿得到成績來畫圖。順序也不能反:compile 只是設定,fit 才是訓練。

新手常把名牌貼錯行

寫成 history = model.compile(...),history 拿到的是 None;之後 history.history 會出現 AttributeError: 'NoneType' object has no attribute 'history'(實測)。看到 NoneType,先回頭檢查那個名字是從哪一行的回傳值來的。

同樣是「點」:plt.plot(...) 和 model.fit(...) 差在哪?

點只是「取出左邊那個東西身上的某樣東西」。plt 是第 46 題 import 進來的模組(工具箱,模組在 Python 裡也是一個物件),plt.plot 是放在工具箱裡的函式,呼叫時不會把 plt 傳進去(實測 type(plt.plot) 是 function)。model 是照設計圖做出來的物件,model.fit 是它的方法,呼叫時 model 會自動被傳進去,fit 才知道要訓練「這一個」模型(實測 type(model.fit) 是 method,model.fit.__self__ is model 是 True)。

看動畫 1 之前:這支要回答什麼

觀念 1 講了 fit 會交回一份成績紀錄。動畫 1 先不看程式,把「訓練」變成一個故事:模型是準備考試的學生,訓練資料是附答案的練習卷(5 萬題),驗證資料是只打分數、從不拿來學的模擬考(1 萬題)。每寫完一遍(一個 epoch)記下兩個分數,連起來就是考卷那張圖。

帶著三個問題看:① 為什麼第一遍的模擬考分數反而比練習卷高?② 橫軸 6 那一點,兩條線差多少、代表什麼?③ 只掉一輪又回來,和真正嚴重的過擬合,差在哪?

看的時候注意顏色:藍線是練習卷(訓練)、紅線是模擬考(驗證)。

動畫 1練習卷和模擬考:兩條線在比什麼2 分 12 秒・有旁白,也有字幕

練習卷、模擬考、學生是比喻;曲線上的數字取自考卷附圖。「一次 32 題、一輪 1,563 批」是用本題的 5 萬張訓練圖和 Keras 預設的 batch_size=32 算出來的。最後「嚴重過擬合」那張小圖是同一個模型拿掉 Dropout 和 BatchNormalization、實際訓練 20 輪的結果(§4 有完整數據)。

看完動畫 1,你應該能回答:訓練資料和驗證資料差在哪?
訓練資料(練習卷)拿來算 loss、更新權重,模型「學」的就是它;驗證資料(模擬考)每輪結束只拿來打分數,從不拿來更新權重,所以它的分數代表模型對「沒學過的圖」的表現。
為什麼第一輪的驗證準確率(0.63)反而比訓練準確率(0.47)高?
訓練準確率是這一輪 1,563 批「一邊學一邊算」的平均,一開始還很生疏的那幾批也算進去;而且訓練時 Dropout 正在隨機關掉神經元。驗證準確率是這一輪練完、模型最完整的狀態下才考的。§4 有實測。
橫軸 6 那一點代表什麼?
第 7 輪(橫軸從 0 開始):訓練 0.81、驗證 0.72,差了 0.09。訓練資料上好、沒看過的資料上差,就是過擬合的樣子,所以答案是 D。
只掉一輪又回來,能不能確定是過擬合?
不能單憑一個點確定。考卷這張圖的驗證準確率在橫軸 7、8 又回到 0.80 以上。真正確定的訊號是:差距持續拉大、驗證 loss 開始回升。但在四個選項裡,只有過擬合能解釋「訓練明顯高於驗證」。

觀念 2|字典 dict:用「鍵」找「值」

fit 交回的成績紀錄本裡,成績是用字典(dict)存的。字典就像一本有索引標籤的筆記本:標籤(鍵,key)是名字,內容(值,value)是資料。用大括號寫,冒號左邊是鍵、右邊是值:

{'accuracy':     [0.47, 0.65, …],   # 訓練準確率,每輪一個
 'loss':         [ …              ],   # 訓練損失
 'val_accuracy': [0.63, 0.69, …],   # 驗證準確率
 'val_loss':     [ …              ]}   # 驗證損失
  • 拿值用方括號+鍵:d['accuracy'] 拿到訓練準確率那一串。鍵是字串,引號不能少。
  • 鍵怎麼來的:'accuracy' 來自 compile 的 metrics=['accuracy'];'loss' 永遠會有;前面加 val_ 的兩個,是因為 fit 給了驗證資料。實測鍵的順序是 accuracy、loss、val_accuracy、val_loss。
  • 看有哪些鍵:d.keys()。不確定鍵叫什麼時,先印出來看,不要猜。
KeyError:鍵打錯或根本不存在

很舊的教學(Keras 2.3 以前)用 'acc',現在的版本鍵叫 'accuracy':寫 history.history['acc'] 會出現 KeyError: 'acc'。fit 沒給驗證資料時,字典裡只有 ['accuracy', 'loss'],再去拿 'val_accuracy' 也是 KeyError(兩個都實測)。

觀念 3|list 與索引:位置從 0 開始數

字典裡每個鍵對應的值是一個 list(串列):照順序排好的一串數字,訓練幾輪就有幾個。拿其中一個用方括號+位置,位置叫索引(index),從 0 開始數:

寫法拿到什麼考卷這張圖
acc[0]第 1 個數字第 1 輪,圖上橫軸 0,0.47
acc[6]第 7 個數字第 7 輪,圖上橫軸 6,0.81(題目說的 Epochs=6)
acc[-1]最後一個(負號從後面數)第 10 輪,0.84
len(acc)有幾個10
acc[10]不存在IndexError: list index out of range(實測)

為什麼圖的橫軸會從 0 開始?因為 plt.plot(acc) 只給了一個 list,matplotlib 就拿每個數字的索引當橫軸(觀念 6、§3 第 9 行)。所以題目的「Epochs=6」是索引 6,也就是第 7 輪。考試只要知道「那一輪」是哪一點就好,但寫程式時這個差 1 很容易讓人看錯。

觀念 4|history.history:變數和屬性剛好同名

這兩個 history 不是同一個東西:

history.history['accuracy']
#  ↑       ↑         ↑
#  名牌    屬性      鍵
#  (你取的變數名,指向 History 物件)
#          (History 物件身上,名叫 history 的那個字典)
#                    (字典裡的一個鍵 → 拿到 list)

第一個 history 是你在 fit 那行自己取的名字,叫什麼都可以;第二個 .history 是 Keras 規定的屬性名,不能改。把變數改名就看得很清楚:

h = model.fit(...)              # 名牌改叫 h
h.history['accuracy']           # 一樣拿得到
h.epoch                         # [0, 1, …, 9]:每一輪的編號(另一個屬性)
h.params                        # {'verbose': …, 'epochs': 10, 'steps': 1563}
少寫一層 .history

寫成 history['accuracy'](少了 .history),是在對 History 物件本身用方括號,會出現 TypeError: 'History' object is not subscriptable(實測):這本紀錄本不能直接翻,要先打開它的 history 那一頁。

觀念 5|tuple 和關鍵字參數:validation_data=(x_test, y_test)

  • tuple(元組):用小括號把幾樣東西包成一組,(x_test, y_test) 就是「測試圖片和測試答案」這一對。和 list 很像,但做好之後不能改內容。第 46 題的 (x_train, y_train), (x_test, y_test) = …load_data(),回傳的也是 tuple。
  • 位置參數:model.fit(x_train, y_train, …) 前兩個不寫名字,靠位置決定意思:第 1 個是輸入、第 2 個是答案,順序不能換。
  • 關鍵字參數:epochs=10、validation_data=(…) 寫出名字,順序可以隨便排。沒寫的參數用預設值,例如沒寫 batch_size 就是 32(實測:100 筆資料、不寫 batch_size,一輪是 4 步)。

觀念 6|plt.plot() 的三樣東西:資料、格式字串、label

plt.plot(history.history['accuracy'], 'bo-', label='Train Accuracy')
#        ↑ 資料(一個 list)              ↑ 格式   ↑ 圖例上的名字
  • 只給一個 list:當成縱軸的值,橫軸自動用索引 0、1、2……;給兩個(plt.plot(x, y))才是自己指定橫軸。
  • 格式字串 'bo-':三個字元各管一件事,b 藍色(blue)、o 圓點、- 實線。'ro-' 就是紅色圓點實線,'g--' 是綠色虛線、沒有點。
  • label:這條線在圖例上的名字;要等到 plt.legend() 才會真的畫出圖例。

pyplot 會記住「目前這張圖」:第一次 plot 時自動開一張畫布(Figure)和一個座標區(Axes),之後的 plot、title、legend 都畫在同一張上,直到 plt.show()。

把 6 個觀念串起來:本題的「類別 → 物件 → 方法」路線

① 類別(設計圖)Sequential(第 47 題)、Adam、History、dict、list、matplotlib 的 Figure、Axes、Line2D
→
② 物件(照設計圖做出來的)model(你做的)
compile 時 Keras 從字串 'adam' 做出 Adam 物件
fit 時 Keras 做出 History 物件 → history
plot 時 matplotlib 做出 Figure、Axes、Line2D
→
③ 方法/屬性(叫它做事、看它身上的東西)model.compile()、model.fit()
history.history、history.epoch
(plt.plot() 是工具箱裡的函式,不是方法)

把觀念合起來:Python 執行 compile、fit、plot 時,記憶體裡發生什麼

看動畫 2 之前:這支要回答什麼

動畫 1 是比喻;觀念 2~6 講了字典、list 索引、history.history、tuple 與關鍵字參數、plot 的參數。動畫 2 打開真正的記憶體,用一次實際執行的位址,一步一步畫出 compile、fit、plot 做出了哪些物件、名牌貼在哪裡。

帶著三個問題看:① compile 有沒有做出新的名牌?字串 'adam' 變成了什麼?② 名牌 history 指向什麼?history.history 又是什麼?③ 為什麼圖上的 6 是第 7 輪?

看的時候抓四種樣子:綠色格子是物件、黃色虛線小卡寫著它是照哪張設計圖做的、藍色格子是 NumPy 陣列、紅色名牌是名字。

動畫 2打開記憶體:從 fit 到那張圖1 分 57 秒・有旁白,也有字幕

位址取自一次實際執行(Python 3.13、TensorFlow 2.20、Keras 3.13.1、matplotlib 3.10.7),每次執行都不一樣。為了快,那次用假資料訓練 2 輪;型別(History、dict、list、float、Line2D)、鍵的順序、history is model.history 都和真正的 CIFAR-10 訓練相同。list 裡的數字用考卷附圖的值;loss 考卷沒給,畫面上用「…」代替。

看完動畫 2,你應該能回答:compile 做出新的名牌了嗎?
沒有。compile 回傳 None,只是把設定記在 model 身上;其中字串 'adam' 被換成一個照 Adam 設計圖做出的優化器物件(model.optimizer,learning_rate 0.001)。
名牌 history 指向什麼?history.history 是什麼?
history 指向 fit 一開始就做好的 History 物件(實測和 model.history 是同一個);history.history 是它身上的字典,4 個鍵各對應一個長度 10 的 list。
history.history['accuracy'][6] 是第幾輪?
第 7 輪。list 的索引從 0 開始,[6] 是第 7 個數字;考卷圖上是 0.81。
為什麼圖的橫軸從 0 開始?
plt.plot() 只給了一個 list,matplotlib 就用索引 0~9 當橫軸(實測 get_xdata() 就是 0~9)。想從 1 開始,要自己給:plt.plot(range(1, 11), acc, 'bo-')。
§2

認識這次登場的框架與名詞:Keras 的訓練、matplotlib、學習曲線

Keras(tf.keras)
François Chollet 2015 年發表;TensorFlow 2.16 起內建的 tf.keras 就是 Keras 3

把訓練包成四個方法:compile(設定)、fit(訓練)、evaluate(評分)、predict(預測)。本題用前兩個。

比喻:駕訓班:教練(fit)帶著學生(model)一圈一圈練,每圈結束記一次成績。

History(keras.callbacks)
每次呼叫 fit,Keras 都會自動做一個;實測類別是 keras.src.callbacks.history.History

它是一種 callback回呼:訓練過程中,在「每一輪開始、每一輪結束」這些固定時間點被 Keras 叫起來做事的物件。History 的工作就是在每一輪結束時把成績抄下來。,每一輪結束時把 logs 裡的分數一個個附加到 history 字典的 list 後面。

比喻:成績紀錄本。

matplotlib.pyplot(plt)
John D. Hunter 2003 年開發;pyplot 是模仿 MATLAB 畫圖指令的那一層

一行一個動作:plot 畫線、title 標題、xlabel/ylabel 軸名、legend 圖例、grid 格線、show 顯示。背後其實是 Figure → Axes → Line2D 這些物件(動畫 2 有畫)。

比喻:畫圖機器人:你一句一句下指令,它都畫在「目前那張紙」上。

CIFAR-10
Alex Krizhevsky、Vinod Nair、Geoffrey Hinton 整理,2009 年

60,000 張 32×32 彩色小圖、10 類(飛機、汽車、鳥、貓、鹿、狗、青蛙、馬、船、卡車),每類 6,000 張;固定分成訓練 50,000 張、測試 10,000 張(第 46 題的 load_data 已經分好)。

10 類亂猜的準確率是 10%。

訓練時一定會看到的五個名詞

名詞白話本題的數字
batch(批次)、batch_size一次拿多少張圖算一次 loss、更新一次權重沒寫 → 預設 32 張
step(步、iteration)處理一個 batch+更新一次權重50,000 ÷ 32 = 1,562.5 → 1,563 步(最後一批只有 16 張)
epoch(輪)整份訓練資料從頭到尾看過一遍epochs=10:10 輪,共 15,630 次更新
accuracy(準確率)猜對的比例圖的縱軸
loss(損失)猜錯的程度,越小越好;模型真正在「降低」的是它考卷沒畫出來

訓練、驗證、測試:三份資料各管什麼

資料拿來做什麼比喻本題
訓練資料算 loss、更新權重:模型「學」的就是它練習卷(附答案)x_train, y_train,5 萬張
驗證資料每輪結束打一次分數,不拿來更新權重;用來挑設定、決定什麼時候停模擬考validation_data=(x_test, y_test),1 萬張
測試資料全部決定好之後,最後只考一次,回報真正的實力正式考試(本題拿它當驗證資料用了)
拿測試資料當驗證資料,是教學常見的偷懶

TensorFlow 官方的 CNN 入門教學也是 validation_data=(test_images, test_labels)。問題是:如果你看著這條驗證曲線去調設定、挑最好的一輪,測試資料就已經「被偷看過」,最後的分數會偏樂觀。嚴謹的做法是從訓練資料再切一塊當驗證(例如 validation_split=0.1,拿最後 10% 的訓練資料出來),測試資料留到最後才用。這不影響本題的判斷。

一輪(epoch)裡面發生的事

① 打亂順序shuffle=True(預設)
→
② 每 32 張前向 → loss → 反向 → Adam 更新
重複 1,563 次
→
③ 訓練分數這 1,563 批一路累積的平均
(Dropout 開著)
→
④ 驗證分數用 1 萬張驗證資料重新算一次
(推論模式,Dropout 關掉)
→
⑤ 抄進 history4 個 list 各加一個數字

注意 ③ 和 ④ 的量法不一樣:訓練分數是「邊學邊算」的平均,而且量的時候 Dropout 正在隨機關掉神經元;驗證分數是一輪結束後、模型最完整的狀態下重新考一次。這就是前幾輪驗證分數常常比訓練分數還高的原因,§4 有實測。

學習曲線的四種典型樣子(§4 都有實測)

樣子訓練曲線驗證曲線白話
學得好升高跟著升高,兩條靠近練習和模擬考都進步
過擬合一直升高停住甚至下降,差距越拉越大;驗證 loss 先降後升背答案:練習卷越來越熟,新題目沒進步
低度擬合低也低,兩條靠近連練習卷都學不會:模型太小或練太少
學習率太低上升很慢也很慢,兩條靠近每次只改一點點,要練很久
§3

逐行解剖(考卷沒印出來的訓練與畫圖程式)

左邊是重建出來的完整程式(接在第 46、47 題的程式後面執行),點任一行可以跳到解說;右邊往下捲時,左邊會自動標出目前講到哪一行。手機上會改成上下排列。

q48.py(重建)16 行

把訓練規則記在 model 身上:用什麼算分(損失函數)、用誰來改權重(優化器)、訓練時順便報告哪個分數。這一行不訓練,也不交回任何東西。

逐字拆解

model
變數第 47 題做好的 Sequential 物件(23 個層、816,938 個參數)。
.compile( … )
方法「設定訓練方式」。括號跨了三行,Python 會一直讀到右括號才算一句。
loss='categorical_crossentropy'
關鍵字參數多類別交叉熵:配合第 46 題轉好的 One-Hot 標籤和第 47 題輸出層的 softmax。正確類別的機率越低,損失越大。
optimizer='adam'
關鍵字參數只寫一個字串,Keras 會照 Adam 設計圖替你做出一個優化器物件,學習率用預設的 0.001。
metrics=['accuracy']
關鍵字參數一個 list,列出訓練時要順便算的分數。history 裡的 'accuracy' 和 'val_accuracy' 就是從這裡來的;不寫這個參數,就只會有 loss。

這一行執行時發生什麼事

Keras 把三個設定存進 model 物件。最特別的是 optimizer:字串 'adam' 被換成一個真正的 Adam 物件,之後每一步「依梯度改權重」都是它在做。整行回傳 None。

執行後多了: model.loss → 'categorical_crossentropy' model.optimizer → Adam 物件(keras.src.optimizers.adam.Adam,learning_rate = 0.001) (回傳值:None;沒有新的名牌)

compile 常用的參數

參數本題還可以怎麼寫
loss'categorical_crossentropy'標籤是整數 0~9(沒轉 One-Hot)時用 'sparse_categorical_crossentropy';二元分類用 'binary_crossentropy'
optimizer'adam'想自己指定學習率,就自己照設計圖做:tf.keras.optimizers.Adam(learning_rate=1e-4);其他還有 'sgd'、'rmsprop'
metrics['accuracy']可以放好幾個,例如 ['accuracy', tf.keras.metrics.Precision()];每一個都會變成 history 裡的一個鍵

新手常踩的坑

  • history = model.compile(...):history 拿到 None,之後 AttributeError: 'NoneType' object has no attribute 'history'(實測)。
  • 忘了 compile 就直接 fit:ValueError: You must call `compile()` before using the model.(實測)。順序永遠是「建模型 → compile → fit」。
  • 標籤沒轉 One-Hot(還是 0~9 的整數)卻用 categorical_crossentropy:ValueError: Arguments `target` and `output` must have the same shape. Received: target.shape=(32, 1), output.shape=(32, 10)(實測的是 3 類的小模型,訊息裡是 (32, 3);本題 10 類就是 (32, 10))。第 46 題已經用 to_categorical 轉成 One-Hot,所以本題配 categorical 是對的。

開始訓練:把 5 萬張訓練圖片看 10 遍,每看完一遍,就用 1 萬張驗證圖片打一次分數;全部跑完,把成績紀錄本交回來,貼上名牌 history。

逐字拆解

history =
變數接住 fit 的回傳值:一個 History 物件。
model.fit( … )
方法「訓練」。model 會被自動傳進去,所以訓練的就是它自己的 816,938 個參數。
x_train, y_train
位置參數訓練圖片 (50000, 32, 32, 3)、One-Hot 答案 (50000, 10)。第 1 個一定是輸入、第 2 個一定是答案。
epochs=10
關鍵字參數整份資料看 10 遍。不寫的話預設只有 1 遍。
validation_data=(x_test, y_test)
關鍵字參數一個 tuple:驗證用的圖片和答案。每輪結束用它打分,不拿來更新權重;history 裡的 val_ 開頭的兩個鍵就是它的成績。
(沒寫)batch_size
預設值32:每 32 張更新一次。

這一行執行時發生什麼事

  1. Keras 先照 History 設計圖做出一個成績紀錄本,掛在 model.history,裡面的 history 字典是空的 {}。
  2. 每一輪:打亂 5 萬張的順序,每 32 張做一次「前向 → 算 loss → 反向 → Adam 更新權重」,共 1,563 次;同時累積這一輪的平均 loss 和 accuracy。
  3. 這一輪練完,用驗證資料(推論模式)算出 val_loss、val_accuracy。
  4. History 把 4 個數字分別附加到字典裡 4 個 list 的最後面。
  5. 10 輪跑完,fit 把 History 物件交回來,貼上名牌 history(實測 history is model.history 是 True:同一個物件)。
執行後多了: history → History 物件 history.history → {'accuracy': [10 個], 'loss': [10 個], 'val_accuracy': [10 個], 'val_loss': [10 個]} history.epoch → [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] history.params → {'verbose': …, 'epochs': 10, 'steps': 1563} model 的 816,938 個參數 → 全部被改過 15,630 次

fit 常用的參數

參數預設做什麼
x、y—輸入和答案
batch_size32每批幾張。越大,一輪的更新次數越少
epochs1看幾遍
validation_dataNone指定驗證資料(tuple)
validation_split0.0改從訓練資料最後面切一個比例當驗證;兩者擇一
shuffleTrue每輪打亂訓練資料的順序
verbose'auto'印多少訊息:0 不印、1 進度條、2 每輪一行
callbacksNone在每輪前後插手做事的物件,例如 EarlyStopping(§6)

新手常踩的坑

  • 沒給驗證資料,後面卻畫 val_accuracy:KeyError: 'val_accuracy'(實測,字典裡只有 accuracy、loss)。
  • 重跑 fit 那一格,模型會從目前的權重接著練,不是重新開始;history 則換成新的一本(實測新的 fit 回傳不同的 History 物件)。想從頭來,要把建模型那一格也重跑。
  • CPU 上一輪要好幾十秒(§6 有實測時間),在 Colab 記得開 GPU。

從 history 裡拿出 10 個訓練準確率,畫成一條藍色圓點實線,圖例上叫 Train Accuracy。

逐字拆解

plt
模組第 46 題 import matplotlib.pyplot as plt 取的綽號。
.plot( … )
函式pyplot 工具箱裡的畫線工具。
history.history['accuracy']
屬性+鍵History 物件 → history 字典 → 'accuracy' 那個 list(10 個 float)。
'bo-'
格式字串b 藍色、o 圓點、- 實線。
label='Train Accuracy'
關鍵字參數圖例上的文字。

這一行執行時發生什麼事

還沒有圖,pyplot 就先開一張 Figure(畫布)、在上面放一個 Axes(座標區)。因為只給了一個 list,橫軸自動用索引 0, 1, …, 9(實測 get_xdata() 就是這 10 個整數),然後照 Line2D 設計圖做出一條線物件放進 Axes,回傳 [Line2D 物件]。

執行後多了: Figure 物件(畫布) └─ Axes 物件(座標區) └─ Line2D 物件:x = [0, 1, …, 9]、y = 10 個訓練準確率、藍、圓點、實線

格式字串怎麼組

位置常用字元
顏色b 藍、r 紅、g 綠、k 黑、c 青、m 洋紅、y 黃
點的形狀o 圓、s 方、^ 三角、x 叉、. 小點(不寫就沒有點)
線型- 實線、-- 虛線、: 點線、-. 點虛線(只寫點、不寫線型,就只有點沒有線)
想讓橫軸從 1 開始

自己給橫軸:epochs = range(1, len(acc) + 1),再 plt.plot(epochs, acc, 'bo-')。114 年第二次科目三第 50 題的程式就是這樣寫的。本題沒有給,所以圖上的 6 是第 7 輪。

新手常踩的坑

  • 少寫一層:plt.plot(history['accuracy']) → TypeError: 'History' object is not subscriptable(實測)。
  • 把整個字典丟進去:plt.plot(history.history) 不是你要的圖。一次只畫一個 list。

再拿出 10 個驗證準確率,畫成紅色圓點實線,圖例上叫 Validation Accuracy。

和第 9 行只差兩個地方:鍵從 'accuracy' 換成 'val_accuracy',格式從 'bo-' 換成 'ro-'。因為還在同一張圖上,matplotlib 在同一個 Axes 裡再做一條 Line2D(實測 Axes 裡有 2 條線),兩條線共用同一組座標,才能直接比高低。

執行後多了: Axes 物件 ├─ Line2D(藍):訓練準確率 └─ Line2D(紅):驗證準確率
這兩條線就是本題的考點

藍線是模型在練過的 5 萬張上的成績,紅線是在沒練過的 1 萬張上的成績。藍線比紅線高,代表模型在練過的資料上表現比較好;兩條線拉得越開,越像是在「背」訓練資料,而不是學到能套用到新圖片的規律。

在圖上方寫標題,在橫軸下面寫 Epochs、縱軸旁邊寫 Accuracy。

plt.title(…)
函式圖的標題。考卷那張圖的標題就是這串文字。
plt.xlabel(…)、plt.ylabel(…)
函式橫軸、縱軸的名字(x 橫、y 直)。

新手常踩的坑

  • 標題寫中文,預設字型沒有中文字:每個中文字會變成方框,並出現 UserWarning: Glyph 35347 (\N{CJK UNIFIED IDEOGRAPH-8A13}) missing from font(s) DejaVu Sans.(實測,35347 就是「訓」)。要先設定有中文的字型,例如 Windows 上 plt.rcParams['font.family'] = 'Microsoft JhengHei'。

收集每條線的 label,在圖上畫出「藍線=Train Accuracy、紅線=Validation Accuracy」的說明框。

沒有指定位置時,loc='best'(實測預設值)會自動找一個最不擋到線的角落;考卷那張圖的圖例就放在左上角。matplotlib 會做出一個 Legend 物件放進 Axes。

新手常踩的坑

  • plot 時沒寫 label,legend 會畫不出東西,並警告 No artists with labels found to put in legend.(實測)。
  • legend 要寫在兩個 plot 之後:寫在前面時還沒有任何線,一樣什麼都沒有。

打開背景格線,並設成虛線。考卷圖上那些灰色虛線就是它。

True 是「打開」;linestyle='--' 是關鍵字參數,指定虛線(和格式字串的 -- 同一個意思)。有了格線,才好讀出「橫軸 6 的藍點大約 0.81、紅點大約 0.72」這種數字。

把前面畫在 Figure 上的所有東西,真的畫到螢幕上。

在 Colab、Jupyter 裡,儲存格跑完就會自動顯示,不寫也看得到;在一般的 .py 程式裡,要 plt.show() 才會跳出視窗,而且程式會停在這行,等你把視窗關掉才繼續。

新手常踩的坑

  • 想存成圖檔:plt.savefig('curve.png', dpi=150) 要寫在 plt.show() 之前。在 .py 程式裡 show 之後,原本那張圖已經關掉,再存常常是一張空白圖。

§4

互動實驗:學習曲線判讀器(全部是真的訓練結果)

下面每一組都是在本機真的訓練出來的(TensorFlow 2.20、Keras 3.13.1、CPU):同一份 CIFAR-10、同一份驗證資料、同一個亂數種子,每次只改一個設定,對應題目的四個選項。考卷那張圖也放進來對照。所有數字都是 history 裡實際記下來的值。

學習曲線判讀器
先選一組訓練結果,再拖動下面的滑桿,看每一輪的訓練、驗證分數和差距。切到「損失」看 loss 的曲線。
看第幾輪
訓練—
驗證—
差距(訓練 − 驗證)—
這組設定—
和考題程式相比,這組改了什麼:

  

實測一:前幾輪,驗證為什麼反而比訓練高?

勾選判讀器上方的方框,會多一條綠色虛線:每一輪結束後,拿 1 萬張訓練圖片用推論模式(Dropout 關掉、BN 用累積的平均,也就是和驗證同一把尺)重新量一次準確率。考題設定那一次的實測:

橫軸fit 記下的訓練 accuracy驗證 val_accuracy訓練資料・推論模式重量
00.4820.5090.517
10.6540.7000.724
20.7120.7480.773
30.7490.7630.794
40.7730.7750.819
⋮
90.8530.8170.898

用同一把尺量,訓練資料每一輪都比驗證高。所以「驗證比訓練高」不是模型在沒看過的圖上比較厲害,而是 fit 記下的訓練分數打了兩次折:① 它是這一輪 1,563 批一路累積的平均,前面還沒學好的批次也算進去;② 量的時候 Dropout 正在隨機關掉神經元。反過來看最後一輪:用同一把尺,訓練 0.898、驗證 0.817,真正的落差是 0.081,比圖上看到的 0.036 還大。

實測二:同一支程式再跑一次,曲線就不一樣

考卷的圖本頁實測(同一支程式)
橫軸 6 訓練/驗證0.81/0.720.812/0.812
最後一輪 訓練/驗證0.84/0.800.853/0.817
每輪耗時—約 32 秒(本機 CPU)

權重的初始值、每一輪打亂資料的順序、Dropout 每次關掉哪些神經元,全都是隨機的,所以每次訓練的曲線都會有些不同。考卷那一次在橫軸 6 驗證掉到 0.72、下一輪又回到 0.80;本頁這一次沒有掉。單一輪的起伏不能當成結論,要看整條曲線的趨勢。

實測三:批次太大那一組,驗證為什麼有 3 輪卡在 0.10?

batch_size=1024 時,訓練準確率照常上升(第 3 輪 0.571),驗證準確率卻停在 0.10,正好是 10 類亂猜的水準;連訓練資料用推論模式重量也只有 0.10。所以這不是「背了訓練資料」。用同樣設定再訓練 3 輪,同一個模型、同一份驗證資料,換兩種模式考:

模式BN 用哪一組平均驗證準確率
推論模式(model.evaluate、predict 用的)累積的移動平均0.100
訓練模式(training=True)這一批自己算的平均0.609

模型其實學會了,問題出在 BatchNormalization 推論時用的移動平均。它每一步只更新一點點(momentum=0.99:新平均 = 0.99 × 舊平均 + 0.01 × 這一批的平均),起始值是平均 0、變異數 1。batch_size=1024 一輪只有 49 步,3 輪才 147 步,起始值還佔 0.99147 ≈ 23%,統計值還沒追上;到第 10 輪(490 步)驗證就回到 0.583。所以「批次太大」也可能讓兩條線分開,但長相完全不同:驗證貼在 0.10,訓練資料用推論模式量也一樣低。考卷的圖兩條線前 6 輪一起上升、緊緊靠著,不是這一種。

一個判斷技巧:用同一把尺,再量一次訓練資料

訓練高、驗證低時,把訓練資料也用推論模式量一次(勾選判讀器的方框):如果訓練資料還是很高,就是模型背了訓練資料,是過擬合(拿掉正則化那組最後一輪:0.933 對 0.732);如果訓練資料也一樣低,問題出在推論模式本身(批次太大那組前 3 輪:都是 0.10)。

動畫 3:四個選項的曲線,各真的訓練一次

看動畫 3 之前:這支要回答什麼

上面的判讀器可以自己切換;動畫 3 把五組實測依序講一遍:先看考題原設定這次跑出來的樣子,再看選項 A(學習率太低)、B(批次太大)、C(低度擬合)、D(拿掉正則化的過擬合)各長什麼樣子,最後把四張並排比較,並示範用 EarlyStopping 在對的時間停下來。

帶著三個問題看:① 哪一組的兩條線會「越拉越開」?② 批次太大那組明明也分開了,為什麼不算過擬合?③ 驗證 loss 先降後升,EarlyStopping 會停在哪一輪?

看的時候注意:藍線是訓練、紅線是驗證、綠色虛線是訓練資料用推論模式重量(只在批次太大那段出現)。

動畫 3四種曲線:哪一種才是過擬合2 分 8 秒・有旁白,也有字幕

每一條曲線都是本機實際訓練的 history(TensorFlow 2.20、Keras 3.13.1、CPU,同一份 CIFAR-10、同一個亂數種子,每次只改一個設定)。「訓練模式考驗證 0.61」來自同樣設定另外訓練 3 輪的確認實驗;EarlyStopping 停在橫軸 6、退回橫軸 3,是把那次真實的驗證 loss 交給 Keras 的 EarlyStopping 物件逐輪檢查的結果。

看完動畫 3,你應該能回答:哪一組的兩條線會越拉越開?
拿掉 Dropout 和 BatchNormalization 那組:訓練升到 0.946,驗證停在 0.73 左右,差距拉大到 0.21,驗證 loss 從橫軸 3 之後一路上升。這就是過擬合(選項 D)。
批次太大那組也分開了,為什麼不算過擬合?
它的驗證前 3 輪貼在 0.10(亂猜),而且連訓練資料用推論模式重量也是 0.10;換訓練模式考同一份驗證資料卻有 0.61。問題出在 BatchNormalization 的移動平均還沒追上,不是模型背了訓練資料。
學習率太低和低度擬合,曲線差在哪?
兩者兩條線都低、靠在一起。學習率太低是「還在慢慢往上爬」(10 輪從 0.19 爬到 0.44);低度擬合是「很快就停住、爬不上去」(小模型第 4 輪起就停在 0.37~0.39)。
EarlyStopping(monitor='val_loss', patience=3) 在拿掉正則化那次會停在哪?
驗證 loss 在橫軸 3 最低,之後連續 3 輪沒有更低,所以在橫軸 6 停下;加上 restore_best_weights=True,權重退回橫軸 3。
§5

回到題目:逐項對答案

(A) 學習率(Learning Rate)太低 錯

學習率太低時,每一步只改一點點,訓練和驗證都會爬得很慢,而且兩條線靠在一起。實測把 Adam 的學習率從 0.001 降到 0.00001:10 輪後訓練 0.436、驗證 0.440(原設定是 0.853/0.817),驗證從頭到尾都沒有落後訓練。本題訓練已經 0.81,而且明顯高於驗證,不是學得慢的樣子。

(B) 批次大小(Batch Size)設定太大 錯

批次太大最直接的影響是每輪更新次數變少:實測 batch_size=1024 一輪只更新 49 次(原設定 1,563 次),10 輪後訓練只到 0.768。這組實測的驗證有 3 輪卡在 0.10,兩條線確實分開,但原因是 BatchNormalization 的移動平均還沒追上(訓練模式考同一份驗證資料有 0.61),長相是「驗證貼在亂猜的 0.10」,和考卷「兩條線一起上升、後面才分開」完全不同。也有研究指出很大的批次可能讓泛化變差(Keskar 等人,2017),但這張圖看不出批次大小,最直接的證據是訓練與驗證的落差。

(C) 模型低度擬合(Underfitting) 錯

低度擬合是模型連訓練資料都學不好,兩條線都低。實測只用一層 Dense(30,730 個參數):10 輪後訓練 0.386、驗證 0.373。本題訓練已經 0.81,不是低度擬合。

(D) 模型過擬合(Overfitting) 正解

訓練 0.81 明顯高於驗證 0.72:模型在練過的資料上表現比較好,對沒看過的資料落後,這是過擬合的典型樣子。實測把 Dropout 和 BatchNormalization 拿掉、訓練 20 輪,就是確定的過擬合:訓練升到 0.946、驗證停在 0.73 左右,差距拉大到 0.21;驗證 loss 在橫軸 3 最低(0.811),之後一路升到 1.69。

這題的小陷阱:只看一個點

考卷的圖在橫軸 6 掉到 0.72,但橫軸 7、8 驗證又回到 0.80、0.82,最後一輪差距只有 0.04。嚴格來說,單一輪的掉落不足以斷定過擬合,要看差距是不是持續拉大、驗證 loss 有沒有回升。不過題目問的是「訓練明顯大於驗證的最可能原因」,四個選項裡只有過擬合會造成這個方向的落差,所以選 D。

記一張表就夠:四個選項的曲線長相(全部實測)

選項改了什麼曲線長相最後一輪 訓練/驗證
(A) 學習率太低lr 0.001 → 0.00001兩條都爬得很慢,靠在一起0.436/0.440
(B) 批次太大batch 32 → 1024訓練上升;驗證先貼在 0.10(BN 統計值還沒追上)0.768/0.583
(C) 低度擬合只剩一層 Dense兩條都低,幾乎不再進步0.386/0.373
(D) 過擬合拿掉 Dropout、BN,20 輪訓練一直升、驗證停住,差距越拉越大;驗證 loss 先降後升0.946/0.732
本題原設定—前幾輪驗證較高,後面訓練開始超前0.853/0.817
§6

動手跑跑看:第 46~48 題接起來,真的訓練一次

把第 46、47 題的程式和本頁補出來的訓練、畫圖接起來,就是一支完整、可以直接跑的程式。下面這份在本機實際跑過(TensorFlow 2.20、Keras 3.13.1、CPU),輸出和圖都是那一次的結果。複製到 Google Colab 也能直接執行(建議開 GPU);第一次執行會下載約 170 MB 的 CIFAR-10。

import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt

tf.keras.utils.set_random_seed(42)        # 固定亂數,讓結果比較能重現

# ---- 第 46 題:載入與前處理 ----
(x_train, y_train), (x_test, y_test) = datasets.cifar10.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
y_train = tf.keras.utils.to_categorical(y_train, 10)
y_test = tf.keras.utils.to_categorical(y_test, 10)

# ---- 第 47 題:建模型 ----
model = models.Sequential([
    layers.Input(shape=(32, 32, 3)),
    layers.Conv2D(32, kernel_size=(3,3), padding="same", activation="relu"),
    layers.BatchNormalization(),
    layers.Conv2D(32, (3,3), activation='relu', padding='same'),
    layers.BatchNormalization(),
    layers.MaxPooling2D((2,2)),
    layers.Dropout(0.25),

    layers.Conv2D(64, (3,3), activation='relu', padding='same'),
    layers.BatchNormalization(),
    layers.Conv2D(64, (3,3), activation='relu', padding='same'),
    layers.BatchNormalization(),
    layers.MaxPooling2D((2,2)),
    layers.Dropout(0.25),

    layers.Conv2D(128, (3,3), activation='relu', padding='same'),
    layers.BatchNormalization(),
    layers.Conv2D(128, (3,3), activation='relu', padding='same'),
    layers.BatchNormalization(),
    layers.MaxPooling2D((2,2)),
    layers.Dropout(0.25),

    layers.Flatten(),
    layers.Dense(256, activation='relu'),
    layers.BatchNormalization(),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

# ---- 第 48 題:訓練+畫圖(考卷沒印,這裡補上) ----
model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

history = model.fit(x_train, y_train,
                    epochs=10,
                    validation_data=(x_test, y_test),
                    verbose=2)                 # 每輪印一行

acc = history.history['accuracy']
val = history.history['val_accuracy']
print('鍵:', list(history.history.keys()))
print('索引 6(第 7 輪):訓練', round(acc[6], 4), '驗證', round(val[6], 4))
print('每輪差距(訓練 − 驗證):', [round(a - v, 3) for a, v in zip(acc, val)])

plt.plot(acc, 'bo-', label='Train Accuracy')
plt.plot(val, 'ro-', label='Validation Accuracy')
plt.title('Training and Validation Accuracy for CIFAR-10 with CNN')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.grid(True, linestyle='--')
plt.savefig('cifar10_curve.png', dpi=120)   # 要存檔,寫在 show 之前
plt.show()
實際跑出來的結果
Epoch 1/10
1563/1563 - 34s - 21ms/step - accuracy: 0.4850 - loss: 1.4934 - val_accuracy: 0.5888 - val_loss: 1.1906
Epoch 2/10
1563/1563 - 32s - 20ms/step - accuracy: 0.6605 - loss: 0.9694 - val_accuracy: 0.6586 - val_loss: 0.9676
Epoch 3/10
1563/1563 - 32s - 20ms/step - accuracy: 0.7147 - loss: 0.8212 - val_accuracy: 0.7328 - val_loss: 0.7728
Epoch 4/10
1563/1563 - 32s - 21ms/step - accuracy: 0.7463 - loss: 0.7296 - val_accuracy: 0.7255 - val_loss: 0.8106
Epoch 5/10
1563/1563 - 33s - 21ms/step - accuracy: 0.7743 - loss: 0.6533 - val_accuracy: 0.7293 - val_loss: 0.8236
Epoch 6/10
1563/1563 - 33s - 21ms/step - accuracy: 0.7957 - loss: 0.5909 - val_accuracy: 0.7799 - val_loss: 0.6552
Epoch 7/10
1563/1563 - 33s - 21ms/step - accuracy: 0.8134 - loss: 0.5394 - val_accuracy: 0.7911 - val_loss: 0.6178
Epoch 8/10
1563/1563 - 32s - 21ms/step - accuracy: 0.8286 - loss: 0.5024 - val_accuracy: 0.7976 - val_loss: 0.6141
Epoch 9/10
1563/1563 - 32s - 21ms/step - accuracy: 0.8411 - loss: 0.4638 - val_accuracy: 0.7909 - val_loss: 0.6434
Epoch 10/10
1563/1563 - 33s - 21ms/step - accuracy: 0.8488 - loss: 0.4307 - val_accuracy: 0.8330 - val_loss: 0.5135
鍵: ['accuracy', 'loss', 'val_accuracy', 'val_loss']
索引 6(第 7 輪):訓練 0.8134 驗證 0.7911
每輪差距(訓練 − 驗證): [-0.104, 0.002, -0.018, 0.021, 0.045, 0.016, 0.022, 0.031, 0.05, 0.016]
本頁實際執行畫出的訓練曲線:藍色訓練準確率從 0.49 升到 0.85;紅色驗證準確率第一輪 0.59 比訓練高,之後在橫軸 3、4 和 8 各掉一下,最後一輪跳到 0.83
這次執行存下的 cifar10_curve.png(和考卷的圖同一種畫法)
  • 每一輪的那一行怎麼讀:1563/1563 是這一輪跑完的步數(50,000 ÷ 32 進位);32s - 20ms/step 是這一輪花的時間和每一步的平均時間(本機 CPU);後面四個數字就是 history 裡 4 個 list 在這一輪新增的值。
  • 這次的曲線更像考卷:驗證在橫軸 3、4 和 8 都掉了一下,最後一輪又跳到 0.833。單一輪的起伏很常見,要看整條趨勢。
  • 第 1 輪驗證比訓練高 0.104:原因就是 §4 實測一,訓練分數是一路累積的平均,而且 Dropout 開著。
  • 和 §4「考題設定」那次用同一個亂數種子,數字還是不一樣:TensorFlow 在 CPU 上的平行運算不保證每次結果完全相同。要完全重現,得另外呼叫 tf.config.experimental.enable_op_determinism()(會變慢)。
  • plt.show() 會停住程式:在一般的 .py 程式裡,它會跳出一個圖的視窗,程式停在這一行,要把視窗關掉才會結束;本頁驗證時就是這樣。在 Colab、Jupyter 裡,圖會直接顯示在儲存格下方。

加一個 EarlyStopping:驗證變差就自動停下

es = tf.keras.callbacks.EarlyStopping(monitor='val_loss',          # 盯著驗證 loss
                                      patience=3,                  # 連續 3 輪沒進步就停
                                      restore_best_weights=True)   # 權重退回最好的那一輪
history = model.fit(x_train, y_train,
                    epochs=50,                                     # 上限給大一點,交給它決定
                    validation_data=(x_test, y_test),
                    callbacks=[es])

EarlyStopping 和 History 一樣是 callback,每一輪結束時被 Keras 叫起來檢查一次。把 §4「拿掉正則化」那次真實的驗證 loss 一輪一輪交給 Keras 的 EarlyStopping 物件檢查(沒有重新訓練):它在橫軸 6 停下(stopped_epoch = 6),權重退回橫軸 3(best_epoch = 3),正好是驗證 loss 最低的那一輪。那次訓練在橫軸 3 之後的 16 輪,驗證 loss 沒有一輪比橫軸 3 好;EarlyStopping 會省下最後 13 輪,留下最好的那一份權重。

新出現的語法是什麼白話
tf.keras.utils.set_random_seed(42)函式一次固定 Python、NumPy、TensorFlow 的亂數種子
verbose=2fit 的參數每一輪只印一行(不要進度條),適合存成文字記錄
zip(acc, val)內建函式把兩個 list 一對一配對:(第 1 輪訓練, 第 1 輪驗證)、(第 2 輪…, …)
[round(a - v, 3) for a, v in …]串列推導式每一對相減、留 3 位小數,收集成新的 list
plt.savefig('cifar10_curve.png', dpi=120)函式把目前的圖存成檔案;dpi 是解析度。要寫在 show 之前
tf.keras.callbacks.EarlyStopping(...)類別 → 物件照 EarlyStopping 設計圖做一個「提早停止」的 callback 物件
callbacks=[es]fit 的參數一個 list,放所有要在訓練中插手的 callback 物件(History 會自動加,不用寫)
§7

一頁總結+自我檢測

這題要帶走的 6 件事
  1. 第 48 題是 CIFAR-10 題組的第 3 題。考卷沒印出來的那段程式,一定是 compile → fit → plot 三步。
  2. model.compile() 只是設定、回傳 None;model.fit() 才訓練,回傳 History 物件。history.history 是字典,'accuracy'、'loss'、'val_accuracy'、'val_loss' 各對應一個 list,一輪一個數字。
  3. epoch=整份訓練資料看一遍;batch_size 不寫就是 32;5 萬張 ÷ 32 → 每輪 1,563 步。
  4. plt.plot(list) 用索引當橫軸,從 0 開始:題目的「Epochs=6」是第 7 輪。'bo-'=藍色、圓點、實線。
  5. 讀曲線:訓練高、驗證低,差距越拉越大 → 過擬合(本題答案 D);兩條都低 → 低度擬合;兩條都爬得很慢 → 學習率太低。真正確定要看趨勢和驗證 loss,不能只看一個點。
  6. 前幾輪驗證比訓練高是正常的:訓練分數是一輪裡「邊學邊算」的平均,而且量的時候 Dropout 正開著。
把第 6 行改成 epochs=20,history.history['accuracy'] 有幾個數字?最後一輪的值怎麼拿?
20 個。最後一輪是 history.history['accuracy'][-1](或 [19]);寫 [20] 會 IndexError: list index out of range。
想讓圖的橫軸從 1 開始,第 9、10 行要怎麼改?
先做一個 1~10 的橫軸:epochs = range(1, len(history.history['accuracy']) + 1),再寫成 plt.plot(epochs, history.history['accuracy'], 'bo-', …),第 10 行同樣多給 epochs。
fit 沒寫 validation_data,程式會在哪一行出錯?錯誤是什麼?
fit 正常跑完,但字典裡只有 'accuracy' 和 'loss';到第 10 行拿 'val_accuracy' 時出現 KeyError: 'val_accuracy'。
batch_size=64 時,一輪有幾步?10 輪一共更新幾次?
50,000 ÷ 64 = 781.25,無條件進位成 782 步(最後一批 16 張);10 輪共 7,820 次。批次加倍,更新次數大約減半。
驗證準確率在某一輪掉下來、下一輪又回來,就一定是過擬合嗎?還要看什麼?
不一定,單一輪的起伏很常見。要看接下來幾輪:差距是不是持續拉大、驗證準確率是不是停止進步,以及驗證 loss 有沒有從最低點開始回升。§4 的「拿掉正則化」那組就是確定的過擬合。
想讓訓練在驗證 loss 不再進步時自動停下來,並退回最好的那一輪,要加什麼?
在 fit 加上 callback:callbacks=[tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)]。它和 History 一樣是 callback,每輪結束時被叫起來檢查一次(§6 有範例)。