iT邦幫忙

2025 iThome 鐵人賽

DAY 18
0
AI & Data

30 天入門常見的機器學習演算法系列 第 18

(Day 18) 全連接神經網絡 (Fully Connected Neural Network)

  • 分享至 

  • xImage
  •  

在進入深度學習的第一步,必須要先認識最基礎的深度學習架構,這個架構稱為:

  • 全連接神經網路 (Fully Connected Neural Network; FCNN)
  • 多層感知機 (Multi-Layer Perceptron; MLP)

這兩個都是指同一個東西,這個架構也是所有深度學習架構的基石,CNN、RNN、Transformer 都可以看作是在 FCNN 上加入特殊結構與限制後的延伸

基本架構

大家可以使用這個 網站 看一下神經網絡的架構與運行過程,簡單來說最基礎的神經網絡架構一定有以下三個 layer:

  • 輸入層 (Input Layer): 接收原始特徵,大小等於特徵數量
  • 隱藏層 (Hidden Layers): 由多個神經元 (Neurons) 組成,每個神經元與前一層的所有神經元相連
  • 輸出層 (Output Layer): 對應任務需求,分類問題常用 softmax、多類別輸出;回歸問題則可能是單一實值,簡單的區分如下:
    • 回歸問題
      • 輸出層神經元數 = 1 (代表一個連續數值)
      • 常用激活函數: 無激活 (linear)
      • Loss function: MSE, MAE
    • 二元分類
      • 寫法一: 1 個神經元 + Sigmoid 激活 (輸出機率 [0,1])
      • 寫法二: 2 個神經元 + Softmax (輸出兩類的機率分佈)
      • Loss function: Binary Crossentropy / Categorical Crossentropy
      • ✅ 工程上最常用的是 1 個輸出神經元 + Sigmoid
    • 多元分類 (N 類別)
      • 輸出層神經元數 = N
      • 激活函數: Softmax (保證所有輸出加起來 = 1,形成機率分布)
      • Loss function: Categorical Crossentropy / Sparse Categorical Crossentropy

運作流程

整個神經網絡的運做過程,核心就是「輸入資料 (X) → 正向傳播 (Forward) → 計算 Loss → 反向傳播 (Backward) → 更新參數 (Gradient Descent) → 再正向傳播」,說明如下:

  • 輸入資料 (X): 把影像、數值或文字丟進神經網絡
  • 正向傳播 (Forward): 一層一層計算,得到模型的輸出
  • 計算 Loss: 拿模型輸出 (output layer 的結果) 和真實標籤比對,計算誤差
  • 反向傳播 (Backward): 誤差往回傳,算每個參數對誤差的影響 (梯度)
  • 更新參數 (Gradient Descent): 用梯度下降修正權重
  • 新的參數 → 下一輪: 進入下一個 batch,再次 forward

這邊先破個梗,什麼時候會停止? 這也是人為控制,透過一個 epoch 的參數,來決定要做幾輪 (N 次的正向傳播 + 反向傳播),所以當 epoch=1 就是表示做 1 次的正向傳播 + 反向傳播

正向傳播

就像「預測」或「算成績」的過程:

  • 你把輸入資料 (例如一張圖片) 丟進去模型
  • 每一層神經元就像一個公式:輸入數字 × 權重 + 偏差 → 經過激活函數 → 輸出
  • 一層一層傳下去,最後在輸出層算出結果(例如:這張圖片是「貓」的機率 0.8,「狗」0.2)

重點:

  • 正向傳播就是單純「照著模型算一次結果」
  • 這時候你拿到的只是「模型的猜測」

反向傳播

就像「檢討錯誤」和「調整公式」的過程:

  • 模型算出一個結果後,會跟真實答案比對
    • 例如:模型說「貓 0.8,狗 0.2」,但真實答案是「狗」
  • 用 Loss Function (損失函數) 計算「模型猜錯的程度」
    • 想像成「考試分數差多少」
  • 反向傳播就是把這個錯誤,從輸出層往回傳,算出每個權重對錯誤的「貢獻」
    • 哪些權重害錯最多,就調整得更多
    • 哪些影響小,就調整得少
  • 調整的方法就是用 梯度下降 (Gradient Descent)

重點:

  • 反向傳播不是在「重跑」,而是在「反推責任歸屬」,然後「更新參數」
  • 正向傳播像是「做題目」,反向傳播像是「老師改考卷,告訴你哪裡要修正」

沒有 label 就不能用神經網絡?

