下面我將詳細介紹使用 PyTorch 處理 MNIST 手寫數字數據集的完整流程,包括數據加載、模型定義、訓練和評估,并解釋每一行代碼的含義和注意事項。
整個流程可以分為五個主要步驟:準備工作、數據加載與預處理、模型定義、模型訓練和模型評估。
# MNIST手寫數字數據集完整處理流程
# 包含數據加載、模型定義、訓練和評估的全步驟# 1. 導入必要的庫
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
import torchvision.datasets as datasets
import torchvision.transforms as transforms
import matplotlib.pyplot as plt# 2. 設置超參數
batch_size = 64 # 每次訓練的樣本數量
learning_rate = 0.001 # 學習率
num_epochs = 5 # 訓練輪數
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 注意:如果有GPU,會使用cuda加速訓練,否則使用CPU# 3. 數據預處理與加載
# 定義數據變換:將圖像轉為Tensor并標準化
transform = transforms.Compose([transforms.ToTensor(), # 轉換為Tensor格式,像素值從0-255歸一化到0-1# 標準化處理:均值為0.1307,標準差為0.3081(MNIST數據集的統計特性)transforms.Normalize((0.1307,), (0.3081,))
])# 加載訓練集
train_dataset = datasets.MNIST(root='./data', # 數據保存路徑train=True, # True表示加載訓練集download=True, # 如果數據不存在則自動下載transform=transform # 應用上面定義的數據變換
)# 加載測試集
test_dataset = datasets.MNIST(root='./data',train=False, # False表示加載測試集download=True,transform=transform
)# 創建數據加載器,用于批量加載數據
train_loader = DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True # 訓練時打亂數據順序
)test_loader = DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=False # 測試時不需要打亂順序
)# 4. 可視化樣本數據(可選,用于理解數據)
def show_samples():# 獲取一些隨機的訓練樣本dataiter = iter(train_loader)images, labels = next(dataiter)# 顯示6個樣本plt.figure(figsize=(10, 4))for i in range(6):plt.subplot(1, 6, i+1)plt.imshow(images[i].numpy().squeeze(), cmap='gray')plt.title(f'Label: {labels[i].item()}')plt.axis('off')plt.show()# 調用函數顯示樣本
show_samples()# 5. 定義神經網絡模型
class SimpleCNN(nn.Module):def __init__(self):super(SimpleCNN, self).__init__()# 第一個卷積塊:卷積層 + 激活函數 + 池化層self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)self.relu1 = nn.ReLU()self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)# 第二個卷積塊self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)self.relu2 = nn.ReLU()self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)# 全連接層self.fc1 = nn.Linear(7 * 7 * 64, 128) # 經過兩次池化后,28x28變為7x7self.relu3 = nn.ReLU()self.fc2 = nn.Linear(128, 10) # 10個輸出,對應0-9十個數字def forward(self, x):# 前向傳播過程x = self.pool1(self.relu1(self.conv1(x)))x = self.pool2(self.relu2(self.conv2(x)))x = x.view(-1, 7 * 7 * 64) # 展平操作x = self.relu3(self.fc1(x))x = self.fc2(x)return x# 初始化模型并移動到設備上
model = SimpleCNN().to(device)# 6. 定義損失函數和優化器
criterion = nn.CrossEntropyLoss() # 交叉熵損失,適合分類問題
optimizer = optim.Adam(model.parameters(), lr=learning_rate) # Adam優化器# 7. 訓練模型
def train_model():# 記錄訓練過程中的損失和準確率train_losses = []train_accuracies = []# 開始訓練model.train() # 設置為訓練模式for epoch in range(num_epochs):running_loss = 0.0correct = 0total = 0# 遍歷訓練數據for i, (images, labels) in enumerate(train_loader):# 將數據移動到設備上images = images.to(device)labels = labels.to(device)# 清零梯度optimizer.zero_grad()# 前向傳播outputs = model(images)loss = criterion(outputs, labels)# 反向傳播和優化loss.backward() # 計算梯度optimizer.step() # 更新參數# 統計損失和準確率running_loss += loss.item()# 計算預測結果_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()# 每100個批次打印一次信息if (i + 1) % 100 == 0:print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], 'f'Loss: {running_loss/100:.4f}, Accuracy: {100*correct/total:.2f}%')running_loss = 0.0# 記錄每個epoch的平均損失和準確率epoch_loss = running_loss / len(train_loader)epoch_acc = 100 * correct / totaltrain_losses.append(epoch_loss)train_accuracies.append(epoch_acc)print(f'Epoch [{epoch+1}/{num_epochs}] completed. Training Accuracy: {epoch_acc:.2f}%')print('訓練完成!')return train_losses, train_accuracies# 調用訓練函數
train_losses, train_accuracies = train_model()# 8. 繪制訓練曲線
def plot_training_curves(losses, accuracies):plt.figure(figsize=(12, 5))# 損失曲線plt.subplot(1, 2, 1)plt.plot(range(1, num_epochs+1), losses)plt.title('Training Loss')plt.xlabel('Epoch')plt.ylabel('Loss')# 準確率曲線plt.subplot(1, 2, 2)plt.plot(range(1, num_epochs+1), accuracies)plt.title('Training Accuracy')plt.xlabel('Epoch')plt.ylabel('Accuracy (%)')plt.tight_layout()plt.show()# 繪制訓練曲線
plot_training_curves(train_losses, train_accuracies)# 9. 評估模型
def evaluate_model():model.eval() # 設置為評估模式correct = 0total = 0# 不需要計算梯度with torch.no_grad():for images, labels in test_loader:images = images.to(device)labels = labels.to(device)outputs = model(images)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print(f'測試集準確率: {100 * correct / total:.2f}%')return 100 * correct / total# 評估模型在測試集上的表現
test_accuracy = evaluate_model()# 10. 可視化預測結果(可選)
def show_predictions():model.eval()dataiter = iter(test_loader)images, labels = next(dataiter)# 選擇前6個樣本進行預測images = images[:6].to(device)labels = labels[:6]with torch.no_grad():outputs = model(images)_, predicted = torch.max(outputs.data, 1)# 顯示結果plt.figure(figsize=(10, 4))for i in range(6):plt.subplot(1, 6, i+1)plt.imshow(images[i].cpu().numpy().squeeze(), cmap='gray')plt.title(f'真實: {labels[i].item()}\n預測: {predicted[i].item()}')plt.axis('off')plt.tight_layout()plt.show()# 顯示預測結果
show_predictions()# 11. 保存模型(可選)
torch.save(model.state_dict(), 'mnist_cnn_model.pth')
print("模型已保存為 'mnist_cnn_model.pth'")
代碼詳細解釋與注意事項
1. 準備工作
- 我們導入了 PyTorch 的核心庫以及處理圖像所需的工具
device
設置會自動檢測是否有可用的 GPU,如果有則使用 GPU 加速訓練,否則使用 CPU
2. 數據加載與預處理
數據變換 (transforms):
ToTensor()
將圖像從 PIL 格式轉換為 PyTorch 的 Tensor 格式,并將像素值從 0-255 歸一化到 0-1 范圍Normalize()
進行標準化,使用的均值和標準差是 MNIST 數據集的統計特性,這有助于模型更快收斂
數據集加載:
datasets.MNIST
會自動下載數據(如果本地沒有)并加載train=True
加載訓練集(60,000 張圖片),train=False
加載測試集(10,000 張圖片)
DataLoader:
- 用于批量加載數據,支持自動打亂數據順序
batch_size=64
表示每次處理 64 張圖片- 訓練時
shuffle=True
打亂數據順序,測試時shuffle=False
保持順序
3. 模型定義
我們定義了一個簡單的卷積神經網絡 (SimpleCNN),包含:
- 兩個卷積塊:每個卷積塊由卷積層、ReLU 激活函數和池化層組成
- 兩個全連接層:最后一層輸出 10 個值,對應 0-9 十個數字的預測概率
卷積操作的作用:
- 提取圖像的局部特征,如邊緣、紋理等
- 池化層用于降低特征圖尺寸,減少計算量
4. 模型訓練
損失函數:使用
CrossEntropyLoss
,適合多分類問題優化器:使用
Adam
優化器,比傳統的 SGD 收斂更快訓練過程中的關鍵步驟:
- 清零梯度:
optimizer.zero_grad()
- 前向傳播:計算模型輸出和損失
- 反向傳播:
loss.backward()
計算梯度 - 更新參數:
optimizer.step()
應用梯度更新
- 清零梯度:
注意事項:
- 訓練前調用
model.train()
設置為訓練模式 - 定期打印損失和準確率,監控訓練進度
- 將數據和模型移動到相同的設備上(CPU 或 GPU)
- 訓練前調用
5. 模型評估
- 評估時調用
model.eval()
設置為評估模式,這會關閉 dropout 等訓練特有的操作 - 使用
torch.no_grad()
關閉梯度計算,節省內存并加速計算 - 計算測試集上的準確率,評估模型的泛化能力
6. 常見問題與解決方法
訓練速度慢:
- 檢查是否使用了 GPU(代碼會自動檢測,但需要正確安裝 PyTorch GPU 版本)
- 嘗試調大
batch_size
(受限于 GPU 內存)
過擬合:
- 增加訓練輪數
- 添加正則化(如 Dropout)
- 增加數據增強
準確率低:
- 檢查模型結構是否合理
- 嘗試調整學習率
- 增加訓練輪數
通過這個完整流程,你可以加載 MNIST 數據集,訓練一個卷積神經網絡對手寫數字進行分類,并評估模型性能。對于初學者來說,這個例子涵蓋了深度學習的基本流程和關鍵概念,是一個很好的入門練習。