1、BP神经网络分类器
1.1 BP算法基本原理
神经网络结构大概如下图1-1:
图1-1
包括输入层,隐层和输出层。包含一层隐层的神经网络称为浅层神经网络,即SNN。包含多层隐层的神经网络称为深度神经网络,即DNN。理论上说单隐层神经网络可以逼近任何连续函数(只要隐层的神经元数足够多)。但从数学表达能力上,多隐层的神经网络工程效果更好。但过多的隐层和神经元节点,会带来过拟合问题。不要试图通过降低神经网络参数量来减缓过拟合,用正则化或者dropout。
1.2 神经网络之传递函数(激活函数)
对输入进行非线性变换,常用的传递函数,也是本实验所使用的,即S函数(sigmoid):
其作用在每个隐层与输出层。正如大脑中的神经网络,并非所有的外界信号都能引起我们强烈的反应。S函数的作用往往会对信号进行相应的筛选,放有用的信号通过神经元,将无用的信号压缩为零。对于通过的信号,控制其以多大的程度通过 。
1.3 算法公式推导
神经网络之BP算法:正向传播求损失,反向传播回传误差。根据误差信号修正每层的权重 与偏置
以3层感知器为例,来进行BP算法推导。其结构如图1-2:
图1-2
计算某一层的第j个单元,i和k分别为其前层和后层的单元,代表本层输出,为输入。
的输入:
的输出:
输出层误差:
误差展开至隐层 :
展开至输入层 :
神经网络之SGD(随机梯度下降):
误差E有了,如何调整权重让误差不断减小?由上述式子可知E是权重w的函数,我们需要找到使得函数值最小的w.
为了使连接权值沿着E 的梯度变化方向得以改善,网络逐渐收敛,取:
,其中 k=1,2,...,n3;j=1,2,...n2; i=1,2,...,n1
对于输出层:
其中:
对于隐层:
自此,公式推到完毕。公式推到中未添加偏置,在代码编程中添加。
2、实验举例:根据样本数据设计男女分类器
2.1 实验内容
本次实验提供的样本数据有410个,每个数据提取5个特征,即身高、体重、是否喜欢数学、是否喜欢文学及是否喜欢运动。将其中360个样本数据作为训练数据,另外50个数据作为测试数据。 将样本数据用于对BP神经网络分类器 。BP神经网络--自行编写代码完成后向传播算法,采用交叉验证的方式实现对于性能指标的评判(包含SE,SP,ACC和AUC,AUC的计算可以基于平台的软件包) 。
2.2 实验平台
本实验编程应用Python完成。其中,读取Excel数据应用了扩展工具包xlrd,BP算法中前向运算中涉及到矩阵的运算,应用到了扩展工具包numpy。
2.3算法流程图
2.4实验结果
输入360个样本数据进行训练后(循环重复训练10次),输入50个测试数据进行测试,测试结果如表2-1所示。通过数据可以看出共50个测试数据,其中有两个正样本被分为负样本,即男生分类成了女生。其中ROC曲线(受试者工作特性曲线)如图2-1,可见该曲线的AUC值为0.96表示分类器的效果是较好的,从敏感性(SE)、特异性(SP)和准确率(ACC)也可以看出分类器的效果还是不错的。
表2-1
图2-2
2.4 源代码-Python
# @Date : -10-18 17:37:02# @Author : Taylen Lee # @Version : 0.1# @modify :# @Description:import xlrdimport math import randomimport numpy as npimport matplotlib.pyplot as pltfrom sklearn import svmfrom sklearn import treefrom sklearn import metricsfrom sklearn import model_selection'''/**************************task1**************************/采用BP神经网络设计男女生分类器。自行编写代码完成后向传播算法注:本次设计的BP算法分类器指针带5-5-1的神经网络结构,不具有通用化,后续仍需慢慢更改,不断完善。/**************************task1**************************/'''#神经元使用Sigmoid函数作为激活函数,其公式为:def sigmoid(parameter_x):parameter_x_dimensions=parameter_x.shapefor i in range(parameter_x_dimensions[0]):parameter_x[i,0]=1.0/(1+math.exp(-parameter_x[i,0]))#parameter_x_modify=parameter_xreturn parameter_x#数据归一化,将身高,体重映射到[0,1]之间def Data_normalization(input_array):Normalization_data=[]for x in input_array:Normalization_data.append((x-min(input_array))/(max(input_array)-min(input_array)))return Normalization_data# 神经网络类class NeuralNetwork:#设置学习率Learning_Rate = 0.5def __init__(self, sizes):''':sizes:list类型,存储每层神经网络的神经元数目本次实验输入层有5个神经单元,隐层有5个神经单元,1个输出神经单元。故sizes=[5,5,1]'''#获取该神经网络各层层数self.sizes=sizesself.num_input_layers=sizes[0]self.num_hidden_layers=sizes[1]self.num_output_layers=sizes[2]#随机产生隐层与输出层中每个神经元的偏置(0-1)self.bias=[np.random.randn(i,1) for i in sizes[1:]]#随机产生每条连线的权重self.weights=[np.random.randn(i,j) for i,j in zip(sizes[:-1],sizes[1:])]def forward_propagation(self,inputs):"""前向传播"""self.inputs=inputsself.Neural_Output_All_Layer=[]for b,w in zip(self.bias,self.weights):inputs=sigmoid(np.dot(w.T,inputs)+b)self.Neural_Output_All_Layer.append(inputs)return self.Neural_Output_All_Layerdef calculate_error(self, target_output):#输出的每个神经元的误差由平均平方误差法计算(cost function)Neural_Output_Lastlayer=self.Neural_Output_All_Layer[1]return 0.5*(target_output - Neural_Output_Lastlayer[0,0]) ** 2def calculate_local_gradient_delta(self,target_output):'''我们可以根据cost function对神经元激活函数输出值Oⱼ的偏导数和激活函数输出值Oⱼ对激活函数输入值z=wx+b的偏导数计算delta(δ).δⱼ = ∂E/∂netⱼ = ∂E/∂Oⱼ * dOⱼ/dnetⱼ 关键key,即求局部梯度'''Neural_Output_Lastlayer=self.Neural_Output_All_Layer[1]return (-(target_output - Neural_Output_Lastlayer[0,0]))*\(Neural_Output_Lastlayer[0,0]*(1 - Neural_Output_Lastlayer[0,0]))#def train(self, training_inputs, training_outputs):def train(self, training_outputs):'''使用在线学习方式,训练每个实例之后对权值进行更新反向传播'''# 1. Output neuron deltas输出层deltaslocal_gradient_delta_output_layler=self.calculate_local_gradient_delta(training_outputs)# 2. Hidden neuron deltas隐藏层deltasNeural_Output_Hidden_Layer=self.Neural_Output_All_Layer[0]local_gradient_delta_hidden_layler=[0]*self.num_hidden_layersfor h in range(self.num_hidden_layers):'''隐层节点不是输出节点, 其输出对后层的全部节点都有影响;我们需要计算误差对每个隐藏层神经元的输出的导数,由于不是输出层所以∂E/∂Oⱼ需要根据下一层反向进行计算,即根据输出层的函数进行计算∂E/∂Oⱼ = Σ ∂E/∂net_k * ∂net_k/∂Oⱼ = Σ ∂E/∂net_k * w_jk = Σ δ_k * w_jk(Σ以k为累加下标)'''error_to_hidden_neuron_output_derivative=local_gradient_delta_output_layler*self.weights[1][h,0]# δⱼ = ∂E/∂netⱼ=∂E/∂Oⱼ * dOⱼ/dnetⱼlocal_gradient_delta_hidden_layler[h] = error_to_hidden_neuron_output_derivative*\(Neural_Output_Hidden_Layer[h,0]*(1 - Neural_Output_Hidden_Layer[h,0]))# 3. Update output neuron weights 更新输出层权重for w_ho in range(len(self.weights[1])):# 注意:输出层权重是隐藏层神经元与输出层神经元连接的权重self.weights[1][w_ho,0]-=self.Learning_Rate*(local_gradient_delta_output_layler*self.Neural_Output_All_Layer[0][w_ho,0])# 4. Update hidden neuron weights 更新隐藏层权重for h in range(self.num_hidden_layers):for w_ih in range(len(self.weights[0])):# 注意:隐藏层权重是输入层神经元与隐藏层神经元连接的权重self.weights[0][w_ih,h]-=self.Learning_Rate*(local_gradient_delta_hidden_layler[h]*self.inputs[w_ih,0])# 5. Update output neuron bias 更新输出层偏值self.bias[1][0,0]-=self.Learning_Rate*local_gradient_delta_output_layler# 6. Update hidden neuron bias 更新隐藏层偏值for h in range(self.num_hidden_layers):self.bias[0][h,0]-=self.Learning_Rate*local_gradient_delta_hidden_layler[h]#从excel中读取样本数据mydata = xlrd.open_workbook('D:/program/py_code/data_.xls')mysheet1 = mydata.sheet_by_name("Sheet1")#获取行数、列数nRows=mysheet1.nrowsnCols=mysheet1.ncolsheight=[]weight=[]likemath=[]likeliterature=[]likesport=[]lable_gender=[]#男1女0#存储相应的样本数据for i in range(nRows):if i+1<nRows:height.append(mysheet1.cell(i+1,3).value)weight.append(mysheet1.cell(i+1,4).value)likemath.append(mysheet1.cell(i+1,6).value)likeliterature.append(mysheet1.cell(i+1,7).value)likesport.append(mysheet1.cell(i+1,8).value)lable_gender.append(mysheet1.cell(i+1,1).value)#将样本的身高、体重数据归一化height_normalization=Data_normalization(height)weight_normalization=Data_normalization(weight)#将以上获取的个样本特征数据合并为array型矩阵Total_sample_array=np.vstack((np.array(height_normalization),np.array(weight_normalization),np.array(likemath),np.array(likeliterature),np.array(likesport)))My_NeuralNetwork=NeuralNetwork([5,5,1])#神经网络类实例化Category_division_threshold=0.5#样本类别判断阈值#男1女0,设男生为正样本,女生为度样本Test_results_TP=0#表示预测正确的正样本数Test_results_FN=0#表示预测错误的正样本数Test_results_TN=0#表示预测正确的负样本数Test_results_FP=0#表示预测错误的负样本数#训练集Train_num=360for j in range(10):for i in range(Train_num):g=My_NeuralNetwork.forward_propagation(Total_sample_array[:,i:i+1])My_NeuralNetwork.train(lable_gender[i])error=My_NeuralNetwork.calculate_error(lable_gender[i])#测试集Test_num=50scores=[]#每个测试样本属于正样本的概率for i in range(Test_num):Predict_result=My_NeuralNetwork.forward_propagation(Total_sample_array[:,360+i:i+361])[1][0,0]scores.append(Predict_result)if (Predict_result>=Category_division_threshold) and (lable_gender[i+360]==1):Test_results_TP+=1elif (Predict_result<Category_division_threshold) and (lable_gender[i+360]==1):Test_results_FN+=1elif (Predict_result<Category_division_threshold) and (lable_gender[i+360]==0):Test_results_TN+=1elif (Predict_result>=Category_division_threshold) and (lable_gender[i+360]==0):Test_results_FP+=1def Display_performance_index(results_TP,results_FN,results_TN,results_FP,test_num,lable_test,lable_pred_test):print('TP:',results_TP)print('FN:',results_FN)print('TN:',results_TN)print('FP:',results_FP)results_SE=results_TP/(results_TP+results_FN)#敏感性,真正的正样本中有多少比列可以被正确划分出来results_SP=results_TN/(results_TN+results_FP)#特异性,真正的负样本中有多少比列可以被正确划分出来results_ACC=(results_TP+results_TN)/test_num#准确率print('SE:',results_SE)print('SP:',results_SP)print('ACC:',results_ACC)'''利用库sklearn来计算AUC.使用 AUC 值作为评价标准是因为很多时候 ROC 曲线并不能清晰的说明哪个分类器的效果更好,而作为一个数值,对应 AUC 更大的分类器效果更好。'''fpr, tpr, thresholds = metrics.roc_curve(lable_test,lable_pred_test)results_AUC=metrics.auc(fpr,tpr)print('AUC:',results_AUC)plt.figure()plt.figure(figsize=(10,10))plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC curve (area = %0.2f)' % results_AUC) #假正率为横坐标,真正率为纵坐标做曲线plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')plt.xlim([0.0, 1.0])plt.ylim([0.0, 1.05])plt.xlabel('False Positive Rate')plt.ylabel('True Positive Rate')plt.legend(loc="lower right")plt.show()Display_performance_index(Test_results_TP,Test_results_FN,Test_results_TN,Test_results_FP,Test_num,lable_gender[360:], np.array(scores))