对于一个神经网络,我们可以根据神经网络结构从头实现,例如一个BP神经网络,我们需要选择损失函数、激活函数,根据公式推导反向传递的梯度,并使用梯度下降更新参数,而卷积神经网络,还要写卷积、池化等函数,同时还要推导出卷积核等梯度......这样一来,便需要更多的时间用于梯度公式的推导与算法的实现。
为了避免每次推导梯度公式,我们可以采取自动微分的方法,写一个自动微分的类,之后每次反向传递,可以自动求出各个参数的梯度,从而用于梯度的更新。即便如此,我们还是需要用更多的时间去写激活函数、梯度下降、前向传递、自动微分等部分。所以使用其它的方法来实现神经网络模型就显得较为重要。兔兔在本文pytorch为例,使用pytorch实现一个简单的BP神经网络。
利用pytorch实现神经网络模型主要分为两种方法——一种是利用torch.nn中Sequential、RNN等方法来直接实现,该方法可以简化搭建过程;另一种是继承nn.Module父类,自己构建一个神经网络,该方法相对第一种较为复杂、灵活,但是十分常用,可以构建自己的神经网络模型,较多的深度学习论文中的模型都采用该方法来实现。
除了以上两种方法,其实也可以利用pytorch自动求导机制实现神经网络模型,即模型参数、前向传递、梯度下降等部分都手动去写,该方法在实际应用中不会用到,但是可以使读者更好地理解其中的细节,所以兔兔在最后以BP神经网络为例,采用自动求导机制来实现该方法。
关于BP神经网络的原理,可以参考兔兔前面的文章。
(一)torch.nn.Sequential搭建BP神经网络
import numpy as np
import torch
from torch import nn
data=https://www.it610.com/article/np.random.normal(0,10,size=(10,3))
label=np.random.normal(0,10,size=(10,4))
data=torch.tensor(data,dtype=torch.float32)
label=torch.tensor(label,dtype=torch.float32)
bp=nn.Sequential(nn.Linear(3,10),nn.Sigmoid(),nn.Linear(10,6),nn.ReLU(),nn.Linear(6,4),nn.Sigmoid())
Loss=nn.MSELoss() #损失函数,这里选用MSE损失函数
optim=torch.optim.SGD(params=bp.parameters(),lr=0.1)#参数更新方法,这里选用随机梯度下降
for i in range(100):
yp=bp(data) #前向传递的预测值
loss=Loss(yp,label) #预测值与实际值的差别
optim.zero_grad()
loss.backward() #反向传递
optim.step() #更新参数
在该模型中,data每行表示每组数据,每列为数据的属性(指标);label每行表示每组数据,每列表示对应的类别。所以BP神经网络的输入层节点个数要与data的列数一致,输出层要与label列数一致(通常label每行是只有一个1,其余为0的一维数组,这里为了表述简便,使用随机生成的数组)。在输入模型前需要将data、label转成tensor张量。
之后用nn.Sequential()来写前馈神经网络模型,第一个nn.Linear()为神经网络输入层神经元,其中第一个参数表示输入数据特征数,第二个参数为输出特征数。所以根据data、label的维数确定神经元输入层与输出层的神经元个数。上述代码中是[3,10,6,4]四层神经元的神经网络,层与层之间分别使sigmoid、ReLU、sigmoid激活函数。之后选择损失函数与参数优化方法,损失函数在nn中,优化方法在torch.optim中,可根据实际需要来选择。优化器中需要传递模型参数params与学习率lr。
最后神经网络通过循环迭代来实现,每次用bp模型对数据进行预测,将预测值与实际值传递到损失函数Loss中,之后backward()反向传递并使用step()更新参数。
模型训练结束后,可以直接用bp(x)来预测x,也可以用bp.forward(x)。训练过程中,可以通过print(loss)或print(loss.data)查看损失函数值的变化。
(二)torch.nn.Module继承父类搭建BP神经网络
import torch
from torch import nn
class BP(nn.Module):
'''BP神经网络模型'''
def __init__(self,node,act):
super().__init__()
self.node=node #各层神将元数
self.n=len(node) #神经网络层数
self.act=act #各层之间激活函数
self.model=nn.Sequential()
for i in range(self.n-1):
self.model.append(nn.Linear(self.node[i],self.node[i+1]))
self.model.append(self.act[i])
def forward(self,input):
output=self.model(input)
return output
if __name__=='__main__':
bp=BP(node=[2,2,3],act=[nn.ReLU(),nn.Sigmoid(),nn.ReLU()])
traindata=https://www.it610.com/article/torch.randint(0,2,size=(4,2),dtype=torch.float32)
labeldata=torch.randint(0,2,size=(4,3),dtype=torch.float32)
optimizer=torch.optim.Adam(params=bp.parameters(),lr=0.01)
Loss=nn.MSELoss()
for i in range(10):
yp=bp(traindata)
loss=Loss(yp,labeldata)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(loss)
在上面代码中,兔兔在__init__()中直接使用了nn.Sequential()来实现构建模型,在一些复杂神经网络中经常会这样做。这里其实也可以由BP神经网络的基本原理来构建,代码如下所示。
import torch
from torch import nn
class BP(nn.Module):
'''BP神经网络模型'''
def __init__(self,node):
super().__init__()
self.node=node #各层神将元数
self.n=len(node) #神经网络层数
self.w=torch.nn.ParameterList([torch.randint(0,2,size=(self.node[i],self.node[i+1]),dtype=torch.float32) for i in range(self.n-1)]) #权重参数weight
self.b=torch.nn.ParameterList([torch.randint(0,2,size=(1,self.node[i]),dtype=torch.float32) for i in range(1,self.n)]) #偏置参数bias
def forward(self,input):
x=torch.mm(input,self.w[0])+self.b[0]
x=torch.relu(x)
for i in range(1,self.n-1):
x=torch.mm(x,self.w[i])+self.b[i]
x=torch.relu(x)
return x
if __name__=='__main__':
bp=BP(node=[2,2,3])
traindata=https://www.it610.com/article/torch.randint(0,2,size=(4,2),dtype=torch.float32)
labeldata=torch.randint(0,2,size=(4,3),dtype=torch.float32)
optimizer=torch.optim.Adam(params=bp.parameters(),lr=0.01)
Loss=nn.MSELoss()
for i in range(10):
yp=bp(traindata)
loss=Loss(yp,labeldata)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(loss)
采用该方法的关键是利用nn.ParameterList()在__init__()中来定义可训练参数,这样该模型才能传递parameters()给optimizer,并在训练过程中更新这些参数。nn.ParameterDict()也可以定义可训练参数,只是此时参数是字典形式,键为参数名,值为参数。在forward函数中需要自己去写前向传播算法。
(三)pytorch自动求导实现BP神经网络 兔兔在这里讲述的方法,主要是介绍pytorch自动求导方法的使用以及BP神经网络的原理,在实际应用中一般不会使用,感兴趣的同学可以阅读该部分。
import torch
class BP:
def __init__(self,traindata,labeldata,node,epoch,lr):
self.traindata=https://www.it610.com/article/traindata
self.labeldata=labeldata
self.node=node #各层节点
self.n=len(node) #层数
self.epoch=epoch #训练次数
self.lr=lr #学习率learning rate
w=[];
b=[]
for i in range(self.n-1):
exec(f'w{i}=torch.randint(0,1,size=(self.node[i],self.node[i+1]),dtype=torch.float32,requires_grad=True)')
w.append(eval(f'w{i}'))
cv=locals()
for i in range(1,self.n):
exec(f'b{i}=torch.randint(0,1,size=(1,self.node[i]),dtype=torch.float32,requires_grad=True)')
b.append(eval(f'b{i}'))
self.w=w
self.b=b
def forward(self,input):
x = torch.mm(input, self.w[0]) + self.b[0]
x = torch.relu(x)
for i in range(1, self.n - 1):
x = torch.mm(x, self.w[i]) + self.b[i]
x = torch.relu(x)
return x
def loss(self,yp,y):
'''损失函数'''
return (yp-y).pow(2).sum() #均方损失函数
def updata(self):
for i in range(self.epoch):
#print('the {} epoch'.format(i+1))
yp=self.forward(self.traindata)
loss=self.loss(yp,self.labeldata)
loss.backward()
for i in range(self.n-1):
self.w[i].data-=self.lr*self.w[i].grad.data
self.b[i].data-=self.lr*self.b[i].grad.data#梯度下降更新参数if __name__=='__main__':
traindata = https://www.it610.com/article/torch.randint(0, 5, size=(10, 2), dtype=torch.float32)
labeldata = torch.randint(0, 5, size=(10, 3), dtype=torch.float32)
bp = BP(traindata=traindata,labeldata=labeldata,node=[2,2,3],epoch=10000,lr=0.01)
bp.updata()
在pytorch的之前版本中,对需要求梯度的参数w、b,需要使用torch.autugrad.Variable(requires_grad=True),使w,b可以求导。新的版本中tensor类型即可求梯度,只需更改其中参数requires_grad=True即可。之后这些参数参与前向传播的计算,通过计算的预测值与实际值构造损失函数,使用loss.backward()反向传播计算各个参数梯度,通过w.grad.data、b.grad.data得到相应梯度值,最终利用梯度下降更新参数。这里的损失函数与梯度下降法兔兔选择了最为简单的一种。
在初始化可求梯度的参数时,兔兔采用了exec内置函数循环批量生成变量名,并将其添加到列表中。
在梯度下降更新过程中,计算图中的叶节点不能直接进行内置运算,所以不可以对self.w[i]或self.b[i]等直接进行更新,否则会出现"RuntimeError: a leaf Variable that requires grad is being used in an in-place operation."这样的错误,这里兔兔利用self.w[i].data更新参数。若不使用该方法,也可以在代码第37行前加上 "with tensor.no_grad:"。
总结 【深度学习基础|基于pytorch的BP神经网络实现】利用pytorch实现神经网络有多种方法,对于BP神经网络这样基础模型使用Sequential()方法较为合适,而对于复杂的神经网络通常采用继承nn.Module父类的方法来实现,一般的搭建流程是先构建一些小的部分或单层神经网络,这些小的部分同样继承Module父类,最终将这些部分组合一起形成完整的神经网络。
推荐阅读
- 机器学习|NTU20220813-数据结构化和深度学习-WSL音频转发
- 机器学习|李宏毅机器学习笔记p5-p8(误差和梯度下降)
- 吴恩达深度学习课程编程作业|吴恩达深度学习课程第四章第一周编程作业(pytorch实现)
- 图像处理|吴恩达深度学习-Course4第三周作业 yolo.h5文件读取错误解决方法
- 机器学习|【进阶版】机器学习之神经网络与深度学习基本知识和理论原理(07)
- 深度学习|深度学习面试题——深度学习的技术发展史
- 11|【嵌入式--伺服电机】足式机器人 外转子无刷电机研究
- 环境配置|Anaconda虚拟环境配置pytorch流程并在Jupyter Notebook中切换
- 深度学习|Anaconda中安装pytorch,并在pycharm中配置【win10】