- vừa được xem lúc

Giới thiệu PyTorch: Có bột mới gột nên hồ

0 0 1

Người đăng: Le Thanh Cong

Theo Viblo Asia

Trong loạt bài viết xây dựng mô hình ngôn ngữ lớn, PyTorch chính là công cụ quan trọng nhất được sử dụng. Tuy nhiên, chúng ta mới chỉ dừng ở mức sử dụng mà chưa có thời gian tìm hiểu kỹ hơn. Bài viết hôm nay bổ sung thiếu sót đó.

1. Làm quen với PyTorch

PyTorch là một thư viện deep learning nguồn mở dành cho Python. Được phát triển bởi Meta của anh Mark Xoăn.

Đối trọng lớn của PyTorch trên thị trường là Tensorflow phát triển bởi Google.

Đây được xem là 2 thư viện phổ biến nhất cho các nhà nghiên cứu và phát triển trí tuệ nhân tạo.

1.1. Các thành phần tạo nên PyTorch

Có thể chia PyTorch ra làm 3 phần:

    1. Tensor libary: Có nhiệm vụ biểu diễn và xử lý dữ liệu. Với tensor library, dữ liệu sẽ được xử lý rất nhanh trên GPU.
    1. Automatic differentiation engine hay còn gọi là autograd: Chưa logic có nhiệm vụ tính toán, tối ưu trong quá trình huấn luyện.
    1. Deep learning libary: Chứa sẵn các mô-đun, công cụ về học sâu. Giúp người dùng gọi ra sử dụng 1 cách tiện lợi.

1.2 Cài đặt

Truy cập https://pytorch.org/get-started/locally/ để xem cách cài đặt PyTorch theo từng nền tảng.

Tôi thì chọn cách cài trên môi trường ảo với Anaconda cho đỡ rác máy.

# Linux x86
curl -O https://repo.anaconda.com/archive/Anaconda3-2024.10-1-Linux-x86_64.sh
chmod +x Anaconda3-2024.10-1-Linux-x86_64.sh
./Anaconda3-2024.10-1-Linux-x86_64.sh
conda init [bash/zsh/fish]
# Instal python
conda create -n myenv python=3.12
conda activate myenv
conda install pytorch::pytorch

Sau khi cài đặt xong, kiểm tra phiên bản trên console python

import torch
torch.__version__

PyTorch với Apple Silicon

Nếu bạn sở hữu máy Mac dùng chip Apple Silicon (như các mẫu M1, M2, M3 ...), thì có thể tăng tốc độ thực thi mã PyTorch thông qua Metal Performance Shaders - MPS.

Để kiểm tra xem máy Mac của bạn có hỗ trợ MPS không, dùng lệnh.

print(torch.backends.mps.is_available())

2. Tìm hiểu tensors

Tensors là một dạng biểu diễn Toán học tổng quát, được đặc trưng bởi rank (số chiều). Chúng là khái niệm mở rộng của scalars (vô hướng), vectors (vectơ), và matrices (ma trận).

Trên ảnh:

  • Số 2 là tensor 0 chiều
  • Vectơ là tensor 1 chiều
  • Ma trận là tensort 2 chiều.

Khi tính toán, tensor đóng vai trò chứa dữ liệu. Tựu chung lại thì tensor tương tự như mảng ở các ngôn ngữ lập trình.

Khởi tạo các tensor

import torch
import numpy as np # 0D tensor
tensor0d = torch.tensor(1) # 1D tensor
tensor1d = torch.tensor([1, 2, 3]) # 2D tensor
tensor2d = torch.tensor([[1, 2], [3, 4]]) # 3D tensor
tensor3d_1 = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) # tạo 1 3D tensor from NumPy array
ary3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
tensor3d_2 = torch.tensor(ary3d) # Copy từ numpy

Tensor PyTorch tương tự như các mảng NumPy nhưng có một số tính năng bổ sung như tăng tốc tính toán trên GPU ....

Các kiểu dữ liệu

