AI & GPU
Cách Dễ Hiểu ResNet trong PyTorch

Cách Dễ Hiểu ResNet trong PyTorch

Giới thiệu về ResNet

ResNet là gì?

ResNet, viết tắt của Residual Neural Network, là một kiến trúc học sâu đã được giới thiệu vào năm 2015 bởi các nhà nghiên cứu tại Microsoft. Nó được thiết kế để giải quyết vấn đề gradient biến mất/lạc hoàn, một vấn đề phổ biến gặp phải khi huấn luyện các mạng nơ-ron sâu.

  1. Mạng Nơ-ron Dư Mirroring: ResNet là một loại mạng nơ-ron sử dụng "kết nối bỏ qua" hoặc "kết nối dư" để cho phép việc huấn luyện các mô hình sâu hơn. Các kết nối bỏ qua này cho phép mạng đi qua một số lớp nhất định, tạo ra một "lối tắt" giúp giảm thiểu vấn đề gradient biến mất.

  2. Giải quyết Vấn đề Gradient Biến Mất/Lạc Hoàn: Trong các mạng nơ-ron sâu, các gradient được sử dụng cho backpropagation có thể biến mất (trở nên rất nhỏ) hoặc lạc hoàn (trở nên rất lớn) khi chúng được lan truyền lại qua mạng. Điều này có thể làm cho việc học hiệu quả của mạng trở nên khó khăn, đặc biệt là ở các lớp sâu hơn. Các kết nối bỏ qua trong ResNet giúp giải quyết vấn đề này bằng cách cho phép các gradient lan truyền dễ dàng hơn qua mạng.

Lợi ích của ResNet

  1. Hiệu suất cải thiện trên các mạng nơ-ron sâu: Các kết nối bỏ qua của ResNet cho phép huấn luyện các mạng nơ-ron sâu hơn, giúp cải thiện đáng kể hiệu suất trên nhiều nhiệm vụ khác nhau, như phân loại hình ảnh, phát hiện đối tượng, và phân đoạn ngữ nghĩa.

  2. Hội tụ nhanh hơn trong quá trình huấn luyện: Các kết nối bỏ qua trong ResNet cũng giúp mạng hội tụ nhanh hơn trong quá trình huấn luyện, vì chúng cho phép các gradient lan truyền một cách hiệu quả hơn qua mạng.

Thực hiện ResNet trong PyTorch

Thiết lập Môi trường

  1. Cài đặt PyTorch: Để bắt đầu thực hiện ResNet trong PyTorch, bạn cần cài đặt thư viện PyTorch. Bạn có thể tải xuống và cài đặt PyTorch từ trang web chính thức (https://pytorch.org/ (opens in a new tab)) dựa trên hệ điều hành và phiên bản Python của bạn.

  2. Nhập các thư viện cần thiết: Sau khi cài đặt PyTorch, bạn cần nhập các thư viện cần thiết cho dự án của bạn. Thông thường bao gồm PyTorch, NumPy và bất kỳ thư viện nào khác mà bạn có thể cần cho xử lý dữ liệu, hiển thị hoặc các nhiệm vụ khác.

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt

Xác định kiến trúc ResNet

Hiểu về các khối xây dựng cơ bản

  1. Các lớp tích chập: ResNet, giống như nhiều mô hình học sâu khác, sử dụng các lớp tích chập làm khối xây dựng chính cho việc trích xuất đặc trưng.

  2. Chuẩn hóa theo lô: ResNet cũng sử dụng các lớp chuẩn hóa theo lô để giúp ổn định quá trình huấn luyện và cải thiện hiệu suất của mô hình.

  3. Hàm kích hoạt: Kiến trúc ResNet thường sử dụng ReLU (đơn vị tuyến tính được chỉnh lực) như hàm kích hoạt, giúp giới thiệu tính phi tuyến vào mô hình.

  4. Các lớp Pooling: ResNet cũng có thể bao gồm các lớp pooling, chẳng hạn như max-pooling hoặc average-pooling, để giảm kích thước không gian của các bản đồ đặc trưng và giới thiệu tính không dịch chuyển.

Thực hiện khối ResNet

  1. Kết nối dư: Điểm đột phá chính của ResNet là kết nối dư, cho phép mạng bỏ qua một số lớp bằng cách thêm đầu vào của một lớp vào đầu ra của nó. Điều này giúp giảm thiểu vấn đề gradient biến mất.
class ResNetBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1):
        super(ResNetBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)
        
        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels)
            )
 
    def forward(self, x):
        residual = self.shortcut(x)
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out += residual
        out = self.relu(out)
        return out
  1. Kết nối tắt: Ngoài kết nối dư, ResNet còn sử dụng một "kết nối tắt" để đảm bảo kích thước của đầu vào và đầu ra của khối ResNet khớp nhau, nếu cần thiết.

