2.2 数据操作
tensor
一般可译作"张量",张量可以看作是一个多维数组,标量可以看作0维张量,向量可以看作1维张量,矩阵可以看作是二维张量。
2.2.1 创建Tensor
导入PyTorch
import torch
创建5×3
的未初始化的Tensor
:
x = torch.empty(5,3)
print(x)
tensor([[-5.9280e-17, 1.0328e-42, -5.9279e-17],
[ 1.0328e-42, -5.9299e-17, 1.0328e-42],
[-5.9283e-17, 1.0328e-42, -5.9304e-17],
[ 1.0328e-42, -5.9293e-17, 1.0328e-42],
[-5.9293e-17, 1.0328e-42, -5.9287e-17]])
创建一个5×3
的随机初始化的Tensor
x = torch.rand(5,3)
print(x)
tensor([[0.9341, 0.5139, 0.7338],
[0.8216, 0.9009, 0.5679],
[0.7110, 0.5658, 0.8897],
[0.5174, 0.1536, 0.6354],
[0.6097, 0.7780, 0.3942]])
创建一个5×3
的long型全0的Tensor
x = torch.zeros(5,3,dtype = torch.long)
print(x)
tensor([[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
根据数据直接创建Tensor
:
x = torch.tensor([5.5,3])
print(x)
tensor([5.5000, 3.0000])
通过现有的Tensor
来创建,此方法会默认重用输入Tensor
的一些属性,例如数据类型,除非自定义数据类型。
x = x.new_ones(5,3,dtype = torch.float64) #返回的tensor默认具有相同的torch.dtype和torch.device
print(x)
x = torch.randn_like(x,dtype = torch.float) #指定新的数据类型
print(x)
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], dtype=torch.float64)
tensor([[-0.1907, -0.1420, -0.6396],
[-0.9567, 0.4238, -0.6407],
[ 0.3809, 1.2904, 2.0543],
[-1.7290, -0.6046, -2.2271],
[ 3.0284, -0.9338, -0.7631]])
通过shape
或者size()
来获取Tensor
的形状:
print(x.size())
print(x.shape)
torch.Size([5, 3])
torch.Size([5, 3])
注:返回的size是一个tuple,支持所有tuple的操作
更多构造Tensor
的函数
函数 | 功能 |
---|---|
Tensor(sizes) | 基础构造函数 |
tensor(data) | 类似np.array的构造函数 |
ones(sizes) | 全1Tensor |
zeros(size) | 全0Tensor |
eye(size) | 对角线为1,其它为0 |
arange(s,e,step) | 从s到e,步长为step |
linspace(s,e,steps) | 从s到e,均匀切分成steps份 |
rand/randn(sizes) | 均匀/标准分布 |
normal(mean,std)/uniform(from,to) | 正态分布/均匀分布 |
randperm(m) | 随机排列 |
这些创建方法都可以在创建的时候指定数据类型dtype和存放device(cpu/gpu)
2.2.2 操作
介绍Tensor
的各种操作
算术操作
在PyTorch中,同一种操作可能有很多种形式,下面用加法作为例子。
#加法形式一
y = torch.rand(5,3)
print(x)
print(x+y)
tensor([[-0.1907, -0.1420, -0.6396],
[-0.9567, 0.4238, -0.6407],
[ 0.3809, 1.2904, 2.0543],
[-1.7290, -0.6046, -2.2271],
[ 3.0284, -0.9338, -0.7631]])
tensor([[ 0.2407, 0.4575, 0.2434],
[-0.7171, 0.7032, 0.2253],
[ 0.8284, 2.2123, 2.3685],
[-0.8832, 0.1701, -1.6400],
[ 3.5063, -0.2672, -0.0373]])
#加法形式二
print(torch.add(x,y))
tensor([[ 0.2407, 0.4575, 0.2434],
[-0.7171, 0.7032, 0.2253],
[ 0.8284, 2.2123, 2.3685],
[-0.8832, 0.1701, -1.6400],
[ 3.5063, -0.2672, -0.0373]])
#指定输出
result = torch.empty(5,3)
print(result)
torch.add(x,y,out=result)
print(result)
tensor([[4.1327e-39, 8.9082e-39, 9.8265e-39],
[9.4592e-39, 1.0561e-38, 1.0929e-38],
[1.0102e-38, 4.5918e-39, 1.0561e-38],
[1.0561e-38, 1.0561e-38, 1.0745e-38],
[1.0561e-38, 8.7245e-39, 9.6429e-39]])
tensor([[ 0.2407, 0.4575, 0.2434],
[-0.7171, 0.7032, 0.2253],
[ 0.8284, 2.2123, 2.3685],
[-0.8832, 0.1701, -1.6400],
[ 3.5063, -0.2672, -0.0373]])
# 加法形式三、inplace
# adds x to y
print(y)
y.add_(x)
print(y)
tensor([[0.4314, 0.5995, 0.8830],
[0.2396, 0.2793, 0.8660],
[0.4475, 0.9219, 0.3143],
[0.8458, 0.7747, 0.5871],
[0.4779, 0.6666, 0.7258]])
tensor([[ 0.2407, 0.4575, 0.2434],
[-0.7171, 0.7032, 0.2253],
[ 0.8284, 2.2123, 2.3685],
[-0.8832, 0.1701, -1.6400],
[ 3.5063, -0.2672, -0.0373]])
索引
使用索引访问Tensor
的一部分
注:索引出来的结果与原数据共享内存,修改一个,另一个也会跟着修改。
y = x[0, : ]
y += 1
print(x)
print(y)
print(x[0,:]) #源tensor也被修改
tensor([[ 2.8093, 2.8580, 2.3604],
[-0.9567, 0.4238, -0.6407],
[ 0.3809, 1.2904, 2.0543],
[-1.7290, -0.6046, -2.2271],
[ 3.0284, -0.9338, -0.7631]])
tensor([2.8093, 2.8580, 2.3604])
tensor([2.8093, 2.8580, 2.3604])
更高级的选择数据函数:
改变形状
用view()
来改变Tensor
的形状
y = x.view(15)
z = x.view(-1,5) # -1所指的维度是x的最后一个维度值
k = x.view(5,-1)
print(x.size(),y.size(),z.size(),k.size())
torch.Size([5, 3]) torch.Size([15]) torch.Size([3, 5]) torch.Size([5, 3])
注: view()
返回的新tensor与源tensor共享内存(其实是同一个tensor),也即更改其中的一个,另一个也会跟着改变。(顾名思义,view仅仅是改变了对张量的观察角度)
x +=1
print(x)
print(y)
tensor([[1.9341, 1.5139, 1.7338],
[1.8216, 1.9009, 1.5679],
[1.7110, 1.5658, 1.8897],
[1.5174, 1.1536, 1.6354],
[1.6097, 1.7780, 1.3942]])
tensor([1.9341, 1.5139, 1.7338, 1.8216, 1.9009, 1.5679, 1.7110, 1.5658, 1.8897,
1.5174, 1.1536, 1.6354, 1.6097, 1.7780, 1.3942])
如果我们想返回一个真正新的副本(即不共享内容)
应当使用reshape()
改变形状
但是此函数并不能保证返回的是其拷贝,所以不推荐使用
推荐使用clone
先创造一个副本然后再使用view
x_cp = x.clone().view(15)
x -= 1
print(x)
print(x_cp)
tensor([[0.9341, 0.5139, 0.7338],
[0.8216, 0.9009, 0.5679],
[0.7110, 0.5658, 0.8897],
[0.5174, 0.1536, 0.6354],
[0.6097, 0.7780, 0.3942]])
tensor([1.9341, 1.5139, 1.7338, 1.8216, 1.9009, 1.5679, 1.7110, 1.5658, 1.8897,
1.5174, 1.1536, 1.6354, 1.6097, 1.7780, 1.3942])
注:使用clone
还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源Tensor
另外一个常用的函数就是item(),它可以将一个标量Tensor
转换成一个Python number:
x = torch.randn(1)
print(x)
print(x.item())
tensor([0.2996])
0.2996445298194885
线性代数
PyTorch 还支持一些线性函数,这里提一下,免得用起来的时候自己造轮子。
2.2.3 广播机制
当两个形状不同的Tensor
按元素运算时,可能会触发广播broadcasting
机制:先适当复制元素使这两个Tensor
形状相同后再按元素运算。例如:
x = torch.arange(1,3).view(1,2)
print(x)
y = torch.arange(1,4).view(3,1)
print(y)
print(x+y)
tensor([[1, 2]])
tensor([[1],
[2],
[3]])
tensor([[2, 3],
[3, 4],
[4, 5]])
x
中第一行的2个元素被广播到了第二行和第三行,而y
中第一列的3个元素被广播到了第二列。
2.2.4 运算的内存开销
索引、view
不会开辟内存,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
的内存
例如:将x+y
的结果通过[:]
写进y
对应的内存中。
x = torch.tensor([1,2])
y = torch.tensor([3,4])
id_before = id(y)
y[:] = y+x
print(id(y) == id_before)
tensor([1, 2])
tensor([3, 4])
True
我们还可以使用运算符全名函数中out
参数或者自加运算符+=
(也即add_())达到上述效果,例如torch.add(x,y,out = y)
和 y += x
(y.add_(x)
)。
x = torch.tensor([1,2])
y = torch.tensor([3,4])
id_before = id(y)
torch.add(x,y,out =y ) # y += x,y.add_(x)
print(id(y) == id_before)
True
2.2.5 Tensor和Numpy相互转换
我们很容易用numpy()
和from_numpy()
将Tensor
和Numpy中的数据相互转换。但是需要注意的一点是:这两个函数所产生的Tensor
和Numpy中的数组共享相同的内存(所以他们之间的转换很快),改变其中一个时另一个也会改变!!!
注:常用将Numpy中的array转换成Tensor
的方法就是torch.tensor()
。此方法总是会进行数据拷贝(消耗更多的时间和空间),所以返回的Tensor
和原来的数据不再共享内存。
Tensor转Numpy
使用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
使用from_numpy
将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)
所有在CPU上的Tensor
(除了CharTensor)都支持与Numpy数组相互转换
直接用torch.tensor()
将Numpy转换成Tensor
,需要注意的是该方法总是会进行数据拷贝,返回的Tensor
和原来的数据不再共享内存
c = torch.tensor(a)
a += 1
print(a,c)
[4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
2.2.6 Tensor ON GPU
用方法to()
可以将Tensor
在CPU和GPU之间相互移动
#以下代码只有在PyTorch GPU版本才会执行
if torch.cuda.is_available():
device = torch.device('cuda') #GPU
y = torch.ones_like(x,device=device) #直接创建一个在GPU上的Tensor
x = x.to(device) #等价于 .to('cuda')
z = x+y
print(z)
print(z.to('cpu',torch.double)) #to()还可以同时更改数据类型
tensor([2, 3], device='cuda:0')
tensor([2., 3.], dtype=torch.float64)