PyTorch【2】-Tensor 与基础操作

几乎所有深度学习框架背后的设计核心都是张量与计算图;

Tensor 即张量,在 tf 中也有这个概念,tensor 是 pytorch 和 tf 非常重要的数据结构,可以理解为多维数组,它可以是一个数、一个向量、一个矩阵、多维数组;

Tensor 可以用 GPU 加速;

在 pytorch 中其用法类似于 numpy;

本教程环境 pytorch 1.3以上

创建 Tensor

方式1:直接用 list. np.array 等创建

示例

a = t.Tensor([1, 2])
print(a)            # tensor([1., 2.])
print(a.type())     # torch.FloatTensor
b = t.Tensor([[1,2], [3, 4]])
print(b)
# tensor([[1., 2.],
#         [3., 4.]])

c = t.tensor(3.)
print(c)                # tensor(3.)
print(c.type())         # torch.FloatTensor
d = t.tensor(4)
print(d, d.type())      # tensor(4) torch.LongTensor

Tensor vs tensor

可以看到上面用了两个方法 Tensor 和 tensor,有什么区别呢?

1. 首先, 在 1.3.1 版本中,tensor 已经被废弃,但是还能用

2. torch.Tensor 是 python 的一个类,确切的说它是默认张量类型 torch.FloatTensor 的别名,生成单精度浮点型张量

3. torch.tensor 是 python 的一个函数,它的输入可以是 data、list、ndarray 等,根据输入类型生成对应类型的张量

4. Tensor 不管输入什么,都生成单精度浮点型张量,而 tensor 根据输入生成对应类型的张量,可以是 torch.LongTensor、torch.FloatTensor和torch.DoubleTensor

e = np.array([1, 2], dtype=np.float64)
print(e)
print(t.Tensor(e).type())   # torch.FloatTensor
print(t.tensor(e).type())   # torch.DoubleTensor

也可以用 t.FloatTensor

方式2:用方法创建 Tensor

  • torch.Tensor(size):
  • torch.empty(size):
  • torch.zeros(size)、torch.zeros_like(input):返回跟 input 同 size 的全 0 tensor
  • torch.ones(size)、torch.ones_like(input)
  • torch.arange(start=0, end, step=1):
  • torch.full(size, value):见示例
  • torch.eye(size):单元矩阵
  • torch.linspace(s, e, step)

查看尺寸用 size 或者 shape

注意:torch.Tensor 创建 Tensor 后,不会马上分配空间,只是计算剩余空间是否够用,当使用该 Tensor 时才会分配空间,而其他创建方式会立即分配空间

示例

import torch as t

x = t.Tensor(2, 3)      ### 构建 2x3 矩阵
print(t.ones_like(x))
print(t.full((2, 3), 5))
# tensor([[5., 5., 5.],
#         [5., 5., 5.]])
print(t.arange(5))      # tensor([0, 1, 2, 3, 4])

print(x.size())         # torch.Size([2, 3])    ### 查看尺寸
print(x.size()[0])      # 2 查看行数
print(x.size(0))        # 2 查看行数
print(t.Size([2, 3]))
print(x.shape)          # torch.Size([2, 3])

方式3:随机数创建

  • torch.rand(size):生成 (0,1) 内均匀分布的随机数
  • torch.randn(size):生成标准正态分布 (0,1) 的随机数
  • torch.normal(mean, std, out=None):生成正态分布的随机数,注意 mean 和 std 都是 tensor 格式,mean 默认 0,std 默认 1

更多的随机抽样方法,参见链接:https://pytorch.org/docs/stable/torch.html#random-sampling

基本操作

有很多操作,这里只介绍简单的,具体查看官网

从接口的角度讲,对 Tensor 的操作可分为两类

1. torch.function,调用 torch 方法处理 Tensor

2. tensor.function,调用 tensor 的方法

如,torch.sum(a, b) 和 a.sum(b);

大多数情况下,二者等价,本文不做区分;

item

获取 Tensor 数值

f = t.Tensor([1])
print(f)        # tensor([1.])
print(f.data)   # tensor([1.])
print(f.item()) # 1.0

获取元素个数

h = t.Tensor(2, 3)
print(h.numel())        # 6
print(h.nelement())     # 6

转成 list

g = t.Tensor([2])
print(g.tolist())       # [2.0]

索引

Tensor 的索引和 numpy 几乎一模一样,请自行测试;

注意,索引出来的 Tensor 与 原 Tensor 共享内存

m = t.Tensor(3, 5)
print(m)
# tensor([[1.3130e-32, 0.0000e+00, 1.3130e-32, 0.0000e+00, 1.3131e-32],
#         [0.0000e+00, 1.3131e-32, 0.0000e+00, 1.3131e-32, 0.0000e+00],
#         [1.3131e-32, 0.0000e+00, 1.3131e-32, 0.0000e+00, 1.3131e-32]])
print(m[:, 1])      # tensor([0.0000e+00, 1.3131e-32, 0.0000e+00])  ### 3 row