Xây dựng mô hình ResNet đầy đủ

  1. Xếp các khối ResNet: Để tạo mô hình ResNet đầy đủ, bạn cần xếp nhiều khối ResNet lại với nhau, chỉnh số lượng lớp và số lượng bộ lọc trong mỗi khối.

  2. Điều chỉnh số lượng lớp: Các mô hình ResNet có các biến thể khác nhau, chẳng hạn như ResNet-18, ResNet-34, ResNet-50, ResNet-101 và ResNet-152, có số lượng lớp khác nhau. Số lượng lớp ảnh hưởng đến độ phức tạp và hiệu suất của mô hình.

Thực hiện ResNet-18 trong PyTorch

Xác định Mô hình ResNet-18

  1. Lớp đầu vào: Lớp đầu vào của mô hình ResNet-18 thông thường sẽ chấp nhận một bức ảnh có kích thước cụ thể, chẳng hạn 224x224 pixel.

  2. Các lớp tích chập: Các lớp tích chập ban đầu của mô hình ResNet-18 sẽ trích xuất các đặc trưng cơ bản từ ảnh đầu vào.

  3. Các khối ResNet: Phần trung tâm của mô hình ResNet-18 là sự xếp chồng nhiều khối ResNet, sử dụng các kết nối dư để cho phép huấn luyện một mạng sâu hơn.

  4. Lớp Kết nối Đầy đủ: Sau các lớp tích chập và các khối ResNet, mô hình sẽ có một lớp kết nối đầy đủ để thực hiện nhiệm vụ phân loại hoặc dự đoán cuối cùng.

  5. Lớp đầu ra: Lớp đầu ra của mô hình ResNet-18 sẽ có một số đơn vị tương ứng với số lượng lớp trong vấn đề được giải quyết.

class ResNet18(nn.Module):
    def __init__(self, num_classes=10):
        super(ResNet18, self).__init__()
        self.in_channels = 64
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
 
        self.layer1 = self._make_layer(64, 64, 2, stride=1)
        self.layer2 = self._make_layer(64, 128, 2, stride=2)
        self.layer3 = self._make_layer(128, 256, 2, stride=2)
        self.layer4 = self._make_layer(256, 512, 2, stride=2)
 
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512, num_classes)
 
    def _make_layer(self, in_channels, out_channels, num_blocks, stride):
        layers = []
        layers.append(ResNetBlock(in_channels, out_channels, stride))
        self.in_channels = out_channels
        for i in range(1, num_blocks):
            layers.append(ResNetBlock(out_channels, out_channels))
        return nn.Sequential(*layers)
 
    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
 
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
 
        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

Khởi tạo Mô hình

Để tạo một phiên bản của mô hình ResNet-18, bạn có thể tạo một thể hiện của lớp ResNet18:

model = ResNet18(num_classes=10)

In tóm tắt mô hình

Bạn có thể in ra một tóm tắt kiến trúc mô hình ResNet-18 bằng cách sử dụng hàm summary() từ thư viện torchsummary:

from torchsummary import summary
summary(model, input_size=(3, 224, 224))

Điều này sẽ cung cấp một cái nhìn tổng quan chi tiết về các lớp của mô hình, bao gồm số lượng tham số và hình dạng đầu ra của mỗi lớp.

Huấn luyện Mô hình ResNet-18

Chuẩn bị Tập dữ liệu

Tải xuống và Tải Tập dữ liệu

Trong ví dụ này, chúng ta sẽ sử dụng tập dữ liệu CIFAR-10, là một tập dữ liệu cực kỳ phổ biến được sử dụng để đánh giá và so sánh các mô hình phân loại hình ảnh. Bạn có thể tải xuống tập dữ liệu bằng module torchvision.datasets.CIFAR10:

# Tải và tải Tập dữ liệu CIFAR-10
train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transforms.ToTensor())
test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transforms.ToTensor())

Tiền xử lý Dữ liệu

