Hướng dẫn tensor python - trăn tensor

  • Dữ liệu biểu diễn dưới dạng số thực
  • Numpy arrays vs PyTorch tensors.
  • Torch Tensors
    • Vector
    • Ma trận
    • Tensor 3D
  • Torch Properties
    • Dtype
    • Torch transpose
  • Torch Storage
    • Storage
    • Tensor metadata: Size, offset, and stride
    • Contiguous tensors
  • Torch GPU
  • Torch Tensor to Numpy Array

Dữ liệu biểu diễn dưới dạng số thực

Thông thường các thuật toán Machine Learning (ML), Deep Learning (DL) chỉ xử lý được dữ liệu dạng số thực nên các dữ liệu đưa vào mô hình thường được chuyển về dạng số thực.

Ảnh màu (rgb) được biểu diễn dưới dạng 1 tensor 3 chiều

Hướng dẫn tensor python - trăn tensor
Biểu diễn ảnh màu

Hay dữ liệu dạng chữ (tôi, yêu, hoa,..) cũng được chuyển về dạng vector trước khi cho vào các mô hình, ví dụ như mô hình word2vec.

Với dữ liệu đầu vào dạng số thì các mô hình ML hay DL sẽ thực hiện các phép tính toán, biến đổi để cho ra được output của mô hình. Vậy nên biểu diễn dữ liệu dạng số thực và các phép tính toán trên số thực đó chính là nền tảng cơ bản cho các mô hình AI.

Numpy arrays vs PyTorch tensors.

Torch Tensors

Vector

  • Ma trận
  • Tensor 3D

Torch Properties

Torch Tensors

Vector

Vector

Ma trận

Hướng dẫn tensor python - trăn tensor
Tensor 3D

Torch Properties

x[x.shape[0] - 1] # x.shape[0] trả về số phần tử trong vector

Dtype

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng

Torch transpose

Torch Storagestart:stop:step

Hướng dẫn tensor python - trăn tensor
Storage

Tensor metadata: Size, offset, and stride start=0, stop=x.shape[0]step=1. Ý tưởng slicing là sẽ lấy từ phần tử index start đến index (stop – 1) với bước nhảy là step.

x = x[:] = x[::] = x[0:x.shape[0]:1] # lấy tất các phần tử trong x

Contiguous tensors

x[1:5:2] # output: [2, 4]

Torch GPU

Ma trận

Tensor 3D \displaystyle A\in\mathbb{R}^{3\times 2}. Ta có thể dùng thuộc tính shape để lấy ra kích thước của A

A.shape # torch.Size([3, 2])
A.shape[0] # 3

Hướng dẫn tensor python - trăn tensor
Torch Properties

Dtype

Torch transpose

Hướng dẫn tensor python - trăn tensor
Torch Storage

# A[1:, :1] # Tương đương A[1:A.shape[0]:1, 0:1:1]

Storage

A[:, 1] # tensor([2, 4, 6])

Tensor metadata: Size, offset, and stride

Tensor 3D

Torch Properties

Dtype

Torch Properties

Dtype

Torch transpose

  • Torch Storage
  • Storage
  • Tensor metadata: Size, offset, and stride
  • torch.int8: signed 8-bit integers
  • torch.uint8: unsigned 8-bit integers
  • torch.int16 or torch.short: signed 16-bit integers
  • torch.int32 or torch.int: signed 32-bit integers
  • torch.int64 or torch.long: signed 64-bit integers
  • torch.bool: Boolean

Bình thường khi bạn gán giá trị cho tensor thì torch sẽ tự động gán dtype bằng dtype của giá trị có kiểu rộng hơn trong tensor. Ví dụ: các giá trị trong tensor có cả int, float thì dtype của tensor sẽ là float.

points = torch.tensor([7, 8, 10, 6.5])
print(points.dtype) # output: torch.float32

Tuy nhiên bạn cũng có thể khởi tạo kiểu dữ liệu cho tensor.

