📢 公告:OpenCV 系列文章目前正在重構整理中(進度約 65%),部分文章已暫時下架,後續會陸續補上,完成時間待定。感謝耐心等候!

Like Share Discussion Bookmark Smile

J.J. Huang   2026-04-03   Python OpenCV 07.物件偵測與辨識篇   瀏覽次數:次   DMCA.com Protection Status

Python | OpenCV 模型評估與測試

📚 前言

在上一篇 模型使用與推論 的章節導覽中,我們了解了推論流程的整體架構。
這一篇進入第一個細節:模型評估與測試 (Model Evaluation & Testing)

訓練結束後,你最常看到的應該是程式印出的 Val Accuracy(驗證正確率)和 Val Loss(驗證損失)。

簡單來說:

  • Val Loss:用來判斷訓練過程是否過擬合(前面文章已經教過)
  • Val Accuracy:代表模型在驗證集上預測正確的比例,例如 90% 表示每 100 張圖片,大約有 90 張猜對

但只看 Val Accuracy 是遠遠不夠的!

因為:

  • 當類別數量不平衡時,Accuracy 很容易被大類別拉高,造成誤判
  • 它無法告訴你「模型在哪個類別上特別容易出錯」
  • 也不能幫助你知道該如何改進模型

這一篇就是要教你更完整、更實用的模型評估方法,讓你清楚知道模型真正的優缺點,以及接下來該怎麼改進。

🔎 常見評估指標是什麼意思?


圖:貓 vs 狗 二分類指標白話版 ─ Accuracy、Precision、Recall、F1-Score 簡單說明

用實際例子幫助理解

假設測試集中有 100 張「狗」的圖片:

  • Accuracy = 90%:100 張圖片裡,模型總共猜對了 90 張。
  • Precision = 95%:模型預測是狗的 80 張圖片中,有 76 張真的是狗(只有 4 張貓被誤認成狗)。
  • Recall = 80%:實際是狗的 100 張圖片中,模型成功找出 80 張(還有 20 張狗被漏掉了)。
  • F1-Score:同時考慮 Precision 和 Recall 的平衡分數,當類別數量不平衡時特別有用。

💡 實際應用小提醒

  • 醫療診斷(如判斷有沒有病)通常最看重 Recall(寧可多報一點,也不要漏掉真正有病的)。
  • 垃圾郵件過濾通常最看重 Precision(寧可讓一些垃圾郵件漏掉,也不要把正常郵件誤判成垃圾)。

🛠️ 評估前必須準備的事

在開始評估之前,請先確認以下事項:

  1. 建立測試集資料夾
    請在你的資料目錄下建立一個獨立的 test 資料夾(與 trainval 同層級):
    1
    2
    3
    4
    data/cat_dog/
    ├── train/
    ├── val/
    └── test/ ← 新增這個資料夾

    把一些從未用來訓練或驗證的圖片放進 test 資料夾,也可以先從 val 複製一份當作測試集使用。

  2. 安裝評估需要的套件
    在開始評估之前,請先安裝 scikit-learn(用來產生分類報告與混淆矩陣):
    1
    pip install scikit-learn

🗺️ 模型評估的正確流程


圖:模型評估的正確流程 ─ 從測試集推論到找出弱勢類別的完整步驟

💻 PyTorch — 完整評估流程

此處使用的是PyTorch 微調範例 - 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
# evaluate_pytorch.py
import torch
import torch.nn as nn
from torchvision import models, transforms
from torch.utils.data import DataLoader
from torchvision import datasets
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
import cv2

# ==================== 設定區 ====================
MODEL_PATH = "models/resnet18/cat_dog/best_model.pth" # ← 改成你的模型路徑
DATA_DIR = "data/cat_dog"
BATCH_SIZE = 32
# ===============================================

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 載入模型
model = models.resnet18(weights=None)
model.fc = nn.Linear(model.fc.in_features, 2) # 改成你的 NUM_CLASSES
model.load_state_dict(torch.load(MODEL_PATH, map_location=device))
model = model.to(device)
model.eval()

print(f"已載入模型:{MODEL_PATH}")

