PyTorch神經網絡入門:全連接層與反向傳播原理

1. 神經網絡的基礎:從神經元到全連接層

神經網絡就像由許多“迷你大腦”組成的網絡,每個“大腦”負責處理部分信息,而全連接層是神經網絡中最基礎的信息傳遞單元。簡單來說,全連接層的作用是讓前一層的所有神經元與當前層的所有神經元都建立連接——就像所有人都和所有人相連的社交網絡,確保信息能充分流動。

舉個例子:假設前一層有3個神經元,當前層有5個神經元,那麼每個當前層神經元的輸入都是前一層3個神經元輸出的加權和。用數學公式表示就是:

\[\text{輸出} = \text{權重矩陣} \times \text{輸入} + \text{偏置向量}\]
  • 權重矩陣:每個元素代表前一層神經元到當前層神經元的連接強度(用W表示)
  • 偏置向量:讓每個當前層神經元有一個獨立的“起點”(用b表示)

2. 前向傳播:信息從輸入到輸出的流動

當我們構建好全連接層後,需要讓數據從輸入層開始,逐層傳播到輸出層,這個過程稱爲前向傳播。以一個簡單的兩層神經網絡爲例:

  1. 輸入層:假設輸入是一個向量 x(例如MNIST手寫數字的784個像素值)
  2. 第一層全連接:經過nn.Linear(in_features=784, out_features=128)(PyTorch的全連接層),得到x1 = W1 @ x + b1
  3. 激活函數:使用ReLU激活函數 y1 = ReLU(x1)(引入非線性,讓網絡能擬合複雜關係)
  4. 第二層全連接:再經過nn.Linear(in_features=128, out_features=10),得到x2 = W2 @ y1 + b2(輸出10個類別得分)

用PyTorch代碼表示就是:

import torch
import torch.nn as nn

# 定義兩層全連接網絡(簡化版)
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc1 = nn.Linear(784, 128)  # 第一層全連接:784->128
        self.fc2 = nn.Linear(128, 10)   # 第二層全連接:128->10
        self.relu = nn.ReLU()  # 激活函數

    def forward(self, x):
        x = self.fc1(x)       # 第一層全連接 + 線性變換
        x = self.relu(x)      # ReLU激活
        x = self.fc2(x)       # 第二層全連接 + 線性變換
        return x

# 僞輸入:1個樣本,784維輸入
x = torch.randn(1, 784)
model = SimpleNet()
output = model(x)  # 前向傳播,得到輸出
print(output.shape)  # 輸出形狀:torch.Size([1, 10])

3. 反向傳播:讓神經網絡“自我修正”的核心

3.1 爲什麼需要反向傳播?

前向傳播只能計算輸出,但無法讓模型“學習”——我們需要找到合適的權重W和偏置b,讓模型的預測誤差最小。反向傳播就是通過計算“損失函數對權重的梯度”,讓模型自動調整參數的方法。

3.2 梯度下降:參數更新的核心思想

想象你站在山坡上,想要最快到達山腳(最小化損失)。梯度就是“當前位置的最陡下降方向”,因此梯度下降算法的更新規則是:

\[W = W - \eta \times \frac{\partial \text{損失}}{\partial W}\]
  • 學習率(η):控制每次移動的步長(步長太大會跳過最低點,太小會收斂太慢)
  • 梯度:損失函數對權重的導數,表示“調整權重後損失會變化多少”

3.3 鏈式法則:反向傳播的數學基礎

反向傳播本質是鏈式法則在神經網絡中的應用。例如,對於三層網絡(輸入→fc1→relu→fc2→輸出),損失函數LW1的梯度需要通過:

\[\frac{\partial L}{\partial W1} = \frac{\partial L}{\partial x2} \times \frac{\partial x2}{\partial y1} \times \frac{\partial y1}{\partial x1} \times \frac{\partial x1}{\partial W1}\]

從輸出層開始“反向計算”每個參數的梯度,再逐層傳遞到輸入層。這個過程中,PyTorch的autograd自動幫我們記錄計算圖並計算梯度!

3.4 反向傳播的具體步驟(以簡單示例說明)

假設我們訓練一個兩層網絡(輸入→fc1→relu→fc2→輸出),損失函數用均方誤差(MSE):

\[L = \frac{1}{2}(y_{pred} - y_{true})^2\]

步驟1:計算前向傳播的輸出
用PyTorch自動記錄計算圖(每個操作的requires_grad=True):

x = torch.tensor([1.0, 2.0])  # 輸入
y_true = torch.tensor([3.0])   # 真實值

