十分钟掌握 PyTorch 搭建神经网络的流程


犇
2023-11-30 11:24:54 65044
分类专栏: 资讯

文章中,我们将使用PyTorch从头开始实现一个简单的神经网络。在阅读本篇文章之前,我们默认你已经了解神经网络的工作原理。

 

最近发现身边的一些初学者朋友捧着各种pytorch指南一边看一边敲代码,到最后反而变成了打字员。

 

敲完代码一运行,出来结果和书上一对比,哦,是书上的结果,就翻到下一章。

 

半天就能把一本书都打完,但是合上书好像什么都不记得。有的甚至看了两三遍,都搭不出一个简单的网络来,这种学习方式很不可取。

 

如果你刚好是这种情况,这篇文章应该能给你一些帮助。如果你已经是进阶的水平了,就直接关掉页面就好了。

 

pytorch的网络搭建,比tensorflow简单很多。格式很好理解。

 

如果你想做一个网络,需要先定义一个Class,继承 nn.Module(这个是必须的,所以先import torch.nn as nn,nn是一个工具箱,很好用),我们把class的名字就叫成Net.

 

Class Net (nn.Module):

 

这个Class里面主要写两个函数,一个是初始化的__init__函数,另一个是forward函数。我们随便搭一个,如下:

    def __init__(self):
       super().__init__()
       self.conv1=nn.Conv2d(1,6,5)
       self.conv2=nn.Conv2d(6,16,5)
   def forward(self, x):
       x=F.max_pool2d(F.relu(self.conv1(x)),2)
       x=F.max_pool2d(F.relu(self.conv2(x)),2)
       return x

 

__init__里面就是定义卷积层,当然先得super()一下,给父类nn.Module初始化一下。

 

(Python的基础知识)在这个里面主要就是定义卷积层的,比如第一层,我们叫它conv1,把它定义成输入1通道,输出6通道,卷积核5*5的的一个卷积层。conv2同理。

 

神经网络“深度学习”其实主要就是学习卷积核里的参数,像别的不需要学习和改变的,就不用放进去。

 

比如激活函数relu(),你非要放进去也行,再给它起个名字叫myrelu,也是可以的。forward里面就是真正执行数据的流动。

 

