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

 
 

点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

编者荐语

 

文章中,我们将使用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()

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

说的有问题的地方感谢指正!

 
 

好消息!

小白学视觉知识星球

开始面向外开放啦👇👇👇

 
 

cb1dc2c1d00a691e8f226f1c9174394f.jpeg

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值