points = torch.tensor([7, 8, 10, 6])
print(points.dtype) # output: torch.int64

# Gán kiểu dữ liệu cho tensor
points = torch.tensor([7, 8, 10, 6], dtype=torch.short)
print(points.dtype) # output: torch.int16

Hoặc mình cũng có thể chuyển kiểu dữ liệu của tensor đã được khai báo.

points = torch.tensor([7, 8, 10, 6]).short()
points = torch.tensor([7, 8, 10, 6]).to(dtype=torch.short)

Hàm to(dtype=…) sẽ kiểm tra kiểu dữ liệu của tensor và chuyển sang kiểu dữ liệu mới nếu cần thiết. Phần dưới mình sẽ dùng hàm to() để chuyển tensor từ CPU sang GPU.

Torch transpose

Hàm torch.transpose(input, dim0, dim1): Nhận input tensor và sẽ đổi chỗ dim0 và dim1 với nhau.

Ví dụ: với ma trận phép tính transpose sẽ chuyển hàng và cột, cụ thể hàng thứ i của A sẽ thành cột thứ i của A^T và cột thứ j của A sẽ thành hàng thứ j của A^T, do đó A\in\mathbb{R}^{3\times 2} \Rightarrow A^T\in\mathbb{R}^{2\times 3}A^T và cột thứ j của A sẽ thành hàng thứ j của A^T, do đó A\in\mathbb{R}^{3\times 2} \Rightarrow A^T\in\mathbb{R}^{2\times 3}

Hướng dẫn tensor python - trăn tensor
Transpose ma trận

Cùng thử nhìn transpose với tensor 3d nhé.

Hướng dẫn tensor python - trăn tensor
Transpose tensor 3D

Mọi người thấy mình transpose chiều sâu và chiều hàng, chiều cột giữ nguyên (số cột giữ nguyên). Vì số cột giữ nguyên, nên mọi người thấy các vector hàng ở A và A^T không thay đổi, chỉ đổi vị trí. Và từng cột ở mỗi ma trận trong A được tách ra thành các phần tử cho chiều sâu.AA^T không thay đổi, chỉ đổi vị trí. Và từng cột ở mỗi ma trận trong A được tách ra thành các phần tử cho chiều sâu.

Ngoài ra torch còn hỗ trợ rất nhiều phép tính toán liên quan đến tensor nữa, chi tiết mọi người xem ở đây.

Torch Storage

Phần này cùng xem thực sự Torch lưu trữ tensor như thế nào.

Storage

Thực ra các giá trị trong tensor sẽ được lưu trên 1 vùng nhớ liên tục trên bộ nhớ, được quản lý bởi torch.Storage. Storage là 1 mảng 1 chiều gồm các số có cùng kiểu dữ liệu (ở trên mình biết các giá trị trong 1 tensor cùng kiểu dữ liệu).

Ví dụ mình tạo 1 vector với torch, kiểu dữ liệu mặc định với số nguyên sẽ là torch.int64, hay mỗi phần tử cần 8 bytes để lưu trữ.

Hướng dẫn tensor python - trăn tensor

x sẽ trỏ đến phần tử đầu tiên, và để lấy phần tử x[i] thì mình sẽ truy cập đến vị trị (x + i * 8). Đây là 1 phần lý do vì sao index mọi người thấy hay bắt đầu từ 0, tại x đã trỏ đến phần tử đầu tiên x[0] rồi, còn x[i] sẽ tiện lấy địa chỉ của phần tử (i+1), thêm nữa mọi người xem ở đây.

Storage 1 chiều thì lưu dữ liệu Torch tensor 2 chiều dạng ma trận như thế nào? Storage xếp hết dữ liệu thành 1 chiều, nối các hàng từ trên xuống dưới lần lượt với nhau cho tới hết.

Hướng dẫn tensor python - trăn tensor
Matrix’s storage

