# 匯入必要的庫
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
# 定義一個多層感知器(MLP)類,繼承自 nn.Module
class MLP(nn.Module):
#建構函式初始化資料,建立類的例項的時候需要輸入這三個引數
def __init__(self, input_size, hidden_size, output_size):
#在建構函式中呼叫父類(nn.Module)的建構函式,確保正確地初始化父類的屬性。
super(MLP, self).__init__()
# 構建網路結構
#self.layers是一個nn.Sequential類的物件,
#layers是自己創造的一個屬性,self類似c++中的this指標
self.layers = nn.Sequential(
nn.Linear(input_size, hidden_size), #定義了一個線性層,其輸入大小為 input_size,輸出大小為 hidden_size
nn.ReLU(), #是一個 ReLU 啟用函式層,不改變大小
nn.Linear(hidden_size, output_size) #定義了另一個線性層,其輸入大小為 hidden_size,輸出大小為 output_size
)
def forward(self, x):
# 定義前向傳播
#對輸入張量 x 進行形狀變換,將其轉換為二維張量,其中第一維保留樣本數量,第二維自動計算得到以保持原有資料個數的維度。
#這一步操作通常被稱為"Flatten",用於將多維資料展平為一維資料。
x = x.view(x.size(0), -1) # Flatten the input tensor
#將flatten後的張量,透過之前在 __init__ 方法中建立的 layers 模型進行前向傳播計算
out = self.layers(x)
return out
# 超引數設定
#輸入層的大小為 784,對應於輸入資料的特徵數量
input_size = 784
#隱藏層的大小為 128,表示網路中間的隱藏單元數量
hidden_size = 128
#輸出層的大小為 10,對應於分類問題中的類別數量
output_size = 10
#學習率為 0.001,控制著在最佳化過程中權重更新的步幅大小
learning_rate = 0.001
#批處理大小為 64,表示每次迭代中所使用的樣本數量
batch_size = 64
#訓練進行的總輪數為 10,表示將對整個訓練資料集進行 10 次遍歷
num_epochs = 10
# 資料預處理
transform = transforms.Compose([
transforms.ToTensor(), #將輸入資料轉換為張量格式。將輸入資料的值從範圍 [0, 255] 歸一化到 [0, 1],並將資料型別轉換為 PyTorch 中的張量型別
transforms.Normalize((0.1307,), (0.3081,)) #對資料進行歸一化處理,對每個通道的資料進行均值和方差歸一化
])
# 載入MNIST資料集
#root 引數指定了資料集儲存的路徑,train=True 表示載入訓練集,train=False 表示載入測試集,download=True 表示如果資料集不存在,則自動從官方網站下載資料集。
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
#使用 DataLoader 類將資料集包裝成可迭代的資料載入器。
# batch_size 引數指定了每個批次的樣本數量,shuffle=True 表示在每個輪次中隨機打亂樣本的順序,shuffle=False 表示使用原始順序載入樣本。
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# 建立模型、損失函式和最佳化器物件
#使用了一個自定義的 MLP 類來建立模型物件
model = MLP(input_size, hidden_size, output_size)
#定義了交叉熵損失函式物件,用於計算模型輸出和真實標籤之間的損失
criterion = nn.CrossEntropyLoss()
#定義了最佳化器物件,使用 Adam 最佳化演算法來更新模型引數。model.parameters() 用於獲取模型的可學習引數,lr 引數指定了初始學習率
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 開始訓練
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
# 前向傳播
images=images
labels= labels
outputs = model(images)
# 計算損失
loss = criterion(outputs, labels)
# 反向傳播和最佳化
#將最佳化器中的梯度快取清零,以避免之前計算得到的梯度對當前步驟的梯度更新產生影響
optimizer.zero_grad()
#根據當前的損失值,透過自動求導(Autograd)計算出引數的梯度。損失值透過反向傳播的過程,將梯度資訊傳遞到各個引數
loss.backward()
#根據計算得到的梯度更新模型的引數。透過最佳化器呼叫該方法,可以根據指定的最佳化演算法以及學習率等引數,更新模型中的引數值
optimizer.step()
# 輸出損失資訊
if (i+1) % 100 == 0:
print(f"Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}")
# 測試模型
#將模型切換到評估模式。在評估過程中,以便在計算過程中不進行梯度計算和引數更新。這通常用於禁用一些具有隨機性的操作
model.eval()
#用於記錄預測正確的樣本數和總樣本數
correct = 0
total = 0
#建立一個環境,在這個環境中的計算不會被記錄梯度。在評估階段,我們通常不需要計算梯度,而且禁用梯度計算可以提高計算效率
with torch.no_grad():
for images, labels in test_loader:
#使用模型對輸入 images 進行預測,得到預測的輸出
outputs = model(images)
#將預測的輸出張量中的最大值和最大值所在的索引提取出來。predicted 是預測的類別標籤
_, predicted = torch.max(outputs.data, 1)
#增加總樣本數,即將當前批次中的樣本個數
total += labels.size(0)
#將預測標籤 predicted 和真實標籤 labels 逐元素進行比較,相同則為預測正確,然後將預測正確的個數累加到 correct 中
correct += (predicted == labels).sum().item()
#用pt格式儲存到本地
torch.save(model,"MLP_model.pt")
#輸出準確率
print(f"Accuracy of the model on the 10000 test images: {100 * correct / total:.2f}%")