比如上面的代码,输入的x先经过定义的conv1(这个名字是你自己起的),再经过激活函数F.relu()(这个就不是自己起的名字了,最开始应该先import torch.nn.functional as F,F.relu()是官方提供的函数。

 

当然如果你在__init__里面把relu定义成了我上面说的myrelu,那你这里直接第一句话就成了x=F.max_pool2d(myrelu(self.conv1(x)),2)。

 

下一步的F.max_pool2d池化也是一样的,不多废话了。在一系列流动以后,最后把x返回到外面去。

 

这个Net的Class定义主要要注意两点。

 

第一:是注意前后输出通道和输入通道的一致性。不能第一个卷积层输出4通道第二个输入6通道,这样就会报错。

 

第二:它和我们常规的python的class还有一些不同,发现了没有?我们该怎么用这个Net呢?

 

先定义一个Net的实例(毕竟Net只是一个类不能直接传参数,output=Net(input)当然不行)

 

net=Net()

 

这样我们就可以往里传x了,假设你已经有一个要往神经网络的输入的数据“input"(这个input应该定义成tensor类型,怎么定义tensor那就自己去看看书了。)在传入的时候,是:

 

 

output=net(input)

 

看之前的定义:

def __init__(self):
  ……
def forward(self, x):
  ……

 

有点奇怪。好像常规python一般向class里面传入一个数据x,在class的定义里面,应该是把这个x作为形参传入__init__函数里的,而在上面的定义中,x作为形参是传入forward函数里面的。

 

其实也不矛盾,因为你定义net的时候,是net=Net(),并没有往里面传入参数。如果你想初始化的时候按需传入,就把需要的传入进去。

 

只是x是神经网络的输入,但是并非是初始化需要的,初始化一个网络,必须要有输入数据吗?

 

未必吧。只是在传入网络的时候,会自动认为你这个x是喂给forward里面的。也就是说,先定义一个网络的实例net=Net(),  这时调用output=net(input), 可以理解为等同于调用output=net.forward(input), 这两者可以理解为一码事。

 

在网络定义好以后,就涉及到传入参数,算误差,反向传播,更新权重…确实很容易记不住这些东西的格式和顺序。

 

传入的方式上面已经介绍了,相当于一次正向传播,把一路上各层的输入x都算出来了。

 

想让神经网络输出的output跟你期望的ground truth差不多,那就是不断减小二者间的差异,这个差异是你自己定义的,也就是目标函数(object function)或者就是损失函数。

 

如果损失函数loss趋近于0,那么自然就达到目的了。

 

损失函数loss基本上没法达到0,但是希望能让它达到最小值,那么就是希望它能按照梯度进行下降。

 

梯度下降的公式,大家应该都很熟悉,不熟悉的话,建议去看一下相关的理论。谁喜欢看公式呢?所以我这里不讲。

 

只是你的输入是由你来决定的,那神经网络能学习和决定什么呢?

 

自然它只能决定每一层卷积层的权重。所以神经网络只能不停修改权重,比如y=wx+b,x是你给的,它只能改变w,b让最后的输出y尽可能接近你希望的y值,这样损失loss就越来越小。

 

如果loss对于输入x的偏导数接近0了,不就意味着到达了一个极值吗?

 

而l在你的loss计算方式已经给定的情况下,loss对于输入x的偏导数的减小,其实只能通过更新参数卷积层参数W来实现(别的它决定不了啊,都是你输入和提供的)。

 

所以,通过下述方式实现对W的更新:(注意这些编号,下面还要提)

 

【1】 先算loss对于输入x的偏导,(当然网络好几层,这个x指的是每一层的输入,而不是最开始的输入input)

 

【2】 对【1】的结果再乘以一个步长(这样就相当于是得到一个对参数W的修改量)

 

【3】 用W减掉这个修改量,完成一次对参数W的修改。

 

说的不太严谨,但是大致意思是这样的。这个过程你可以手动实现,但是大规模神经网络怎么手动实现?那是不可能的事情。所以我们要利用框架pytorch和工具箱torch.nn。

 

所以要定义损失函数,以MSEloss为例:

 

compute_loss=nn.MSELoss()

 

明显它也是个类,不能直接传入输入数据,所以直接loss=nn.MSEloss(target,output)是不对的。需要把这个函数赋一个实例,叫成compute_loss。

 

之后就可以把你的神经网络的输出,和标准答案target传入进去:

 

loss=compute_loss(target,output)

 

算出loss,下一步就是反向传播:

 

loss.backward()

 

这一步其实就是把【1】给算完了,得到对参数W一步的更新量,算是一次反向传播。

 

这里就注意了,loss.backward()是啥玩意?如果是自己的定义的loss(比如你就自己定义了个def loss(x,y):return y-x )这样肯定直接backward会出错。所以应当用nn里面提供的函数。

 

当然搞深度学习不可能只用官方提供的loss函数,所以如果你要想用自己的loss函数。

 

必须也把loss定义成上面Net的样子(不然你的loss不能反向传播,这点要注意,注:这点是以前写的,很久之前的版本不行,现在都可以了,基本不太需要这样了)。

 

也是继承nn.Module,把传入的参数放进forward里面,具体的loss在forward里面算,最后return loss。__init__()就空着,写个super().__init__就行了。

 

在反向传播之后,第【2】和第【3】怎么实现?就是通过优化器来实现。让优化器来自动实现对网络权重W的更新。

 

所以在Net定义完以后,需要写一个优化器的定义(选SGD方式为例):

 

from torch import optim
optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

 

同样,优化器也是一个类,先定义一个实例optimizer,然后之后会用。

 

注意在optimizer定义的时候,需要给SGD传入了net的参数parameters,这样之后优化器就掌握了对网络参数的控制权,就能够对它进行修改了。

 

传入的时候把学习率lr也传入了。

 

在每次迭代之前,先把optimizer里存的梯度清零一下(因为W已经更新过的“更新量”下一次就不需要用了)

 

optimizer.zero_grad()

 

在loss.backward()反向传播以后,更新参数:

 

  •  
optimizer.step()

 

所以我们的顺序是:

 

1.先定义网络:写网络Net的Class,声明网络的实例net=Net(),

 

2.定义优化器

 

optimizer=optim.xxx(net.parameters(),lr=xxx),

 

3.再定义损失函数(自己写class或者直接用官方的,compute_loss=nn.MSELoss()或者其他。

 

4.在定义完之后,开始一次一次的循环:

 

①先清空优化器里的梯度信息,optimizer.zero_grad();

 

②再将input传入,output=net(input) ,正向传播

 

③算损失,loss=compute_loss(target,output)   ##这里target就是参考标准值GT,需要自己准备,和之前传入的input一一对应

 

④误差反向传播,loss.backward()

 

⑤更新参数,optimizer.step()

 

这样就实现了一个基本的神经网络。大部分神经网络的训练都可以简化为这个过程,无非是传入的内容复杂,网络定义复杂,损失函数复杂,等等等等。

转载自丨古月居

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=29218
赞同 0
评论 0 条
L2
粉丝 1 发表 45 + 关注 私信
上周热门
银河麒麟添加网络打印机时,出现“client-error-not-possible”错误提示  1323
银河麒麟打印带有图像的文档时出错  1236
银河麒麟添加打印机时,出现“server-error-internal-error”  1023
统信桌面专业版【如何查询系统安装时间】  951
统信操作系统各版本介绍  944
统信桌面专业版【全盘安装UOS系统】介绍  903
麒麟系统也能完整体验微信啦!  889
统信【启动盘制作工具】使用介绍  499
统信桌面专业版【一个U盘做多个系统启动盘】的方法  441
信刻全自动档案蓝光光盘检测一体机  386
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
信创开放社区邀请他人注册的具体步骤如下 15
如何玩转信创开放社区—从小白进阶到专家 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

添加我为好友,拉您入交流群!

请使用微信扫一扫!