pytorch 从放弃到入门(二)
时间:2023-02-15 11:00:00
深入学习相关知识:
基本配置
导入包:
import os import numpy as np import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader import torch.optim as optimizer
很有可能使用涉及表格信息的读入pandas,对于不同的项目可能还需要导入一些更上层的包如cv2.如果涉及可视化,也会使用。matplotlib、seaborn等等。也常用于下游分析和指标计算sklearn。
可统一设置超参数,便于后续调试修改:
batch_size = 16 lr = 1e-4 max_epochs = 100
GPU有两种常见的设置方法:
# 方案一:使用os.environ,如果使用这种情况GPU不需要设置 os.environ['CUDA_VISIBLE_DEVICES'] = '0,1' # 方案二:使用device”,后续使用GPU的变量用.to(device)即可 device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
数据读入:
简单描述流程图,首先是for循环使用DataLoader,进入DataLoader之后是否采用多进程进入DataLoaderlter,进入DataLoaderIter之后会使用sampler去获取Index,拿到索引后传输DatasetFetcher,在DatasetFetcher中会调用Dataset,Dataset根据给定的Index,在getitem从硬盘中读取实际内容Img和Label,读取了一个batch_size在数据之后,通过一个数据collate_fn整理数据,整理成batch_Data然后可以输入模型进行训练。有些代码表达不同,但流程相似。以后会仔细整理。
pytorch - 在数据读取机制中Dataloader与Dataset - 全栈程序员必看
class MyDataset(Dataset): def __init__(self, data_dir, info_csv, image_list, transform=None): """ Args: data_dir: path to image directory. info_csv: path to the csv file containing image indexes with corresponding labels. image_list: path to the txt file contains image names to training/validation set transform: optional transform to be applied on a sample. """ label_info = pd.read_csv(info_csv) image_file = open(image_list).readlines() self.data_dir = data_dir self.image_file = image_file self.label_info = label_info self.transform = transform def __getitem__(self, index): """ Args: index: the index of item Returns: image and its labels """ image_name = self.image_file[index].strip('\n') #按行读取图片,去掉每行换行符"\n" raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name] #显示索引:.loc,第一个参数是 index切片,第二个为 columns列名 #https://blog.csdn.net/zz5957/article/details/118549238 label = raw_label.iloc[:,0] #.iloc[:,0]隐藏索引:.iloc(integer_location),只能传入整数 image_name = os.path.join(self.data_dir, image_name) #path.join拼接规则 https://www.jianshu.com/p/3090f7875f9b image = Image.open(image_name).convert('RGB') if self.transform is not None: #https://blog.csdn.net/weixin_40123108/article/details/85099449 image = self.transform(image) return image, label#返回的单个样本 def __len__(self): return len(self.image_file) #构建好Dataset之后,就可以用了DataLoader按批次读入数据,实现代码如下: from torch.utils.data import DataLoader train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True) val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)
-
batch_size:样本按批读入,batch_size是每次读入的样本数
-
num_workers:读取数据有多少个过程?
-
shuffle:读入的数据是否被打乱?
-
drop_last:样本的最后一部分没有达到批次数,因此不再参加培训
PyTorch中的DataLoader可以使用读取next和iter来完成:
import matplotlib.pyplot as plt images, labels = next(iter(val_loader)) print(images.shape) plt.imshow(images[0].transpose(1,2,0)) plt.show()
next () 返回下一个迭代器项目。next() 生成迭代器的函数和函数 iter () 一起使用函数。
iter () 函数获取这些可迭代对象的迭代器。不断使用获得的迭代器 next () 通过函数获取条数据,调用可迭代对象 iter 方法.
iterable – 可迭代对象,default – 可选设置在没有下一个元素的情况下返回默认值。如果不设置,则不触发下一个元素 StopIteration 异常。
模型构建:
基于 Module 类的模型来完成的,它让模型构造更加灵活。
Module 类是 nn 模块中提供的模型结构类是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型。下面继承 Module 多层感知机的类结构。这里定义的 MLP 类重载了 Module 类的 init 函数和 forward 函数。它们用于创建模型参数和定义前向计算。前向计算也是正向传播。
import torch from torch import nn class MLP(nn.Module): # 声明了带有模型参数的层,声明了两个全连接层 def __init__(self, **kwargs): # 调用MLP父类Block构造函数进行必要的初始化。这样,在构造实例中也可以指定其他函数 super(MLP, self).__init__(**kwargs) self.hidden = nn.Linear(784, 256#设置全连接层 self.act = nn.ReLU() self.output = nn.Linear(256,10) # 定义模型的前向计算,即如何根据输入x计算返回所需的模型输出 def forward(self, x)
o = self.act(self.hidden(x))
return self.output(o)
#实例化。我们可以实例化 MLP 类得到模型变量 net 。
#下面的代码初始化 net 并传入输入数据 X 做一次前向计算。
#其中, net(X) 会调用 MLP 继承自 Module 类的 call 函数,
#这个函数将调用 MLP 类定义的forward 函数来完成前向计算。
X = torch.rand(2,784)
net = MLP()
print(net)
net(X)
以上的 MLP 类中无须定义反向传播函数。系统将通过自动求梯度而自动生成反向传播所需的 backward 函数,输出如下:
#输出:
MLP(
(hidden): Linear(in_features=784, out_features=256, bias=True)
# in_features:size of each input sample
#out_features: size of each output sample
#bias:if set to false,the layer will not learn an additive bias
#[in_features,out_features]
#从输入输出的张量的shape角度来理解,相当于一个输入为[batch_size, in_features]的张量变换成了[batch_size, out_features]的输出张量。
(act): ReLU()
(output): Linear(in_features=256, out_features=10, bias=True)
)
tensor([[ 0.0149, -0.2641, -0.0040, 0.0945, -0.1277, -0.0092, 0.0343, 0.0627,
-0.1742, 0.1866],
[ 0.0738, -0.1409, 0.0790, 0.0597, -0.1572, 0.0479, -0.0519, 0.0211,
-0.1435, 0.1958]], grad_fn=)
注意,这里并没有将 Module 类命名为 Layer (层)或者 Model (模型)之类的名字,这是因为该类是一个可供自由组建的部件。它的子类既可以是⼀个层(如PyTorch提供的 Linear 类),又可以是一个模型(如这里定义的 MLP 类),或者是模型的⼀个部分。
神经网络中常见层:
不含模型参数的层
构造的 MyLayer 类通过继承 Module 类自定义了一个将输入减掉均值后输出的层,并将层的计算定义在了 forward 函数里。这个层里不含模型参数。
import torch
from torch import nn
class MyLayer(nn.Module):
def __init__(self, **kwargs):
super(MyLayer, self).__init__(**kwargs)
def forward(self, x):
return x - x.mean()
#测试,实例化该层,然后做前向计算
layer = MyLayer()
layer(torch.tensor([1, 2, 3, 4, 5], dtype=torch.float))
#结果:
tensor([-2., -1., 0., 1., 2.])
含模型参数的层
自定义含模型参数的自定义层。其中的模型参数可以通过训练学出。
Parameter 类其实是 Tensor 的子类,如果一个 Tensor 是 Parameter ,那么它会自动被添加到模型的参数列表里。所以在自定义含模型参数的层时,我们应该将参数定义成 Parameter ,除了直接定义成 Parameter 类外,还可以使用 ParameterList 和 ParameterDict 分别定义参数的列表和字典。
class MyListDense(nn.Module):
# nn.Module 就是一个pytorch类,能够保持对状态的跟踪。
#我们可以创建 nn.Module 子类,nn.Module有很多属性和方法(如.parameters()和.zero_grad()),继承后将使用这些属性和方法。
def __init__(self):
super(MyListDense, self).__init__()
self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4)) for i in range(3)])
self.params.append(nn.Parameter(torch.randn(4, 1)))
def forward(self, x):
for i in range(len(self.params)):
x = torch.mm(x, self.params[i])
return x
net = MyListDense()
print(net)
class MyDictDense(nn.Module):
def __init__(self):
super(MyDictDense, self).__init__()
self.params = nn.ParameterDict({
'linear1': nn.Parameter(torch.randn(4, 4)),
'linear2': nn.Parameter(torch.randn(4, 1))
})
self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增
def forward(self, x, choice='linear1'):
return torch.mm(x, self.params[choice])
net = MyDictDense()
print(net)
二维卷积层
二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。卷积层的模型参数包括了卷积核和标量偏差。在训练模型的时候,通常我们先对卷积核随机初始化,然后不断迭代卷积核和偏差。
#卷积运算(二维互相关)
def corr2d(X, K):
h, w = K.shape
X, K = X.float(), K.float()
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
return Y
#Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
# 二维卷积层
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super(Conv2D, self).__init__()
self.weight = nn.Parameter(torch.randn(kernel_size))
self.bias = nn.Parameter(torch.randn(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias #corr2d 上面的二维卷积运算
#padding
X = X.view((1, 1) + X.shape)#原shape前加两个维度
# 在⾼和宽两侧的填充数分别为2和1,将每次滑动的行数和列数称为步幅(stride)
conv2d = nn.Conv2d(..., padding=(2, 1), stride=(3, 4))
#pooling最大池化或平均池化
if mode == 'max':
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
#一个nn.Module包含各个层和一个forward(input)方法,该方法返回output
#instance:
#前馈神经网络 (feed-forward network)(LeNet)
#步骤:
#定义包含一些可学习参数(或者叫权重)的神经网络
#在输入数据集上迭代
#通过网络处理输入
#计算 loss (输出和正确答案的距离)
#将梯度反向传播给网络的参数
#更新网络的权重,一般使用一个简单的规则:weight = weight - learning_rate * gradient
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
#n包则依赖于autograd包来定义模型并对它们求导。
#一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。
#backward函数会在使用autograd时自动定义,backward函数用来计算导数。
def __init__(self):
super(Net, self).__init__()#Net找其父类,将父类init东西给self
# 输入图像channel:1;输出channel:6;5x5卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 2x2 Max pooling
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 如果是方阵,则可以只使用一个数字进行定义
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))#把特征拍扁
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):#计算特征数用于拍扁
size = x.size()[1:] # 除去批处理维度的其他所有维度
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)#print the net's structure.
#返回可学习参数
print(net.parameters())
params = list(net.parameters())
#print(params)
print(len(params))
print(params[0].size()) # conv1的权重
#to use my model
#注意:这个网络 (LeNet)的期待输入是 32x32 的张量。
#如果使用 MNIST 数据集来训练这个网络,要把图片大小重新调整到 32x32。
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
net.zero_grad()#梯度清零
out.backward(torch.randn(1, 10))#随机梯度的反向传播
#torch.nn supports mini-batches rather than only one sample.
#nn.Conv2d 接受一个4维的张量,即nSamples x nChannels x Height x Width。
#如果是一个单独的样本,只需要使用input.unsqueeze(0) 来添加一个“假的”批大小维度。
torch.nn
只支持小批量处理 (mini-batches)。整个 torch.nn
包只支持小批量样本的输入,不支持单个样本的输入。比如,nn.Conv2d
接受一个4维的张量,即nSamples x nChannels x Height x Width
如果是一个单独的样本,只需要使用input.unsqueeze(0)
来添加一个“假的”批大小维度。
-
torch.Tensor
- 一个多维数组,支持诸如backward()
等的自动求导操作,同时也保存了张量的梯度。 -
nn.Module
- 神经网络模块。是一种方便封装参数的方式,具有将参数移动到GPU、导出、加载等功能。 -
nn.Parameter
- 张量的一种,当它作为一个属性分配给一个Module
时,它会被自动注册为一个参数。 -
autograd.Function
- 实现了自动求导前向和反向传播的定义,每个Tensor
至少创建一个Function
节点,该节点连接到创建Tensor
的函数并对其历史进行编码。
AlexNet
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256*5*5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
模型初始化:
#随机初始化:
#高斯分布
torch.nn.init.normal_(tensor: torch.Tensor, mean: float = 0.0, std: float = 1.0) → torch.Tensor
#均匀分布
torch.nn.init.uniform_(tensor: torch.Tensor, a: float = 0.0, b: float = 1.0) → torch.Tensor
#常数分布
torch.nn.init.constant_(tensor: torch.Tensor, val: float) → torch.Tensor
#全0分布
torch.nn.init.zeros_(tensor: torch.Tensor) → torch.Tensor
#全1分布
torch.nn.init.ones_(tensor: torch.Tensor) → torch.Tensor
#等等等等...
我们通常会根据实际模型来使用torch.nn.init
进行初始化,通常使用isinstance
来进行判断模块(回顾模型构建)属于什么类型。
import torch
import torch.nn as nn
conv = nn.Conv2d(1,3,3)
linear = nn.Linear(10,1)
isinstance(conv,nn.Conv2d)
isinstance(linear,nn.Conv2d)
#输出
True
False
#对于不同的类型层,我们就可以设置不同的权值初始化的方法。
# 对conv进行kaiming初始化
torch.nn.init.kaiming_normal_(conv.weight.data)
conv.weight.data
# 对linear进行常数初始化
torch.nn.init.constant_(linear.weight.data,0.3)
linear.weight.data
初始化函数的封装
将各种初始化方法定义为一个initialize_weights()
的函数并在模型初始后进行使用:
def initialize_weights(self):
for m in self.modules():
# 判断是否属于Conv2d
if isinstance(m, nn.Conv2d):
torch.nn.init.xavier_normal_(m.weight.data)
# 判断是否有偏置
if m.bias is not None:
torch.nn.init.constant_(m.bias.data,0.3)
elif isinstance(m, nn.Linear):
torch.nn.init.normal_(m.weight.data, 0.1)
if m.bias is not None:
torch.nn.init.zeros_(m.bias.data)
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zeros_()
这段代码流程是遍历当前模型的每一层,然后判断各层属于什么类型,然后根据不同类型层,设定不同的权值初始化方法。我们可以通过下面的例程进行一个简短的演示:
# 模型的定义
class MLP(nn.Module):
# 声明带有模型参数的层,这里声明了两个全连接层
def __init__(self, **kwargs):
# 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Conv2d(1,1,3)
self.act = nn.ReLU()
self.output = nn.Linear(10,1)
# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
def forward(self, x):
o = self.act(self.hidden(x))
return self.output(o)
mlp = MLP()
print(list(mlp.parameters()))
print("-------初始化-------")
initialize_weights(mlp)
print(list(mlp.parameters()))
------------------------------------------------------------------------------------
tensor([[[[ 0.2103, -0.1679, 0.1757],
[-0.0647, -0.0136, -0.0410],
[ 0.1371, -0.1738, -0.0850]]]], requires_grad=True), Parameter containing:
tensor([0.2507], requires_grad=True), Parameter containing:
tensor([[ 0.2790, -0.1247, 0.2762, 0.1149, -0.2121, -0.3022, -0.1859, 0.2983,
-0.0757, -0.2868]], requires_grad=True), Parameter containing:
tensor([-0.0905], requires_grad=True)]
"-------初始化-------"
[Parameter containing:
tensor([[[[-0.3196, -0.0204, -0.5784],
[ 0.2660, 0.2242, -0.4198],
[-0.0952, 0.6033, -0.8108]]]], requires_grad=True),
Parameter containing:
tensor([0.3000], requires_grad=True),
Parameter containing:
tensor([[ 0.7542, 0.5796, 2.2963, -0.1814, -0.9627, 1.9044, 0.4763, 1.2077,
0.8583, 1.9494]], requires_grad=True),
Parameter containing:
tensor([0.], requires_grad=True)]
损失函数:
总结如下:
3.6 损失函数 — 深入浅出PyTorch
训练和评估:
#我们前面在DataLoader构建完成后介绍了如何从中读取数据,在训练过程中使用类似的操作即可,区别在于此时要用for循环读取DataLoader中的全部数据。
for data, label in train_loader:
#之后将数据放到GPU上用于后续计算,此处以.cuda()为例
data, label = data.cuda(), label.cuda()
#开始用当前批次数据做训练时,应当先将优化器的梯度置零:
optimizer.zero_grad()
#之后将data送入模型中训练:
output = model(data)
#根据预先定义的criterion计算损失函数:
loss = criterion(output, label)
#将loss反向传播回网络:
loss.backward()
#使用优化器更新模型参数:
optimizer.step()
#这样一个训练过程就完成了,后续还可以计算模型准确率等指标。
验证/测试的流程基本与训练过程一致,不同点在于:
-
需要预先设置torch.no_grad,以及将model调至eval模式
-
不需要将优化器的梯度置零
-
不需要将loss反向回传到网络
-
不需要更新optimizer
#一个完整的图像分类的训练过程如下所示:
def train(epoch):
model.train()
train_loss = 0
for data, label in train_loader:
data, label = data.cuda(), label.cuda()
optimizer.zero_grad()
output = model(data)
loss = criterion(label, output)
loss.backward()
optimizer.step()
train_loss += loss.item()*data.size(0)
train_loss = train_loss/len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
#一个完整图像分类的验证过程如下所示:
def val(epoch):
model.eval()
val_loss = 0
with torch.no_grad():
for data, label in val_loader:
data, label = data.cuda(), label.cuda()
output = model(data)
preds = torch.argmax(output, 1)
loss = criterion(output, label)
val_loss += loss.item()*data.size(0)
running_accu += torch.sum(preds == label.data)
val_loss = val_loss/len(val_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, val_loss))
Pytorch优化器
优化器是根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值,使得模型输出更加接近真实标签
Optimizer
有三个属性:
-
defaults
:存储的是优化器的超参数 -
state
:参数的缓存 -
param_groups
:管理的参数组,是一个list,其中每个元素是一个字典,顺序是params,lr,momentum,dampening,weight_decay,nesterov
Optimizer
有以下的方法:
-
zero_grad()
:清空所管理参数的梯度,PyTorch的特性是张量的梯度不自动清零,因此每次反向传播后都需要清空梯度。 -
step()
:执行一步梯度更新,参数更新 -
add_param_group()
:添加参数组 -
load_state_dict()
:加载状态参数字典,可以用来进行模型的断点续训练,继续上次的参数进行训练 -
state_dict()
:获取优化器当前状态信息字典
#实际操作
import os
import torch
# 设置权重,服从正态分布 --> 2 x 2
weight = torch.randn((2, 2), requires_grad=True)
# 设置梯度为全1矩阵 --> 2 x 2
weight.grad = torch.ones((2, 2))
# 输出现有的weight和data
print("The data of weight before step:\n{}".format(weight.data))
print("The grad of weight before step:\n{}".format(weight.grad))
# 实例化优化器
optimizer = torch.optim.SGD([weight], lr=0.1, momentum=0.9)
# 进行一步操作
optimizer.step()
# 查看进行一步后的值,梯度
print("The data of weight after step:\n{}".format(weight.data))
print("The grad of weight after step:\n{}".format(weight.grad))
# 权重清零
optimizer.zero_grad()
# 检验权重是否为0
print("The grad of weight after optimizer.zero_grad():\n{}".format(weight.grad))
# 输出参数
print("optimizer.params_group is \n{}".format(optimizer.param_groups))
# 查看参数位置,optimizer和weight的位置一样,我觉得这里可以参考Python是基于值管理
print("weight in optimizer:{}\nweight in weight:{}\n".format(id(optimizer.param_groups[0]['params'][0]), id(weight)))
# 添加参数:weight2
weight2 = torch.randn((3, 3), requires_grad=True)
optimizer.add_param_group({"params": weight2, 'lr': 0.0001, 'nesterov': True})
# 查看现有的参数
print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
# 查看当前状态信息
opt_state_dict = optimizer.state_dict()
print("state_dict before step:\n", opt_state_dict)
# 进行5次step操作
for _ in range(50):
optimizer.step()
# 输出现有状态信息
print("state_dict after step:\n", optimizer.state_dict())
# 保存参数信息
torch.save(optimizer.state_dict(),os.path.join(r"D:\pythonProject\Attention_Unet", "optimizer_state_dict.pkl"))
print("----------done-----------")
# 加载参数信息
state_dict = torch.load(r"D:\pythonProject\Attention_Unet\optimizer_state_dict.pkl") # 需要修改为你自己的路径
optimizer.load_state_dict(state_dict)
print("load state_dict successfully\n{}".format(state_dict))
# 输出最后属性信息
print("\n{}".format(optimizer.defaults))
print("\n{}".format(optimizer.state))
print("\n{}".format(optimizer.param_groups))
---------------------------------------输出结果------------------------------------------、
# 进行更新前的数据,梯度
The data of weight before step:
tensor([[-0.3077, -0.1808],
[-0.7462, -1.5556]])
The grad of weight before step:
tensor([[1., 1.],
[1., 1.]])
# 进行更新后的数据,梯度
The data of weight after step:
tensor([[-0.4077, -0.2808],
[-0.8462, -1.6556]])
The grad of weight after step:
tensor([[1., 1.],
[1., 1.]])
# 进行梯度清零的梯度
The grad of weight after optimizer.zero_grad():
tensor([[0., 0.],
[0., 0.]])
# 输出信息
optimizer.params_group is
[{'params': [tensor([[-0.4077, -0.2808],
[-0.8462, -1.6556]], requires_grad=True)], 'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}]
# 证明了优化器的和weight的储存是在一个地方,Python基于值管理
weight in optimizer:1841923407424
weight in weight:1841923407424
# 输出参数
optimizer.param_groups is
[{'params': [tensor([[-0.4077, -0.2808],
[-0.8462, -1.6556]], requires_grad=True)], 'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}, {'params': [tensor([[ 0.4539, -2.1901, -0.6662],
[ 0.6630, -1.5178, -0.8708],
[-2.0222, 1.4573, 0.8657]], requires_grad=True)], 'lr': 0.0001, 'nesterov': True, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0}]
# 进行更新前的参数查看,用state_dict
state_dict before step:
{'state': {0: {'momentum_buffer': tensor([[1., 1.],
[1., 1.]])}}, 'param_groups': [{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}, {'lr': 0.0001, 'nesterov': True, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'params': [1]}]}
# 进行更新后的参数查看,用state_dict
state_dict after step:
{'state': {0: {'momentum_buffer': tensor([[0.0052, 0.0052],
[0.0052, 0.0052]])}}, 'param_groups': [{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}, {'lr': 0.0001, 'nesterov': True, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'params': [1]}]}
# 存储信息完毕
----------done-----------
# 加载参数信息成功
load state_dict successfully
# 加载参数信息
{'state': {0: {'momentum_buffer': tensor([[0.0052, 0.0052],
[0.0052, 0.0052]])}}, 'param_groups': [{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [0]}, {'lr': 0.0001, 'nesterov': True, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'params': [1]}]}
# defaults的属性输出
{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}
# state属性输出
defaultdict(, {tensor([[-1.3031, -1.1761],
[-1.7415, -2.5510]], requires_grad=True): {'momentum_buffer': tensor([[0.0052, 0.0052],
[0.0052, 0.0052]])}})
# param_groups属性输出
[{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [tensor([[-1.3031, -1.1761],
[-1.7415, -2.5510]], requires_grad=True)]}, {'lr': 0.0001, 'nesterov': True, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'params': [tensor([[ 0.4539, -2.1901, -0.6662],
[ 0.6630, -1.5178, -0.8708],
[-2.0222, 1.4573, 0.8657]], requires_grad=True)]}]
注意:
#1.每个优化器都是一个类,我们一定要进行实例化才能使用,比如下方实现:
class Net(nn.Moddule):
···
net = Net()
optim = torch.optim.SGD(net.parameters(),lr=lr)
optim.step()
#2.optimizer在一个神经网络的epoch中需要实现下面两个步骤:
#梯度置零
#梯度更新
optimizer = torch.optim.SGD(net.parameters(), lr=1e-5)
for epoch in range(EPOCH):
...
optimizer.zero_grad() #梯度置零
loss = ... #计算loss
loss.backward() #BP反向传播
optimizer.step() #梯度更新
#3.给网络不同的层赋予不同的优化器参数。
from torch import optim
from torchvision.models import resnet18
net = resnet18()
optimizer = optim.SGD([
{'params':net.fc.parameters()},#fc的lr使用默认的1e-5
{'params':net.layer4[0].conv1.parameters(),'lr':1e-2}],lr=1e-5)# 可以使用param_groups查看属性