# Convolutional Neural Network
卷積神經網路(CNN)是一種深度學習模型,專門為處理網格狀數據(如圖片)而設計。它的結構靈感來自於生物的視覺皮層,即大腦處理視覺資訊的方式。簡單來說,CNN 是一種特別擅長「看懂」圖片的人工智慧。
## 核心原理
CNN 主要由三種特殊的層結構組成:卷積層 (Convolutional Layer)、激活函數 (Activation Function) 和 池化層 (Pooling Layer)。它們通常會搭配一個全連接層 (Fully Connected Layer) 來完成最終的分類任務。
#### 卷積層 (Convolutional Layer):
這是 CNN 的靈魂。卷積層的工作是偵測並提取圖片中的局部特徵,它使用一個稱為「濾鏡 (Filter)」或「核心 (Kernel)」的小窗口(可以想像成一個帶有放大鏡的特徵探測器)在原始圖片上滑動掃描。
卷積在數學上是一種衡量兩個函數重疊程度的運算,輸入是一個有 3 個通道 (RGB) 的 2D 圖像 `I`,濾鏡 (Kernel) 是 `K`。輸出的特徵圖 (Feature Map) 為 `S`。
輸出特徵圖上位於 `(i, j)` 位置的像素值,其計算公式如下:
$$
S(i, j) = \left( \sum_{c=1}^{d_{in}} \sum_{m}\sum_{n} I(s \cdot i + m, s \cdot j + n, c) \cdot K(m, n, c) \right) + b
$$
這個公式表示,要計算輸出特徵圖上的一個點,需要將濾鏡的每個通道與輸入的對應通道分別進行 2D 卷積,然後將所有通道的卷積結果相加,最後再加上一個統一的偏置 b。
一個卷積層通常包含多個濾鏡,每個濾鏡都會產生一個單獨的輸出通道。如果一個卷積層有 $d_{out}$ 個濾鏡,那麼它最終會生成一個維度為 $h_{out}$ $\times$ $w_{out}$ $\times$ $d_{out}$ 的輸出。
#### 激活函數 (Activation Function):
卷積運算是線性的,但真實世界的模式(如貓的形狀)是高度非線性的。因此,我們需要在卷積層後加入一個激活函數。
最常用的激活函數是 **ReLU (Rectified Linear Unit)** :
$$
ReLU(x)=max(0,x)
$$
意思是,如果經過卷積計算後的值是負數,就把它變成 0;如果是正數,則保持不變。這簡單的操作卻能有效地為網路增加非線性,使其能夠學習更複雜的模式。
#### 池化層 (Pooling Layer):
池化層的主要目的是對特徵圖進行降採樣(Downsampling),以減少數據的空間維度、降低計算量,並提升模型對特徵微小位移的穩健性。池化操作是獨立應用於輸入特徵圖的每一個通道(channel)的。
常見的池化類型有 :
- 最大池化 (Max Pooling)
在每個滑動窗口的位置,只保留那個窗口裡最強的信號(最大值)。
$$S(i, j) = \max_{(p, q) \in R_{i,j}} A(p, q)$$
- 平均池化 (Average Pooling)
在每個滑動窗口的位置,用該窗口內所有信號的平均強度來代表這個區域。
$$S(i, j) = \frac{1}{f^2} \sum_{(p, q) \in R_{i,j}} A(p, q)$$
#### 整體架構
一個典型的 CNN 架構就像一個特徵提取和分類的流水線:
`輸入圖片 -> [卷積層 -> 激活層 -> 池化層] -> [卷積層 -> 激活層 -> 池化層] -> ... -> 全連接層 -> 輸出`
## Training
使用 food-11 資料集實作辨識食物圖片的訓練任務。
#### 原始效果