#### 共享内存
n = m[0]            # 索引,只有一个代表行
n[0] = 10000        ### 改变 n,m 也会变化
print(m)
# tensor([[1.0000e+04, 0.0000e+00, 1.3130e-32, 0.0000e+00, 1.3131e-32],
#         [0.0000e+00, 1.3131e-32, 0.0000e+00, 1.3131e-32, 0.0000e+00],
#         [1.3131e-32, 0.0000e+00, 1.3131e-32, 0.0000e+00, 1.3131e-32]])
m[0][0] = 558555     ### 改变 m,n 也会变化
print(n)             # tensor([5.5856e+05, 0.0000e+00, 1.3130e-32, 0.0000e+00, 1.3131e-32])

也可以是条件索引

print(m>1)          ### 返回 bool 值
print(m[m>1])

注意,条件索引不共享内存

x = m[m>1]
x[0] = 99999        ### 改变 x,m 不会变化
print(m)

选择函数

torch.index_select(input, dim, index, out=None) → Tensor

  • input (Tensor) – the input tensor.

  • dim (python:int) – the dimension in which we index

  • index (LongTensor) – the 1-D tensor containing the indices to index

  • out (Tensor,optional) – the output tensor

x = t.randn(3, 4)
print(x)
# tensor([[ 0.1427,  0.0231, -0.5414, -1.0009],
#         [-0.4664,  0.2647, -0.1228, -1.1068],
#         [-1.1734, -0.6571,  0.7230, -0.6004]])
indices = t.tensor([0, 2])
print(t.index_select(x, 0, indices))
# tensor([[ 0.1427,  0.0231, -0.5414, -1.0009],
#         [-1.1734, -0.6571,  0.7230, -0.6004]])

torch.masked_select(input, mask, out=None) → Tensor

  • input (Tensor) – the input tensor.

  • mask (ByteTensor) – the tensor containing the binary mask to index with

  • out (Tensor,optional) – the output tensor.

类似于条件索引

x = t.randn(3, 4)
print(x)
# tensor([[ 0.3552, -2.3825, -0.8297,  0.3477],
#         [-1.2035,  1.2252,  0.5002,  0.6248],
#         [ 0.1307, -2.0608,  0.1244,  2.0139]])
mask = x.ge(0.5)        ### x > 0.5
print(mask)         ### 相当于条件索引  
# tensor([[False, False, False, False],
#         [False, True, True, True],
#         [False, False, False, True]])
print(t.masked_select(x, mask))
# tensor([ 1.2252,  0.5002,  0.6248,  2.0139])

torch.nonzero(input, *, out=None, as_tuple=False) → LongTensor or tuple of LongTensors

获取非 0 元素

print(t.nonzero(t.tensor([1, 1, 1, 0, 1])))
# tensor([[0],
#         [1],
#         [2],
#         [4]])
print(t.nonzero(t.tensor([1, 1, 1, 0, 1]), as_tuple=True))      # (tensor([0, 1, 2, 4]),)

合并分割

t.cat(seq, dim, out=None):合并,dim 指定维度

dim 为 0 代表按第 0 维度拼接,确切的说是在第 0 维度上拼接,即在行上拼接,1相反

a = t.ones(1, 2)
b = t.zeros(1, 2)
c = t.cat((a, b), 0)
print(c)
# tensor([[1., 1.],
#         [0., 0.]])

t.chunk(tensor, chunks, dim):分割,chunks 代表分割的块数,dim 代表分割的维度

dim 为 0 代表在第 0 个维度上进行分割,也就是横着切,1 相反

e = t.ones(3, 2)
print(t.chunk(e, 2, 1))
# (tensor([[1.],
#         [1.],
#         [1.]]), tensor([[1.],
#         [1.],
#         [1.]]))
print(t.chunk(e, 2, 0))
# (tensor([[1., 1.],
#         [1., 1.]]), tensor([[1., 1.]]))

分割还有一种方法 t.split(tensor, split_size_or_sections, dim),具体自行尝试

尺寸变换

t.reshape(input, shape) 和 tensor.view(shape) 都可以实现尺寸变换,前者是 torch 类的一个方法,后者是 tensor 对象的方法,当然 前者也可以用 tensor 对象的方法;

也就是说 reshape 大于 view;

注意1,二者都不会改变原 tensor 的尺寸,除非把结果赋给一个新的对象;

import torch as t

f = t.arange(10)

