歡迎來到啾啾的博客🐱。
記錄學習點滴。分享工作思考和實用技巧,偶爾也分享一些雜談💬。
有很多很多不足的地方,歡迎評論交流,感謝您的閱讀和評論😄。
目錄
- 引言
- 1 Tensor
- 1.1 🛠?Tensor 的核心用途(5大場景)
- 1.2 表示現實世界數據|輸入數據
- 1.3 表示標簽(Labels / Targets)
- 1.4 存儲模型參數(Weights & Biases)
- 1.5 中間計算結果(Forward Pass)
- 1.6 梯度計算(Backward Pass)
- 1.7 📚 常用 API 速查表
- 1.8 💡 Tensor 演示
- 2 Module
- 2.1 簡介
- 2.2 創建Module
- 2.3 Module 的核心操作
- 3 Autograd
- 3.1 簡介
- 3.2 梯度追蹤
- 3.3 反向傳播
- 3.4 禁用梯度計算
- 4 📌 三件套關系圖解
- 5 🌟 三件套協同工作流程(完整訓練示例)
- 5.1 Demo:三件套簡單串聯
- 5.2 構建一個簡單線性回歸模型 y=wx+b
- 5.3 Demo:搭建一個簡單的多層感知機
- 刻意練習
引言
本篇將講述PyTorch的核心“三件套”:Tensor, Module, Autograd。
嘗試以后端崗位工程應用角度來理解、串聯起這個三個概念。
AI使用聲明:本文內容由作者基于對深度學習和PyTorch框架的學習與理解撰寫。在內容整理、結構優化和語言表達的過程中,我使用了人工智能(AI)工具作為輔助。
資料:
《深入淺出PyTorch 第二章節》
《動手學深度學習第四章》
1 Tensor
讓我們思考一個問題,我們要怎么把現實世界的數據變成計算機能處理的數字形式呢?
通過Tensor,張量,它是現代機器學習的基礎。
n維數組,也稱為_張量_(tensor)。
幾何代數中定義的張量是基于向量和矩陣的推廣,比如我們可以將標量視為零階張量,矢量可以視為一階張量,矩陣就是二階張量。
張量維度 | 代表含義 | 示例 |
---|---|---|
0D | 標量(Scalar) | torch.tensor(3.14) |
1D | 向量(Vector) | [1, 2, 3] |
2D | 矩陣(Matrix) | 圖像展平后的特征 |
3D | 序列/單圖 | (C, H, W) 彩色圖像 |
4D | 批量圖像 | (B, C, H, W) |
在PyTorch中,?torch.Tensor
?是存儲和變換數據的主要工具,比起NumPy更適合深度學習。
1.1 🛠?Tensor 的核心用途(5大場景)
1.2 表示現實世界數據|輸入數據
現實世界常見數據的Tensor示例如下:
數據類型 | Tensor形狀示例 | 說明 |
---|---|---|
圖像 | (3, 224, 224) | RGB 三通道圖像 |
批量圖像 | (64, 3, 224, 224) | 一次處理64張圖 |
文本 | (1, 512) | 512個詞的編碼序列 |
音頻 | (1, 16000) | 1秒音頻(16kHz采樣) |
# 圖像轉 Tensor(使用 torchvision)
from torchvision import transforms
transform = transforms.ToTensor()
image_tensor = transform(pil_image) # PIL圖像 → Tensor
1.3 表示標簽(Labels / Targets)
可以告訴模型“正確答案”。
# 分類任務:類別標簽
labels = torch.tensor([3, 1, 7, 0]) # 4個樣本的真實類別# 回歸任務:連續值
targets = torch.tensor([1.5, 2.3, 0.8])# 語義分割:每個像素的類別
mask = torch.randint(0, 20, (1, 256, 256)) # (C,H,W)
1.4 存儲模型參數(Weights & Biases)
神經網絡的“記憶”就存在 Tensor 里。
linear = torch.nn.Linear(10, 5)
print(linear.weight.shape) # torch.Size([5, 10]) ← 這是個 Tensor!
print(linear.bias.shape) # torch.Size([5]) ← 這也是 Tensor!
這些參數 Tensor 會:
- 在訓練中不斷更新(梯度下降)
- 決定模型的預測能力
- 可以保存和加載(
.pth
文件)
1.5 中間計算結果(Forward Pass)
網絡每一層的輸出都是 Tensor 。
x = torch.randn(1, 784) # 輸入
w = torch.randn(784, 256) # 權重
b = torch.zeros(256) # 偏置# 每一步都是 Tensor 運算
z = x @ w + b # 線性變換
a = torch.relu(z) # 激活函數
# a 仍然是 Tensor,傳給下一層
1.6 梯度計算(Backward Pass)
Autograd 用 Tensor 記錄梯度 。
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
y.backward() # 計算 dy/dx
print(x.grad) # tensor(4.) ← 梯度也存在 Tensor 中!
1.7 📚 常用 API 速查表
類別 | API | 說明 | 示例 |
---|---|---|---|
創建 | torch.tensor() | 通用創建 | x = torch.tensor([1,2,3]) |
torch.randn() | 隨機正態分布 | x = torch.randn(2,3) | |
torch.zeros() | 全零張量 | x = torch.zeros(4,4) | |
轉換 | .numpy() | → NumPy 數組 | arr = x.numpy() |
torch.from_numpy() | ← NumPy 數組 | x = torch.from_numpy(arr) | |
.to('cuda') | → GPU | x_gpu = x.to('cuda') | |
運算 | + ,- ,* ,/ | 基礎運算 | z = x + y |
torch.matmul() | 矩陣乘法 | z = torch.matmul(x,y) | |
x.view() | 形狀變換 | x_2d = x.view(2,3) | |
自動求導 | .requires_grad_() | 開啟梯度追蹤 | x.requires_grad_() |
.detach() | 剝離梯度計算 | x_no_grad = x.detach() |
1.8 💡 Tensor 演示
import torch # 1. 創建張量(開啟梯度追蹤)
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
print(f"初始張量: {x} | 是否追蹤梯度: {x.requires_grad}") # 2. 張量運算(自動構建計算圖)
y = x * 2 + 3
z = y.mean()
print(f"中間結果 y: {y} | 最終結果 z: {z}") # 3. 自動求導(反向傳播)
z.backward() # 計算 dz/dxprint(f"梯度 dz/dx: {x.grad}") # 輸出: [0.6667, 0.6667, 0.6667] # 4. GPU 加速演示
if torch.cuda.is_available(): x_gpu = x.to('cuda') # 一鍵遷移到GPU print(f"GPU張量: {x_gpu} | 位于設備: {x_gpu.device}")
2 Module
讓我們思考一個問題,我們要怎么定義一個神經網絡的“結構”和“行為”呢?
通過 nn.Module
,它是PyTorch中所有神經網絡模塊的基類。你可以把它想象成一個可以“思考”的容器,它不僅存儲網絡的參數(如權重和偏置),還定義了數據應該如何流動(前向傳播)。
本質:所有神經網絡層的基類,管理參數 + 定義計算流程
核心:forward()
方法定義數據流動邏輯
2.1 簡介
在PyTorch中,torch.nn.Module
是構建神經網絡的核心抽象。無論是簡單的線性層 nn.Linear
,還是復雜的ResNet模型,它們都是 nn.Module
的子類。通過繼承 nn.Module
,我們可以輕松地定義自己的神經網絡。
經過本節的學習,你將收獲:
nn.Module
的簡介- 如何使用
nn.Sequential
快速構建模型 - 如何自定義
nn.Module
子類來創建復雜模型 nn.Module
的核心操作(參數管理、設備遷移)
2.2 創建Module
在接下來的內容中,我們將介紹幾種常見的創建 Module
的方法。
-
使用
nn.Sequential
構建模型:
nn.Sequential
是一個有序的容器,它將傳入的模塊按順序組合起來。對于不需要復雜邏輯的“直筒型”網絡,這是最簡單的方法。import torch.nn as nn# 定義一個簡單的多層感知機 (MLP) model = nn.Sequential(nn.Flatten(), # 將圖像展平成一維向量nn.Linear(28*28, 128), # 全連接層 (輸入784 -> 輸出128)nn.ReLU(), # 激活函數nn.Linear(128, 10), # 輸出層 (輸入128 -> 輸出10類)nn.Softmax(dim=1) # 輸出概率分布 ) print(model)
Sequential((0): Flatten(start_dim=1, end_dim=-1)(1): Linear(in_features=784, out_features=128, bias=True)(2): ReLU()(3): Linear(in_features=128, out_features=10, bias=True)(4): Softmax(dim=1) )
-
自定義
nn.Module
子類:
對于更復雜的網絡結構(如包含分支、殘差連接等),我們需要創建自己的類,繼承nn.Module
。import torch.nn as nnclass SimpleMLP(nn.Module):def __init__(self):super().__init__()# 在 __init__ 中定義網絡層self.flatten = nn.Flatten()self.fc1 = nn.Linear(28*28, 128)self.relu = nn.ReLU()self.fc2 = nn.Linear(128, 10)def forward(self, x):# 在 forward 中定義數據流動的邏輯x = self.flatten(x)x = self.fc1(x)x = self.relu(x)x = self.fc2(x)return x# 實例化模型 model = SimpleMLP() print(model)
SimpleMLP((flatten): Flatten(start_dim=1, end_dim=-1)(fc1): Linear(in_features=784, out_features=128, bias=True)(relu): ReLU()(fc2): Linear(in_features=128, out_features=10, bias=True) )
一個標準的 nn.Module
子類有兩個必須理解的方法。
__init__
方法:
初始化網絡層,并將它們作為類的屬性,在這里定義參數。forward
方法:
接收輸入x
(一個Tensor),然后執行一系列計算并返回輸出(也是一個Tensor)。
不需要手動調用forward
,執行model實例是,PyTorch 會自動調用它的forward方法。
2.3 Module 的核心操作
在接下來的內容中,我們將介紹 Module
的幾個關鍵操作。
-
參數管理:
nn.Module
會自動將nn.Parameter
或任何nn.Module
子類的實例注冊為模型的參數。我們可以使用以下方法訪問它們。# 獲取模型的所有參數 for param in model.parameters():print(param.shape)# 獲取模型的命名參數 for name, param in model.named_parameters():print(f"{name}: {param.shape}")
flatten._parameters: {} # Flatten層無參數 fc1.weight: torch.Size([128, 784]) fc1.bias: torch.Size([128]) fc2.weight: torch.Size([10, 128]) fc2.bias: torch.Size([10])
-
設備遷移:
為了利用GPU加速,我們可以使用.to(device)
方法將整個模型(包括其所有參數和子模塊)遷移到指定設備。device = 'cuda' if torch.cuda.is_available() else 'cpu' model = model.to(device) # 一鍵遷移到GPU print(f"模型設備: {next(model.parameters()).device}")
-
狀態保存與加載:
我們可以使用state_dict()
來獲取模型參數的字典,這非常適合保存和加載訓練好的模型。# 保存模型 torch.save(model.state_dict(), 'simple_mlp.pth')# 加載模型 (需要先創建相同結構的模型) new_model = SimpleMLP() new_model.load_state_dict(torch.load('simple_mlp.pth')) new_model.eval() # 切換到評估模式
-
訓練/評估模式切換:
一些層(如Dropout
,BatchNorm
)在訓練和評估時的行為不同。我們可以使用model.train()
和model.eval()
來切換模式。model.train() # 啟用Dropout等訓練專用操作 # ... 訓練代碼 ...model.eval() # 禁用Dropout,使用BatchNorm的統計值 # ... 推理代碼 ... with torch.no_grad(): # 通常與 no_grad() 一起使用output = model(input_tensor)
3 Autograd
讓我們思考一個問題,我們要怎么讓模型“學會”調整自己的參數呢?
通過 autograd
,它是PyTorch的自動微分引擎。它會默默地記錄我們對張量進行的所有操作,構建一個“計算圖”,然后在需要時自動計算梯度,使得反向傳播變得極其簡單。
3.1 簡介
在深度學習中,我們通過反向傳播算法來更新模型參數。手動計算梯度不僅繁瑣而且容易出錯。PyTorch的 autograd
包可以自動完成這一過程。
autograd
的核心是 torch.Tensor
和 torch.Function
。
當 Tensor
的 requires_grad
屬性為 True
時,autograd
會追蹤所有作用于該張量的操作
。一旦計算完成,調用 .backward()
方法,autograd
就會自動計算所有梯度,并將它們累積到 .grad
屬性中。
經過本節的學習,你將收獲:
autograd
的工作原理- 如何使用
requires_grad
控制梯度追蹤 - 如何執行反向傳播并查看梯度
- 如何使用
torch.no_grad()
上下文管理器
3.2 梯度追蹤
-
開啟梯度追蹤:
通過設置requires_grad=True
,我們可以告訴autograd
需要追蹤對這個張量的所有操作。import torch# 方法1: 創建時指定 x = torch.tensor([1., 2., 3.], requires_grad=True) print(f"x.requires_grad: {x.requires_grad}")# 方法2: 對已有張量啟用 y = torch.tensor([4., 5., 6.]) y.requires_grad_(True) # 注意是方法,帶下劃線 print(f"y.requires_grad: {y.requires_grad}")
-
構建計算圖:
一旦開啟了梯度追蹤,后續的運算都會被記錄下來。z = x * y + 2 print(f"z.grad_fn: {z.grad_fn}") # <AddBackward0 object> print(f"z's creator: {z.grad_fn}")
3.3 反向傳播
-
執行反向傳播:
調用.backward()
方法來觸發反向傳播。如果loss
是一個標量(0維張量),可以直接調用loss.backward()
。loss = z.sum() # loss 是一個標量 print(f"loss: {loss}") # tensor(27., grad_fn=<SumBackward0>)loss.backward() # 計算梯度 print(f"dx/dloss: {x.grad}") # tensor([4., 5., 6.]) print(f"dy/dloss: {y.grad}") # tensor([1., 2., 3.])
注意:
.backward()
會累積梯度。在下一次迭代前,務必使用optimizer.zero_grad()
或手動清零x.grad.zero_()
。 -
非標量輸出:
如果要對非標量張量調用.backward()
,需要傳入一個gradient
參數(形狀與該張量相同),作為外部梯度。v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float) z.backward(gradient=v) # 這里會累加梯度 # x.grad 會增加 v * y 的值
3.4 禁用梯度計算
在模型推理(inference)或某些不需要梯度的計算中,我們可以使用 torch.no_grad()
上下文管理器來臨時禁用梯度計算,這可以節省內存并加快計算速度。
print(f"Before no_grad, x.requires_grad: {x.requires_grad}")with torch.no_grad():a = x * 2print(f"Inside no_grad, a.requires_grad: {a.requires_grad}") # Falseprint(f"After no_grad, x.requires_grad: {x.requires_grad}") # 仍然是 True# 另一種方式:使用 .detach()
b = x.detach() * 3
print(f"b.requires_grad: {b.requires_grad}") # False
4 📌 三件套關系圖解
┌─────────────┐ ┌───────────┐ ┌─────────────┐│ │ │ │ │ ││ Tensor │─────?│ Module │─────?│ Autograd ││ (數據載體) │?─────│ (計算邏輯)│?─────│ (梯度引擎) │└─────────────┘ └───────────┘ └─────────────┘▲ │ ││ ▼ ▼┌─────┴──────┐ ┌─────────────┐ ┌─────────────┐│ 數據加載 │ │ 模型定義 │ │ 反向傳播 ││ DataLoader │ │ forward() │ │ loss.backward() └────────────┘ └─────────────┘ └─────────────┘
? 關鍵口訣:
“Tensor 存數據,Module 定流程,Autograd 算梯度,三件套合訓練成”
5 🌟 三件套協同工作流程(完整訓練示例)
資料:https://zh-v2.d2l.ai/chapter_multilayer-perceptrons/mlp-scratch.html
5.1 Demo:三件套簡單串聯
import torch
import torch.nn as nn
import torch.optim as optim# 1. Tensor:準備數據
x = torch.randn(64, 1, 28, 28) # 64張28x28灰度圖
y_true = torch.randint(0, 10, (64,)) # 10分類標簽# 2. Module:創建模型
model = nn.Sequential(nn.Flatten(),nn.Linear(28*28, 128),nn.ReLU(),nn.Linear(128, 10)
)# 3. Autograd:訓練循環
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)# 前向傳播 → 計算損失 → 反向傳播 → 更新參數
optimizer.zero_grad() # 清零梯度(Autograd)
logits = model(x) # 前向傳播(Module)
loss = criterion(logits, y_true) # 計算損失(Tensor)
loss.backward() # 反向傳播(Autograd)
optimizer.step() # 更新參數(Tensor)print(f"訓練完成!損失值: {loss.item():.4f}")
5.2 構建一個簡單線性回歸模型 y=wx+b
我們將構建一個簡單的線性回歸模型 y = wx + b
,并模擬一個真實的訓練過程。
- 創建一個簡單的線性模型(
nn.Module
)。 - 使用真實數據和模型預測來手動計算損失(均方誤差)。
- 調用
loss.backward()
觸發自動微分。 - 觀察模型參數(權重
w
和偏置b
)的.grad
屬性,看到梯度的產生。
import torch
import torch.nn as nn# ----------------------------------------
# 第一步:準備虛擬數據 (y = 3x + 2)
# ----------------------------------------
# 我們假設真實的世界關系是 y = 3x + 2
# 我們生成一些帶點“噪聲”的數據來模擬真實情況# 創建輸入 x (形狀: [5, 1])
x = torch.tensor([[1.0], [2.0], [3.0], [4.0], [5.0]]) # 5個樣本
print("輸入 x:")
print(x)# 創建真實標簽 y_true (形狀: [5, 1])
true_w = 3.0
true_b = 2.0
y_true = true_w * x + true_b + torch.randn_like(x) * 0.1 # 加點小噪聲
print("\n真實標簽 y_true (y ≈ 3x + 2):")
print(y_true)# ----------------------------------------
# 第二步:定義模型 (nn.Module)
# ----------------------------------------
# 我們創建一個非常簡單的模型,它就是一個線性層class SimpleLinear(nn.Module):def __init__(self):super().__init__()# nn.Linear(in_features, out_features) -> y = Wx + b# 因為我們只有一個輸入和一個輸出,所以是 Linear(1, 1)self.linear = nn.Linear(1, 1)def forward(self, x):return self.linear(x)# 實例化模型
model = SimpleLinear()
print("\n模型結構:")
print(model)# 打印初始參數 (模型剛開始是“瞎猜”的)
print("\n訓練前的模型參數:")
print(f"權重 w: {model.linear.weight.item():.3f}") # .item() 取出單個數值
print(f"偏置 b: {model.linear.bias.item():.3f}")
# 你會發現初始的 w 和 b 是隨機的,和 3.0, 2.0 差很遠# ----------------------------------------
# 第三步:前向傳播 (Forward Pass)
# ----------------------------------------
# 讓模型對輸入 x 進行預測model.train() # 確保模型在訓練模式
y_pred = model(x) # 調用 forward 方法print("\n模型預測 y_pred (訓練前):")
print(y_pred)# ----------------------------------------
# 第四步:手動計算損失 (Loss)
# ----------------------------------------
# 我們使用最簡單的均方誤差 (MSE) 損失
# loss = (1/N) * Σ (y_true - y_pred)2# 手動計算損失
loss = torch.mean((y_true - y_pred) ** 2)print(f"\n訓練前的損失 (MSE): {loss.item():.4f}")# ----------------------------------------
# 第五步:反向傳播 (Backward Pass) 和 觀察梯度
# ----------------------------------------
# 這是最關鍵的一步!
# 在反向傳播之前,必須先將梯度清零,否則梯度會累積
model.zero_grad() # 等價于 optimizer.zero_grad()# 執行反向傳播
loss.backward()# 🔥 現在,我們來“親眼”看看梯度產生了!
print("\n" + "="*50)
print("調用 loss.backward() 后,模型參數的梯度:")
print("="*50)# 查看權重 w 的梯度
w_grad = model.linear.weight.grad
print(f"權重 w 的梯度 (.grad): {w_grad.item():.4f}")
# 這個梯度告訴我們:為了減小損失,w 應該增加還是減少?# 查看偏置 b 的梯度
b_grad = model.linear.bias.grad
print(f"偏置 b 的梯度 (.grad): {b_grad.item():.4f}")
# 同理,這個梯度指導 b 如何更新# ----------------------------------------
# 第六步:(可選)更新參數
# ----------------------------------------
# 雖然練習目標不包括更新,但我們可以手動模擬一下
# 這就是優化器(如SGD)做的事情:參數 = 參數 - 學習率 * 梯度learning_rate = 0.01# 手動更新權重
with torch.no_grad(): # 更新參數時不需要記錄梯度model.linear.weight -= learning_rate * w_gradmodel.linear.bias -= learning_rate * b_gradprint("\n" + "="*50)
print("一次梯度下降更新后的模型參數:")
print("="*50)
print(f"更新后的權重 w: {model.linear.weight.item():.3f}")
print(f"更新后的偏置 b: {model.linear.bias.item():.3f}")
print("現在參數更接近真實的 3.0 和 2.0 了嗎?")
在這個Demo中,三大件協作如下:
(數據)↓┌─────────────┐│ Tensor │ ← 存放 x, y, w, b 這些數字└─────────────┘↓ (輸入)┌─────────────┐│ Module │ ← 定義 y = w*x + b 這個計算公式└─────────────┘↓ (輸出預測 y_pred)┌─────────────┐│ 計算損失 │ ← 比較 y_pred 和 真實 y 的差距└─────────────┘↓ (標量 loss)┌─────────────┐│ Autograd │ ← 調用 loss.backward(),自動算出 w 和 b 的梯度└─────────────┘↓ (梯度 dw, db)┌─────────────┐│ 優化器 │ ← 用梯度更新 w 和 b,讓它們更接近真實值└─────────────┘
5.3 Demo:搭建一個簡單的多層感知機
Transformer模型中的“前饋網絡(Feed-Forward Network)”部分,其本質就是一個多層感知機(MLP)。
讓我們仿照教程,搭建搭建一個簡單的多層感知機:手寫數字分類MLP。
import torch
import torch.nn.functional as F
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import numpy as np # =============================================================================
# 1. Tensor:數據的容器
# =============================================================================
"""
Tensor 是 PyTorch 中存儲和操作數據的核心結構。
它不僅是多維數組,還能追蹤梯度,是深度學習的“通用貨幣”。
""" # 演示:Tensor 的基本用途
print("=== 1. Tensor 演示 ===") # 創建一個需要梯度追蹤的張量
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
print(f"初始張量: {x} | 追蹤梯度: {x.requires_grad}") # 執行運算(構建計算圖)
y = x * 2 + 3
z = y.mean()
print(f"中間結果 y: {y} | 最終標量 z: {z}") # 反向傳播
z.backward()
print(f"梯度 dz/dx: {x.grad}") # 應為 [2/3, 2/3, 2/3]
# GPU 加速(如果可用)
if torch.cuda.is_available(): x_gpu = x.to('cuda') print(f"GPU張量: {x_gpu} | 設備: {x_gpu.device}")
else: print("GPU不可用,使用CPU") # =============================================================================
# 2. Module:計算邏輯的藍圖(手動實現)
# =============================================================================
"""
我們不使用 nn.Module 和 nn.Linear,
而是手動創建權重和偏置,并定義前向傳播函數。
""" print("\n=== 2. 手動實現 MLP(無 nn.Module / nn.Linear)===") # 超參數
input_size = 784 # 28x28 圖像展平
hidden_size = 128 # 隱藏層大小
output_size = 10 # MNIST 10 類
learning_rate = 0.01 # 學習率
num_epochs = 3 # 訓練輪數# 手動初始化參數(替代 nn.Linear)
torch.manual_seed(42)
W1 = torch.randn(input_size, hidden_size) * 0.01
b1 = torch.zeros(hidden_size)
W2 = torch.randn(hidden_size, output_size) * 0.01
b2 = torch.zeros(output_size) # 開啟梯度追蹤
W1.requires_grad_(True)
b1.requires_grad_(True)
W2.requires_grad_(True)
b2.requires_grad_(True) print(f"W1: {W1.shape}, b1: {b1.shape}")
print(f"W2: {W2.shape}, b2: {b2.shape}") # 定義前向傳播函數(替代 forward)
def forward(x): """ 手動實現前向傳播 :param x: 輸入張量 (B, 784) :return: logits (B, 10) """ z1 = x @ W1 + b1 # 第一層線性變換 a1 = F.relu(z1) # 激活函數 z2 = a1 @ W2 + b2 # 第二層線性變換 return z2 # =============================================================================
# 3. Autograd:自動微分引擎
# =============================================================================
"""
Autograd 會自動追蹤所有操作,調用 loss.backward() 即可計算梯度。
我們手動實現訓練循環,替代 optimizer.step() 和 optimizer.zero_grad()""" print("\n=== 3. 數據加載與訓練 ===") # 數據預處理
transform = transforms.ToTensor() # 加載 MNIST 數據集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False) print(f"訓練集大小: {len(train_dataset)}")
print(f"測試集大小: {len(test_dataset)}") # 訓練循環
train_losses = [] for epoch in range(num_epochs): epoch_loss = 0.0 count = 0 for x_batch, y_batch in train_loader: # 展平圖像: (B, 1, 28, 28) -> (B, 784) x_batch = x_batch.view(x_batch.size(0), -1) # 前向傳播 logits = forward(x_batch) loss = F.cross_entropy(logits, y_batch) # 使用函數式 API # 反向傳播 loss.backward() # 手動更新參數(替代 optimizer.step()) with torch.no_grad(): W1 -= learning_rate * W1.grad b1 -= learning_rate * b1.grad W2 -= learning_rate * W2.grad b2 -= learning_rate * b2.grad # 清零梯度(替代 optimizer.zero_grad()) W1.grad.zero_() b1.grad.zero_() W2.grad.zero_() b2.grad.zero_() epoch_loss += loss.item() count += 1 avg_loss = epoch_loss / count train_losses.append(avg_loss) print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}") # =============================================================================
# 4. 模型評估
# =============================================================================
print("\n=== 4. 模型評估 ===") correct = 0
total = 0
with torch.no_grad(): # 推理時關閉梯度 for x_batch, y_batch in test_loader: x_batch = x_batch.view(x_batch.size(0), -1) logits = forward(x_batch) _, predicted = torch.max(logits, 1) total += y_batch.size(0) correct += (predicted == y_batch).sum().item() accuracy = 100 * correct / total
print(f"測試準確率: {accuracy:.2f}%") # =============================================================================
# 5. Demo:線性回歸(觀察梯度生成過程)
# =============================================================================
print("\n=== 5. 線性回歸 Demo:觀察梯度如何生成 ===") # 生成虛擬數據:y = 3x + 2 + noise
x_reg = torch.tensor([[1.0], [2.0], [3.0], [4.0], [5.0]])
true_w, true_b = 3.0, 2.0
y_true = true_w * x_reg + true_b + torch.randn_like(x_reg) * 0.1 # 初始化參數
w = torch.randn(1, 1, requires_grad=True)
b = torch.zeros(1, requires_grad=True) print(f"真實參數: w={true_w}, b={true_b}")
print(f"初始參數: w={w.item():.3f}, b={b.item():.3f}") # 前向傳播
y_pred = x_reg @ w + b
loss = F.mse_loss(y_pred, y_true) # 反向傳播
loss.backward() print(f"\n調用 loss.backward() 后:")
print(f"權重 w 的梯度: {w.grad.item():.4f}")
print(f"偏置 b 的梯度: {b.grad.item():.4f}") print("\n梯度方向正確:w 的梯度為正,說明當前 w 偏小,應增大") # =============================================================================
# 6. 總結口訣
# =============================================================================
print("\n" + "="*60)
print("🎯 PyTorch 三件套核心口訣")
print("="*60)
print("Tensor 存數據,Module 定流程,Autograd 算梯度")
print("三件套合訓練成,手動實現才真懂!")
print("="*60)
概念補充:
- 超參數:一般指一些模型的配置參數,想當于模型的學習規則。
超參數 | 說明 | 為什么重要 |
---|---|---|
學習率 (Learning Rate,?lr ) | 每次更新參數時的“步長” | 太大:學得快但可能錯過最優解 太小:學得慢,可能卡住 |
批次大小 (Batch Size) | 一次訓練使用的樣本數量 | 太大:內存壓力大,泛化可能差 太小:訓練不穩定 |
訓練輪數 (Epochs) | 整個數據集被完整遍歷的次數 | 太少:欠擬合 太多:過擬合(死記硬背) |
隱藏層大小 (Hidden Size) | 神經網絡中隱藏層的神經元數量 | 決定了模型的“容量” 太大:容易過擬合 太小:無法學習復雜模式 |
網絡層數 (Number of Layers) | 有多少個隱藏層 | 層數多 → “深度網絡”,能學更復雜特征,但也更難訓練 |
優化器類型 | 使用 SGD、Adam 還是 RMSprop | 不同優化器收斂速度和穩定性不同 |
正則化參數 | 如 Dropout 概率、L2 權重衰減 | 用于防止過擬合 |
刻意練習
Q:如何給MLP增加一個隱藏層?