經過前面 10 多天的洗禮,我相信看到這邊大家都會有個疑問,分群 (Clustering) 沒有 label 的,那表示神經網絡沒有分群 (Clustering) 這個概念? 當然不是,神經網絡不會這麼弱,差別在於 Loss 的設計邏輯不一樣:

  • Supervised Learning (with labels)
    • 有明確答案,loss 直接來自「預測 vs. 真實答案」的差異
  • Unsupervised Learning (without labels)
    • 沒有 y,所以不能用 cross entropy 這種監督式的 Loss
    • 改成「自我監督 / 結構化目標」來建 Loss,例如:
      • 自編碼器 (Autoencoder)
      • GANs (生成對抗網絡)

這些技術後面慢慢談,所以神經網絡的應用場景很廣的

優缺點

目前大多數的研究都顯示出,面對真實問題的情況下,深度學習模型都比經典機器學習模型表現更好,但是模型的訓練不能只看準確度,還要看成本跟訓練速度,很多複雜的問題,神經網絡寫好,訓練執行下去,可能一次就要跑一週以上,大概率你的筆電也很難訓練這種模型,但是在經典機器學習就不會出現這種問題,深度學習主要的缺點:

  • 訓練成本高,對大資料需求大
  • 參數數量龐大,容易過擬合
  • 可解釋性差,屬於黑盒模型

模型實作

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# 1. 數據準備 (MNIST)
transform = transforms.Compose(  # 組合多個前處理步驟,會依序做 ToTensor() → Normalize()
    [
        transforms.ToTensor(),  # 把圖片 (通常是 PIL image 或 numpy array) 轉成 PyTorch Tensor,同時會把像素值 從 [0,255] 縮放到 [0,1]
        transforms.Normalize((0.5,), (0.5,))  # 標準化 (Normalization) $x_{norm} = \frac{x - mean}{std}$
    ]
)

## 這是 torchvision 提供的資料集接口,可以直接下載或載入 MNIST 手寫數字資料集
##  - root="./data": 指定資料儲存的目錄 (如果沒下載過,會自動下載到這個資料夾)
## 	- train=True: 載入訓練集 (60,000 筆資料)
## 	- train=False: 載入測試集 (10,000 筆資料)
## 	- download=True: 如果資料不存在,會自動下載
## 	- transform=transform: 指定前處理方法 (剛剛定義的 ToTensor() + Normalize())
train_data = datasets.MNIST(root="./data", train=True, download=True, transform=transform)
test_data = datasets.MNIST(root="./data", train=False, download=True, transform=transform)

## 前面執行後會得到 Dataset 物件,但還不能直接拿來訓練,需要包裝成 DataLoader
## PyTorch 提供的 資料批次處理器,負責把資料分批 (batch) 餵給模型
## 	- train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
## 	- 把訓練資料 train_data 包裝成 DataLoader
## 	- batch_size=64: 每次會取 64 筆資料出來訓練
## 	- shuffle=True: 每個 epoch 開始時會隨機打亂資料,避免模型學到資料順序的偏差
## 	- test_loader = DataLoader(test_data, batch_size=1000, shuffle=False)
## 	- 測試資料一次取 1000 筆出來測試
## 	- shuffle=False: 測試時不需要打亂,保持固定順序即可
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
test_loader = DataLoader(test_data, batch_size=1000, shuffle=False)


# 2. 建立模型
class FCNN(nn.Module):
    def __init__(self):
        # 如果不寫 nn.Module 的初始化沒被呼叫,forward() 裡用的 self.fc1 = nn.Linear(...) 雖然看起來「存在」,但 模型不會追蹤到它的參數
        super().__init__()

        # NIST 的圖片是 28 × 28 灰階,一張圖片攤平成一個向量,長度就是 28×28 = 784
        # 輸出是 128 個神經元
        self.fc1 = nn.Linear(28*28, 128)

        # 自由選,這是超參數 (hyperparameter),你完全可以改:
        # nn.Linear(784, 256) → nn.Linear(256, 128) → nn.Linear(128, 10) 或者再多加幾層、更寬 (512, 1024…)
        # 這個不影響「輸入跟輸出」的對齊,只會影響模型容量與訓練難度
        # 輸出是 64 個神經元
        self.fc2 = nn.Linear(128, 64)

        # MNIST 的標籤是 0–9,總共 10 個類別,所以最後一層要輸出 10 維 logits,對應到 softmax 之後的 10 類機率分佈
        # 如果是二元分類 (例如貓/狗),輸出就會設成 1 或 2 (看你選 binary sigmoid 還是 softmax)
        self.fc3 = nn.Linear(64, 10)

        self.relu = nn.ReLU()

        # 在訓練時,有 20% 的神經元會被隨機「丟掉」(設成 0),其餘 80% 的神經元會被保留,但會 放大輸出 (除以 0.8),確保整體期望值不變
        # 為什麼要這樣設計?
        #  - 假設一個神經元輸出值是 h=0.5:
        # 	 - 如果被丟掉 → 輸出 = 0
        # 	 - 如果沒被丟掉 → 輸出 = 0.5 ÷ 0.8 = 0.625
        #  - 這樣的好處是:在測試時不用再做任何調整,因為訓練時的期望值已經被 scale 過了
        # 常見的 p 值選擇
        #  - p=0.1 ~ 0.3:小模型 (例如 MNIST),避免太多資訊丟掉
        #  - p=0.5:經典值,常用在大模型的全連接層
        #  - p=0.7+:幾乎不用,因為會把大部分資訊都丟掉,網路很難收斂
        self.dropout = nn.Dropout(p=0.2)

    # 資料流動定義
    def forward(self, x):
        # -1: 自動推算 batch_size,不用你自己算。例如 batch_size=64,那這裡就是 64
        # 28*28: 固定每張圖片攤平成 784 維的向量
        x = x.view(-1, 28*28)

        x = self.fc1(x)
        x = self.relu(x)

        # 第一層參數最多
        #  - MNIST 784 → 128: 這裡的權重數量是 784×128 ≈ 100k,容易過擬合
        #  - Dropout 可以迫使網路不要過度依賴某幾個輸入維度
        # 高維度 → 容易記憶資料
        #  - 如果沒有 Dropout,網路可能直接「記住」訓練資料而不是學 general 特徵
        #  - 在大維度特徵處加 Dropout,可以降低這種風險
        x = self.dropout(x)
        x = self.fc2(x)
        x = self.relu(x)
        # 為什麼第二層常常不用 Dropout?
        # - 後面神經元比較少
        #   - 128 → 64,參數數量大幅減少,過擬合風險相對低
        # - 避免過度稀疏
        #   - 如果每一層都 Dropout,訊號可能被「打掉太多」,模型學不到東西
        # 	- 一般經驗: 對大的全連接層用 Dropout,小層就不一定需要
        x = self.fc3(x)
        return x

