文章目录
【torch.xxx】
torch.addmm() / torch.addmm_()
来自 Pytorch里addmm()和addmm_()的用法详解
addmm_()
的操作和 addmm()
函数功能相同,区别就是addmm_()有inplace的操作,也就是在原对象基础上进行修改,即把改变之后的变量再赋给原来的变量。
def addmm(self, beta=1, mat, alpha=1, mat1, mat2, out=None):
...
torch.clamp() / torch.clamp_()
torch.clamp(input, min, max, out=None) → Tensor
| min, if x_i < min
y_i = | x_i, if min <= x_i <= max
| max, if x_i > max
demo
a=torch.randint(low=0,high=10,size=(10,1))
print(a)
a=torch.clamp(a,3,9)
print(a)
这相当于是个框,把框外的拉回来,框内不动
使用场景
但凡是除数,未免是0,都用 torch.clamp(a ,min=1e-6),将除数设置为不小于1e-6的值。
pytorch clamp 与clamp_ ,有下划线的表示修改并赋给自身,无下划线的表示需要返回处理后的值,比如:
h = k.clamp(min=0) #将结果存入h,k保留原值
k.clamp_(min=0) # 将结果存入k
torch.eq() / torch.ne()
torch.eq(input, other, *, out=None) → Tensor
- input (Tensor) – the tensor to compare
- other (Tensor or float) – the tensor or value to compare
torch.ne(input, other, *, out=None) → Tensor
eg1
torch.eq(torch.tensor([[1, 2], [3, 4]]), torch.tensor([[1, 1], [4, 4]]))
tensor([[ True, False],
[False, True]])
eg2
torch.eq(torch.tensor([[1, 2], [3, 4]]), 1)
tensor([[ True, False],
[False, False]])
torch.manual_seed()
torch.manual_seed() #为CPU设置种子用于生成随机数,以使得结果是确定的
torch.cuda.manual_seed() #为当前GPU设置随机种子;
torch.cuda.manual_seed_all() #为所有的GPU设置种子。
torch.unique()
torch.unique(input, sorted=True, return_inverse=False, return_counts=False, dim=None) → Tuple[Tensor, Tensor, Tensor]
Returns the unique elements of the input tensor.
output = torch.unique(torch.tensor([1, 3, 2, 3], dtype=torch.long))
print(output)
output
tensor([1, 2, 3])
eg2
output, inverse_indices = torch.unique(torch.tensor([[1, 3], [2, 3]], dtype=torch.long), sorted=True, return_inverse=True)
print(output)
print(inverse_indices)
output
tensor([1, 2, 3])
tensor([[0, 2],
[1, 2]])
torch.save() / torch.load()
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
device = torch.device("cuda:2") # run on GPU
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = nn.Conv2d(3,6,5)
self.pool = nn.MaxPool2d(2)
self.conv2 = nn.Conv2d(6,16,5)
self.fc1 = nn.Linear(16*5*5,120)
self.fc2 = nn.Linear(120,84)
self.fc3 = nn.Linear(84,10)
def forward(self,x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(x.size(0),-1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
net.to(device)
optimizer = optim.SGD(net.parameters(),lr = 0.001,momentum=0.9)
print("Model's state_dict")
for para_tensor in net.state_dict():
print(para_tensor,'\t',net.state_dict()[para_tensor].size())
"""
conv1.weight torch.Size([6, 3, 5, 5])
conv1.bias torch.Size([6])
conv2.weight torch.Size([16, 6, 5, 5])
conv2.bias torch.Size([16])
fc1.weight torch.Size([120, 400])
fc1.bias torch.Size([120])
fc2.weight torch.Size([84, 120])
fc2.bias torch.Size([84])
fc3.weight torch.Size([10, 84])
fc3.bias torch.Size([10])
Process finished with exit code 0
"""
# print(net.fc1.out_features) # can see the input size and output size
# print(net.fc1.in_features) # can see the input size and output size
# **********************************************************************
# print("Optimizer's state dict:")
#
# for var_name in optimizer.state_dict():
# print(var_name,"\t",optimizer.state_dict()[var_name])
"""
state {}
param_groups [{'lr': 0.001, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [140660776619680, 140660585225240, 140660585225312, 140660585225384, 140660585225456, 140660585225528, 140660585225600, 140660585225672, 140660585225744, 140660585225816]}]
"""
# save and load: method 1 pth
# save
torch.save(net.state_dict(),"model.pth")
# load
net.load_state_dict(torch.load("model.pth"))
net.eval() # to set dropout and BN as inference manner
# save and load: method 2 pth
# save the total model
torch.save(net,"model2.pth")
torch.load("model2.pth")
net.eval()
# save and load: method 3 tar
# save the checkpoint
# for example
epoch = 10
loss = 0.5e-5
torch.save({
'epoch':epoch,
'model_state_dict':net.state_dict(),
'optimizer_state_dict':optimizer.state_dict(),
'loss':loss
},"model3.tar")
checkpoint = torch.load("model3.tar")
net.load_state_dict((checkpoint['model_state_dict']))
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
# or
# net.train()
torch.squeeze( )
去掉向量中维度为 1 的维度,即只有维度为 1 的那一维才能去掉。
squeeze(input, dim=None,out=None) -> Tensor
eg:去掉 batch-size = 1
torch.view() / torch.permute() / torch. transpose() / torch.reshape()
transpose是交换两个维度,permute()是交换多个维度。
permute().contiguous().view()相当于reshape
transpose()和view()实现方式不一样,shape 相同但内容不同
view()只能对连续的张量进行操作,并且返回的张量仍然是连续的。
transpose()既可以在连续张量上操作,也可以在非连续张量上操作。与view()不同,返回的张量可能不再是连续的。
来自
【torch.cuda.xxx】
torch.cuda.synchronize()
来自
测试模型运行时间
torch.cuda.synchronize()
start = time.time()
result = model(input)
torch.cuda.synchronize()
end = time.time()
基本等价于
start = time.time()
result = model(input)
print(result)
end = time.time()
不等同
start = time.time()
result = model(input)
end = time.time()
【torch.distributed.xxx】
来自
- [深度学习] 分布式Pytorch介绍(三)
- PyTorch多卡分布式训练DistributedDataParallel 使用方法
- Pytorch 分布式训练
- WRITING DISTRIBUTED APPLICATIONS WITH PYTORCH
优势(相较于 DataParallel)
- 每个进程对应一个独立的训练过程,且只对梯度等少量数据进行信息交换。
在每次迭代中,每个进程具有自己的 optimizer ,并独立完成所有的优化步骤,进程内与一般的训练无异。
在各进程梯度计算完成之后,各进程需要将梯度进行汇总平均,然后再由 rank=0 的进程,将其 broadcast 到所有进程。之后,各进程用该梯度来更新参数。由于各进程中的模型,初始参数一致 (初始时刻进行一次 broadcast),而每次用于更新参数的梯度也一致,因此,各进程的模型参数始终保持一致。
而在 DataParallel 中,全程维护一个 optimizer,对各 GPU 上梯度进行求和,而在主 GPU 进行参数更新,之后再将模型参数 broadcast 到其他 GPU。
相较于 DataParallel,torch.distributed 传输的数据量更少,因此速度更快,效率更高。
- 每个进程包含独立的解释器和 GIL。
由于每个进程拥有独立的解释器和 GIL,消除了来自单个 Python 进程中的多个执行线程,模型副本或 GPU 的额外解释器开销和 GIL-thrashing ,因此可以减少解释器和 GIL 使用冲突。这对于严重依赖 Python runtime 的 models 而言,比如说包含 RNN 层或大量小组件的 models 而言,这尤为重要。
(GIL 全局解释器锁(Global Interpreter Lock)是Python用于同步线程的工具,使得任何时刻仅有一个线程在执行)
1)单机多卡,DataParallel(DP,最常用,最简单,是单进程,多线程)—— single process multi-gpus
2)单机多卡,DistributedDataParallel(DDP,较高级,多进程)——multi-processes multi-gpus
3)多机多卡,DistributedDataParallel(最高级)
1)DP
- 尽管 DataLoader 可以指定 num_worker,增加负责加载数据的线程数量,但是线程的资源受限于父进程,且由于python的GIL机制,不能利用好多核的并行优势
- 模型在 gpu 群组中进行初始化与广播过程依赖单一进程的串行操作
- DP模式相当于将多个GPU卡合并为一个卡进行训练
2)DDP
- 一个进程一个GPU(当然可以让每个进程控制多个GPU,但这显然比每个进程有一个GPU要慢)
- 充分利用多核并行的优势加载数据
- 模型在 gpu 群组中进行初始化的过程由各自的进程负责调度
- 代码可以无缝切换单机多卡与多机多卡训练,因为此时单机单卡成为了单机多卡/多机多卡并行下的一个特例
- GPU可以都在同一个节点上,也可以分布在多个节点上。每个进程都执行相同的任务,并且每个进程都与所有其他进程通信。进程或者说GPU之间只传递梯度,这样网络通信就不再是瓶颈。
all reduce(即汇总不同 GPU 计算所得的梯度,并同步计算结果)
【torch.nn.xxx】
torch.nn.MaxPool2d
PyTorch中的MaxPool(最大池化)有一个属性:ceil_mode,默认为False(地板模式),为True时是天花板模式。
【torchvision.xxx】
来自:
torchvision 是 PyTorch 中专门用来处理图像的库。这个包中有四个大类。
-
torchvision.datasets
- MNISTCOCO
- Captions
- Detection
- LSUN
- ImageFolder
- Imagenet-12
- CIFAR
- STL10
- SVHN
- PhotoTour
…
test_data = torchvision.datasets.CIFAR10( root='../BigData', transform=torchvision.transforms.ToTensor(), train=False, download=True )
-
torchvision.models
- AlexNet
- VGG
- ResNet
- SqueezeNet
- DenseNet
…
import torchvision.models as models resnet18 = models.resnet18() alexnet = models.alexnet() squeezenet = models.squeezenet1_0() densenet = models.densenet_161()
-
torchvision.transforms
transform = transforms.Compose([ transforms.Resize(96), # 缩放到 96 * 96 大小 transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 归一化 ])
-
torchvision.utils
torchvision.transforms 方法整理如下
-
裁剪(Crop)
transforms.CenterCrop(size,padding,pad_if_needed,fill,padding_mode)
中心裁剪transforms.RandomCrop(size)
随机裁剪transforms.RandomResizedCrop(size, scale, ratio, interpolation)
随机长宽比裁剪transforms.FiveCrop(size)
上下左右中心裁剪transforms.TenCrop(size, vertical_flip)
上下左右中心裁剪后翻转
-
翻转和旋转(Flip and Rotation)
transforms.RandomHorizontalFlip(p=0.5)
依概率p水平翻转transforms.RandomVerticalFlip(p=0.5)
依概率p垂直翻转transforms.RandomRotation(degrees, resample=False, expand=False, center=None)
随机旋转
-
图像变换(resize)
transforms.Resize(size, interpolation=2)
transforms.Normalize(mean, std)
标准化:transforms.ToTensor()
转为tensor,并归一化至[0-1]:transforms.Pad(padding, fill=0, padding_mode='constant')
填充transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0)
修改亮度、对比度和饱和度:transforms.Grayscale(num_output_channels=1)
转灰度图:transforms.LinearTransformation(transformation_matrix)
线性变换transforms.RandomAffine(degrees, translate=None, scale=None, shear=None, resample=False, fillcolor=0)
仿射变换transforms.RandomGrayscale(p=0.1)
依概率p转为灰度图transforms.ToPILImage(mode=None)
将数据转换为PILImagetransforms.Lambda(lambd)
,Apply a user-defined lambda as a transform
-
对 transforms 操作,使数据增强更灵活
transforms.RandomChoice(transforms)
, 从给定的一系列transforms中选一个进行操作transforms.RandomApply(transforms, p=0.5)
,给一个transform加上概率,依概率进行操作transforms.RandomOrder(transforms)
,将transforms中的操作随机打乱
较新版本的 torchvision 包含
from torchvision.transforms import autoaugment,transforms
transforms.Compose([
autoaugment.AutoAugment(xxx),
autoaugment.RandAugment(xxx),
transforms.RandomErasing(xxx),
])
tensor 转图片
from PIL import Image
import torchvision.transforms as transforms
image = imgdata.cpu().clone()
image = image.squeeze(0)
unloader = transforms.ToPILImage()
unloader(image) # 转换为PIL image
【pytorch to caffe】
https://github.com/xxradon/PytorchToCaffe/blob/master/pytorch_to_caffe.py
【Pinned Memory】
来自:
虚拟内存技术定义了一个连续的虚拟地址空间,并把内存扩展到硬盘空间。它使得应用程序认为自己拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
为何使用虚拟内存技术?
电脑中运行的所有程序,都需经过内存来执行,若执行的程序占用内存很大或很多,则会导致内存消耗殆尽。为了解决该问题,Windows 运用了虚拟内存技术,即匀出一部分硬盘空间来充当内存使用。若计算机运行程序或操作所需要的 RAM(随机存储器)不足时,则 Windows 会用这部分虚拟存储器进行补偿,以缓解内存的紧张。
物理内存 VS 虚拟内存
- 作用不同:物理内存是与 CPU 进行沟通的桥梁,而虚拟内存是在物理内存不足时,起到辅助补偿的作用。
- 主体不同:物理内存是指通过插在主板内存槽上的物理内存条而获得的内存空间,虚拟内存是指根据系统需要从硬盘匀出来的用来充当内存的那部分空间。
- 速度不同:物理内存是内存条的读写速度,虚拟内存是硬盘的读写速度,物理内存的处理速度是虚拟内存的30倍以上(即使是固态硬盘,速度也比内存条差很多)。
对于 CUDA 架构而言,主机端的内存可分为两种:
1)可分页内存(Pageable Memory)。
2)页锁定内存(Page-locked Memory),或称固定内存(Pinned Memory)。
对于页锁定内存,操作系统不会对其进行分页和交换操作,一定是存储在物理内存,不会存储在虚拟内存,因此,GPU 可直接通过 DMA 机制,在主机和 GPU 之间快速复制数据。而对于可分页内存,CUDA 驱动会首先将 Pageable Memory 的数据拷贝到临时的 Pinned Memory,然后再通过 DMA 机制进行数据传输。如下图所示:
C++ 中,通过操作系统接口 malloc() 在主机上分配可分页内存,通过 CUDA 函数 cudaHostAlloc() 在主机上分配页锁定内存。
torch.utils.data.DataLoader() 中的 pin_memory 参数同理
【Parameter 和 buffer】
来自:Pytorch模型中的parameter与buffer
If you have parameters in your model, which should be saved and restored in the state_dict, but not trained by the optimizer, you should register them as buffers.Buffers won’t be returned in model.parameters(), so that the optimizer won’t have a change to update them.
模型中需要保存下来的参数包括两种:
- 一种是反向传播需要被 optimizer 更新的,称之为 parameter
- 一种是反向传播不需要被 optimizer 更新,称之为 buffer
模型保存的是 state_dict 返回的 OrderDict
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
buffer = torch.randn(2, 3) # tensor
self.register_buffer('my_buffer', buffer)
self.param = nn.Parameter(torch.randn(3, 3)) # 模型的成员变量
def forward(self, x):
# 可以通过 self.param 和 self.my_buffer 访问
pass
model = MyModel()
for param in model.parameters():
print(param)
print("----------------")
for buffer in model.buffers():
print(buffer)
print("----------------")
print(model.state_dict())
【hook】
来自 PyTorch使用记录
我们可以简单理解一下,hook是用来获得网络中间的一些值的,主要有下面的两种
(1)register hook
register_hook,是针对Variable对象的。
The hook will be called every time after forward() has computed an output. It should have the following signature:
hook(grad) -> Tensor or None
(2)hook module
除了上面可以 hook 变量外,我们还可以 hook 住 nn.Module 这个对象
The hook will be called every time after forward() has computed an output. It should have the following signature:
hook(module, input, output) -> None
先看看 register hook 的例子
import torch
from torch.autograd import Variable
import torch.nn as nn
"register_hook"
x = Variable(torch.randn(2, 1), requires_grad=True)
y = x + 2
z = torch.mean(torch.pow(y, 2))
z.backward()
print(x.grad.data)
print(y.grad) # auto release
output
tensor([[2.8412],
[4.0042]])
None
可以看到,梯度被自动释放,查看不到其值为多少,下面我们借助 hook 来观察其梯度
def variable_hook(grad):
print("the gradient of y\n", grad)
x = Variable(torch.randn(2, 1), requires_grad=True)
y = x + 2
hook_handle = y.register_hook(variable_hook)
z = torch.mean(torch.pow(y, 2))
z.backward()
print("the gradient of x\n", x.grad.data)
print(y.grad) # auto release
hook_handle.remove() # 记得删除掉 hook
output
the gradient of y
tensor([[3.0885],
[0.7618]])
the gradient of x
tensor([[3.0885],
[0.7618]])
None
再看看 hook module 的例子
"hook module"
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.l1 = nn.Linear(2, 2, bias=False)
self.l2 = nn.Linear(2, 2, bias=False)
def forward(self, x):
out = self.l2(self.l1(x))
return out
model = NN()
print(model)
for num, (name, param) in enumerate(model.named_parameters()):
print(name, param)
model.l1.weight.data = torch.FloatTensor([[1, 2], [3, 4]]) # 初始化 l1 权重
model.l2.weight.data = torch.FloatTensor([[5, 6], [7, 8]]) # 初始化 l2 权重
for num, (name, param) in enumerate(model.named_parameters()):
print(name, param)
output
NN(
(l1): Linear(in_features=2, out_features=2, bias=False)
(l2): Linear(in_features=2, out_features=2, bias=False)
)
l1.weight Parameter containing:
tensor([[ 0.1781, 0.3915],
[-0.3135, 0.5730]], requires_grad=True)
l2.weight Parameter containing:
tensor([[ 0.0094, -0.6729],
[-0.0336, -0.4310]], requires_grad=True)
l1.weight Parameter containing:
tensor([[1., 2.],
[3., 4.]], requires_grad=True)
l2.weight Parameter containing:
tensor([[5., 6.],
[7., 8.]], requires_grad=True)
activation = {}
def get_linear(name):
def hook(module, input, output):
print("module:\n", module)
print("input:\n", input)
print("output:\n", output)
activation[name] = output.detach()
print("="*30)
return hook
hook_handle1 = model.l1.register_forward_hook(get_linear("l1"))
hook_handle2 = model.l2.register_forward_hook(get_linear("l2"))
input_data = Variable(torch.FloatTensor([[1, 2]]))
print("input data:\n", input_data)
out = model(input_data)
print("out data:\n", out)
input data:
tensor([[1., 2.]])
module:
Linear(in_features=2, out_features=2, bias=False)
input:
(tensor([[1., 2.]]),)
output:
tensor([[ 5., 11.]], grad_fn=<MmBackward0>)
==============================
module:
Linear(in_features=2, out_features=2, bias=False)
input:
(tensor([[ 5., 11.]], grad_fn=<MmBackward0>),)
output:
tensor([[ 91., 123.]], grad_fn=<MmBackward0>)
==============================
out data:
tensor([[ 91., 123.]], grad_fn=<MmBackward0>)
print(activation)
output
{'l1': tensor([[ 5., 11.]]), 'l2': tensor([[ 91., 123.]])}
记得 remove 掉 hook
hook_handle1.remove()
hook_handle2.remove()
常用代码段
参考学习来自 [深度学习框架]PyTorch常用代码段
PyTorch可复现
学习摘抄来自
(1)随机种子设置
pytorch
torch.manual_seed(seed) # 为CPU设置随机种子
torch.cuda.manual_seed(seed) # 为当前GPU设置随机种子
torch.cuda.manual_seed_all(seed) # 为所有GPU设置随机种子
python & numpy
import random
import numpy as np
random.seed(seed)
np.random.seed(seed)
(2)多线程
注意,如果dataloader采用了多线程(num_workers > 1), 那么由于读取数据的顺序不同,最终运行结果也会有差异。也就是说,改变num_workers参数,也会对实验结果产生影响。目前暂时没有发现解决这个问题的方法,但是只要固定num_workers数目(线程数)不变,基本上也能够重复实验结果。
GLOBAL_SEED = 1
def set_seed(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
GLOBAL_WORKER_ID = None
def worker_init_fn(worker_id):
global GLOBAL_WORKER_ID
GLOBAL_WORKER_ID = worker_id
set_seed(GLOBAL_SEED + worker_id)
dataloader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=2, worker_init_fn=worker_init_fn)
从只包含一个元素的张量中提取值
value = torch.rand(1).item()
打乱顺序
tensor = tensor[torch.randperm(tensor.size(0))] # 打乱第一个维度
水平翻转
# pytorch不支持tensor[::-1]这样的负步长操作,水平翻转可以通过张量索引实现
# 假设张量的维度为[N, D, H, W].
tensor = tensor[:,:,:,torch.arange(tensor.size(3) - 1, -1, -1).long()]
复制
# Operation | New/Shared memory | Still in computation graph |
tensor.clone() # | New | Yes |
tensor.detach() # | Shared | No |
tensor.detach.clone()() # | New | No |
将整数标签转为one-hot编码
# pytorch的标记默认从0开始
tensor = torch.tensor([0, 2, 1, 3])
N = tensor.size(0)
num_classes = 4
one_hot = torch.zeros(N, num_classes).long()
one_hot.scatter_(dim=1, index=torch.unsqueeze(tensor, dim=1), src=torch.ones(N, num_classes).long())
得到非零元素
torch.nonzero(tensor) # index of non-zero elements
torch.nonzero(tensor==0) # index of zero elements
torch.nonzero(tensor).size(0) # number of non-zero elements
torch.nonzero(tensor == 0).size(0) # number of zero elements
判断两个张量是否相等
torch.allclose(tensor1, tensor2) # float tensor
torch.equal(tensor1, tensor2) # int tensor
计算两组数据之间两两欧式距离
dist = torch.sqrt(torch.sum((X1[:,None,:] - X2) ** 2, dim=2))
将在 GPU 保存的模型加载到 CPU
model.load_state_dict(torch.load('model.pth', map_location='cpu'))
梯度裁减
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=20)
得到当前学习率
# If there is one global learning rate (which is the common case).
lr = next(iter(optimizer.param_groups))['lr']
# If there are multiple learning rates for different layers.
all_lr = []
for param_group in optimizer.param_groups:
all_lr.append(param_group['lr'])
另一种方法,在一个batch训练代码里,当前的 lr是 optimizer.param_groups[0]['lr']
优化器链式更新
从1.4版本开始,torch.optim.lr_scheduler 支持链式更新(chaining),即用户可以定义两个 schedulers,并交替在训练中使用。
import torch
from torch.optim import SGD
from torch.optim.lr_scheduler import ExponentialLR, StepLR
model = [torch.nn.Parameter(torch.randn(2, 2, requires_grad=True))]
optimizer = SGD(model, 0.1)
scheduler1 = ExponentialLR(optimizer, gamma=0.9)
scheduler2 = StepLR(optimizer, step_size=3, gamma=0.1)
for epoch in range(4):
print(epoch, scheduler2.get_last_lr()[0])
optimizer.step()
scheduler1.step()
scheduler2.step()
微调 FC 层
model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
param.requires_grad = False
model.fc = nn.Linear(512, 100) # Replace the last fc layer
optimizer = torch.optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9, weight_decay=1e-4)
以较大学习率微调全连接层,较小学习率微调卷积层
model = torchvision.models.resnet18(pretrained=True)
finetuned_parameters = list(map(id, model.fc.parameters()))
conv_parameters = (p for p in model.parameters() if id(p) not in finetuned_parameters)
parameters = [{'params': conv_parameters, 'lr': 1e-3},
{'params': model.fc.parameters()}]
optimizer = torch.optim.SGD(parameters, lr=1e-2, momentum=0.9, weight_decay=1e-4)
model.eval() 和 torch.no_grad()
model.eval() 和 torch.no_grad() 的区别在于,model.eval() 是将网络切换为测试状态,例如 BN 和dropout在训练和测试阶段使用不同的计算方法。torch.no_grad() 是关闭 PyTorch 张量的自动求导机制,以减少存储使用和加速计算,得到的结果无法进行 loss.backward()。
model.zero_grad() 和 optimizer.zero_grad()
model.zero_grad()会把整个模型的参数的梯度都归零, 而optimizer.zero_grad()只会把传入其中的参数的梯度归零.