x trỏ đến phần tử hàng 0, cột 0 (x[0][0]). Phần tử x[i][j] sẽ ở ô nhớ (x+i*col+j), trong đó col là số cột của ma trận, hay x[i][j] = storage[i*col+j]

Ví dụ ma trận trên có 2 hàng, 3 cột, thì phần tử x[1][2] (=6) sẽ ở địa chỉ x+1*3+2 = x+5, để truy cập giá trị x[1][2] qua storage mình dùng storage[5].

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
0

Tensor metadata: Size, offset, and stride

Để tensor lấy được giá trị từ storage thì mình cần 1 vài thông tin: size, offset và stride.

  • Offset là vị trí bắt đầu lưu giá trị của tensor trong storage.là vị trí bắt đầu lưu giá trị của tensor trong storage.
  • Size là kích thước của tensor.là kích thước của tensor.
  • Stride có số chiều bằng số chiều của Size, ý nghĩa là cần nhảy bao nhiêu phần tử trong storage để được phần tử tiếp theo trong chiều đấy.có số chiều bằng số chiều của Size, ý nghĩa là cần nhảy bao nhiêu phần tử trong storage để được phần tử tiếp theo trong chiều đấy.

Như trong ví dụ dưới thì size hay shape, chính là kích thước ma trận (3×3). Offset = 1, tức là giá trị của tensor này lưu từ index 1 của storage thay vì index 0 như các ví dụ ở trên.

Stride = (3,1) ý là:

  • để lấy giá trị ở cột đấy nhưng ở hàng phía dưới, cần nhảy 3 phần tử trên storage, ví dụ: x[1][1] (=3) lưu ở index 5 trên storage, thì x[2][1] (=3) lưu ở vị trí 5 + 3 = 8 trên storage.
  • để lấy giá trị ở hàng đấy nhưng ở cột lân cận, cần nhảy 1 phần tử trên storage , ví dụ: x[1][1] (=3) lưu ở index 5 trên storage, thì x[1][2] (=2) lưu ở vị trí 5 + 1 = 6 trên storage.

Rõ ràng có 1 storage và biết được các chỉ số size, offset, stride sẽ lấy lấy được các phần tử trong tensor.

Phần tử x[i][j] sẽ tương ứng với storage[offset +stride[0] * i + stride[1] * j].x[i][j] sẽ tương ứng với storage[offset +stride[0] * i + stride[1] * j].

Tại sao cần nhiều thông tin như vậy? Tưởng ở trên chỉ cần mỗi số cột là lấy được hết các giá trị của tensor. Câu trả lời là để có thể lưu nhiều tensor cùng trên 1 storage. Cùng xem ví dụ về transpose tensor ở dưới.Câu trả lời là để có thể lưu nhiều tensor cùng trên 1 storage. Cùng xem ví dụ về transpose tensor ở dưới.

Transposing tensor

Torch tensor x và x_t (transpose) sẽ dùng chung 1 storage thay vì phải copy ra 1 vùng nhớ khác.

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
1

Ví dụ trên mình thấy là x và x_t dùng chung 1 storage. Thuộc tính offset cả 2 đều bằng 0, size thì khác nhau, \displaystyle A\in\mathbb{R}^{2\times 3}, A^T\in\mathbb{R}^{3\times 2} \displaystyle A\in\mathbb{R}^{2\times 3}, A^T\in\mathbb{R}^{3\times 2}

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
2

Và stride khác nhau, ở x thì mình cần nhảy 3 phần tử trong storage để đến vị trí cột đấy nhưng ở hàng dưới, x[0][0] = storage[0] = 3, x[1][0] = storage[3] = 4. Tuy nhiên, ở x_t thì mình chỉ cần nhảy 1 phần tử trong storage để đến vị trí cột đấy nhưng ở hàng dưới, x_t[0][0] = storage[0] = 3, x_t[1][0] = storage[1] = 1.

