卷積神經網路(Convolutional Neural Network, CNN)是專為影像分類設計的深度學習架構。它模仿人類視覺系統:先辨識低階特徵(邊緣、色塊),再組合成中階特徵(眼睛、耳朵),最後輸出類別。CNN 透過兩種特殊隱藏層——卷積層搜尋局部特徵、池化層壓縮維度——加上端到端的學習,在 CIFAR100 等影像分類任務中展現驚人成效。 📚 James, Witten, Hastie, Tibshirani (2023) An Introduction to Statistical Learning with Python, §10.3, pp. 407–412
本節的核心案例是 CIFAR100 影像分類:50,000 張訓練影像 + 10,000 張測試影像,每張 32×32 彩色(RGB 三通道),目標是分成 100 個類別。與前一節的 MNIST 手寫數字不同,CIFAR100 的影像包含複雜場景(動植物、交通工具、日常物品),傳統的全連接神經網路效果有限——這就是 CNN 登場的舞台。
CNN 的核心洞見非常直觀:一隻老虎不管出現在圖片的左上角或右下角,它仍然是老虎。CNN 的卷積濾波器(convolution filter)會在整張圖片上「滑動」,尋找相同的局部特徵——這是傳統神經網路做不到的,因為傳統網路把每個像素當成獨立變數,失去了空間關係。
把卷積濾波器想像成一個小偵探用的放大鏡(通常是 3×3 或 5×5 的矩陣),在圖片上逐格滑動。每到一個位置,放大鏡裡的權重與圖片上對應的像素值相乘再加總,得到一個數字。這個數字越大,代表該位置越符合濾波器要找的特徵。
數學上,對於 2×2 濾波器 F 與 4×3 影像,卷積運算為:
課本以一個 2×2 濾波器對 4×3 影像的示範展示:濾波器掃過影像的每個 2×2 子區域,產生一個較小的「卷積後影像」。
課本 Figure 10.7 用一張老虎的照片展示了兩個濾波器——一個偵測垂直條紋,一個偵測水平條紋。垂直濾波器在老虎的垂直斑紋處輸出高值,水平濾波器在水平邊緣處輸出高值。一個濾波器 = 一種特徵偵測器。
對於 CIFAR100 的 32×32 彩色影像,每個卷積濾波器也有三個通道(RGB),分別對三個顏色通道做卷積後加總。第一個卷積層使用 K 個濾波器,就會產生 K 個輸出特徵圖(feature map),每一張特徵圖都是這 K 個「偵探」對原始影像的報告。
池化層的作用是把一張大影像「濃縮」成小影像。最常見的是最大池化(Max Pooling):把影像切成不重疊的 2×2 區塊,每個區塊只保留最大值。
課本例:左上是 {1,2,3,0} → 3;右上是 {5,3,1,2} → 5;依此類推。
最大池化的雙重好處:壓縮資料量(2×2 → 1,減少 75%)和位置不變性(只要某個 2×2 區域中有一個高值,縮小後就能被偵測到,不因微小位移而消失)。
典型的深度 CNN 架構(課本 Figure 10.8,用於 CIFAR100):
關鍵設計原則:(1) 每次池化後通道數翻倍(補償空間維度損失),(2) 卷積+池化重複數次直到特徵圖只剩幾像素,(3) 最後接全連接層做分類。CIFAR100 目前最佳測試準確率約 75%。
資料擴增(Data Augmentation)是影像建模的「作弊合法化」:把每張訓練影像複製成多個版本,每個版本經過隨機變形——縮放、平移、旋轉、翻轉——但類別標籤不變。課本 Figure 10.9 展示了老虎照片的各種變體:水平翻轉後的老虎仍然是老虎。
資料擴增本質上是一種正則化:在每張原始影像周圍建立一個「雲」──所有自然變形都被視為同一個類別。SGD 的 mini-batch 訓練天生適合搭配擴增,因為可以在每次取 batch 時即時變形影像,不需要儲存擴增後的資料。
課本展示了一個產業級的 CNN:ResNet50,在 ImageNet(百萬張影像、上千類別)上預訓練。對新影像,ResNet50 的前幾十層已經學會了通用的邊緣、形狀、紋理特徵——這些知識可以「凍結」並轉移到新任務上,只需訓練最後幾層。
課本 Figure 10.10 用六張照片展示了 ResNet50 的表現:成功辨識老鷹(Cooper's hawk)但放大後誤判為噴泉(fountain)。這種遷移學習(transfer learning)讓小資料集也能享有大模型的視覺智慧——是當代電腦視覺最實用的技術之一。
# 10.3 卷積神經網路 — 手動實作卷積與最大池化(自給自足版本)
try:
from google.colab import drive
drive.mount('/content/drive')
DATA_PATH = '/content/drive/MyDrive/ISLP_data/'
except ImportError:
DATA_PATH = '/tmp/'
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
# 載入 digits 作為小型影像資料(8×8 灰階,轉成 2D)
digits = load_digits()
img = digits.images[0] # 第一張圖(數字 0)
print(f"原始影像 shape: {img.shape}")
print(f"像素值範圍: {img.min():.0f} – {img.max():.0f}")
# 展示原始影像
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
axes[0].imshow(img, cmap='gray')
axes[0].set_title('原始影像 (8×8)', fontsize=11)
axes[0].axis('off')
# === 卷積運算:用一個邊緣偵測濾波器 ===
# Sobel 水平邊緣濾波器(3×3):偵測水平方向的亮度變化
sobel_h = np.array([[-1, -2, -1],
[ 0, 0, 0],
[ 1, 2, 1]], dtype=float)
# 手動實作卷積(無填充,步長=1)
H, W = img.shape
kH, kW = sobel_h.shape
out_H, out_W = H - kH + 1, W - kW + 1
conv_out = np.zeros((out_H, out_W))
for i in range(out_H):
for j in range(out_W):
patch = img[i:i+kH, j:j+kW]
conv_out[i, j] = np.sum(patch * sobel_h)
axes[1].imshow(conv_out, cmap='gray')
axes[1].set_title(f'卷積後 ({out_H}×{out_W}):水平邊緣', fontsize=11)
axes[1].axis('off')
# === 最大池化:2×2 區塊取最大值 ===
pool_H, pool_W = conv_out.shape[0] // 2, conv_out.shape[1] // 2
pool_out = np.zeros((pool_H, pool_W))
for i in range(pool_H):
for j in range(pool_W):
patch = conv_out[i*2:(i+1)*2, j*2:(j+1)*2]
pool_out[i, j] = np.max(patch)
axes[2].imshow(pool_out, cmap='gray')
axes[2].set_title(f'池化後 ({pool_H}×{pool_W}):壓縮 75%', fontsize=11)
axes[2].axis('off')
plt.suptitle('CNN 基礎運算:卷積 → 池化(digits 數字 0)', fontsize=13, y=1.02)
plt.tight_layout()
plt.show()
print(f"\n維度變化: {img.shape} → {conv_out.shape} → {pool_out.shape}")
print(f"總像素: {img.size} → {conv_out.size} → {pool_out.size} (壓縮 {(1 - pool_out.size/img.size)*100:.0f}%)")
# 卷積濾波器多樣性展示:不同濾波器捕捉不同特徵(自給自足版本)
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
# 載入資料
digits = load_digits()
img = digits.images[5] # 數字 5
# 定義三個濾波器
filters = {
'水平邊緣': np.array([[-1,-2,-1],[ 0, 0, 0],[ 1, 2, 1]], dtype=float),
'垂直邊緣': np.array([[-1, 0, 1],[-2, 0, 2],[-1, 0, 1]], dtype=float),
'銳化': np.array([[ 0,-1, 0],[-1, 5,-1],[ 0,-1, 0]], dtype=float),
}
H, W = img.shape
kH, kW = 3, 3
out_H, out_W = H - kH + 1, W - kW + 1
fig, axes = plt.subplots(1, 4, figsize=(14, 3.5))
axes[0].imshow(img, cmap='gray')
axes[0].set_title('原始影像 (數字 5)', fontsize=10)
axes[0].axis('off')
for idx, (name, f) in enumerate(filters.items()):
conv = np.zeros((out_H, out_W))
for i in range(out_H):
for j in range(out_W):
conv[i, j] = np.sum(img[i:i+3, j:j+3] * f)
axes[idx+1].imshow(conv, cmap='gray')
axes[idx+1].set_title(f'{name} 濾波器', fontsize=10)
axes[idx+1].axis('off')
plt.suptitle('不同卷積濾波器捕捉不同特徵', fontsize=13, y=1.02)
plt.tight_layout()
plt.show()
print("\nCNN 的核心概念:每個濾波器學到一種特定的特徵模式。")
print("水平濾波器:強調橫向筆畫(數字 5 的頂部橫線)")
print("垂直濾波器:強調縱向邊緣(數字 5 的垂直結構)")
print("銳化濾波器:強化所有邊緣(讓輪廓更清晰)")
# 資料擴展示範:對同一張影像做隨機變形(自給自足版本)
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from scipy.ndimage import rotate, shift, zoom
# 載入數字 3 的影像
digits = load_digits()
img = digits.images[3]
augmentations = {
'原始': lambda x: x,
'旋轉 15°': lambda x: rotate(x, 15, reshape=False, mode='nearest'),
'水平翻轉': lambda x: np.fliplr(x),
'平移+縮放': lambda x: zoom(shift(x, (1, 0.5), mode='nearest'), 1.1, mode='nearest'),
}
fig, axes = plt.subplots(1, 4, figsize=(12, 3))
for idx, (name, aug_fn) in enumerate(augmentations.items()):
aug_img = aug_fn(img.astype(float))
axes[idx].imshow(aug_img, cmap='gray')
axes[idx].set_title(name, fontsize=10)
axes[idx].axis('off')
plt.suptitle('資料擴增(Data Augmentation):同一張影像的多種變體', fontsize=13, y=1.05)
plt.tight_layout()
plt.show()
print("\n資料擴增 = 正則化的一種形式。")
print("每張原始影像周圍產生「雲」——不增加標註成本,卻大幅降低過擬合。")
Google 的糖尿病視網膜病變篩檢系統使用深度 CNN,在數十萬張眼底照片上訓練。卷積層先抓取微血管瘤(低階特徵),中層辨識出血點,高層判斷病變等級。在印度臨床試驗中,CNN 的靈敏度超過 87%,媲美眼科專科醫師。預訓練模型(如 ResNet)加上遷移學習讓小醫院也能部署。
Tesla 和 Waymo 的自駕系統依賴 CNN 在毫秒級辨識行人、車輛、交通號誌。卷積層偵測邊緣和形狀,池化層壓縮運算量以滿足即時性要求(≥30 FPS)。資料擴增尤其重要——同一輛車在晴天、雨天、逆光的影像都要能正確分類。CIFAR100 的 32×32 分類是自駕車影像辨識的「Hello World」。
Google Translate 的鏡頭翻譯功能:CNN 先從手機畫面中定位文字區域(卷積層偵測邊緣),OCR 辨識文字,再翻譯並覆蓋回畫面。因為手機算力有限,通常使用 MobileNet 等輕量 CNN,用權重凍結技術繼承大模型的特徵萃取能力。這正是課本 §10.3.5 的預訓練+微調策略的實戰應用。
CNN 的「卷積濾波器 = 可重複使用的特徵偵測器」對應 Agent 系統的 skill 模組化設計:每個 skill 就像一個濾波器,針對特定任務模式(如 PDF 擷取、GA4 查詢、程式審查),在主 Agent 的協調下「滑動」到不同輸入上。預訓練模型(resnet50)對應 skill 中的參考實作模板——不從零開始,繼承既有知識。資料擴增對應子 Agent 的多角度覆核:讓不同模型(Gemini、DeepSeek、Qwen)對同一輸入給出獨立判斷,再用共識機制篩選——這就是 AI 版的 max pooling。
| 架構 | 隱藏層 | 參數共享? | 空間感知? | 適用領域 | 參數效率 |
|---|---|---|---|---|---|
| 單層 NN (§10.1) | 1 | 否 | 否 | 迴歸、二元分類 | 高(簡單) |
| 多層 NN (§10.2) | ≥2 | 否 | 否 | 多元分類、表格資料 | 中(需正則化) |
| CNN (§10.3) | ≥5(含卷積+池化) | 是 | 是 | 影像、影片分類 | 極高(權重共享) |
| RNN (§10.5) | ≥1(遞歸) | 是(時間軸) | 否(時序感知) | 文字、序列資料 | 中 |
📚 對照對象選自 ISLP §10.1, §10.2, §10.5。
「從 CIFAR100 的 32×32×3 像素到 100 個類別標籤,CNN 做了傳統統計學無法想像的事:讓電腦自己學會什麼是『有用的特徵』。不是人類設計特徵(SIFT、HOG),而是梯度下降在數百萬次迭代中,把 3×3 的小方塊打磨成邊緣、斑紋、形狀的偵測器。這不是魔法——這是權重共享、空間不變性、和層層抽象的數學勝利。」