1、经典函数,求导
从最简单的tensor开始,我们新建了一个tensor变量a,建立b = 3*a +3,那可以通过b.backward()对函数求导
import torch
from torch import nn
a = torch.tensor(3.0,requires_grad=True)
b = 3*a +3
b.backward(retain_graph=True)
print(a.grad)
输出:tensor(3.0)
b.backward()
print(a.grad)
输出:tensor(6.0)
torch在求导时,不会清除上一次的梯度,会累加
因此,可以使用a.grad.zero_()清零
2、复杂模型求导
当我们使用了神经网络,模型变得复杂,变量被隐藏在了torch的module模块中
问题一:怎么管理我们的参数呢?
问题二:怎么进行求导呢?
2.1、首先,构造一个简单的模型,继承Module类,构造net1网络对象
from torch import nn
torch.manual_seed(0)
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1,out_channels=1,kernel_size=2,stride=1,padding=0)
self.conv2 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=2, stride=1, padding=0)
def forward(self,x):
out = self.conv1(x)
return out
net1 = model()
对于这样的对象,它的参数可以使用net1.parameters()查看,返回一个迭代器
for para in net1.parameters():
print(para)
那么,我们怎么对我们模型的参数进行求导呢?和经典函数一样,我们获取目标值,然后利用backward() torch自动对目标值关联的变量进行求导。这里我额外构造了一个损失,因为用out.backward(),会报错,说想要求导的是一个标量(不懂,以后再说)。下面构造了一个损失,变成标量。
print('\n变量')
for para in net1.parameters():
print(para)
out = net1.forward(torch.ones([1,1,16,16]))
## 损失函数
loss = torch.nn.MSELoss()
out = loss.forward(out,torch.ones_like(out-1))
out.backward(retain_graph=True)
print('\n梯度')
for para in net1.parameters():
print(para.grad)
tensor([[[[-3.4152, -3.4152],
[-3.4152, -3.4152]]]])
tensor([-3.4152])
能够正常输出梯度!!!
那然后,我们怎么将每个参数的值,通过梯度下降,结合梯度,更新到参数上呢,想想都觉得太麻烦。
另外,还有一个麻烦的点,我们的梯度在backward()后,会自动加上前面计算的梯度,如果没有归零的话。
2.2、利用优化器来管理参数
注意,我这里说“使用优化器来管理参数”,我理解的就是这样的。
torch.optim这个类实现了两个功能,第一,让所有加载到optim的参数梯度归零;第二,将计算得到的梯度,更新到参数上。
1)首先,我们来演示,torch.optim具有参数梯度归零的本领,这里选择使用SGD进行演示(梯度下降法,比较简单)
import torch
from torch import nn
torch.manual_seed(0)
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1,out_channels=1,kernel_size=2,stride=1,padding=0)
self.conv2 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=2, stride=1, padding=0)
def forward(self,x):
out = self.conv1(x)
return out
net1 = model()
out = net1.forward(torch.ones([1,1,16,16]))
## 损失函数
loss = torch.nn.MSELoss()
out = loss.forward(out,torch.ones_like(out-1))
## 优化器
optimizer_1 = torch.optim.SGD(net1.parameters(),lr = 4)
print('\n输出梯度')
out.backward(retain_graph=True)
for i in net1.parameters():
print(i.grad)
print('\n梯度清零')
optimizer_1.zero_grad()
print('\b输出梯度')
for i in net1.parameters():
print(i.grad)
结果说明,optimizer_1.zero_grad()确实实现了梯度清零的效果
2)演示,torch.optim具有参数梯度更新到参数的本领
import torch
from torch import nn
torch.manual_seed(0)
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1,out_channels=1,kernel_size=2,stride=1,padding=0)
self.conv2 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=2, stride=1, padding=0)
def forward(self,x):
out = self.conv1(x)
return out
net1 = model()
out = net1.forward(torch.ones([1,1,16,16]))
## 损失函数
loss = torch.nn.MSELoss()
out = loss.forward(out,torch.ones_like(out-1))
## 优化器
optimizer_1 = torch.optim.SGD(net1.parameters(),lr = 0.4)
print('\n输出参数')
for para in net1.parameters():
print(para)
optimizer_1.zero_grad() # 梯度清零
out.backward() # 计算梯度
print('\n输出梯度')
for i in net1.parameters():
print(i.grad)
optimizer_1.step() # 梯度更新
print('\n输出参数')
for para in net1.parameters():
print(para)
证明完毕