Mình thực hiện phép tính transpose nhưng vẫn dùng chung storage. Ngoài ra, ví dụ như khi mọi người slicing chẳng hạn, thì để dùng chung storage mình sẽ cần thay đổi offset, size, stride.

Contiguous tensors

Một vài phép tính trong Torch tensors chỉ chạy trên contigous tensors, ví dụ view. Để kiểm tra xem tensor có contiguous không mình dùng hàm is_contiguous().

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
3

Khi mình khởi tạo 1 tensor x bình thường, thì các giá trị x sẽ được lữu trữ liên tiếp (theo từng hàng, hết hàng xuống hàng dưới) và x[i,j] sẽ tương ứng storage[offset+i*col+j] do đó x sẽ là contiguous tensor, còn khi mình thực hiện transpose thì x_t dùng chung storage với x nên thứ tự index không còn được như mặc định, do đó x_t không phải contiguous tensor.

Mình có thể chuyển 1 tensor không phải contiguous tensor sang contigous tensor bằng hàm contiguous().

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
4

Mình thấy là giá trị x_t_con và x_t là như nhau, tuy nhiên vùng storage khác nhau và stride sẽ khác nhau.

Torch GPU

Phần trước mình có nói về storage thì mặc định sẽ lưu ở CPU, tuy nhiên Torch cho phép tensor lưu ở GPU để tính toán song song cũng như tăng tốc độ xử lý.

Nếu 1 tensor được lưu ở GPU, thì các phép tính toán sẽ được thực hiện ở GPU.

Để khởi tạo 1 tensor và lưu trên gpu thì mình dùng thuộc tính device.

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
5

Hoặc mình có thể copy 1 tensor từ CPU sang GPU

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
6

Mỗi tensor chỉ được lưu trên 1 GPU nhất định nên nếu có nhiều GPU thì phải chỉ rõ lưu trên GPU nào, index GPU cũng bắt đầu từ 0.

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
7

Để chuyển ngược lại từ GPU về CPU thì mình dùng

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
8

Vậy là mình đã đi qua kiến thức cơ bản của Torch tensors, những bài sau mình sẽ dùng tensors để xây các mô hình neural network, CNN,…

Torch Tensor to Numpy Array

Torch cho phép chuyển tensor sang Numpy array. Các thuộc tính về size, shape sẽ được giữ nguyên, type sẽ chuyển từ Torch sang Numpy.

x[-1] # Tương đương với x[x.shape[0] - 1], lấy phần tử cuối cùng
9

Nếu tensor được lưu trên CPU, Torch tensor và Numpy array sẽ dùng chung vùng nhớ, nên thay đổi giá trị ở 1 biến thì giá trị biến còn lại cũng thay đổi.

x = x[:] = x[::] = x[0:x.shape[0]:1] # lấy tất các phần tử trong x
0

Nếu tensor được lưu trên GPU thì mọi người sẽ không thể chuyển trực tiếp tensor sang Numpy array được, mà mình cần copy nội dung của tensor sang CPU trước rồi mới chuyển sang Numpy array. Do đó 2 biến trên gpu và np không dùng chung vùng nhớ và sửa 1 biến không ảnh hưởng biến còn lại.

x = x[:] = x[::] = x[0:x.shape[0]:1] # lấy tất các phần tử trong x
1

Tương tự, mình có thể chuyển Numpy array sang Torch tensor. Torch tensor sẽ lưu ở CPU và 2 biến trên np và cpu sẽ dùng chung vùng nhớ.

x = x[:] = x[::] = x[0:x.shape[0]:1] # lấy tất các phần tử trong x
2

Vậy là bài này mình đã học các kiến thức cơ bản của Torch Tensors, bài sau mình sẽ học về autograd trong tensors.

Code bài này mọi người lấy ở đây.

Tags: contiguous tensortensortensor gputensor metadatatorchtorch storagetorch tensortranspose contiguous tensortensortensor gputensor metadatatorchtorch storagetorch tensortranspose