在我们训练神经网络时,通常使用的优化算法就是梯度下降,在这篇文章中,我以卷积神经网络为例,来具体展示一下在Pytorch中如何使用梯度下降算法来进行卷积神经网络的参数优化。
我们先来构建一个简单的卷积网络。
import torch
import torch.nn as nn
import torch.optim as optim
class Conv_net(nn.Module):
def __init__(self):
super(Conv_net,self).__init__()
self.conv=nn.Conv2d(1,1,2,bias=False)# 为了方便,不设置偏置
def forward(self,x):
x=self.conv(x)
return x.sum() # 为了方便,这里不做flatten以及全连接操作,直接对feature map各元素求和作为输出
我们可以看到,这个网络只有一个卷积核大小为2x2且不带偏置项的卷积层,输入和输出的channel个数均为1。同时为了简化,卷积层输出不经过池化以及后续的flatten和全连接操作,而是直接将卷积层输出的特征图的各个元素求和作为网络输出。
根据我们上述的模型输出,我们可以设定**MSE(Mean Squared Error)**作为我们的损失函数。
criterion=nn.MSELoss()
基于上述这个简单的卷积网络结果,需要训练的参数实际上就是这个2x2的卷积核,为了后续方便观察参数的变化情况,这里我们自定义参数的初始值。
convnet=Conv_net()
init_weight=torch.tensor([1.,2.,3.,4.])
init_weight=init_weight.view(1,1,2,2) # reshape到指定形状
# 用上述的自定义初始权值替换默认的随机初始值
convnet.conv._paramaters['weight'].data=init_weight
# 打印一下看看结果
for p in convnet.parameters():
print(p)
'''Output
Parameter containing:
tensor([[[[1., 2.],
[3., 4.]]]], requires_grad=True)
'''
在完成了上述的模型搭建,损失函数设定以及参数初始化后,接下来我们需要创建一个简单的训练数据,我们设定一个简单的单通道,尺寸为3x3的类图像数据作为input tensor.
input_=torch.tensor([1.,2.,3.,4.,5.,6.,7.,8.,9.])
input_=input_.view(1,1,3,3)# reshape到指定形状
在完成了上述步骤后,我们终于可以开始模型的训练了。首先就是通过前向传播来计算损失,当然这里我们需要设定一个target用于计算loss。
target=torch.tensor(250.) # 随意设定的值
output=convnet(input_) # 网络输出
# 计算损失
loss=criterion(output,target)
# 打印看看结果
print("MSE Loss:",loss.item())
'''Output
MSE Loss: 484.0
'''
在完成loss的计算后,我们就可以通过反向传播去计算每个参数的梯度,然后设定某个确定的学习率并使用梯度下降算法对参数进行更新。
# 将之前buffer中的梯度清零
convnet.zero_grad()
# 反向传播计算梯度
loss.backward()
# 打印看看卷积核各个参数的梯度情况
print('卷积核参数梯度为:',convnet.conv.weight.grad)
'''Output
卷积核参数梯度为: tensor([[[[ -528., -704.],
[-1056., -1232.]]]])
'''
# 设置学习率
learning_rate=0.01
# 更新参数(梯度下降)
for p in convnet.parameters():
p.data.sub_(p.grad.data*learning_rate) # p=p-grad*learning rate
# 打印看看更新后的参数
print('更新后的卷积核参数:')
for p in convnet.parameters():
print(p)
'''Output
更新后的卷积核参数:
Parameter containing:
tensor([[[[ 6.2800, 9.0400],
[13.5600, 16.3200]]]], requires_grad=True)
'''
但是在一般情况下,我们使用Pytorch去训练网络模型时,不太会去采用上述这种手动更新模型参数的方法,因为Pytorch为我们提供了一种更高效的方式,即通过构建一个optimizer来完成参数的更新。这种方法能够使我们灵活高效地选取各种参数优化策略,比如SGD,Adam等等。下面拿SGD作为例子。
optimizer=optim.SGD(convnet.parameters(),lr=0.01) # 定义SGD优化器
optimizer.zero_grad() # 清除buffer中累积的前一次梯度数据
loss.backward() # 反向传播
optimizer.step() # 更新参数
# 打印看看更新后的参数
print('使用optimizer更新的卷积核参数:')
for p in convnet.parameters():
print(p)
'''Output
使用optimizer更新的卷积核参数:
Parameter containing:
tensor([[[[ 6.2800, 9.0400],
[13.5600, 16.3200]]]], requires_grad=True)
'''
我们可以看到在这个简单的例子中,两种写法得到的结果是完全一致的。
以上就是这篇博客的全部内容,如有任何疑问,欢迎在评论区留言。