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.
Đang xem: Giới thiệu tensor là gì, tensor trong học sâu là gì
Ảnh màu (rgb) được biểu diễn dưới dạng 1 tensor 3 chiều
Tensor, nguồn.
Vector
Để truy cập đến phần tử của vector và sửa phần tử của vector ta dùng chỉ số index. Index sẽ được đánh bắt đầu từ 0 đến phần tử cuối cùng của vector.
Slicing
Nếu mọi người không truyền gì thì mặc định start=0, stop=x.shape<0> và 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 xNhư ví dụ x<1:5:2> ở trên thì mình sẽ lấy phần tử đầu ở index 1, sau đó lấy phần tử ở index 3, tuy nhiên sẽ không lấy phần tử ở index 5 vì mình chỉ lấy từ index start đến (stop – 1) hay từ 1 -> 4.
x[1:5:2> # output: [2, 4>Torch tensors không hỗ trợ step âm như python list.
Ma trận
Khác với vector là 1D, ma trận 2D, biểu diễn dưới dạng hàng và cột, kích thước ma trận được quy định là số hàng * số cột, ví dụ ma trận ở dưới displaystyle Ainmathbb{R}^{3 imes 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
Slicing trên ma trận
# A[1:, :1> # Tương đương A[1:A.shape[0>:1, 0:1:1>Mình nói là slicing cũng truyền hàng trước, cột sau. Phần hàng truyền 1: nên sẽ lấy từ hàng 1 đến hết, còn phần cột truyền :1 nên sẽ hiểu là 0:1 và chỉ lấy cột 0.
A[:, 1> # tensor([2, 4, 6>)Mình nói là slicing cũng truyền hàng trước, cột sau. Phần hàng truyền “:” nên hiểu là “0:A.shape<0>:1” ý là lấy tất các hàng, còn phần cột truyền 1 nên sẽ lấy cột 1. Do đó A<:, 1> lấy ra cột index 1.
Tensor 3D
Với tensor 3D thì thuộc tính shape sẽ cho ra 3 giá trị, tương ứng độ sâu (depth), số hàng, số cột. Để truy cập phần tử thì mình cũng phải chỉ rõ index của depth, hàng và cột. Tương tự để slicing thì mình cũng phải slicing trên cả 3 chiều.
Ý tưởng với tensor n dimension tương tự.
Torch Properties
Dtype
Torch tensors chỉ chứa dữ liệu kiểu số và kiểu bool (True/False). Mỗi torch tensor thuộc 1 kiểu dữ liệu, ở thuộc tính dtype. Đây là danh sách các kiểu dữ liệu torch tensors có thể chứa:
torch.float32 or torch.float: 32-bit floating-pointtorch.float64 or torch.double: 64-bit, double-precision floating-pointtorch.float16 or torch.half: 16-bit, half-precision floating-pointtorch.int8: signed 8-bit integerstorch.uint8: unsigned 8-bit integerstorch.int16 or torch.short: signed 16-bit integerstorch.int32 or torch.int: signed 32-bit integerstorch.int64 or torch.long: signed 64-bit integerstorch.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.float32Tuy 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 tensorpoints = torch.tensor([7, 8, 10, 6>, dtype=torch.short)print(points.dtype) # output: torch.int16Hoặ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 đó Ainmathbb{R}^{3 imes 2} Rightarrow A^Tinmathbb{R}^{2 imes 3}
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.
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ữ.
x sẽ trỏ đến phần tử đầu tiên, và để lấy phần tử x 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 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.
x trỏ đến phần tử hàng 0, cột 0 (x<0><0>). Phần tử x
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 = torch.tensor([[1,2,3>,[4,5,6>>)x.storage() # output: 1,2,3,4,5,6x[1>[2> == x.storage()[5> # output: True
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.
Size 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.
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
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.
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 = torch.tensor([[3, 1, 2>, [4, 1, 7>>)x_t = x.t() # Viết gọn cho x.transpose(0, 1)id(x.storage()) == id(x_t.storage()) # output: True. Hàm id trả về địa chỉ của x.storage(), mình thấy là x và x_t có cùng storage.
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 Ainmathbb{R}^{2 imes 3}, A^Tinmathbb{R}^{3 imes 2}
x.stride() # (3,1)x_t.stride() # (1,3)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.is_contiguous() # output: Truex_t.is_contiguous() # output: Falsex.view(1, -1) # [3, 1, 2, 4, 1, 7>x_t.view(1, -1) # RuntimeErrorKhi 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 sẽ tương ứng storage
Mình có thể chuyển 1 tensor không phải contiguous tensor sang contigous tensor bằng hàm contiguous().
x_t_con = x_t.contiguous()x_t_con.is_contiguous() # Trả về Truex_t_con.storage() # 3 4 1 1 2 7Mì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_gpu = torch.tensor([[4.0, 1.0>, [5.0, 3.0>, [2.0, 1.0>>, device=”cuda”)Hoặc mình có thể copy 1 tensor từ CPU sang GPU
x = torch.tensor([[4.0, 1.0>, [5.0, 3.0>, [2.0, 1.0>>)x_gpu = x.to(device=”cuda”)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_gpu = x.to(device=”cuda:0″)# hoặcx_gpu = x.cuda(0)x_gpu = x_gpu + 4 # Thực hiện phép tính trên GPUĐể chuyển ngược lại từ GPU về CPU thì mình dùng
x_cpu = x_gpu.to(device=”cpu”)# hoặcx_cpu = x_gpu.cpu()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 = torch.tensor([1,2,3>)x_np = x.numpy()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[1> = 0print(x) # output: [1, 0, 3>print(x_np) # output: [1, 0, 3>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_gpu = torch.tensor([1, 2, 3>, device=”cuda”)x_np = x_gpu.numpy() # Errorx_np = x_gpu.cpu().numpy() # okx_gpu[1> = 0 print(x_gpu) # output: [1, 0, 3>print(x_np) # output: [1, 2, 3>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ớ.
Xem thêm: Chi Phí Vốn Là Gì ? Ý Nghĩa Của Chi Phí Vốn Trong Kinh Doanh
x_np = np.array([1, 2, 3>)x_cpu = torch.from_numpy(x_np)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.