关于pytorch的一些笔记

Posted by kevin on March 20, 2020

preface

深度学习框架学起来还是 pytorch 更舒服,简洁易懂,个人觉得比 tensorflow 学起来更轻松,并且目前学术界大多用的也都是 pytorch 来复现代码,所以这篇博客就记录一下我学习的过程中的笔记。

unsqueeze

tensor.unsqueeze(tensor, dim=x) 用来给 tensor 升维,也就是加上一个维数为 1 的维度(dim 参数不能超过 tensor 的维度),例如

import torch 
a = torch.rand((1, 2, 3))
a.size()
# torch.Size([1, 2, 3])
b = torch.unsqueeze(a, 2)
b.size()
# torch.Size([1, 2, 1, 3])

unsqueeze 这个函数还是挺常用的,例如在处理逻辑回归时输入的点为一维的数据,我们就要用 unsqueeze 来升维使其变成二维的数据。同理,tensor.squeeze(tensor, dim=x) 就是用来降维的,如果不指定 dim 参数的话就默认将所有维数为 1 的维度都删除

import torch 
a = torch.rand((1, 1, 2, 3))
a.size()
# torch.Size([1, 1, 2, 3])
b = torch.squeeze(a)
b.size()
# torch.Size([2, 3])

如果指定了 dim 参数的话,则将该 dim 上进行维度删除,如果该 dim 的维数不为 1 的话则 tensor 不变,为 1 的话则将该 dim 删除

import torch 
a = torch.rand((1, 1, 2, 3))
a.size()
# torch.Size([1, 1, 2, 3])
b = torch.squeeze(a, 2)
b.size()
# torch.Size([1, 1, 2, 3])
c = torch.squeeze(a, 1)
c.size()
# torch.Size([1, 2, 3])

图像在通过网络的卷积层 forward 之后,出来的维度是 batch_size, chanels, height, width ,有四个维度,所以测试的时候要用 unsqueeze(0) 来将测试用的三维图像提升一个维度(在图像预处理时就已经用 transforms.ToTensor() 来将测试图像变成了 channels, height, width 格式)

并且如果在通过卷积层后还要继续接全连接层的话,一般用 tensor.view(tensor.size(0), -1) 来将卷积过后的所有特征都变成一个特征向量进行全连接

x.view

visualization

在用 matplotlib 进行画图可视化时要用 tensor.data.numpy() 将 tensor 转化为 numpy 的 ndarray 数据,不能用代表 Variable 的 tensor 来画图


用了 GPU 训练的数据不能用 matplotlib 进行可视化,要用 data.cpu() 将其转到 CPU 上


在 jupyter notebook 上用 OpenCV 的 cv2.imshow() 会使进程崩溃,可以用 matplotlib 来代替

import cv2
import matplotlib.pyplot as plt
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show()

因为 OpenCV 储存的图像是 BGR 格式的,而 matplotlib 是 RGB 格式,所以要转换一下颜色空间再显示,否则颜色会有些奇怪


可视化训练集,用 DataLoader 的话可以先将其变成一个迭代器,然后用 next() 方法获取 batch_size 张图片,用 torchvision.utils.make_grid(img, padding=x) 可以将多张图片变成一张,padding 是图片之间的间隔。一般都用 matplotlib 来可视化,注意 DataLoader 中的图如果是 tensor 形式的话,要先转成 numpy 形式,此时它的通道是(channels,imgSize,imgSize),而 matplotlib 中 show 图的通道形式是(imgSize,imgSize,channels),因此还需要用 np.transpose(1, 2, 0) 来转置一下通道

def imshow(img):
    npimg = img.numpy()
    plt.axis('off')
    plt.imshow(np.transpose(npimg, (1, 2, 0)))

dataiter = iter(trainset_loader)
images, labels = dataiter.next()

imshow(torchvision.utils.make_grid(images, padding=10))

GPU

将网络或数据从 CPU 转到 GPU 上可以用 data.cuda() 或者 data.to('cuda'),不过一般都用后面这种形式,因为会在前面加一行代码

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
data.to(device)

这样就能使得没有 GPU 的情况下也需要改代码就能够跑模型了


网络结构较小的时候,CPU 和 GPU 之间进行数据传输耗时更高,这时用 CPU 更快,当网络庞大的时候,用 GPU 可以明显感觉到提速。我自己试了一个简单的回归网络,跑 200 个 epoch 在 CPU 上 2.5s ,GPU 要 6.6s

loss_function

loss_function 有些在 torch.nn.functionaltorch.nn 里面都有,但是调用起来的方法是不一样的,而且一个需要大写首字母,一个不需要。具体内容看以下代码

import torch.nn as nn
criterion = nn.CrossEntropyLoss()
loss = criterion(out, label)
loss = nn.CrossEntropyLoss()(out, label) # 或者直接这样写
import torch.nn.functional as F
loss = F.cross_entropy(out, label)

train&test

save&load

torch.max

torch.max(tensor, dim=x) 返回的是 tensor 中的最大值以及最大值的索引号,dim 参数表示取的是横向的还是竖向的最大值,0 代表每个纵向的最大值,1 代表每个横向的最大值

import torch
torch.manual_seed(1)
a = torch.rand(3, 4)
value, index = torch.max(a, dim=1)
print('{}\n{}\n{}'.format(a, value, index))
# tensor([[0.7576, 0.2793, 0.4031, 0.7347],
#         [0.0293, 0.7999, 0.3971, 0.7544],
#         [0.5695, 0.4388, 0.6387, 0.5247]])
# tensor([0.7576, 0.7999, 0.6387])
# tensor([0, 1, 2])

经常在神经网络的分类任务求准确率的时候用到这个函数,要记住的是 max 函数有两个返回值,并且也要知道 dim 代表的含义,在 torch.nn.functional.softmax(tensor, dim=x) 中的 dim 跟这里的 dim 也是有一样的含义

torch.manual_seed(x)

torch.manual_seed(x) 用来固定随机数,使得每次生成的随机数都是相同的