classification_BPNeuralNetwork
本文介绍了通过 Python 实现 BP 神经网络分类算法,对不同半径的圆进行多分类(3 分类),特征即为圆的半径。 输入层 12 节点,一个 6 节点的隐藏层,输出层 3 个节点。
1.目标
通过 BP 算法实现对不同半径的圆的分类。
2.开发环境
IDE:PyCharm 2018.3.3(Community Edition) Python 及相关库的版本号如下图所示:
3.准备数据
目的: 生成 3 类圆在第一象限内的坐标(圆心都是原点) 第 1 类:半径范围为 1~10,分类标识为‘0’ 第 2 类:半径范围为 10~20,分类标识为‘1’ 第 3 类:半径范围为 20~30,分类标识为‘2’
代码如下:data_generate.py
importnumpyasnpimportmathimportrandomimportcsv# 只生成第一象限内的坐标即可。每个圆生成12个坐标(x,y),相当于12个特征维度defgenerate_circle(lower,upper):# 圆在第一象限内的坐标data_ur=np.zeros(shape=(12,2))# 在上下限范围内,随机产生一个值作为半径radius=random.randint(int(lower),int(upper))# 在0~90度内,每隔7.5度取一次坐标,正好取12次angles=np.arange(0,0.5*np.pi,1/24*np.pi)foriinrange(12):temp_ur=np.zeros(2)x=round(radius*math.cos(angles[i]),2)y=round(radius*math.sin(angles[i]),2)temp_ur[0]=xtemp_ur[1]=ydata_ur[i]=temp_urreturndata_ur,label# 将坐标保存到CSV文件中defsave2csv(data,batch,label):out=open("D:\\circles.csv",'a',newline='')csv_write=csv.writer(out,dialect='excel')length=int(data.size/2)foriinrange(length):string=str(data[i][0])+','+str(data[i][1])+','+str(batch)+','+str(label)temp=string.split(',')csv_write.writerow(temp)out.close()if__name__=="__main__":''' 生成3类圆,标签(label)分别为:0、1、2 第1类圆的半径下限为1,上限为10 第2类圆的半径下限为10,上限为20 第3类圆的半径下限为20,上限为30 圆心都为原点 '''lower=[1,10,20]# 半径随机值的下限upper=[10,20,30]# 半径随机值的上限label=['0','1','2']# 种类的标签foriinrange(len(label)):# 每类数据生成50组forjinrange(50):data,label=generate_circle(lower[i],upper[i])batch=50*i+j+1# 数据的批次,用来区分每个坐标是属于哪个圆的save2csv(data,batch,label[i])
共 3 类圆,每类生成 50 个圆,每个圆有 12 个坐标,因此在输出文件 D:\circles.csv 中总共有 3×50×12=1800 行数据:
通过生成的坐标绘制散点图如下:
图中蓝色的点是 label 为 0 的圆,绿色的点是 label 为 1 的圆,红色的点是 label 为 2 的圆。
4.处理数据
目标: 根据第 3 步获得的坐标,计算每个圆的半径(勾股定理)作为神经网络的输入。
代码如下:data_process.py
importcsvimportmathdefprocess(file_name):# 要读取的CSV文件csv_file=csv.reader(open(file_name,encoding='utf-8'))# 要生成的CSV文件out_file=open("D:\\circles_data.csv",'a',newline='')csv_write=csv.writer(out_file,dialect='excel')# 将csv_file每一行的圆坐标取出,如果是同一批次的(同一个圆),则写入到out_file的一行中rows=[rowforrowincsv_file]current_batch='unknown'current_label='unknown'data_list=[]forrinrows:# 将无关字符都替换为空格temp_string=str(r).replace('[','').replace(']','').replace('\'','')# 将字符串以逗号分隔item=str(temp_string).split(',')# 分别取出x轴坐标、y轴坐标、批次、标签x=float(item[0])y=float(item[1])batch=item[2]label=item[3]# 如果是同一批次(同一个圆),则都放入data_list中ifcurrent_batch==batch:# 根据勾股定理计算半径distance=math.sqrt(pow(x,2)+pow(y,2))data_list.append(distance)# 如果不是同一批次(同一个圆),则在末尾加上标签后,作为一行写入输出文件else:iflen(data_list)!=0:# 这个地方需注意一下,最后的标签用3列来表示,而不是一列iflabel.strip()=='0':data_list.append(1)data_list.append(0)data_list.append(0)eliflabel.strip()=='1':data_list.append(0)data_list.append(1)data_list.append(0)else:data_list.append(0)data_list.append(0)data_list.append(1)result_string=str(data_list).replace('[','').replace(']','').replace('\'','').strip()csv_write.writerow(result_string.split(','))# 清空data_list,继续写入下一个批次data_list.clear()distance=math.sqrt(pow(x,2)+pow(y,2))data_list.append(distance)current_batch=batchcurrent_label=label# 确保最后一个批次的数据能写入ifcurrent_label.strip()=='0':data_list.append(1)data_list.append(0)data_list.append(0)elifcurrent_label.strip()=='1':data_list.append(0)data_list.append(1)data_list.append(0)else:data_list.append(0)data_list.append(0)data_list.append(1)result_string=str(data_list).replace('[','').replace(']','').replace('\'','').strip()csv_write.writerow(result_string.split(','))# 关闭输出文件out_file.close()if__name__=="__main__":process('D:\\circles.csv')
需要注意的是,生成的 CSV 文件共有 15 列,前 12 列为坐标对应的半径值,最后三列组合起来表示分类(label):
(1,0,0)表示类型为“0”的圆,(0,1,0)表示类型为“1”的圆,(0,0,1)表示类型为“2”的圆,这样做的目的是为了下一步使用神经网络时处理起来方便。
5.构建 BP 神经网络
上一步处理好的数据可以作为训练数据,命名为:circles_data_training.csv 重复第 3 步和第 4 步,可以生成另一批数据作为测试数据,命名为:circles_data_test.csv 当然,也可以手动划分出训练数据和测试数据。 训练数据和测试数据在输入时,做了矩阵的转置,将列转置为行。
代码如下:data_analysis_bpnn.py
importpandasaspdimportnumpyasnpimportdatetimefromsklearn.utilsimportshuffle# 1.初始化参数definitialize_parameters(n_x,n_h,n_y):np.random.seed(2)# 权重和偏置矩阵w1=np.random.randn(n_h,n_x)*0.01b1=np.zeros(shape=(n_h,1))w2=np.random.randn(n_y,n_h)*0.01b2=np.zeros(shape=(n_y,1))# 通过字典存储参数parameters={'w1':w1,'b1':b1,'w2':w2,'b2':b2}returnparameters# 2.前向传播defforward_propagation(X,parameters):w1=parameters['w1']b1=parameters['b1']w2=parameters['w2']b2=parameters['b2']# 通过前向传播来计算a2z1=np.dot(w1,X)+b1# 这个地方需注意矩阵加法:虽然(w1*X)和b1的维度不同,但可以相加a1=np.tanh(z1)# 使用tanh作为第一层的激活函数z2=np.dot(w2,a1)+b2a2=1/(1+np.exp(-z2))# 使用sigmoid作为第二层的激活函数# 通过字典存储参数cache={'z1':z1,'a1':a1,'z2':z2,'a2':a2}returna2,cache# 3.计算代价函数defcompute_cost(a2,Y,parameters):m=Y.shape[1]# Y的列数即为总的样本数# 采用交叉熵(cross-entropy)作为代价函数logprobs=np.multiply(np.log(a2),Y)+np.multiply((1-Y),np.log(1-a2))cost=-np.sum(logprobs)/mreturncost# 4.反向传播(计算代价函数的导数)defbackward_propagation(parameters,cache,X,Y):m=Y.shape[1]w2=parameters['w2']a1=cache['a1']a2=cache['a2']# 反向传播,计算dw1、db1、dw2、db2dz2=a2-Ydw2=(1/m)*np.dot(dz2,a1.T)db2=(1/m)*np.sum(dz2,axis=1,keepdims=True)dz1=np.multiply(np.dot(w2.T,dz2),1-np.power(a1,2))dw1=(1/m)*np.dot(dz1,X.T)db1=(1/m)*np.sum(dz1,axis=1,keepdims=True)grads={'dw1':dw1,'db1':db1,'dw2':dw2,'db2':db2}returngrads# 5.更新参数defupdate_parameters(parameters,grads,learning_rate=0.0075):w1=parameters['w1']b1=parameters['b1']w2=parameters['w2']b2=parameters['b2']dw1=grads['dw1']db1=grads['db1']dw2=grads['dw2']db2=grads['db2']# 更新参数w1=w1-dw1*learning_rateb1=b1-db1*learning_ratew2=w2-dw2*learning_rateb2=b2-db2*learning_rateparameters={'w1':w1,'b1':b1,'w2':w2,'b2':b2}returnparameters# 建立神经网络defnn_model(X,Y,n_h,n_input,n_output,num_iterations=10000,print_cost=False):np.random.seed(3)n_x=n_input# 输入层节点数n_y=n_output# 输出层节点数# 1.初始化参数parameters=initialize_parameters(n_x,n_h,n_y)# 梯度下降循环foriinrange(0,num_iterations):# 2.前向传播a2,cache=forward_propagation(X,parameters)# 3.计算代价函数cost=compute_cost(a2,Y,parameters)# 4.反向传播grads=backward_propagation(parameters,cache,X,Y)# 5.更新参数parameters=update_parameters(parameters,grads)# 每1000次迭代,输出一次代价函数ifprint_costandi%1000==0:print('迭代第%i次,代价函数为:%f'%(i,cost))returnparameters# 对模型进行测试defpredict(parameters,x_test,y_test):w1=parameters['w1']b1=parameters['b1']w2=parameters['w2']b2=parameters['b2']z1=np.dot(w1,x_test)+b1a1=np.tanh(z1)z2=np.dot(w2,a1)+b2a2=1/(1+np.exp(-z2))# 结果的维度n_rows=y_test.shape[0]n_cols=y_test.shape[1]# 预测值结果存储output=np.empty(shape=(n_rows,n_cols),dtype=int)# 取出每条测试数据的预测结果foriinrange(n_cols):# 将每条测试数据的预测结果(概率)存为一个行向量temp=np.zeros(shape=n_rows)forjinrange(n_rows):temp[j]=a2[j][i]# 将每条结果(概率)从小到大排序,并获得相应下标sorted_dist=np.argsort(temp)length=len(sorted_dist)# 将概率最大的置为1,其它置为0forkinrange(length):ifk==sorted_dist[length-1]:output[k][i]=1else:output[k][i]=0print('预测结果:')print(output)print('真实结果:')print(y_test)count=0forkinrange(0,n_cols):ifoutput[0][k]==y_test[0][k]andoutput[1][k]==y_test[1][k]andoutput[2][k]==y_test[2][k]:count=count+1acc=count/int(y_test.shape[1])*100print('准确率:%.2f%%'%acc)if__name__=="__main__":# 读取数据data_set=pd.read_csv('D:\\circles_data_training.csv',header=None)data_set=shuffle(data_set)# 打乱数据的输入顺序# 取出“特征”和“标签”,并做了转置,将列转置为行X=data_set.ix[:,0:11].values.T# 前12列是特征Y=data_set.ix[:,12:14].values.T# 后3列是标签Y=Y.astype('uint8')# 开始训练start_time=datetime.datetime.now()# 输入12个节点,隐层6个节点,输出3个节点,迭代10000次parameters=nn_model(X,Y,n_h=6,n_input=12,n_output=3,num_iterations=10000,print_cost=True)end_time=datetime.datetime.now()print("用时:"+str((end_time-start_time).seconds)+'s'+str(round((end_time-start_time).microseconds/1000))+'ms')# 对模型进行测试data_test=pd.read_csv('D:\\circles_data_test.csv',header=None)x_test=data_test.ix[:,0:11].values.Ty_test=data_test.ix[:,12:14].values.Ty_test=y_test.astype('uint8')predict(parameters,x_test,y_test)
上述代码可以参考这篇文章:纯 Python 实现鸢尾属植物数据集神经网络模型。
代码中需要注意的几个关键参数:
- learning_rate=0.0075,学习率(可调)
- n_h=6,隐藏层节点数(可调)
- n_input=12,输入层节点数
- n_output=3,输出层节点数
- num_iterations=10000,迭代次数(可调)
另外,对于 predict(parameters, x_test, y_test) 函数需要说明一下: a2 矩阵是最终的预测结果,但是是以概率的形式表示的(可以打印看一下)。通过比较 3 个类的概率,选出概率最大的那个置为 1,其它两个置为 0,形成 output 矩阵。
运行结果:
上图中第一红框表示神经网络预测出的分类结果,第二个红框表示测试集中真实的分类((1,0,0)表示这个圆属于类型“0”)。
每次运行时,正确率可能不一样,最高能达到 100%。通过调整刚才提到的关键参数中的学习率、隐藏层节点数、迭代次数可以提高正确率。
总结
神经网络的输入为 12 个半径值,输出结果为一个 3 维向量,其中置 1 的位就是对应的分类。 在实际应用中,12 个半径值对应 12 个特征,3 维向量表示能分 3 类。只要根据实际应用的需要修改特征数和分类数即可将上述程序应用于不同分类场景。