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上有三种写法:
-
torch.rand(2,3).cuda()
-
torch.rand(2,3).to("cuda")
-
torch.rand(2,3, device="cuda")
注意,如果你感觉???为什么我电脑上有GPU,但是操作之后却挪不上去? 不先去看一下是不是自己的显卡太拉胯了。支持GPU加速计算的必须是NVIDIA的,安装了cuda,并且你的显卡版本不是太拉胯。下图是CUDA要求的显卡驱动版本,自己对应一下。
接下来我们尝试一下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
会产生新维度。
二者都需要指定量测参数:
- 待拼接的张量的列表
- 待拼接的维度
假设你用的是二维张量,那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中一维的张量没有这种说法。
就算你用3×4的张量乘4×1的张量,得出的结果本应该是3×1的张量,但是因为是一维张量,也会变成默认的3(也不是1×3)。
可以执行的状态下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]])
比如上边矩阵是4×3的。
第二行代码你可以使用
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
是一个n×m张量,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
对矩阵mat1
和mat2
进行相乘。 如果mat1
是一个n×m张量,mat2
是一个 m×p张量,将会输出一个 n×p张量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数据集处理 – 掘金 (juejin.cn)