Pytorch搭建MyNet實現MNIST手寫數字識別

碧水云天4發表於2024-06-19

影片:https://www.bilibili.com/video/BV1Wf421B74f/?spm_id_from=333.880.my_history.page.click

1.1 Model類

import torch
import torch.nn as nn


# 改進的三層神經網路
class MyNet(nn.Module):
    def __init__(self):
        super().__init__()
        # 定義全連線層
        self.fc1 = nn.Linear(28 * 28, 256)  # 輸入層 輸入是28*28的灰度影像,輸出是256個神經元
        self.fc2 = nn.Linear(256, 128)  # 第二層,全連線層,輸入256個神經元,輸出128個神經元
        self.fc3 = nn.Linear(128, 64)  # 第三層,全連線層,輸入128個神經元,輸出64個神經元
        self.fc4 = nn.Linear(64, 10)  # 第四層,全連線層,輸入64個神經元,輸出10個類別

    def forward(self, x):
        x = torch.flatten(x, start_dim=1)  # 展平資料,方便進行全連線
        x = torch.relu(self.fc1(x))  # 第一層+ReLU啟用
        x = torch.relu(self.fc2(x))  # 第二層+ReLU啟用
        x = torch.relu(self.fc3(x))  # 第三層+ReLU啟用
        x = self.fc4(x)  # 第四層 輸出層 不需要啟用函式
        return x

2 train.py建立

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
from .model import MyNet

# 設定隨機種子
torch.manual_seed(21)

# 檢查是否有可用的GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device:{device}")

transform = transforms.Compose([
    transforms.ToTensor(),  # 將影像轉為張量
    transforms.Normalize((0.5,), (0.5,))  # 標準化影像資料,對於灰度影像,只需要一個通道的標準化
])

# 載入MNIST資料集
train_dataset = datasets.MNIST(root='../../datasets', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='../../datasets', train=False, download=True, transform=transform)

# 建立資料載入器
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

# 初始化模型並將模型移到GPU上
model = MyNet().to(device)

# 定義損失函式和最佳化器
lr = 0.001  # 學習率為0.001
criterion = nn.CrossEntropyLoss()  # 交叉熵
optimizer = optim.Adam(model.parameters(), lr=lr)  # 使用Adam最佳化器

# 儲存訓練過程中的損失和準確率
train_losses = []
train_accuracies = []
test_accuracies = []

epochs = 10
best_accuracy = 0.0  # 記錄最佳驗證集準確率
best_model_path = 'best_mnist_model.pth'

# 訓練10個epoch
for epoch in range(epochs):
    running_loss = 0.0
    corrct_train = 0  # 正確預測的數量
    total_train = 0  # 樣本總數

    # 訓練過程
    model.train()  # 設定模型為訓練模式
    for inputs, labels in train_loader:
        inputs, labels = inputs.to(device), labels.to(device)  # 將資料移動到GPU上
        optimizer.zero_grad()  # 梯度清零
        outputs = model(inputs)  # 前向傳播
        loss = criterion(outputs, labels)  # 計算損失
        loss.backward()  # 反向傳播
        optimizer.step()  # 更新引數
        running_loss += loss.item()  # 累加損失

        # 計算訓練集上的準確率
        _, predicted = torch.max(outputs, 1)  # 獲取預測結果
        total_train += labels.size(0)  # 累加樣本數量
        corrct_train += (predicted == labels).sum().item()  # 累加正確預測的數量

    # 計算訓練集上的準確率
    train_accuracy = corrct_train / total_train
    train_losses.append(running_loss / len(train_loader))  # 記錄每個 epoch 的平均損失,len(train_loader)為批次數,一個epoch結束後
    train_accuracies.append(train_accuracy)  # 記錄每個 epoch 的訓練集準確率
    print(
        f"Epoch: {epoch + 1}/{epochs}, Loss: {running_loss / len(train_loader):.4f}, Train Accuracy: {train_accuracy:.2%}")

    # 在測試集上評估模型
    model.eval()  # 設定模型為評估模式
    correct = 0  # 正確的預測數量
    total = 0  # 樣本總數

    with torch.no_grad():  # 關閉梯度計算
        for inputs, labels in test_loader:
            inputs, labels = inputs.to(device), labels.to(device)  # 將資料移動到GPU上
            outputs = model(inputs)  # 前向傳播
            _, predicted = torch.max(outputs, 1)  # 獲取預測結果
            total += labels.size(0)  # 累加樣本數量
            correct += (predicted == labels).sum().item()  # 累加正確預測的數量

    # 計算測試集上的準確率
    test_accuracy = correct / total  # 記錄每個 epoch 的測試集準確率
    test_accuracies.append(test_accuracy)
    print(f"Epoch: {epoch + 1}/{epochs},Test Accuracy: {test_accuracy:.2%}")

    # 如果測試集準確率提高,儲存當前模型的權重
    if test_accuracy > best_accuracy:
        best_accuracy = test_accuracy
        torch.save(model.state_dict(), best_model_path)
        print(f"Best model saved with accuracy: {best_accuracy:.2%}")

