You are on page 1of 8

Tensor基础知识

Tensor是pytorch中一种基本的数据结构,它与numpy中的ndarray非常相似,通过将数
据组织成多维张量的方式来对模型的输入、输出,以及模型参数等进行编码。使用
Tensor表示的输入、输出数据以及模型参数,可以在GPU等硬件上获得计算加速,并且
可以使用pytorch中的Auto-Grad(自动求导)等机制进行模型的训练。

1.Tensor的创建:

创建方式主要有四种:

1.1 直接从数据创建:

data = [[0, 1],[2, 3]]


x = torch.tensor(data, dtype=torch.float32, device='cpu')
x_auto = torch.tensor(data)
# 如果不指定数据类型以及所在设备,pytorch会自动推断数据类型,并且默认使用cpu

1.2 从Numpy创建:

array = np.array(data)
x = torch.from_numpy(array)
x_numpy = x.numpy() # 使用.numpy()操作将tensor转化为numpy的ndarray格式

1.3 从其他tensor创建:

x_ones = torch.ones_like(x)
x_zeros = torch.zeros_like(x)
x_rand = torch.rand_like(x, dtype=torch.float64)
#可以修改tensor的数据类型,如果不显式指定则保留原先的数据类型

1.4 创建特殊类型的tensor:

ones = torch.ones(2, 3) #2行3列的全1矩阵


zeros = torch.zeros(3, 4, dtype=torch.float32, device='cpu')
eye = torch.eye(3) # eye是单位矩阵(可以不是方阵)
rand = torch.rand(2, 3) # 元素来自(0,1)的均匀分布
# 使用torch.randn函数生成的张量元素来自标准正态分布(均值为0,标准差为1)

2.Tensor的属性:

tensor主要具有的属性包括数据类型(dtype)、所在设备(device)、形状(shape)
等,根据一定的原则可以对这些属性进行修改

2.1 修改形状:

修改tensor的形状可以通过调用tensor.view()或者tensor.reshape()来实现,注意
形状改变不可以改变数据量(size,也就是所有维数的乘积和不能变,这个要求很
正常)。

对比:
tensor_view = tensor.view(1, 12) #view要求数据在内存中是连续储存的,性能更高
tensor_reshape = tensor.reshape(4, 3) #reshape不要求数据在内存中是连续储存
的,更加灵活

2.2 增删、复制某个/些维度:

如果想要增删、复制某个维度,可以使用 tensor.squeeze() 、
tensor.unsqueeze() 、 tensor.repeat() 等函数。

tensor = torch.randn(1, 3, 4)
tensor_squeeze = tensor.squeeze(0) #将第零维压缩没有 结果维度是(3,4)
tensor_unsqueeze = tensor.unsqueeze(1) #在指定维度上增加一个维度(初始为1)结
果是(1,1,3,4)
#其中-1代表最后一维,比如tensor.unsqueeze(-1)就是在最后一维上增加一个维度,变成
(1, 3, 4, 1)
#而tensor.unsqueeze(0)就是在第0维上增加一个维度,变成(1, 1, 3, 4) PS:第一个1是
新增的
tensor_repeat = tensor.repeat(2, 1, 2) #2,1,2是指在0、1、2维上分别重复2,1,2次,
变成(2,3,8)

2.3 修改数据类型:

可以调用 tensor.float() , tensor.double() , tensor.long() 等命令,或者使用


torch.type() 并传入目标数据类型来实现。

tensor_float = tensor.float()
tensor_double = tensor.double()
tensor_int32 = tensor.type(torch.int32)

2.4 tensor修改所在的设备

修改tensor所在的设备可以通过调用 tensor.to() 并传入设备名称来实现,或者直接


使用 tensor.cuda() / tensor.cpu() 来将tensor在CPU和GPU之间搬运。

if torch.cuda.is_available(): #cuda.is_availabel()用于判断当前环境是否支持GPU
tensor_gpu0 = tensor.to('cuda:0') #将tensor放到编号为0的GPU上
tensor_gpu = tensor.cuda() #将tensor放到第一块可以使用的GPU上(无法指定编
号)
tensor_cpu = tensor.cpu() #把tensor转移到到cpu上

PS:和直接用 cuda() 函数相比,使用 to() 函数能更明确的转移tensor的设备,而且还


可以结合 torch.device() 函数使用: device = torch.device('cuda:0' if
torch.cuda.is_available else 'cpu') tensor=tensor.to(device)

3.Tensor的运算:

Pytorch为Tensor实现了丰富的运算操作,包括索引、切片、合并、转置、数学运算等
操作,很多操作与Numpy中对ndarray的操作非常相似,这些操作都可以在GPU上运行以
获得运算加速。

3.1 索引和切片:

可以对tensor的每一维度索引若干长度s 1, ,得到形状为(s
s2, s3, . . . 1, 的一个新
s2, s3, . . . )

tensor。

tensor = torch.randn(3, 4, 5).float()


print(tensor[1:3, 2:3, 0:2].shape) # torch.Size([2, 1, 2])
print(tensor[1:3, 2, 0:2].shape) # torch.Size([2, 2])

