在進入深度學習的第一步,必須要先認識最基礎的深度學習架構,這個架構稱為:
這兩個都是指同一個東西,這個架構也是所有深度學習架構的基石,CNN、RNN、Transformer 都可以看作是在 FCNN 上加入特殊結構與限制後的延伸
大家可以使用這個 網站 看一下神經網絡的架構與運行過程,簡單來說最基礎的神經網絡架構一定有以下三個 layer:
整個神經網絡的運做過程,核心就是「輸入資料 (X) → 正向傳播 (Forward) → 計算 Loss → 反向傳播 (Backward) → 更新參數 (Gradient Descent) → 再正向傳播」,說明如下:
這邊先破個梗,什麼時候會停止? 這也是人為控制,透過一個 epoch 的參數,來決定要做幾輪 (N 次的正向傳播 + 反向傳播),所以當 epoch=1 就是表示做 1 次的正向傳播 + 反向傳播
就像「預測」或「算成績」的過程:
重點:
就像「檢討錯誤」和「調整公式」的過程:
重點:
經過前面 10 多天的洗禮,我相信看到這邊大家都會有個疑問,分群 (Clustering) 沒有 label 的,那表示神經網絡沒有分群 (Clustering) 這個概念? 當然不是,神經網絡不會這麼弱,差別在於 Loss 的設計邏輯不一樣:
這些技術後面慢慢談,所以神經網絡的應用場景很廣的
目前大多數的研究都顯示出,面對真實問題的情況下,深度學習模型都比經典機器學習模型表現更好,但是模型的訓練不能只看準確度,還要看成本跟訓練速度,很多複雜的問題,神經網絡寫好,訓練執行下去,可能一次就要跑一週以上,大概率你的筆電也很難訓練這種模型,但是在經典機器學習就不會出現這種問題,深度學習主要的缺點:
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 等深度學習架構的共同底層邏輯