PyTorch là một framework học sâu mạnh mẽ, nổi tiếng với khả năng tính toán linh hoạt và cơ chế tự động phân biệt (autograd) hiệu quả. Nền tảng của PyTorch là hai cấu trúc dữ liệu chính: Tensor và Variable (Biến).
Cấu Trúc Dữ Liệu Tensor
Tensor là khối xây dựng cơ bản trong PyTorch, tương tự như các mảng NumPy nhưng có khả năng chạy trên GPU để tăng tốc tính toán. Tensor được sử dụng để biểu diễn dữ liệu đầu vào, đầu ra, và các tham số của mô hình.
Các Kiểu Dữ Liệu Tensor Phổ Biến
PyTorch hỗ trợ nhiều kiểu dữ liệu cho Tensor, phù hợp với các nhu cầu tính toán khác nhau:
- Số thực dấu phẩy động 32-bit:
torch.FloatTensor() - Số thực dấu phẩy động 64-bit:
torch.DoubleTensor() - Số nguyên 16-bit:
torch.ShortTensor() - Số nguyên 32-bit:
torch.IntTensor() - Số nguyên 64-bit:
torch.LongTensor()
Mặc định, khi bạn gọi torch.Tensor(), nó sẽ tạo ra một Tensor kiểu torch.FloatTensor().
Khởi Tạo Tensor
Có nhiều cách để khởi tạo Tensor. Dưới đây là một số ví dụ:
Tạo Tensor với tất cả các phần tử là 0
import torch
# Tạo một Tensor 2 hàng, 4 cột với giá trị 0
mat_zeros = torch.zeros((2, 4))
print(mat_zeros)
# Kết quả:
# tensor([[0., 0., 0., 0.],
# [0., 0., 0., 0.]])
Tạo Tensor với các giá trị ngẫu nhiên từ phân phối chuẩn
# Tạo một Tensor 3x3 với các giá trị ngẫu nhiên
mat_rand_norm = torch.randn((3, 3))
print(mat_rand_norm)
# Kết quả có thể khác nhau do tính ngẫu nhiên:
# tensor([[-0.5694, 0.7229, 0.0387],
# [-0.1772, -0.6276, -0.0543],
# [-0.3006, 0.4284, -0.4079]])
Truy Cập và Chỉnh Sửa Phần Tử Tensor
Bạn có thể truy cập hoặc thay đổi các phần tử trong Tensor bằng cách sử dụng cú pháp lập chỉ mục (indexing) tương tự như mảng NumPy.
# Tiếp tục với mat_zeros từ ví dụ trên
print(f"Giá trị ban đầu tại (0, 1): {mat_zeros[0, 1]}")
# Thay đổi giá trị tại vị trí hàng 0, cột 1 thành 7.5
mat_zeros[0, 1] = 7.5
print(mat_zeros)
# Kết quả:
# tensor([[0.0000, 7.5000, 0.0000, 0.0000],
# [0.0000, 0.0000, 0.0000, 0.0000]])
# Truy cập lại giá trị đã thay đổi
print(f"Giá trị mới tại (0, 1): {mat_zeros[0, 1]}")
# Kết quả:
# Giá trị mới tại (0, 1): 7.5
Chuyển Đổi giữa Tensor PyTorch và Mảng NumPy
Việc chuyển đổi dữ liệu giữa PyTorch Tensor và NumPy array là rất dễ dàng, cho phép tích hợp linh hoạt với hệ sinh thái Python.
Chuyển từ Tensor sang NumPy array
import numpy as np
# Tạo một Tensor PyTorch
py_tensor = torch.tensor([[5., 6.], [1., 9.], [2., 4.]])
print("Tensor PyTorch:\n", py_tensor)
# Chuyển đổi sang NumPy array
np_array = py_tensor.numpy()
print("NumPy Array:\n", np_array)
# Kết quả:
# Tensor PyTorch:
# tensor([[5., 6.],
# [1., 9.],
# [2., 4.]])
# NumPy Array:
# [[5. 6.]
# [1. 9.]
# [2. 4.]]
Chuyển từ NumPy array sang Tensor
# Tạo một NumPy array
arr_input = np.array([[10, 20], [30, 40]], dtype=np.int32)
print("NumPy Array đầu vào:\n", arr_input)
# Chuyển đổi sang Tensor PyTorch
tensor_from_np = torch.from_numpy(arr_input)
print("Tensor từ NumPy:\n", tensor_from_np)
# Kết quả:
# NumPy Array đầu vào:
# [[10 20]
# [30 40]]
# Tensor từ NumPy:
# tensor([[10, 20],
# [30, 40]], dtype=torch.int32)
# Thay đổi kiểu dữ liệu của Tensor sang float
float_tensor = tensor_from_np.float()
print("Tensor kiểu float:\n", float_tensor)
# Kết quả:
# Tensor kiểu float:
# tensor([[10., 20.],
# [30., 40.]])
Để thay đổi kiểu dữ liệu của một Tensor, bạn chỉ cần gọi phương thức tương ứng với kiểu dữ liệu mong muốn (ví dụ: .float(), .long(), v.v.) trên Tensor đó.
Sử Dụng Tensor trên GPU
PyTorch tận dụng sức mạnh của GPU để tăng tốc các phép tính. Bạn có thể kiểm tra xem máy tính của mình có hỗ trợ GPU hay không và chuyển Tensor lên GPU để xử lý.
# Kiểm tra khả năng hỗ trợ CUDA (GPU của NVIDIA)
if torch.cuda.is_available():
print("GPU (CUDA) có sẵn!")
# Chuyển một Tensor lên GPU
my_tensor = torch.tensor([1, 2, 3])
my_tensor_on_gpu = my_tensor.cuda()
print("Tensor trên GPU:", my_tensor_on_gpu)
else:
print("GPU (CUDA) không có sẵn. Sử dụng CPU.")
Khái Niệm Variable (Biến) và Autograd
Trong PyTorch, Variable là một wrapper (bộ bọc) xung quanh Tensor. Mục đích chính của Variable là cung cấp khả năng tự động phân biệt (autograd), một tính năng cần thiết cho việc tối ưu hóa các mô hình học sâu thông qua lan truyền ngược (backpropagation). Mặc dù các Tensor trong PyTorch hiện đại (phiên bản 0.4 trở lên) có thể trực tiếp theo dõi các gradient thông qua thuộc tính requires_grad=True, khái niệm Variable vẫn hữu ích để hiểu cách thức hoạt động của autograd.
Chuyển đổi Tensor thành Variable
Để một Tensor có thể theo dõi gradient, bạn bọc nó trong một đối tượng Variable và đặt requires_grad=True. Điều này báo hiệu cho PyTorch rằng chúng ta muốn tính toán gradient cho biến này trong quá trình lan truyền ngược.
from torch.autograd import Variable
# Tạo một Variable từ Tensor, yêu cầu tính toán gradient
input_var = Variable(torch.tensor([1.5]), requires_grad=True)
print(input_var)
# Kết quả:
# tensor([1.5000], requires_grad=True)
Các Thuộc Tính của Variable
Mỗi Variable có ba thuộc tính quan trọng:
.data: Chứa Tensor thực tế mà Variable đang bọc..grad: Chứa gradient của Variable này đối với một phép toán nào đó, sau khi gọi.backward()..grad_fn: Tham chiếu đến hàm đã tạo ra Variable này (nếu nó là kết quả của một phép toán). Nó là một phần của biểu đồ tính toán.
Truy cập .data
print("Dữ liệu Tensor của Variable:", input_var.data)
# Kết quả:
# Dữ liệu Tensor của Variable: tensor([1.5000])
Hiểu về .grad_fn
grad_fn cho biết phép toán nào đã tạo ra Variable hiện tại.
# Tạo một biểu thức đơn giản
output_var = 3 * input_var + 2
print("Variable đầu ra:", output_var)
print("Hàm đã tạo ra output_var:", output_var.grad_fn)
# Kết quả:
# Variable đầu ra: tensor([6.5000], grad_fn=<AddBackward0>)
# Hàm đã tạo ra output_var: <AddBackward0 object at 0x...>
AddBackward0 cho biết output_var được tạo ra bởi một phép cộng (sau một phép nhân).
Tính toán và Truy cập .grad
Sau khi định nghĩa một biểu thức, bạn có thể gọi phương thức .backward() trên Variable đầu ra để tính toán các gradient của tất cả các Variable có requires_grad=True trong biểu đồ. Gradient sau đó được lưu trữ trong thuộc tính .grad của các Variable đầu vào.
Ví dụ với đầu ra là vô hướng (scalar)
Khi đầu ra của biểu thức là một vô hướng, bạn chỉ cần gọi .backward() mà không cần đối số.
# Tiếp tục với input_var và output_var
# input_var = Variable(torch.tensor([1.5]), requires_grad=True)
# output_var = 3 * input_var + 2
output_var.backward() # Tính toán gradient
print("Gradient của input_var:", input_var.grad)
# Kết quả:
# Gradient của input_var: tensor([3.])
Ở đây, đạo hàm của output_var = 3 * input_var + 2 theo input_var là 3.
Ví dụ với đầu ra là vector/ma trận
Khi đầu ra của biểu thức không phải là một vô hướng (ví dụ: một vector), bạn phải truyền một Tensor có cùng kích thước với đầu ra cho phương thức .backward(). Tensor này đại diện cho "gradient của kết quả đối với chính nó" hoặc một vector trọng số cho phép tính tổng gradient.
# Tạo một Variable đầu vào là vector
vec_input = Variable(torch.tensor([2.0, 3.0, 4.0]), requires_grad=True)
# Tạo một biểu thức với đầu ra là vector
vec_output = vec_input * vec_input # y = x^2
print("Output vector:", vec_output)
# Kết quả:
# Output vector: tensor([ 4., 9., 16.], grad_fn=<MulBackward0>)
# Gọi .backward() với một Tensor gradient có cùng kích thước
# Ví dụ: đạo hàm của một tổng giả định L = sum(y * [1, 1, 1])
vec_output.backward(torch.tensor([1.0, 1.0, 1.0]))
print("Gradient của vec_input:", vec_input.grad)
# Kết quả:
# Gradient của vec_input: tensor([ 4., 6., 8.])
Trong ví dụ trên, đạo hàm của y = x^2 theo x là 2x. Với vec_input là [2, 3, 4], gradient sẽ là [2*2, 2*3, 2*4] = [4, 6, 8]. Nếu bạn truyền một Tensor gradient khác (ví dụ: torch.tensor([0.5, 1.0, 2.0])), các gradient cuối cùng sẽ được nhân tương ứng với các giá trị này.