# 定義參數(簡化版)
W1 = torch.tensor([[0.5, 0.3], [0.2, 0.7]])  # 784→128的簡化
b1 = torch.tensor([0.1, 0.2])
W2 = torch.tensor([[0.4, 0.6, 0.1]])          # 128→10的簡化
b2 = torch.tensor([0.3])

# 前向傳播
x1 = W1 @ x + b1  # x1 = [0.5*1+0.3*2+0.1=1.2, 0.2*1+0.7*2+0.2=1.8]
y1 = torch.relu(x1)  # y1 = [1.2, 1.8](ReLU不改變正的)
x2 = W2 @ y1 + b2    # x2 = 0.4*1.2 + 0.6*1.8 + 0.1 = 0.48+1.08+0.1=1.66
y_pred = x2[0]       # 簡化輸出爲單個值

步驟2:計算損失函數對參數的梯度
loss.backward()自動求導(PyTorch的autograd幫我們完成鏈式法則):

loss = (y_pred - y_true) ** 2 / 2  # 均方誤差(除以2簡化計算)
loss.backward()  # 反向傳播計算梯度

# 此時參數的grad屬性會自動填充梯度值
print("W2的梯度:", W2.grad)  # tensor([[1.2, 1.8, 0.0]])(示例值)
print("W1的梯度:", W1.grad)  # tensor([[0.4*0.2*(1.66-3)=...]](需鏈式計算)

步驟3:參數更新
使用優化器(如SGD)按梯度更新權重:

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
optimizer.step()  # 用梯度更新W1, b1, W2, b2

4. 反向傳播在PyTorch中的完整流程

4.1 核心步驟總結:

  1. 定義網絡:用nn.Modulenn.Linear定義全連接層與激活函數
  2. 前向傳播:輸入數據通過網絡計算輸出
  3. 計算損失:用損失函數(如MSE、CrossEntropyLoss)比較預測值與真實值
  4. 反向傳播:調用loss.backward()自動計算所有參數的梯度
  5. 參數更新:用優化器(如torch.optim.SGD)根據梯度更新權重

4.2 完整代碼示例(訓練一個簡單迴歸任務):

# 1. 導入庫
import torch
import torch.nn as nn
import torch.optim as optim

# 2. 生成模擬數據(y = 2x1 + 3x2 + 5 + 噪聲)
x = torch.randn(100, 2)  # 100個樣本,2個特徵
true_w = torch.tensor([2.0, 3.0])
true_b = torch.tensor(5.0)
y_true = (x @ true_w) + true_b + 0.1 * torch.randn(100)  # 帶噪聲

# 3. 定義模型
class LinearNet(nn.Module):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Linear(2, 1)  # 輸入2維,輸出1維(簡化版,無激活函數)
    def forward(self, x):
        return self.fc(x)

model = LinearNet()

# 4. 定義損失函數和優化器
criterion = nn.MSELoss()  # 均方誤差
optimizer = optim.SGD(model.parameters(), lr=0.1)  # 學習率0.1

# 5. 訓練循環
for epoch in range(100):
    # 前向傳播
    y_pred = model(x)
    loss = criterion(y_pred, y_true.unsqueeze(1))  # 調整維度匹配
    # 反向傳播
    optimizer.zero_grad()  # 清空梯度(PyTorch默認累加梯度)
    loss.backward()        # 計算梯度
    optimizer.step()       # 更新參數
    if epoch % 10 == 0:
        print(f"Epoch {epoch}, Loss: {loss.item():.4f}")

# 6. 輸出最終參數(應該接近true_w和true_b)
print("學習後的權重:", model.fc.weight)  # tensor([[2.0005, 3.0002]])
print("學習後的偏置:", model.fc.bias)    # tensor([5.0001])

5. 關鍵概念回顧

  • 全連接層:每層神經元與上一層所有神經元連接,實現特徵加權組合
  • 前向傳播:數據從輸入到輸出的正向計算過程
  • 反向傳播:從輸出層反向計算損失對參數的梯度,通過鏈式法則實現
  • 梯度下降:沿梯度方向更新參數,最小化損失函數
  • 自動求導:PyTorch的autograd自動記錄計算圖並計算梯度,簡化參數更新

通過本文,你應該已經理解了全連接層如何傳遞信息,以及反向傳播如何讓神經網絡“學習”。實際應用中,PyTorch會幫我們處理大部分細節,但理解背後的原理能讓你更好地調試和優化模型!

小夜