Bài viết này hướng dẫn cách xây dựng và huấn luyện một mạng nơ-ron tích chập (CNN) để nhận dạng chữ số viết tay từ tập dữ liệu MNIST sử dụng PyTorch.
1. Khởi tạo và Siêu tham số
Để đảm bảo tính tái lập của quá trình huấn luyện, chúng ta thiết lập một hạt giống ngẫu nhiên.
import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
import cv2
# Khởi tạo hạt giống ngẫu nhiên cho PyTorch
torch.manual_seed(42)
# Định nghĩa các siêu tham số cho quá trình huấn luyện
TRAIN_EPOCHS = 1 # Số chu kỳ huấn luyện
BATCH_SIZE = 64 # Kích thước của mỗi lô dữ liệu
LEARNING_RATE = 0.001 # Tốc độ học
DOWNLOAD_DATA = True # Cờ để tải xuống tập dữ liệu nếu chưa có
Các siêu tham số này bao gồm số chu kỳ huấn luyện, kích thước lô dữ liệu, tốc độ học và một cờ để quyết định có tải xuống tập dữ liệu MNIST hay không.
2. Tải và Tiền xử lý Dữ liệu MNIST
Chúng ta sử dụng thư viện torchvision để tải tập dữ liệu MNIST. Dữ liệu hình ảnh sẽ được chuyển đổi thành tensor và chuẩn hóa về khoảng giá trị [0, 1].
# Tải tập dữ liệu huấn luyện MNIST
train_dataset = torchvision.datasets.MNIST(
root='./data_mnist/', # Thư mục lưu trữ dữ liệu
train=True, # Lấy tập dữ liệu huấn luyện
transform=transforms.ToTensor(), # Chuyển đổi hình ảnh thành tensor và chuẩn hóa
download=DOWNLOAD_DATA, # Tải xuống nếu cần
)
# Tải tập dữ liệu kiểm tra MNIST
test_dataset = torchvision.datasets.MNIST(
root='./data_mnist/',
train=False, # Lấy tập dữ liệu kiểm tra
transform=transforms.ToTensor(),
)
# Chuẩn bị dữ liệu kiểm tra để đánh giá (chỉ lấy một phần nhỏ)
# Thêm chiều kênh và chuyển đổi kiểu dữ liệu, chuẩn hóa
test_images = test_dataset.data.type(torch.FloatTensor)[:2000].unsqueeze(1) / 255.0
test_labels = test_dataset.targets[:2000]
print(f"Kích thước dữ liệu huấn luyện: {len(train_dataset)}")
print(f"Kích thước dữ liệu kiểm tra: {len(test_dataset)}")
print(f"Kích thước tensor ảnh kiểm tra: {test_images.shape}")
print(f"Kích thước tensor nhãn kiểm tra: {test_labels.shape}")
Dữ liệu hình ảnh gốc có giá trị pixel từ 0 đến 255. Bước / 255.0 chuẩn hóa các giá trị này về khoảng [0, 1], một bước tiền xử lý quan trọng cho các mô hình học sâu.
3. Tạo DataLoader
DataLoader giúp chia dữ liệu thành các lô nhỏ và xáo trộn dữ liệu huấn luyện để tăng hiệu quả học.
# Tạo DataLoader cho tập dữ liệu huấn luyện
train_loader = Data.DataLoader(
dataset=train_dataset,
batch_size=BATCH_SIZE,
shuffle=True, # Xáo trộn dữ liệu huấn luyện
)
4. Xây dựng Mô hình Mạng Nơ-ron Tích chập (CNN)
Mô hình CNN bao gồm hai lớp tích chập, mỗi lớp theo sau là một hàm kích hoạt ReLU và một lớp gộp tối đa (MaxPool), và cuối cùng là một lớp tuyến tính đầy đủ để phân loại.
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# Lớp tích chập thứ nhất
self.layer1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, stride=1, padding=2),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2) # Giảm kích thước ảnh từ 28x28 xuống 14x14
)
# Lớp tích chập thứ hai
self.layer2 = nn.Sequential(
nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2) # Giảm kích thước ảnh từ 14x14 xuống 7x7
)
# Lớp phân loại đầy đủ
# Kích thước đầu vào là 32 kênh * 7x7 kích thước ảnh
self.fc_layer = nn.Linear(in_features=32 * 7 * 7, out_features=10) # 10 lớp cho 10 chữ số
def forward(self, x):
# Truyền dữ liệu qua các lớp tích chập
out = self.layer1(x)
out = self.layer2(out)
# Làm phẳng tensor đầu ra trước khi đưa vào lớp tuyến tính
# x.size(0) là kích thước batch
out = out.view(out.size(0), -1)
# Truyền qua lớp tuyến tính để lấy kết quả phân loại
output = self.fc_layer(out)
return output
# Khởi tạo mô hình CNN
model = SimpleCNN()
print("Cấu trúc mô hình CNN:")
print(model)
nn.Module là lớp cơ sở cho tất cả các mô hình mạng thần kinh trong PyTorch. Lớp SimpleCNN định nghĩa hai khối tích chập và một lớp tuyến tính.
- Lớp
nn.Conv2dthực hiện tích chập 2D. Các tham sốin_channels,out_channels,kernel_size,stride, vàpaddingxác định cách thức hoạt động của bộ lọc tích chập. nn.ReLUlà hàm kích hoạt phổ biến, thêm tính phi tuyến vào mô hình.nn.MaxPool2dthực hiện lấy mẫu tối đa, giảm kích thước không gian của bản đồ đặc trưng, giúp giảm số lượng tham số và tính toán, đồng thời tăng khả năng chống lại sự dịch chuyển nhỏ.nn.Linearlà lớp đầy đủ kết nối, ánh xạ các đặc trưng đã trích xuất từ các lớp tích chập thành các điểm số cho từng lớp đầu ra.
5. Hàm Mất mát và Tối ưu hóa
Chúng ta sử dụng hàm mất mát CrossEntropyLoss cho bài toán phân loại đa lớp và thuật toán tối ưu hóa Adam để cập nhật trọng số của mô hình.
# Định nghĩa hàm mất mát (Cross Entropy Loss cho phân loại đa lớp)
criterion = nn.CrossEntropyLoss()
# Định nghĩa bộ tối ưu hóa (Adam)
optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)
Adam là một thuật toán tối ưu hóa hiệu quả, thường hoạt động tốt trong nhiều tác vụ học sâu.
6. Quá trình Huấn luyện Mô hình
Vòng lặp huấn luyện sẽ lặp qua các chu kỳ và các lô dữ liệu, thực hiện lan truyền tiến, tính toán mất mát, lan truyền ngược và cập nhật trọng số.
print("\nBắt đầu quá trình huấn luyện...")
for epoch in range(TRAIN_EPOCHS):
for i, (images, labels) in enumerate(train_loader):
# Đưa dữ liệu qua mô hình
outputs = model(images)
# Tính toán mất mát
loss = criterion(outputs, labels)
# Xóa gradient của các tham số đã cập nhật ở bước trước
optimizer.zero_grad()
# Lan truyền ngược để tính toán gradient
loss.backward()
# Cập nhật trọng số của mô hình
optimizer.step()
# In thông tin tiến trình sau mỗi 100 bước
if (i + 1) % 100 == 0:
print(f'Epoch [{epoch+1}/{TRAIN_EPOCHS}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}')
# Đánh giá độ chính xác trên tập dữ liệu kiểm tra sau mỗi chu kỳ
with torch.no_grad(): # Tắt tính toán gradient trong quá trình đánh giá
correct_predictions = 0
total_samples = 0
# Đưa dữ liệu kiểm tra qua mô hình đã huấn luyện
test_outputs = model(test_images)
# Lấy các lớp dự đoán từ kết quả đầu ra
_, predicted_labels = torch.max(test_outputs, 1)
# So sánh nhãn dự đoán với nhãn thực tế
correct_predictions += (predicted_labels == test_labels).sum().item()
total_samples += test_labels.size(0)
accuracy = 100 * correct_predictions / total_samples
print(f'Độ chính xác trên tập kiểm tra sau Epoch {epoch+1}: {accuracy:.2f}%')
print("Huấn luyện hoàn tất.")
# Lưu trạng thái của mô hình đã huấn luyện
torch.save(model.state_dict(), 'trained_cnn_mnist.pkl')
print("Mô hình đã được lưu vào 'trained_cnn_mnist.pkl'")
Trong quá trình huấn luyện, chúng ta thường xuyên kiểm tra độ chính xác trên một tập dữ liệu kiểm tra riêng biệt để theo dõi hiệu suất của mô hình.
7. Trực quan hóa và Dự đoán
Sau khi huấn luyện, chúng ta tải lại mô hình và thực hiện dự đoán trên một số mẫu dữ liệu kiểm tra, sau đó trực quan hóa kết quả.
# Tải lại mô hình đã huấn luyện
model.load_state_dict(torch.load('trained_cnn_mnist.pkl'))
# Đặt mô hình ở chế độ đánh giá
model.eval()
# Lấy một số mẫu dữ liệu kiểm tra để dự đoán và trực quan hóa
num_samples_to_show = 16
sample_images = test_images[:num_samples_to_show]
sample_labels = test_labels[:num_samples_to_show]
# Thực hiện dự đoán trên các mẫu đã chọn
with torch.no_grad():
predictions = model(sample_images)
# Lấy nhãn dự đoán với giá trị lớn nhất
_, predicted_classes = torch.max(predictions, 1)
print("\nThực hiện dự đoán trên một số mẫu kiểm tra:")
# In nhãn dự đoán và nhãn thực tế
print("Nhãn dự đoán:", predicted_classes.numpy())
print("Nhãn thực tế:", sample_labels.numpy())
# Trực quan hóa hình ảnh và dự đoán
# Tạo lưới hình ảnh
images_grid = torchvision.utils.make_grid(sample_images)
# Chuyển đổi tensor sang định dạng NumPy có thể hiển thị
images_np = images_grid.numpy().transpose(1, 2, 0)
# Hiển thị hình ảnh
plt.figure(figsize=(10, 5))
plt.imshow(images_np)
# Thêm chú thích cho từng hình ảnh
for i in range(num_samples_to_show):
plt.subplot(4, 4, i + 1)
plt.imshow(sample_images[i].squeeze().numpy(), cmap='gray')
plt.title(f"Pred: {predicted_classes[i].item()}\nTrue: {sample_labels[i].item()}")
plt.axis('off')
plt.tight_layout()
plt.show()
Phần này trình bày cách tải lại mô hình đã huấn luyện, đặt nó ở chế độ đánh giá, thực hiện dự đoán trên một tập con của dữ liệu kiểm tra và hiển thị các hình ảnh cùng với nhãn dự đoán và nhãn thực tế.