完整代碼:
# import torch
# print(torch.__version__)#1.X 1、驗證安裝的開發環境是否正確,'''
MNIST包含70,000張手寫數字圖像: 60,000張用于訓練,10,000張用于測試。
圖像是灰度的,28x28像素的,并且居中的,以減少預處理和加快運行。
'''
import torch
from torch import nn #導入神經網絡模塊,
from torch.utils.data import DataLoader #數據包管理工具,打包數據,
from torchvision import datasets #封裝了很多與圖像相關的模型,及數據集
from torchvision.transforms import ToTensor #數據轉換,張量,將其他類型的數據轉換為tensor張量,numpy array,dataframe'''下載訓練數據集(包含訓練圖片+標簽)'''
training_data = datasets.MNIST( #跳轉到函數的內部源代碼,pycharm 按下ctrl +鼠標點擊root="data",#表示下載的手寫數字 到哪個路徑。60000train=True,#讀取下載后的數據 中的 訓練集download=True,#如果你之前已經下載過了,就不用再下載transform=ToTensor(), #張量,圖片是不能直接傳入神經網絡模型
) #對于pytorch庫能夠識別的數據一般是tensor張量.
print(len(training_data))
# datasets.MNIST的參數:
# root(string): 表示數據集的根目錄,
# train(bool, optional): 如果為True,則從training.pt創建數據集,否則從test.pt創建數據集
# download(bool, optional): 如果為True,則從internet下載數據集并將其放入根目錄。如果數據集已下載,則不會再次下載
# transform(callable, optional): 接收PIL圖片并返回轉換后版本圖片的轉換函數'''下載測試數據集(包含訓練圖片+標簽) '''
test_data = datasets.MNIST(root="data",train=False,download=True,transform=ToTensor(),#Tensor是在深度學習中提出并廣泛應用的數據類型,它與深度學習框架(如 PyTorch、TensorFlow)緊密集成,方便進行神經網絡的訓練和推理。
)#NumPy 數組只能在CPU上運行。Tensor可以在GPU上運行,這在深度學習應用中可以顯著提高計算速度。
print(len(test_data))# '''展示手寫字圖片,把訓練數據集中的前59000張圖片展示一下'''
# from matplotlib import pyplot as plt
# figure = plt.figure()
# for i in range(9):#
# img, label = training_data[i+59000]#提取第59000張圖片
#
# figure.add_subplot(3, 3, i+1)#圖像窗口中創建多個小窗口,小窗口用于顯示圖片
# plt.title(label)
# plt.axis("off") # plt.show(I)#顯示矢量,
# plt.imshow(img.squeeze(), cmap="gray") #plt.imshow()將NumPy數組data中的數據顯示為圖像,并在圖形窗口中顯示該圖像
# a = img.squeeze() # img.squeeze()從張量img中去掉維度為1的。如果該維度的大小不為1則張量不會改變。#cmap="gray"表示使用灰度色彩映射來顯示圖像。這意味著圖像將以灰度模式顯示
# plt.show()'''創建數據DataLoader(數據加載器)batch_size:將數據集分成多份,每一份為batch_size個數據。優點:可以減少內存的使用,提高訓練速度。
'''
train_dataloader = DataLoader(training_data, batch_size=64)#64張圖片為一個包,1、損失函數2、GPU一次性接受的圖片個數
test_dataloader = DataLoader(test_data, batch_size=64)
for X, y in test_dataloader:#X是表示打包好的每一個數據包print(f"Shape of X [N, C, H, W]: {X.shape}")#print(f"Shape of y: {y.shape} {y.dtype}")break'''判斷當前設備是否支持GPU,其中mps是蘋果m系列芯片的GPU。'''#返回cuda,mps。CPU m1 ,m2 集顯CPU+GPU RTX3060,
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")#字符串的格式化。 CUDA驅動軟件的功能:pytorch能夠去執行cuda的命令,cuda通過GPU指令集去控制GPU
#神經網絡的模型也需要傳入到GPU,1個batchsize的數據集也需要傳入到GPU,才可以進行訓練。''' 定義神經網絡 類的繼承這種方式'''
class NeuralNetwork(nn.Module):#通過調用類的形式來使用神經網絡,神經網絡的模型,nn.moduledef __init__(self):#python基礎關于類,self類自己本身super().__init__()#繼承的父類初始化self.flatten = nn.Flatten()#展開,創建一個展開對象flattenself.hidden1 = nn.Linear(28*28, 128)#第1個參數:有多少個神經元傳入進來,第2個參數:有多少個數據傳出去前一層神經元的個數,當前本層神經元個數self.hidden2 = nn.Linear(128, 256)#為什么你要用128self.out = nn.Linear(256, 10)#輸出必需和標簽的類別相同,輸入必須是上一層的神經元個數def forward(self, x): #前向傳播,你得告訴它 數據的流向。是神經網絡層連接起來,函數名稱不能改。當你調用forward函數的時候,傳入進來的圖像數據x = self.flatten.forward(x) #圖像進行展開 self.flatten.forwardx = self.hidden1.forward(x)x = torch.relu(x) #激活函數,torch使用的relu函數 relu,tanhx = self.hidden2.forward(x)x = torch.relu(x)x = self.out.forward(x)return xmodel = NeuralNetwork().to(device)#把剛剛創建的模型傳入到Gpu
print(model)def train(dataloader, model, loss_fn, optimizer):model.train()#告訴模型,我要開始訓練,模型中w進行隨機化操作,已經更新w。在訓練過程中,w會被修改的
#pytorch提供2種方式來切換訓練和測試的模式,分別是:model.train() 和 model.eval()。
# 一般用法是:在訓練開始之前寫上model.trian(),在測試時寫上 model.eval() 。batch_size_num = 1 #統計 訓練的batch數量for X, y in dataloader: #其中batch為每一個數據的編號X, y = X.to(device), y.to(device) #把訓練數據集和標簽傳入cpu或GPUpred = model(X) #.forward可以被省略,父類中已經對此功能進行了設置。自動初始化 w權值loss = loss_fn(pred, y) #通過交叉熵損失函數計算損失值loss# Backpropagation 進來一個batch的數據,計算一次梯度,更新一次網絡optimizer.zero_grad() #梯度值清零loss.backward() #反向傳播計算得到每個參數的梯度值woptimizer.step() #根據梯度更新網絡w參數loss_value = loss.item() #從tensor數據中提取數據出來,tensor獲取損失值if batch_size_num %100 ==0:print(f"loss: {loss_value:>7f} [number:{batch_size_num}]")batch_size_num += 1def test(dataloader, model, loss_fn):size = len(dataloader.dataset)#10000num_batches = len(dataloader)#打包的數量model.eval() #測試,w就不能再更新。test_loss, correct = 0, 0 #with torch.no_grad(): #一個上下文管理器,關閉梯度計算。當你確認不會調用Tensor.backward()的時候。這可以減少計算所用內存消耗。for X, y in dataloader:X, y = X.to(device), y.to(device) #送到GPUpred = model.forward(X)test_loss += loss_fn(pred, y).item() #test_loss是會自動累加每一個批次的損失值correct += (pred.argmax(1) == y).type(torch.float).sum().item()a = (pred.argmax(1) == y) #dim=1表示每一行中的最大值對應的索引號,dim=0表示每一列中的最大值對應的索引號b = (pred.argmax(1) == y).type(torch.float)test_loss /= num_batches #能來衡量模型測試的好壞。correct /= size #平均的正確率print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")loss_fn = nn.CrossEntropyLoss() #創建交叉熵損失函數對象,因為手寫字識別中一共有10個數字,輸出會有10個結果
# L1Loss:L1損失,也稱為平均絕對誤差(Mean Absolute Error, MAE)。它計算預測值與真實值之間的絕對差值的平均值。
# NLLLoss:負對數似然損失(Negative Log Likelihood Loss)。它用于多分類問題,通常與LogSoftmax輸出層配合使用。
# NLLLoss2d:這是NLLLoss的一個特殊版本,用于處理2D圖像數據。在最新版本的PyTorch中,這個損失函數可能已經被整合到NLLLoss中,通過指定reduction參數來實現同樣的功能。
# PoissonNLLLoss:泊松負對數似然損失,用于泊松回歸問題。
# GaussianNLLLoss:高斯負對數似然損失,用于高斯分布(正態分布)的回歸問題。
# KLDivLoss:Kullback-Leibler散度損失,用于度量兩個概率分布之間的差異。
# MSELoss:均方誤差損失(Mean Squared Error Loss),計算預測值與真實值之間差值的平方的平均值。
# BCELoss:二元交叉熵損失(Binary Cross Entropy Loss),用于二分類問題。
# BCEWithLogitsLoss:結合了Sigmoid激活函數和二元交叉熵損失的損失函數,用于提高數值穩定性。
# HingeEmbeddingLoss:鉸鏈嵌入損失,用于學習非線性嵌入或半監督學習。
# MultiLabelMarginLoss:多標簽邊際損失,用于多標簽分類問題。
# SmoothL1Loss:平滑L1損失,是L1損失和L2損失(MSE)的結合,旨在避免梯度爆炸問題。
# HuberLoss:Huber損失,與SmoothL1Loss類似,但有一個可調的參數來控制L1和L2損失之間的平衡。
# SoftMarginLoss:軟邊際損失,用于二分類問題,可以看作是Hinge損失的一種軟化版本。
# CrossEntropyLoss:交叉熵損失,用于多分類問題。它結合了LogSoftmax和NLLLoss的功能。
# MultiLabelSoftMarginLoss:多標簽軟邊際損失,用于多標簽二分類問題。
# CosineEmbeddingLoss:余弦嵌入損失,用于學習非線性嵌入,通過余弦相似度來度量樣本之間的相似性。
# MarginRankingLoss:邊際排序損失,用于排序問題,如學習到排序的嵌入空間。
# MultiMarginLoss:多邊際損失,用于多分類問題,旨在優化分類邊界的邊際。
# TripletMarginLoss:三元組邊際損失,用于學習嵌入空間中的距離度量,通常用于人臉識別或圖像檢索等任務。
# TripletMarginWithDistanceLoss:這是TripletMarginLoss的一個變體,允許使用自定義的距離函數。
# CTCLoss:連接時序分類損失(Connectionist Temporal Classification Loss),用于序列到序列的學習問題,特別是當輸出序列的長度不固定時(如語音識別)。#一會改成adam優化器 梯度下降
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)#創建一個優化器,SGD為隨機梯度下降算法
# #params:要訓練的參數,一般我們傳入的都是model.parameters()。
# #lr:learning_rate學習率,也就是步長。#loss表示模型訓練后的輸出結果與 樣本標簽的差距。如果差距越小,就表示模型訓練越好,越逼近于真實的模型。# train(train_dataloader, model, loss_fn, optimizer)#訓練1次完整的數據,多輪訓練,
# test(test_dataloader, model, loss_fn)epochs = 10 #到底選擇多少呢?
for t in range(epochs):print(f"Epoch {t+1}\n-------------------------------")train(train_dataloader, model, loss_fn, optimizer)#10次訓練
print("Done!")
test(test_dataloader, model, loss_fn)# # #分析sigmiod,relu
# # # sgd,Adam
按代碼模塊進行解析:
第一部分:環境驗證與數據加載
# import torch
# print(torch.__version__)#1.X 1、驗證安裝的開發環境是否正確,
注釋掉了,用于檢查 PyTorch 是否安裝成功。
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
導入 PyTorch 核心庫及相關模塊:
nn
:構建神經網絡。DataLoader
:批量加載數據。datasets
:內置數據集(如 MNIST)。ToTensor
:將圖片轉為 Tensor 格式。
training_data = datasets.MNIST(root="data",train=True,download=True,transform=ToTensor(),
)
下載 訓練集(60,000 張圖):
root="data"
:保存到本地data/
文件夾。transform=ToTensor()
:將圖片轉為 Tensor(灰度值歸一化到 [0, 1])。
test_data = datasets.MNIST(root="data",train=False,download=True,transform=ToTensor(),
)
下載 測試集(10,000 張圖)。
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
使用
DataLoader
將數據打包成 批次(每批 64 張圖),方便訓練。
?第二部分:設備選擇與模型定義
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
自動選擇設備:
優先使用 NVIDIA GPU(cuda);
其次 Apple 芯片(mps);
最后回退到 CPU。
class NeuralNetwork(nn.Module):def __init__(self):super().__init__()self.flatten = nn.Flatten()self.hidden1 = nn.Linear(28*28, 128)self.hidden2 = nn.Linear(128, 256)self.out = nn.Linear(256, 10)def forward(self, x):x = self.flatten(x)x = torch.relu(self.hidden1(x))x = torch.relu(self.hidden2(x))x = self.out(x)return x
定義一個 三層全連接神經網絡:
輸入層:28×28 = 784 像素;
隱藏層1:128 個神經元;
隱藏層2:256 個神經元;
輸出層:10 個類別(0~9 數字);
激活函數:ReLU。
model = NeuralNetwork().to(device)
將模型遷移到 GPU(或 CPU)。
第三部分:訓練與測試函數
def train(dataloader, model, loss_fn, optimizer):model.train()for batch, (X, y) in enumerate(dataloader):X, y = X.to(device), y.to(device)pred = model(X)loss = loss_fn(pred, y)optimizer.zero_grad()loss.backward()optimizer.step()if batch % 100 == 0:print(f"loss: {loss.item():.7f} [batch {batch}]")
訓練函數:
每個批次前向傳播 → 計算損失 → 反向傳播 → 更新權重;
每 100 個批次打印一次損失值。
def test(dataloader, model, loss_fn):model.eval()test_loss, correct = 0, 0with 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()correct += (pred.argmax(1) == y).type(torch.float).sum().item()test_loss /= len(dataloader)correct /= len(dataloader.dataset)print(f"Test Accuracy: {100*correct:.2f}%, Avg loss: {test_loss:.4f}")
測試函數:
不更新權重(
model.eval()
);計算整體損失與準確率。
第四部分:損失函數與優化器
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
使用 交叉熵損失(適合多分類);
使用 Adam 優化器(比 SGD 更穩定)。
第五部分:訓練循環
epochs = 10
for t in range(epochs):print(f"Epoch {t+1}\n-------------------------------")train(train_dataloader, model, loss_fn, optimizer)
print("Done!")
test(test_dataloader, model, loss_fn)
訓練 10 輪;
每輪遍歷一次完整訓練集;
最后測試模型性能。
代碼模塊具體逐行逐句解釋:
class NeuralNetwork(nn.Module):def __init__(self):super().__init__()self.flatten = nn.Flatten()self.hidden1 = nn.Linear(28*28, 128)self.hidden2 = nn.Linear(128, 256)self.out = nn.Linear(256, 10)def forward(self, x):x = self.flatten(x)x = torch.relu(self.hidden1(x))x = torch.relu(self.hidden2(x))x = self.out(x)return x
第一部分:類定義與初始化 (__init__
方法)
class NeuralNetwork(nn.Module):
作用:聲明一個繼承自 PyTorch 基類 nn.Module
的新類
含義:所有自定義神經網絡必須繼承此類才能享受 PyTorch 的訓練/推理功能(如 model.train()
, model.eval()
)
super().__init__()
作用:調用父類 nn.Module
的構造函數
重要性:初始化模塊的必要內部結構(如參數注冊器),不可省略
self.flatten = nn.Flatten()
作用:創建一個展平層對象
功能詳解:將多維輸入(如圖像 [B, H, W])壓縮為一維向量 [B, H×W]
典型場景:連接卷積層和非全連接層時的過渡操作
self.hidden1 = nn.Linear(28*28, 128)
作用:定義第一個全連接層(又稱密集層)
參數解析:
in_features=28*28=784
:輸入特征數(對應 28×28 圖像的像素總數)out_features=128
:本層神經元數量
內部機制:自動創建權重矩陣W?
(形狀 784×128) 和偏置向量b?
(長度 128)
self.hidden2 = nn.Linear(128, 256)
作用:定義第二個全連接層
參數解析:
in_features=128
:前一層的輸出特征數out_features=256
:本層神經元數量
內部機制:自動創建權重矩陣W?
(形狀 128×256) 和偏置向量b?
(長度 256)
self.out = nn.Linear(256, 10)
作用:定義輸出層
特殊設計:
out_features=10
:對應分類任務的類別數(如 MNIST 手寫數字識別)輸出未經過激活函數(直接輸出 logits),配合交叉熵損失函數使用
第二部分:前向傳播 (forward
方法)
def forward(self, x):
作用:定義數據的前向傳播路徑
關鍵性質:每次調用 model(input)
時會自動執行此方法
x = self.flatten(x)
作用:展平輸入張量
示例:
輸入形狀
[batch_size, 28, 28]
→ 輸出形狀[batch_size, 784]
必要性:全連接層只能接受一維特征向量
x = torch.relu(self.hidden1(x))
作用:通過第一隱藏層并進行 ReLU 激活
計算過程:
線性變換:
x = W?·x + b?
ReLU 激活:
x[x<0]=0
(保留正值,引入非線性)
設計理由:解決線性模型無法擬合復雜模式的問題
x = torch.relu(self.hidden2(x))
作用:通過第二隱藏層并進行 ReLU 激活
計算過程:
線性變換:
x = W?·x + b?
ReLU 激活:同上
效果:進一步提取高階特征,增加模型表達能力
x = self.out(x)
作用:通過輸出層生成最終結果
注意:此處不添加激活函數
原因:分類任務通常在損失函數中結合 LogSoftmax(如 CrossEntropyLoss
),logits 更靈活可控
return x
作用:返回模型輸出
輸出形式:原始 logits(未歸一化的概率分數)
后續處理:通常會接入軟最大值函數(Softmax)進行概率轉換,或直接用于計算損失
def train(dataloader, model, loss_fn, optimizer):model.train()for batch, (X, y) in enumerate(dataloader):X, y = X.to(device), y.to(device)pred = model(X)loss = loss_fn(pred, y)optimizer.zero_grad()loss.backward()optimizer.step()if batch % 100 == 0:print(f"loss: {loss.item():.7f} [batch {batch}]")
第1行:model.train()
功能:將模型設置為 訓練模式
底層邏輯:
激活模型中所有適用于訓練的特殊組件。
示例:
nn.Dropout(p=0.5)
在訓練時會隨機屏蔽50%的神經元,而在推理模式(model.eval()
)下無效。
確保模型處于可學習狀態(參數注冊鉤子啟用)。
關鍵性:若省略此步,模型可能因未啟用必要層(如 Dropout)導致性能下降或錯誤。
第2行:for batch, (X, y) in enumerate(dataloader):
功能:遍歷數據集的一個完整周期(Epoch)
參數解析:
dataloader
:PyTorch 的DataLoader
對象,負責按批次加載數據。enumerate()
:同時獲取當前批次的索引batch
和數據(X, y)
。
典型輸出:X
: 輸入特征張量,形狀為[batch_size × input_dim]
(如圖像數據為[B, C, H, W]
)。y
: 目標標簽張量,形狀為[batch_size × output_dim]
(分類任務通常為 one-hot 編碼或類別索引)。
設計目的:通過迭代實現 mini-batch SGD(隨機梯度下降),逐步優化模型參數。
第3-4行:X, y = X.to(device), y.to(device)
功能:將數據遷移到指定計算設備(CPU/GPU)
底層邏輯:
device
通常是預定義的變量(如torch.device('cuda')
),表示可用的硬件資源。.to(device)
方法執行以下操作:數據搬運:將張量從 CPU 內存復制到 GPU 顯存(若
device='cuda'
)。類型匹配:自動轉換數據類型以匹配模型參數的類型(如
float32
)。
重要性:確保模型與數據在同一設備上運算,否則會拋出RuntimeError: ... not on the same device
。
注意:此操作僅影響張量的存儲位置,不改變其數值內容。
第5行:pred = model(X)
功能:執行前向傳播(Forward Propagation)
計算流程:
輸入
X
經模型各層依次變換(如線性層、激活函數、歸一化層等)。輸出
pred
是模型對輸入X
的原始預測值(Logits),尚未應用任何激活函數。
維度示例:
輸入形狀:
[batch_size, input_dim]
→ 輸出形狀:[batch_size, num_classes]
(分類任務)。
注意:此處保持線性輸出,供損失函數后續處理。
第6行:loss = loss_fn(pred, y)
功能:計算當前批次的損失值
核心機制:
loss_fn
是預定義的損失函數(如nn.CrossEntropyLoss()
或nn.MSELoss()
)。對比模型輸出
pred
與真實標簽y
,量化預測誤差。
數學本質:分類任務:交叉熵損失
L = -Σ(y * log(softmax(pred)))
。回歸任務:均方誤差
L = ||pred - y||2_2
。
作用:為反向傳播提供優化目標,指導參數更新方向。
第7行:optimizer.zero_grad()
功能:清空優化器的梯度緩存
底層邏輯:
PyTorch 采用 累積梯度 策略,每次調用
loss.backward()
會將新梯度累加到現有梯度上。此操作將所有可訓練參數的梯度置零,防止跨批次梯度混合。
必要性:若不執行此步,梯度會指數級增長,導致參數更新異常劇烈(如爆炸性梯度)。
內部實現:遍歷模型的所有可訓練參數,執行param.grad = None
。
第8行:loss.backward()
功能:執行反向傳播(Backward Propagation)
計算流程:
根據鏈式法則自動計算損失對每個參數的梯度。
梯度存儲在
param.grad
屬性中(僅存在于requires_grad=True
的參數)。
技術核心:利用自動微分系統高效計算復雜計算圖的梯度。
注意:此操作 不會立即更新參數,僅計算梯度。
第9行:optimizer.step()
功能:根據梯度更新模型參數
執行過程:
優化器(如 SGD、Adam)按照預設規則(學習率、動量等)更新參數。
SGD 示例:
param = param - lr * param.grad
。
完成一次參數更新后,梯度會被自動清零(部分優化器除外)。
效果:使模型向損失降低的方向調整參數。
注意:此操作是參數更新的唯一入口,必須在zero_grad()
之后調用。
第10-11行:if batch % 100 == 0: print(f"loss: {loss.item():.7f} [batch {batch}]")
功能:定期打印訓練進度
實現細節:
batch % 100 == 0
:每處理 100 個批次打印一次日志。loss.item()
:將張量轉換為 Python 標量(浮點數),用于格式化輸出。
輸出示例:loss: 0.1234567 [batch 100]
。
用途:監控訓練穩定性,輔助調試(如發現 NaN 或爆炸性損失)。