在PyTorch中,torch.Tensor是存储和变换数据的主要工具。如果之前用过NumPy,会发现Tensor和NumPy的多维数组非常类似。然而,Tensor提供GPU计算和自动求梯度等更多功能,这些使Tensor更加适合深度学习
“tensor”,张量可以看作是一个多维数组。标量可以看作是0维张量,向量可以看作1维张量,矩阵可以看作是二维张量。
创建Tensor
import torch#创建一个5*3的未初始化的Tensorx = torch.empty(5,3)print(x)tensor([[1.7753e+28, 7.4389e+34, 1.8040e+28],[3.3840e-12, 7.5555e+31, 1.4605e-19],[2.7517e+12, 7.5338e+28, 7.6286e-19],[3.0313e+32, 9.1042e-12, 6.2609e+22],[4.7428e+30, 3.5833e-14, 0.0000e+00]])#创建一个5*3的随机初始化的Tensorx = torch.rand(5,3)print(x)tensor([[0.9999, 0.4164, 0.5664],[0.0104, 0.3030, 0.2805],[0.7643, 0.3122, 0.3903],[0.9240, 0.0910, 0.8578],[0.3053, 0.0977, 0.9361]])#创建一个5x3的long型全0的Tensorx = 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]])#还可以直接根据数据创建x = torch.tensor([5.5,3])print(x)tensor([5.5000, 3.0000])#还可以通过现有的Tensor来创建,此方法会默认重用输入Tensor的一些属性,例如数据类型,除非自定义数据类型x = torch.tensor([5.5,3])x = x.new_ones(5, 3, dtype=torch.float64) # 返回的tensor默认具有相同的torch.dtype和torch.deviceprint(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.6035, 0.8110, -0.0451],[ 0.8797, 1.0482, -0.0445],[-0.7229, 2.8663, -0.5655],[ 0.1604, -0.0254, 1.0739],[ 2.2628, -0.9175, -0.2251]])#可以通过shape或者size()来获取Tensor的形状print(x.size())print(x.shape)torch.Size([5, 3])torch.Size([5, 3]) #返回的torch.Size其实就是一个tuple, 支持所有tuple的操作
Tensor基本操作
加法
y = torch.rand(5, 3)print(x + y)print(torch.add(x, y))#指定输出result = torch.empty(5, 3)torch.add(x, y, out=result)print(result)#inplace——PyTorch操作inplace版本都有后缀_, 例如x.copy_(y), x.t_()# adds x to yy.add_(x)print(y)tensor([[ 1.3967, 1.0892, 0.4369],[ 1.6995, 2.0453, 0.6539],[-0.1553, 3.7016, -0.3599],[ 0.7536, 0.0870, 1.2274],[ 2.5046, -0.1913, 0.4760]])
索引
可以使用类似NumPy的索引操作来访问Tensor的一部分,需要注意的是:索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改
y = x[0, :]y += 1print(y)print(x[0, :]) # 源tensor也被改了tensor([1.6035, 1.8110, 0.9549])tensor([1.6035, 1.8110, 0.9549])
改变形状
y = x.view(15)z = x.view(-1, 5) # -1所指的维度可以根据其他维度的值推出来print(x.size(), y.size(), z.size())torch.Size([5, 3]) torch.Size([15]) torch.Size([3, 5])#注意view()返回的新Tensor与源Tensor虽然可能有不同的size,但是是共享data的,也即更改其中的一个,另外一个也会跟着改变。view仅仅是改变了对这个张量的观察角度,内部数据并未改变x += 1print(x)print(y) # 也加了1tensor([[1.6035, 1.8110, 0.9549],[1.8797, 2.0482, 0.9555],[0.2771, 3.8663, 0.4345],[1.1604, 0.9746, 2.0739],[3.2628, 0.0825, 0.7749]])tensor([1.6035, 1.8110, 0.9549, 1.8797, 2.0482, 0.9555, 0.2771, 3.8663, 0.4345,1.1604, 0.9746, 2.0739, 3.2628, 0.0825, 0.7749])#如果我们想返回一个真正新的副本(即不共享data内存)该怎么办呢?Pytorch还提供了一个reshape()可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。先用clone创造一个副本然后再使用viewx_cp = x.clone().view(15)x -= 1print(x)print(x_cp)tensor([[ 0.6035, 0.8110, -0.0451],[ 0.8797, 1.0482, -0.0445],[-0.7229, 2.8663, -0.5655],[ 0.1604, -0.0254, 1.0739],[ 2.2628, -0.9175, -0.2251]])tensor([1.6035, 1.8110, 0.9549, 1.8797, 2.0482, 0.9555, 0.2771, 3.8663, 0.4345,1.1604, 0.9746, 2.0739, 3.2628, 0.0825, 0.7749])#使用clone还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源Tensor#item()它可以将一个标量Tensor转换成一个Python numberx = torch.randn(1)print(x)print(x.item())tensor([2.3466])2.3466382026672363
广播机制
当对两个形状不同的Tensor按元素运算时,可能会触发广播机制:先适当复制元素使这两个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和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 + xprint(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 + xprint(id(y) == id_before) # 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
注:虽然view返回的Tensor与源Tensor是共享data的,但是依然是一个新的Tensor(因为Tensor除了包含data外还有一些其他属性),二者id(内存地址)并不一致
Tensor和Numpy的相互转换
我们很容易用numpy()和from numpy()将Tensor和NumPy中的数组相互转换。但是需要注意的一点是: 这两个函数所产生的的Tensor和NumPy中的数组共享相同的内存(所以他们之间的转换很快),改变其中一个时另一个也会改变!
还有一个常用的将NumPy中的array转换成Tensor的方法就是torch.tensor(), 此方法总是会进行数据拷贝(就会消耗更多的时间和空间),所以返回的Tensor和原来的数据不再共享内存
Tensor转Numpy
a = torch.ones(5)b = a.numpy()print(a, b)a += 1print(a, b)b += 1print(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 npa = np.ones(5)b = torch.from_numpy(a)print(a, b)a += 1print(a, b)b += 1print(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
c = torch.tensor(a)a += 1print(a, c)[4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
Tensor on GPU
# 以下代码只有在PyTorch GPU版本上才会执行if torch.cuda.is_available():device = torch.device("cuda") # GPUy = torch.ones_like(x, device=device) # 直接创建一个在GPU上的Tensorx = x.to(device) # 等价于 .to("cuda")z = x + yprint(z)print(z.to("cpu", torch.double)) # to()还可以同时更改数据类型
