基於PyTorch框架的多層全連線神經網路實現MNIST手寫數字分類

0214jx發表於2024-04-13

基於PyTorch框架的多層全連線神經網路實現MNIST手寫數字分類

簡單的三層全連線神經網路

匯入了PyTorch相關的庫,定義了一個名為SimpleNet的類,繼承自nn.Module,這個神經網路有三個全連線層,分別是layer1、layer2和layer3。在初始化函式__init__中,指定了輸入維度in_dim、兩個隱藏層的神經元數量n_hidden_1和n_hidden_2,以及輸出維度out_dim。每個全連線層的結構是一個線性變換。在forward函式中,輸入x透過每一層全連線層並經過啟用函式後得到輸出。最終返回神經網路的輸出。

import torch
import torch.nn as nn
from torch.autograd.variable import Variable
import numpy as np


# 定義三層全連線神經網路
class SimpleNet(nn.Module):
    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super(SimpleNet, self).__init__()
        self.layer1 = nn.Linear(in_dim, n_hidden_1)
        self.layer2 = nn.Linear(n_hidden_1, n_hidden_2)
        self.layer3 = nn.Linear(n_hidden_2, out_dim)

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x


新增啟用函式

# 新增啟用函式,增加網路的非線性
class Activation_Net(nn.Module):
    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super(Activation_Net, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Linear(in_dim, n_hidden_1), nn.ReLU(True)
        )
        self.layer2 = nn.Sequential(
            nn.Linear(n_hidden_1, n_hidden_2)
        )
        self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x
  

新增批標準化

隱藏層後面新增了批次歸一化層(Batch Normalization)。該模型的輸入維度為in_dim,第一個隱藏層的神經元數量為n_hidden_1,第二個隱藏層的神經元數量為n_hidden_2,輸出層的維度為out_dim。

在forward函式中,輸入資料x首先經過第一個隱藏層self.layer1,然後經過批次歸一化層和啟用函式,接著透過第二個隱藏層self.layer2,再次經過批次歸一化層和啟用函式,最後經過輸出層self.layer3。最終返回神經網路的輸出。

class Batch_Net(nn.Module):
    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super(Batch_Net, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Linear(in_dim, n_hidden_1),
            nn.BatchNorm1d(n_hidden_1),
            nn.ReLU(True)
        )
        self.layer2 = nn.Sequential(
            nn.Linear(n_hidden_1, n_hidden_2),
            nn.BatchNorm1d(n_hidden_2),
            nn.ReLU(True)
        )
        self.layer3 = nn.Sequential(
            nn.Linear(n_hidden_2, out_dim)
        )

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x

批標準化一般放在全連線層的後面,非線性層(啟用函式)的前面

訓練網路

  • 匯入需要用的包
import torch
from torch import nn, optim
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import net

  • 定義超引數
# 超引數 Hyper parameters
batch_size = 64
learning_rate = 1e-2
num_epochs = 20
  • 資料預處理
# 資料預處理
data_tf = transforms.Compose(
    [
        # 將圖片轉換成PyTorch中處理的物件Tensor,在轉換的過程中自動將圖片標準化,即Tensor的範圍是0~1
        transforms.ToTensor(),
        # 第一個引數是均值,第二個引數是方差,做的處理就是減均值,再除以方差
        transforms.Normalize([0.5], [0.5])
    ]
)

  • 匯入資料集
# 下載訓練集MNIST手寫數字訓練集
train_dataset = datasets.MNIST(
    root='./data',
    train=True,
    transform=data_tf,
    download=True
)
test_dataset = datasets.MNIST(
    root='./data',
    train=False,
    transform=data_tf
)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

  • 匯入網路
# 匯入網路
model = net.SimpleNet(28 * 28, 300, 100, 10)
if torch.cuda.is_available():
    model = model.cuda()

# 定義損失函式和最佳化方法
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

  • 訓練網路
