Python | OpenCV 避免過擬合
📑 目錄
📚 前言 🔎 過擬合是什麼? 🔎 如何判斷過擬合? 💡 先看 Loss 曲線,再學方法 💻 診斷:繪製 Loss 曲線 🔸 PyTorch 🔸 TensorFlow/Keras 💻 方法一:Dropout 🔸 PyTorch 🔸 TensorFlow/Keras 💻 方法二:L2 正規化(Weight Decay) 🔸 PyTorch 🔸 TensorFlow/Keras 💻 方法三:Early Stopping 🔸 PyTorch(手動實作) 🔸 TensorFlow/Keras(使用 Callback) 💻 方法四:學習率排程 🔸 PyTorch 🔸 TensorFlow/Keras ⚠️ 注意事項 📊 應用場景 🎯 結語
📚 前言 在上一篇 資料增強 中,我們學會了透過增強訓練資料來提升泛化能力。 這一篇介紹另一個關鍵主題:避免過擬合 (Overfitting) 。
資料增強是「讓模型看更多種類的圖片」,而這一篇的方法是「限制模型過度記憶訓練資料」。兩者都是提升泛化能力的手段,但切入角度不同,通常會一起使用。
本篇會先說明如何判斷過擬合,再介紹四種常用技巧:Dropout 、L2 正規化 、Early Stopping 、學習率排程 ,每一種都同時示範 PyTorch 與 TensorFlow/Keras 的寫法,並說明如何整合進前幾篇的 train.py。
🔎 過擬合是什麼? 想像一個學生只死背教科書上的例題,考試時遇到稍微不同的題目就完全不會。這就是過擬合——模型在訓練資料上表現很好,但對新資料(驗證集或真實世界)表現很差。
簡單來說:
資料增強 → 「讓模型看更多種圖片」
避免過擬合 → 「限制模型不要死背訓練資料」
這兩招通常會一起使用,才能讓模型真正學會「懂」而不是「背」。
🔎 如何判斷過擬合? 圖:過擬合的 Loss 曲線特徵 ─ Train Loss 持續下降、Val Loss 先降後升,代表模型開始「背答案」而非真正學會
觀察訓練過程中 Train Loss 與 Val Loss 的走勢,就能判斷模型目前的狀態:
現象
判斷
Train Loss 下降,Val Loss 也跟著下降
正常,模型持續改善
Train Loss 下降,Val Loss 先降後升,兩者差距越來越大
過擬合
Train Loss 與 Val Loss 都很高且停滯
欠擬合,模型容量不足或訓練次數不夠
💡 先看 Loss 曲線,再學方法 在進入四種方法之前,先用模擬資料跑出一條過擬合的 Loss 曲線,讓你有直觀的畫面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import numpy as npimport matplotlib.pyplot as pltepochs = list(range(1 , 21 )) train_loss = [1.0 * (0.78 ** i) for i in epochs] val_loss = [1.0 * (0.78 ** i) + 0.008 * i for i in epochs] plt.figure(figsize=(8 , 4 )) plt.plot(epochs, train_loss, label="Train Loss" ) plt.plot(epochs, val_loss, label="Val Loss" , linestyle="--" ) plt.xlabel("Epoch" ) plt.ylabel("Loss" ) plt.title("過擬合的 Loss 曲線特徵" ) plt.legend() plt.tight_layout() plt.savefig("output/simulate_overfitting.png" ) plt.show()
圖:模擬 20 個 epoch 的 Loss 曲線 ─ Train Loss 持續下降,Val Loss 在第 8 個 epoch 後開始上升,典型的過擬合模式
看完這個模式,接下來的四種方法都是為了讓 Val Loss 不再往上走。
💻 診斷:繪製 Loss 曲線 在 PyTorch 微調範例 與 TensorFlow/Keras 微調範例 的訓練迴圈裡,把每個 epoch 的 Loss 記錄下來,就能繪製真實的診斷曲線。
🔸 PyTorch 在 train.py 的訓練迴圈外初始化兩個清單,每個 epoch 結束後記錄 Loss,訓練完成後繪製曲線。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import matplotlib.pyplot as plttrain_losses = [] val_losses = [] for epoch in range(NUM_EPOCHS): train_losses.append(train_loss) val_losses.append(val_loss) plt.figure(figsize=(8 , 4 )) plt.plot(train_losses, label="Train Loss" ) plt.plot(val_losses, label="Val Loss" , linestyle="--" ) plt.xlabel("Epoch" ) plt.ylabel("Loss" ) plt.legend() plt.title("Training / Validation Loss" ) plt.tight_layout() plt.savefig("output/loss_curve.png" ) plt.show()
點擊展開 / 收合完整 train.py 程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 import osimport jsonimport torchimport torch.nn as nnimport torch.optim as optimfrom torchvision import datasets, models, transformsfrom torch.utils.data import DataLoaderimport matplotlib.pyplot as plt BATCH_SIZE = 32 NUM_EPOCHS = 15 LEARNING_RATE = 1e-3 NUM_CLASSES = 2 TASK_NAME = "cat_dog" DATA_DIR = f"data/{TASK_NAME} " SAVE_DIR = f"models/resnet18/{TASK_NAME} " os.makedirs(SAVE_DIR, exist_ok=True ) train_transform = transforms.Compose([ transforms.Resize((224 , 224 )), transforms.RandomHorizontalFlip(), transforms.RandomRotation(10 ), transforms.ColorJitter(brightness=0.2 , contrast=0.2 ), transforms.ToTensor(), transforms.Normalize([0.485 , 0.456 , 0.406 ], [0.229 , 0.224 , 0.225 ]) ]) val_transform = transforms.Compose([ transforms.Resize((224 , 224 )), transforms.ToTensor(), transforms.Normalize([0.485 , 0.456 , 0.406 ], [0.229 , 0.224 , 0.225 ]) ]) train_dataset = datasets.ImageFolder(f"{DATA_DIR} /train" , transform=train_transform) val_dataset = datasets.ImageFolder(f"{DATA_DIR} /val" , transform=val_transform) train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True , num_workers=0 ) val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False , num_workers=0 ) print(f"類別:{train_dataset.classes} " ) print(f"訓練集:{len(train_dataset)} 張,驗證集:{len(val_dataset)} 張" ) device = torch.device("cuda" if torch.cuda.is_available() else "cpu" ) print(f"使用裝置:{device} " ) model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) for param in model.parameters(): param.requires_grad = False model.fc = nn.Linear(model.fc.in_features, NUM_CLASSES) model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.fc.parameters(), lr=LEARNING_RATE) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5 , gamma=0.5 ) train_losses = [] val_losses = [] best_val_acc = 0.0 for epoch in range(NUM_EPOCHS): model.train() running_loss = 0.0 correct = total = 0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() _, predicted = torch.max(outputs, 1 ) total += labels.size(0 ) correct += (predicted == labels).sum().item() train_loss = running_loss / len(train_loader) train_acc = correct / total * 100 model.eval() val_correct = val_total = 0 val_running_loss = 0.0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) loss = criterion(outputs, labels) val_running_loss += loss.item() _, predicted = torch.max(outputs, 1 ) val_total += labels.size(0 ) val_correct += (predicted == labels).sum().item() val_loss = val_running_loss / len(val_loader) val_acc = val_correct / val_total * 100 train_losses.append(train_loss) val_losses.append(val_loss) scheduler.step() print(f"Epoch [{epoch+1 :02 d} /{NUM_EPOCHS} ] " f"Train Loss: {train_loss:.4 f} | Val Loss: {val_loss:.4 f} | " f"Train Acc: {train_acc:.2 f} % | Val Acc: {val_acc:.2 f} %" ) if val_acc > best_val_acc: best_val_acc = val_acc torch.save(model.state_dict(), f"{SAVE_DIR} /best_model.pth" ) config = { "model" : "resnet18" , "num_classes" : NUM_CLASSES, "classes" : train_dataset.classes } with open(f"{SAVE_DIR} /config.json" , "w" ) as f: json.dump(config, f, ensure_ascii=False , indent=2 ) print(f" → 已儲存最佳模型 (Val Acc: {best_val_acc:.2 f} %)" ) plt.figure(figsize=(10 , 5 )) plt.plot(train_losses, label="Train Loss" , marker='o' ) plt.plot(val_losses, label="Val Loss" , marker='o' , linestyle="--" ) plt.xlabel("Epoch" ) plt.ylabel("Loss" ) plt.title(f"Loss Curve - {TASK_NAME} (ResNet18)" ) plt.legend() plt.grid(True ) plt.tight_layout() plt.savefig(f"{SAVE_DIR} /loss_curve.png" ) plt.show() print(f"\n訓練完成!最佳驗證準確率:{best_val_acc:.2 f} %" ) print(f"Loss 曲線已儲存至:{SAVE_DIR} /loss_curve.png" )
圖:記錄每個 epoch 的 Train Loss 與 Val Loss 並繪製曲線,用於診斷過擬合現象
🔸 TensorFlow/Keras Keras 的 model.fit 會自動回傳 history 物件,直接存有每個 epoch 的 Loss,不需要手動記錄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import matplotlib.pyplot as plthistory = model.fit(train_ds, validation_data=val_ds, epochs=20 ) plt.figure(figsize=(8 , 4 )) plt.plot(history.history["loss" ], label="Train Loss" ) plt.plot(history.history["val_loss" ], label="Val Loss" , linestyle="--" ) plt.xlabel("Epoch" ) plt.ylabel("Loss" ) plt.legend() plt.title("Training / Validation Loss" ) plt.tight_layout() plt.savefig("output/loss_curve_keras.png" ) plt.show()
點擊展開 / 收合完整 train.py(含 Loss 曲線)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 import osimport jsonimport tensorflow as tffrom tensorflow.keras import layers, Modelfrom tensorflow.keras.applications import MobileNetV2from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateauimport matplotlib.pyplot as plt IMG_SIZE = 224 BATCH_SIZE = 32 NUM_CLASSES = 2 TASK_NAME = "cat_dog" DATA_DIR = f"data/{TASK_NAME} " SAVE_DIR = f"models/mobilenetv2/{TASK_NAME} " os.makedirs(SAVE_DIR, exist_ok=True ) SAVE_PATH = f"{SAVE_DIR} /best_model.keras" train_ds = tf.keras.utils.image_dataset_from_directory( f"{DATA_DIR} /train" , image_size=(IMG_SIZE, IMG_SIZE), batch_size=BATCH_SIZE, shuffle=True , seed=42 ) val_ds = tf.keras.utils.image_dataset_from_directory( f"{DATA_DIR} /val" , image_size=(IMG_SIZE, IMG_SIZE), batch_size=BATCH_SIZE, shuffle=False , seed=42 ) class_names = train_ds.class_names print(f"類別:{class_names} " ) config = { "model" : "mobilenetv2" , "num_classes" : NUM_CLASSES, "classes" : class_names } with open(f"{SAVE_DIR} /config.json" , "w" ) as f: json.dump(config, f, ensure_ascii=False , indent=2 ) AUTOTUNE = tf.data.AUTOTUNE train_ds = train_ds.prefetch(buffer_size=AUTOTUNE) val_ds = val_ds.prefetch(buffer_size=AUTOTUNE) data_augmentation = tf.keras.Sequential([ layers.RandomFlip("horizontal" ), layers.RandomRotation(0.1 ), layers.RandomZoom(0.1 ), layers.RandomBrightness(0.2 ), ], name="data_augmentation" ) base_model = MobileNetV2( input_shape=(IMG_SIZE, IMG_SIZE, 3 ), include_top=False , weights="imagenet" ) base_model.trainable = False inputs = tf.keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3 )) x = data_augmentation(inputs) x = tf.keras.applications.mobilenet_v2.preprocess_input(x) x = base_model(x, training=False ) x = layers.GlobalAveragePooling2D()(x) x = layers.Dropout(0.3 )(x) outputs = layers.Dense(NUM_CLASSES, activation="softmax" )(x) model = Model(inputs, outputs) model.summary() model.compile( optimizer=tf.keras.optimizers.Adam(1e-3 ), loss="sparse_categorical_crossentropy" , metrics=["accuracy" ] ) callbacks_phase1 = [ ModelCheckpoint(SAVE_PATH, monitor="val_accuracy" , save_best_only=True , verbose=1 ), EarlyStopping(monitor="val_accuracy" , patience=5 , restore_best_weights=True , verbose=1 ), ] print("\n── 第一階段訓練(Feature Extraction)──" ) history1 = model.fit( train_ds, validation_data=val_ds, epochs=10 , callbacks=callbacks_phase1 ) base_model.trainable = True fine_tune_at = len(base_model.layers) - 30 for layer in base_model.layers[:fine_tune_at]: layer.trainable = False model.compile( optimizer=tf.keras.optimizers.Adam(1e-5 ), loss="sparse_categorical_crossentropy" , metrics=["accuracy" ] ) callbacks_phase2 = [ ModelCheckpoint(SAVE_PATH, monitor="val_accuracy" , save_best_only=True , verbose=1 ), EarlyStopping(monitor="val_accuracy" , patience=5 , restore_best_weights=True , verbose=1 ), ReduceLROnPlateau(monitor="val_loss" , factor=0.5 , patience=3 , verbose=1 ), ] print("\n── 第二階段訓練(Fine-Tuning)──" ) history2 = model.fit( train_ds, validation_data=val_ds, epochs=10 , callbacks=callbacks_phase2 ) print("\n繪製訓練 Loss 曲線..." ) plt.figure(figsize=(10 , 5 )) train_loss = history1.history["loss" ] + history2.history["loss" ] val_loss = history1.history["val_loss" ] + history2.history["val_loss" ] plt.plot(train_loss, label="Train Loss" , marker='o' ) plt.plot(val_loss, label="Val Loss" , marker='o' , linestyle="--" ) plt.xlabel("Epoch" ) plt.ylabel("Loss" ) plt.title(f"Loss Curve - {TASK_NAME} (MobileNetV2)" ) plt.legend() plt.grid(True ) plt.tight_layout() plt.savefig(f"{SAVE_DIR} /loss_curve.png" ) plt.show() print(f"訓練完成!模型已儲存至 {SAVE_PATH} " ) print(f"Loss 曲線已儲存至:{SAVE_DIR} /loss_curve.png" )
圖:使用 Keras history 物件繪製 Train Loss 與 Val Loss 曲線,直觀呈現訓練與驗證的差距
💻 方法一:Dropout 圖:Fig 1. Dropout (取自 Tikz/Dropout )
Dropout 在訓練時隨機將一部分神經元的輸出設為 0,迫使模型不能過度依賴特定神經元,從而提升泛化能力。
白話原理 :訓練時隨機關掉一些神經元,強迫模型學得更均衡,不容易死背訓練資料。
🔸 PyTorch 在 PyTorch 微調範例 的 train.py 中,分類頭原本是一個 nn.Linear。 在它之前插入 nn.Dropout 即可啟用 Dropout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import torch.nn as nnfrom torchvision import modelsmodel = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) for param in model.parameters(): param.requires_grad = False model.fc = nn.Sequential( nn.Dropout(p=0.5 ), nn.Linear(model.fc.in_features, NUM_CLASSES) )
🔸 TensorFlow/Keras 在 TensorFlow/Keras 微調範例 的模型架構中,在全連接層之間插入 layers.Dropout:
1 2 3 4 5 6 7 8 from tensorflow.keras import layersx = layers.GlobalAveragePooling2D()(base_model.output) x = layers.Dropout(0.5 )(x) x = layers.Dense(256 , activation="relu" )(x) x = layers.Dropout(0.3 )(x) outputs = layers.Dense(NUM_CLASSES, activation="softmax" )(x)
💡 Dropout 只在訓練時啟用,推論時自動關閉。PyTorch 需呼叫 model.eval() 才會正確停用;Keras 則透過 training 參數自動控制。
💻 方法二:L2 正規化(Weight Decay) L2 正規化對模型的權重大小施加懲罰,避免某些權重過大,讓模型的決策更分散、不過度依賴少數特徵。
白話原理 :對太大的權重進行懲罰,讓模型的權重不會過度集中。
🔸 PyTorch 在 PyTorch 微調範例 的 train.py 中,optimizer 只需加入 weight_decay 參數即可啟用:
1 2 3 4 5 6 7 import torch.optim as optimoptimizer = optim.Adam(model.fc.parameters(), lr=LEARNING_RATE, weight_decay=1e-4 )
🔸 TensorFlow/Keras 在 TensorFlow/Keras 微調範例 的模型架構中,對 Dense 層加入 kernel_regularizer:
1 2 3 4 5 6 7 8 from tensorflow.keras import layers, regularizersx = layers.Dense( 256 , activation="relu" , kernel_regularizer=regularizers.l2(1e-4 ) )(x)
💻 方法三:Early Stopping Early Stopping 監控驗證集 Loss,當連續 patience 個 epoch 都沒有改善時,自動停止訓練並恢復最佳模型,避免在過擬合的方向繼續訓練。
白話原理 :如果驗證集好幾個 epoch 都沒有進步,就自動停止訓練,避免繼續過擬合。
🔸 PyTorch(手動實作) 在 PyTorch 微調範例 的 train.py 訓練迴圈中,加入以下邏輯取代固定 epoch 數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 best_val_loss = float("inf" ) patience = 5 no_improve = 0 for epoch in range(NUM_EPOCHS): if val_loss < best_val_loss: best_val_loss = val_loss torch.save(model.state_dict(), f"{SAVE_DIR} /best_model.pth" ) no_improve = 0 else : no_improve += 1 if no_improve >= patience: print(f"Early stopping at epoch {epoch + 1 } " ) break model.load_state_dict(torch.load(f"{SAVE_DIR} /best_model.pth" ))
🔸 TensorFlow/Keras(使用 Callback) Keras 的 EarlyStopping callback 自動處理所有邏輯,restore_best_weights=True 等同於 PyTorch 版本的「訓練結束後載入最佳模型」:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpointcallbacks = [ EarlyStopping( monitor="val_loss" , patience=5 , restore_best_weights=True , verbose=1 ), ModelCheckpoint( f"{SAVE_DIR} /best_model.keras" , monitor="val_accuracy" , save_best_only=True , verbose=1 ), ] model.fit(train_ds, validation_data=val_ds, epochs=50 , callbacks=callbacks)
💻 方法四:學習率排程 學習率從大到小逐步衰減,讓模型在訓練後期更細緻地調整,避免 Loss 在最小值附近反覆震盪。
🔸 PyTorch 在 PyTorch 微調範例 的 train.py 中已使用 StepLR。以下展示兩種常用排程器的用法,擇一加入 train.py 即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 import torch.optim as optimfrom torch.optim import lr_scheduleroptimizer = optim.Adam(model.fc.parameters(), lr=LEARNING_RATE) scheduler = lr_scheduler.StepLR(optimizer, step_size=5 , gamma=0.5 ) for epoch in range(NUM_EPOCHS): scheduler.step()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import torch.optim as optimfrom torch.optim import lr_scheduleroptimizer = optim.Adam(model.fc.parameters(), lr=LEARNING_RATE) scheduler = lr_scheduler.ReduceLROnPlateau( optimizer, mode="min" , factor=0.5 , patience=3 , verbose=True ) for epoch in range(NUM_EPOCHS): scheduler.step(val_loss)
🔸 TensorFlow/Keras 在 TensorFlow/Keras 微調範例 的 callbacks 清單中加入 ReduceLROnPlateau:
1 2 3 4 5 6 7 8 9 10 11 12 from tensorflow.keras.callbacks import ReduceLROnPlateaucallbacks = [ ReduceLROnPlateau( monitor="val_loss" , factor=0.5 , patience=3 , min_lr=1e-7 , verbose=1 ) ]
⚠️ 注意事項
先增加資料,再加正規化 :過擬合最根本的原因是資料不足,正規化只是輔助,無法完全取代資料量。上一篇的資料增強是第一道防線,這一篇的方法是第二道。
Dropout 比率不要太高 :0.3 ~ 0.5 是常用範圍,過高(如 0.8)會讓模型難以學習,Train Loss 也降不下來。
Early Stopping 的 patience 要夠 :patience 太小可能在模型還沒充分訓練時就停止;太大則失去提早停止的意義。通常設 5 ~ 10。
StepLR 與 ReduceLROnPlateau 的選擇 :資料集穩定、訓練行為可預期時用 StepLR;訓練曲線不穩定、需要自動調整時用 ReduceLROnPlateau。
scheduler.step() 的位置 :StepLR.step() 必須在每個 epoch 結束後呼叫;ReduceLROnPlateau.step(val_loss) 必須傳入驗證 Loss 才能判斷是否縮小。
📊 應用場景
小資料集訓練 :資料量少時,過擬合幾乎必然發生,需積極搭配資料增強、Dropout 與 Weight Decay 一起使用。
訓練前期穩定、後期微調 :ReduceLROnPlateau 讓模型在收斂後期自動降低學習率,不需手動介入調整。
長時間訓練監控 :搭配 Matplotlib 繪製 Loss 曲線,提早發現過擬合趨勢,適時介入加入正規化技巧。
模型上線前的品質把關 :Early Stopping + ModelCheckpoint 確保儲存的一定是驗證集最佳模型,而非最後一個 epoch 的模型。
競賽與生產模型 :通常四種方法組合使用(資料增強 + Dropout + Weight Decay + Early Stopping)以得到最穩定的泛化效果。
🎯 結語 過擬合是深度學習訓練中最常遇到的問題之一,也是工程師最需要具備診斷與處理能力的環節。 掌握 Dropout、L2 正規化、Early Stopping 與學習率排程這四種核心技巧,搭配上一篇的資料增強,能讓模型的泛化能力大幅提升。 下一步是 GPU 加速與效能優化 ,讓訓練速度大幅提升。
📖 如在學習過程中遇到疑問,或是想了解更多相關主題,建議回顧一下 Python | OpenCV 系列導讀 ,掌握完整的章節目錄,方便快速找到你需要的內容。
註:以上參考了PyTorch 官方文件 — torch.optim TensorFlow 官方文件 — Callbacks Deep Learning Book — Regularization