Bài 4: Train Neural Network
Bài trước mình đã học về cách xây dựng model (NN, CNN) bằng Pytorch, bài này mình sẽ học cách load dữ liệu, sau đó train model bằng Pytorch.
Nội dung
Dataset
torch.utils.data.Dataset trong Pytorch mô tả dữ liệu giống như dữ liệu thực tế mọi người có, trong đó có hai hàm quan trọng:
- __len__: trả về số sample trong dataset, tương ứng len(dataset).
- __getitem__ : lấy ra sample thứ i trong dataset, tương ứng dataset[i].
Ví dụ như khi load dataset cifar10 từ torchvision.dataset
from torchvision import datasets
# Đường dẫn lưu để sau load lại không cần download.
data_path = '/content/drive/MyDrive/Pytorch/Pytorch Tutorial/'
# load dataset cifar10
cifar10 = datasets.CIFAR10(data_path, train=True, download=True)
Khi đó cifar10 là một Dataset.
print(isinstance(cifar10, torch.utils.data.Dataset)) # True
print(len(cifar10)) # 50000
print(cifar10[0]) # (<PIL.Image.Image image mode=RGB size=32x32 at 0x7F729582BF50>, 6)
Dataset cifar10 có 50.000 dữ liệu, trong đó khi lấy index theo từng phần tử sẽ trả lại 1 tuple gồm 2 phần tử (PIL image, label).
Mình có thể visualize ảnh lên, tuy nhiên ảnh kích thước nhỏ (32*32) nên mở lên sẽ hơi vỡ.
img, label = cifar10[0]
plt.imshow(img)
Transformer
Dữ liệu mình lấy được ở trên thì ảnh ở dạng PIL image, mình cần convert về dạng Torch tensor để cho Pytorch xử lý và tính toán. Module torchvision.transforms hỗ trợ các phép chuyển đổi trên ảnh như: chuyển sang tensor, normalization, augmentation,…
ToTensor
to_tensor = transforms.ToTensor()
# Hàm chuyển PIL image sang tensor
img_t = to_tensor(img)
print(img_t.shape) # torch.Size([3, 32, 32])
Mình có thể transform trong lớp dataset khi load dữ liệu cifar10
tensor_cifar10 = datasets.CIFAR10(data_path, train=True, download=False,
transform=transforms.ToTensor())
img_t, _ = tensor_cifar10[99]
print(type(img_t)) # torch.Tensor
Ảnh ở dạng PIL image thì các pixel có giá trị từ 0-255, tuy nhiên khi chuyển về dạng tensor (ToTensor) thì dữ liệu pixel được scale về khoảng 0.0-1.0
print(img_t.min(), img_t.max()) # tensor(0.) tensor(1.)
Normalization
Việc normalization dữ liệu giúp các pixel có cùng scale cũng như distribution, do đó mình có thể dùng thuật toán gradient descent cho các tham số với cùng một learning rate. Thông thường mình sẽ normalize để mỗi channel về standard normal distribution (N(0, 1), normal distribution với 0 mean và 1 standard deviation)
\displaystyle v[c] = \frac{v[c] - mean[c]}{std[c]}Mình có thể tính mean và std theo từng channel của dữ liệu.
imgs.view(3, -1).mean(dim=1) # tensor([0.4915, 0.4823, 0.4468])
imgs.view(3, -1).std(dim=1) # tensor([0.2470, 0.2435, 0.2616])
Sau đó mình sẽ dùng transforms.Normalize để normalize dữ liệu.
# Tham số đầu là mean, tham số sau là std
transforms.Normalize((0.4915, 0.4823, 0.4468), (0.2470, 0.2435, 0.2616))
Compose
Ngoài ra Pytorch transforms còn hỗ trợ rất nhiều phép biển đổi khác như: RandomCrop, RandomRotation, RandomAffine (để shear), RandomHorizontalFlip,… Chi tiết mọi người xem ở đây.
Để thực hiện nhiều phép biến đổi trên dữ liệu đầu vào, transforms hỗ trợ hàm compose để gộp các transforms lại.
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4915, 0.4823, 0.4468),
(0.2470, 0.2435, 0.2616))
])
Khi load Dataset mình sẽ dùng transform cho tất cả các ảnh
tensor_cifar10 = datasets.CIFAR10(data_path, train=True, download=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4915, 0.4823, 0.4468),
(0.2470, 0.2435, 0.2616))
]))
Như vậy là mình đã có Dataset và thực hiện transform các ảnh đưa về dạng tensor cũng như normalize các ảnh. Giờ mình cần lấy các ảnh cho quá trình traning.
DataLoader
Khi cho dữ liệu vào model để học thì thông thường sẽ cho dữ liệu theo từng batch một, DataLoader sẽ giúp chúng ta lấy dữ liệu theo từng batch, shuffle dữ liệu cũng như load dữ liệu song song với nhiều multiprocessing workers.
train_loader = torch.utils.data.DataLoader(cifar10, batch_size=64,
shuffle=True)
Tham số mình sẽ truyền vào Dataset (cifar10), batch_size bằng 64 và để shuffle bằng True (shuffle data sau mỗi epoch).
Train CIFAR10 classifier
Model
Mình sẽ xây dựng một mạng CNN đơn giản.
# model
class Net(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 8, kernel_size=3, padding=1)
self.fc1 = nn.Linear(8 * 8 * 8, 32)
# bài toán phân loại 10 lớp nên output ra 10 nodes
self.fc2 = nn.Linear(32, 10)
def forward(self, x):
out = F.max_pool2d(torch.tanh(self.conv1(x)), 2)
out = F.max_pool2d(torch.tanh(self.conv2(out)), 2)
# flatten về dạng vector để cho vào neural network
out = out.view(-1, 8 * 8 * 8)
out = torch.tanh(self.fc1(out))
out = self.fc2(out)
return out
Loss
Loss mình sẽ dùng categorical crossentropy loss
loss_fn = nn.CrossEntropyLoss()
Lưu ý: Hàm CrossEntropyLoss của Pytorch đã bao gồm cả activation softmax ở lớp output và categorial crossentropy loss thế nên khi dựng model không cần dùng activation softmax ở output layer.
Train
def training_loop(n_epochs, optimizer, model, loss_fn, train_loader, val_loader):
for epoch in range(1, n_epochs + 1):
loss_train = 0.0
for imgs, labels in train_loader:
outputs = model(imgs)
loss = loss_fn(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss_train += loss.item()
correct = 0
# tính độ chính xác trên tập validation
with torch.no_grad():
for data in val_loader:
images, labels = data
outputs = model(images)
_, predicted = torch.max(outputs, 1)
c = (predicted == labels).squeeze()
correct += c.sum()
if epoch == 1 or epoch % 1 == 0:
print('Epoch {}, Training loss {}, Val accuracy {}'.format(
epoch,
loss_train / len(train_loader),
correct / len(cifar10_val)))
Code mọi người xem ở đây.