PyTorch Tutorial 1: Kiến thức nền tảng về PyTorch
Chào mừng bạn đến với blog PyTorch của tôi – nơi khởi đầu hành trình khám phá sức mạnh của deep learning! Trong bài viết này, chúng ta sẽ tìm hiểu những kiến thức nền tảng của PyTorch, từ cách tạo và thao tác với tensor đến các phép biến đổi kích thước và chuyển đổi dữ liệu, cũng như cách tận dụng GPU để tăng tốc quá trình tính toán. Hãy cùng nhau biến những con số và ma trận thành công cụ đột phá cho dự án của bạn!
1. Pytorch là gì?
PyTorch là một framework mã nguồn mở dành cho machine learning (học máy) và deep learning (học sâu). Nó được phát triển bởi Facebook AI Research (FAIR) và được sử dụng rộng rãi trong cộng đồng nghiên cứu và công nghiệp nhờ vào tính linh hoạt, dễ sử dụng và khả năng tính toán trên GPU.
2. Ứng dụng của PyTorch
PyTorch cho phép bạn xử lý dữ liệu và viết thuật toán machine learning bằng Python.
Một số ứng dụng cụ thể:
- Xây dựng và huấn luyện các mô hình Neural Network
- PyTorch có thể triển khai trên điện thoại với PyTorch Mobile
3. Ai đang sử dụng PyTorch
- Meta (Facebook): Sử dụng PyTorch để phát triển AI và mô hình deep learning cho các sản phẩm như Instagram, Facebook AI Research (FAIR).
- Tesla: Dùng PyTorch để huấn luyện mô hình thị giác máy tính cho hệ thống tự lái (Autopilot & FSD). Andrej Karpathy (cựu giám đốc AI Tesla) đã chia sẻ nhiều về việc này tại PyTorch DevCon 2019, Tesla AI Day 2021.
- Microsoft: Tích hợp PyTorch vào Azure Machine Learning, phát triển các mô hình AI trên cloud.
- OpenAI: Dùng PyTorch để xây dựng các mô hình AI tiên tiến như GPT-3, GPT-4, DALL·E.
4. Vì sao nên sử dụng PyTorch
PyTorch được yêu thích và sử dụng rộng rãi vì những lý do sau:
- Phổ biến trong nghiên cứu AI: Hỗ trợ dynamic computation graph, giúp thay đổi kiến trúc mô hình dễ dàng mà không cần định nghĩa trước.
- Hiệu suất cao, hỗ trợ GPU mạnh mẽ: Giúp quản lý GPU (CUDA) một cách tự động, làm cho mã nguồn chạy nhanh mà không cần cấu hình phức tạp
- Dễ sử dụng, linh hoạt: Cú pháp đơn giản, thân thiện với Python giúp dễ dàng viết thuật toán, trực quan và dễ debug hơn so với các framework khác như TensorFlow. Thư viện hỗ trợ phong phú torchaudio (Xử lý âm thành), torchvision (xử lý ảnh), torchtext (xử lý ngôn ngữ tự nhiên)
- Được tin dùng bởi các công ty lớn: Tesla, Meta (rõ ràng :)) ), Microsoft, OpenAI
5 Giới thiệu về Tensor trong PyTorch
5.1 Tạo tensor
Tạo scalar (một số vô hướng), thuộc kiểu type torch.Tensor.
1
2
3
# Scalar
scalar = torch.tensor(7)
scalar
Bởi vì nó là một số vô hướng nên ta có thể lấy được giá trị của nó bằng cách dùng phương thức item() (Điều này có nghĩa là vector, matrix, … không dùng được phương thức này nhé)
1
2
# Get the Python number within a tensor (only works with one-element tensors)
scalar.item() # output: 7
Kiểm tra số chiều → thuộc tính ndim (output: int)
Kiểm tra thông tin chiều cụ thể → thuộc tính shape (output: torch.Size)
5.2 Tạo random tensor
Trong các mô hình học máy, học sâu các kiểu thì giá trị ban đầu của các parameters thông thường là random hoặc zero → Cách tạo?
1
2
3
4
5
6
7
8
9
# Create a random tensor of size (3, 4)
random_tensor = torch.rand(size=(3, 4))
random_tensor, random_tensor.dtype
# Output:
(tensor([[0.6541, 0.4807, 0.2162, 0.6168],
[0.4428, 0.6608, 0.6194, 0.8620],
[0.2795, 0.6055, 0.4958, 0.5483]]),
torch.float32)
5.3 Zeros và Ones
Thông thường khi làm việc với tensor ta cũng cần tạo các giá trị init là 0 hoặc 1.
1
2
3
# Create a tensor of all zeros
zeros = torch.zeros(size=(3, 4))
zeros, zeros.dtype
1
2
3
# Create a tensor of all ones
ones = torch.ones(size=(3, 4))
ones, ones.dtype
5.4 Tạo tensor arrange và like
Bạn có thể tạo một tensor từ một giá trị đến một giá trị với step tương ứng (Mình thường dùng để vẽ đồ thị cho nó smooth):
1
2
3
# Create a range of values 0 to 10
zero_to_ten = torch.arange(start=0, end=10, step=1)
zero_to_ten
Một cách tạo zeros, và ones tensor một cách nhanh chóng là dùng hai phương thức zeros_like() và ones_like() với parameter input:
1
2
3
# Can also create a tensor of zeros similar to another tensor
ten_zeros = torch.zeros_like(input=zero_to_ten) # will have same shape
ten_zeros
5.5 Tensor Datatype
Có nhiều kiểu dữ liệu trong PyTorch.
Một số sẽ phù hợp với CPU, một số cho hiệu năng tốt hơn với GPU (ví dụ torch.cuda).
Các kiểu float: torch.float32 (torch.float); torch.float16 (torch.half); torch.float64 (torch.double)
Các kiểu int tương tự cũng có 8-bit, 16-bit, 32-bit, 64-bit
Số bit các cao (tính toán càng chi tiết) thì càng chính xác, tuy nhiên tốc độ tính toán lại chậm
Một trong số những vấn đề phổ biến khi sử dụng PyTorch chính là Tensor Datatype và device:
- Pytorch sẽ thích hai tensor cùng type tương tác với nhau
- Pytorch sẽ thích hai tensor cùng device (chỉ cpu hoặc chỉ gpu) làm việc với nhau
6. Một vài thông tin liên quan từ tensor
3 thông tin (tương ứng với 3 thuộc tính) phổ biến nhất khi sử dụng tensor là:
- shape
- dtype
- device
1
2
3
4
5
6
7
8
# Create a tensor
some_tensor = torch.rand(3, 4)
# Find out details about it
print(some_tensor)
print(f"Shape of tensor: {some_tensor.shape}")
print(f"Datatype of tensor: {some_tensor.dtype}")
print(f"Device tensor is stored on: {some_tensor.device}") # will default to CPU
7. Các phép tính trên tensor
7.1 Các phép tính đơn giản
- Phép công
+ - Phép trừ
- - Phép nhân vô hướng
*hoặctorch.multiply
Chỉ cần chú ý:
1
torch.multiply(tensor, 10)
7.2 Phép nhân ma trận
Đây là một trong những phép toán phổ biến nhất trong ML và DL → cũng là nơi xảy ra lỗi nhiều
Phép nhân ma trận được triển khai bằng torch.matmul()
1
torch.matmul(tensor, tensor)
Bạn cũng có thể thay thế bằng dấu @ (mặc dù không khuyến khích → ta nên thống nhất dùng torch.matmul() để đồng bộ và dễ debug)
1
tensor @ tensor
7.3 Phép transpose
Phép chuyển vị (transpose) có thể triển khai theo 2 cách:
tensor_A.Ttorch.transpose(tensor_A, dim_0, dim_1): Bạn cứ hình dung size của A sẽ là (a, b, c) → hoán đổi chiềudim_0↔dim_1. Ví dụ: nếudim_0=0vàdim_1=1thì size của A bây giờ là: (b, a, c)1 2 3 4 5 6 7 8
# Tạo một tensor 2x3 x = torch.tensor([[1, 2, 3], [4, 5, 6]]) # Hoán đổi chiều 0 và chiều 1 x_transposed = torch.transpose(x, 0, 1) print(x_transposed)
8. Lỗi phổ biến nhất trong PyTorch
Một lỗi phổ biến trong PyTorch là “shape mismatch” khi nhân ma trận. Cụ thể, khi nhân hai tensor, số cột của tensor bên trái phải bằng số hàng của tensor bên phải. Nếu không, sẽ xuất hiện lỗi.
Thường cách xử lý thông thường sẽ giúp phép transpose
9. Tìm min, max, mean, sum, …
1
2
3
4
5
print(f"Minimum: {x.min()}")
print(f"Maximum: {x.max()}")
# print(f"Mean: {x.mean()}") # this will error
print(f"Mean: {x.type(torch.float32).mean()}") # won't work without float datatype
print(f"Sum: {x.sum()}")
10. Positional min, max
Tức là tìm vị trí chứa phần tử lớn nhất hoặc nhỏ nhất trong tensor:
- torch.argmax()
- torch.argmin()
1
2
3
4
5
6
7
# Create a tensor
tensor = torch.arange(10, 100, 10)
print(f"Tensor: {tensor}")
# Returns index of max and min values
print(f"Index where max value occurs: {tensor.argmax()}")
print(f"Index where min value occurs: {tensor.argmin()}")
11. Thay đổi dtype của tensor
Ta có thể thay đổi bằng cách dùng torch.Tensor.type(dtype=…) :
1
2
3
4
5
6
7
8
9
10
11
# Create a tensor and check its datatype
tensor = torch.arange(10., 100., 10.)
tensor.dtype # torch.float32
# Create a float16 tensor
tensor_float16 = tensor.type(torch.float16)
tensor_float16 # tensor([10., 20., 30., 40., 50., 60., 70., 80., 90.], dtype=torch.float16)
# Create an int8 tensor
tensor_int8 = tensor.type(torch.int8)
tensor_int8 # tensor([10, 20, 30, 40, 50, 60, 70, 80, 90], dtype=torch.int8)
12. Reshape, View, stack, squeeze, unsqueeze
Reshape
Ngoài cách dùng transpose thì reshape cũng là một cách để khắc phục lỗi dimension mismatch:
1
2
3
4
5
6
7
8
9
10
import torch
x = torch.arange(1., 8.)
x, x.shape
# output: (tensor([1., 2., 3., 4., 5., 6., 7.]), torch.Size([7]))
# Add an extra dimension
x_reshaped = x.reshape(1, 7)
x_reshaped, x_reshaped.shape
# output: (tensor([[1., 2., 3., 4., 5., 6., 7.]]), torch.Size([1, 7]))
Lưu ý: reshape không thay đổi tensor gốc mà return một tensor mới
View
Khác với reshape, view cũng tạo ra tensor với shape đã thay đổi tuy nhiên nó sẽ share dữ liệu với tensor cũ (cũng trỏ một vùng nhớ). Do đó, khi thay đổi dữ liệu tensor gốc thì tensor view cũng thay đổi theo:
1
2
3
4
5
6
7
8
9
x_new_view = x.view(1, 7)
x[0] = 10
x_new_view, x_new_view.shape, x
# output:
(tensor([[10., 2., 3., 4., 5., 6., 7.]]),
torch.Size([1, 7]),
tensor([10., 2., 3., 4., 5., 6., 7.]))
Stack
Ghép các tensor lại với nhau với dim phù hợp: dim=0 theo dòng, dim=1 theo cột
1
2
3
4
5
6
7
8
9
10
11
12
13
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
c = torch.tensor([7, 8, 9])
# Stack theo chiều 0
result1 = torch.stack([a, b, c], dim=0)
print(result1)
print(result1.shape) # (3, 3) - Ma trận 3x3
# Output
tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
Squeeze
Được sử dụng để loại bỏ các chiều có kích thước bằng 1 trong tensor, giúp giảm số chiều nhưng không làm thay đổi dữ liệu
1
2
3
4
5
6
7
8
9
10
11
12
13
print(f"Previous tensor: {x_reshaped}")
print(f"Previous shape: {x_reshaped.shape}")
# output:
Previous tensor: tensor([[5., 2., 3., 4., 5., 6., 7.]])
Previous shape: torch.Size([1, 7])
# Remove extra dimension from x_reshaped
x_squeezed = x_reshaped.squeeze()
print(f"\nNew tensor: {x_squeezed}")
print(f"New shape: {x_squeezed.shape}")
# output:
New tensor: tensor([5., 2., 3., 4., 5., 6., 7.])
New shape: torch.Size([7])
Unsqueeze
Được sử dụng để thêm một chiều mới vào một vị trí cụ thể trong tensor:
1
2
3
4
5
6
7
8
x = torch.tensor([1, 2, 3]) # Tensor có shape (3,)
print("Trước unsqueeze:", x.shape)
y = torch.unsqueeze(x, dim=0) # Thêm chiều mới tại dim=0
print("Sau unsqueeze dim=0:", y.shape) # (1, 3)
z = torch.unsqueeze(x, dim=1) # Thêm chiều mới tại dim=1
print("Sau unsqueeze dim=1:", z.shape) # (3, 1)
Permute
Khác với reshape, thì permute sẽ hoán đổi các trục (dữ liệu mỗi trục sẽ dữ nguyên):
1
2
3
4
5
6
7
8
9
10
11
12
# Create tensor with specific shape
x_original = torch.rand(size=(224, 224, 3))
# Permute the original tensor to rearrange the axis order
x_permuted = x_original.permute(2, 0, 1) # shifts axis 0->1, 1->2, 2->0
print(f"Previous shape: {x_original.shape}")
print(f"New shape: {x_permuted.shape}")
# output:
Previous shape: torch.Size([224, 224, 3])
New shape: torch.Size([3, 224, 224])
13. Truy cập phần tử trong tensor
Tương tự như list, tôi sẽ đưa ra một số case để mọi người hình dung dễ hơn:
1
2
3
4
5
6
7
8
9
10
11
# Get all values of 0th dimension and the 0 index of 1st dimension
x[:, 0]
# Get all values of 0th & 1st dimensions but only index 1 of 2nd dimension
x[:, :, 1]
# Get all values of the 0 dimension but only the 1 index value of the 1st and 2nd dimension
x[:, 1, 1]
# Get index 0 of 0th and 1st dimension and all values of 2nd dimension
x[0, 0, :] **# same as x[0][0]**
14. Pytorch Tensor và Numpy
Có 2 phương thức chính cần lưu ý:
- Numpy Array → PyTorch Tensor:
torch.from_numpy(ndarray) - Pytorch Tensor → Numpy Array:
torch.Tensor.numpy()
Lưu ý: Mặc định Numpy Array có dtype là float64 → Tuy nhiên trong PyTorch các tương tác chính mặc định sẽ là float32. Do đó sau khi Array → Tensor, ta cần change type lại bằng method type để chuyển thành float32
1
tensor = torch.from_numpy(array).type(torch.float32)
15. Reproducibility
Để việc kiểm thử các model một cách công bằng thì dữ liệu phải có sự đồng nhất. Vấn đề của việc sinh ngẫu nhiên (ví dụ torch.rand) là mỗi lần cho một kết quả khác nhau. nên ta cần dùng seed() để lấy các mẫu đã có sẵn → Chạy ở bất kỳ đâu đều có dữ liệu tương tự:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch
# Đặt seed cố định
RANDOM_SEED = 42
torch.manual_seed(RANDOM_SEED)
random_tensor_C = torch.rand(3, 4)
# Reset lại seed để đảm bảo cùng dãy số được sinh ra
torch.manual_seed(RANDOM_SEED)
random_tensor_D = torch.rand(3, 4)
print("Tensor C:\n", random_tensor_C)
print("\nTensor D:\n", random_tensor_D)
# Kết quả của random_tensor_C và random_tensor_D sẽ giống nhau
16. Chạy tensor trên GPU
Kiểm tra GPU với lệnh (khi dùng colab)
!nvidia-smi
Kiểm tra GPU khi dùng PyTorch và ta hoàn toàn có thể chuyển device của tensor
1
2
3
device = "cuda" if torch.cuda.is_available() else "cpu”
tensor = torch.tensor([1, 2, 3])
tensor_on_gpu = tensor.to(device)
Hoặc chuyển lại về cpu:
1
tensor_back_on_cpu = tensor_on_gpu.cpu().numpy()