# Tensor概念介绍 飞桨(PaddlePaddle,以下简称Paddle)和其他深度学习框架一样,使用**Tensor**来表示数据,在神经网络中传递的数据均为**Tensor**。 **Tensor**可以将其理解为多维数组,其可以具有任意多的维度,不同**Tensor**可以有不同的**数据类型** (dtype) 和**形状** (shape)。 同一**Tensor**的中所有元素的dtype均相同。如果你对 [Numpy](https://www.paddlepaddle.org.cn/tutorials/projectdetail/590690) 熟悉,**Tensor**是类似于 **Numpy array** 的概念。 ### 目录 * [Tensor的创建](#1) * [Tensor的shape](#2) * [Tensor其他属性](#3) * [Tensor的操作](#4) ---------- ##

Tensor的创建

首先,让我们开始创建一个 **Tensor** : ### 1. 创建类似于vector的**1-D Tensor**,其rank为1 ```python # 可通过dtype来指定Tensor数据类型,否则会创建float32类型的Tensor rank_1_tensor = paddle.to_tensor([2.0, 3.0, 4.0], dtype='float64') print(rank_1_tensor) ``` ```text Tensor: generated_tensor_1 - place: CUDAPlace(0) - shape: [3] - layout: NCHW - dtype: double - data: [2.0, 3.0, 4.0] ``` 特殊地,如果仅输入单个scalar类型数据(例如float/int/bool类型的单个元素),则会创建shape为[1]的**Tensor** ```python paddle.to_tensor(2) paddle.to_tensor([2]) ``` 上述两种创建方式完全一致,shape均为[1],输出如下: ```text Tensor: generated_tensor_0 - place: CUDAPlace(0) - shape: [1] - layout: NCHW - dtype: int32_t - data: [2] ``` ### 2. 创建类似于matrix的**2-D Tensor**,其rank为2 ```python rank_2_tensor = paddle.to_tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) print(rank_2_tensor) ``` ```text Tensor: generated_tensor_2 - place: CUDAPlace(0) - shape: [2, 3] - layout: NCHW - dtype: double - data: [1.0 2.0 3.0 4.0 5.0 6.0] ``` ### 3. 同样地,还可以创建rank为3、4...N等更复杂的多维Tensor ``` # Tensor可以有任意数量的轴(也称为维度) rank_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(rank_3_tensor) ``` ```text Tensor: generated_tensor_3 - place: CUDAPlace(0) - shape: [2, 2, 5] - layout: NCHW - dtype: double - data: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20] ``` 上述不同rank的**Tensor**可以可视化的表示为:

图1 不同rank的Tensor可视化表示
你可以通过 Tensor.numpy() 方法方便地将 **Tensor** 转化为 **Numpy array**: ```python print(rank_2_tensor.numpy()) ``` ```text array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=float32) ``` **Tensor**不仅支持 floats、ints 类型数据,也支持 complex numbers 数据: ```python rank_2_complex_tensor = paddle.to_tensor([[1+1j, 2+2j], [3+3j, 4+4j]]) ``` ```text CompleTensor[real]: generated_tensor_0.real - place: CUDAPlace(0) - shape: [2, 2] - layout: NCHW - dtype: float - data: [1 2 3 4] CompleTensor[imag]: generated_tensor_0.real - place: CUDAPlace(0) - shape: [2, 2] - layout: NCHW - dtype: float - data: [1 2 3 4] ``` 如果检测到输入数据包含complex numbers,则会自动创建一个**ComplexTensor**,**ComplexTensor**是Paddle中一种特殊的数据结构, 其包含实部(real)与虚部(imag)两个形状与数据类型相同的**Tensor**,其结构可视化表示为:

