Mục lục
- Mục tiêu bài học
- Recap: tensor là gì
- torch.Tensor — ndarray có thêm GPU và autograd
- Các cách tạo tensor
- Qua lại với NumPy
- Attributes của tensor
- dtype — kiểu dữ liệu
- Indexing và slicing
- Reshape: view, reshape, squeeze, permute
- Math operations
- Broadcasting
- Concatenate và stack
- In-place vs out-of-place
- Contiguous memory layout
- Random seed
- Tensor vs NumPy ndarray
- Code Python — tổng hợp các thao tác
- Bài tập
- Tóm tắt
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Hiểu
torch.Tensorlà gì và khácnumpy.ndarrayở điểm nào. - Tạo tensor từ list, từ NumPy, từ các hàm khởi tạo (
zeros,ones,rand,randn,arange,eye…). - Đọc và đổi 5 thuộc tính chính:
shape,ndim,dtype,device,requires_grad. - Biết các
dtypehay dùng (float32,float16,bfloat16,int64,bool) và khi nào dùng cái nào. - Thực hiện indexing, slicing, boolean mask, fancy index như với NumPy.
- Phân biệt
viewvsreshape, dùngsqueeze/unsqueeze/permuteđúng chỗ. - Hiểu in-place vs out-of-place, biết hệ luỵ với autograd.
- Hiểu khái niệm contiguous và khi nào cần gọi
.contiguous().
Bài 12 đã giải thích vì sao chọn PyTorch. Bài này bắt đầu với data structure — viên gạch nền của mọi tính toán trong PyTorch. Các bài tiếp theo (GPU/CUDA — Bài 14, Autograd — Bài 15) đều xây trên nền này.
Recap: tensor là gì
Series 1 (Bài 20 — Tensor là gì) đã định nghĩa tensor như một generalization theo số chiều:
- Scalar — tensor 0 chiều, ví dụ
3.14. - Vector — tensor 1 chiều, ví dụ
[1, 2, 3]. - Matrix — tensor 2 chiều, một bảng số.
- Tensor n chiều — mảng số nhiều chiều bất kỳ. Ảnh batch RGB là tensor 4 chiều
(batch, channel, height, width); sequence batch là tensor 3 chiều(batch, seq_len, dim).
Trong PyTorch, mọi thứ "đi vào" và "đi ra" model đều là tensor: input, weight, bias, gradient, output, loss. Hiểu thao tác trên tensor là điều kiện cần trước khi đụng tới nn.Module.
torch.Tensor — ndarray có thêm GPU và autograd
torch.Tensor nhìn từ ngoài rất giống numpy.ndarray: cùng có shape, dtype, indexing, slicing, broadcasting, ufunc. Phần lớn người đến PyTorch từ NumPy thấy quen mặt trong vài phút.
Ba điểm tensor có mà ndarray không có:
- Chạy được trên GPU: chỉ cần
tensor.to("cuda")hoặctensor.to("mps")(Apple Silicon). Cùng một API hoạt động trên CPU và GPU — bài 14 sẽ đi sâu. - Hỗ trợ autograd: nếu tensor có
requires_grad=True, PyTorch sẽ ghi lại đồ thị tính toán và tính gradient tự động khi gọi.backward()— bài 15. - Tích hợp với hệ sinh thái deep learning: layer (
nn.Linear,nn.Conv2d…), optimizer, loss function đều nhận / trả tensor.
Đối lại, ndarray có ưu thế khi bạn không cần GPU và không cần gradient — interop với scikit-learn, matplotlib, pandas thuần. Trong code thực tế, hai bên hay được chuyển qua lại (mục 5).
Các cách tạo tensor
Một số hàm khởi tạo dùng thường xuyên:
import torch
torch.tensor([1, 2, 3]) # từ list -> tensor 1D, dtype suy ra
torch.tensor([[1., 2.], [3., 4.]]) # từ nested list -> tensor 2D
torch.zeros(3, 4) # ma trận 3x4 toàn 0
torch.ones(2, 3) # ma trận 2x3 toàn 1
torch.full((2, 2), 7) # ma trận 2x2 toàn 7
torch.empty(3, 3) # 3x3 chưa init -> giá trị rác
torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]
torch.linspace(0, 1, 11) # 11 điểm cách đều từ 0 đến 1
torch.rand(3, 4) # uniform [0, 1)
torch.randn(3, 4) # standard normal N(0, 1)
torch.randint(0, 10, (3, 4)) # int [0, 10)
torch.eye(4) # ma trận đơn vị 4x4
torch.zeros_like(x) # cùng shape + dtype với x, toàn 0
torch.ones_like(x) # cùng shape + dtype với x, toàn 1
Ghi nhớ:
torch.tensor(data)luôn copy dữ liệu. Kháctorch.as_tensor(data)— share memory nếu được.torch.emptykhông init giá trị; chỉ dùng khi sẽ overwrite ngay. Đừng nhầm vớitorch.zeros.- Mọi hàm khởi tạo nhận thêm tham số
dtype=,device=,requires_grad=. Ví dụtorch.zeros(3, 4, dtype=torch.float64, device="cuda").
Qua lại với NumPy
Hai chiều chuyển đổi chính:
import numpy as np
import torch
arr = np.array([1.0, 2.0, 3.0])
t = torch.from_numpy(arr) # tensor share memory với arr (CPU)
arr2 = t.numpy() # ndarray share memory với t (CPU)
Cảnh báo:
- Share memory: sửa
arrsẽ thấytđổi theo, và ngược lại. Đây là điểm rất kháctorch.tensor(arr)(luôn copy). - Chỉ áp dụng cho CPU tensor. Nếu tensor đang trên GPU, phải gọi
t.cpu().numpy(). - Một số dtype không có ánh xạ 1–1 (ví dụ
bfloat16không có trong NumPy gốc) — chuyển sẽ lỗi, cần convert dtype trước.
Attributes của tensor
5 thuộc tính cần thuộc:
t = torch.randn(2, 3, 4)
t.shape # torch.Size([2, 3, 4])
t.size() # giống shape (gọi như hàm)
t.size(1) # 3 -> chiều thứ 1
t.ndim # 3 -> số chiều
t.dtype # torch.float32
t.device # device(type='cpu')
t.requires_grad # False (mặc định)
t.numel() # 24 -> tổng số phần tử (2*3*4)
shapevàsize()trả về cùng objecttorch.Size.shapedùng tiện hơn vì là attribute (không cần ngoặc).devicecó thể làcpu,cuda:0,cuda:1, hoặcmps(Apple Silicon).requires_grad=Truebật autograd. Mặc định tensor mới tạo từzeros,ones,randn… đềuFalse; tensor là tham số củann.Modulemặc địnhTrue.
dtype — kiểu dữ liệu
Các dtype hay gặp trong deep learning:
torch.float32(aliastorch.float) — mặc định cho training. 4 byte mỗi số.torch.float64(aliastorch.double) — 8 byte. Chỉ dùng cho code khoa học cần precision cao; chậm và tốn RAM hơn float32.torch.float16(aliastorch.half) — 2 byte. Dùng trong mixed-precision training để tiết kiệm RAM và tăng tốc trên GPU có Tensor Core.torch.bfloat16— 2 byte, dải số (exponent) giốngfloat32nhưng độ chính xác (mantissa) thấp hơn. Ổn định hơnfloat16khi train LLM; hỗ trợ tốt trên Ampere (A100), Hopper (H100), TPU.torch.int32,torch.int64(aliastorch.long) — cho index, label, lookup.CrossEntropyLossyêu cầu nhãn làint64.torch.bool— cho mask (kết quả của comparison).
Convert dtype:
t = torch.randn(3, 3) # float32 mặc định
t.to(torch.float64) # cách tổng quát
t.float() # -> float32
t.double() # -> float64
t.half() # -> float16
t.long() # -> int64
t.bool() # -> bool
Convert tạo tensor mới — không thay đổi t. Gán lại nếu muốn giữ: t = t.float().
Indexing và slicing
API gần như sao chép NumPy:
t = torch.arange(12).reshape(3, 4)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]])
t[0, 1] # tensor(1) -> phần tử hàng 0, cột 1
t[:, 1] # tensor([1, 5, 9])-> cả cột 1
t[1:3, 0:2] # 2x2 submatrix
t[-1] # hàng cuối: tensor([ 8, 9, 10, 11])
# Boolean mask
mask = t > 5
t[mask] # tensor([ 6, 7, 8, 9, 10, 11])
# Fancy index
t[[0, 2]] # chọn hàng 0 và 2
Một số khác biệt nhỏ so với NumPy:
- Single-element indexing trả về
tensor(...)0-D, không phải Python scalar. Muốn lấy scalar thuần, gọit[0, 1].item(). - Slice trả về view chia sẻ bộ nhớ với tensor gốc; sửa view sẽ sửa tensor gốc.
- Fancy index (list / tensor làm index) trả về copy.
Reshape: view, reshape, squeeze, permute
Các thao tác đổi shape (không đổi data):
t = torch.arange(12) # shape (12,)
t.view(3, 4) # (3, 4) - share memory, yêu cầu contiguous
t.reshape(3, 4) # (3, 4) - flexible hơn, có thể copy
t.view(-1, 4) # tự suy ra chiều -1 = 3
t.flatten() # (12,)
t.unsqueeze(0) # (1, 12) -> thêm chiều ở vị trí 0
t.unsqueeze(-1) # (12, 1)
t.unsqueeze(0).squeeze() # bỏ mọi chiều = 1
t.unsqueeze(0).squeeze(0) # bỏ chiều 0 (nếu = 1)
m = torch.arange(6).reshape(2, 3)
m.transpose(0, 1) # đổi 2 chiều -> shape (3, 2)
m.T # alias cho transpose ở 2D
img = torch.randn(3, 224, 224) # (C, H, W)
img.permute(1, 2, 0) # (H, W, C) - thứ tự matplotlib mong đợi
Phân biệt view vs reshape:
viewchỉ thay metadata (stride, shape), không di chuyển data. Yêu cầu tensor đang contiguous; nếu không sẽ raiseRuntimeError.reshapecố gắng dùngviewtrước; nếu không được sẽ copy data. An toàn hơn nhưng có thể "âm thầm" tạo copy.- Code nội bộ PyTorch hay dùng
viewkhi đảm bảo contiguous (sau.contiguous()) để chắc chắn không có copy bất ngờ.
permute đổi thứ tự nhiều chiều — kết quả thường không contiguous; nếu cần view sau đó, gọi .contiguous() trước (xem mục 14).
Math operations
Element-wise (giống NumPy):
a = torch.tensor([1., 2., 3.])
b = torch.tensor([4., 5., 6.])
a + b # tensor([5., 7., 9.])
a - b
a * b # element-wise multiply, KHÔNG phải matrix multiply
a / b
a ** 2 # luỹ thừa từng phần tử
torch.exp(a); torch.log(a); torch.sqrt(a)
Matrix multiplication (cần phân biệt với *):
A = torch.randn(3, 4)
B = torch.randn(4, 5)
torch.matmul(A, B) # (3, 5)
A @ B # alias - dạng đẹp hơn
A.mm(B) # chỉ cho 2D
Reduction theo axis:
x = torch.arange(12.).reshape(3, 4)
x.sum() # scalar -> tổng tất cả
x.sum(dim=0) # (4,) -> tổng theo cột (gộp hàng)
x.sum(dim=1) # (3,) -> tổng theo hàng
x.sum(dim=1, keepdim=True) # (3, 1) - giữ chiều để broadcast tiếp
x.mean(); x.std(); x.var()
x.max(); x.min(); x.argmax(); x.argmin()
# Với dim=, max/min trả về NamedTuple (values, indices)
val, idx = x.max(dim=1)
Mọi reduction đều nhận tham số dim= (axis nào để "gộp") và keepdim= (giữ lại chiều = 1 hay không).
Broadcasting
Quy tắc broadcasting trong PyTorch giống NumPy (Series 1 Bài 33). Hai tensor có thể thực hiện element-wise op nếu, đọc shape từ phải sang trái, mỗi cặp chiều thoả: bằng nhau, hoặc một bên = 1, hoặc một bên thiếu.
x = torch.randn(64, 128) # (B=64, features=128)
b = torch.randn(128) # (features=128,)
(x + b).shape # (64, 128) - b broadcast lên mỗi hàng
m = torch.randn(64, 1) # cột
r = torch.randn(1, 128) # hàng
(m + r).shape # (64, 128) - outer-add bằng broadcasting
Trong forward pass của nn.Linear, bias shape (out_features,) được broadcast lên batch — đây là broadcasting bạn dùng mỗi ngày dù không nhận ra.
Concatenate và stack
Hai thao tác hay nhầm:
a = torch.zeros(2, 3)
b = torch.ones(2, 3)
torch.cat([a, b], dim=0).shape # (4, 3) - nối dọc theo dim 0 đã có
torch.cat([a, b], dim=1).shape # (2, 6) - nối ngang
torch.stack([a, b], dim=0).shape # (2, 2, 3) - tạo dim MỚI ở vị trí 0
torch.stack([a, b], dim=1).shape # (2, 2, 3) - dim mới ở vị trí 1
cat: nối các tensor dọc theo một chiều đã tồn tại. Tổng số chiều không đổi.stack: gom các tensor cùng shape thành tensor có thêm một chiều mới. Tổng số chiều tăng 1.- Ứng dụng quen thuộc:
catnối các batch lại với nhau;stackgom list các sample shape(C, H, W)thành batch shape(N, C, H, W).
In-place vs out-of-place
PyTorch quy ước: tên hàm có dấu gạch dưới ở cuối = in-place.
a = torch.tensor([1., 2., 3.])
a + 1 # OUT-of-place: tạo tensor mới, a không đổi
a.add(1) # cũng out-of-place
a.add_(1) # IN-place: sửa thẳng a -> a = [2, 3, 4]
a += 1 # in-place (giống add_)
a.zero_() # set toàn 0, in-place
a.fill_(7) # set toàn 7, in-place
Đánh đổi:
- In-place tiết kiệm RAM (không cấp phát tensor mới) — quan trọng với GPU và batch lớn.
- In-place có thể phá autograd: nếu một tensor đang nằm trong đồ thị tính toán và cần thiết cho
backward(), sửa in-place sẽ raise lỗi hoặc cho gradient sai. Khi chưa nắm chắc, dùng out-of-place. - Quy tắc thực dụng: trong code training thông thường, để PyTorch tự quản; chỉ dùng in-place khi đang tối ưu RAM rõ ràng và biết tensor đó không cần cho backward (ví dụ
optimizer.zero_grad()bản chất là gọigrad.zero_()).
Contiguous memory layout
Tensor lưu data trong một block bộ nhớ phẳng (1D), kèm metadata stride để biết cách "đi" qua các chiều. Một tensor được gọi là contiguous nếu thứ tự phần tử trong bộ nhớ khớp với thứ tự "row-major" của shape hiện tại.
Các thao tác như transpose, permute, một số slice không di chuyển data — chúng chỉ đổi stride. Kết quả thường không còn contiguous.
t = torch.arange(12).reshape(3, 4)
t.is_contiguous() # True
tT = t.transpose(0, 1) # shape (4, 3), stride đảo
tT.is_contiguous() # False
tT.view(12) # RuntimeError: cần contiguous
tT.contiguous().view(12) # OK - copy thành contiguous rồi view
tT.reshape(12) # cũng OK - reshape tự xử lý
viewđòi contiguous;reshapekhông.- Một số phép trên GPU hoạt động nhanh hơn (hoặc chỉ hoạt động) với tensor contiguous.
- Gọi
.contiguous()khi cần đảm bảo layout — chi phí là 1 lần copy. Đừng gọi tràn lan; chỉ khi gặp lỗiviewhoặc khi đo thấy chậm bất thường.
Random seed
torch.manual_seed(42) # seed cho CPU RNG
torch.cuda.manual_seed(42) # seed cho 1 GPU
torch.cuda.manual_seed_all(42) # seed cho mọi GPU
# Reproducible across runs
torch.manual_seed(42)
torch.randn(3) # luôn cho cùng kết quả mỗi lần chạy
Để reproducible hoàn toàn, ngoài seed PyTorch, bạn còn cần seed NumPy (np.random.seed(42)) và Python (random.seed(42)) nếu dùng các thư viện đó. PyTorch còn có cờ torch.use_deterministic_algorithms(True) để bắt buộc thuật toán deterministic — chậm hơn nhưng cần khi debug bug khó tái lập.
Tensor vs NumPy ndarray
Chủ đề NumPy ndarray PyTorch Tensor
─────────────────────────────────────────────────────────────────
Tạo np.array, np.zeros torch.tensor, torch.zeros
Shape arr.shape t.shape / t.size()
dtype mặc định float float64 float32
Device CPU CPU / CUDA / MPS
Autograd không có (requires_grad)
Matrix multiply arr @ arr2, np.matmul t @ t2, torch.matmul
Reshape arr.reshape t.view / t.reshape
Transpose arr.T, arr.transpose t.T, t.transpose, t.permute
In-place suffix không quy ước dấu _ (add_, mul_, ...)
Random np.random.randn(...) torch.randn(...)
Seed np.random.seed(42) torch.manual_seed(42)
Convert t.numpy() torch.from_numpy(arr)
Điểm hay đụng:
- Mặc định float: NumPy là
float64, PyTorch làfloat32.torch.from_numpy(np.array([1., 2.]))sẽ rafloat64— mất 2× RAM nếu không convert. - Tensor có method
.item()để lấy scalar Python từ tensor 0-D; NumPy có.item()tương tự. - API tên hàm gần như khớp 1–1:
np.exp↔torch.exp,np.concatenate↔torch.cat(lưu ý đổi tên),np.stack↔torch.stack,np.linspace↔torch.linspace.
Code Python — tổng hợp các thao tác
import numpy as np
import torch
torch.manual_seed(0)
# 1) Tạo các loại tensor
t0 = torch.tensor([[1., 2., 3.], [4., 5., 6.]]) # 2x3
t1 = torch.zeros(3, 4) # 3x4 toàn 0
t2 = torch.full((2, 2), 7) # 2x2 toàn 7
t3 = torch.arange(0, 10, 2) # [0,2,4,6,8]
t4 = torch.randn(3, 4) # 3x4 N(0,1)
t5 = torch.eye(3) # I_3
print(t0.shape, t0.dtype, t0.device)
# torch.Size([2, 3]) torch.float32 cpu
# 2) Indexing & slicing
x = torch.arange(20).reshape(4, 5)
print(x[1, 2]) # tensor(7)
print(x[:, 0]) # cột 0 -> tensor([ 0, 5, 10, 15])
print(x[x % 2 == 0]) # các phần tử chẵn
# 3) Math ops + reduction
A = torch.randn(3, 4)
B = torch.randn(4, 2)
print((A @ B).shape) # torch.Size([3, 2])
print(A.sum(dim=0).shape) # torch.Size([4])
print(A.mean(dim=1, keepdim=True).shape) # torch.Size([3, 1])
# 4) Reshape
v = torch.arange(12)
print(v.view(3, 4).shape) # (3, 4)
print(v.reshape(2, 2, 3).shape) # (2, 2, 3)
img = torch.randn(3, 32, 32)
print(img.permute(1, 2, 0).shape) # (32, 32, 3)
# 5) NumPy bridge
arr = np.array([1.0, 2.0, 3.0])
t = torch.from_numpy(arr)
arr[0] = 99.0
print(t) # tensor([99., 2., 3.], dtype=torch.float64)
# -> share memory, nên sửa arr cũng đổi t
Output rút gọn:
torch.Size([2, 3]) torch.float32 cpu
tensor(7)
tensor([ 0, 5, 10, 15])
tensor([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
torch.Size([3, 2])
torch.Size([4])
torch.Size([3, 1])
torch.Size([3, 4])
torch.Size([2, 2, 3])
torch.Size([32, 32, 3])
tensor([99., 2., 3.], dtype=torch.float64)
Bài tập
- Tạo tensor shape
(3, 4)toàn 0, dtypefloat32, in rashape,dtype,device,numel(). - Tạo NumPy array
arr = np.arange(6, dtype=np.float32).reshape(2, 3). Convert sang tensor bằngtorch.from_numpy. Sửa phần tửarr[0, 0] = 100.0— kiểm tra tensor có đổi không. Giải thích vì sao. - Cho ma trận
M = torch.arange(12.).reshape(3, 4). Tính mean theo từng hàng (axis=1) và theo từng cột (axis=0). In shape của kết quả. - Cho tensor 1D
v = torch.arange(12). Reshape thành(3, 4)rồi tiếp(2, 2, 3). In shape và in tensor cuối ra để xác nhận thứ tự phần tử. - Tạo
x = torch.randn(4, 3). Tínhx.T @ xvàx @ x.T— shape mỗi kết quả là gì? Hai kết quả có cùng giá trị không? - Tạo
y = torch.randn(64, 10). Lấyargmaxtheo dim 1 — đây là cách convert logits 10 lớp thành dự đoán cho 64 sample. Inshapevà 5 giá trị đầu.
Đáp án ngắn
torch.zeros(3, 4, dtype=torch.float32). Shape(3,4), dtypefloat32, devicecpu, numel 12.- Tensor cũng đổi: phần tử [0,0] thành 100.0.
from_numpyshare memory với mảng NumPy gốc (chỉ áp dụng CPU + dtype tương thích). M.mean(dim=1)shape(3,),M.mean(dim=0)shape(4,). Thêmkeepdim=Truesẽ ra(3,1)và(1,4).v.view(3,4)rồi.view(2,2,3)— phần tử đi theo thứ tự row-major:[[[0,1,2],[3,4,5]], [[6,7,8],[9,10,11]]].x.T @ xshape(3,3),x @ x.Tshape(4,4). Khác giá trị; đều là ma trận đối xứng nửa xác định dương (Gram matrix).y.argmax(dim=1)shape(64,), dtypeint64— đây chính là format input choCrossEntropyLossở phía label.
Tóm tắt
torch.Tensor=ndarray+ GPU + autograd.- Tạo từ list, từ NumPy (share memory), từ các hàm khởi tạo:
zeros,ones,rand,randn,arange,linspace,eye,full,empty, các bản_like. - 5 attribute cần thuộc:
shape,ndim,dtype,device,requires_grad. dtypemặc địnhfloat32. Mixed-precision dùngfloat16/bfloat16; label dùngint64; mask dùngbool.- Indexing / slicing / boolean mask / fancy index — gần như NumPy. Slice trả view (share memory), fancy index trả copy.
viewshare memory, đòi contiguous;reshapelinh hoạt hơn, có thể copy.squeeze/unsqueezeđổi chiều = 1.permuteđổi thứ tự nhiều chiều.- Math ops element-wise như NumPy. Matrix mul dùng
@hoặctorch.matmul. Reduction nhậndim=vàkeepdim=. - Broadcasting giống NumPy.
- In-place có suffix
_(nhưadd_). Tiết kiệm RAM nhưng có thể phá autograd. - Contiguous: data layout khớp shape.
transpose/permutephá contiguous; gọi.contiguous()khi cầnview. torch.manual_seedcho CPU;torch.cuda.manual_seed_allcho GPU.
Bài 14 sẽ học cách chuyển tensor giữa CPU và GPU (CUDA, MPS), cách viết code device-agnostic, và những gotcha thường gặp khi mix tensor ở các device khác nhau.
- PyTorch Docs - torch.Tensor
- PyTorch Docs - Tensor Creation Ops
- PyTorch Docs - Tensor Attributes (dtype, device, layout)
- PyTorch Tutorial - Tensors
- PyTorch Docs - Broadcasting semantics
- PyTorch Docs - PyTorch for NumPy users
- PyTorch Docs - Reproducibility
- PyTorch Docs - Tensor.view
- PyTorch Docs - torch.reshape
- PyTorch Docs - Tensor.contiguous
- PyTorch Docs - torch.from_numpy
- NumPy Docs - Broadcasting
