目录
Tensor是PyTorch中心的数据抽象,本文深度详细介绍PyTorch的torch.Tensor类。包括创建Tensor的方法,Tensor的shape属性,tensor的数据类型和数学运算,tensor的广播机制,克隆tensor,改变tensor的维数,将tensor转换维Numpy的ndarray以及将ndarray转为为tensor。
创建Tensor
import torch
import math
x = torch.empty(3, 4)
print(type(x))
print(x)
-
使用torch模块的工厂函数torch.empty()创建一个tensor,该tensor是2维,有3行4列。返回的对象类型是torch.Tensor,是torch.FloatTensor的别名。默认情况下PyTorch tensor是32bit的浮点数。你可能会看到打印的tensor是像随机数的值,这是因为torch.empty为tensor调用内存分配,但是并没有初始化其值,你看到的是在分配内存时的值。
-
1维的tensor叫做向量vector,2维的tensor叫做矩阵matrix,高于两维一般称其为tensor
更多情况下,你想用一些值来初始化tensor,通常情况下将初始化为全0,全1或者随机值。torch模块为这些初始化提供了工厂函数:
zeros = torch.zeros(2, 3)
print(zeros)
ones = torch.ones(2, 3)
print(ones)
torch.manual_seed(1729)
random = torch.rand(2, 3)
print(random)
torch.manual_seed()用于手动设置随机数生成器的种子,保证使用torch模块随机数工厂函数torch.rand()的代码的可再现性
torch.manual_seed(1729)
random1 = torch.rand(2, 3)
print(random1)
random2 = torch.rand(2, 3)
print(random2)
torch.manual_seed(1729)
random3 = torch.rand(2, 3)
print(random3)
random4 = torch.rand(2, 3)
print(random4)
输出:
tensor([[0.3126, 0.3791, 0.3087], [0.0736, 0.4216, 0.0691]]) tensor([[0.2332, 0.4047, 0.2162], [0.9927, 0.4128, 0.5938]]) tensor([[0.3126, 0.3791, 0.3087], [0.0736, 0.4216, 0.0691]]) tensor([[0.2332, 0.4047, 0.2162], [0.9927, 0.4128, 0.5938]])
最后一种创建tensor的方法:
some_constants = torch.tensor([[3.1415926, 2.71828], [1.61803, 0.0072897]])
print(some_constants)
some_integers = torch.tensor((2, 3, 5, 7, 11, 13, 17, 19))
print(some_integers)
more_integers = torch.tensor(((2, 4, 6), [3, 6, 9]))
print(more_integers)
Tensor的shape
我们经常对两个或者多个tensor执行操作,这些tensor需要是相同的shape,也就是有相同的维数,并且在各个维度上的cells数目相同,我们可以使用torch.*_liek()方法,比如torch.empty_like(),torch.zeros_like(), torch.ones_like(),toch.rand_like()
x = torch.empty(2, 2, 3)
print(x.shape)
print(x)
empty_like_x = torch.empty_like(x)
print(empty_like_x.shape)
print(empty_like_x)
zeros_like_x = torch.zeros_like(x)
print(zeros_like_x.shape)
print(zeros_like_x)
ones_like_x = torch.ones_like(x)
print(ones_like_x.shape)
print(ones_like_x)
rand_like_x = torch.rand_like(x)
print(rand_like_x.shape)
print(rand_like_x)
tensor数据类型
通过在torch的工厂函数输入参数中指定dtype的值,给创建的tensor指定数据类型,默认的数据类型是torch.float(32位浮点),常见的数据类型如下 Available data types include:
torch.bool
torch.int8
torch.uint8
torch.int16
torch.int32
torch.int64
torch.half
torch.float
torch.double
torch.bfloat
PyTorch tensor的数学和逻辑运算
ones = torch.zeros(2, 2) + 1
twos = torch.ones(2, 2) * 2
threes = (torch.ones(2, 2) * 7 - 1) / 2
fours = twos ** 2
sqrt2s = twos ** 0.5
print(ones)
print(twos)
print(threes)
print(fours)
print(sqrt2s)
输出:
tensor([[1., 1.], [1., 1.]]) tensor([[2., 2.], [2., 2.]]) tensor([[3., 3.], [3., 3.]]) tensor([[4., 4.], [4., 4.]]) tensor([[1.4142, 1.4142], [1.4142, 1.4142]])
Tensor Broadcasting广播
一般情况下进行算术运算的tensor的shape必须相同,但是有一种例外情况是tensor的广播。
rand = torch.rand(2, 4)
doubled = rand * (torch.ones(1, 4) * 2)
print(rand)
print(doubled)
输出:
tensor([[0.6146, 0.5999, 0.5013, 0.9397], [0.8656, 0.5207, 0.6865, 0.3614]]) tensor([[1.2291, 1.1998, 1.0026, 1.8793], [1.7312, 1.0413, 1.3730, 0.7228]])
a = torch.ones(4, 3, 2)
b = a * torch.rand( 3, 2) # 3rd & 2nd dims identical to a, dim 1 absent
print(b)
c = a * torch.rand( 3, 1) # 3rd dim = 1, 2nd dim identical to a
print(c)
d = a * torch.rand( 1, 2) # 3rd dim identical to a, 2nd dim = 1
print(d)
输出:
tensor([[[0.6493, 0.2633], [0.4762, 0.0548], [0.2024, 0.5731]], [[0.6493, 0.2633], [0.4762, 0.0548], [0.2024, 0.5731]], [[0.6493, 0.2633], [0.4762, 0.0548], [0.2024, 0.5731]], [[0.6493, 0.2633], [0.4762, 0.0548], [0.2024, 0.5731]]]) tensor([[[0.7191, 0.7191], [0.4067, 0.4067], [0.7301, 0.7301]], [[0.7191, 0.7191], [0.4067, 0.4067], [0.7301, 0.7301]], [[0.7191, 0.7191], [0.4067, 0.4067], [0.7301, 0.7301]], [[0.7191, 0.7191], [0.4067, 0.4067], [0.7301, 0.7301]]]) tensor([[[0.6276, 0.7357], [0.6276, 0.7357], [0.6276, 0.7357]], [[0.6276, 0.7357], [0.6276, 0.7357], [0.6276, 0.7357]], [[0.6276, 0.7357], [0.6276, 0.7357], [0.6276, 0.7357]], [[0.6276, 0.7357], [0.6276, 0.7357], [0.6276, 0.7357]]])
tensor的大多数二元操作将返回第三个新的tensor,例如,c = a * b,a和b是两个tensor,新的tensor c将占据不同与a和b的内存区域。 有时候,当你做元素操作时希望丢弃中间结果,可以使用单下划线_的函数版本,可就地改变tensor。
a = torch.ones(2, 2)
b = torch.rand(2, 2)
print('Before:')
print(a)
print(b)
print('\nAfter adding:')
print(a.add_(b))
print(a)
print(b)
print('\nAfter multiplying')
print(b.mul_(b))
print(b)
输出:
Before: tensor([[1., 1.], [1., 1.]]) tensor([[0.0905, 0.4485], [0.8740, 0.2526]]) After adding: tensor([[1.0905, 1.4485], [1.8740, 1.2526]]) tensor([[1.0905, 1.4485], [1.8740, 1.2526]]) tensor([[0.0905, 0.4485], [0.8740, 0.2526]]) After multiplying tensor([[0.0082, 0.2012], [0.7638, 0.0638]]) tensor([[0.0082, 0.2012], [0.7638, 0.0638]])
需要注意的是in-place算术函数是torch.Tensor对象的方法,而不是想许多其他函数一样附加在torch模块。
复制tensor
与Python中的任何对象一样,将tensor赋值给变量,会使变量是tensor的标签,而不是拷贝它。
当需要分开拷贝数据时,可以使用clone()方法。
a = torch.ones(2, 2)
b = a.clone()
assert b is not a # 在内存中是不同的对象
print(torch.eq(a, b)) # 但是所存储的内容相同
a[0][1] = 561 # a 改变了
print(b) # 但b仍然是1
在许多情况下,如果你的模型在forward()方法中有多种计算路径。并且原始的tensor和克隆的tensor都对模型的输出有贡献,为了使能模型学习,你希望autograd在这两种tensor上开启。如果原始tensor的autograd开启了,你将得到你所希望的结果。 另一方面,如果在做计算时,原始tensor和克隆的tensor都不需要跟踪梯度,只要原始tensor的autograd没有开启,你将得到你所希望的结果。 第三种情况是,设想你在模型的forward()函数中执行计算,梯度是默认开启的,你想拉一些中间值去生成新的特性,这种情况下,你不想克原始tensor的克隆拷贝跟踪梯度--当autograd的历史跟踪关闭时性能将提高。这种情况下,可以在原始tensor上使用.detech()方法
a = torch.rand(2, 2, requires_grad=True) # 开启autograd
print(a)
b = a.clone()
print(b)
c = a.detach().clone()
print(c)
print(a)
输出:
tensor([[0.6545, 0.4144], [0.0696, 0.4648]], requires_grad=True) tensor([[0.6545, 0.4144], [0.0696, 0.4648]], grad_fn=<CloneBackward0>) tensor([[0.6545, 0.4144], [0.0696, 0.4648]]) tensor([[0.6545, 0.4144], [0.0696, 0.4648]], requires_grad=True)
改变维数
有时候我们需要改变维数,例如传递单个输入实例到模型。Pytorch模型通常期望是batch的输入。假设你的模型处理3x226x226的图像(3个颜色通道,226x226的尺寸),输入的tensor的shape是(3,226,226),但是模型期望的输入是(N,3,226,226),N表示一个batch的图像数目,如果将输入转换为包含一个图像的batch呢?
a = torch.rand(3, 226, 226)
b = a.unsqueeze(0)
print(a.shape)
print(b.shape)
输出:
torch.Size([3, 226, 226]) torch.Size([1, 3, 226, 226])
还可以使用squeeze()将batch转换为non-batch计算
a = torch.rand(1, 20)
print(a.shape)
print(a)
b = a.squeeze(0)
print(b.shape)
print(b)
c = torch.rand(2, 2)
print(c.shape)
d = c.squeeze(0)
print(d.shape)
d = c.squeeze(1)
print(d.shape)
输出:
torch.Size([1, 20]) tensor([[0.3118, 0.9180, 0.7293, 0.5351, 0.5078, 0.8012, 0.5088, 0.3142, 0.8068, 0.6503, 0.4621, 0.6882, 0.7282, 0.9156, 0.4836, 0.1451, 0.7946, 0.4126, 0.1625, 0.9214]]) torch.Size([20]) tensor([0.3118, 0.9180, 0.7293, 0.5351, 0.5078, 0.8012, 0.5088, 0.3142, 0.8068, 0.6503, 0.4621, 0.6882, 0.7282, 0.9156, 0.4836, 0.1451, 0.7946, 0.4126, 0.1625, 0.9214]) torch.Size([2, 2]) torch.Size([2, 2]) torch.Size([2, 2])
可以看到使用squeeze()后,2维的tensor变成了1维的tensor,a的外层比b多一个方括号[]
squeeze()只能是改变宽度为1的维,当在c中尺寸是2的维上使用squeeze()时,并不会有任何改变。
有时候希望彻底改变tensor的形状,但保留其元素和内容不变。 出现这种需求的一种情况是在卷积层和线性层的接口,通常发生在图像分类模型。一个卷积核产生的tensor的形状是特征图的个数宽度高度,但是紧接着的线性层希望一维的输入。reshape()将为你做这一工作。
output3d = torch.rand(6, 20, 20)
print(output3d.shape)
input1d = output3d.reshape(6 * 20 * 20)
print(input1d.shape)
# can also call it as a method on the torch module:
print(torch.reshape(output3d, (6 * 20 * 20,)).shape)
输出:
torch.Size([6, 20, 20]) torch.Size([2400]) torch.Size([2400])
reshape()执行成功以后会返回tensor的一个改变的视图view,但底层的内存区域保持不变。这意味着对原来tensor的任何改变都会反映在这个tensor的视图上,除非使用clone()方法。
和Numpy的转换
当你有已经存在使用Numpy的ndarray存储的数据的机器学习或者科学的代码,你希望将这些数据表示成PyTorch tensor,利用PyTorch的GPU加速或者它用于构建机器学习模型的高效抽象能力。可以很容的在ndarray和PyTorch tensor直接切换。使用torch.from_numpy()可以将numpy ndarray转换为tensor,使用torch.numpy()可以将tensor转换为numpy ndarray。但是tensor和numpy ndarray使用同样的内存,除非使用clone()函数。