Các kiểu dữ liệu của tensor cũng không khác so với các kiểu dữ liệu trong 1 ngôn ngữ lập trình. Xem tất cả các kiểu dữ liệu tại đây

int64float32 là 2 kiểu dữ liệu mặc định cho số nguyên và số thập phân.

tensor1d = torch.tensor([1, 2, 3])
print(tensor1d.dtype) # torch.int64 floatvec = torch.tensor([1.0, 2.0, 3.0])
print(floatvec.dtype) # torch.float32

Có thể thay đổi kiểu dữ liệu bằng cách

floatvec = tensor1d.to(torch.float32)
print(floatvec.dtype) # torch.float32
print(floatvec) # tensor([1., 2., 3.])

Các lệnh cơ bản thường dùng với tensor

  • Lệnh tạo mới tensor
tensor2d = torch.tensor([[1, 2, 3], [4, 5, 6]])

  • Truy cập shape (kích thước) của tensor
tensor0d = torch.tensor(1) # 1D tensor
tensor1d = torch.tensor([1, 2, 3]) # 2D tensor
tensor2d = torch.tensor([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) # 3D tensor
tensor3d_1 = torch.tensor(
[ [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] ], [ [4, 3, 2, 1], [8, 7, 6, 5], [12, 11, 10, 9] ]
]) print(tensor0d.shape) # In ra: torch.Size([])
print(tensor1d.shape) # In ra: torch.Size([3])
print(tensor2d.shape) # In ra: torch.Size([2, 5])
print(tensor3d_1.shape) # In ra: torch.Size([2, 3, 4]) 
  • Tensor 0D thì không có shape
  • Tensor 1D thì thông số shape bằng số phần tử
  • Tensor 2D thì có shape bằng (số hàng, số cột)
  • Tensor 3D thì có 2 phần tử, mỗi phần từ có 3 hàng x 4 cột => torch.Size([2, 3, 4])

  • Lệnh reshape: Đổi thích kích tensor
tensor2d = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(tensor2d.reshape(3, 2)) """
tensor([[1, 2], [3, 4], [5, 6]])
"""

Lệnh view cũng tương tự reshape. Chỉ khác nhau về cách chúng xử lý bộ nhớ:

  • view yêu cần dữ liệu phải liên tục trên bộ nhớ, nếu ko sẽ trả về lỗi
  • reshape thì không cần

  • Chuyển vị của tensor (transpose)
print(tensor2d.T)
"""
tensor([[1, 4], [2, 5], [3, 6]])
"""

< br />

  • Nhân 2 tensor với nhau
print(tensor2d.matmul(tensor2d.T))
"""
tensor([[14, 32], [32, 77]])
"""

Hoặc

print(tensor2d @ tensor2d.T)

3. Triển khai mạng nơ-ron đa lớp với PyTorch

Lý thuyết đủ rồi, giờ bắt tay vào code thôi. Phần này ta sẽ dùng PyTorch triển khai một mạng nơ-ron đa lớp gồm 2 lớp ẩn (hidden layer).

3.1 Khởi tạo mạng nơ-ron

Trước tiên cùng xem qua mạng nơ-ron có hình thù như thế nào qua hình minh hoạ dưới đây.

Mạng nơ-ron trên có thông số như sau:

  • Đầu vào gồm 10 đơn vị (units).
  • Lớp ẩn thứ nhất có 6 nodes và 1 bias unit. bias là một node đặc biệt luôn có giá trị 1.
  • Lớp ẩn thứ hai có 4 node và 1 bias.
  • Đầu ra co lại chỉ còn 3 đơn vị.

class NeuralNetwork(torch.nn.Module): def __init__(self, num_inputs, num_outputs): super().__init__() # Sequential là một container cho phép bạn "xếp chồng" các lớp lại với nhau theo một cách tuần tự self.layers = torch.nn.Sequential( # hidden layer thứ nhất # Một lớp có cả Linear và ReLU để học được các các đặc trưng tuyến tính và phi tuyến torch.nn.Linear(num_inputs, 30), torch.nn.ReLU(), # hidden layer thứ hai torch.nn.Linear(30, 20), torch.nn.ReLU(), # lớp đầu ra torch.nn.Linear(20, num_outputs), ) def forward(self, x): logits = self.layers(x) return logits
  • Tạo mạng nơ-ron từ class NeuralNetwork
