持续创作,加快生长!这是我参与「日新计划 10 月更文挑战」的第2天,点击查看活动概况

Tensor

所谓Tensor翻译成汉语便是张量,咱们一般能够这么以为:

  • 单个数字叫常量或许标量

  • 一维数组叫向量

  • 二维数组叫矩阵

  • 更高维的叫张量

在PyTorch中间便是用张量统称常量以外的多维数字了。

刚触摸深度学习的人必定多少了解过一点Numpy,假如你了解那就很好办了,由于PyTorch能够以为是功用多一点的Numpy。

Tensor类似于NumPy的ndarray,二者的区别是Tensor能够运用gpu等硬件加快核算。

其实Tensor和NumPy数组大部分情况下能够同享相同的底层内存,从而消除了仿制数据的需求。Tensor也对主动微分(Autograd)进行了优化。假如你很熟悉ndarray,那么Tensor API便是so easy。


初始化张量

直接从数据初始化

能够从数据直接初始化张量,你不需求指定数据类型,它能够主动获取数据类型。

import torch
data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)

从 NumPy 数组获取

也能够运用.from_numpy()办法从numpy的数组中直接获取,当然也能够运用.numpy()转换回numpy的数组。

import numpy as np
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(type(x_np))
y_t = x_np.numpy()
print(type(y_t))

输出成果:

>>
<class 'torch.Tensor'>
<class 'numpy.ndarray'>

这儿要留意一点,两个不同类型的变量在底层是同享内存的。便是你改了一个另一个也会受到影响。

print(y_t)
print(x_np)
x_np.add_(1)
print(y_t)
print(x_np)

输出成果:

>>
[[1 2]
 [3 4]]
tensor([[1, 2],
        [3, 4]], dtype=torch.int32)
[[2 3]
 [4 5]]
tensor([[2, 3],
        [4, 5]], dtype=torch.int32)

从别的Tensor获取

The new tensor retains the properties (shape, datatype) of the argument tensor, unless explic。tly overridden.

新张量保留参数张量的特色(形状、数据类型),除非显式掩盖。

x_ones = torch.ones_like(x_data)
# 保留x_data的特色
print(f"Ones Tensor: \n {x_ones} \n")
x_rand = torch.rand_like(x_data, dtype=torch.float)
# 掩盖x_data的特色
print(f"Random Tensor: \n {x_rand} \n")

随机初始化或许运用固定值初始化:

shape 是指名张量维度的元组,决定了张量的形状。

shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
empty_tensor = torch.empty(shape)
print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor} \n")
print(f"Empty Tensor: \n {zeros_tensor}")

输出成果:

>>
Random Tensor: 
 tensor([[0.4571, 0.3255, 0.1300],
        [0.8492, 0.0999, 0.5253]]) 
Ones Tensor: 
 tensor([[1., 1., 1.],
        [1., 1., 1.]]) 
Zeros Tensor: 
 tensor([[0., 0., 0.],
        [0., 0., 0.]]) 
Empty Tensor: 
 tensor([[0., 0., 0.],
        [0., 0., 0.]])

张量的特色

张量的特色是一些描述比方形状(shape), 数据类型(datatype),存储设备(device)等特色。

tensor = torch.rand(3,4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")
>>
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu

张量上的操作

PyTorch提供了上百种张量操作的办法,包含数学核算、线代、矩阵变换、采样等等,具体能够看:torch — PyTorch 1.12 documentation

之前说到了,相对于Numpy,Pytorch的优势在于每个操作都能够在GPU上履行。

默许情况下张量都是创立在CPU上的,咱们需求显式地将张量移动到GPU上,可是要知道跨设备仿制大张量有很大的时间和内存开支。

# 假如GPU可用的话将数据移到GPU上。
if torch.cuda.is_available():
    tensor = tensor.to("cuda")
print(f"Device tensor is stored on: {tensor.device}")

创立张量时分直接将其挪到GPU上有三种写法:

  1. torch.rand(2,3).cuda()

  2. torch.rand(2,3).to("cuda")

  3. torch.rand(2,3, device="cuda")

留意,假如你感觉???为什么我电脑上有GPU,可是操作之后却挪不上去? 不先去看一下是不是自己的显卡太拉胯了。支持GPU加快核算的有必要是NVIDIA的,安装了cuda,而且你的显卡版别不是太拉胯。下图是CUDA要求的显卡驱动版别,自己对应一下。

PyTorch的Tensor这么简单,你还用不明白吗?

接下来咱们尝试一下Pytorch中的一些操作。假如你熟悉NumPy API,你会发现Tensor的API很容易运用。

和numpy类索引和切片一样用法:

tensor = torch.ones(4, 4)
print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}")
tensor[:,1] = 0
print(tensor)
First row: tensor([1., 1., 1., 1.])
First column: tensor([1., 1., 1., 1.])
Last column: tensor([1., 1., 1., 1.])
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])

