張量(Tensor)、標量(scalar)、向量(vector)、矩陣(matrix)
飛槳 使用張量(Tensor) 來表示神經網路中傳遞的資料
,Tensor 可以理解為多維陣列,類似於 Numpy 陣列(ndarray) 的概念。與 Numpy 陣列相比,Tensor 除了支援執行在 CPU 上,還支援執行在 GPU 及各種 AI 晶片上,以實現計算加速;此外,飛槳基於 Tensor,實現了深度學習所必須的反向傳播功能和多種多樣的組網運算元,從而可更快捷地實現深度學習組網與訓練等功能。
Tensor 必須形如矩形,即在任何一個維度上,元素的數量必須相等,否則會丟擲異常
import paddle
# 建立類似向量(vector)的 1 維 Tensor:
ndim_1_Tensor = paddle.to_tensor([2.0, 3.0, 4.0])
print(ndim_1_Tensor)
# 建立類似矩陣(matrix)的 2 維 Tensor:
ndim_2_Tensor = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
print(ndim_2_Tensor)
# 建立 3 維 Tensor:
ndim_3_Tensor = paddle.to_tensor([[[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]],
[[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20]]])
print(ndim_3_Tensor)
輸出
"D:\Program Files\Python38\python.exe" D:/OpenSource/PaddlePaddle/Tensor.py
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[2., 3., 4.])
Tensor(shape=[2, 3], dtype=float32, place=Place(cpu), stop_gradient=True,
[[1., 2., 3.],
[4., 5., 6.]])
Tensor(shape=[2, 2, 5], dtype=int64, place=Place(cpu), stop_gradient=True,
[[[1 , 2 , 3 , 4 , 5 ],
[6 , 7 , 8 , 9 , 10]],
[[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20]]])
Process finished with exit code 0
paddle.zeros([m, n]) # 建立資料全為 0,形狀為 [m, n] 的 Tensor
paddle.ones([m, n]) # 建立資料全為 1,形狀為 [m, n] 的 Tensor
paddle.full([m, n], 10) # 建立資料全為 10,形狀為 [m, n] 的 Tensor
paddle.ones([2,3])
輸出
Tensor(shape=[2, 3], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[[1., 1., 1.],
[1., 1., 1.]])
如果要在指定區間內建立 Tensor,可以使用paddle.arange、 paddle.linspace 實現。
paddle.arange(start, end, step) # 建立以步長 step 均勻分隔區間[start, end)的 Tensor
paddle.linspace(start, stop, num) # 建立以元素個數 num 均勻分隔區間[start, stop)的 Tensor
paddle.arange(start=1, end=5, step=1)
輸出
Tensor(shape=[4], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[1, 2, 3, 4])
在常見深度學習任務中,資料樣本可能是圖片(image)、文字(text)、語音(audio)等多種型別,在送入神經網路訓練或推理前,這些資料和對應的標籤均需要建立為 Tensor。以下是影象場景和 NLP 場景中手動轉換 Tensor 方法的介紹。
import numpy as np
from PIL import Image
import paddle.vision.transforms as T
import paddle.vision.transforms.functional as F
fake_img = Image.fromarray((np.random.rand(224, 224, 3) * 255.).astype(np.uint8)) # 建立隨機圖片
transform = T.ToTensor()
tensor = transform(fake_img) # 使用 ToTensor()將圖片轉換為 Tensor
print(tensor)
除了手動建立 Tensor 外,實際在飛槳框架中有一些 API 封裝了 Tensor 建立的操作,從而無需使用者手動建立 Tensor。例如 paddle.io.DataLoader 能夠基於原始 Dataset,返回讀取 Dataset 資料的迭代器,迭代器返回的資料中的每個元素都是一個 Tensor。另外在一些高層 API,如 paddle.Model.fit 、paddle.Model.predict ,如果傳入的資料不是 Tensor,會自動轉為 Tensor 再進行模型訓練或推理。
paddle.Model.fit、paddle.Model.predict 等高層 API 支援傳入 Dataset 或 DataLoader,如果傳入的是 Dataset,那麼會用 DataLoader 封裝轉為 Tensor 資料;如果傳入的是 DataLoader,則直接從 DataLoader 迭代讀取 Tensor 資料送入模型訓練或推理。因此即使沒有寫將資料轉為 Tensor 的程式碼,也能正常執行,提升了程式設計效率和容錯性。
以下範例程式碼中,分別列印了原始資料集的資料,和送入 DataLoader 後返回的資料,可以看到資料結構由 Python list 轉為了 Tensor。
import paddle
from paddle.vision.transforms import Compose, Normalize
transform = Compose([Normalize(mean=[127.5],
std=[127.5],
data_format='CHW')])
test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)
print(test_dataset[0][1]) # 列印原始資料集的第一個資料的 label
loader = paddle.io.DataLoader(test_dataset)
for data in enumerate(loader):
x, label = data[1]
print(label) # 列印由 DataLoader 返回的迭代器中的第一個資料的 label
break
在前文中,可以看到列印 Tensor 時有 shape、dtype、place 等資訊,這些都是 Tensor 的重要屬性,想要了解如何操作 Tensor 需要對其屬性有一定了解,接下來分別展開介紹 Tensor 的屬性相關概念。
Tensor(shape=[3], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[2., 3., 4.])
形狀是 Tensor 的一個重要的基礎屬性,可以通過 Tensor.shape 檢視一個 Tensor 的形狀,以下為相關概念:
建立 1 個四維 Tensor ,並通過圖形來直觀表達以上幾個概念之間的關係:
ndim_4_Tensor = paddle.ones([2, 3, 4, 5])
print("Data Type of every element:", ndim_4_Tensor.dtype)
print("Number of dimensions:", ndim_4_Tensor.ndim)
print("Shape of Tensor:", ndim_4_Tensor.shape)
print("Elements number along axis 0 of Tensor:", ndim_4_Tensor.shape[0])
print("Elements number along the last axis of Tensor:", ndim_4_Tensor.shape[-1])
重新設定 Tensor 的 shape 在深度學習任務中比較常見,如一些計算類 API 會對輸入資料有特定的形狀要求,這時可通過 paddle.reshape 介面來改變 Tensor 的 shape,但並不改變 Tensor 的 size 和其中的元素資料。
以下範例程式碼中,建立 1 個 shape=[3] 的一維 Tensor,使用 reshape 功能將該 Tensor 重置為 shape=[1, 3] 的二維 Tensor。這種做法經常用在把一維的標籤(label)資料擴充套件為二維,由於飛槳框架中神經網路通常需要傳入一個 batch 的資料進行計算,因此可將資料增加一個 batch 維,方便後面的資料計算。
ndim_1_Tensor = paddle.to_tensor([1, 2, 3])
print("the shape of ndim_1_Tensor:", ndim_1_Tensor.shape)
reshape_Tensor = paddle.reshape(ndim_1_Tensor, [1, 3])
print("After reshape:", reshape_Tensor.shape)
在指定新的 shape 時存在一些技巧:
origin:[3, 2, 5] reshape:[3, 10] actual: [3, 10] # 直接指定目標 shape
origin:[3, 2, 5] reshape:[-1] actual: [30] # 轉換為 1 維,維度根據元素總數推斷出來是 3*2*5=30
origin:[3, 2, 5] reshape:[-1, 5] actual: [6, 5] # 轉換為 2 維,固定一個維度 5,另一個維度根據元素總數推斷出來是 30÷5=6
origin:[3, 2, 5] reshape:[0, -1] actual: [3, 6] # reshape:[0, -1]中 0 的索引值為 0,按照規則,轉換後第 0 維的元素數量與原始 Tensor 第 0 維的元素數量相同,為 3;第 1 維的元素數量根據元素總值計算得出為 30÷3=10。
origin:[3, 2] reshape:[3, 1, 0] error: # reshape:[3, 1, 0]中 0 的索引值為 2,但原 Tensor 只有 2 維,無法找到與第 3 維對應的元素數量,因此出錯。
飛槳框架的 API 有原位(Inplace)操作和非原位元運算之分,原位元運算即在原 Tensor 上儲存操作結果,輸出 Tensor 將與輸入 Tensor 共用資料,並且沒有 Tensor 資料拷貝的過程。非原位元運算則不會修改原 Tensor,而是返回一個新的 Tensor。通過 API 名稱區分兩者,如 paddle.reshape 是非原位元運算,paddle.reshape_ 是原位元運算。
Tensor 的資料型別 dtype 可以通過 Tensor.dtype 檢視,支援型別包括:bool、float16、float32、float64、uint8、int8、int16、int32、int64、complex64、complex128。
同一 Tensor 中所有元素的資料型別均相同,通常通過如下方式指定:
飛槳框架提供了paddle.cast 介面來改變 Tensor 的 dtype:
float32_Tensor = paddle.to_tensor(1.0)
float64_Tensor = paddle.cast(float32_Tensor, dtype='float64')
print("Tensor after cast to float64:", float64_Tensor.dtype)
int64_Tensor = paddle.cast(float32_Tensor, dtype='int64')
print("Tensor after cast to int64:", int64_Tensor.dtype)
索引或切片的第一個值對應第 0 維,第二個值對應第 1 維,依次類推,如果某個維度上未指定索引,則預設為 :
Python Numpy 切片
import paddle
# 建立 3 維 Tensor:
ndim_3_Tensor = paddle.to_tensor([[[[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15]],
[[21, 22, 23, 24, 25],
[26, 27, 28, 29, 30],
[31, 32, 33, 34, 35]]],
[[[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15]],
[[29, 22, 23, 24, 25],
[26, 57, 28, 29, 30],
[31, 32, 33, 34, 59]]]])
print("Origin Tensor:", ndim_3_Tensor.ndim)
print("Tensor Shape:", ndim_3_Tensor.shape) # [2, 2, 3, 5]
print("Slice:", ndim_3_Tensor[1, 1, 2, 4].numpy()) # 對應 shape
print("First row:", ndim_3_Tensor[0].numpy())
print("First row:", ndim_3_Tensor[1, 1, 2, 4].numpy())
print("First column:", ndim_3_Tensor[:, 0].numpy())
print("Last column:", ndim_3_Tensor[:, -1].numpy())
print("All element:", ndim_3_Tensor[:].numpy())
print("First row and second column:", ndim_3_Tensor[1, 0].numpy())
飛槳 Tensor 的廣播機制
Python NumPy 廣播(Broadcast)