print(tensor[:, :3, :4].shape) # torch.Size([3, 3, 4])


如果对某个维度的索引为某个指定的位置(明确是一个数而不是一次切片结果是1的切
片),那么得到的新tensor将失去该维度。

3.2 合并:

使用 torch.cat() 或 torch.stack() 对一组tensor进行合并。对于 torch.cat 来说,要求


被合并的一组tensor除了在被合并的维度上以外,具有相同的大小。对于 torch.stack
来说,要求被合并的一组tensor在所有维度上都具有相同的大小。
PS: cat 之后指定维度的长度是被合并的两个张量长度之和, stack 相当于是现在两个
张量要合并的指定维度上先new一个维度(为1),然后再合并

x = torch.randn(4, 3)
y = torch.randn(4, 3)
stack_xy0 = torch.stack([x, y], dim=0) #shape是2,4,3
stack_xy1 = torch.stack([x, y], dim=1) #shape是4,2,3
stack_xy2 = torch.stack([x,y],dim=2) #shape是4,3,2

stack 函数还能够对暂时没有的维度(比如上面的第2维)进行合并(原理就是所说的先
新建一个维度,再合并),因此最多也只能针对现有最后一维的后一维

3.3 转置:

使用 tensor.transpose() 调整两个维度的顺序,使用 tensor.permute() 对多个维度的顺


序进行调整。

x = torch.randn(2,3,4,5)
x_transpose = x.transpose(0, 2) # 输入要被交换的两个维度,第0维和第2维交换
# shape是(4,3,2,5)
x_permute = x.permute(1, 0, 2, 3) # 输入期待的维度顺序,按照指定顺序排列
# shape是(3,2,4,5)

3.4 数学运算

3.4.1(四则运算):

tensor可以进行最基本的加法、减法、按位乘法、矩阵乘法等数学运算,参与运算的两
个tensor需要满足一定的形状要求。(可能会涉及到广播法则)
a = torch.randn(5,2,3)
b = torch.randn(5,3,2)
i = torch.matmul(a,b) #得到的结果是一个(5,2,2)的张量
# 矩阵乘法可以批量计算,如果两个tensor维度数量相同,除了最后两维以外其他维度长度
相等,并且最后两维满足矩阵乘法的要求,那么可以直接在最后两维进行矩阵乘法。
#此时a和b的第一维5可以看成批量运算的批数

3.4.2(单点运算):

tensor可以进行求绝对值、对数、指数、平方、开方、三角函数、反三角函数、截断等
丰富的数学运算。

tensor = torch.rand(2, 3)
tensor_abs = tensor.abs() #取绝对值
tensor_abs = torch.abs(tensor)
# 很多单点运算都有torch.xxx(tensor)和tensor.xxx()两种用法,二者功能完全一样。其
中tensor是创建了的tensor类的实例
tensor_log = torch.log(tensor) #自然对数底
tensor_log10 = torch.log10(tensor) #以10为底
tensor_exp = torch.exp(tensor)
tensor_square = torch.square(tensor)
tensor_sqrt = torch.sqrt(tensor)
tensor_sin = torch.sin(tensor)
tensor_atan = torch.atan(tensor) #反正切
tensor_clamp = torch.clamp(tensor, min=0.2, max=0.8)
#截断函数,将tensor中的元素限制在[min, max]之间

其中的截断函数很有用,需要注意

3.4.3(统计量运算):

tensor的统计量包括最大、最小值(以及出现的位置),总和,平均值、中位数、众
数、方差、标准差等。

tensor_sum = tensor.sum()
tensor_mean = tensor.mean()
tensor_mean = torch.mean(tensor) #均值操作
# 和单点操作类似,统计量计算操作通常也支持tensor.xxx()和torch.xxx(tensor)两种写
法。
tensor_mean = tensor.mean(0, keepdim=True)
# 对于沿某个维度的统计量计算而言,可以使用keepdim参数来控制返回值是否在该维度上补
充一个长度为1的维度,来保持输入和输出的维度数量相同。
tensor_median = torch.median(tensor, dim=0, keepdim=False) #求中位数操作
tensor_var = torch.var(tensor, unbiased=True)
# 可以使用unbiased参数控制计算无偏或有偏的方差。
tensor_std = tensor.std() #计算标准差

找最大(最小)值:

tensor = torch.rand(2, 3)
tensor_max = torch.max(tensor) # 默认min、max操作只会返回最小、最大值
print(f'The maximum of tensor: {tensor_max}')
tensor_min, tensor_argmin = torch.min(tensor, dim=1)
# 如果指定维度,那么min、max操作除了返回最小、最大值以外还会返回最小、最大值的位
置。

PS:指定维度使用min和max操作可以获得索引位置:

* , tensor_argmin = torch.min(tensor , dim=1)


min_elements = tensor[tensor_argmin , torch.arange(tensor.size(1))]
# 指定了维度之后,返回的argmin是每列最小值所在的索引位置
# 所以可以使用[tensor_argmin , torch_arange(tensor.size(1))]得到所有列的最小值

3.4.4(比较运算):

tensor的常用比较运算包括了比较大小、示性函数、排序等

