numpy实现神经网络反向传播算法的步骤
一、任务
实现一个4层的全连接网络实现二分类任务,网络输入节点数为2,隐藏层的节点数设计为:25,50,25,输出层2个节点,分别表示属于类别1的概率和类别2的概率,如图所示。我们并没有采用Softmax函数将网络输出概率值之和进行约束,而是直接利用均方差误差函数计算与One-hot编码的真实标签之间的误差,所有的网络激活函数全部采用Sigmoid函数,这些设计都是为了能直接利用梯度推导公式。
二、数据集
通过scikit-learn库提供的便捷工具生成2000个线性不可分的2分类数据集,数据的特征长度为2,采样出的数据分布如图所示,所有的红色点为一类,所有的蓝色点为一类,可以看到数据的分布呈月牙状,并且是是线性不可分的,无法用线性网络获得较好效果。为了测试网络的性能,按照7:3比例切分训练集和测试集,其中2000∗03=600个样本点用于测试,不参与训练,剩下的1400个点用于网络的训练。
importmatplotlib.pyplotasplt importseabornassns#要注意的是一旦导入了seaborn,matplotlib的默认作图风格就会被覆盖成seaborn的格式 fromsklearn.datasetsimportmake_moons fromsklearn.model_selectionimporttrain_test_split N_SAMPLES=2000#采样点数 TEST_SIZE=0.3#测试数量比率 #利用工具函数直接生成数据集 X,y=make_moons(n_samples=N_SAMPLES,noise=0.2,random_state=100) #将2000个点按着7:3分割为训练集和测试集 X_train,X_test,y_train,y_test=train_test_split(X,y, test_size=TEST_SIZE,random_state=42) print(X.shape,y.shape) #绘制数据集的分布,X为2D坐标,y为数据点的标签 defmake_plot(X,y,plot_name,file_name=None,XX=None,YY=None,preds=None,dark=False): if(dark): plt.style.use('dark_background') else: sns.set_style("whitegrid") plt.figure(figsize=(16,12)) axes=plt.gca() axes.set(xlabel="$x_1$",ylabel="$x_2$") plt.title(plot_name,fontsize=30) plt.subplots_adjust(left=0.20) plt.subplots_adjust(right=0.80) if(XXisnotNoneandYYisnotNoneandpredsisnotNone): plt.contourf(XX,YY,preds.reshape(XX.shape),25,alpha=1,cmap=plt.cm.Spectral) plt.contour(XX,YY,preds.reshape(XX.shape),levels=[.5],cmap="Greys",vmin=0,vmax=.6) #绘制散点图,根据标签区分颜色 plt.scatter(X[:,0],X[:,1],c=y.ravel(),s=40,cmap=plt.cm.Spectral,edgecolors='none') plt.savefig('dataset.svg') plt.close() #调用make_plot函数绘制数据的分布,其中X为2D坐标,y为标签 make_plot(X,y,"ClassificationDatasetVisualization") plt.show()
三、网络层
通过新建类Layer实现一个网络层,需要传入网络层的数据节点数,输出节点数,激活函数类型等参数,权值weights和偏置张量bias在初始化时根据输入、输出节点数自动生成并初始化:
classLayer: #全连接网络层 def__init__(self,n_input,n_neurons,activation=None,weights=None, bias=None): """ :paramintn_input:输入节点数 :paramintn_neurons:输出节点数 :paramstractivation:激活函数类型 :paramweights:权值张量,默认类内部生成 :parambias:偏置,默认类内部生成 """ #通过正态分布初始化网络权值,初始化非常重要,不合适的初始化将导致网络不收敛 self.weights=weightsifweightsisnotNoneelse np.random.randn(n_input,n_neurons)*np.sqrt(1/n_neurons) self.bias=biasifbiasisnotNoneelsenp.random.rand(n_neurons)*0.1 self.activation=activation#激活函数类型,如'sigmoid' self.last_activation=None#激活函数的输出值o self.error=None#用于计算当前层的delta变量的中间变量 self.delta=None#记录当前层的delta变量,用于计算梯度 defactivate(self,x): #前向传播 r=np.dot(x,self.weights)+self.bias#X@W+b #通过激活函数,得到全连接层的输出o self.last_activation=self._apply_activation(r) returnself.last_activation #其中self._apply_activation实现了不同的激活函数的前向计算过程: def_apply_activation(self,r): #计算激活函数的输出 ifself.activationisNone: returnr#无激活函数,直接返回 #ReLU激活函数 elifself.activation=='relu': returnnp.maximum(r,0) #tanh elifself.activation=='tanh': returnnp.tanh(r) #sigmoid elifself.activation=='sigmoid': return1/(1+np.exp(-r)) returnr #针对于不同的激活函数,它们的导数计算实现如下: defapply_activation_derivative(self,r): #计算激活函数的导数 #无激活函数,导数为1 ifself.activationisNone: returnnp.ones_like(r) #ReLU函数的导数实现 elifself.activation=='relu': grad=np.array(r,copy=True) grad[r>0]=1. grad[r<=0]=0. returngrad #tanh函数的导数实现 elifself.activation=='tanh': return1-r**2 #Sigmoid函数的导数实现 elifself.activation=='sigmoid': returnr*(1-r) returnr
四、网络模型
完成单层网络类后,再实现网络模型的类NeuralNetwork,它内部维护各层的网络层Layer类对象,可以通过add_layer函数追加网络层,实现如下:
classNeuralNetwork: #神经网络大类 def__init__(self): self._layers=[]#网络层对象列表 defadd_layer(self,layer): #追加网络层 self._layers.append(layer) #网络的前向传播只需要循环调用个网络层对象的前向计算函数即可 deffeed_forward(self,X): #前向传播 forlayerinself._layers: #依次通过各个网络层 X=layer.activate(X) returnX #网络模型的反向传播实现稍复杂,需要从最末层开始,计算每层的