张量拼接 你能够运用torch.cat在给定维度上拼接张量,也能够运用torch.stack拼接张量。

二者的区别在于torch.cat不会新增维度,可是torch.stack会发生新维度。

二者都需求指定量测参数:

  1. 待拼接的张量的列表
  2. 待拼接的维度

假设你用的是二维张量,那torch.cat之后仍是二维的dim能够取0,1,可是torch.stack之后会变成三维,dim能够取0,1,2。

t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
t2 = torch.stack([tensor, tensor, tensor], dim=1)
print(t2)

成果:

>>
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
tensor([[[1., 0., 1., 1.],
         [1., 0., 1., 1.],
         [1., 0., 1., 1.]],
        [[1., 0., 1., 1.],
         [1., 0., 1., 1.],
         [1., 0., 1., 1.]],
        [[1., 0., 1., 1.],
         [1., 0., 1., 1.],
         [1., 0., 1., 1.]],
        [[1., 0., 1., 1.],
         [1., 0., 1., 1.],
         [1., 0., 1., 1.]]])

运算

最重要的乘法运算

首要声明一个向量和一个二维矩阵

import torch
vec = torch.arange(4)
mtx = torch.arange(12).reshape(4,3)
print(vec, mtx,sep='\n')

输出成果:

>>
tensor([0, 1, 2, 3])
tensor([[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8],
        [ 9, 10, 11]])

按方位

这个*在pytorch中是按方位相乘,存在播送机制。

import torch
vec = torch.arange(4)
mtx = torch.arange(12).reshape(4,3)
print(vec*vec)
print(mtx*mtx)
>>
tensor([0, 1, 4, 9])
tensor([[  0,   1,   4],
        [  9,  16,  25],
        [ 36,  49,  64],
        [ 81, 100, 121]])

可是需求留意的一点是尽管很多地方说到向量默许是列向量,可是在pytorch中一维的张量没有这种说法。 就算你用34的张量乘41的张量,得出的成果本应该是31的张量,可是由于是一维张量,也会变成默许的3(也不是13)。

能够履行的状态下print(mtx*vec)print(vec*mtx)的成果是彻底一样的。

可是上面的例子中假如履行print(mtx*vec)或许print(vec*mtx)会报错。由于默许情况下,一维的张量和矩阵履行*操作的时分,一维张量中元素的个数有必要和二维矩阵列数相同,否则播送功用失效。

当然也能够运用reshap()为其添加一个维度。可是添加维度之后要恪守一些维度规则。

import torch
vec = torch.arange(4).reshape(4,1) # 添加维度
mtx = torch.arange(12).reshape(4,3)
print(vec*mtx)
print(mtx*vec)
>>
tensor([[ 0,  0,  0],
        [ 3,  4,  5],
        [12, 14, 16],
        [27, 30, 33]])
tensor([[ 0,  0,  0],
        [ 3,  4,  5],
        [12, 14, 16],
        [27, 30, 33]])

比方上边矩阵是43的。

第二行代码你能够运用

  • vec = torch.arange(4).reshape(4,1)
  • vec = torch.arange(3).reshape(1,3)

便是说有必要在行或许列上坚持元素个数的一致。

数乘torch.mul

torch.mul(input, value, out=None)

用标量值value乘以输入input的每个元素,并回来一个新的成果张量。便是张量的数乘运算。

import torch
vec = torch.arange(4)
mtx = torch.arange(12).reshape(3,4)
print(torch.mul(vec,2))
print(torch.mul(mtx,2))
>>
tensor([0, 2, 4, 6])
tensor([[ 0,  2,  4,  6],
        [ 8, 10, 12, 14],
        [16, 18, 20, 22]])

矩阵向量相乘torch.mv

torch.mv(mat, vec, out=None) → Tensor

对矩阵mat和向量vec进行相乘。 假如mat是一个nm张量,vec是一个m元 1维张量,将会输出一个n元 1维张量。

有必要前边是矩阵后边是向量,维度要契合矩阵乘法。出来的是一维张量。

import torch
vec = torch.arange(4)
mtx = torch.arange(12).reshape(3,4)
print(torch.mv(mtx,vec))
>>
tensor([14, 38, 62])

