Tensor基础实践
Tensor基礎(chǔ)實(shí)踐
飛槳(PaddlePaddle,以下簡(jiǎn)稱Paddle)和其他深度學(xué)習(xí)框架一樣,使用Tensor來(lái)表示數(shù)據(jù),在神經(jīng)網(wǎng)絡(luò)中傳遞的數(shù)據(jù)均為Tensor。
Tensor可以將其理解為多維數(shù)組,其可以具有任意多的維度,不同Tensor可以有不同的數(shù)據(jù)類型 (dtype) 和形狀 (shape)。
同一Tensor的中所有元素的dtype均相同。如果對(duì) Numpy 熟悉,Tensor是類似于 Numpy array 的概念。
Tensor的創(chuàng)建
首先,讓開始創(chuàng)建一個(gè) Tensor , 并用 ndim 表示 Tensor 維度的數(shù)量:
- 創(chuàng)建類似于vector的1-D Tensor,其 ndim 為1
可通過dtype來(lái)指定Tensor數(shù)據(jù)類型,否則會(huì)創(chuàng)建float32類型的Tensor
ndim_1_tensor = paddle.to_tensor([2.0, 3.0, 4.0], dtype=‘float64’)
print(ndim_1_tensor)
Tensor(shape=[3], dtype=float64, place=CUDAPlace(0), stop_gradient=True,
[2., 3., 4.])
特殊地,如果僅輸入單個(gè)scalar類型數(shù)據(jù)(例如float/int/bool類型的單個(gè)元素),則會(huì)創(chuàng)建shape為[1]的Tensor
paddle.to_tensor(2)
paddle.to_tensor([2])
上述兩種創(chuàng)建方式完全一致,shape均為[1],輸出如下:
Tensor(shape=[1], dtype=int64, place=CUDAPlace(0), stop_gradient=True,
[2])
2. 創(chuàng)建類似于matrix的2-D Tensor,其 ndim 為2
ndim_2_tensor = paddle.to_tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
print(ndim_2_tensor)
Tensor(shape=[2, 3], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
[[1., 2., 3.],
[4., 5., 6.]])
3. 同樣地,還可以創(chuàng)建 ndim 為3、4…N等更復(fù)雜的多維Tensor
Tensor可以有任意數(shù)量的軸(也稱為維度)
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)
Tensor(shape=[2, 2, 5], dtype=int64, place=CUDAPlace(0), stop_gradient=True,
[[[1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10]],
[[11, 12, 13, 14, 15],[16, 17, 18, 19, 20]]])
上述不同ndim的Tensor可以可視化的表示為:
圖1 不同ndim的Tensor可視化表示
可以通過 Tensor.numpy() 方法方便地將 Tensor 轉(zhuǎn)化為 Numpy array:
ndim_2_tensor.numpy()
array([[1., 2., 3.],
[4., 5., 6.]], dtype=float32)
Tensor不僅支持 floats、ints 類型數(shù)據(jù),也支持 complex numbers數(shù)據(jù):
ndim_2_complex_tensor = paddle.to_tensor([[1+1j, 2+2j],
[3+3j, 4+4j]])
如果輸入為復(fù)數(shù)數(shù)據(jù),則Tensor的dtype為 complex64 或 complex128 ,其每個(gè)元素均為1個(gè)復(fù)數(shù):
Tensor(shape=[2, 2], dtype=complex64, place=CUDAPlace(0), stop_gradient=True,
[[(1+1j), (2+2j)],
[(3+3j), (4+4j)]])
Tensor必須形狀規(guī)則,類似于“矩形”的概念,也就是,沿任何一個(gè)軸(也稱作維度)上,元素的數(shù)量都是相等的,如果為以下情況:
ndim_2_tensor = paddle.to_tensor([[1.0, 2.0],
[4.0, 5.0, 6.0]])
該情況下將會(huì)拋出異常:
ValueError:
Faild to convert input data to a regular ndarray :
- Usually this means the input data contains nested lists with different lengths.
上面介紹了通過Python數(shù)據(jù)來(lái)創(chuàng)建Tensor的方法,也可以通過 Numpy array 來(lái)創(chuàng)建Tensor:
ndim_1_tensor = paddle.to_tensor(numpy.array([1.0, 2.0]))
ndim_2_tensor = paddle.to_tensor(numpy.array([[1.0, 2.0],
[3.0, 4.0]]))
ndim_3_tensor = paddle.to_tensor(numpy.random.rand(3, 2))
創(chuàng)建的 Tensor 與原 Numpy array 具有相同的 shape 與 dtype。
如果要?jiǎng)?chuàng)建一個(gè)指定shape的Tensor,Paddle也提供了一些API:
paddle.zeros([m, n]) # 創(chuàng)建數(shù)據(jù)全為0,shape為[m, n]的Tensor
paddle.ones([m, n]) # 創(chuàng)建數(shù)據(jù)全為1,shape為[m, n]的Tensor
paddle.full([m, n], 10) # 創(chuàng)建數(shù)據(jù)全為10,shape為[m, n]的Tensor
paddle.arange( start, end, step) # 創(chuàng)建從start到end,步長(zhǎng)為step的Tensor
paddle.linspace( start, end, num) # 創(chuàng)建從start到end,元素個(gè)數(shù)固定為num的Tensor
Tensor的shape
基本概念
查看一個(gè)Tensor的形狀可以通過 Tensor.shape,shape是 Tensor 的一個(gè)重要屬性,以下為相關(guān)概念:
- shape:描述了tensor的每個(gè)維度上元素的數(shù)量
- ndim: tensor的維度數(shù)量,例如vector的 ndim 為1,matrix的 ndim 為2.
- axis或者dimension:指tensor某個(gè)特定的維度
- size:指tensor中全部元素的個(gè)數(shù)
讓來(lái)創(chuàng)建1個(gè)4-D Tensor,并通過圖形來(lái)直觀表達(dá)以上幾個(gè)概念之間的關(guān)系;
ndim_4_tensor = paddle.ones([2, 3, 4, 5])
圖2 Tensor的shape、axis、dimension、ndim之間的關(guān)系
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])
Data Type of every element: VarType.FP32
Number of dimensions: 4
Shape of tensor: [2, 3, 4, 5]
Elements number along axis 0 of tensor: 2
Elements number along the last axis of tensor: 5
對(duì)shape進(jìn)行操作
重新定義Tensor的shape在實(shí)際編程中具有重要意義。
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]],
[[21, 22, 23, 24, 25],
[26, 27, 28, 29, 30]]])
print(“the shape of ndim_3_tensor:”, ndim_3_tensor.shape)
the shape of ndim_3_tensor: [3, 2, 5]
Paddle提供了reshape接口來(lái)改變Tensor的shape:
ndim_3_tensor = paddle.reshape(ndim_3_tensor, [2, 5, 3])
print(“After reshape:”, ndim_3_tensor.shape)
After reshape: [2, 5, 3]
在指定新的shape時(shí)存在一些技巧:
- -1 表示這個(gè)維度的值是從Tensor的元素總數(shù)和剩余維度推斷出來(lái)的。因此,有且只有一個(gè)維度可以被設(shè)置為-1。 2.0 表示實(shí)際的維數(shù)是從Tensor的對(duì)應(yīng)維數(shù)中復(fù)制出來(lái)的,因此shape中0的索引值不能超過x的維度。
有一些例子可以很好解釋這些技巧:
origin:[3, 2, 5] reshape:[3, 10] actual: [3, 10]
origin:[3, 2, 5] reshape:[-1] actual: [30]
origin:[3, 2, 5] reshape:[0, 5, -1] actual: [3, 5, 2]
可以發(fā)現(xiàn),reshape為[-1]時(shí),會(huì)將tensor按其在計(jì)算機(jī)上的內(nèi)存分布展平為1-D Tensor。
print(“Tensor flattened to Vector:”, paddle.reshape(ndim_3_tensor, [-1]).numpy())
Tensor flattened to Vector: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30]
Tensor其他屬性
Tensor的dtype
Tensor的數(shù)據(jù)類型,可以通過 Tensor.dtype 來(lái)查看,dtype支持:‘bool’,‘float16’,‘float32’,‘float64’,‘uint8’,‘int8’,‘int16’,‘int32’,‘int64’。
? 通過Python元素創(chuàng)建的Tensor,可以通過dtype來(lái)進(jìn)行指定,如果未指定:
o 對(duì)于python整型數(shù)據(jù),則會(huì)創(chuàng)建int64型Tensor
o 對(duì)于python浮點(diǎn)型數(shù)據(jù),默認(rèn)會(huì)創(chuàng)建float32型Tensor,并且可以通過set_default_type來(lái)調(diào)整浮點(diǎn)型數(shù)據(jù)的默認(rèn)類型。
? 通過Numpy array創(chuàng)建的Tensor,則與其原來(lái)的dtype保持相同。
print(“Tensor dtype from Python integers:”, paddle.to_tensor(1).dtype)
print(“Tensor dtype from Python floating point:”, paddle.to_tensor(1.0).dtype)
Tensor dtype from Python integers: VarType.INT64
Tensor dtype from Python floating point: VarType.FP32
Paddle提供了cast接口來(lái)改變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)
Tensor after cast to float64: VarType.FP64
Tensor after cast to int64: VarType.INT64
Tensor的place
初始化Tensor時(shí)可以通過place來(lái)指定其分配的設(shè)備位置,可支持的設(shè)備位置有三種:CPU/GPU/固定內(nèi)存,其中固定內(nèi)存也稱為不可分頁(yè)內(nèi)存或鎖頁(yè)內(nèi)存,其與GPU之間具有更高的讀寫效率,并且支持異步傳輸,這對(duì)網(wǎng)絡(luò)整體性能會(huì)有進(jìn)一步提升,但其缺點(diǎn)是分配空間過多時(shí)可能會(huì)降低主機(jī)系統(tǒng)的性能,因?yàn)槠錅p少了用于存儲(chǔ)虛擬內(nèi)存數(shù)據(jù)的可分頁(yè)內(nèi)存。
? 創(chuàng)建CPU上的Tensor:
cpu_tensor = paddle.to_tensor(1, place=paddle.CPUPlace())
print(cpu_tensor)
Tensor(shape=[1], dtype=int64, place=CPUPlace, stop_gradient=True,
[1])
? 創(chuàng)建GPU上的Tensor:
gpu_tensor = paddle.to_tensor(1, place=paddle.CUDAPlace(0))
print(gpu_tensor)
Tensor(shape=[1], dtype=int64, place=CUDAPlace(0), stop_gradient=True,
[1])
? 創(chuàng)建固定內(nèi)存上的Tensor:
pin_memory_tensor = paddle.to_tensor(1, place=paddle.CUDAPinnedPlace())
print(pin_memory_tensor)
Tensor(shape=[1], dtype=int64, place=CUDAPinnedPlace, stop_gradient=True,
[1])
Tensor的name
Tensor的name是其唯一的標(biāo)識(shí)符,為python 字符串類型,查看一個(gè)Tensor的name可以通過Tensor.name屬性。默認(rèn)地,在每個(gè)Tensor創(chuàng)建時(shí),Paddle會(huì)自定義一個(gè)獨(dú)一無(wú)二的name。
print(“Tensor name:”, paddle.to_tensor(1).name)
Tensor name: generated_tensor_0
Tensor的操作
索引和切片
可以通過索引或切片方便地訪問或修改 Tensor。Paddle 使用標(biāo)準(zhǔn)的 Python 索引規(guī)則與 Numpy 索引規(guī)則,與 Indexing a list or a string in Python類似。具有以下特點(diǎn):
- 基于 0-n 的下標(biāo)進(jìn)行索引,如果下標(biāo)為負(fù)數(shù),則從尾部開始計(jì)算
- 通過冒號(hào): 分隔切片參數(shù) start:stop:step 來(lái)進(jìn)行切片操作,其中 start、stop、step 均可缺省
訪問 Tensor
? 針對(duì)1-D Tensor,則僅有單個(gè)軸上的索引或切片:
ndim_1_tensor = paddle.to_tensor([0, 1, 2, 3, 4, 5, 6, 7, 8])
print(“Origin Tensor:”, ndim_1_tensor.numpy())
print(“First element:”, ndim_1_tensor[0].numpy())
print(“Last element:”, ndim_1_tensor[-1].numpy())
print(“All element:”, ndim_1_tensor[:].numpy())
print(“Before 3:”, ndim_1_tensor[:3].numpy())
print(“From 6 to the end:”, ndim_1_tensor[6:].numpy())
print(“From 3 to 6:”, ndim_1_tensor[3:6].numpy())
print(“Interval of 3:”, ndim_1_tensor[::3].numpy())
print(“Reverse:”, ndim_1_tensor[::-1].numpy())
Origin Tensor: [0 1 2 3 4 5 6 7 8])
First element: [0]
Last element: [8]
All element: [0 1 2 3 4 5 6 7 8]
Before 3: [0 1 2]
From 6 to the end: [6 7 8]
From 3 to 6: [3 4 5]
Interval of 3: [0 3 6]
Reverse: [8 7 6 5 4 3 2 1 0]
? 針對(duì)2-D及以上的 Tensor,則會(huì)有多個(gè)軸上的索引或切片:
ndim_2_tensor = paddle.to_tensor([[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11]])
print(“Origin Tensor:”, ndim_2_tensor.numpy())
print(“First row:”, ndim_2_tensor[0].numpy())
print(“First row:”, ndim_2_tensor[0, :].numpy())
print(“First column:”, ndim_2_tensor[:, 0].numpy())
print(“Last column:”, ndim_2_tensor[:, -1].numpy())
print(“All element:”, ndim_2_tensor[:].numpy())
print(“First row and second column:”, ndim_2_tensor[0, 1].numpy())
Origin Tensor: [[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
First row: [0 1 2 3]
First row: [0 1 2 3]
First column: [0 4 8]
Last column: [ 3 7 11]
All element: [[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
First row and second column: [1]
索引或切片的第一個(gè)值對(duì)應(yīng) axis 0,第二個(gè)值對(duì)應(yīng) axis 1,以此類推,如果某個(gè) axis 上未指定索引,則默認(rèn)為 : 。例如:
ndim_2_tensor[1]
ndim_2_tensor[1, :]
這兩種操作的結(jié)果是完全相同的。
Tensor(shape=[4], dtype=int64, place=CPUPlace, stop_gradient=True,
[4, 5, 6, 7])
修改 Tensor
注意:
通過索引或切片修改 Tensor,該操作會(huì)原地修改該 Tensor 的數(shù)值,且原值不會(huì)被保存。如果被修改的 Tensor 參與梯度計(jì)算,將僅會(huì)使用修改后的數(shù)值,這可能會(huì)給梯度計(jì)算引入風(fēng)險(xiǎn)。Paddle 之后將會(huì)對(duì)具有風(fēng)險(xiǎn)的操作進(jìn)行檢測(cè)和報(bào)錯(cuò)。
與訪問 Tensor 類似,修改 Tensor 可以在單個(gè)或多個(gè)軸上通過索引或切片操作。同時(shí),支持將多種類型的數(shù)據(jù)賦值給該 Tensor,當(dāng)前支持的數(shù)據(jù)類型有:int, float, numpy.ndarray, Tensor。
import paddle
import numpy as np
x = paddle.to_tensor(np.ones((2, 3)).astype(np.float32)) # [[1., 1., 1.], [1., 1., 1.]]
x[0] = 0 # x : [[0., 0., 0.], [1., 1., 1.]] id(x) = 4433705584
x[0:1] = 2.1 # x : [[2.1, 2.1, 2.1], [1., 1., 1.]] id(x) = 4433705584
x[…] = 3 # x : [[3., 3., 3.], [3., 3., 3.]] id(x) = 4433705584
x[0:1] = np.array([1,2,3]) # x : [[1., 2., 3.], [3., 3., 3.]] id(x) = 4433705584
x[1] = paddle.ones([3]) # x : [[1., 2., 3.], [1., 1., 1.]] id(x) = 4433705584
同時(shí),Paddle 還提供了豐富的 Tensor 操作的 API,包括數(shù)學(xué)運(yùn)算符、邏輯運(yùn)算符、線性代數(shù)相關(guān)等100余種 API,這些 API 調(diào)用有兩種方法:
x = paddle.to_tensor([[1.1, 2.2], [3.3, 4.4]], dtype=“float64”)
y = paddle.to_tensor([[5.5, 6.6], [7.7, 8.8]], dtype=“float64”)
print(paddle.add(x, y), “\n”)
print(x.add(y), “\n”)
Tensor(shape=[2, 2], dtype=float64, place=CUDAPlace(0), stop_gradient=True,
[[6.60000000, 8.80000000],
[ 11., 13.20000000]])
Tensor(shape=[2, 2], dtype=float64, place=CUDAPlace(0), stop_gradient=True,
[[6.60000000, 8.80000000],
[ 11., 13.20000000]])
可以看出,使用 Tensor 類成員函數(shù) 和 Paddle API 具有相同的效果,由于 類成員函數(shù) 操作更為方便,以下均從 Tensor 類成員函數(shù) 的角度,對(duì)常用 Tensor 操作進(jìn)行介紹。
數(shù)學(xué)運(yùn)算符
x.abs() #逐元素取絕對(duì)值
x.ceil() #逐元素向上取整
x.floor() #逐元素向下取整
x.round() #逐元素四舍五入
x.exp() #逐元素計(jì)算自然常數(shù)為底的指數(shù)
x.log() #逐元素計(jì)算x的自然對(duì)數(shù)
x.reciprocal() #逐元素求倒數(shù)
x.square() #逐元素計(jì)算平方
x.sqrt() #逐元素計(jì)算平方根
x.sin() #逐元素計(jì)算正弦
x.cos() #逐元素計(jì)算余弦
x.add(y) #逐元素相加
x.subtract(y) #逐元素相減
x.multiply(y) #逐元素相乘
x.divide(y) #逐元素相除
x.mod(y) #逐元素相除并取余
x.pow(y) #逐元素冪運(yùn)算
x.max() #指定維度上元素最大值,默認(rèn)為全部維度
x.min() #指定維度上元素最小值,默認(rèn)為全部維度
x.prod() #指定維度上元素累乘,默認(rèn)為全部維度
x.sum() #指定維度上元素的和,默認(rèn)為全部維度
Paddle對(duì)python數(shù)學(xué)運(yùn)算相關(guān)的魔法函數(shù)進(jìn)行了重寫,以下操作與上述結(jié)果相同。
x + y -> x.add(y) #逐元素相加
x - y -> x.subtract(y) #逐元素相減
x * y -> x.multiply(y) #逐元素相乘
x / y -> x.divide(y) #逐元素相除
x % y -> x.mod(y) #逐元素相除并取余
x ** y -> x.pow(y) #逐元素冪運(yùn)算
邏輯運(yùn)算符
x.isfinite() #判斷tensor中元素是否是有限的數(shù)字,即不包括inf與nan
x.equal_all(y) #判斷兩個(gè)tensor的全部元素是否相等,并返回shape為[1]的bool Tensor
x.equal(y) #判斷兩個(gè)tensor的每個(gè)元素是否相等,并返回shape相同的bool Tensor
x.not_equal(y) #判斷兩個(gè)tensor的每個(gè)元素是否不相等
x.less_than(y) #判斷tensor x的元素是否小于tensor y的對(duì)應(yīng)元素
x.less_equal(y) #判斷tensor x的元素是否小于或等于tensor y的對(duì)應(yīng)元素
x.greater_than(y) #判斷tensor x的元素是否大于tensor y的對(duì)應(yīng)元素
x.greater_equal(y) #判斷tensor x的元素是否大于或等于tensor y的對(duì)應(yīng)元素
x.allclose(y) #判斷tensor x的全部元素是否與tensor y的全部元素接近,并返回shape為[1]的bool Tensor
同樣地,Paddle對(duì)python邏輯比較相關(guān)的魔法函數(shù)進(jìn)行了重寫,以下操作與上述結(jié)果相同。
x == y -> x.equal(y) #判斷兩個(gè)tensor的每個(gè)元素是否相等
x != y -> x.not_equal(y) #判斷兩個(gè)tensor的每個(gè)元素是否不相等
x < y -> x.less_than(y) #判斷tensor x的元素是否小于tensor y的對(duì)應(yīng)元素
x <= y -> x.less_equal(y) #判斷tensor x的元素是否小于或等于tensor y的對(duì)應(yīng)元素
x > y -> x.greater_than(y) #判斷tensor x的元素是否大于tensor y的對(duì)應(yīng)元素
x >= y -> x.greater_equal(y) #判斷tensor x的元素是否大于或等于tensor y的對(duì)應(yīng)元素
以下操作僅針對(duì)bool型Tensor:
x.logical_and(y) #對(duì)兩個(gè)bool型tensor逐元素進(jìn)行邏輯與操作
x.logical_or(y) #對(duì)兩個(gè)bool型tensor逐元素進(jìn)行邏輯或操作
x.logical_xor(y) #對(duì)兩個(gè)bool型tensor逐元素進(jìn)行邏輯亦或操作
x.logical_not(y) #對(duì)兩個(gè)bool型tensor逐元素進(jìn)行邏輯非操作
線性代數(shù)相關(guān)
x.cholesky() #矩陣的cholesky分解
x.t() #矩陣轉(zhuǎn)置
x.transpose([1, 0]) #交換axis 0 與axis 1的順序
x.norm(‘fro’) #矩陣的Frobenius 范數(shù)
x.dist(y, p=2) #矩陣(x-y)的2范數(shù)
x.matmul(y) #矩陣乘法
需要注意,Paddle中Tensor的操作符均為非inplace操作,即 x.add(y) 不會(huì)在tensor x上直接進(jìn)行操作,而會(huì)返回一個(gè)新的Tensor來(lái)表示運(yùn)算結(jié)果。
總結(jié)
以上是生活随笔為你收集整理的Tensor基础实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Paddle预训练模型应用工具Paddl
- 下一篇: Paddle广播 (broadcasti