# start train net
model.eval()	# 將神經網路模型設定為評估模式,這意味著模型將不會進行梯度計算和引數更新
eval_loss = 0	# 初始化評估損失和評估準確率為0
eval_acc = 0
for data in text_loader:	# 歷測試集資料載入器中的每個資料
    img, label = data		 # 從資料中獲取影像和標籤
    img = img.view(img.size(0), -1)	 #  將影像資料展平以便輸入到神經網路模型中
    if torch.cuda.is_available():
        with torch.no_grad():
            img = img.cuda()
            label = label.cuda()
    else:
        with torch.no_grad():
            img = img
            label = label
    out = model(img)	# 將影像資料輸入到神經網路模型中進行前向傳播,得到輸出
    loss = criterion(out, label)	# 計算模型輸出和標籤之間的損失
    eval_loss += loss.item() * label.size(0)
    _, pred = torch.max(out, 1)
    num_correct = (pred == label).sum()
    eval_acc += num_correct.item()
print(
    'Text Loss:{:.6f},ACC:{:.6f}'.format(
        eval_loss / (len(text_dataset)),
        eval_acc / (len(text_dataset))
    )
)

在較新版本的PyTorch中,num_correct.data[0]已經被棄用,這裡使用num_correct.item()來獲取Python數值型別的正確預測數量。

在舊版本中,volatile=True用於告訴PyTorch不需要計算梯度,因此在推斷階段可以提高執行效率。然而,由於volatile引數已被移除,這裡使用torch.no_grad()上下文管理器來達到相同的效果。

實現

import torch
from torch import nn, optim
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision.datasets import mnist
import matplotlib.pyplot as plt
import torchvision
from torchvision.transforms import ToPILImage

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
batch_size =32

# 資料預處理
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize([0.5], [0.5])
])

# 載入資料集
train_set = mnist.MNIST('./data', train=True,
                        transform=transform, download=False)
test_set = mnist.MNIST('./data', train=False,
                       transform=transform, download=False)

train_loader = DataLoader(dataset=train_set, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(dataset=test_set, batch_size=batch_size, shuffle=False)

# 定義模型
model = CNN().to(device)

# 定義損失函式和最佳化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model.parameters(),
                            lr=0.01, momentum=0.5)

# 訓練函式
def train(epoch):
    running_loss = 0
    for batch_idx, data in enumerate(train_loader):
        images, labels = data
        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()
        if (batch_idx + 1) % 300 == 0:
            print('[%d,%d],loss is %.2f' %
                  (epoch, batch_idx, running_loss / 300))
            running_loss = 0

# 測試函式
def test():
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            images, labels = data
            images = images.to(device)
            labels = labels.to(device)
            outputs = model(images)
            _, predict = torch.max(outputs, dim=1)
            correct += (labels == predict).sum().item()
            total += labels.size(0)
        print('correct/total:%d/%d,Accuracy:%.2f%%' % (correct, total, 100 * (correct / total)))


if __name__ == '__main__':
    for epoch in range(10):
        train(epoch)
        test()

再實現MINIST手寫數字分類

  • 準備訓練集
  • 構建神經網路模型
  • 損失函式和最佳化器
  • 訓練神經網路
  • 測試神經網路

首先建立一個工程來儲存我們的資料和程式碼,data資料夾負責存放資料集, model.py用於儲存神經網路模型, test.py用於測試一些程式碼, recognize_digit.py是工程的主要控制程式碼

img

一.準備資料集


batch_size_train = 64
batch_size_test = 1000

train_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('./data/', train=True, download=False,
                               transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,))
                               ])),
    batch_size=batch_size_train, shuffle=True)
test_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('./data/', train=False, download=False,
                               transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,))
                               ])),
    batch_size=batch_size_test, shuffle=True)

讓我們在test.py中看看一批測試資料由什麼組成

examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
print(example_targets)
print(example_data.shape)

img

example_targets是圖片實際對應的數字標籤

img

這意味著我們有1000個例子的28x28畫素的灰度

我們可以用matplotlib這個庫顯示其中一些:

import matplotlib.pyplot as plt
fig = plt.figure()
for i in range(6):
  plt.subplot(2,3,i+1)
  plt.tight_layout()
  plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
  plt.title("Ground Truth: {}".format(example_targets[i]))
  plt.xticks([])
  plt.yticks([])
