从头学pytorch(一):数据操作
跟着Dive-into-DL-PyTorch.pdf从头开始学pytorch,夯实基础.
Tensor创建
创建未初始化的tensor
import torch
x = torch.empty(5,3)
print(x)
输出
tensor([[ 2.0909e+21, 3.0638e-41, -2.4612e-30],
[ 4.5650e-41, 3.0638e-41, 1.7753e+28],
[ 4.4339e+27, 1.3848e-14, 6.8801e+16],
[ 1.8370e+25, 1.4603e-19, 6.8794e+11],
[ 2.7253e+20, 3.0866e+29, 1.5835e-43]])
创建随机初始化的tensor
x = torch.rand(5,3)
print(x)
输出
tensor([[0.7302, 0.0805, 0.9499],
[0.9323, 0.2995, 0.2943],
[0.7428, 0.8312, 0.6465],
[0.7843, 0.7300, 0.7509],
[0.4965, 0.1318, 0.9063]])
创建全0的tensor,指定类型为long
x = torch.zeros(5,3,dtype=torch.long)
输出
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和已有tensor具有相同数据类型,除非手动指定.
print(x.shape,x.dtype)
x = x.new_ones(5, 3, dtype=torch.float64) # 返回的tensor默认具有相同的torch.dtype和torch.device
print(x)
输出
torch.Size([2]) torch.float32
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], dtype=torch.float64)
x=torch.rand_like(x)
print(x)
输出
tensor([[0.0646, 0.4401, 0.3052],
[0.7657, 0.7720, 0.5770],
[0.2951, 0.4179, 0.6013],
[0.2007, 0.0473, 0.7378],
[0.1789, 0.1498, 0.1430]], dtype=torch.float64)
还有很多创建tensor的方法
函数 | 功能 |
---|---|
Tensor(*sizes) | 基础构造函数 |
tensor(data,) | 类似np.array的构造函数 |
ones(*sizes) | 全1Tensor |
zeros(*sizes) | 全0Tensor |
eye(*sizes) | 对角线为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)。
各种操作
- 算数操作
- 索引
- 改变形状
- 线性代数
算数操作
在PyTorch中,同一种操作可能有很多种形式,下面用加法作为例子。
- 加法形式一
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
# adds x to y y.add_(x) print(y)
注:PyTorch操作inplace版本都有后缀"_", 例如
x.copy_(y), x.t_()
索引
我们还可以使用类似NumPy的索引操作来访问Tensor
的一部分,需要注意的是:索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改。
y = x[0, :]
y += 1
print(y)
print(x[0, :]) # 源tensor也被改了
输出:
tensor([1.6035, 1.8110, 0.9549])
tensor([1.6035, 1.8110, 0.9549])
除了常用的索引选择数据之外,PyTorch还提供了一些高级的选择函数:
函数 | 功能 |
---|---|
index_select(input, dim, index) | 在指定维度dim上选取,比如选取某些行、某些列 |
masked_select(input, mask) | 例子如上,a[a>0],使用ByteTensor进行选取 |
non_zero(input) | 非0元素的下标 |
gather(input, dim, index) | 根据index,在dim维度上选取数据,输出的size与index一样 |
用view()
来改变Tensor
的形状:
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共享内存(其实是同一个tensor),也即更改其中的一个,另外一个也会跟着改变。(顾名思义,view仅仅是改变了对这个张量的观察角度)
x += 1
print(x)
print(y) # 也加了1
输出:
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]])
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])
所以如果我们想返回一个真正新的副本(即不共享内存)该怎么办呢?Pytorch还提供了一个reshape()
可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先用clone
创造一个副本然后再使用view
。参考此处
x_cp = x.clone().view(15)
x -= 1
print(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 number:
x = torch.randn(1)
print(x)
print(x.item())
输出:
tensor([2.3466])
2.3466382026672363
线性代数
另外,PyTorch还支持一些线性函数,这里提一下,免得用起来的时候自己造轮子,具体用法参考官方文档。如下表所示:
函数 | 功能 |
---|---|
trace | 对角线元素之和(矩阵的迹) |
diag | 对角线元素 |
triu/tril | 矩阵的上三角/下三角,可指定偏移量 |
mm/bmm | 矩阵乘法,batch的矩阵乘法 |
addmm/addbmm/addmv/addr/badbmm.. | 矩阵运算 |
t | 转置 |
dot/cross | 内积/外积 |
inverse | 求逆矩阵 |
svd | 奇异值分解 |
PyTorch中的Tensor
支持超过一百种操作,包括转置、索引、切片、数学运算、线性代数、随机数等等,可参考官方文档。
2.2.3 广播机制
前面我们看到如何对两个形状相同的Tensor
做按元素运算。当对两个形状不同的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
和y
分别是1行2列和3行1列的矩阵,如果要计算x + y
,那么x
中第一行的2个元素被广播(复制)到了第二行和第三行,而y
中第一列的3个元素被广播(复制)到了第二列。如此,就可以对2个3行2列的矩阵按元素相加。
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) # 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)