a = torch.randn(2, 3)
b = torch.randn(2, 3)
c = (a > b) # 直接使用>、<、>=、<=、==会得到按位比较的bool型结果。
d = torch.maximum(a, b)
e = torch.minimum(a, b)
# 使用minimum或maximum会得到两个tensor每个位置的较小值或较大值。
f = torch.isnan(a) #判断是否是NaN值
g = torch.isinf(a) #判断是否是inf (包括正无穷和负无穷)
h = torch.isfinite(a) #判断是否是有限数(不是inf也不是NaN)
# 示性函数返回tensor的每个位置是否为某种特殊类型,比如NaN、Inf等,返回值为bool型
tensor。
i = torch.sort(a, dim=0)
j = torch.argsort(a, dim=1, descending=True)
# 排序操作可以通过descending参数控制按从大到小或从小到大排序。
k = torch.topk(a, k=2, dim=1, largest=False, sorted=True)
# topk操作可以用largest参数控制返回最大的topk或最小的topk,sorted参数控制返回值
是否按大小顺序排序。k决定了返回多少个值

3.4.5 其他常用操作:

复制(clone)、累加(cunsum)、累乘(cumprod)、爱因斯坦求和(einsum)、摊
平(flatten、ravel)、范数计算(norm)等
torch.einsum可以实现一对tensor的特定操作, 对于两个tensor: X和Y, 它们在某些维度
上相同, 那么可以使用eisum简单地将这些相同的维度进行缩并
比如X: (i, m, j, n), Y: (m, o, p, n), 可以进行如下操作对m, n两个维度进行缩并:

Zi,j,o,p = ∑
m

n
.
Xi,j,m,n ∗ Ym,o,p,n

x = torch.randn(2, 3)
x_copy = x.clone() #复制一个tensor
x_cumsum = torch.cumsum(x, dim=1) # 累加,直接加到下面一个元素
# x = tensor([[-0.0605, -0.2938, 1.3624], [-0.7537, -0.3233, 1.3853]])
# x_cumsum = tensor([[-0.0605, -0.3543, 1.0082], [-0.7537, -1.0770,
0.3083]])
x_cumprod = torch.cumprod(x, dim=1) #累乘
# dim=1表示沿着列进行累加或累乘

X = torch.randn(2, 3, 4, 5)
Y = torch.randn(3, 6, 7, 5)
Z = torch.einsum('imjn,mopn->ijop', X, Y) # 指定合并方式(Einstein求和)
print(Z.shape) # 结果是(2,4,6,7)
Z = Z.flatten(start_dim=1, end_dim=2) # flatten操作可以对某段连续的维度进行摊平
Z.shape是(2,24,7)
Z = Z.ravel() #ravel操作可以直接将tensor摊平成一维 Z.shape是(336) --> 一维张量

4.Tensor自动求导机制:

Pytorch中的tensor和Numpy的ndarray最大的区别就在于,使用tensor可以实现自动的
导数计算。神经网络的本质是一个可以优化的函数,而在诸多优化方法中,最常见的一
种就是基于梯度的优化。对于简单的函数形式,导数可以很容易地用解析式表示出来,
然而神经网络作为一种高度复杂的函数,通常包含上千万个甚至上亿个参数,并且包含
大量复杂的非线性操作,想要手动求得每个参数的导数根本不可能。Pytorch则可以帮助
我们自动地对网络中所有的参数进行导数计算。
例子:

w1 = torch.randn(2, 3, requires_grad=True).float()
x = torch.tensor([[1.0],[2.0],[3.0]]).float()
b1 = torch.randn(2, 1, requires_grad=True).float()
y = torch.matmul(w1, x) + b1
w2 = torch.randn(1, 2, requires_grad=True).float()
z = torch.matmul(w2, y)
loss = z.mean()
loss.backward()
print(w1.grad) # 比如某一次结果是tensor([[ 0.0139, 0.0279, 0.0418], [-1.0121,
-2.0242, -3.0363]])
# print(y.grad)是无效的,原因见下文

我们可以成功获得所有参数(w1,b1,w2)的导数,但当我们访问中间计算结果y的导
数时,输出的结果为None,并且显示了一段warning信息。仔细阅读warning信息可以发
现,Pytorch默认只会保留“叶子节点”的导数,而y是一个“非叶子节点”。什么是叶子节
点,什么是非叶子节点呢?在神经网络的计算中,会有很多类似于y这样的中间结果,它
们是通过对其他tensor进行计算或操作得到的,这样的tensor被称为非叶子节点。而
w1,b1,w2这些参数都是直接创建得到的tensor,这些tensor则称为叶子节点。为了
减少存储量,Pytorch只会默认保留叶子节点的梯度信息。
如果一定要获得非叶子节点的导数,那么则需要在backward操作之前,对非叶子节点执
行retain_grad操作,即进行 y.retain_grad() 操作后,就可以正常获取y的导数了。

PS:如果直接修改一个叶子节点的值,则可能会直接获得报错信息(取决于Pytorch的
版本,也可能会不显示报错信息而将叶子节点变为非叶子节点)。一般不希望后面再更
改叶子节点的值

You might also like