Image classification
Phân loại hình ảnh với Mạng nơ-ron Tích chập
Phân loại hình ảnh là một trong những nhiệm vụ cơ bản nhất trong lĩnh vực thị giác máy tính, nơi thuật toán nhận dạng đối tượng xuất hiện trong hình ảnh. Kể từ khi học sâu bùng nổ, Mạng nơ-ron Tích chập (CNN) đã thống trị lĩnh vực này, đạt được độ chính xác đáng kinh ngạc trên các bộ dữ liệu hình ảnh phức tạp. Bài viết này khám phá phân loại hình ảnh, các phương pháp, thách thức và ứng dụng của nó.
Hiểu về Phân loại Hình ảnh
Phân loại hình ảnh là nhiệm vụ gán nhãn hoặc danh mục cho toàn bộ hình ảnh. Với một hình ảnh đầu vào, thuật toán dự đoán phân phối xác suất trên một tập hợp các lớp được xác định trước. Ví dụ, khi được hiển thị hình ảnh của một con mèo, một bộ phân loại được huấn luyện tốt sẽ xuất ra "mèo" với độ tin cậy cao.
Các loại Phân loại Hình ảnh
Phân loại Nhị phân: Phân biệt giữa hai lớp (ví dụ: hình ảnh spam và không spam)
Phân loại Đa lớp: Gán một nhãn duy nhất từ nhiều lớp (ví dụ: mèo, chó, chim)
Phân loại Đa nhãn: Gán nhiều nhãn cho một hình ảnh duy nhất (ví dụ: hình ảnh chứa cả mèo và chó)
Phân loại Phân cấp: Tổ chức các danh mục theo hệ thống phân cấp (ví dụ: động vật → động vật có vú → mèo → mèo Ba Tư)
Quy trình Phân loại Hình ảnh
Một quy trình phân loại hình ảnh điển hình bao gồm một số thành phần chính:
1. Thu thập và Chuẩn bị Dữ liệu
Dữ liệu huấn luyện chất lượng là yếu tố quyết định cho phân loại hình ảnh thành công:
Bộ dữ liệu đa dạng: Thu thập hình ảnh đại diện cho tất cả các lớp trong các điều kiện, góc độ và môi trường khác nhau
Làm sạch dữ liệu: Loại bỏ hình ảnh bị hỏng, trùng lặp hoặc không liên quan
Chú thích: Gán nhãn hình ảnh với các danh mục chính xác
Chia tập Train/Validation/Test: Thường là 70-80% cho huấn luyện, 10-15% cho kiểm định và 10-15% cho kiểm tra
2. Tiền xử lý
Trước khi đưa hình ảnh vào mô hình, một số bước tiền xử lý thường được áp dụng:
Điều chỉnh kích thước: Chuẩn hóa kích thước hình ảnh (thường là 224×224 hoặc 299×299 pixel)
Chuẩn hóa: Chia tỷ lệ giá trị pixel theo phạm vi tiêu chuẩn (thường là [0,1] hoặc [-1,1])
Tăng cường dữ liệu: Tạo các biến thể của hình ảnh huấn luyện thông qua các kỹ thuật như:
Cắt ngẫu nhiên
Lật ngang/dọc
Xoay
Thay đổi màu sắc
Xóa ngẫu nhiên
3. Trích xuất Đặc trưng và Kiến trúc Mô hình
CNN là mạng nơ-ron chuyên biệt được thiết kế cho xử lý hình ảnh:
Lớp tích chập: Áp dụng các bộ lọc để phát hiện đặc trưng như cạnh, kết cấu và mẫu
Lớp gộp (Pooling): Giảm kích thước không gian nhưng vẫn giữ thông tin quan trọng
Hàm kích hoạt: Đưa vào tính phi tuyến (ReLU phổ biến nhất)
Chuẩn hóa theo batch: Ổn định và tăng tốc quá trình huấn luyện
Dropout: Ngăn chặn overfitting bằng cách vô hiệu hóa ngẫu nhiên các nơ-ron trong quá trình huấn luyện
4. Đầu phân loại
Phần cuối cùng của mạng thực hiện phân loại thực tế:
Global Pooling: Giảm các feature map thành một vector kích thước cố định
Lớp kết nối đầy đủ: Chuyển đổi đặc trưng thành điểm số lớp
Hàm Softmax: Chuyển đổi điểm số thành phân phối xác suất trên các lớp
5. Huấn luyện
Quá trình tối ưu hóa mô hình:
Hàm mất mát: Cross-entropy phân loại cho các bài toán đa lớp
Bộ tối ưu hóa: Phương pháp như Adam, SGD với momentum
Lập lịch tốc độ học: Giảm dần tốc độ học
Dừng sớm: Ngăn chặn overfitting bằng cách giám sát hiệu suất trên tập validation
Giới hạn gradient: Xử lý hiện tượng gradient bùng nổ
6. Đánh giá
Đánh giá hiệu suất mô hình:
Độ chính xác: Tỷ lệ phần trăm hình ảnh được phân loại chính xác
Precision và Recall: Cân bằng giữa false positive và false negative
F1 Score: Trung bình điều hòa của precision và recall
Ma trận nhầm lẫn: Phân tích chi tiết dự đoán theo lớp
Độ chính xác Top-k: Liệu nhãn đúng có nằm trong k lớp có xác suất cao nhất không
Các kiến trúc CNN phổ biến cho Phân loại Hình ảnh
Một số kiến trúc đã đẩy ranh giới của phân loại hình ảnh:
Kiến trúc Cổ điển
LeNet-5 (1998): Kiến trúc CNN đầu tiên, được phát triển bởi Yann LeCun, được sử dụng để nhận dạng chữ số viết tay
AlexNet (2012): Mô hình đột phá thắng cuộc thi ImageNet, đánh dấu sự khởi đầu của kỷ nguyên học sâu trong thị giác máy tính
VGG (2014): Kiến trúc đơn giản, đồng nhất với các lớp tích chập 3×3 và pooling 2×2 lặp lại
GoogLeNet (Inception) (2014): Giới thiệu module Inception với các đường dẫn tích chập song song
Kiến trúc Hiện đại
ResNet (2015): Giới thiệu kết nối tắt (skip connections) để xây dựng mạng rất sâu lên đến 152 lớp
DenseNet (2017): Kết nối mỗi lớp với tất cả các lớp tiếp theo, tăng cường truyền đặc trưng và gradient
EfficientNet (2019): Sử dụng phương pháp mở rộng hỗn hợp để cân bằng độ sâu, chiều rộng và độ phân giải
Vision Transformers (ViT) (2020): Áp dụng kiến trúc Transformer từ NLP vào phân loại hình ảnh
ConvNeXt (2022): Kết hợp các ý tưởng từ Transformer với cấu trúc CNN truyền thống
Triển khai Phân loại Hình ảnh với PyTorch
Dưới đây là một ví dụ đơn giản về cách xây dựng pipeline phân loại hình ảnh sử dụng PyTorch:
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
# 1. Định nghĩa các phép chuyển đổi dữ liệu
transform_train = transforms.Compose([
transforms.Resize((224, 224)), # Điều chỉnh kích thước ảnh
transforms.RandomHorizontalFlip(), # Lật ngang ngẫu nhiên
transforms.RandomRotation(10), # Xoay nhẹ
transforms.ColorJitter(brightness=0.2, contrast=0.2), # Thay đổi màu sắc
transforms.ToTensor(), # Chuyển đổi sang tensor
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # Chuẩn hóa
])
transform_val = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
# 2. Tải bộ dữ liệu
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform_train)
trainloader = DataLoader(trainset, batch_size=32, shuffle=True, num_workers=2)
valset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform_val)
valloader = DataLoader(valset, batch_size=32, shuffle=False, num_workers=2)
# 3. Xây dựng mô hình CNN đơn giản
class SimpleCNN(nn.Module):
def __init__(self, num_classes=10):
super(SimpleCNN, self).__init__()
# Khối tích chập 1
self.conv1 = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
# Khối tích chập 2
self.conv2 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
# Khối tích chập 3
self.conv3 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
# Global Average Pooling và đầu phân loại
self.gap = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Linear(128, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.gap(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
# 4. Khởi tạo mô hình và thiết lập huấn luyện
model = SimpleCNN(num_classes=10)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=3, factor=0.5)
# 5. Hàm huấn luyện
def train(model, trainloader, criterion, optimizer, epochs=10):
model.train()
for epoch in range(epochs):
running_loss = 0.0
correct = 0
total = 0
for i, data in enumerate(trainloader, 0):
inputs, labels = data
# Zero the parameter gradients
optimizer.zero_grad()
# Forward + backward + optimize
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# Print statistics
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
if i % 100 == 99:
print(f'[{epoch + 1}, {i + 1}] loss: {running_loss / 100:.3f} | acc: {100 * correct / total:.2f}%')
running_loss = 0.0
# Validate after each epoch
validate(model, valloader, criterion)
# 6. Hàm đánh giá
def validate(model, valloader, criterion):
model.eval()
val_loss = 0.0
correct = 0
total = 0
with torch.no_grad():
for data in valloader:
images, labels = data
outputs = model(images)
loss = criterion(outputs, labels)
val_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
avg_loss = val_loss / len(valloader)
print(f'Validation Loss: {avg_loss:.3f} | Accuracy: {accuracy:.2f}%')
# Cập nhật scheduler
scheduler.step(avg_loss)
return accuracy
# 7. Bắt đầu huấn luyện
train(model, trainloader, criterion, optimizer, epochs=20)Chiến lược nâng cao Phân loại Hình ảnh
Transfer Learning
Thay vì huấn luyện từ đầu, transfer learning sử dụng một mô hình đã được huấn luyện trước (pre-trained) trên một bộ dữ liệu lớn như ImageNet, sau đó tinh chỉnh (fine-tune) mô hình cho nhiệm vụ cụ thể:
import torchvision.models as models
# Tải mô hình ResNet50 đã huấn luyện trước
model = models.resnet50(weights='IMAGENET1K_V2')
# Đóng băng các lớp
for param in model.parameters():
param.requires_grad = False
# Thay thế lớp fully connected cuối cùng
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, num_classes) # num_classes là số lớp trong bộ dữ liệu mới
# Chỉ cập nhật tham số của lớp fc mới
optimizer = optim.Adam(model.fc.parameters(), lr=0.001)Lợi ích của transfer learning:
Giảm đáng kể thời gian huấn luyện
Hoạt động tốt với bộ dữ liệu nhỏ
Cải thiện hiệu suất trên nhiều nhiệm vụ khác nhau
Ensemble Learning
Kết hợp nhiều mô hình để cải thiện độ chính xác và độ tin cậy:
def ensemble_predict(models, inputs):
# Tổng hợp dự đoán từ nhiều mô hình
outputs = torch.zeros(inputs.size(0), num_classes)
for model in models:
model.eval()
with torch.no_grad():
output = model(inputs)
outputs += nn.functional.softmax(output, dim=1)
# Lấy trung bình các dự đoán
outputs /= len(models)
# Trả về lớp có xác suất cao nhất
_, predicted = torch.max(outputs, 1)
return predictedCác phương pháp ensemble phổ biến:
Bagging: Huấn luyện nhiều mô hình trên các tập con dữ liệu khác nhau
Boosting: Huấn luyện mô hình mới tập trung vào các lỗi của mô hình trước
Stacking: Sử dụng một mô hình meta để kết hợp dự đoán từ các mô hình cơ sở
Knowledge Distillation
Chuyển kiến thức từ một mô hình lớn (teacher) sang mô hình nhỏ hơn (student):
def distillation_loss(student_outputs, teacher_outputs, labels, T=2.0, alpha=0.5):
# T là tham số nhiệt độ, alpha là trọng số cân bằng
# Mất mát cứng - sử dụng nhãn thật
hard_loss = nn.CrossEntropyLoss()(student_outputs, labels)
# Mất mát mềm - học từ phân phối xác suất của teacher
soft_targets = nn.functional.softmax(teacher_outputs / T, dim=1)
soft_prob = nn.functional.log_softmax(student_outputs / T, dim=1)
soft_loss = nn.KLDivLoss(reduction='batchmean')(soft_prob, soft_targets) * (T * T)
# Kết hợp hai loại mất mát
return alpha * hard_loss + (1 - alpha) * soft_lossXử lý Mất cân bằng Lớp
Khi số lượng mẫu giữa các lớp không cân bằng:
Điều chỉnh trọng số lớp:
# Tính trọng số ngược với tần suất lớp
class_counts = [len(trainset.dataset[trainset.dataset.targets == i]) for i in range(num_classes)]
weights = 1.0 / torch.tensor(class_counts, dtype=torch.float)
weights = weights / weights.sum() * num_classes
class_weights = weights.to(device)
# Sử dụng trọng số trong hàm mất mát
criterion = nn.CrossEntropyLoss(weight=class_weights)Lấy mẫu lại (Resampling):
Oversampling: Tăng mẫu từ các lớp thiểu số
Undersampling: Giảm mẫu từ các lớp đa số
SMOTE: Tạo mẫu tổng hợp cho các lớp thiểu số
Thách thức trong Phân loại Hình ảnh
1. Độ biến thiên nội tại
Các đối tượng cùng lớp có thể trông rất khác nhau:
Góc nhìn khác nhau
Điều kiện ánh sáng khác nhau
Kích thước và tỷ lệ khác nhau
Biến thể và phong cách khác nhau
Giải pháp: Tăng cường dữ liệu, thu thập dữ liệu đa dạng hơn
2. Giảm thiểu Overfitting
Mô hình phức tạp có thể ghi nhớ dữ liệu huấn luyện thay vì học các mẫu chung:
Giải pháp:
Dropout
Chuẩn hóa batch
Tăng cường dữ liệu
Regularization (L1, L2)
Early stopping
3. Khả năng Tổng quát
Mô hình thường hoạt động kém khi đối mặt với dữ liệu từ phân phối khác:
Giải pháp:
Domain adaptation
Huấn luyện đối nghịch (Adversarial training)
Tăng cường dữ liệu mạnh
Test-time augmentation
4. Hiệu quả Tính toán
Mô hình CNN sâu thường đòi hỏi tài nguyên tính toán lớn:
Giải pháp:
Distillation
Pruning (tỉa nhánh)
Quantization
Mô hình nhẹ như MobileNet, EfficientNet
Ứng dụng Phân loại Hình ảnh trong Thực tế
1. Y tế
Chẩn đoán hình ảnh y tế (X-quang, CT, MRI)
Phát hiện khối u và bất thường
Phân loại tế bào và mô
2. Nông nghiệp
Phát hiện bệnh cây trồng
Phân loại cây trồng và quả
Theo dõi sức khỏe của cây trồng
3. Bán lẻ
Hệ thống thanh toán tự động
Phân tích kệ hàng
Tìm kiếm hình ảnh sản phẩm
4. An ninh
Nhận dạng khuôn mặt
Giám sát và phát hiện đối tượng
Phát hiện vũ khí và vật nguy hiểm
5. Ô tô tự lái
Nhận diện đường phố, biển báo và phương tiện
Phát hiện người đi bộ
Hiểu cảnh quan xung quanh
Xu hướng mới trong Phân loại Hình ảnh
1. Mô hình Transformer
Vision Transformer (ViT) và các biến thể đã vượt qua hiệu suất CNN truyền thống trên nhiều benchmark:
import timm
# Tải mô hình Vision Transformer
model = timm.create_model('vit_base_patch16_224', pretrained=True, num_classes=num_classes)2. Neural Architecture Search (NAS)
Tự động tìm kiếm kiến trúc tối ưu thay vì thiết kế thủ công:
EfficientNet và EfficientNetV2
NASNet
MnasNet
3. Học tự giám sát (Self-supervised Learning)
Học đặc trưng có ý nghĩa mà không cần nhãn:
Contrastive Learning (SimCLR, MoCo)
Masked Autoencoders (MAE)
DINO và DINO-v2
4. Học ít mẫu (Few-shot Learning)
Nhận dạng lớp mới với rất ít mẫu huấn luyện:
Prototypical Networks
Matching Networks
Model-Agnostic Meta-Learning (MAML)
Đánh giá và Phân tích Mô hình
1. Confusion Matrix
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
def plot_confusion_matrix(true_labels, predictions, class_names):
cm = confusion_matrix(true_labels, predictions)
plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_names, yticklabels=class_names)
plt.xlabel('Dự đoán')
plt.ylabel('Thực tế')
plt.title('Ma trận nhầm lẫn')
plt.show()2. Activation Maps
Hiển thị vùng quan trọng khi mô hình ra quyết định (CAM, Grad-CAM):
from pytorch_grad_cam import GradCAM
from pytorch_grad_cam.utils.image import show_cam_on_image
def visualize_class_activation_map(model, img_tensor, target_layer):
cam = GradCAM(model=model, target_layers=[target_layer])
grayscale_cam = cam(input_tensor=img_tensor.unsqueeze(0))[0]
# Chuyển đổi tensor thành ảnh numpy
img = img_tensor.permute(1, 2, 0).numpy()
# Chuẩn hóa lại ảnh để hiển thị
img = (img - img.min()) / (img.max() - img.min())
# Áp dụng heatmap lên ảnh gốc
visualization = show_cam_on_image(img, grayscale_cam, use_rgb=True)
return visualization3. Phân tích lỗi
Hiểu tại sao mô hình phạm sai lầm:
Phân tích các trường hợp khó
Xác định các mẫu bị phân loại sai một cách nhất quán
Tìm kiếm sự tương đồng giữa các lớp hay bị nhầm lẫn
Kết luận
Phân loại hình ảnh đã phát triển vượt bậc nhờ sự tiến bộ của mạng nơ-ron tích chập và các kiến trúc mới như Transformer. Từ việc nhận dạng chữ số đơn giản trong LeNet, đến khả năng phân biệt hàng nghìn danh mục trong EfficientNet và ViT, lĩnh vực này tiếp tục phát triển.
Các xu hướng mới tập trung vào hiệu quả, khả năng mở rộng và học với ít dữ liệu có nhãn. Với sự kết hợp của CNN, Transformer và các kỹ thuật học tự giám sát, tương lai của phân loại hình ảnh hứa hẹn những ứng dụng mới và hiệu suất cao hơn nữa.
Để thành công trong phân loại hình ảnh, các nhà nghiên cứu và kỹ sư nên tập trung vào chất lượng dữ liệu, kỹ thuật tăng cường, lựa chọn kiến trúc phù hợp và chiến lược huấn luyện tối ưu. Đồng thời, việc hiểu rõ các chỉ số đánh giá và phân tích kỹ lưỡng lỗi cũng rất quan trọng để cải thiện liên tục hiệu suất mô hình.
Last updated