Pytorch 基础

导言:之前将所有东西都揉捏在一个文件中显得很冗长,故分类将这些知识点做整理。

pytorch 给了我们很多的库,这些库都有不同的功能,在使用时需要落实:

  • torch
  • torch.autograd
  • torchvision
  • torch.utils.data

变量以及其操作

  • tensor
  • variable

variable可以装载tensor的数据,并且可以拥有自动求导机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
from torch.autograd import Variable
import numpy as mp

tensor = torch.FloatTensor([[1,2],[3,4]]) # 注意model中默认所有数据必是float类型
variable = Variable(tensor,requires_grad = True)

t_out = torch.mean(tensor*tensor)
v_out = torch.mean(variable*variable)

v_out.backward()

# d(v_out)/d(variavble) = 1/4 * 2 * variable
print(variable.grad)

Variable和Parameter的区别

Parameter是torch.autograd.Variable的一个字类,常被用于Module的参数。例如权重和偏置。

Parameters和Modules一起使用的时候会有一些特殊的属性。parameters赋值给Module的属性的时候,它会被自动加到Module的参数列表中,即会出现在Parameter()迭代器中。将Varaible赋给Module的时候没有这样的属性。这可以在nn.Module的实现中详细看一下。这样做是为了保存模型的时候只保存权重偏置参数,不保存节点值。所以复写Variable加以区分。

另外一个不同是parameter不能设置volatile,而且require_grad默认设置为true。Varaible默认设置为False.

parameter.data 得到tensor数据
parameter.requires_grad 默认为 True, BP过程中会求导
Parameter一般是在Modules中作为权重和偏置,自动加入参数列表,可以进行保存恢复。和Variable具有相同的运算。

我们可以这样简单区分,在计算图中,数据(包括输入数据和计算过程中产生的feature map等)时 variable 类型,该类型不会被保存到模型中。 网络的权重是 parameter 类型,在计算过程中会被更新,将会被保存到模型中。

优化函数

优化函数可以只传入模型的部分参数:

1
optimizer = torch.optim.Adam(model.Classifier.parameters(),lr = lr)

仅仅传入最后全连接层的参数,存疑这样是否就可以不用设置冻结之前层?
《pytorch-cv》是既设置了冻结,又只传入了部分参数

显存的节约

在测试或验证集的预测中,不要带有梯度可以省去一大步分的cuda,GPU显存。可以一定程度上缓解CUDA.memery的问题。

1
2
3
4
5
6
if phrase == "valid":
with torch.no_grad():
x,y = Variable(x).cuda(),Variable(y).cuda()
y_pred = model(x)
_,y_pred_class = torch.max(y_pred,1)
loss = loss_f(y_pred,y)

模型中参数理解

1
2
3
4
5
6
7
8
9
10
11
12
import torchvision.models as models

model = models.vgg16(pretrained =True)

model_modules = model._modules

feature_layers = model._modules["feature"]

conv1_2 = feature_layers[2] # layer 也可以通过下标访问

for layer in feature_layers:
pass

models.vgg16(pretrained =True) pretrained =True表示不下载模型

model:包含了所有参数

model_modules: OrderedDict类型,字典类的派生,键值为模型中定义的网络块(Sequential定义的名称就为定义的名字,其值为Sequential类型包含了定义的所有层)

feature_layers: Sequential类型包含了各种层,也可以直接model.feature访问,因为其feature相当于是model的一个公变量

layer: 层的定义类型,conv层是conv层类型,pool层是pool层类型,包含各自的参数,也可以通过 feature_layers 下标访问。其中比较重要的有:

  1. weight : parameter类型
  2. bias : parameter类型

样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#全冻
for parma in model.parameters():
parma.require_grad = False
#只部分冻
list_freeze_idx = [0,1,3,5,7]
for num,param in enumerate(model.parameters(),0):
if num in list_freeze_idx:
param.requires_grad = False
model.Classifier = torch.nn.Sequential(
torch.nn.Linear()
torch.nn.ReLU()
torch.nn.Dropout(p=0.5)
torch.nn.Linear()
torch.nn.ReLU()
torch.nn.Dropout(p=0.5)
torch.nn.Linear()
)

Parameters():

凡是关于参数的,都使用Parameters()来进行操作

model.parameters(): 返回的是所有参数的生成器,每个item是parameter类型,包含Tensor,requires_grad等类型。无法下标访问 注意的是requires_grad 是有s的

parame 即生成器的返回变量,拥有data参数,可以访问到该的数据,有grad,shape,等。

新建的Sequential是默认可以更新的。

model.Classifier 可以直接操作整个Squential

从PyTorch中取出数据进行numpy操作:

如果对象是Variable类型,取出其数据需要注意以下几点:

  • 如果是有grad_requires = True的,那么需要with torch.no_grad()申明
  • 如果不使用with torch.no_grad,也可以使用x.cpu().detach().numpy()来获得
  • 如果在gpu上的数据,需要使用data_x.cpu放在cpu上
  • 数据得到后Tensor 2 numpy只需要执行 tesnor.numpy即可