### 单纯调用 view
print(f.view(2, 5))     ### view 设置了新的尺寸
# tensor([[0, 1, 2, 3, 4],
#         [5, 6, 7, 8, 9]])
print(f)    # tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])    ### 但是并没有改变 原来的 tensor
# print(t.view(f, (2, 5)))    ### view 不能这么用,它不是 torch 的方法

g = f.view(2, 5)    ### 这样会生成新的 tensor

### reshape,也不会改变本身
print(f.reshape(2, 5))
# tensor([[0, 1, 2, 3, 4],
#         [5, 6, 7, 8, 9]])
print(f)    # tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(t.reshape(f, (2, 5)))     ### reshape 可以作为 torch 的方法
# tensor([[0, 1, 2, 3, 4],
#         [5, 6, 7, 8, 9]])

注意2, reshape 和 view 转换前后的对象共享内存,一个改变,另一个也跟着改变

g = f.view(2, 5)    ### 这样会生成新的 tensor
f[0] = 100
print(f)       # tensor([100,   1,   2,   3,   4,   5,   6,   7,   8,   9])
print(g)       ### 改变 f,g 也变了
# tensor([[100,   1,   2,   3,   4],
#         [  5,   6,   7,   8,   9]])

resize:也是重置尺寸,在老版本中 它会改变 原 Tensor 的尺寸,在新版本(本版本1.3.1) 中经测试不改变原尺寸

a = t.rand(2, 3)
a.resize(3, 2)
print(a)                ### 原 Tensor 尺寸没变
print(a.resize(3, 2))   ### 尺寸变了

squeeze and unsqueeze

squeeze :通过减少维度压缩数据

1. 去掉维数为 1 的维度,从而对数据进行压缩

2. squeeze 的参数是 Tensor 的维度,如果 Tensor 是 N 维,参数就只能是 0-(N-1)

3. 如果 squeeze 的参数是 0, 代表 如果第 0 维的维数为1,则去掉,否则不变化

a = t.rand(1, 2)
print(a)                # tensor([[0.4960, 0.0531]])        ### 2 维
print(a.squeeze(0))     # tensor([0.8795, 0.8799])          ### 第 0 维的维数是 1 [1行],去掉
print(a.squeeze(1))     # tensor([[0.4960, 0.0531]])        ### 第 1 维的维数是 2 [2列],不变
# print(a.squeeze(2))   # 报错,因为没有 第2维

unsqueeze:对数据进行扩充,与 squeeze 作用相反,用法类似

unsqueeze 的参数就是 Tensor 的维度,参数为 0 时,表示在第 0 维上增加一维数

a = t.rand(2, 2)
print(a)
# tensor([[0.6855, 0.2439],
#         [0.0222, 0.7391]])
print(a.unsqueeze(0))   ### 第 0 维上增加一个维数
# tensor([[[0.6855, 0.2439],
#          [0.0222, 0.7391]]])
print(a.unsqueeze(0).shape) # torch.Size([1, 2, 2])     ### 第 0 维加了1
print(a.unsqueeze(1))   ### 第 1 维上增加一个维数
# tensor([[[0.6855, 0.2439]],
#
#         [[0.0222, 0.7391]]])
print(a.unsqueeze(1).shape) # torch.Size([2, 1, 2])     ### 第 1 维加了1

Tensor-Numpy 互转

操作如下

n = t.ones(2, 3)
print(n.numpy())    ### Tensor 转 Numpy
# [[1. 1. 1.]
#  [1. 1. 1.]]

import numpy as np
p = np.ones((2, 3))
q = t.from_numpy(p)      ### Numpy 转 Tensor
print(p)
# tensor([[1., 1., 1.],
#         [1., 1., 1.]], dtype=torch.float64)

Tensor 与 Numpy 共享内存,使得他们之间的转换很快,而且几乎不会消耗资源;

共享内存意味着,一个变了,另一个也跟着变;

q.add_(n)       ### q 被改变
print(q)        ###
# tensor([[2., 2., 2.],
#         [2., 2., 2.]], dtype=torch.float64)
print(p)        ### p 竟然也被改变了
# [[2. 2. 2.]
#  [2. 2. 2.]]

GPU 加速

注意,GPU 的优势体现在大规模数据集和复杂运算上,把数据从内存转移到显存产生额外开销,导致小数据反而不占优势

print(t.cuda.is_available())    # False
if t.cuda.is_available():
    x = x.cuda()        ### Tensor 通过 cuda 方法转换为 GPU 的 Tensor
    y = y.cuda()
    x + y

共享内存

共享内存的操作汇总如下

1. 下标索引   取出的 Tensor 与 原 Tensor

2. reshape、view   生成的新 Tensor 与 原 Tensor

3. Tensor 与 Numpy 互转

参考资料:

《深度学习框架PyTorch:入门与实践_陈云(著)》

https://www.jianshu.com/p/7dbfc7076e5a