图2 ComplexTensor的可视化表示
**Tensor**必须形状规则,类似于“矩形”的概念,也就是,沿任何一个轴(也称作维度)上,元素的数量都是相等的,如果为以下情况: ``` rank_2_tensor = paddle.to_tensor([[1.0, 2.0], [4.0, 5.0, 6.0]]) ``` 该情况下将会抛出异常: ```text ValueError: Faild to convert input data to a regular ndarray : - Usually this means the input data contains nested lists with different lengths. ``` 上面介绍了通过Python数据来创建**Tensor**的方法,我们也可以通过 **Numpy array** 来创建**Tensor**: ```python rank_1_tensor = paddle.to_tensor(Numpy array([1.0, 2.0])) rank_2_tensor = paddle.to_tensor(Numpy array([[1.0, 2.0], [3.0, 4.0]])) rank_3_tensor = paddle.to_tensor(numpy.random.rand(3, 2)) ``` 创建的 **Tensor** 与原 **Numpy array** 具有相同的 shape 与 dtype。 如果要创建一个指定shape的**Tensor**,Paddle也提供了一些API: ```text paddle.zeros([m, n]) # 创建数据全为0,shape为[m, n]的Tensor paddle.ones([m, n]) # 创建数据全为1,shape为[m, n]的Tensor paddle.full([m, n], 10) # 创建数据全为10,shape为[m, n]的Tensor paddle.arrange(start, end, step) # 创建从start到end,步长为step的Tensor paddle.linspace(start, end, num) # 创建从start到end,元素个数固定为num的Tensor ``` ---------- ##

Tensor的shape

### 基本概念 查看一个**Tensor**的形状可以通过 **Tensor.shape**,shape是 **Tensor** 的一个重要属性,以下为相关概念: 1. shape:描述了tensor的每个维度上的元素数量 2. rank: tensor的维度的数量,例如vector的rank为1,matrix的rank为2. 3. axis或者dimension:指tensor某个特定的维度 4. size:指tensor中全部元素的个数 让我们来创建1个4-D **Tensor**,并通过图形来直观表达以上几个概念之间的关系; ```python rank_4_tensor = paddle.ones([2, 3, 4, 5]) ```