plt.show()

img

二.構建神經網路

下面就正式開始構建我們的網路模型了。

開啟model.py檔案,在類的初始化函式中構建神經網路模型

# 匯入需要的包
import torch
from torch import nn
import torch.nn.functional as F

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.conv2_drop = nn.Dropout = nn.Dropout2d()
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)

    def forward(self, x):
        x = F.relu(F.max_pool2d(self.conv1(x.to(device)), 2))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = x.view(-1, 320)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)


這樣我們模型也構建好了。接下來我們回到一開始的recognize_digit.py檔案,匯入這個模型

from model import Net

例項化模型

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
network = Net().to(device)

到這裡, 神經網路模型也構建完成了。我們可以在test.py檔案內板鞋如下程式碼來簡單驗證一下神經網路是否構建正確

from model import Net
import torch

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
network = Net().to(device)

one = torch.zeros((64, 1, 28, 28))  # 建立一個張量 
out = network(i)                    # 將張量one傳遞給神經網路, 得到out

print(out.shape)	               # 列印輸出張量的形狀以驗證網路的輸出大小是否符合預期		

對應的輸出如下:

img

三.準備損失函式和最佳化器

損失函式使用交叉熵損失函式

交叉熵是統計學中的一個概念,用於衡量兩個機率分佈的差異性,而我們神經網路輸出的十個數字剛好可以看作一個機率分佈。這樣,利用交叉熵就可以很容易的衡量出當前的輸出與目標輸出的差距是多少。

# 交叉熵損失函式
loss_func = torch.nn.CrossEntropyLoss()

接下來是最佳化器,我們選擇SGD最佳化器

# 最佳化器
learning_rate = 0.01
momentum = 0.5
optimizer = optim.SGD(network.parameters(), lr=learning_rate, momentum=momentum)

四.訓練與測試神經網路

n_epochs = 3
log_interval = 10
# 模型訓練
train_losses = []
train_counter = []
test_losses = []
test_counter = [i * len(train_loader.dataset) for i in range(n_epochs + 1)]


def train(epoch):
    network.train()  # 將神經網路設定為訓練模式
    for batch_idx, (data, target) in enumerate(train_loader):  # 遍歷訓練資料集
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()  # 梯度清零
        output = network(data)  # 向前傳播
        loss = F.nll_loss(output, target)  # 計算損失
        loss.backward()  # 反向傳播
        optimizer.step()  # 更新引數
        if batch_idx % log_interval == 0:  # 每隔一定批次列印一次資訊
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                       100. * batch_idx / len(train_loader), loss.item()))
            train_losses.append(loss.item())  # 記錄訓練損失
            train_counter.append(
                (batch_idx * 64) + ((epoch - 1) * len(train_loader.dataset)))  # 記錄訓練步數
            torch.save(network.state_dict(), '../model.pth')  # 儲存模型
            torch.save(optimizer.state_dict(), '../optimizer.pth')  # 儲存最佳化器狀態
            
def test():
    network.eval()  # 將神經網路設定為評估模式
    test_loss = 0  # 初始化
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = network(data)
            test_loss += F.nll_loss(output, target, size_average=False).item()
            pred = output.data.max(1, keepdim=True)[1]
            correct += pred.eq(target.data.view_as(pred)).sum()
    test_loss /= len(test_loader.dataset)
    test_losses.append(test_loss)
    print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))


            
    
train(1)
test()  # 不加這個,後面畫圖就會報錯:x and y must be the same size
for epoch in range(1, n_epochs + 1):
    train(epoch)
    test()


    

我們來畫一下訓練曲線。

import matplotlib.pyplot as plt
fig = plt.figure()
plt.plot(train_counter, train_losses, color='blue')
plt.scatter(test_counter, test_losses, color='red')
plt.legend(['Train Loss', 'Test Loss'], loc='upper right')
plt.xlabel('number of training examples seen')
plt.ylabel('negative log likelihood loss')
plt.show()

img

相關文章