print(f"Best Accuracy on test set: {best_accuracy:.2%}")

# 繪製並儲存損失和準確率曲線
plt.figure(figsize=(12, 5))

# 繪製損失曲線
plt.subplot(1, 2, 1)  # 選擇第一個子圖
plt.plot(train_losses, label='Training Loss')  # 傳入資料、設定標籤為Training Loss
plt.xlabel('Epoch')  # x軸資料
plt.ylabel('Loss')
plt.title('Training Loss over Epochs')  # 設定標題
plt.legend()  # 新增圖例
plt.grid(True)  # 新增網格

# 繪製訓練集和測試集準確率曲線
plt.subplot(1, 2, 2)
plt.plot(train_accuracies, label='Train Accuracy')
plt.plot(test_accuracies, label='Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Train and Test Accuracy over Epochs')
plt.legend()
plt.grid(True)

# 儲存影像
plt.tight_layout()
plt.savefig('loss_and_accuracy.png')
plt.show()

3 predict.py 建立

import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
from .model import MyNet

# 設定隨機種子
torch.manual_seed(21)

# 檢查是否有可用的GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device:{device}")

# 定義資料預處理
transform = transforms.Compose([
    transforms.ToTensor(),  # 將影像轉為張量
    transforms.Normalize((0.5,), (0.5,))  # 標準化影像資料,對於灰度影像,只需要一個通道的標準化
])

# 載入MNIST資料集
test_dataset = datasets.MNIST(root='../../datasets', train=False, download=True, transform=transform)

# 建立資料載入器
batch_size = 10
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

# 初始化模型並將模型移到GPU上
model = MyNet().to(device)

# 載入儲存的權重
best_model_path = 'best_mnist_model.pth'
model.load_state_dict(torch.load(best_model_path))

# 設定模型為評估模式
model.eval()

# 從測試集中取10張圖片
examples = enumerate(test_loader)  # 從 test loader 中獲取一個帶索引的迭代器
# #使用 next()函式從迭代器中獲取下一個批次的資料。
# next()返回一個元組,第一個元素是批次的索引(batch idx),
# #第二個元素是一個包含輸入資料和目標標籤的元組(example_data,example_targets)
batch_idx, (example_data, example_targets) = next(examples)
# 將資料移動到指定的裝置上(例如,CPU或GPU),
# 以便利用裝置的計算能力進行進一步的操作(如模型推理或訓練)
example_data, example_targets = example_data.to(device), example_targets.to(device)

# 進行推理
with torch.no_grad():  # 使用 torch.no_grad()上下文管理器,表示在此上下文中不計算梯度。
    outputs = model(example_data)  # 使用模型對 example_data 進行前向傳播,獲取輸出結果。
    # 使用 torch.max()函式,獲取 outputs 張量中每行的最大值及其索引。
    # 1 表示在第一個維度上(通常是類別維度)尋找最大值。
    _, predicted = torch.max(outputs, 1)

# 視覺化並顯示預測結果
fig, axes = plt.subplots(1, 10, figsize=(15, 2))  # 建立一個包含1行10列的子圖(subplots)的圖形,並指定每個子圖的大小
for i in range(10):
    axes[i].imshow(example_data[i].cpu().squeeze(), cmap='gray')  # 在指定的子圖(axes[i])中顯示影像
    axes[i].set_title(f"Pred:{predicted[i].item()}")  # 在每個子圖(axes[i])上設定標題,標題內容顯示模型對該樣本的預測結果
    axes[i].axis('off')  # 關閉第 i個子圖(axes[i])的座標軸顯示

plt.show()
# 輸出批次中部分樣本的真實標籤和模型的預測標籤
print(f"True labels: {example_targets.cpu().numpy()}")
print(f"Predicted labels: {predicted.cpu().numpy()}")

相關文章