1、pytorch如何微调fine tuning?
在加载了预训练模型参数之后,需要finetuning 模型,可以使用不同方式finetune。
model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
param.requires_grad = False
# 替换最后的全连接层, 改为训练100类
# 新构造的模块的参数默认requires_grad为True
model.fc = nn.Linear(512, 100)
# 只优化最后的分类层
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
ignored_params = list(map(id, model.fc.parameters()))
base_params = filter(lambda p: id(p) not in ignored_params,
model.parameters())
optimizer = torch.optim.SGD([
{'params': base_params},
{'params': model.fc.parameters(), 'lr': 1e-3}
], lr=1e-2, momentum=0.9)
2、Pytorch如何使用多gpu?
3、Pytorch如何实现大部分layer?
pytorch可以实现大部分layer,这些层都继承于nn.Module。如nn.Conv2卷积层;AvgPool, Maxpool, AdaptiveAvgPool平均池化,最大池化; TransposeConv逆卷积; nn.Linear全连接层; nn.BatchNorm1d(1d,2d,3d)归一化层; nn.dropout; nn.ReLU; nn.Sequential。
net1 = nn.Sequential()
net1.add_module("conv1", nn.Conv2d(3,3,3))## add_module
net1.add_module("BatchNormalization", nn.BatchNorm2d(3))
net1.add_module('activation_layer', nn.ReLU())
net2 = nn.Sequential(nn.Conv2d(3,3,3),
nn.BatchNorm2d(3),
nn.Relu())
from collections import OrderedDict
net3 = nn.Sequential(OrderedDict([("conv1", nn.Conv2d(3,3,3)),
("BatchNormalization", nn.BatchNorm2d(3)),
("activation_layer", nn.Relu())]))
4、nn.Module与autograd的区别。
5、inplace的理解
a = torch.tensor([1.0, 3.0], requires_grad=True)
b = a + 2
print(b._version) # 0
loss = (b * b).mean()
b[0] = 1000.0
print(b._version) # 1
loss.backward()
# RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation ...
6、nn.Functional和nn.Module
7、Pytorch数据
8、自定义层的步骤。
要实现一个自定义层大致分为以下几个主要步骤:
9、nn.ModuleList和nn.Sequential的不同
nn.Sequential 里面的顺序是你想要的,而且不需要再添加一些其他处理的函数 (比如nn.functional 里面的函数,nn 与 nn.functional 有什么区别? ),那么完全可以直接用 nn.Sequential。这么做的代价就是失去了部分灵活性,毕竟不能自己去定制 forward 函数里面的内容了。一般情况下 nn.Sequential 的用法是来组成卷积块 (block),然后像拼积木一样把不同的 block 拼成整个网络,让代码更简洁,更加结构化。
10、apply-参数初始化
Pytorch中对卷积层和批归一化层权重进行初始化,也就是weight和bias,主要会用到torch的apply()函数。
def weight_init(m):
classname = m.__class__.__name__ # 得到网络层的名字,如ConvTranspose2d
if classname.find('Conv') != -1: # 使用了find函数,如果不存在返回值为-1,所以让其不等于-1
m.weight.data.normal_(0.0, 0.02)
elif classname.find('BatchNorm') != -1:
m.weight.data.normal_(1.0, 0.02)
m.bias.data.fill_(0)
model = net()
model.apply(weight_init)
11、Sequential三种实现方法
import torch.nn as nn
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
print(model)
print(model[2]) # 通过索引获取第几个层
'''运行结果为:
Sequential(
(0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(1): ReLU()
(2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
(3): ReLU()
)
Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
'''
import torch.nn as nn
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
print(model)
print(model[2]) # 通过索引获取第几个层
'''运行结果为:
Sequential(
(0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(1): ReLU()
(2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
(3): ReLU()
)
Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
'''
import torch.nn as nn
from collections import OrderedDict
model = nn.Sequential()
model.add_module("conv1",nn.Conv2d(1,20,5))
model.add_module('relu1', nn.ReLU())
model.add_module('conv2', nn.Conv2d(20,64,5))
model.add_module('relu2', nn.ReLU())
print(model)
print(model[2]) # 通过索引获取第几个层
12、计算图
计算图通常包含两种元素,一个是tensor,另一个是Function。Function指的是计算图中某个节点(node)所进行的运算,比如加减乘除卷积等等。Function内部有forward()和backward()两个方法,分别应用于正向、反向传播。
a = torch.tensor(2.0, requires_grad=True)
b = a.exp()
print(b)
# tensor(7.3891, grad_fn=)
input = torch.ones([2, 2], requires_grad=False)
w1 = torch.tensor(2.0, requires_grad=True)
w2 = torch.tensor(3.0, requires_grad=True)
w3 = torch.tensor(4.0, requires_grad=True)
l1 = input * w1
l2 = l1 + w2
l3 = l1 * w3
l4 = l2 * l3
loss = l4.mean()
print(w1.data, w1.grad, w1.grad_fn)
# tensor(2.) None None
print(l1.data, l1.grad, l1.grad_fn)
# tensor([[2., 2.],
# [2., 2.]]) None
print(loss.data, loss.grad, loss.grad_fn)
# tensor(40.) None
需要注意的是,我们给定的 w 们都是一个常数,利用了广播的机制实现和常数和矩阵的加法乘法,比如 w2 + l1,实际上我们的程序会自动把 w2 扩展成 [[3.0, 3.0], [3.0, 3.0]],和 l1 的形状一样之后,再进行加法计算,计算的导数结果实际上为 [[2.0, 2.0], [2.0, 2.0]],为了对应常数输入,所以最后 w2 的梯度返回为矩阵之和 8 。另外还有一个问题,虽然 w 开头的那些和我们的计算结果相符,但是为什么 l1,l2,l3,甚至其他的部分的求导结果都为空呢?
a = torch.ones([2, 2], requires_grad=True)
print(a.is_leaf)
# True
b = a + 2
print(b.is_leaf)
# False
# 因为 b 不是用户创建的,是通过计算生成的
# 和前边一样
# ...
loss = l4.mean()
l1.retain_grad()
l4.retain_grad()
loss.retain_grad()
loss.backward()
print(loss.grad)
# tensor(1.)
print(l4.grad)
# tensor([[0.2500, 0.2500],
# [0.2500, 0.2500]])
print(l1.grad)
# tensor([[7., 7.],
# [7., 7.]])
13、requires_grad
当我们创建一个张量 (tensor) 的时候,如果没有特殊指定的话,那么这个张量是默认是不需要求导的。我们可以通过 tensor.requires_grad 来检查一个张量是否需要求导。在张量间的计算过程中,如果在所有输入中,有一个输入需要求导,那么输出一定会需要求导;相反,只有当所有输入都不需要求导的时候,输出才会不需要 。
input = torch.randn(8, 3, 50, 100)
print(input.requires_grad)
# False
net = nn.Sequential(nn.Conv2d(3, 16, 3, 1),
nn.Conv2d(16, 32, 3, 1))
for param in net.named_parameters():
print(param[0], param[1].requires_grad)
# 0.weight True
# 0.bias True
# 1.weight True
# 1.bias True
output = net(input)
print(output.requires_grad)
# True
14、把网络参数的 requires_grad 设置为 False 会怎么样?
同样的网络在训练的过程中冻结部分网络,让这些层的参数不再更新,这在迁移学习中很有用处。
input = torch.randn(8, 3, 50, 100)
print(input.requires_grad)
# False
net = nn.Sequential(nn.Conv2d(3, 16, 3, 1),
nn.Conv2d(16, 32, 3, 1))
for param in net.named_parameters():
param[1].requires_grad = False
print(param[0], param[1].requires_grad)
# 0.weight False
# 0.bias False
# 1.weight False
# 1.bias False
output = net(input)
print(output.requires_grad)
# False
只更新FC层:
model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
param.requires_grad = False
# 用一个新的 fc 层来取代之前的全连接层
# 因为新构建的 fc 层的参数默认 requires_grad=True
model.fc = nn.Linear(512, 100)
# 只更新 fc 层的参数
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
# 通过这样,我们就冻结了 resnet 前边的所有层,
# 在训练过程中只更新最后的 fc 层中的参数。
15、反向传播的流程
loss.backward();optimizer.step() 权重更新;optimizer.zero_grad() 导数清零–必须的
class Simple(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 16, 3, 1, padding=1, bias=False)
self.conv2 = nn.Conv2d(16, 32, 3, 1, padding=1, bias=False)
self.linear = nn.Linear(32*10*10, 20, bias=False)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.linear(x.view(x.size(0), -1))
return x
# 创建一个很简单的网络:两个卷积层,一个全连接层
model = Simple()
# 为了方便观察数据变化,把所有网络参数都初始化为 0.1
for m in model.parameters():
m.data.fill_(0.1)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1.0)
model.train()
# 模拟输入8个 sample,每个的大小是 10x10,
# 值都初始化为1,让每次输出结果都固定,方便观察
images = torch.ones(8, 3, 10, 10)
targets = torch.ones(8, dtype=torch.long)
output = model(images)
print(output.shape)
# torch.Size([8, 20])
loss = criterion(output, targets)
print(model.conv1.weight.grad)
# None
loss.backward()###############################################################
print(model.conv1.weight.grad[0][0][0])
# tensor([-0.0782, -0.0842, -0.0782])
# 通过一次反向传播,计算出网络参数的导数,
# 因为篇幅原因,我们只观察一小部分结果
print(model.conv1.weight[0][0][0])
# tensor([0.1000, 0.1000, 0.1000], grad_fn=)
# 我们知道网络参数的值一开始都初始化为 0.1 的
optimizer.step()###########################################################
print(model.conv1.weight[0][0][0])
# tensor([0.1782, 0.1842, 0.1782], grad_fn=)
# 回想刚才我们设置 learning rate 为 1,这样,
# 更新后的结果,正好是 (原始权重 - 求导结果) !
optimizer.zero_grad()############每次更新完权重之后,我们记得要把导数清零啊,
# 不然下次会得到一个和上次计算一起累加的结果。
print(model.conv1.weight.grad[0][0][0])
# tensor([0., 0., 0.])
# 每次更新完权重之后,我们记得要把导数清零啊,
# 不然下次会得到一个和上次计算一起累加的结果。
# 当然,zero_grad() 的位置,可以放到前边去,
# 只要保证在计算导数前,参数的导数是清零的就好。
16、detach
多说无益,我们来看个例子吧:
这个导数计算的结果明显是错的,但没有任何提醒,之后再 Debug 会非常痛苦。所以,建议大家都用 tensor.detach()。
a = torch.tensor([7., 0, 0], requires_grad=True)
b = a + 2
print(b)
# tensor([9., 2., 2.], grad_fn=)
loss = torch.mean(b * b)
b_ = b.detach()
b_.zero_()
print(b)
# tensor([0., 0., 0.], grad_fn=)
# 储存空间共享,修改 b_ , b 的值也变了
loss.backward()
# RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
这个例子中,b 是用来计算 loss 的一个变量,我们在计算完 loss 之后,进行反向传播之前,修改 b 的值。这么做会导致相关的导数的计算结果错误,因为我们在计算导数的过程中还会用到 b 的值,但是它已经变了(和正向传播过程中的值不一样了)。在这种情况下,PyTorch 选择报错来提醒我们。但是,如果我们使用 tensor.data 的时候,结果是这样的:
a = torch.tensor([7., 0, 0], requires_grad=True)
b = a + 2
print(b)
# tensor([9., 2., 2.], grad_fn=)
loss = torch.mean(b * b)
b_ = b.data
b_.zero_()
print(b)
# tensor([0., 0., 0.], grad_fn=)
loss.backward()
print(a.grad)
# tensor([0., 0., 0.])
# 其实正确的结果应该是:
# tensor([6.0000, 1.3333, 1.3333])
17、tensor-numpy
使用GPU还有一个点,在我们想要GPUtensor转换成Numpy变量的时候,需要先将tensor转换到CPU中去,因为Numpy是CPU-only。其次,如果tensor需要求导的话,还需要加一步detach,再转成Numpy。
18、tensor.item()
x = torch.randn(1, requires_grad=True, device='cuda')
print(x)
# tensor([-0.4717], device='cuda:0', requires_grad=True)
y = x.item()
print(y, type(y))
# -0.4717346727848053
x = torch.randn([2, 2])
y = x.tolist()
print(y)
# [[-1.3069953918457031, -0.2710231840610504], [-1.26217520236969, 0.5559719800949097]]
19、torch.backends.cudnn.benchmark=True
设置 torch.backends.cudnn.benchmark=True 将会让程序在开始时花费一点额外时间,为整个网络的**每个卷积层搜索最适合它的卷积实现算法,进而实现网络的加速。**适用场景是网络结构固定(不是动态变化的),网络的输入形状(包括 batch size,图片大小,输入的通道)是不变的,其实也就是一般情况下都比较适用。反之,如果卷积层的设置一直变化,将会导致程序不停地做优化,反而会耗费更多的时间。
20、静态图和动态图
# 这是一个关于 PyTorch 是动态图的例子:
a = torch.tensor([3.0, 1.0], requires_grad=True)
b = a * a
loss = b.mean()
loss.backward() # 正常
loss.backward() # RuntimeError
# 第二次:从头再来一遍
a = torch.tensor([3.0, 1.0], requires_grad=True)
b = a * a
loss = b.mean()
loss.backward() # 正常