导言:之前将所有东西都揉捏在一个文件中显得很冗长,故分类将这些知识点做整理。
库
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
14import 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
6if 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 | import torchvision.models as models |
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
下标访问。其中比较重要的有:
- weight : parameter类型
- bias : parameter类型
样例:
1 | #全冻 |
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 | x,y = next(iter(dataloader["test"])) |
一个拟合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
37def 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
6t = torch.randn(2,1,3,4)
print(t)
y = t.view(4,6)
y[0] = 1
print(y)
print(t)
视图的操作可以对原来的数据进行抽象的修改,从不同视图来修改数据。view()
得到的数据不会分配内存。
而tensor
的view()
操作依赖于内存是整块的,如果当前的tensor并不是占用一整块内存,而是由不同的数据块组成,那么view()
将报错。
而为了使得view
能操作这些非同块内存的数据,Pytorch
提供了一个contiguous
函数来将分散的数据块整合成一块。
给一个例子方便理解:1
2
3
4
5
6
7
8
9import 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_size
、stride
是元组的情况
1 | torch.nn.Conv2d( |
参考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_size
,stride
,padding
,dilation
也可以是一个int的数据,此时卷积height
和width
值相同;也可以是一个tuple
数组,tuple
的第一维度表示height
的数值,tuple
的第二维度表示width
的数值
1 | #with square kernels and equal_stride |
如果Conv传入的是元组,即两个方向上的参数,而不是之前默认的正方形的核、或者移动了。