图3 Tensor的shape、axis、dimension、rank之间的关系
```python print("Data Type of every element:", rank_4_tensor.dtype) print("Number of dimensions:", rank_4_tensor.ndim) print("Shape of tensor:", rank_4_tensor.shape) print("Elements number along axis 0 of tensor:", rank_4_tensor.shape[0]) print("Elements number along the last axis of tensor:", rank_4_tensor.shape[-1]) ``` ```text 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 ``` ### 索引 通过索引能方便地对Tensor进行“切片”操作。Paddle使用标准的 Python索引规则 与 Numpy索引规则,与[ndexing a list or a string in Python](https://docs.python.org/3/tutorial/introduction.html#strings)类似。具有以下特点: 1. 如果索引为负数,则从尾部开始计算 2. 如果索引使用 ``:`` ,则其对应格式为start: stop: step,其中start、stop、step均可缺省 * 针对1-D **Tensor**,则仅有单个轴上的索引: ```python rank_1_tensor = paddle.to_tensor([0, 1, 2, 3, 4, 5, 6, 7, 8]) print("Origin Tensor:", rank_1_tensor.numpy()) print("First element:", rank_1_tensor[0].numpy()) print("Last element:", rank_1_tensor[-1].numpy()) print("All element:", rank_1_tensor[:].numpy()) print("Before 3:", rank_1_tensor[:3].numpy()) print("From 6 to the end:", rank_1_tensor[6:].numpy()) print("From 3 to 6:", rank_1_tensor[3:6].numpy()) print("Interval of 3:", rank_1_tensor[::3].numpy()) print("Reverse:", rank_1_tensor[::-1].numpy()) ``` ```text Origin Tensor: array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=int64) 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] ``` * 针对2-D及以上的 **Tensor**,则会有多个轴上的索引: ```python rank_2_tensor = paddle.to_tensor([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]) print("Origin Tensor:", rank_2_tensor.numpy()) print("First row:", rank_2_tensor[0].numpy()) print("First row:", rank_2_tensor[0, :].numpy()) print("First column:", rank_2_tensor[:, 0].numpy()) print("Last column:", rank_2_tensor[:, -1].numpy()) print("All element:", rank_2_tensor[:].numpy()) print("First row and second column:", rank_2_tensor[0, 1].numpy()) ``` ```text Origin Tensor: array([[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]], dtype=int64) 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] ``` 输入索引的第一个值对应axis 0,第二个值对应axis 1,以此类推,如果某个axis上未指定索引,则默认为 ``:`` 。例如: ``` rank_3_tensor[1] rank_3_tensor[1, :] rank_3_tensor[1, :, :] ``` 以上三种索引的结果是完全相同的。 ### 对shape进行操作 重新定义**Tensor**的shape在实际编程中具有重要意义。 ```python rank_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 rank_3_tensor:", rank_3_tensor.shape) ``` ```text the shape of rank_3_tensor: [3, 2, 5] ``` Paddle提供了reshape接口来改变Tensor的shape: ```python rank_3_tensor = paddle.reshape(rank_3_tensor, [2, 5, 3]) print("After reshape:", rank_3_tensor.shape) ``` ```text After reshape: [2, 5, 3] ``` 在指定新的shape时存在一些技巧: **1.** -1 表示这个维度的值是从Tensor的元素总数和剩余维度推断出来的。因此,有且只有一个维度可以被设置为-1。 **2.** 0 表示实际的维数是从Tensor的对应维数中复制出来的,因此shape中0的索引值不能超过x的维度。 有一些例子可以很好解释这些技巧: ```text 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] ``` 可以发现,reshape为[-1]时,会将tensor按其在计算机上的内存分布展平为1-D Tensor。 ```python print("Tensor flattened to Vector:", paddle.reshape(rank_3_tensor, [-1]).numpy()) ``` ```text 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**的数据类型,可以通过 Tensor.dtype 来查看,dtype支持:'bool','float16','float32','float64','uint8','int8','int16','int32','int64'。 * 通过Python元素创建的Tensor,可以通过dtype来进行指定,如果未指定: * 对于python整型数据,则会创建int64型Tensor * 对于python浮点型数据,默认会创建float32型Tensor,并且可以通过set_default_type来调整浮点型数据的默认类型。 * 通过Numpy array创建的Tensor,则与其原来的dtype保持相同。 ```python print("Tensor dtype from Python integers:", paddle.to_tensor(1).dtype) print("Tensor dtype from Python floating point:", paddle.to_tensor(1.0).dtype) ``` ```text Tensor dtype from Python integers: VarType.INT64 Tensor dtype from Python floating point: VarType.FP32 ``` Paddle提供了**cast**接口来改变dtype: ```python 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.dthpe) ``` ```text Tensor after cast to float64: VarType.FP64 Tensor after cast to int64: VarType.INT64 ``` ### Tensor的place 初始化**Tensor**时可以通过**place**来指定其分配的设备位置,可支持的设备位置有三种:CPU/GPU/固定内存,其中固定内存也称为不可分页内存或锁页内存,其与GPU之间具有更高的读写效率,并且支持异步传输,这对网络整体性能会有进一步提升,但其缺点是分配空间过多时可能会降低主机系统的性能,因为其减少了用于存储虚拟内存数据的可分页内存。 * **创建CPU上的Tensor**: ```python cpu_tensor = paddle.to_tensor(1, place=paddle.CPUPlace()) print(cpu_tensor) ``` ```text Tensor: generated_tensor_0 - place: CPUPlace ``` * **创建GPU上的Tensor**: ```python gpu_tensor = paddle.to_tensor(1, place=paddle.CUDAPlace(0)) print(gpu_tensor) ``` ```text Tensor: generated_tensor_0 - place: CUDAPlace(0) ``` * **创建固定内存上的Tensor**: ```python pin_memory_tensor = paddle.to_tensor(1, place=paddle.CUDAPinnedPlace()) print(pin_memory_tensor) ``` ```text Tensor: generated_tensor_0 - place: CUDAPinnedPlace ``` ### Tensor的name Tensor的name是其唯一的标识符,为python 字符串类型,查看一个Tensor的name可以通过Tensor.name属性。默认地,在每个Tensor创建时,Paddle会自定义一个独一无二的name。 ```python print("Tensor name:", paddle.to_tensor(1).name) ``` ```text Tensor name: generated_tensor_0 ``` ---------- ##

Tensor的操作