model = FCNN()

# 3. 訓練設置
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 4. 訓練迴圈
epochs = 10
for ep in range(1, epochs+1):
    model.train()  # 切換成訓練模式 (啟用 Dropout、BatchNorm 等): PyTorch 底層邏輯
    loss = 0.0
    for images, labels in train_loader:
        optimizer.zero_grad()  # 關鍵: 清空上一步的梯度 (PyTorch 預設會把梯度「累加」,如果不清空,第二個 batch 的梯度會加到第一個 batch 上,導致更新錯誤,所以每次迭代前必須先歸零)
        outputs = model(images)  # 執行模型的 forward()
        loss = criterion(outputs, labels)  # 這裡會自動做 softmax + cross entropy
        loss.backward()  # 自動計算每個參數的梯度,梯度會存放在對應的 param.grad 裡 (這是 PyTorch 的 autograd 機制)
        optimizer.step()  # 用計算好的梯度更新參數,這裡使用的是 SGD (w ← w - lr * grad),如果你換成 Adam,會是另一種更新規則
    print(f"Epoch {ep}/{epochs}, Loss: {loss.item():.4f}")

# 5. 測試
model.eval()  # 切換模型到評估模式 (evaluation mode)
correct = 0.0
total = 0.0
with torch.no_grad():  # 關閉梯度計算,測試時我們只需要 forward pass,不需要反向傳播
    for images, labels in test_loader:
        output = model(images)  # 把影像送進模型,得到輸出 logits (還沒 softmax 的值)
        pred = torch.argmax(output, dim=1)  # 功能: 在維度 dim=1 上找出最大值的 index → 代表模型預測的類別,例:[0.1, 0.7, 0.2] → argmax=1 → 預測為類別 1

        # (pred == labels) → 逐元素比較,產生 True/False 的 tensor
        # .sum() → 計算這個 batch 預測正確的數量
        # .item() → 把 tensor 轉成純數字 (int/float)
        # correct → 累加正確數量
        correct += (pred == labels).sum().item()

        # images.size(0) → batch 的樣本數 (通常等於 batch_size,最後一批可能比較小)
        # total → 累加測試資料總數。
        total += images.size(0)

print(f"Accuracy: {correct/total:.4f}")

結語

今天簡單的介紹了全連接神經網絡 (FCNN) 是深度學習的起點與基石,雖然在高維與非結構化資料上不是最佳選擇,但它提供了一個理解神經網路如何運作的直觀框架: 輸入 → 加權和 → 非線性轉換 → 多層堆疊 → 輸出。

實務上,FCNN 常用於:

  • 表格數據的分類/回歸
  • 特徵壓縮與嵌入
  • 作為複雜架構的子模組

理解 FCNN,不只是為了使用它本身,而是為了更好地理解 CNN、RNN、Transformer 等深度學習架構的共同底層邏輯


上一篇
(Day 17) 淺談深度學習 (Deep Learning)
下一篇
(Day 19) 神經元 (Neuron)
系列文
30 天入門常見的機器學習演算法30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言