Trước khi huấn luyện mô hình, bạn cần tiền xử lý dữ liệu, chẳng hạn như chuẩn hóa giá trị pixel và áp dụng các kỹ thuật tăng dữ liệu:

# Định nghĩa các biến đổi dữ liệu
transform_train = transforms.Compose([
    transforms.RandomCrop(32, padding=4),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
 
transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
 
# Tạo các data loader
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=100, shuffle=False, num_workers=2)

Xác định Vòng lặp Huấn luyện

Thiết lập Thiết bị (CPU hoặc GPU)

Để tận dụng sự tăng tốc GPU, bạn có thể di chuyển mô hình và dữ liệu lên GPU:

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)

Định nghĩa Hàm lỗi và Bộ tối ưu hóaTiếp theo, bạn cần định nghĩa hàm mất mát và bộ tối ưu hóa được sử dụng trong quá trình huấn luyện:

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)

Triển khai vòng lặp Huấn luyện

Vòng lặp huấn luyện sẽ bao gồm các bước sau:

  1. Quá trình truyền thuận qua mô hình
  2. Tính toán hàm mất mát
  3. Lan truyền ngược các độ dốc
  4. Cập nhật các tham số của mô hình
  5. Theo dõi sự mất mát và độ chính xác trong quá trình huấn luyện
num_epochs = 100
train_losses = []
train_accuracies = []
val_losses = []
val_accuracies = []
 
for epoch in range(num_epochs):
    # Quá trình huấn luyện
    model.train()
    running_loss = 0.0
    correct = 0
    total = 0
    for i, (inputs, labels) in enumerate(train_loader):
        inputs, labels = inputs.to(device), labels.to(device)
        
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)

Tối ưu hóa Mô hình

Chính quy hóa

Chính quy hóa là một kỹ thuật được sử dụng để ngăn chặn quá khớp trong các mô hình học sâu. Quá khớp xảy ra khi một mô hình hoạt động tốt trên dữ liệu huấn luyện nhưng không thể tổng quát hóa cho dữ liệu mới, chưa từng thấy trước. Các kỹ thuật chính quy hóa giúp mô hình tổng quát hơn bằng cách đặt một khoản phạt cho sự phức tạp hoặc thêm nhiễu vào quá trình huấn luyện.

Một kỹ thuật chính quy phổ biến là chính quy hóa L2, còn được gọi là giảm trọng lượng. Phương pháp này thêm một thuật ngữ khoản phạt vào hàm mất mát là tỉ lệ với bình phương độ lớn của trọng số của mô hình. Hàm mất mát với chính quy hóa L2 có thể được viết như sau:

mất mát = mất mát ban đầu + lambda * tổng(w^2)

trong đó lambda là mức độ chính quy, và w là trọng số của mô hình.

Một kỹ thuật chính quy hóa phổ biến khác là Dropout. Dropout ngẫu nhiên đặt một phần của các hoạt động trong một lớp bằng không trong quá trình huấn luyện, hiệu quả giảm khả năng mô hình và buộc nó học các tính năng mạnh mẽ hơn. Điều này giúp ngăn chặn quá khớp và có thể cải thiện hiệu suất tổng quát hóa của mô hình.

Dưới đây là một ví dụ về cách triển khai Dropout trong một mô hình PyTorch:

import torch.nn as nn
 
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.fc1 = nn.Linear(64, 128)
        self.dropout = nn.Dropout(p=0.5)
        self.fc2 = nn.Linear(128, 10)
 
    def forward(self, x):
        x = self.fc1(x)
        x = self.dropout(x)
        x = self.fc2(x)
        return x

Trong ví dụ này, lớp Dropout được áp dụng sau lớp fully connected đầu tiên, với tỷ lệ Dropout là 0,5, có nghĩa là 50% các hoạt động sẽ được đặt ngẫu nhiên thành không trong quá trình huấn luyện.

Thuật toán Tối ưu hóa

Lựa chọn thuật toán tối ưu hóa có thể ảnh hưởng đáng kể đến hiệu suất và hội tụ của một mô hình học sâu. Dưới đây là một số thuật toán tối ưu hóa phổ biến được sử dụng trong học sâu:

Stochastic Gradient Descent (SGD)

