Pytorch 模拟卷积网络(CNN)的梯度下降过程

前言

在我们训练神经网络时,通常使用的优化算法就是梯度下降,在这篇文章中,我以卷积神经网络为例,来具体展示一下在Pytorch中如何使用梯度下降算法来进行卷积神经网络的参数优化。

1.网络搭建

我们先来构建一个简单的卷积网络。

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和全连接操作,而是直接将卷积层输出的特征图的各个元素求和作为网络输出。

2.设置损失函数

根据我们上述的模型输出,我们可以设定**MSE(Mean Squared Error)**作为我们的损失函数。

criterion=nn.MSELoss()

3.自定义参数初始值

基于上述这个简单的卷积网络结果,需要训练的参数实际上就是这个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)
'''

4.构建训练数据

在完成了上述的模型搭建,损失函数设定以及参数初始化后,接下来我们需要创建一个简单的训练数据,我们设定一个简单的单通道,尺寸为3x3的类图像数据作为input tensor.

input_=torch.tensor([1.,2.,3.,4.,5.,6.,7.,8.,9.])
input_=input_.view(1,1,3,3)# reshape到指定形状

5.前向传播

在完成了上述步骤后,我们终于可以开始模型的训练了。首先就是通过前向传播来计算损失,当然这里我们需要设定一个target用于计算loss。

target=torch.tensor(250.) # 随意设定的值
output=convnet(input_) # 网络输出

# 计算损失
loss=criterion(output,target)

# 打印看看结果
print("MSE Loss:",loss.item())

'''Output
MSE Loss: 484.0
'''

6.反向传播更新参数

在完成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)
'''

我们可以看到在这个简单的例子中,两种写法得到的结果是完全一致的。

以上就是这篇博客的全部内容,如有任何疑问,欢迎在评论区留言。

你可能感兴趣的:(Pytorch,Pytorch,CNN,梯度下降,SGD)