---
# System prepended metadata

title: Convolutional Neural Network
tags: [ML]

---

# 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 資料集實作辨識食物圖片的訓練任務。
#### 原始效果
![image](https://hackmd.io/_uploads/ByS1T0_rle.png)
![image](https://hackmd.io/_uploads/r1zZpR_rel.png)
從上圖分析，訓練使 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()` : 隨機水平翻轉
食物照片左右翻轉通常不影響其類別。
![image](https://hackmd.io/_uploads/r1JuBZcBgx.png)
![image](https://hackmd.io/_uploads/BJTdH-5Sge.png)
scale=(0.5, 1.0)
![image](https://hackmd.io/_uploads/HyI-3j5Bge.png)
![image](https://hackmd.io/_uploads/HJgz2o5Hgl.png)



測試上述基本的 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% 的機率將圖片轉換為灰階。強迫模型不要只依賴顏色特徵來辨識食物，而是更多地關注紋理和形狀。

單一測試結果 : 
- 色彩抖動
![image](https://hackmd.io/_uploads/H1rekfcHxl.png)
![image](https://hackmd.io/_uploads/ryDbkMqSle.png)
- 隨機旋轉
![image](https://hackmd.io/_uploads/rJARw59Hle.png)
![image](https://hackmd.io/_uploads/SyTk_c5Sgl.png)
- 隨機仿射變換
跟上面差不多
- 色彩抖動 + 隨機旋轉
![image](https://hackmd.io/_uploads/Hkk1025rgl.png)
![image](https://hackmd.io/_uploads/rk51A25Hex.png)

相較之下有比較好，但不確定模型是否收斂，試著增加 epoch 到 160。
![image](https://hackmd.io/_uploads/SJvUPwsSxx.png)
![image](https://hackmd.io/_uploads/BySwDPjHgl.png)
多跑了幾個 epoch 後，loss 漸漸往上，代表還有 overfitting ，且發現 loss 震盪幅度很大，代表模型在谷底附近來回震盪，需要調整learning rate。嘗試以下調整: 
- learning rate scheduler
- semi-supervised learning

先嘗試 learning scheduler : 
![image](https://hackmd.io/_uploads/ryTtaknreg.png)
![image](https://hackmd.io/_uploads/rkqq6ynSgl.png)
鋸齒狀相當明顯，有谷底震盪的跡象，可能是以下問題 : 
- 初始 learning rate 太高
- gradient clipping 限制不夠

嘗試調整後
![image](https://hackmd.io/_uploads/rJKechhrlx.png)
![image](https://hackmd.io/_uploads/rkr-5nnrle.png)
loss穩定後，接著嘗試提高模型效能。

#### 嘗試 semi-supervise learning

![image](https://hackmd.io/_uploads/SkViBUTSgx.png)
![image](https://hackmd.io/_uploads/HkgnBUprxe.png)
大約在第 30-40 輪之後，模型的準確率就達到了瓶頸，雖然有波動，但無法持續穩定地突破 60% 的天花板。
分析原因 : 
- 模型能力上限 : 搭建的模型太簡單，需要更深更複雜的模型架構才能學到更細微的特徵。
- 確認偏誤的影響 : sudo-label 可能引入了雜訊，錯誤的標記帶來干擾。

嘗試以下解決方法 : 
- 提高門檻值 `threshold`：試著將 sudo-label 門檻提高。這樣可以確保只有模型「極度確認」的樣本才會被納入訓練，讓 sudo-label 的品質更高、更穩定。雖然這會減少偽標籤的數量，但「高品質的少量資料」通常遠勝於「帶有雜訊的大量資料」。
- 增加預熱期 (Warm-up)：先只用有標籤的資料訓練 15-20 個 epochs，讓模型先建立一個穩固的基礎，之後再開啟半監督機制。
- 更改模型結構，使用更深更寬的模型做訓練。