SGD là thuật toán tối ưu hóa cơ bản nhất, trong đó các độ dốc được tính trên một ví dụ huấn luyện duy nhất hoặc một lô nhỏ các ví dụ, và các trọng số được cập nhật tương ứng. SGD có thể chậm để hội tụ, nhưng nó đơn giản và hiệu quả.

import torch.optim as optim
 
model = MyModel()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

Adam

Adam (Adaptive Moment Estimation) là một thuật toán tối ưu hóa tiên tiến hơn, tính toán tỉ lệ học tập thích ứng cho mỗi tham số. Nó kết hợp lợi ích của Động lượng và RMSProp, làm cho nó trở thành một lựa chọn phổ biến cho nhiều nhiệm vụ học sâu.

optimizer = optim.Adam(model.parameters(), lr=0.001)

AdaGrad

AdaGrad (Adaptive Gradient) là một thuật toán tối ưu hóa điều chỉnh tỷ lệ học tập cho mỗi tham số dựa trên các độ dốc lịch sử. Nó hiệu quả cho dữ liệu thưa, nhưng nó có thể bị giảm tỷ lệ học tập mạnh theo thời gian.

optimizer = optim.Adagrad(model.parameters(), lr=0.01)

RMSProp

RMSProp (Root Mean Square Propagation) là một thuật toán tối ưu hóa tỷ lệ học tập điều chỉnh khác, duy trì một trung bình trượt của các độ dốc bình phương. Nó đặc biệt hữu ích cho các mục tiêu không ổn định, như mục tiêu tìm thấy trong các mạng nơ-ron lặp đi lặp lại.

optimizer = optim.RMSprop(model.parameters(), lr=0.001, alpha=0.99)

Lựa chọn thuật toán tối ưu hóa phụ thuộc vào vấn đề cụ thể, cấu trúc của mô hình và đặc điểm của dữ liệu. Thường là một ý tưởng tốt để thử nghiệm với các thuật toán khác nhau và so sánh hiệu suất của chúng trên nhiệm vụ của bạn.

Học chuyển giao

Học chuyển giao là một kỹ thuật trong đó mô hình được huấn luyện trên một tập dữ liệu lớn được sử dụng làm điểm khởi đầu cho mô hình trên một tác vụ khác nhưng liên quan. Điều này có thể đặc biệt hữu ích khi tập dữ liệu đích nhỏ, vì nó cho phép mô hình tận dụng các tính năng đã học trên tập dữ liệu lớn.

Một phương pháp chuyển giao thông dụng trong học sâu là sử dụng mô hình có sẵn, như những mô hình có sẵn cho các nhiệm vụ xử lý hình ảnh hoặc xử lý ngôn ngữ tự nhiên phổ biến, và điều chỉnh mô hình trên tập dữ liệu mục tiêu. Điều này bao gồm đóng băng các lớp thấp hơn của mô hình có sẵn và chỉ huấn luyện các lớp cao hơn trên dữ liệu mới.

Dưới đây là một ví dụ về cách điều chỉnh mô hình ResNet có sẵn để phân loại hình ảnh trong PyTorch:

import torchvision.models as models
import torch.nn as nn
 
# Tải mô hình ResNet có sẵn
resnet = models.resnet18(pretrained=True)
 
# Đóng băng các tham số của mô hình có sẵn
for param in resnet.parameters():
    param.requires_grad = False
 
# Thay thế lớp cuối cùng bằng một lớp fully connected mới
num_features = resnet.fc.in_features
resnet.fc = nn.Linear(num_features, 10)  # Giả sử 10 lớp
 
# Huấn luyện mô hình trên tập dữ liệu mới
optimizer = optim.Adam(resnet.fc.parameters(), lr=0.001)

Trong ví dụ này, chúng ta trước tiên tải mô hình ResNet18 có sẵn và đóng băng các tham số của các lớp thấp hơn. Sau đó, chúng ta thay thế lớp fully connected cuối cùng bằng một lớp mới có số đầu ra phù hợp với nhiệm vụ mục tiêu của chúng ta (10 lớp trong trường hợp này). Cuối cùng, chúng ta huấn luyện mô hình bằng cách sử dụng bộ tối ưu hóa Adam, chỉ cập nhật các tham số của lớp fully connected mới.

Học chuyển giao có thể cải thiện đáng kể hiệu suất của các mô hình học sâu, đặc biệt là khi tập dữ liệu đích nhỏ. Đây là một kỹ thuật mạnh mẽ có thể tiết kiệm thời gian và nguồn lực trong quá trình phát triển mô hình.