1
2
3
4
5
6
7
x,y = next(iter(dataloader["test"]))
with torch.no_grad():
x, y = Variable(x).cuda(), Variable(y).cuda()
pre = self.model.forward(x)
plt.plot(x.cpu().numpy(),pre.cpu().numpy(),'x')
plt.plot(x.cpu().detach().numpy(),y.cpu().detach().numpy(),'o')
plt.show()

一个拟合2次曲线的例子:

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
37
def train(self,dataloader):
self.model.cuda()
opt = torch.optim.SGD(self.model.parameters(),lr=0.001)
loss_f = torch.nn.MSELoss()
for epoch in range(self.EPOCH):
for phase in ["train", "test"]:
loss_epoch = 0.0
acc_epoch = 0.0
for batch_n, data in enumerate(dataloader[phase],1):
x,y = data
x,y = Variable(x).cuda(),Variable(y).cuda()
pre = self.model.forward(x)
opt.zero_grad()
loss = loss_f(pre,y)
if phase == "train":
loss.backward()
opt.step()
loss_epoch += loss
if phase == "train" and batch_n % 10 ==0:
print("+" * 30)
loss_batch_ave = loss_epoch.__float__()/(batch_n.__float__())
print("loss = %.2f"%loss_batch_ave)
if (phase == "train"):
print("+"*30)
print("train")
print("epoch:{}/{} loss = {}".format(epoch + 1,self.EPOCH,loss_epoch.__float__()/batch_n.__float__()))
if(phase == "test"):
print("+"*30)
print("Test:")
print("epoch:{}/{} loss = {}".format(epoch + 1, self.EPOCH, loss_epoch.__float__() / batch_n.__float__()))
x,y = next(iter(dataloader["test"]))
with torch.no_grad():
x, y = Variable(x).cuda(), Variable(y).cuda()
pre = self.model.forward(x)
plt.plot(x.cpu().numpy(),pre.cpu().detach().numpy(),'x')
plt.plot(x.cpu().detach().numpy(),y.cpu().detach().numpy(),'o')
plt.show()

Pytorch 中的 view()

把原先tensor中的数据按照行优先的顺序排成一个一维的数据(这里应该是因为要求地址是连续存储的),然后按照参数组合成其他维度的tensor。比如说是不管你原先的数据是[[[1,2,3],[4,5,6]]]还是[1,2,3,4,5,6],因为它们排成一维向量都是6个元素,所以只要view后面的参数一致,得到的结果都是一样的。

总之一句话: 行优先的视图排列

1
2
3
4
5
6
t = torch.randn(2,1,3,4)
print(t)
y = t.view(4,6)
y[0] = 1
print(y)
print(t)

视图的操作可以对原来的数据进行抽象的修改,从不同视图来修改数据。view() 得到的数据不会分配内存。

tensorview()操作依赖于内存是整块的,如果当前的tensor并不是占用一整块内存,而是由不同的数据块组成,那么view()将报错。

而为了使得view能操作这些非同块内存的数据,Pytorch提供了一个contiguous函数来将分散的数据块整合成一块。
给一个例子方便理解:

1
2
3
4
5
6
7
8
9
import torch
x = torch.ones(5, 10)
x.is_contiguous() # True
x.transpose(0, 1).is_contiguous() # False
x.permute(1,0).is_contiguous() # False

x.permute(1,0).view(10,5) # 报错!

x.transpose(0, 1).contiguous().is_contiguous() # True

各个框架之间conv2d的区别:

这里也可视化了dilated conv的作用

疑问的产生处是这里,在之前没有遇见过conv2d()中的kernel_sizestride是元组的情况

1
2
3
4
5
6
7
torch.nn.Conv2d(
out_channels,
out_channels,
(kernel_size[0], 1),
(stride, 1),
padding,
)

参考pytorch中文文档的解释:

1
class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)

  • bigotimes: 表示二维的相关系数计算 stride: 控制相关系数的计算步长
  • dilation: 用于控制内核点之间的距离,详细描述在这里
  • groups: 控制输入和输出之间的连接: group = 1,输出是所有的输入的卷积;group=2,此时相当于有并排的两个卷积层,每个卷积层计算输入通道的一半,并且产生的输出是输出通道的一半,随后将这两个输出连接起来。
  • 参数kernel_sizestride,paddingdilation也可以是一个int的数据,此时卷积heightwidth值相同;也可以是一个tuple数组,tuple的第一维度表示height的数值,tuple的第二维度表示width的数值
1
2
3
4
5
6
7
8
#with square kernels and equal_stride
m = nn.Conv2d(16,33,3,stride = 2)

# non-square kernels and unequal stride and with padding
m = nn.Conv2d(16,33,(3,5),stride = (2,1),padding = (4,2))

# non-square kernels and unequal stride and with padding and dilation
m = nn.Conv2d(16,33,(3,5),stride = (2,1), padding = (4,2), dilation = (3,1))

如果Conv传入的是元组,即两个方向上的参数,而不是之前默认的正方形的核、或者移动了。