pytorch常用方法记录

  1. 1. pytorch常用方法记录
    1. 1.1. 常用损失函数
      1. 1.1.1. 交叉熵CrossEntropyLoss
      2. 1.1.2. 均方差MSELoss (L2Loss)
      3. 1.1.3. BCELoss
      4. 1.1.4. L1Loss (MAE)
    2. 1.2. 常用激活函数
      1. 1.2.1. Sigmoid函数
      2. 1.2.2. ReLu函数
      3. 1.2.3. LeakyReLU
    3. 1.3. 各种层
      1. 1.3.1. 二元自适应均值汇聚层
    4. 1.4. 常用方法
      1. 1.4.1. torch.squeeze()
      2. 1.4.2. torch.unsqueeze()
      3. 1.4.3. torch.empty()
      4. 1.4.4. torch.split()
      5. 1.4.5. 四种padding模式
        1. 1.4.5.1. nn.ReflectionPad2d
        2. 1.4.5.2. nn.ZeroPad2d
        3. 1.4.5.3. nn.ReplicationPad2d
        4. 1.4.5.4. nn.ConstantPad2d
      6. 1.4.6. 自动混合精度(torch.cuda.amp)
        1. 1.4.6.1. 为什么使用自动混合梯度
        2. 1.4.6.2. 使用方法
      7. 1.4.7. 图片存储
      8. 1.4.8. nn.DataParallel数据并行
      9. 1.4.9. net.train()/eval()
      10. 1.4.10. torch.where()
      11. 1.4.11. torch.as_tensor()和torch.from_numpy()
    5. 1.5. 调试工具
      1. 1.5.1. 自动请求导异常检测

pytorch常用方法记录

本篇记录在学习使用pytorch时,常用的东西记录,以便随时查看。遇到新学到的会更新在这里。

常用损失函数

交叉熵CrossEntropyLoss

计算公式:

这个公式在深度学习入门04中讲过。

python代码:

1
2
cross_entropy_loss = torch.CrossEntropyLoss()
loss = cross_entropy_loss(input: Tensor, target: Tensor)

注意:CrossEntropyLoss的输入就直接是Model的输出,不要是softmax之后的。还有就是label必须是long类型在[0, class-1]之间。

查看源码中的解释即可知道,CrossEntropyLoss = log_softmax + NllLoss,因此不需要softmax。其中的C代表的类型。

均方差MSELoss (L2Loss)

计算公式:

1
2
mseLoss = torch.MSELoss()
loss = mseLoss(input: Tensor, target: Tensor)

BCELoss

bce loss分类,用于二分类问题。数学公式如下

pytorch中bceloss

1
class torch.nn.BCELoss(weight: Optional[torch.Tensor] = None, size_average=None, reduce=None, reduction: str = 'mean')

weight: 初始化权重矩阵

size_average: 默认是True,对loss求平均数

reduction: 默认求和, 对于batch_size的loss平均数

L1Loss (MAE)

指的是一范数损失,也叫MAE,就是估计值和目标值做差的绝对值

公式:

1
2
l1_loss = nn.L1Loss()
loss = l1_loss(input:Tensor, target:Tensor)

常用激活函数

Sigmoid函数

公式:

代码:

1
nn.Sigmoid()

ReLu函数

ReLU函数:在输入大于0时,直接输出该值;在小于等于0时为0

公式:


代码:

1
nn.ReLU(inplace= False)

inplace若为True,则覆盖之前的值,可以减少内存消耗。

LeakyReLU

虽然relu在SGD中收敛很快,但是对于小于0的值梯度永远是0,那么激活函数收到的值通常都会有bias,如果偏置很小,导致值一直都是负数,使得梯度无法更新,最终导致神经元“死亡”。所以提出了LeakyReLU,当x小于0时不再等于0而是等于一个weight x。通常这个权重取0.2
$
y = max(0,x) + weight
min(0,x)
$
代码:

1
nn.LeakyReLU(0.2)

各种层

二元自适应均值汇聚层

在实现ResNet的时候,看了下pytorch里面的ResNet源码,看到平均池化层使用的是nn.AdaptiveAvgPool2d((1, 1)),就觉的好奇查了下。

其实就是平均池化层,好处就是不需要我们自己算平均池化的步长啊,核大小啊,填充啊这些,只用传入一个我想要的大小,其他都是去自适应,还是蛮舒服的哈:happy:

常用方法

torch.squeeze()

对数据的维度进行压缩,去掉某行或某列。只能对于为1的维度。比如:

1
2
3
4
t = torch.randn((2,3,1))
print(t.size())
t = t.squeeze(2)
print(t.size())

out:

torch.Size([2, 3, 1])
torch.Size([2, 3])

torch.unsqueeze()

将数据维数进行扩充。比如:

1
2
3
4
5
6
t = torch.randn((2,3,3))
print(t.size())
t = torch.unsqueeze(t,dim=2)
print(t.size())
# 或者 tensor.unsqueeze(dim)
t = t.unsqueeze(2) # 是一样的

out:

torch.Size([2, 3, 3])
torch.Size([2, 3, 1, 3])

torch.empty()

之前pytorch入门笔记01讲过,这里将如何赋值

1
2
3
t = torch.empty(2,3)
t.data[0].copy_(torch.randn(3))
t.data[1].copy_(torch.randn(3))

data找到对应的值,然后copy进去即可。

torch.split()

数据分割使用的

1
2
3
4
5
6
7
8
9
10
images = torch.randn((3, 2, 2, 2))
im1, im2 = images.split(1, dim=1)
print(im1.shape)
# tensor.split(split_size, dim)
# 参数说明:
# -- split_size
# 分割大小,比如如果是(3, 6, 5, 5)
# 6通道的图,此时设定split_size为3,那么返回的tuple为两个也就是
# im1, im2 = tensor.split(3, dim=1)
# -- dim 分割那个维度,这个好理解。

四种padding模式

四个模式为Reflect,Zero,Replication,Constant。注意padding的大小不能超过matrix的大小。这部分的四个模式也是对应Convolution中的padding_mode设置。该设置有四种模式(’zeros’, ‘reflect’, ‘replicate’, ‘circular’)

一下例子以3 x 3大小的矩阵举例。这部分是pytorch源码中用的例子。

1
testA = torch.arange(9, dtype=torch.float).reshape(1, 1, 3, 3)

out:

tensor([[[[0., 1., 2.],
[3., 4., 5.],
[6., 7., 8.]]]])

注意:还有这里就是所有的Pad参数都是填padding大小,ConstantPad当中需要在填入一个value。padding的参数如果是int就是周围都填充这么多。如果是一个有4个参数的tuple,表示的是左,右,上,下的顺序依次填充数量。

nn.ReflectionPad2d

字面意思镜像复制,就是以当前位置作为对称轴,将后面的值按照镜像翻转一份出来。

1
2
reflectPadding = nn.ReflectionPad2d(2)
testA = reflectPadding(testA)

nn.ZeroPad2d

这个是使用0进行填充

1
2
zerosPadding = nn.ZeroPad2d(1)
testA = zerosPadding(testA)

nn.ReplicationPad2d

复制填充,将周围的值复制一份填充到边框。

1
2
replicationPadding = nn.ReplicationPad2d(1)
testA = replicationPadding(testA)

nn.ConstantPad2d

设定固定值作为填充

1
2
constantPadding = nn.ConstantPad2d(1, 2)
testA = constantPadding(testA)

自动混合精度(torch.cuda.amp)

作用:减小显存占用,加快训练速度。

适用设备:支持Tensor Core的CUDA设备。

使用方式:torch.cuda.amp.autocast和torch.cuda.amp.GradScaler结合适用

混合精度:表示不止一种精度的Tensor,在AMP中有两种:FloatTensor,HalfTensor

自动:框架可以按需自动调整Tensor的dtype

为什么使用自动混合梯度

torch.HalfTensor优势:

存储小、计算快、更好利用Tensor Core,减少显存占用意味着可以多添加batchsize,训练加快。

torch.HalfTensor劣势:

数值范围小容易Overflow、舍入误差。

解决方案

  1. 梯度Scale,放大loss的值防止梯度underflow。
  2. 回落到torch.FloatTensor,至于什么时候回落这个由框架自行决定

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
model = Net().to("cuda")
optimizer = optim.SGD(model.parameters(), ...)

# 在训练前初实例化一个GradScaler对象
scaler = torch.cuda.amp.GradScaler()

for step, data in enumerate(dataLoader):
optimizer.zero_grad()
images, labels = data
images = images.to("cuda")
labels = labels.to("cuda")
# 前向过程(model + loss)开启autocast
with torch.cuda.amp.autocast():
out = model(images)
loss = loss_fn(output, target)

# 反向传播在autocast之外
# Scaler loss,放大梯度
scaler.scale(loss).backward()
# scaler.step() 首先吧梯度值unscaler回来
# 如果梯度值不是infs或者NaNs,那么调用optimizer.step()更新权重
# 否则忽略step调用,保证权重不更新
scaler.step(optimizer)

# 准备着,看是否增大scaler
scaler.update()

这里不需要在input上手动调用.half(),这个是框架自动去做的。

图片存储

1
torchvision.utils.save_image(tensor, filename)