從上圖分析,訓練使 training loss 下降,但 validation loss 先降後升,代表模型出現 overfitting 的情況,可以嘗試以下調整 :
1. Early Stopping : 在訓練過程中,持續監控驗證損失 (Validation Loss)。當驗證損失連續好幾個 epoch 都不再下降,甚至開始上升時,就立刻停止訓練。
2. Data Augmentation : 對訓練圖片進行隨機的變換,例如隨機旋轉、翻轉、裁剪、調整顏色、亮度等,藉此「無中生有」地創造出更多樣的訓練資料。
3. Regularization : 在損失函數 (Loss Function) 中加入一個「懲罰項」,用來懲罰過於複雜的模型(模型的權重過大)。(已設定 weight_decay,可能需要調整參數)
4. Drop Out : 在模型訓練時,於每一層隨機地「丟棄」(暫時忽略)一部分的神經元。
5. Simplify Model : 模型太複雜(層數太深、神經元太多),它的「記憶能力」就太強,更容易死背。可以嘗試減少網路的層數或每一層的神經元數量。
#### 嘗試 Data Augmentation
- `transforms.RandomResizedCrop(size=128)` : 隨機調整圖片尺寸並進行裁剪
從原圖中選取一個隨機區域,強制縮放 (resize) 成指定的 128x128 大小,這能讓模型對物體在圖片中的位置和大小不那麼敏感。
- `transforms.RandomHorizontalFlip()` : 隨機水平翻轉
食物照片左右翻轉通常不影響其類別。


scale=(0.5, 1.0)


測試上述基本的 augmentation 後,overfitting 狀況明顯改善,希望能使模型更好,嘗試更多 augmentation 方法 :
- `transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1)` : 色彩抖動
在一定範圍內隨機調整圖片的亮度 (brightness)、對比度 (contrast)、飽和度 (saturation) 和色調 (hue)。同一道菜在黃光、白光、或窗邊自然光下看起來顏色會很不一樣。這個增強能讓模型學會忽略光線色溫的影響。
- `transforms.RandomRotation(15)` : 隨機旋轉
(-15, 15) 度之間隨機旋轉圖片。拍攝食物時,相機可能會有輕微的傾斜角度。這個增強可以提高模型對旋轉的容忍度。
- `transforms.RandomAffine(degrees=15, translate=(0.1, 0.1), scale=(0.9, 1.1), shear=10)` : 隨機仿射變換
更強大的幾何變換工具,可以同時進行旋轉、平移、縮放和錯切 (shear)。可以模擬更複雜的拍攝視角變化。
- `transforms.RandomGrayscale(p=0.1)` : 隨機灰階
以 10% 的機率將圖片轉換為灰階。強迫模型不要只依賴顏色特徵來辨識食物,而是更多地關注紋理和形狀。
單一測試結果 :
- 色彩抖動


- 隨機旋轉


- 隨機仿射變換
跟上面差不多
- 色彩抖動 + 隨機旋轉


相較之下有比較好,但不確定模型是否收斂,試著增加 epoch 到 160。


多跑了幾個 epoch 後,loss 漸漸往上,代表還有 overfitting ,且發現 loss 震盪幅度很大,代表模型在谷底附近來回震盪,需要調整learning rate。嘗試以下調整:
- learning rate scheduler
- semi-supervised learning
先嘗試 learning scheduler :


鋸齒狀相當明顯,有谷底震盪的跡象,可能是以下問題 :
- 初始 learning rate 太高
- gradient clipping 限制不夠
嘗試調整後


loss穩定後,接著嘗試提高模型效能。
#### 嘗試 semi-supervise learning


大約在第 30-40 輪之後,模型的準確率就達到了瓶頸,雖然有波動,但無法持續穩定地突破 60% 的天花板。
分析原因 :
- 模型能力上限 : 搭建的模型太簡單,需要更深更複雜的模型架構才能學到更細微的特徵。
- 確認偏誤的影響 : sudo-label 可能引入了雜訊,錯誤的標記帶來干擾。
嘗試以下解決方法 :
- 提高門檻值 `threshold`:試著將 sudo-label 門檻提高。這樣可以確保只有模型「極度確認」的樣本才會被納入訓練,讓 sudo-label 的品質更高、更穩定。雖然這會減少偽標籤的數量,但「高品質的少量資料」通常遠勝於「帶有雜訊的大量資料」。
- 增加預熱期 (Warm-up):先只用有標籤的資料訓練 15-20 個 epochs,讓模型先建立一個穩固的基礎,之後再開啟半監督機制。
- 更改模型結構,使用更深更寬的模型做訓練。