Khả năng giải thích Mô hình

Khi các mô hình học sâu trở nên phức tạp và phổ biến hơn, việc cần thiết của việc giải thích mô hình đã trở nên ngày càng quan trọng. Khả năng giải thích đề cập đến khả năng hiểu và giải thích quyết định nội bộ của một mô hình.

Một kỹ thuật phổ biến để cải thiện khả năng giải thích của mô hình là sử dụng cơ chế chú ý. Chú ý cho phép mô hình tập trung vào các phần quan trọng nhất của đầu vào khi đưa ra dự đoán, và nó có thể được trực quan hóa để hiểu được những đặc trưng mà mô hình đang sử dụng.

Dưới đây là một ví dụ về cách triển khai cơ chế chú ý trong một mô hình PyTorch cho một nhiệm vụ xử lý ngôn ngữ tự nhiên:

import torch.nn as nn
import torch.nn.functional as F
 
class AttentionModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim):
        super(AttentionModel, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, hidden_dim, bidirectional=True, batch_first=True)
        self.attention = nn.Linear(hidden_dim * 2, 1)
 
    def forward(self, input_ids):
        # Nhúng đầu vào
        embedded = self.embedding(input_ids)
 
        # Truyền đầu vào đã nhúng qua LSTM
        lstm_output, _ = self.lstm(embedded)
 
        # Tính toán trọng số chú ý
        attention_weights = F.softmax(self.attention(lstm_output), dim=1)
 
        # Tính tổng có trọng số của các đầu ra của LSTM
        context = torch.sum(attention_weights * lstm_output, dim=1)
 
        return context

Trong ví dụ này, cơ chế chú ý được triển khai dưới dạng một lớp tuyến tính nhận các đầu ra của LSTM làm đầu vào và tạo ra một tập hợp trọng số chú ý. Sau đó, các trọng số này được sử dụng để tính tổng có trọng số của các đầu ra của LSTM, đó là đầu ra cuối cùng của mô hình.

Bằng cách trực quan hóa các trọng số chú ý, bạn có thể thu được những hiểu biết về các phần của đầu vào mà mô hình đang tập trung vào khi đưa ra dự đoán. Điều này giúp bạn hiểu quá trình ra quyết định của mô hình và xác định các thành phần tiềm năng có khả năng thiên vị hoặc các vùng cần cải thiện.

Một kỹ thuật khác để cải thiện khả năng giải thích của mô hình là sử dụng phân tích ý nghĩa đặc trưng. Điều này liên quan đến xác định các đặc trưng quan trọng nhất mà mô hình đang sử dụng để đưa ra dự đoán. Một phương pháp phổ biến để thực hiện điều này là giá trị Shapley, cung cấp một cách tính đóng góp của mỗi đặc trưng vào đầu ra của mô hình.

Cải thiện khả năng giải thích của mô hình là một lĩnh vực nghiên cứu quan trọng trong học sâu, vì nó có thể giúp xây dựng niềm tin vào những mô hình mạnh mẽ này và đảm bảo chúng được sử dụng một cách có trách nhiệm.

Kết luận

Trong bài hướng dẫn này, chúng ta đã đề cập đến một loạt các chủ đề liên quan đến học sâu, bao gồm tối ưu hóa mô hình, học chuyển giao và khả năng giải thích của mô hình. Chúng ta đã thảo luận về các kỹ thuật như chính quy hóa, thuật toán tối ưu hóa và cơ chế chú ý, và cung cấp ví dụ về cách triển khai các khái niệm này trong PyTorch.

Khi học sâu tiếp tục phát triển và trở nên phổ biến hơn, quan trọng để hiểu các chủ đề nâng cao này và cách áp dụng chúng vào các dự án của bạn. Bằng cách thành thạo những kỹ thuật này, bạn sẽ được trang bị tốt hơn để xây dựng các mô hình học sâu hiệu quả, mạnh mẽ và có khả năng giải thích có thể giải quyết một loạt các vấn đề.

Hãy nhớ rằng học sâu là một lĩnh vực thay đổi nhanh, và quan trọng để cập nhật với các nghiên cứu và phương pháp tốt nhất mới nhất. Hãy tiếp tục khám phá, thử nghiệm và học hỏi, và bạn sẽ trở thành một chuyên gia về học sâu.