01 | 什么是Tensor(张量)?
Tensor是PyTorch中的基础数据概念,直译过来为“张量”。那么什么是张量呢?
我们知道,数学是研究世间数量和空间关系的学科,“张量”作为一种特殊的数量形式,自然也来自于数学领域。
最原始的数量就是标量,因为其只有“数量”而没有变化方向,如数字“1”。
若将标量沿着某个方向进行数据扩展,就会得到一个1维数组,也称为1维张量(通俗理解是沿着某个方向“扩张”)。
若在1维数组的基础上再添加一个方向,则容易变成2维数组,即2维张量。
类似地,如果对一个二维数组进行第三个维度的“扩张”,那么可以得到一个3维数组,即3维张量;因此,从数学角度来看,Tensor(张量)本质是一个多维数组,它是标量、向量、矩阵的高维扩展。
下图中展示了从“标量”→“1维张量”→“2维张量”→“3维张量”的演化过程。需要注意的是,Tensor可以无穷维度扩张下去,因此理论上可以得到N维张量。
张量的应用源于现实应用需要,如早期的数字图像使用灰度值表示像素颜色,即每个像素中标记有0~255的数值,表示从白到黑的不同程度,图像整体由不同的像素灰度值组合而成,此时可以将灰度图像看作是一个二维矩阵。
若采用RGB三原色分别建立三个二维矩阵,那么采用一组颜色数组(R,G,B)来表示图像一个像素的颜色,即可得到一副RGB彩色图像。
02 | Tensor数据结构
PyTorch的Tensor是一种源于数学高维数组的数据结构,其中不仅包含原始的data部分,而补充了用于数学说明与梯度求解相关的数据属性。其中:
-
data部分表示数学上的数据张量
-
dtype表示高维数组的数据类型,通常默认使用64float类型
-
shape表示张量的形状,如(64,3,224,224),显然此时张量达到了四维度
-
device表示张量所在的设备,通常分为GPU和CPU,而能否存在GPU是借助GPU加速的关键
-
grad表示data部分的梯度
-
grad_fn表示创建Tensor的Function,是自动求导的关键
-
requires_grad表示是否需要梯度,因为计算梯度需要消耗时间和资源,因此取消本项可以节省时间
-
is_leaf表示是否是叶子节点,主要用于图模型中使用
03 | Tensor创建:直接创建
PyTorch中提供了直接创建Tensor的方法,一种方式为使用“torch.tensor()”直接创建,但是需指定相应的参数:
-
data:数据,可以是list和numpy
-
dtype:数据类型,默认与data一致
-
device:所在设备,选择cuda/cpu
-
requires_grad:是否需要计算梯度
-
pin_memory:是否存于锁页内存,默认False即可
我们尝试在PyCharm上运行代码,可以发现:1)新建tensor的数据类型默认继承了list/numpy数据类型;2)设置device参数后可以将其tensor存储在cuda上加速运算,默认在CPU上。
import torch
import numpy as np
# **************************** example 1 ****************************
# 通过torch.tensor()直接创建张量
flag = True
if flag:
# 创建一个3×3的单位矩阵 arr = np.ones((3,3))
print("ndarray的数据类型是: ", arr.dtype)
# 在CPU上基于numpy数据创建tensor
t1 = torch.tensor(arr)
print(t1)
# 在GPU上基于numpy数据创建tensor
t2 = torch.tensor(arr, device='cuda')
print(t2)
除去torch.tensor()之外,还可以直接从numpy的ndarray数据类型中创建tensor,j即“torch.from_numpy()”。但是需要注意的是,此时创建的tensor与原始的ndarray共享内存,当修改其中一个数据时,另外一个也会改动。
运行下述代码,可以发现ndarray数据arr与张量t的第[0,0]元素同时发生改变。
# **************************** example 2 ****************************
# 通过torch.from_numpy()创建共享张量
flag = True
if flag:
arr = np.array([[1,2,3], [1,2,3]])
t = torch.from_numpy(arr)
print("numpy array: ", arr)
print("tensor: ", t)
# 修改numpy的ndarray
arr[0,0] = 0
print("numpy array: ", arr)
print("tensor: ", t)
04 | Tensor创建:依据数值创建
041 | 创建等数值张量
第二种创建Tensor的方式是基于数值创建,此时常见于创建全0或全1的张量。
第一个介绍的方法是“torch.zeros()”,顾名思义,创建一个元素全0张量,大小形状基于size确定。其中的参数为:
-
size:表示张量的形状,如(3,3)或(3,224,224)
-
out:输出的张量
-
layout:内存中布局形式,有strided和sparse_coo等,后者用于稀疏矩阵提升处理效率,默认使用strided即可
-
device:表示张量所在设备,gpu/cpu
-
requires_grad:表示是否需要计算梯度
可以接受list或numpy数据
out_t = torch.tensor([1])
print(out_t)
# 新建全0张量并输出到已有的out_t
t = torch.zeros((4,4), out=out_t)
print(t)
print(out_t)
类似地,大家可以理解同样新建全1张量的命令“torch.ones()”,其用法基本相同。如果想自定义全X张量,那么可以采用升级版命令“torch.full()”来创建任意数值的命令,其重要参数有两个:
- size:表示张量的形状,如(9,9)
- fill_value:表示张量的元素值
042 | 创建等差变化张量
借助torch.full()可以创建等数值张量,同时也可以借助“torch.arange()”创建等差张量,注意这里的意思是“a range”而非“arrangement”的意思。此时有三个重要参数:
-
start:表示数列起始值
-
end:表示数列结束值,非闭合区间
-
step:数列公差,默认为1
此外还可以使用“torch.linspace”均分数列张量,这里的“linspace”表示“linear space”的意思。其主要参数如下:
-
start:表示数列起始值
-
end:数列结束值
-
steps:数列长度(或通俗理解为均分线段后的端点总数)
需要注意的是,不同于torch.arange(),torch.linspace()需要均分[end - start]之间的数列,因此需要完全闭合区间,即选取数列范围时包含start和end两个端点。
有趣的是,当我们按照“steps=5”等分[2,10]时,会发现间隔为2得到[2,4,6,8,10],即得到“5”个元素;而使用“steps=6”等分[2,10]时,考虑到“爬楼梯”问题中,所在楼层减一恰好等于实际爬过的层数,因而实际将闭合区间五等分后输出得到的6个端点。
# **************************** example 4 ****************************
# 通过torch.arange()创建等差张量,利用torch.linspace()均分数列创建张量
flag = True
if flag:
# 等差张量下,起始为闭开半闭区间
t1 = torch.arange(2,10,2)
print(t1)
# 均分数列张量下,起始均为闭合区间
t2 = torch.linspace(2,10,5)
t3 = torch.linspace(2,10,6)
print(t2)
print(t3)
此外,PyTorch中也提供了基于对数均分创建张量的方法,即“torch_logspace()”,其最大的不同就是添加了对数函数的底作为“base”参数。
另外我们还可以使用“torch.eye()”创建对角矩阵(2维张量),注意所得结果默认为方针,因此一般只提供行/列参数即可。
05 | Tensor创建:依据概率分布创建张量
上面我们介绍了张量的直接创建、从数值创建两类方法,还有一类方法十分常用,如初始化权值参数的时候往往选择某种概率分布。
我们可以使用“torch.normal()”建立符合正态分布/高斯分布的张量,对于正态分布而言,只需要指定均值和标准差即可。需要注意的是,由于均值和标准差分别可以选择标量和张量(一维数组),因而实际中共有四种组合。
这里需要注意的是,当mean与std均为标量时,需要指定size以确定张量大小,且张量中每个元素都是从同一个正态分布中采样所得;当mean或std为张量时,则采用一一对应的原则,即mean第一个元素与std第一个元素确定第一个正态分布,并采样获得目标张量的第一个元素数值,以此类推。
# **************************** example 5 ****************************
# 通过torch.normal()创建符合正态分布张量
flag = True
if flag:
# mean张量,std张量
mean = torch.arange(1, 5, dtype=torch.float)
std = torch.arange(1, 5, dtype=torch.float)
t_normal = torch.normal(mean, std)
print("mean:{}\nstd:{}".format(mean, std))
print(t_normal)
# mean标量,std标量
# 由于mean与std都是标量,无法确定张量大小,故需指定size=()
# 分别从指定正态分布中采样4次和采样10次创建张量
t_normal_0 = torch.normal(1, 0, size=(4,))
t_normal_1 = torch.normal(1, 1, size=(10,))
print("mean标量,std标量:", t_normal_0)
print("mean标量,std标量:", t_normal_1)
# mean张量,std标量
# 此时所得张量的元素来自与张量mean与标量std的组合所得的4个正态分布采样
mean = torch.arange(1, 5, dtype=torch.float)
std = 1
t_normal_0 = torch.normal(mean, std)
t_normal_1 = torch.normal(mean, std)
print("第1次采样:", t_normal_0)
print("第2次采样:", t_normal_1)
运行结果如下:
此外,我们还可以使用“torch.randn()”生成标准分布采样的张量,或采用“torch.rand()”在(0,1]上生成均匀分布。