# Đầu vào 50, nhiều gấp 5 lần hình minh hoạ
model = NeuralNetwork(50, 3)
print(model) # In ra thông số các lớp của mạng nơ-ron """
NeuralNetwork( (layers): Sequential( (0): Linear(in_features=50, out_features=30, bias=True) (1): ReLU() (2): Linear(in_features=30, out_features=20, bias=True) (3): ReLU() (4): Linear(in_features=20, out_features=3, bias=True) )
)
"""
  • Xem tổng số tham số của mạng là bao nhiêu ?
num_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print("Total number of trainable model parameters:", num_params) # Total number of trainable model parameters: 2213

  • Truyền đầu vào và xem đầu ra của mạng nơ-ron
# Dùng seed random giúp mô hình khi khởi tạo ở mỗi lần chạy đều có bộ tham số như nhau
torch.manual_seed(123) X = torch.rand((1, 50)) # Đang ko huấn luyện nên dùng tuỳ chọn `no_grad` để code chạy nhanh hơn
with torch.no_grad(): out = model(X)
print(out) # tensor([[-0.1262, 0.1080, -0.1792]], grad_fn=<AddmmBackward0>)

  • Có thể chuẩn hoá thêm với softmax
with torch.no_grad(): out = torch.softmax(model(X), dim=1)
print(out) # tensor([[0.3113, 0.3934, 0.2952]])

3.2 DataLoader

PyTorch định nghĩa sẵn 2 class Dataset và DataLoader.

  • Dataset được dùng để định nghĩa cách dữ liệu được nạp.
  • DataLoader xử lý việc trộn dữ liệu (shuffle) và chia chúng thành các batch.

Ví dụ với Dataset:

# Giả dụ có 2 tập train và test như sau:
X_train = torch.tensor([ [-1.2, 3.1], [-0.9, 2.9], [-0.5, 2.6], [2.3, -1.1], [2.7, -1.5]
]) y_train = torch.tensor([0, 0, 0, 1, 1]) X_test = torch.tensor([ [-0.8, 2.8], [2.6, -1.6],
]) y_test = torch.tensor([0, 1]) # Định nghĩa class ToyDataset nạp dữ liệu
# Một custom Dataset class phải triển khai 3 hàm: __init__, __len__, and __getitem__
class ToyDataset(Dataset): def __init__(self, X, y): self.features = X self.labels = y def __getitem__(self, index): one_x = self.features[index] one_y = self.labels[index] return one_x, one_y def __len__(self): return self.labels.shape[0] train_ds = ToyDataset(X_train, y_train)
test_ds = ToyDataset(X_test, y_test)

Thử in ra một số thuộc tính

print(train_ds.labels)
print(train_ds.__getitem__(0)) """
tensor([0, 0, 0, 1, 1])
(tensor([-1.2000, 3.1000]), tensor(0))
"""

Ví dụ với DataLoader

Load dữ liệu xong với Dataset, tiến hành dùng DataLoader để chia dữ liệu thành các batch trước khi huấn luyện.

from torch.utils.data import DataLoader torch.manual_seed(123) train_loader = DataLoader( dataset=train_ds, batch_size=2, # Một batch có 2 dữ liệu đầu vào shuffle=True, # Có xáo dữ liệu không ? Mặc định shuffle=false num_workers=0 # sử dụng bao nhiêu tiến trình con để load dữ liệu
) test_ds = ToyDataset(X_test, y_test) test_loader = DataLoader( dataset=test_ds, batch_size=2, shuffle=False, num_workers=0
)

In thử ra train_loader

for idx, (x, y) in enumerate(train_loader): print(f"Batch {idx+1}:", x, y) """
Batch 1: tensor([[-1.2000, 3.1000], [-0.9000, 2.9000]]) tensor([0, 0])
Batch 2: tensor([[-0.5000, 2.6000], [ 2.3000, -1.1000]]) tensor([0, 1])
Batch 3: tensor([[ 2.7000, -1.5000]]) tensor([1])
"""

3.3 Tiến hành huấn luyện

Mục đích là sau mỗi lần huấn luyện, giá trị mất mát giảm dần => tức là mô hình đang dự đoán đúng nhiều hơn.

import torch.nn.functional as F torch.manual_seed(123)
model = NeuralNetwork(num_inputs=2, num_outputs=2) # 2 đầu vào và 2 đầu ra
optimizer = torch.optim.SGD(model.parameters(), lr=0.5) num_epochs = 3 # Huấn luyện 3 chu kỳ for epoch in range(num_epochs): model.train() for batch_idx, (features, labels) in enumerate(train_loader): logits = model(features) loss = F.cross_entropy(logits, labels) # Tính hàm mất mát (Loss function) optimizer.zero_grad() loss.backward() optimizer.step() ### LOGGING print(f"Epoch: {epoch+1:03d}/{num_epochs:03d}" f" | Batch {batch_idx:03d}/{len(train_loader):03d}" f" | Train/Val Loss: {loss:.2f}") model.eval() # Optional model evaluation """
Epoch: 001/003 | Batch 000/002 | Train/Val Loss: 0.75
Epoch: 001/003 | Batch 001/002 | Train/Val Loss: 0.65
Epoch: 002/003 | Batch 000/002 | Train/Val Loss: 0.44
Epoch: 002/003 | Batch 001/002 | Train/Val Loss: 0.13
Epoch: 003/003 | Batch 000/002 | Train/Val Loss: 0.03
Epoch: 003/003 | Batch 001/002 | Train/Val Loss: 0.00 """

Lưu ý: Do dữ liệu mẫu của chúng ta đơn giản nên quá trình huấn luyện diễn ra nhanh và đạt kết quả tối ưu. Đạt kết quả Loss = 0 trong các bài toán thực tế là rất hiếm và thường không phải mục đích vì có thể xảy ra tình trạng overfit.

Xem độ chính xác của mô hình trên 2 tập train và val.

def compute_accuracy(model, dataloader): model = model.eval() correct = 0.0 total_examples = 0 for idx, (features, labels) in enumerate(dataloader): with torch.no_grad(): logits = model(features) predictions = torch.argmax(logits, dim=1) compare = labels == predictions correct += torch.sum(compare) total_examples += len(compare) return (correct / total_examples).item() compute_accuracy(model, train_loader) # In ra: 1.0 compute_accuracy(model, test_loader) # In ra: 1.0

Đều đúng 100 % 😀

3.4 Lưu lại và load lại mô hình

Lưu lại mô hình

  • Lưu vào file có đuôi .pth
torch.save(model.state_dict(), "model.pth")

Load mô hình đã lưu

model = NeuralNetwork(2, 2) # Thông số phải khớp với mô hình đã lưu, nếu không báo lỗi
model.load_state_dict(torch.load("model.pth", weights_only=True))

4. Tối ưu hóa hiệu suất với GPU

4.1 PyTorch và vấn đề device

  • Mặc định, các tensor được xử lý trên CPU
tensor_1 = torch.tensor([1., 2., 3.])
tensor_2 = torch.tensor([4., 5., 6.])
print(tensor_1 + tensor_2) # tensor([5., 7., 9.])

  • Hàm tođể chuyển tensor sang thiết bị khác (cụ thể ở đây là GPU)
tensor_1 = tensor_1.to("cuda")
tensor_2 = tensor_2.to("cuda")
print(tensor_1 + tensor_2) # tensor([5., 7., 9.], device='cuda:0')

Chú ý: Tất cả tensor đều phải nằm trên một thiết bị duy nhất, nếu không sẽ báo lỗi.

Ví dụ:

# tensor_1 trên CPU
# tensor_2 trên GPU tensor_1 = tensor_1.to("cpu") print(tensor_1 + tensor_2)

4.2 GPU đơn

Tốc độ huấn luyện trên GPU là nhanh hơn so với CPU. Do đó, khi huấn luyện ưu tiên dùng GPU. Nếu máy tính không có GPU thì đành chấp nhận dùng CPU.

# Kiểm tra nếu máy có GPU thì xài, ko thì dùng CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

Trên máy Mac dùng Apple Silicon chip (M1, M2, M3 ...) thì dùng câu lệnh sau để chọn device

device = torch.device( "mps" if torch.backends.mps.is_available() else "cpu"
)

4.3 Huấn luyện trên nhiều GPU

Gọi là Distributed training có ý tưởng chính là chia việc huấn luyện một khối lượng lớn dữ liệu ra trên nhiều thiết bị thay vì một. Điều này giúp giảm đáng kể thời gian huấn luyện (tất nhiên là phải có nhiều tiền để mua nhiều GPU 😆).

Distributed training có nhiều cách tiếp cận. Ở đây ta sẽ xem qua chiến lược ** DistributedDataParallel (DDP)** của PyTorch.



    1. PyTorch khởi chạy mỗi tiến trình trên mỗi GPU sau đó nhận và lưu một bản sao của mô hình.
    1. Trong mỗi chu kỳ huấn luyện, mỗi GPU sẽ nhận một mini-batch (Mb) khác nhau từ DataLoader.
    1. GPU tính toán trên mỗi Mb.
    1. Sau khi tính gradient trên từng GPU. PyTorch sẽ tổng hợp kết quả, tính trung bình và đồng bộ kết quả với các GPU.

Tài liệu tham khảo

https://github.com/rasbt/LLMs-from-scratch/tree/main/appendix-A

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tấn công và phòng thủ bậc nhất cực mạnh cho các mô hình học máy

tấn công bậc nhất cực mạnh = universal first-order adversary. Update: Bleeding edge của CleverHans đã lên từ 3.1.0 đến 4.

0 0 43

- vừa được xem lúc

[Deep Learning] Key Information Extraction from document using Graph Convolution Network - Bài toán trích rút thông tin từ hóa đơn với Graph Convolution Network

Các nội dung sẽ được đề cập trong bài blog lần này. . Tổng quan về GNN, GCN. Bài toán Key Information Extraction, trích rút thông tin trong văn bản từ ảnh.

0 0 229

- vừa được xem lúc

Trích xuất thông tin bảng biểu cực đơn giản với OpenCV

Trong thời điểm nhà nước đang thúc đẩy mạnh mẽ quá trình chuyển đổi số như hiện nay, Document Understanding nói chung cũng như Table Extraction nói riêng đang trở thành một trong những lĩnh vực được quan tâm phát triển và chú trọng hàng đầu. Vậy Table Extraction là gì? Document Understanding là cái

0 0 235

- vừa được xem lúc

Con đường AI của tôi

Gần đây, khá nhiều bạn nhắn tin hỏi mình những câu hỏi đại loại như: có nên học AI, bắt đầu học AI như nào, làm sao tự học cho đúng, cho nhanh, học không bị nản, lộ trình học AI như nào... Sau nhiều lần trả lời, mình nghĩ rằng nên viết hẳn một bài để trả lời chi tiết hơn, cũng như để các bạn sau này

0 0 163

- vừa được xem lúc

[B5'] Smooth Adversarial Training

Đây là một bài trong series Báo khoa học trong vòng 5 phút. Được viết bởi Xie et. al, John Hopkins University, trong khi đang intern tại Google. Hiện vẫn là preprint do bị reject tại ICLR 2021.

0 0 48

- vừa được xem lúc

Deep Learning với Java - Tại sao không?

Muốn tìm hiểu về Machine Learning / Deep Learning nhưng với background là Java thì sẽ như thế nào và bắt đầu từ đâu? Để tìm được câu trả lời, hãy đọc bài viết này - có thể kỹ năng Java vốn có sẽ giúp bạn có những chuyến phiêu lưu thú vị. DJL là tên viết tắt của Deep Java Library - một thư viện mã ng

0 0 152