目錄
一、PyTorch 核心優勢與框架定位
二、實戰基礎:核心庫與數據準備
1. 關鍵庫導入與功能說明
2. MNIST 數據集加載與可視化
(1)數據集下載與封裝
(2)數據集可視化(可選)
3. DataLoader:批次數據加載
三、模型構建:全連接神經網絡設計
1. 模型結構設計
2. 模型代碼實現與設備部署
四、訓練與測試:模型學習流程
1. 核心組件配置
(1)損失函數:CrossEntropyLoss
(2)優化器:Adam
(3)訓練輪次:epochs=10
2. 訓練函數實現
3. 測試函數實現
4. 啟動訓練與測試
五、訓練結果與優化方向
1. 預期訓練結果
2. 模型優化方向(提升準確率至 99%+)
六、總結
在深度學習領域,PyTorch 憑借動態圖機制、簡潔 API 和靈活的模型構建方式,成為初學者入門與科研落地的優選框架。本文以 MNIST 手寫數字識別任務為核心,結合完整 PyTorch 代碼與關鍵理論知識,從數據加載、模型構建到訓練測試,帶你掌握 PyTorch 深度學習實戰的核心流程。
一、PyTorch 核心優勢與框架定位
在開始實戰前,先明確 PyTorch 在主流深度學習框架中的獨特價值。相較于 Caffe(配置繁瑣、更新停滯)、TensorFlow(1.x 版本冗余、2.x 兼容性問題),PyTorch 具備兩大核心優勢:
- 低門檻易上手:支持動態圖調試,代碼邏輯與 Python 原生語法高度一致,無需復雜配置即可搭建模型;
- 靈活性與效率平衡:既支持快速原型驗證(如本文 MNIST 任務),也能通過 CUDA 無縫對接 GPU,滿足大規模訓練需求。
正如 PyTorch 官方設計理念:“讓科研與工程的邊界更模糊”,其 “代碼即模型” 的特性,非常適合從基礎任務入門深度學習。
二、實戰基礎:核心庫與數據準備
1. 關鍵庫導入與功能說明
首先導入任務所需的 PyTorch 核心庫,各庫功能與后續作用如下表:
導入語句 | 核心功能 | 實戰作用 |
---|---|---|
import torch | PyTorch 核心庫,提供 Tensor 與自動求導 | 數據存儲、梯度計算基礎 |
from torch import nn | 神經網絡模塊,封裝各類層與損失函數 | 定義全連接層、展平層等 |
from torch.utils.data import DataLoader | 數據批次管理工具 | 按批次加載數據,避免顯存溢出 |
from torchvision import datasets | 圖像數據集封裝,含 MNIST 等經典數據集 | 一鍵下載手寫數字數據集 |
from torchvision.transforms import ToTensor | 圖像數據轉換工具 | 將 PIL 圖像轉為 PyTorch 可處理的 Tensor |
import matplotlib.pyplot as plt | 圖像可視化庫 | 查看數據集中的手寫數字樣本 |
導入代碼與基礎配置(解決中文顯示問題)如下:
# 導入必要的庫
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
from matplotlib import pyplot as plt
import matplotlib# 配置matplotlib:解決中文亂碼和負號顯示問題
matplotlib.rcParams["font.family"] = ["SimHei"]
matplotlib.rcParams['axes.unicode_minus'] = False# 驗證PyTorch環境(打印版本,確認安裝成功)
print("PyTorch版本:", torch.__version__)
2. MNIST 數據集加載與可視化
MNIST 是手寫數字識別的 “入門數據集”,包含 60000 張訓練樣本和 10000 張測試樣本,每張圖片為 28×28 像素的灰度圖,標簽為 0-9 的整數。
(1)數據集下載與封裝
通過datasets.MNIST
類自動下載并封裝數據,核心參數說明:
root="data"
:數據保存路徑(本地不存在時自動創建);train=True/False
:區分訓練集(True)與測試集(False);download=True
:本地無數據時從 PyTorch 官網自動下載;transform=ToTensor()
:將原始圖像(PIL 格式)轉為 Tensor,同時歸一化像素值到[0,1]
區間(模型訓練需標準化輸入)。
代碼實現
# 下載并加載訓練集(用于模型學習)
training_data = datasets.MNIST(root="data",train=True,download=True,transform=ToTensor()
)# 下載并加載測試集(用于驗證模型泛化能力)
test_data = datasets.MNIST(root="data",train=False,download=True,transform=ToTensor()
)# 查看數據集規模(MNIST標準規模:訓練6萬、測試1萬)
print("訓練集樣本數量:", len(training_data)) # 輸出:60000
print("測試集樣本數量:", len(test_data)) # 輸出:10000
(2)數據集可視化(可選)
通過matplotlib
查看數據集中的手寫數字,驗證 “圖像 - 標簽” 對應關系:
# 創建3×3網格的圖像窗口,大小8×8英寸
figure = plt.figure(figsize=(8, 8))
for i in range(9):# 獲取第59080+i張樣本(圖像Tensor + 標簽)img, label = training_data[i + 59080]# 添加子圖(3行3列,第i+1個位置)figure.add_subplot(3, 3, i + 1)# 設置子圖標題(顯示標簽)plt.title(f"標簽: {label}")# 關閉坐標軸,避免干擾plt.axis("off")# 顯示圖像:squeeze()去除維度為1的通道([1,28,28]→[28,28]),cmap="gray"用灰度顯示plt.imshow(img.squeeze(), cmap="gray")
# 彈出窗口展示圖像
plt.show()
運行后將看到 9 張手寫數字圖片及對應標簽,如下:
?
3. DataLoader:批次數據加載
直接遍歷training_data
會逐樣本輸入模型,效率低且易導致 GPU 顯存溢出。DataLoader
的核心作用是按批次打包數據,平衡訓練效率與硬件資源。
核心參數與代碼實現:
batch_size = 64 # 每批次加載64張圖片(經驗值,可根據GPU顯存調整)# 訓練集DataLoader:按批次加載,支持多線程(默認)
train_dataloader = DataLoader(training_data, batch_size=batch_size)
# 測試集DataLoader:與訓練集批次大小一致,僅用于推理(無參數更新)
test_dataloader = DataLoader(test_data, batch_size=batch_size)# 驗證DataLoader輸出的數據形狀(確保符合模型輸入要求)
for X, y in test_dataloader:# 圖像形狀:[N, C, H, W] = [批次大小, 通道數, 高度, 寬度]print(f"圖像Tensor形狀 [N, C, H, W]: {X.shape}") # 輸出:torch.Size([64, 1, 28, 28])# 標簽形狀:一維Tensor,長度=批次大小,類型為整數print(f"標簽形狀與類型: {y.shape} {y.dtype}") # 輸出:torch.Size([64]) torch.int64break # 僅查看第一個批次,避免循環打印
關鍵說明:PyTorch 模型輸入需為[N, C, H, W]
格式的 Tensor,其中N=batch_size
(64)、C=1
(灰度圖單通道)、H=28
、W=28
,上述輸出驗證了數據格式正確性。
三、模型構建:全連接神經網絡設計
在 PyTorch 中,自定義神經網絡需繼承nn.Module
基類,并實現兩個核心方法:
__init__
:定義模型的層結構(如展平層、全連接層);forward
:定義數據在模型中的前向傳播路徑(必須命名為forward
,PyTorch 會自動調用)。
1. 模型結構設計
針對 MNIST 任務,設計 “展平層 + 2 個隱藏層 + 1 個輸出層” 的全連接網絡,結構如下:
- 展平層(nn.Flatten):將
[64,1,28,28]
的 2D 圖像轉為[64,784]
的 1D 向量(全連接層僅接受 1D 輸入); - 隱藏層 1(nn.Linear (784, 128)):輸入 784 個神經元(28×28 像素),輸出 128 個神經元,進行線性變換;
- 激活函數(torch.sigmoid):引入非線性關系(若沒有激活函數,多層網絡等價于單層,無法擬合復雜特征);
- 隱藏層 2(nn.Linear (128, 256)):輸入 128 個神經元,輸出 256 個神經元,進一步提取特征;
- 輸出層(nn.Linear (256, 10)):輸入 256 個神經元,輸出 10 個神經元(對應 0-9 共 10 個類別,輸出值為各分類的預測分數)。
2. 模型代碼實現與設備部署
PyTorch 支持 CPU/GPU 自動切換,優先使用 GPU 可顯著提升訓練速度(需 NVIDIA 顯卡支持 CUDA)。代碼中通過torch.cuda.is_available()
判斷 GPU 是否可用,將模型與數據部署到同一設備(否則會報錯)。
完整代碼:
# 自動選擇訓練設備:CUDA(NVIDIA GPU)> MPS(蘋果M系列GPU)> CPU
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"使用訓練設備: {device}") # 輸出示例:cuda / cpu# 定義神經網絡類
class NeuralNetwork(nn.Module):def __init__(self):super().__init__() # 調用父類nn.Module的初始化方法# 1. 展平層:將2D圖像轉為1D向量self.flatten = nn.Flatten()# 2. 全連接隱藏層1:784→128self.hidden1 = nn.Linear(28 * 28, 128)# 3. 全連接隱藏層2:128→256self.hidden2 = nn.Linear(128, 256)# 4. 輸出層:256→10(10個類別)self.out = nn.Linear(256, 10)# 前向傳播:定義數據流動路徑def forward(self, x):x = self.flatten(x) # 步驟1:展平([64,1,28,28]→[64,784])x = self.hidden1(x) # 步驟2:隱藏層1線性變換x = torch.sigmoid(x) # 步驟3:sigmoid激活函數(引入非線性)x = self.hidden2(x) # 步驟4:隱藏層2線性變換x = torch.sigmoid(x) # 步驟5:再次激活x = self.out(x) # 步驟6:輸出層(預測分數)return x# 創建模型實例,并部署到指定設備(GPU/CPU)
model = NeuralNetwork().to(device)
# 打印模型結構,驗證層定義是否正確
print("\n神經網絡模型結構:")
print(model)
模型結構打印輸出(示例):
NeuralNetwork((flatten): Flatten(start_dim=1, end_dim=-1)(hidden1): Linear(in_features=784, out_features=128, bias=True)(hidden2): Linear(in_features=128, out_features=256, bias=True)(out): Linear(in_features=256, out_features=10, bias=True)
)
四、訓練與測試:模型學習流程
1. 核心組件配置
模型訓練需三大核心組件:損失函數(衡量預測誤差)、優化器(更新模型參數)、訓練輪次(遍歷訓練集的次數)。
(1)損失函數:CrossEntropyLoss
MNIST 是 10 分類任務,選擇nn.CrossEntropyLoss
(交叉熵損失),其核心作用:
- 自動將模型輸出的 “預測分數” 通過 Softmax 函數轉為概率分布;
- 計算預測概率與真實標簽(獨熱編碼)的交叉熵,量化誤差大小。
(2)優化器:Adam
優化器負責根據損失反向傳播的梯度更新模型參數(如全連接層的權重w
和偏置b
)。本文選擇torch.optim.Adam
,相比傳統 SGD(隨機梯度下降),Adam 具備 “自適應學習率” 特性,收斂速度更快,泛化能力更強。
核心參數:
model.parameters()
:傳入模型中所有可學習的參數(需更新的權重和偏置);lr=0.001
:學習率(步長),控制參數更新的幅度(過大會導致訓練震蕩,過小會導致收斂緩慢)。
(3)訓練輪次:epochs=10
1 個輪次(epoch)表示完整遍歷一次訓練集。MNIST 任務較簡單,10 輪即可達到較高準確率(97%+)。
配置代碼:
# 1. 損失函數:多分類任務用交叉熵損失
loss_fn = nn.CrossEntropyLoss()# 2. 優化器:Adam,學習率0.001
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)# 3. 訓練輪次:10輪
epochs = 10
2. 訓練函數實現
訓練函數的核心邏輯是 “遍歷所有訓練批次,完成前向傳播→損失計算→反向傳播→參數更新” 的閉環,代碼與注釋如下:
def train(dataloader, model, loss_fn, optimizer):model.train() # 開啟訓練模式:告訴模型“需要更新參數”(部分層如Dropout會生效)batch_num = 1 # 批次計數器,用于打印訓練進度# 遍歷訓練集中的每個批次(X:圖像數據,y:真實標簽)for X, y in dataloader:# 關鍵步驟:將數據部署到與模型同一設備(GPU/CPU)X, y = X.to(device), y.to(device)# 1. 前向傳播:計算模型預測結果pred = model(X) # 等價于model.forward(X),PyTorch自動調用forward# 2. 計算損失:預測值與真實標簽的誤差loss = loss_fn(pred, y)# 3. 反向傳播與參數更新(核心步驟)optimizer.zero_grad() # 梯度清零:避免前一批次的梯度累積影響當前更新loss.backward() # 反向傳播:計算每個可學習參數的梯度(損失對參數的偏導數)optimizer.step() # 參數更新:根據梯度和學習率調整權重和偏置# 每100個批次打印一次損失(監控訓練進度,判斷是否收斂)if batch_num % 100 == 1:loss_value = loss.item() # 從Tensor中提取損失數值print(f"批次 {batch_num} | 損失: {loss_value:.4f}")batch_num += 1 # 批次計數器加1
3. 測試函數實現
測試函數的核心邏輯是 “遍歷所有測試批次,計算準確率和平均損失,驗證模型泛化能力”,需注意:
- 測試時無需更新參數,因此要開啟
model.eval()
模式; - 禁用梯度計算(
with torch.no_grad()
),節省內存并加速計算。
代碼與注釋如下:
def test(dataloader, model, loss_fn):model.eval() # 開啟測試模式:告訴模型“不需要更新參數”(部分層如Dropout會關閉)size = len(dataloader.dataset) # 測試集總樣本數(10000)num_batches = len(dataloader) # 測試集總批次數(10000/64≈157)test_loss = 0 # 測試集總損失correct = 0 # 預測正確的樣本數# 禁用梯度計算:測試時無需反向傳播,大幅節省內存with torch.no_grad():for X, y in dataloader:# 數據部署到同一設備X, y = X.to(device), y.to(device)# 前向傳播:計算預測結果pred = model(X)# 累積當前批次的損失test_loss += loss_fn(pred, y).item()# 累積正確樣本數:pred.argmax(1)取預測分數最大的類別(維度1為類別維度),與y比較correct += (pred.argmax(1) == y).type(torch.float).sum().item()# 計算平均損失和準確率avg_loss = test_loss / num_batches # 平均損失=總損失/總批次數accuracy = correct / size # 準確率=正確數/總樣本數# 打印測試結果(準確率保留2位小數,損失保留4位小數)print(f"\n測試集性能:")print(f"準確率: {(100 * accuracy):.2f}% | 平均損失: {avg_loss:.4f}\n")print("-" * 50)
4. 啟動訓練與測試
遍歷每個訓練輪次,先調用train
函數完成當前輪次的訓練,再調用test
函數驗證模型在測試集上的性能:
print("=" * 50)
print("開始訓練")
print("=" * 50)for epoch in range(epochs):# 打印當前輪次信息print(f"\n輪次 {epoch + 1}/{epochs}")print("-" * 50)# 1. 訓練當前輪次(遍歷所有訓練批次)train(train_dataloader, model, loss_fn, optimizer)# 2. 測試當前輪次(驗證泛化能力)test(test_dataloader, model, loss_fn)print("=" * 50)
print("訓練結束")
print("=" * 50)
五、訓練結果與優化方向
1. 預期訓練結果
在 GPU(如 RTX 3060)上運行,10 輪訓練后輸出示例:
輪次 10/10
--------------------------------------------------
Loss: 0.0130 [number:1]
Loss: 0.0594 [number:101]
Loss: 0.0244 [number:201]
Loss: 0.0326 [number:301]
Loss: 0.0205 [number:401]
Loss: 0.0444 [number:501]
Loss: 0.0048 [number:601]
Loss: 0.0325 [number:701]
Loss: 0.0665 [number:801]
Loss: 0.0314 [number:901]測試集性能:
準確率: 97.45% | 平均損失: 0.0863--------------------------------------------------
==================================================
訓練結束
==================================================
關鍵結論:
- 訓練損失逐步下降,說明模型在持續學習;
- 測試準確率達 97.45%,說明模型具備良好的泛化能力(未過擬合)。
2. 模型優化方向
若需進一步提升性能,可嘗試以下優化手段:
- 更換激活函數:將
sigmoid
改為ReLU
(torch.relu(x)
),解決 sigmoid 在深層網絡中的梯度消失問題; - 增加網絡深度 / 寬度:添加隱藏層(如
nn.Linear(256, 512)
)或增加神經元數量(如nn.Linear(784, 256)
); - 數據增強:通過
torchvision.transforms
添加旋轉(RandomRotation(5)
)、平移(RandomAffine(5)
)等操作,提升模型抗干擾能力; - 正則化:添加
nn.Dropout(p=0.2)
層,隨機 “關閉” 部分神經元,避免過擬合; - 調整超參數:增大訓練輪次(如 20 輪)、微調學習率(如 0.0005)或批次大小(如 128)。