python和numpy纯手写BP神经神经网络
二级目录
三级目录 神经网络的基本理解神经网络公式
前向传播激活函数误差求解反向传播参数更新 神经网络python代码 python和numpy纯手写BP神经神经网络 二级目录 三级目录 神经网络的基本理解
神经网络分为三层,输入层,隐含层以及输出层。隐含层可以为多层神经网络。输入层为输入样本的数据维度(N*D),输出层为样本标签y,y可以为离散值(分类任务)也可以为连续值(回归任务)。
个人理解,神经网络的训练过程主要有四个步骤,包括:
(1)正向传播:通过多层线性网络结构(w_1,w_2,…w_n),将输入样本进行特征信息提取,降低原始数据的维度。
(2)激活函数:将每层网络线性求解的结果,进行非线性变换,实现特征信息的非线性转换。常见的激活函数:simgoid函数,relu函数,tanh函数等。
(3)误差求解:将模型求出的预测值,与真实值进行误差求解,得到模型预测值与真实值之间的差异,用于后续的反向传播。
(4)反向传播。根据误差值,调整模型参数w,使得模型的预测值接近于模型的真实值。
神经网络的主要原理就是通过不断调整模型参数,使得模型的预测值接近于真实值。
神经网络公式 神经网络结构如上图所示,下图是上图的公式理解,模型参数的设定,关键在于层层神经元的个数,也就是说上层的输入维度需与下一层的输入维度相同。
例:输入样本X为N个样本,3维特征,X-[N,3],第一层隐含层的w_1-[3,d],d为经过第一层网络后,对原始数据进行升维或者降维(特征提取),设w_1-[3,4],b_1-[1,4],进过第一层网络得到z_1,z_1-[N,4],通过激活函数,得到h_1,h_1便是第一层网络对原始数据特征提取后得到的结果。
第一层网络公式如下(输入层→第一层隐含层):
f()为激活函数
第二层网络公式如下(第一层隐含层→第二层隐含层):
第三层网络公式如下(第二层隐含层→输出层):
最后输出的是h3,(N,10),N是样本个数,10是类别种类个数。
激活函数前向传播中的f()就是激活函数
误差求解 计算模型输出值与真实值之间的误差。误差一般都是经过绝对值或者平方的数,以得的一个始终大于0得的数,以无限接近于0,作为预测精准度。本文以一个L2-LOSS为例:
反向传播是神经网络中的关键一步,如何将误差层层方向传递到各个网络层中的w是关键。求解dw_out,dw_2.dw_1.
(1)求解dw_out;
通过链式法则,将复合函数进行分解进行逐步求导。
h2-(5,N)
注意:1、这里用的激活函数是sigmoid函数,不同的激活函数,在该项中求得的结果是不一样的;2、h3矩阵与(1-h3)矩阵相乘,而不是点乘,这里是对矩阵中的每个元素进行求导,因此出来的结果是逐元素相乘。在求导过程中,有的是点乘,有的是相乘。
h2是输出层的输入值,(h3-y)(h3(1-h3))可以看做误差,所以dwout其实就是等于输入的转置点乘误差。(计算的时候,先计算误差,误差中的乘法运算是相乘。)
求解db_out;
db_out的输入只是单位矩阵I,其实就是将误差的所有行相加。
db就是误差的求和。
需要改变的梯度方向,其实就是输入的转置·误差
(2)求解dw_2;
将上一层的误差error_1通过上一层的参数w进行传递,并再乘以该层的激活函数的求导,便是这一层的误差。
这里w的转置,是为了与前面的维度相匹配,其实求导出来的是w还是w的转置,这个是根据分子布局还是分母分局来确定的,w和w的装置在这里其实是一样的,只是元素排列的方式不同。(个人理解)
(3)求解dw_1;
求出dw,db只是说明在某点参数w,b下降最快的方向,并不能确定下降多少距离。因此引入一个学习率a,用于表示下降的距离。学习率a不能过大,因为过大的话,有可能下降距离过大,导致错过最小极值点。
w_out=w_out-adw_out
b_out=b_out-adb_out
w_2=w_2-adw_2
b_2=b_2-adb_2
w_1=w_1-adw_1
b_1=b_1-adb_1
dw只是个变化方向,通过a来赋予距离,在于原始的w进行向量的加减,便可以达到新w在某一方向变化的距离。
神经网络python代码初始化模型参数w,b。利用np.random.randn随机生成0-1之间的数
def __init__(self,input_size,label_size): np.random.seed(6) self.w_1=np.random.randn(input_size,4) self.b_1=np.random.randn(1,4) self.w_2=np.random.randn(4,5) self.b_2=np.random.randn(1,5) self.w_out=np.random.randn(5,label_size) self.b_out=np.random.randn(1,label_size)
定义前向传播
def feed_forward(self,x): z_1=np.dot(x,self.w_1)+self.b_1 h_1=self.relu(z_1) ##print(h_1.shape) z_2=np.dot(h_1,self.w_2)+self.b_2 h_2=self.sigmoid(z_2) ##print(h_2.shape) z_3=np.dot(h_2,self.w_out)+self.b_out h_3=self.sigmoid(z_3) ##print(h_3.shape) return h_1,h_2,h_3
得到每一层神经网络的输出,并通过激活函数对输出进行非线性转换。
def back(self,x,y): h_1,h_2,h_3=self.feed_forward(x) error,loss=self.loss_l2(h_3,y) error_tans_out=np.multiply(error,self.de_sigmoid(h_3)) dw_out=np.dot(h_2.T,error_tans_out) db_out=np.sum(error_tans_out,axis=0,keepdims=True) error_trans_2=np.multiply(np.dot(error_tans_out,w_out.T),self.de_sigmoid(h_2)) dw_2=np.dot(h_1.T,error_trans_2) db_2=np.sum(error_trans_2,axis=0,keepdims=True) error_trans_3=np.multiply(np.dot(error_trans_2,w_2.T),self.relu(h_1)) dw_1=np.dot(x.T,error_trans_3) db_1=np.sum(error_trans_3,axis=0,keepdims=True) return dw_out,db_out,dw_2,db_2,dw_1,db_1
在反向传播中,一定要搞清楚点乘和叉乘。
参数更新:
def update_weight(self,w_1,b_1,w_2,b_2,w_out,b_out,learn_rate): self.w_1=self.w_1-learn_rate*dw_1 self.b_1=self.b_1-learn_rate*db_1 self.w_2=self.w_2-learn_rate*dw_2 self.b_2=self.b_2-learn_rate*db_2 self.w_out=self.w_out-learn_rate*dw_out self.b_out=self.b_out-learn_rate*db_out
损失函数:
def loss_l2(self,h,y): error=h-y error_2=0.5*(error*error) return error,np.sum(error_2)
激活函数与激活函数的求导
def relu(self,x): z = np.maximum(x, 0) return z def tanh(self,x): return np.tanh(x) def sigmoid(self,x): ex=np.exp(x) return ex/(ex+1) def de_relu(self,z,h): z[z <= 0] = 0 z[z > 0] = 1.0 return z def de_sigmoid(self,h): return h*(1-h)
训练过程
def train(self,epoch_num,x,y,learning_rate): losses=[] for i in range(epoch_num): h_1,h_2,h_3=self.feed_forward(x) error,error_2=self.loss_l2(h_3,y) dw_out,db_out,dw_2,db_2,dw_1,db_1=self.back(x,y) self.update_weight(dw_1,db_1,dw_2,db_2,dw_out,db_out,learning_rate) losses.append(error_2) if(i%20==0): print("iter:{},loss:{}".format(i,error_2)) return losses
所有代码:
class BPNN(object): def __init__(self,input_size,label_size): np.random.seed(6) self.w_1=np.random.randn(input_size,10) self.b_1=np.random.randn(1,10) self.w_2=np.random.randn(10,5) self.b_2=np.random.randn(1,5) self.w_out=np.random.randn(5,label_size) self.b_out=np.random.randn(1,label_size) def feed_forward(self,x): z_1=np.dot(x,self.w_1)+self.b_1 h_1=self.relu(z_1) ##print(h_1.shape) z_2=np.dot(h_1,self.w_2)+self.b_2 h_2=self.sigmoid(z_2) ##print(h_2.shape) z_3=np.dot(h_2,self.w_out)+self.b_out h_3=self.sigmoid(z_3) ##print(h_3.shape) return h_1,h_2,h_3 def back(self,x,y): h_1,h_2,h_3=self.feed_forward(x) error,loss=self.loss_l2(h_3,y) error_tans_out=np.multiply(error,self.de_sigmoid(h_3)) dw_out=np.dot(h_2.T,error_tans_out) db_out=np.sum(error_tans_out,axis=0,keepdims=True) error_trans_2=np.multiply(np.dot(error_tans_out,w_out.T),self.de_sigmoid(h_2)) dw_2=np.dot(h_1.T,error_trans_2) db_2=np.sum(error_trans_2,axis=0,keepdims=True) error_trans_3=np.multiply(np.dot(error_trans_2,w_2.T),self.relu(h_1)) dw_1=np.dot(x.T,error_trans_3) db_1=np.sum(error_trans_3,axis=0,keepdims=True) return dw_out,db_out,dw_2,db_2,dw_1,db_1 def update_weight(self,w_1,b_1,w_2,b_2,w_out,b_out,learn_rate): self.w_1=self.w_1-learn_rate*dw_1 self.b_1=self.b_1-learn_rate*db_1 self.w_2=self.w_2-learn_rate*dw_2 self.b_2=self.b_2-learn_rate*db_2 self.w_out=self.w_out-learn_rate*dw_out self.b_out=self.b_out-learn_rate*db_out def loss_l2(self,h,y): error=h-y error_2=0.5*(error*error) return error,np.sum(error_2) def relu(self,x): z = np.maximum(x, 0) return z def tanh(self,x): return np.tanh(x) def sigmoid(self,x): ex=np.exp(x) return ex/(ex+1) def de_relu(self,z,h): z[z <= 0] = 0 z[z > 0] = 1.0 return z def de_sigmoid(self,h): return h*(1-h) def train(self,epoch_num,x,y,learning_rate): losses=[] for i in range(epoch_num): h_1,h_2,h_3=self.feed_forward(x) error,error_2=self.loss_l2(h_3,y) dw_out,db_out,dw_2,db_2,dw_1,db_1=self.back(x,y) self.update_weight(dw_1,db_1,dw_2,db_2,dw_out,db_out,learning_rate) losses.append(error_2) if(i%20==0): print("iter:{},loss:{}".format(i,error_2)) return lossesif __name__ == '__main__': n_samples=10 n_feature=5 x=np.random.randn(n_samples,n_feature) y=np.array([1,2,2,3,2,3,1,3,1,3]) y=y-1 out_size=len(np.unique(y)) y_1=np.zeros(shape=(n_samples,out_size)) for i in range(len(y)): y_1[i][y[i]]=1 epoch_num = 10000 model=BPNN(5,3) losses = model.train(epoch_num=epoch_num,x=x,y=y_1,learning_rate=0.01)
关键点:
参数梯度变化的方向(dw)=输入的转置·误差
误差就是将上一层的误差通过上一层的w进行传递,在叉乘该层激活函数的导数。
参考文献:
王木头学科学:添加链接描述(非常宝藏的up主,通俗易懂的讲解原理)
鲁东大学课件:添加链接描述
python和numpy纯手写3层神经网络,干货满满:
BP神经网络以及在手写数字分类中python代码的详细注释