Paddle提供了丰富的Tensor操作的API,包括数学运算符、逻辑运算符、线性代数相关等100+余种API,这些API调用有两种方法: ```python x = paddle.to_tensor([[1.1, 2.2], [3.3, 4.4]]) y = paddle.to_tensor([[5.5, 6.6], [7.7, 8.8]]) print(paddle.add(x, y), "\n") print(x.add(y), "\n") ``` ```text Tensor: eager_tmp_2 - place: CUDAPlace(0) - shape: [2, 2] - layout: NCHW - dtype: float - data: [6.6 8.8 11 13.2] Tensor: eager_tmp_3 - place: CUDAPlace(0) - shape: [2, 2] - layout: NCHW - dtype: float - data: [6.6 8.8 11 13.2] ``` 可以看出,使用 **Tensor类成员函数** 和 **paddle API** 具有相同的效果,由于 **类成员函数** 操作更为方便,以下均从 **Tensor类成员函数** 的角度,对常用**Tensor**操作进行介绍。 #### 数学运算符 ```python x.abs() #绝对值 x.ceil() #向上取整 x.floor() #向下取整 x.exp() #逐元素计算自然常数为底的指数 x.log() #逐元素计算x的自然对数 x.reciprocal() #求倒数 x.square() #逐元素计算平方 x.sqrt() #逐元素计算平方根 x.sum() #计算所有元素的和 x.asin() #逐元素计算反正弦函数 x.add(y) #逐元素相加 x.add(-y) #逐元素相减 x.multiply(y) #逐元素相乘 x.divide(y) #逐元素相除 x.floor_divide(y) #逐元素相除并取整 x.remainder(y) #逐元素相除并取余 x.pow(y) #逐元素幂运算 x.reduce_max() #所有元素最大值,可以指定维度 x.reduce_min() #所有元素最小值,可以指定维度 x.reduce_prod() #所有元素累乘,可以指定维度 x.reduce_sum() #所有元素的和,可以指定维度 ``` Paddle对python数学运算相关的魔法函数进行了重写,以下操作与上述结果相同。 ```text x + y -> x.add(y) #逐元素相加 x - y -> x.add(-y) #逐元素相减 x * y -> x.multiply(y) #逐元素相乘 x / y -> x.divide(y) #逐元素相除 x // y -> x.floor_divide(y) #逐元素相除并取整 x % y -> x.remainder(y) #逐元素相除并取余 x ** y -> x.pow(y) #逐元素幂运算 ``` #### 逻辑运算符 ```python x.is_empty() #判断tensor是否为空 x.isfinite() #判断tensor中元素是否是有限的数字,即不包括inf与nan x.euqal_all(y) #判断两个tensor的所有元素是否相等 x.euqal(y) #判断两个tensor的每个元素是否相等 x.not_equal(y) #判断两个tensor的每个元素是否不相等 x.less_than(y) #判断tensor x的元素是否小于tensor y的对应元素 x.less_equal(y) #判断tensor x的元素是否小于或等于tensor y的对应元素 x.greater_than(y) #判断tensor x的元素是否大于tensor y的对应元素 x.greater_equal(y) #判断tensor x的元素是否大于或等于tensor y的对应元素 ``` 同样地,Paddle对python逻辑比较相关的魔法函数进行了重写,以下操作与上述结果相同。 ```text x == y -> x.euqal(y) #判断两个tensor的每个元素是否相等 x != y -> x.not_equal(y) #判断两个tensor的每个元素是否不相等 x < y -> x.less_than(y) #判断tensor x的元素是否小于tensor y的对应元素 x <= y -> x.less_equal(y) #判断tensor x的元素是否小于或等于tensor y的对应元素 x > y -> x.greater_than(y) #判断tensor x的元素是否大于tensor y的对应元素 x >= y -> x.greater_equal(y) #判断tensor x的元素是否大于或等于tensor y的对应元素 ``` 以下操作仅针对bool型Tensor: ```python x.reduce_all() #判断一个bool型tensor是否所有元素为True x.reduce_any() #判断一个bool型tensor是否存在至少1个元素为True x.logical_and(y) #对两个bool型tensor逐元素进行逻辑与操作 x.logical_or(y) #对两个bool型tensor逐元素进行逻辑或操作 x.logical_xor(y) #对两个bool型tensor逐元素进行逻辑亦或操作 x.logical_not(y) #对两个bool型tensor逐元素进行逻辑非操作 ``` #### 线性代数相关 ```python x.cholesky() #矩阵的cholesky分解 x.t() #矩阵转置 x.transpose([1, 0]) #交换axis 0 与axis 1的顺序 x.norm('pro') #矩阵的Frobenius 范数 x.dist(y, p=2) #矩阵(x-y)的2范数 x.matmul(y) #矩阵乘法 ``` 需要注意,Paddle中Tensor的操作符均为非inplace操作,即 ``x.add(y)`` 不会在**tensor x**上直接进行操作,而会返回一个新的**Tensor**来表示运算结果。 更多Tensor操作相关的API,请参考[class paddle.Tensor](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/tensor/creation/Tensor_cn.html)