Pytorch踩坑记录

ToTensor

Pytorch中有ToTensor()函数,经常用在加载数据的时候。注意该函数的API文档是这样说的:

1
2
3
4
5
6
7
8
9
 """Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor.

Converts a PIL Image or numpy.ndarray (H x W x C) in the range
[0, 255] to a torch.FloatTensor of shape (C x H x W) in the range [0.0, 1.0]
if the PIL Image belongs to one of the modes (L, LA, P, I, F, RGB, YCbCr, RGBA, CMYK, 1)
or if the numpy.ndarray has dtype = np.uint8

In the other cases, tensors are returned without scaling.
"""

也就是说,当该函数的输入为numpy.ndarray (H x W x C) np.uint8或者PIL Image(L, LA, P, I, F, RGB, YCbCr, RGBA, CMYK, 1)的时候,会对数据进行归一化处理。对于图像分割任务,一二分类为例,当类标为{0,1}的时候,满足以上条件,照样会对数据进行预处理。此时,类标就会变成很小的数,导致网络预测出的结果全为0。

值得注意的是,torchvision0.2.0并没有上面输入的前提,全部会归一化,也就是说下面的代码是错误的,而在torchvision0.2.0没有问题。

1
2
3
4
5
6
7
8
9
10
11
12
mask = Image.open(mask_path)
mask = mask.resize((224, 224))
# 将255转换为1, 0转换为0
mask = np.around(np.array(mask.convert('L'))/256.)
# mask = mask[:, :, np.newaxis] # Wrong, will convert range
mask = np.reshape(mask, (np.shape(mask)[0],np.shape(mask)[1],1)).astype("float32")
to_tensor = transforms.ToTensor()

transform_compose = transforms.Compose([to_tensor])
mask = transform_compose(mask)
mask = torch.squeeze(mask)
return mask.float()

对于这种情况,可以使用torch.from_numpy用法。避免不同torchvision版本的不同造成错误的影响。

1
2
3
4
5
6
mask = Image.open(mask_path)
mask = mask.resize((224, 224))
# 将255转换为1, 0转换为0
mask = np.around(np.array(mask.convert('L'))/256.)
mask = torch.from_numpy(mask)
return mask.float()

同样的,对于输入数据而言,使用ToTensor()的时候,也会归一化。例如下面代码:

1
2
3
4
5
6
7
8
image = self.image_transform(img)

resize = transforms.Resize(224)
to_tensor = transforms.ToTensor()
normalize = transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
transform_compose = transforms.Compose([resize, to_tensor, normalize])

image = transform_compose(image)

这里的数据首先经过resize函数进行resize,接着使用ToTensor()函数归一化。最终使用transforms.Normalize函数标准化。可以看到这里使用transforms.Normalize函数的失活,均值和方差均小于1,这也是因为ToTensor()函数会对数据归一化的体现。而我之前使用Tensorflow的时候,因为没有经过归一化,所以标准化的时候,数据各个通道的均值为[103.939, 116.779, 123.68]

model.zero_grad() vs optimizer.zero_grad()

  1. 引言

在PyTorch中,对模型参数的梯度置0时通常使用两种方式:model.zero_grad()optimizer.zero_grad()。二者在训练代码都很常见,那么二者的区别在哪里呢?

  1. model.zero_grad()

model.zero_grad()的作用是将所有模型参数的梯度置为0。其源码如下:

1
2
3
4
for p in self.parameters():
if p.grad is not None:
p.grad.detach_()
p.grad.zero_()
  1. optimizer.zero_grad()

optimizer.zero_grad()的作用是清除所有优化的torch.Tensor的梯度。其源码如下:

1
2
3
4
5
for group in self.param_groups:
for p in group['params']:
if p.grad is not None:
p.grad.detach_()
p.grad.zero_()
  1. 总结
  • 当使用optimizer = optim.Optimizer(net.parameters())设置优化器时,此时优化器中的param_groups等于模型中的parameters(),此时,二者是等效的,从二者的源码中也可以看出来。
  • 当多个模型使用同一个优化器时,二者是不同的,此时需要根据实际情况选择梯度的清除方式。
  • 当一个模型使用多个优化器时,二者是不同的,此时需要根据实际情况选择梯度的清除方式。

bilinear中的align=True or False

关于blinear中 api里面的align=True和False,其实就是角点是否对齐的选项。

  1. 在opencv中,blinear默认调用的是align=False
  2. 在pytorch和tensorflow中, 可以通过api去切换align

先放一张图直观的理解一下:

img

可以明显的看到align_corners=True的时候,角点是对齐状态。而align_cornels=False的时候,角点并没有对齐。

用代码去描述上述的区别为:

1
2
3
4
# align_corners = False
# x_ori is the coordinate in original image
# x_up is the coordinate in the upsampled image
x_ori = (x_up + 0.5) / factor - 0.5
1
2
3
4
5
6
7
8
9
10
11
# align_corners = True
# h_ori is the height in original image
# h_up is the height in the upsampled image
stride = (h_ori - 1) / (h_up - 1)
x_ori_list = []
# append the first coordinate
x_ori_list.append(0)
for i in range(1, h_up - 1):
x_ori_list.append(0 + i * stride)
# append the last coordinate
x_ori_list.append(h_ori - 1)

参考

PyTorch中的model.zero_grad() vs optimizer.zero_grad()
【图像处理】bilinear中的align=True or False
PyTorch中grid_sample的使用方法
pytorch 模型 .pt, .pth, .pkl的区别及模型保存方式

------ 本文结束------
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道