1
2
3
4
# 使用matplotlib
import matplotlib.pyplot as plt
plt.imshow(numpydata)
plt.savefig(name)

nn.DataParallel数据并行

该方法在模块级别上实现了数据并行。相当nice的一个东西,当有多块显卡的时候,并且单块显存不足的时候,训练慢的时候都可以用。他做的工作是:将模块复制到每个设备上,每个副本都处理输入数据的一部分,然后在后向传递的过程中,每个副本的梯度会被累加到原始模块中。

注意 batch_size应该比GPU数量要多。

官方推荐使用的是DistributedDataParallel,来替代nn.DataParallel。即便只有一块卡,也是推荐使用DistributedDataParallel。两个重要区别就是DataParallel用的是多线程,DistributedDataParallel使用的是多进程。使用multiprocessing,每个GPU都有自己的专用进程,避免了python解释器GIL带来的性能开销。

DataParallel使用方法:

1
2
3
net = Net()	# 网络
net = torch.nn.DataParallel(net, device_ids=[0, 1]) # 使用0,1号GPU
# 后面正常使用就可以了

如果用DataParallel的方法训练,参数拿下来,想本地CPU跑怎么办呢

同样使用torch.load()加载参数,但是记得设定map_location=“cpu”转到CPU上的参数,然后将参数进行调整。代码如下。

1
2
3
4
5
6
7
8
check_point = torch.load("./model/net_parm.pth", map_location="cpu")
dicts = collections.OrderedDict()
for k, value in check_point.items():
if "module" in k:
k = k.split(".")[1:]
k = ".".join(k)
dicts[k] = value
net.load_state_dict(dicts)

DistributedDataParallel使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import torch
import torch.distributed as dist
import torch.multiprocessing as mp
import torch.nn as nn
import torch.optim as optim
from torch.nn.parallel import DistributedDataParallel as DDP


def example(rank, world_size):
# create default process group
dist.init_process_group("gloo", rank=rank, world_size=world_size)
# create local model
model = nn.Linear(10, 10).to(rank)
# construct DDP model
ddp_model = DDP(model, device_ids=[rank])
# define loss function and optimizer
loss_fn = nn.MSELoss()
optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)

# forward pass
outputs = ddp_model(torch.randn(20, 10).to(rank))
labels = torch.randn(20, 10).to(rank)
# backward pass
loss_fn(outputs, labels).backward()
# update parameters
optimizer.step()

def main():
world_size = 2
mp.spawn(example,
args=(world_size,),
nprocs=world_size,
join=True)

if __name__=="__main__":
main()

net.train()/eval()

对应修改的模型中的training属性,如果是train()方法,表示训练模式,training属性为True,可以在模型中控制在训练的时候,在模型中通过training属性进行判断,是否执行指定部分代码。如果调用eval(),表示是测试模式,training为False。

torch.where()

1
torch.where(condition: Tensor, self, other)

根据条件,将tensor修改。用法第一个参数是条件,第二个是条件为True的值,第三个是条件为False的值。

torch.as_tensor()和torch.from_numpy()

两者都是共享数据,所以转换tensor很快,且节省内存。可以推荐使用as_tensor(),因为as_tensor()可以接受任何像Python数据结构这样的数组。

1
2
3
4
5
6
7
'''
torch.as_tensor(data, dtype, device)
- data(类似数组一样的) :tensor初始化数据。可以是list, tuple, Numpy的ndarray,scalar,和其他类型。
- dtype(torch.dtype) 希望返回的tensor数据类型。默认:如果是None,从data中推断数据类型
- device(torch.device) 希望返回的tensor所在设备。默认:如果是None,使用当前设备作为默认tensor类型。
'''
torch.as_tensor(np.array(label), dtype=torch.int64) # 在做图像分割任务时的label为单通道,里面值对应类型数时可以用这个转为tensor

调试工具

自动请求导异常检测

在训练模型的过程中,常常也会遇到。one of the variables needed for gradient computation has been modified by an inplace operation。这类似的问题。意思是一个变量需要计算梯度,但这个变量被就地操作修改。往往是出现在ReLu中的inplace参数设置为True的原因。但有时候也可能不是因为ReLu,可是报错也没有给出具体哪个地方的梯度设置问题,使用这个求导一场检测可以给出具体哪里出的问题。

使用:torch.autograd.set_detect_anomaly(True)torch.autograd.detect_anomaly()

1
2
3
4
5
6
7
8
import torch

# 正向传播时:开启自动求导的异常侦测
torch.autograd.set_detect_anomaly(True)

# 反向传播时:在求导时开启侦测
with torch.autograd.detect_anomaly():
loss.backward()