文章目录
-
- 前言 _ PyTorch主要模块
- Tensor
-
- 张量数据类型
- 张量的创建
-
- 1. 对于预先有数据的情况,通过Torch.tensor()创建
- 2. 通过`torch`模块下的内置函数创建特殊形状的张量
- 张量存储设备
- 张量运算
- 张量维度
-
-
- 关于contiguous该方法的应用场景
-
- 张量极值和排序
- 张量乘法
- 拼接分割张量
- 扩张、压缩和广播
- 扩张、压缩和广播
前言 _ PyTorch主要模块
本专栏以张校捷为主PyTorch:从模型到源代码的阅读笔记,既有作者自己的思考和补充,又有更详细的内容,请支持原作者。
模块名 | 功能 |
---|---|
torch |
1. 常见的,例如torch.relu 和torch.sigmoid ;2. PyTorch中张量的一些操作,比如torch.add(x,y) ,注:这些操作通常也可以通过张量本身来实现,两者都是等价的,例如x.add(y) ,对于一些常用的操作,PyTorch运算符的重载已经实现,例如x y 3. 产生一定形状的张量,如torch.zeros 和torch.randn |
torch.Tensor |
PyTorch中张量类型 |
torch.cuda |
cuda比如检查cuda是否可用,torch.cuda.is_avalible |
torch.nn |
PyTorch神经网络模块的核心1. 卷积层nn.Conv2d 和全连接层nn.Linear 等等,其实是调用的nn.functional.Conv2d 2. 在构建深度学习模型时,需要继承nn.Module 类,并重写forward 实现新神经网络的方法3. ,nn.MSELoss 和nn.CrossEntropyLoss |
torch.nn.functional |
卷积函数,,不常见的激活函数,如relu6 等 |
torch.optim |
优化器;学习率下降的子模块,如optim.lr_scheduler |
torch.autograd |
自动微分包括反向传播autograd.backward 函数,autograd.grad 实现一个标量(scalar)求导一张量;设置不需要参与求导的模块 |
torch.distributed |
PyTorch分布式计算模块的原理是多过程 |
torch.distributions |
可以解决 加强学习Policy Gradient 输出结果离散,无法求导的问题(采样,对数求导) |
torch.jit |
动态图 → 静态图 |
torch.utils.data |
主要包括数据集(Dataset )数据载入器(DataLoader ) |
torch.utils.tensorboard |
可视化 |
Tensor
张量数据类型
PyTorch目前支持9种数据类型(torch.float32,torch.int32,torch.uint8,torch.bool,etc.),对应每种数据类型CPU和GPU例如,两个版本,torch.FloatTensor
和torch.cuda.FloatTensor
,但是目前PyTorch复数类型不支持。
可以通过数据类型to例如,转换方法>>> x.to(torch.FloatTensor)
张量的创建
1. 对于预先有数据的情况,通过Torch.tensor()创建
若已提前有数据x
(Python list 或 numpy array)可以通过这种方法创建torch.tensor(x)
,需要注意问题:
- 对于numpy数组,精度不变(numpy数组和tensor转换非常快,和tensor这里不表示底层实现)
- 对于Python list类型数据,PyTorch默默地将浮点数转换为单精度浮点数(torch.float32)而不是 torch.float64
此外,该方法,但子列表的大小要一致,否则会报错。
注意:torch.Tensor()
也支持这种创建方式,但不支持直接引入 例如,创建标量的实数torch.tensor(1)
结果是一个scalar
类型标量,其shape为torch.Size([])
,不同于torch.tensor([1])
的shape。
2. 通过torch
模块下的内置函数创建特殊形状的张量
>>> import torch >>> torch.rand(3,3) # [0,1)均匀分布,注意,如果没有特别说明,一般是前闭后开 tensor([[0.6227, 0.5353, 0.4991],
[0.9781, 0.3917, 0.0828],
[0.7478, 0.5403, 0.7267]])
>>> torch.randn(3,3) # 标准正态分布
tensor([[-0.0391, 0.4533, 1.1559],
[-0.0949, 0.0836, 0.1401],
[-0.2005, 0.2361, -0.5077]])
>>> torch.zeros(3,3)
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
>>> torch.eye(3,3)
tensor([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
>>> torch.ones(3,3)
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
>>> torch.randint(0,10,(3,3)) # [0,10)均匀分布的整数
tensor([[2, 9, 4],
[1, 7, 7],
[5, 3, 0]])
注意最后一个torch.randint(0,10,(3,3))
的用法。
也可以创建形状相同的张量
torch.ones_like()
,torch.zeros_like()
, torch.rand_like()
,torch.randn_like()
或者直接传入张量的shape/形状的元组,会先检查空间是否足够,然后创建,用时才分配空间。
也在创建时,通过.dtype(torch.float32)
指定张量的元素类型
张量的存储设备
注意:只有在相同设备上的张量才能进行运算,否则会报错。
将数据转移到其他设备的方法: x.cpu()
或者x.gpu(0)
;x.to("cuda:0")
推荐使用后一种,参数可以是设备名,也可以是torch.device
实例。
张量运算
一般有两种等价的运算方法
torch.add(a,b)
或者torch.add(a,b,out=c)
,第二种需提前声明ca.add(b)
或者a.add_(b)
, 第二种方法后加下划线,表明是就地操作,即修改了a的值
张量维度
可以通过size(x)
或者x.shape
查看张量形状,两种方法等价,返回的都是PyTorch基于Python Tuple封装的torch.Size
类型,通过索引查看每个维度的大小。
改变张量形状:
-
view
方法,指定-1,PyTorch会自动计算,只改变tensor头部中的步长(stride)信息,而改变底层数据,注意,一定要保证前后元素个数一致。如果想要将内容复制到新的一份内存空间,若前后不兼容,使用
.contiguous()
,否则,直接深拷贝torch.clone()
即可。More about contiguous
-
reshape方法
相当于view+contiguous
关于contiguous方法的一个应用场景
需求:将四张256*256的RGB图片截取成上下两部分,并保存为六张照片。
思考:实际就是[4,3,256,256]→[12,3,128,256]
img_PIL = torch.rand(4,3,256,256)
new_img_tensor = img_tensor.view(4,3,2,128,256)
new_img_tensor = new_img_tensor.permute(0,2,1,3,4)
new_img_tensor.contiguous() # 注意,view方法只能处理语义和内存一致/兼容的张量,需要提前进行一致化处理
new_img_tensor = new_img_tensor.view(8,2,128,256)
张量极值和排序
使用案例
>>> x = torch.rand(3,4)
>>> x
tensor([[0.6890, 0.6742, 0.1902, 0.9747],
[0.4639, 0.3918, 0.4839, 0.8264],
[0.7149, 0.8934, 0.9888, 0.4748]])
>>> x.argmin(1)
tensor([2, 1, 3])
>>> x.min(1)
torch.return_types.min(
values=tensor([0.1902, 0.3918, 0.4748]),
indices=tensor([2, 1, 3]))
>>> torch.argmin(x,1)
tensor([2, 1, 3])
>>> torch.min(x,1)
torch.return_types.min(
values=tensor([0.1902, 0.3918, 0.4748]),
indices=tensor([2, 1, 3]))
>>> x.sort(1)
torch.return_types.sort(
values=tensor([[0.1902, 0.6742, 0.6890, 0.9747],
[0.3918, 0.4639, 0.4839, 0.8264],
[0.4748, 0.7149, 0.8934, 0.9888]]),
indices=tensor([[2, 1, 0, 3],
[1, 0, 2, 3],
[3, 0, 1, 2]]))
>>> x.sort(1,descending=True)
torch.return_types.sort(
values=tensor([[0.9747, 0.6890, 0.6742, 0.1902],
[0.8264, 0.4839, 0.4639, 0.3918],
[0.9888, 0.8934, 0.7149, 0.4748]]),
indices=tensor([[3, 0, 1, 2],
[3, 2, 0, 1],
[2, 1, 0, 3]]))
argmin和min既可以使用torch模块下的方法,也可以使用tensor自带的方法,前者返回indices,后者返回最小值和其在原始张量上的位置,sort排序默认是升序,也可通过descending=True
指定为降序。
张量乘法
前面的a.mul(b)
是对应元素相乘,而a.mm(b)
对应的是矩阵乘法,在Python3.5之后,根据PEP465提案,也可以用a@b
进行矩阵乘法。
mini-batch 矩阵乘法:torch.bmm(a,b)
针对有batch的情况,如果此时调用a@b
,会自动调用bmm
。
对于更多维度的张量相乘的情况,需要决定各自张量元素乘积的结果沿着哪些维度求和,这个过程称作 contraction,需引入爱因斯坦求和约定 Einstein Summation Convention:
More about Einsum in PyTorch
但是,einsum的运行速度非常慢,下面在cpu上写一个测试时间的装饰器
# 验证torch.einsum的速度
import time
import torch
def time_test(func):
def inner(*args,**kwargs):
start_time = time.time()
func(*args,**kwargs)
end_time = time.time()
return end_time - start_time
return inner
@time_test
def common_test(a,b):
for i in range(1000):
a.mul(b)
@time_test
def einsum_test(a,b):
for i in range(1000):
torch.einsum('ik,kj->ij',[a,b])
def main():
a = torch.arange(10000).reshape(100, 100)
b = torch.arange(10000).reshape(100, 100)
einsum_time = einsum_test(a,b)
common_time = common_test(a,b)
print("common_test takes %s\neinsum_test takes %.3f s"%(common_time,einsum_time))
if __name__ == "__main__":
main()
# common_test takes 0.016 s
# einsum_test takes 2.647 s
张量的拼接与分割
拼接
torch.stack
传入 张量列表,指定并一个维度堆叠torch.cat
传入张量列表,一个维度拼接
分割
torch.split
传入 被分割张量,分割后维度的 整数/列表,分割的维度torch.chunk
传入 被分割张量,分割的 整数/列表,分割的维度
张量的扩增、压缩和广播
torch.squeeze
和torch.unsqueeze
广播机制,主要是用来解决 四则运算时 张量维度不一致的问题
-
如果两张量的形状不一致,不能进行计算,例如(3,4,5)只能和(3,4,5)形状的张量运算
-
除了一种特殊情况,张量的某个维度为1,则进行复制,使其满足条件1
例如 (3,4,5)和(3,1,5),需要将(3,1,5)扩增为(3,4,5),然后进行运算
数/列表,分割的维度
张量的扩增、压缩和广播
torch.squeeze
和torch.unsqueeze
广播机制,主要是用来解决 四则运算时 张量维度不一致的问题
-
如果两张量的形状不一致,不能进行计算,例如(3,4,5)只能和(3,4,5)形状的张量运算
-
除了一种特殊情况,张量的某个维度为1,则进行复制,使其满足条件1
例如 (3,4,5)和(3,1,5),需要将(3,1,5)扩增为(3,4,5),然后进行运算