Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Python

Nhận dạng chữ số viết tay bằng Logistic Regression trong PyTorch

Trong bài này, chúng ta sẽ sử dụng PyTorch để huấn luyện một CNN nhận dạng chữ số viết tay sử dụng bộ dữ liệu MNIST.

MNIST là bộ dữ liệu phổ biến cho nhiệm vụ phân loại chữ viết tay, bao gồm hơn 70.000 hình ảnh grayscale 28x28 pixel đã được gán nhãn. Bộ dữ liệu chứa gần 60.000 ảnh huấn luyện và 10.000 ảnh kiểm tra. Nhiệm vụ của chúng ta là huấn luyện mô hình bằng 60.000 ảnh huấn luyện và kiểm tra độ chính xác phân loại trên 10.000 ảnh kiểm tra.

Cài đặt

Trước tiên chúng ta cần phiên bản mới nhất của MXNet, chạy lệnh sau trên terminal:

$pip install mxnet

Kết quả sẽ như sau:

Collecting mxnet
Downloading https://files.pythonhosted.org/packages/60/6f/071f9ef51467f9f6cd35d1ad87156a29314033bbf78ad862a338b9eaf2e6/mxnet-1.2.0-py2.py3-none-win32.whl (12.8MB)
100% |████████████████████████████████| 12.8MB 131kB/s
Requirement already satisfied: numpy in c:\python\python361\lib\site-packages (from mxnet) (1.16.0)
Collecting graphviz (from mxnet)
Downloading https://files.pythonhosted.org/packages/1f/e2/ef2581b5b86625657afd32030f90cf2717456c1d2b711ba074bf007c0f1a/graphviz-0.10.1-py2.py3-none-any.whl
….
….
Installing collected packages: graphviz, mxnet
Successfully installed graphviz-0.10.1 mxnet-1.2.0

Thứ hai, chúng ta cần thư viện torch và torchvision - nếu chưa có, cài bằng pip.

Nhập thư viện

import torch
import torchvision

Tải bộ dữ liệu MNIST

Trước khi bắt đầu chương trình, chúng ta cần bộ dữ liệu MNIST. Hãy tải hình ảnh và nhãn vào bộ nhớ và định nghĩa các siêu tham số cho thí nghiệm.

# n_epochs là số lần lặp qua toàn bộ tập huấn luyện
n_epochs = 3
batch_size_train = 64
batch_size_test = 1000

# Learning_rate và momentum cho optimizer
learning_rate = 0.01
momentum = 0.5
log_interval = 10
random_seed = 1

torch.backends.cudnn.enabled = False
torch.manual_seed(random_seed)
# 

Bây giờ tải bộ dữ liệu MNIST bằng TorchVision. Chúng ta dùng batch_size 64 cho huấn luyện và 1000 cho kiểm tra. Để chuẩn hóa, dùng giá trị trung bình 0.1307 và độ lệch chuẩn 0.3081 của MNIST.

train_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('/files/', train=True, download=True,
        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('/files/', train=False, download=True,
        transform=torchvision.transforms.Compose([
            torchvision.transforms.ToTensor(),
            torchvision.transforms.Normalize(
                (0.1307,), (0.3081,))
        ])
    ),
    batch_size=batch_size_test, shuffle=True)

Kết quả

Downloading https://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Downloading https://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Downloading https://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Downloading https://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Processing... Done!

Sử dụng test_loader để tải dữ liệu kiểm tra

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

Kết quả

torch.Size([1000, 1, 28, 28])

Từ đầu ra, một batch dữ liệu kiểm tra là tensor có hình状: [1000, 1, 28, 28] nghĩa là 1000 ví dụ 28x28 pixel grayscale.

Hãy vẽ một số mẫu dữ liệu bằng matplotlib.

import matplotlib.pyplot as plt
fig = plt.figure()
for i in range(5):
    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([])
print(fig)

Kết quả

Nhận dạng chữ số viết tay bằng Logistic Regression trong PyTorch

Xây dựng mạng nơ-ron

Chúng ta sẽ xây dựng mạng với 2 lớp convolution 2D tiếp theo là 2 lớp fully-connected. Trước đó hãy nhập các module cần thiết.

import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

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.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), 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)

Khởi tạo mạng và optimizer:

network = Net()
optimizer = optim.SGD(network.parameters(), lr=learning_rate, momentum=momentum)

Huấn luyện mô hình

Xây dựng vòng lặp huấn luyện. Kiểm tra mạng ở chế độ train, lặp qua toàn bộ dữ liệu huấn luyện mỗi epoch. DataLoader tải từng batch. Đặt gradient về 0 bằng optimizer.zero_grad().

train_losses = []
train_counter = []
test_losses = []
test_counter = [i*len(train_loader.dataset) for i in range(n_epochs + 1)]

Để vẽ đường cong huấn luyện, tạo 2 danh sách lưu loss huấn luyện và kiểm tra. Trục x hiển thị số ví dụ huấn luyện.

Lệnh backward() thu thập gradient mới, lan truyền ngược qua các tham số mạng bằng optimizer.step().

def train(epoch):
    network.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        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(), '/results/model.pth')
            torch.save(optimizer.state_dict(), '/results/optimizer.pth')

Module mạng nơ-ron và optimizer có thể lưu/tải trạng thái nội bộ bằng .state_dict().

Đối với vòng lặp kiểm tra, tính tổng loss và theo dõi số chữ số phân loại đúng để tính độ chính xác.

def test():
    network.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            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)))

Để chạy huấn luyện, thêm lời gọi test() trước khi lặp qua n_epochs để đánh giá mô hình với tham số khởi tạo ngẫu nhiên.

test()
for epoch in range(1, n_epochs + 1):
    train(epoch)
    test()

Kết quả

Test set: Avg. loss: 2.3048, Accuracy: 1063/10000 (10%)
Train Epoch: 1 [0/60000 (0%)]Loss: 2.294911
Train Epoch: 1 [640/60000 (1%)]Loss: 2.314225
...
Train Epoch: 3 [59520/60000 (99%)]Loss: 0.318569
Test set: Avg. loss: 0.0912, Accuracy: 9716/10000 (97%)

Đánh giá hiệu suất mô hình

Với chỉ 3 epoch huấn luyện, chúng ta đạt 97% độ chính xác trên tập kiểm tra. Với tham số khởi tạo ngẫu nhiên, ban đầu độ chính xác chỉ 10% trước khi huấn luyện.

Vẽ đường cong huấn luyện:

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')
fig

Kết quả

Nhận dạng chữ số viết tay bằng Logistic Regression trong PyTorch

Kiểm tra đầu ra, có thể tăng số epoch để xem thêm kết quả, độ chính xác tăng qua 3 epoch.

Trước đó, chạy thêm vài ví dụ và so sánh đầu ra của mô hình:

with torch.no_grad():
    output = network(example_data)
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("Prediction: {}".format(output.data.max(1, keepdim=True)[1][i].item()))
    plt.xticks([])
    plt.yticks([])
fig

Nhận dạng chữ số viết tay bằng Logistic Regression trong PyTorch

Như thấy, dự đoán của mô hình khá chính xác cho các ví dụ này.