矩阵乘法torch.mm

torch.mm(mat1, mat2, out=None) → Tensor

对矩阵mat1mat2进行相乘。 假如mat1是一个nm张量,mat2是一个mp张量,将会输出一个np张量out

便是咱们线代中学的矩阵乘法,维度有必要对应正确。

import torch
mtx = torch.arange(12)
m1 = mtx.reshape(3,4)
m2 = mtx.reshape(4,3)
print(torch.mm(m1, m2))

输出成果:

>>
tensor([[ 42,  48,  54],
        [114, 136, 158],
        [186, 224, 262]])

点乘积torch.dot

torch.dot(tensor1, tensor2) → float

核算两个张量的点乘积(内积),两个张量都为一维向量。

import torch
vec = torch.arange(4)
print(torch.dot(vec, vec))

输出成果:

>>
tensor(14)

黑科技@

还存在一个黑科技@,也是严厉按照第一个参数的列数要等于第二个参数的行数。

import torch
vec = torch.arange(4)
mtx = torch.arange(12)
m1 = mtx.reshape(4,3)
m2 = mtx.reshape(3,4)
print(vec @ vec)
print(vec @ m1)
print(m2 @ vec)
print(m1 @ m2)

输出成果:

>>
tensor(14)
tensor([42, 48, 54])
tensor([14, 38, 62])
tensor([[ 20,  23,  26,  29],
        [ 56,  68,  80,  92],
        [ 92, 113, 134, 155],
        [128, 158, 188, 218]])

上边的成果或许不行直观,那看看下边:

import torch
vec = torch.arange(4)
mtx = torch.arange(12)
m1 = mtx.reshape(4,3)
m2 = mtx.reshape(3,4)
print(vec @ vec==torch.dot(vec,vec))
print(vec @ m1) # 本句直接运用torch.mv()无法履行。
print(m2 @ vec==torch.mv(m2,vec))
print(m1 @ m2==torch.mm(m1,m2))

运用一个@就能够代替上边的那三个函数。

  • 对一维张量履行@操作便是dot
  • 对一维和二维张量履行操作便是mv
  • 对二维张量履行@操作便是mm

输出成果:

>>
tensor(True)
tensor([42, 48, 54])
tensor([True, True, True])
tensor([[True, True, True, True],
        [True, True, True, True],
        [True, True, True, True],
        [True, True, True, True]])

第二个无法替换的怎么办?为了满意强迫症,能够这样:

import torch
vec = torch.arange(4)
mtx = torch.arange(12).reshape(4,3)
print(vec @ mtx) # 本句直接运用torch.mv()无法履行。
print(torch.mm(vec.reshape(1,4),mtx))
print(vec @ mtx==torch.mm(vec.reshape(1,4),mtx))

输出成果:

>>
tensor([42, 48, 54])
tensor([[42, 48, 54]])
tensor([[True, True, True]])

再加一个torch.matmul

vec = torch.arange(3)
mtx = torch.arange(12).reshape(3,4)
print(torch.matmul(vec,mtx))
print(torch.matmul(vec,vec))
print(torch.matmul(mtx.T,mtx))
print(torch.matmul(mtx.T,vec))

输出成果:

>>
tensor([20, 23, 26, 29])
tensor(5)
tensor([[ 80,  92, 104, 116],
        [ 92, 107, 122, 137],
        [104, 122, 140, 158],
        [116, 137, 158, 179]])
tensor([20, 23, 26, 29])

@看起来差不多,也是能够:

  • 对一维张量履行操作便是dot
  • 对一维和二维张量履行操作便是mv
  • 对二维张量履行操作便是mm

可是他们的区别在于matmul不知局限于一二维,能够进行高维张量的乘法。

其他管用运算

经由Pytorch核算出来的数字,即使最后只剩一个元素了数据类型依旧是张量,假如你想将其转为普通的python数据类型能够运用.item办法。

a = tensor.sum()
print(type(a))
a_item = a.item()
print(type(a_item))

In-place operations,就地核算,我个人感觉有点类似于C++里边的i++这种操作。 对一个张量履行就地操作torch.xxx_()

  • xxx是操作名称
  • ()参数是待处理的张量
print(tensor)
tensor.add_(5)
print(tensor)

就地操作节省了一些内存,但在核算导数时或许会出现问题,由于会立即丢掉历史记录。所以神经网络要核算梯度下降这种不要运用就地核算。


数据集处理部分看这儿:PyTorch数据集处理 – ()