pytorch学习笔记-张量(Tensor)操作

张量是深度学习中必不可少的内容,虽然十分基础但是在整个深度学习中每个地方都会涉及到,下面从创建、运算、广播以及转换等方面记录一下学习心得,学习过程中参考了动手学深度学习(pytorch版本)和pytorch官方文档。
导入包:import torch

创建Tensor

torch.empty(5, 3) #创建一个5*3的张量,并不进行初始化
torch.rand(5, 3) #创建一个5*3的张量,并随机初始化
torch.zeros(5, 3, dtype = torch.long) #创建一个5*3的全零张量,指定数据类型为long
torch.tensor([[1, 2, 3], [4, 5, 6]]) #创建自定义张量
x = x.new_ones(5, 3)  # 返回的tensor默认具有与x相同的torch.dtype和torch.devic
x = torch.randn_like(x, dtype=torch.float) # 指定新的数据类型

还有一些常见的创建张量的操作如下表

命令

含义

torch.Tensor(*sizes)

基础的Tensor构造函数

tensor(data,)

将数据转换成tensor数据类型

torch.ones(*size)

全1张量

torch.zeros(*size)

全0张量

torch.eye(*size)

对角线为1,其他为0

torch.arange(s,e,step)

范围步长

torch.linspace(s,e,steps)

从s到e,均匀切分成steps份

torch.rand/randn(*sizes)

均匀标准分布

torch.normal(mean,std)/uniform(from,to)

正太均匀分布

torch.randperm(m)

随机排列

Note:也可以通过torch.cuda.Tensor(*size)将数据放在GPU上

张量操作

加法操作

# 形式一
y = torch.rand(5, 3)
print(x + y)

# 形式二
torch.add(x, y)
result = torch.empty(5, 3)
torch.add(x, y, out=result)

# 形式三,加到y上面
y.add_(x)

Note:后缀_表示inplace,例如x.copy_(y), x.t_()表示在原地操作并替代

索引切片

我们还可以使用类似NumPy的索引操作来访问Tensor的一部分,需要注意的是:这里索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改。

y = x[0, :]
y += 1 # x也会被跟着修改

除了常用的索引选择数据之外,PyTorch还提供了一些高级的选择函数如下表:

函数

操作

index_select(input, dim, index)

在指定维度dim上选取,比如选取某些行、某些列

masked_select(input, mask)

例子如上,a[a>0],使用ByteTensor进行选取

nonzero(input)

非0元素的下标

gather(input, dim, index)

根据index,在dim维度上选取数据,输出的size与index一样

改变形状

# 用view()来改变Tensor的形状,仅仅是改变观看的视角,其内存地址不会发生改变,因此对改变后的张量进行操作,原张量也会改变
y = x.view(15)
z = x.view(-1, 5)

所以如果我们想返回一个真正新的副本(即不共享data内存)该怎么办呢?Pytorch还提供了一个reshape()可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先用clone创造一个副本然后再使用view

# 这里x与x_cp相互之间是独立的
x_cp = x.clone().view(15)
x -= 1

Note:使用clone还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源Tensor

pytorch中另外一个常用的函数就是item(), 它可以将一个标量Tensor转换成一个Python number

# 返回的是一个数,将一个标量转成一个数
x = torch.randn(1)
print(x)
print(x.item())

下表中提供了一些代数运算

函数

功能

torch.trace

对角线元素之和(矩阵的迹)

torch.diag

对角线元素

torch.triu/tril

矩阵的上三角/下三角,可指定偏移量

torch.mm/bmm

矩阵乘法,batch的矩阵乘法

torch.addmm/addbmm/addmv/addr/baddbmm..

矩阵运算

torch.t

转置

torch.dot/cross

内积/外积

torch.inverse

求逆矩阵

torch.svd

奇异值分解

Note:其他运算可以参考官方文档

广播机制

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)
###
tensor([[2, 3],
        [3, 4],
        [4, 5]])
###

由于x和y分别是1行2列和3行1列的矩阵,如果要计算x + y,那么x中第一行的2个元素被广播(复制)到了第二行和第三行,而y中第一列的3个元素被广播(复制)到了第二列。如此,就可以对2个3行2列的矩阵按元素相加。

运算的内存开销

索引操作是不会开辟新内存的,而像y = x + y这样的运算是会新开内存的,然后将y指向新内存。为了演示这一点,我们可以使用Python自带的id函数:如果两个实例的ID一致,那么它们所对应的内存地址相同;反之则不同。

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y = y + x #开辟新的内存
print(id(y) == id_before) # False 

y[:] = y + x #不开辟新的内存
print(id(y) == id_before) # True

torch.add(x, y, out=y) # y += x, y.add_(x) 都不开辟新的内存空间
print(id(y) == id_before) # True

tensor与numpy转换

tensor转numpy

a = torch.ones(5)
b = a.numpy()
print(a, b)

a += 1
print(a, b)
b += 1
print(a, b)

###
tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.]
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.]
tensor([3., 3., 3., 3., 3.]) [3. 3. 3. 3. 3.]
###

numpy转tensor

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
print(a, b)

a += 1
print(a, b)
b += 1
print(a, b)

###
[1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
[2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
[3. 3. 3. 3. 3.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
###

如果直接将numpy作为参数传入torch.tensor()实现转换,则新的张量与原来的数组不共享内存,会开辟新的内存空间。

c = torch.tensor(a)
a += 1
print(a, c)

## [4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)

torch.tensor()实现转换,则新的张量与原来的数组不共享内存,会开辟新的内存空间。

c = torch.tensor(a)
a += 1
print(a, c)

## [4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)