注:
目前仅提供原理的python代码实现,并未实例进行。如果有需要可以评论亦或者是查阅其他博客的实现过程
在本文的代码原理实现中,使用了Linear层进行搭建
MLP介绍:
MLP简称多层感知机,其结构与我的一篇博客BP神经网络(python实现)-CSDN博客相似,其实也可以讲大部分的神经网络结构相似,就比如多层感知机(MLP)、全连接神经网络(FCNN)、前馈神经网络(FNN)、深度神经网络(DNN)这些网络其实侧重点不同,但在大体结构上并未过多的差别,在此篇博客中有详细讲解:多层感知机(MLP)、全连接神经网络(FCNN)、前馈神经网络(FNN)、深度神经网络(DNN)与BP算法详解_mlp神经网络-CSDN博客
回到我们的正题,了解多层感知机之前,我们可以先了解一下单层感知机结构:
而在此基础上添加多层神经元,这样就是实现了多层感知机
python实现:
在此处的实现来源于此篇博客用python实现多层感知机_多层感知机python csdn-CSDN博客的代码,也可以参考他的实现过程。本文在其代码上进行了解读(具体看代码,最后已附上)
此图为class Linear中的不易于理解的部分,其中包括def forward部分中的self.Onet计算过程,原作者并未给出注释,因此此处为笔者的推测。-->首先我们定义的权重是根据该层输入特征数量in_feature和out_feature给出的维度,然而self.Oin则是根据输入的x,因此两者之间的维度关系不确定,故而我们给定x这一维度时需要确保其维度大小为(in_feature,batch_size)
下图为对应的维度计算,以及所得维度,其中in_feature加1是因为经过了row_stack
注:此段代码对于原作者的代码并未更改,笔者仅在其基础上进行解读,关于实践部分并未进行尝试,同时此代码最具特色的点在于将神经网络拆分称为一层一层的,即代码中class:Linear的实现。与笔者之前读过的BP神经网络代码不太一样,它将神经网络拆分称为输入层节点,隐含层节点,输出层节点。这样能进行细致的更改,但却仅实现了单隐层,而这里的module则可以实现多隐层,但原作者也仅定义了单隐层(请先读后续的Linear,这是关键)
import numpy as np
import math
from collections import OrderedDict
class Module:
def __init__(self,lr=None):
self.linear1=Linear(7,15,batch_size=20)
self.linear1.first_layer=True
self.linear1.add_Motivation('sigmoid')
self.linear2=Linear(15,5,batch_size=20)
self.linear2.add_Motivation('relu')
self.linear3=Linear(5,1,batch_size=20)
self.linear3.last_layer=True
self.lr=1e-3 #学习率
if(lr!=None):
self.lr=lr
def forward(self,x): #对整个三层的前向预测
x=x.copy()
x=self.linear1.forward(x)
x=self.linear2.forward(x)
x=self.linear3.forward(x)
return x
def compute_gradient(self,x):
x=x.copy()
self.linear3.compute_local_gradient(x)
x=self.linear3.to_last.copy()
self.linear2.compute_local_gradient(x)
x=self.linear2.to_last.copy()
self.linear1.compute_local_gradient(x)
def backward(self):
self.linear3.backward(self.lr)
self.linear2.backward(self.lr)
self.linear1.backward(self.lr)
class Linear:
def __init__(self,in_feature,out_feature,batch_size=None,bias=None): #其中none表示可以选择传入,如果没有传入则其值为none
self.in_feature=in_feature #定义该层输入特征数量,也可以理解为该层接入的箭头多少个(可以看看图)
self.out_feature=out_feature #定义该层输出特征数量
self.Oin=None #用于存储输入数据x_i
self.Oout=None #用于存储该层的输出output
self.Onet=None #用于存储每一个神经元的加权和
self.bias=True #此处用ture表示在我们定义的类Linear中含有偏置项bias
self.batch_size=1 #batch_size指一次性输入给模型的样本数,即我在训练过程中训练集为10个,但由于此处batch_size=1,则一次迭代只取一个样本
if(batch_size!=None):
self.batch_size=batch_size
if(bias):
self.bias=bias
# 此处用于生成输出特征数量,输入特征数量+1的矩阵 按正常情况来说应该是o行i列,但多余的一列用于初始化偏置bias
self.Weights=np.random.randn(self.out_feature,self.in_feature+1)
self.local_gradient=None #local_gradient称为局部梯度
self.motivation=None #激活函数
self.last_layer=False #此处用于标识是否为最后一层
self.to_last=None
self.first_layer=None #此处用于标识是否为第一层
def sigmoid(self,x):
return 1/(1+np.exp(-x))
def relu(self,x):
return np.maximum(0,x)
def forward(self,x): #这里具体的x应为(in_feature,batch_size)维度
self.Oin=x.copy() #x表示传入的参数,而x.copy则表示将此参数拷贝
ones=np.ones((1,self.batch_size))
self.Oin=np.row_stack((self.Oin,ones)) #此处引入row_stack函数,将ones数据这一行拼接在self.Oin后一行上
self.Onet=self.Weights.dot(self.Oin) #将最开始随机生成的权重与后续拼接之后的self.Oin进行矩阵乘法计算,这样就能实现权重乘以输入x_i加上偏置
if(self.motivation==None):
self.Oout=self.Onet.copy() #没有激活函数则直接输出
elif(self.motivation=='sigmoid'):
self.Oout=self.sigmoid(self.Onet)
elif(self.motivation=='relu'):
self.Oout=self.relu(self.Onet)
return self.Oout
def add_Motivation(self,TT):
if(TT=='sigmoid'):
self.motivation='sigmoid'
elif(TT=='relu'):
self.motivation='relu'
def compute_local_gradient(self,x): #梯度计算
if(self.last_layer):
tp=-(x-self.Oout) #此处应为实际值与预测值的差值
self.local_gradient=tp*(self.motivation_back) #此处的motivation_back用于计算激活函数的导数,在后续定义函数
else:
#这里的self.local_gradient为该层计算得到的梯度,我们称之为局部梯度
self.local_gradient= self.motivation_back * x
to_l = []
for i in range(self.batch_size):
a=[]
for j in range(self.in_feature):
a.append(self.Weights[:,j]*self.local_gradient[:,i]) #这里计算得到权重更新值
to_l.append(np.sum(a,axis=1))
to_l = np.array(to_l).T
self.to_last = to_l.copy()
def backward(self,lr):
# 这里局部梯度乘以self.Oin的转置矩阵得到维度(out,in+1)每个权重的梯度,然后除以批次能够减小方差
g=self.local_gradient.dot(self.Oin.T/self.batch_size)
self.Weights=self.Weights-g*lr #更新得到新的权重
@property #此处用于声明定义的函数motivation_back为属性,即在调用此函数是self.motivation_back而不是motivation_back()
def motivation_back(self):
if(self.motivation):
if(self.motivation=='sigmoid'):
the_loss=self.sigmoid(self.Onet)*(1-self.sigmoid(self.Onet))
return the_loss
elif(self.motivation=='relu'):
tp: object=self.Onet.copy() #此处创建新的对象tp
tp[tp>0]=1
tp[tp<=0]=0
return tp #经过上述更新得到tp中大于0的数变为1,小于等于0的数变为0
else:
return np.ones(np.shape(self.Onet))