模式识别:BP神经网络算法

1、BP神经网络分类器

1.1 BP算法基本原理

神经网络结构大概如下图1-1:

                                                                                                  图1-1

包括输入层,隐层和输出层。包含一层隐层的神经网络称为浅层神经网络,即SNN。包含多层隐层的神经网络称为深度神经网络,即DNN。理论上说单隐层神经网络可以逼近任何连续函数(只要隐层的神经元数足够多)。但从数学表达能力上,多隐层的神经网络工程效果更好。但过多的隐层和神经元节点,会带来过拟合问题。不要试图通过降低神经网络参数量来减缓过拟合,用正则化或者dropout。

1.2 神经网络之传递函数(激活函数)

对输入进行非线性变换,常用的传递函数,也是本实验所使用的,即S函数(sigmoid): f(x)=\frac{1}{1+e^{-x}}

其作用在每个隐层与输出层。正如大脑中的神经网络,并非所有的外界信号都能引起我们强烈的反应。S函数的作用往往会对信号进行相应的筛选,放有用的信号通过神经元,将无用的信号压缩为零。对于通过的信号,控制其以多大的程度通过 。

1.3 算法公式推导

神经网络之BP算法:正向传播求损失,反向传播回传误差。根据误差信号修正每层的权重 与偏置

以3层感知器为例,来进行BP算法推导。其结构如图1-2:

                                                                                                      图1-2

计算某一层的第j个单元,i和k分别为其前层和后层的单元,O_j代表本层输出,net_j为输入。

j的输入: net_j=\sum_{i}w_{ij}O_i

j的输出:O_j=f(net_j)

输出层误差: E=\frac{1}{2}(d-O)^2=\frac{1}{2}\sum_{k=1}(d_k-O_k)^2

误差展开至隐层 :E=\frac{1}{2}\sum_{k=1}^{n3}[d_k-f(net_k)]^2=\frac{1}{2}\sum_{k=1}^{n3}[d_k-f(\sum_{j=1}^{n2}w_{jk}O_j)]^2

展开至输入层 :E=\frac{1}{2}\sum_{k=1}^{n3}[d_k-f(\sum_{j=1}^{n2}w_{jk}f(net_j))]^2=\frac{1}{2}\sum_{k=1}^{n3}[d_k-f(\sum_{j=1}^{n2}w_{jk}f(\sum_{i=1}^{n1}w_{ij}x_i))]^2

神经网络之SGD(随机梯度下降):

误差E有了,如何调整权重让误差不断减小?由上述式子可知E是权重w的函数,我们需要找到使得函数值最小的w.

为了使连接权值沿着E 的梯度变化方向得以改善,网络逐渐收敛,取:

\Delta w_{jk}=-\eta\frac{\partial E}{\partial w_{jk}}\space ,\Delta w_{ij}=-\eta\frac{\partial E}{\partial w_{ij}}

w_{jk}(t+1)=w_{jk}+\Delta w_{jk}(t),w_{ij}(t+1)=w_{ij}+\Delta w_{ij}(t),其中 k=1,2,...,n3;j=1,2,...n2; i=1,2,...,n1

对于输出层:

\frac{\partial E}{\partial w_{jk}}=\frac{\partial E}{\partial O_k}\frac{\partial O_k}{\partial net_k}\frac{\partial net_k}{\partial w_{jk}}

其中:

\frac{\partial E}{\partial O_k}\frac{\partial O_k}{\partial net_k}=\frac{\partial E}{\partial net_k}=\delta_k

\frac{\partial E}{\partial O_k}=-(d_k-O_k),\frac{\partial O_k}{\partial net_k}=O_k(1-O_k),\frac{\partial net_k}{\partial w_{jk}}=O_j

\Delta w_{jk}=-\eta (-(d_k-O_k))O_k(1-O_k)O_j

对于隐层:

\frac{\partial E}{\partial w_{ij}}=\frac{\partial E}{\partial O_j}\frac{\partial O_j}{\partial net_j}\frac{\partial net_j}{\partial w_{ij}}=\delta_jO_i=\delta_jx_i

\frac{\partial E}{\partial O_j}=\sum_{k}^{n3}\frac{\partial E}{\partial O_k}\frac{\partial O_k}{\partial net_k}\frac{\partial net_k}{\partial O_j}=\sum_{k}^{n3}\delta_kw_{jk}

\frac{\partial O_j}{\partial net_j}=f^{'}(net_j)=O_j(1-O_j)

\frac{\partial E}{\partial w_{ij}}=x_iO_j(1-O_j)\sum_{k}^{n3}\delta_kw_{jk}

自此,公式推到完毕。公式推到中未添加偏置,在代码编程中添加。

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)也可以看出分类器的效果还是不错的。

TP

FN

TN

FP

SE

SP

ACC

39

0

6

5

1

0.54

0.9

                                                              表2-1

                                                                                                 图2-2

2.4 源代码-Python

# @Date    : 2018-10-18 17:37:02
# @Author  : Taylen Lee 
# @Version : 0.1
# @modify  :
# @Description:


'''
/**************************task1**************************/
采用BP神经网络设计男女生分类器。自行编写代码完成后向传播算法
注:本次设计的BP算法分类器指针带5-5-1的神经网络结构,不具有通
用化,后续仍需慢慢更改,不断完善。
/**************************task1**************************/
'''
#神经元使用Sigmoid函数作为激活函数,其公式为:
def sigmoid(parameter_x):
	parameter_x_dimensions=parameter_x.shape
	for i in range(parameter_x_dimensions[0]):
		parameter_x[i,0]=1.0/(1+math.exp(-parameter_x[i,0]))
	#parameter_x_modify=parameter_x
	return 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.5

	def __init__(self, sizes):
		'''
		:sizes:list类型,存储每层神经网络的神经元数目
		本次实验输入层有5个神经单元,隐层有5个神经单元,1个输出神经单元。
		故sizes=[5,5,1]
		'''
		#获取该神经网络各层层数
		self.sizes=sizes
		self.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=inputs
		self.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_Layer

	def 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]) ** 2

	def 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输出层deltas
		local_gradient_delta_output_layler=self.calculate_local_gradient_delta(training_outputs)

        # 2. Hidden neuron deltas隐藏层deltas
		Neural_Output_Hidden_Layer=self.Neural_Output_All_Layer[0]
		local_gradient_delta_hidden_layler=[0]*self.num_hidden_layers
		for 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_2018.xls')
mysheet1 = mydata.sheet_by_name("Sheet1")
#获取行数、列数
nRows=mysheet1.nrows
nCols=mysheet1.ncols

height=[]
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=360
for 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=50

scores=[]	#每个测试样本属于正样本的概率
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+=1
	elif (Predict_result<Category_division_threshold) and (lable_gender[i+360]==1):
		Test_results_FN+=1
	elif (Predict_result<Category_division_threshold) and (lable_gender[i+360]==0):
		Test_results_TN+=1
	elif (Predict_result>=Category_division_threshold) and (lable_gender[i+360]==0):
		Test_results_FP+=1
def 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))

猜你喜欢

转载自blog.csdn.net/u011591807/article/details/83384589
今日推荐