# 準備測試資料
test_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

test_dataset = datasets.ImageFolder(f"{DATA_DIR}/test", transform=test_transform)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=0)

class_names = test_dataset.classes

# ==================== 評估 ====================
all_preds = []
all_labels = []

print("正在對測試集進行推論...")

with torch.no_grad():
for inputs, labels in test_loader:
inputs = inputs.to(device)
labels = labels.to(device)
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
all_preds.extend(preds.cpu().numpy())
all_labels.extend(labels.cpu().numpy())

# 1. 分類報告
print("\n── Classification Report ──")
print(classification_report(all_labels, all_preds, target_names=class_names))

# 2. 混淆矩陣(只儲存圖片,不彈出視窗)
cm = confusion_matrix(all_labels, all_preds)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_names)
disp.plot(cmap="Blues")
plt.title("Confusion Matrix")
plt.savefig("output/confusion_matrix.png", dpi=200, bbox_inches='tight')
plt.close()

print(f"混淆矩陣已儲存為:output/confusion_matrix.png")

# 3. 找出並儲存預測錯誤的圖片
print("\n正在找出預測錯誤的樣本...")

error_count = 0
max_show = 10

for i, (pred, label) in enumerate(zip(all_preds, all_labels)):
if pred != label and error_count < max_show:
img_path = test_dataset.samples[i][0]
img = cv2.imread(img_path)
img = cv2.resize(img, (224, 224))

text = f"真實: {class_names[label]} → 預測: {class_names[pred]}"
cv2.putText(img, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

cv2.imwrite(f"output/error_sample_{error_count:02d}.jpg", img)
print(f"已儲存錯誤樣本 {error_count}: {text}")
error_count += 1

if error_count == 0:
print("恭喜!模型在測試集中沒有預測錯誤。")
else:
print(f"已儲存 {error_count} 張預測錯誤的樣本圖片")

print("\n評估完成!")


圖:使用 PyTorch 對測試集推論,並以 classification_report 輸出各類別 Precision、Recall、F1 及繪製混淆矩陣

輸出範例:

1
2
3
4
5
6
7
8
9
── Classification Report ──
precision recall f1-score support

cat 0.96 0.96 0.96 48
dog 0.96 0.96 0.96 50

accuracy 0.96 98
macro avg 0.96 0.96 0.96 98
weighted avg 0.96 0.96 0.96 98

報告說明

  • 整體準確率 (Accuracy): 96% —— 非常好的成績!
  • 貓的類別:Precision 0.96、Recall 0.96、F1 0.96
  • 狗的類別:Precision 0.96、Recall 0.96、F1 0.96
  • macro avg 和 weighted avg 都是 0.96,表示兩個類別表現非常均衡。
    1
    2
    3
    4
    5
    support 是什麼?
    support = 該類別在測試集中的實際樣本數量

    cat 的 support = 48 → 表示測試集中實際有 48 張貓的圖片
    dog 的 support = 50 → 表示測試集中實際有 50 張狗的圖片

⚠️ 注意事項

  • 使用測試集而非驗證集評估:驗證集在訓練過程中用於調整超參數,不能代表真實的模型表現,最終評估應使用從未接觸過的測試集。
  • 類別不均衡時看 F1-Score:若各類別的樣本數差異很大,Accuracy 會被大類別主導,此時 F1-Score 或 macro avg 更能反映真實表現。
  • 混淆矩陣要標準化:若各類別樣本數不同,建議使用 normalize="true" 將混淆矩陣轉為比例,更容易比較。

🎯 結語

完整的模型評估能讓你清楚知道模型的強項與弱項,是改進模型的起點而非終點。
發現哪個類別的 Recall 特別低?回去補充那個類別的訓練資料或調整增強策略。
下一步是 模型保存與載入,學習如何妥善保存訓練成果。

📖 如在學習過程中遇到疑問,或是想了解更多相關主題,建議回顧一下 Python | OpenCV 系列導讀,掌握完整的章節目錄,方便快速找到你需要的內容。

註:以上參考了
scikit-learn — classification_report
scikit-learn — ConfusionMatrixDisplay