PyTorch cung cấp các module và lớp thiết kế một cách tinh tế: torch.nn, torch.optim, Dataset, và DataLoader để tạo và huấn luyện các mạng thần kinh. Để khai thác tối đa chức năng của chúng và tùy chỉnh theo vấn đề cụ thể, bạn cần hiểu rõ mỗi module hoạt động như thế nào. Hướng dẫn này sẽ giúp bạn làm rõ các khái niệm bằng cách huấn luyện một mô hình cơ bản trên dataset MNIST mà không sử dụng bất kỳ chức năng nào từ các module này, ngoại trừ việc sử dụng các hàm cơ bản của PyTorch tensor. Sau đó, chúng ta sẽ dần dần thêm các tính năng từ torch.nn, torch.optim, Dataset, và DataLoader để thấy rõ chức năng của từng phần và cách chúng giúp mã nguồn trở nên ngắn gọn và linh hoạt hơn.
Giả sử bạn đã cài đặt PyTorch và quen thuộc với các thao tác cơ bản của tensor.
Thiết lập dữ liệu MNIST
Chúng ta sẽ sử dụng dataset MNIST kinh điển, bao gồm các hình ảnh đen trắng của các chữ số tay viết (0-9). Mỗi hình ảnh có kích thước 28x28 và được lưu dưới dạng một dòng có độ dài 784.
Chúng ta sẽ sử dụng thư viện pathlib và requests để xử lý đường dẫn và tải dữ liệu:
from pathlib import Path
import requests
data_path = Path('data')
mnist_path = data_path / "mnist"
mnist_path.mkdir(parents=True, exist_ok=True)
url = "https://github.com/pytorch/tutorials/raw/master/_static/"
filename = "mnist.pkl.gz"
if not (mnist_path / filename).exists():
content = requests.get(url + filename).content
(mnist_path / filename).open('wb').write(content)
Dữ liệu được lưu trong định dạng pickle (mặc định của NumPy để lưu trữ dữ liệu).
import pickle
import gzip
with gzip.open((mnist_path / filename).as_posix(), 'rb') as f:
((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding='latin-1')
Chúng ta chuyển đổi dữ liệu sang tensor PyTorch:
import torch
x_train, y_train, x_valid, y_valid = map(
torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
print(x_train.shape)
print(y_train.min(), y_train.max())
Tạo mô hình từ đầu (không sử dụng torch.nn)
Chúng ta sẽ tạo một mô hình cơ bản sử dụng các thao tác tensor PyTorch:
import math
w = torch.randn(784, 10) / math.sqrt(784)
w.requires_grad_()
b = torch.zeros(10, requires_grad=True)
def log_softmax(x):
return x - x.logsumexp(-1, keepdim=True)
def mod(xb):
return log_softmax(xb @ w + b)
Chúng ta sẽ huấn luyện mô hình bằng cách chạy vòng lặp:
bs = 64 # Kích thước batch
xb = x_train[:bs]
yb = y_train[:bs]
preds = mod(xb)
print(loss_func(preds, yb))
print(accuracy(preds, yb))
Sử dụng torch.nn.functional
Chúng ta thay thế các hàm手写 bằng các hàm từ torch.nn.functional để mã nguồn trở nên ngắn gọn và dễ hiểu hơn:
import torch.nn.functional as F
loss_func = F.cross_entropy
def mod(xb):
return xb @ w + b
Ứng dụng nn.Module
Chúng ta tái cấu trúc mô hình bằng cách thừa kế từ nn.Module để có thể sử dụng các phương thức hữu ích như .parameters() và .zero_grad():
from torch import nn
class Mnist_Logistic(nn.Module):
def __init__(self):
super().__init__()
self.w = nn.Parameter(torch.randn(784, 10) / math.sqrt(784))
self.b = nn.Parameter(torch.zeros(10))
def forward(self, xb):
return xb @ self.w + self.b
mod = Mnist_Logistic()
Sử dụng nn.Linear
Chúng ta thay thế việc khai báo và khởi tạo trọng số, bias bằng cách sử dụng lớp nn.Linear để mã nguồn trở nên dễ đọc hơn:
class Mnist_Logistic(nn.Module):
def __init__(self):
super().__init__()
self.lin = nn.Linear(784, 10)
def forward(self, xb):
return self.lin(xb)
mod = Mnist_Logistic()
Áp dụng optimizer
Chúng ta sử dụng thư viện torch.optim để thay thế việc cập nhật trọng số một cách thủ công:
from torch import optim
def get_mod():
mod = Mnist_Logistic()
opt = optim.SGD(mod.parameters(), lr=lr)
return mod, opt
mod, opt = get_mod()
Dataset và DataLoader
PyTorch cung cấp các lớp Dataset và DataLoader để quản lý dữ liệu một cách hiệu quả:
from torch.utils.data import TensorDataset, DataLoader
train_ds = TensorDataset(x_train, y_train)
valid_ds = TensorDataset(x_valid, y_valid)
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)
valid_dl = DataLoader(valid_ds, batch_size=bs*2)
Thử nghiệm với mạng CNN
Chúng ta sẽ xây dựng một mạng CNNsimple với 3 lớp conv:
class Mnist_CNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1)
self.conv2 = nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1)
self.conv3 = nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1)
def forward(self, xb):
xb = xb.view(-1, 1, 28, 28)
xb = F.relu(self.conv1(xb))
xb = F.relu(self.conv2(xb))
xb = F.relu(self.conv3(xb))
xb = F.avg_pool2d(xb, 4)
return xb.view(-1, xb.size(1))
lr = 0.1
mod = Mnist_CNN()
opt = optim.SGD(mod.parameters(), lr=lr, momentum=0.9)
Wrapping DataLoader
Chúng ta tạo một lớp customize để preprocess dữ liệu:
def preprocess(x, y):
return x.view(-1, 1, 28, 28), y
class WrappedDataLoader:
def __init__(self, dl, func):
self.dl = dl
self.func = func
def __len__(self):
return len(self.dl)
def __iter__(self):
for b in iter(self.dl):
yield (self.func(*b))
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)
Sử dụng GPU
Chúng ta kiểm tra và sử dụng GPU nếu có sẵn:
print(torch.cuda.is_available())
dev = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
def preprocess(x, y):
return x.view(-1, 1, 28, 28).to(dev), y.to(dev)
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)
mod.to(dev)
Tổng kết
Bây giờ chúng ta đã có một pipeline huấn luyện mô hình mạnh mẽ và linh hoạt, có thể sử dụng để huấn luyện nhiều loại mô hình khác nhau. Các thành phần chính trong PyTorch:
- torch.nn: Cung cấp các lớp như
Module,Parameter, và các hàm chức năng nhưfunctional. - torch.optim: Cung cấp các optimizer như SGD để cập nhật trọng số.
- Dataset: Là một abstract class để xử lý dữ liệu.
- DataLoader: Quản lý việc tải và preprocess dữ liệu.