这里用我自己的语言来讲解鸢尾花分类的三种算法,基础的是随机梯度下降法,第二部分用Adagrad(自适应梯度下降法)来替代随机梯度下降法进行优化,第三部分在基础代码上增加dropout算法来优化模型。
其中Adagrad(自适应梯度下降法)和dropout算法的原理需要大家自己去了解,了解了之后再看我的代码就可以很快地理解了!
目录
原理
通过构建BP神经网络来实现鸢尾花数据的分类。
传入一组数据,经过神经元加权运算后得到下一个神经元的值,如此计算,最终输出一个分类结果。
BP神经网络的构建分为几个部分,首先要确定神经元层数和个数,再为神经元及其之间的链接赋予参数和算法,最后设计一套参数更新流程用来优化神经网络的效能。
具体原理和设置如下:
先确定神经元层数和个数。这里我选择构建四层的神经网络,第一层为输入层,第二层和第三层都为中间层,第四层为输出层。
因为鸢尾花数据中每朵花有四个特征属性,最后只有三种分类结果,所以我设置输入层为四个神经元(每类特征属性都能参与计算),输出层为三个神经元(分别对应三个类别的概率大小),两个中间层根据经验确定为二十五个神经元。
将不同层神经元进行全链接,中间的链接即为权重w,除了输入层,其它层神经元都赋予一个偏置b以及激活函数f,另外给最后输出结果一个评判误差的损失函数。
权重w和偏置b通过随机数生成,中间层激活函数设置为relu函数,输出层激活函数设置为softmax函数(用来分类),损失函数设置为交叉熵误差(因为分类时用到了独热编码,因此适合用交叉熵误差)。
参数更新的方法设置为随机梯度下降法。即通过反向传播计算不同参数的梯度,再用梯度进行参数的优化。
代码如下:
#训练集:鸢尾花150*50%
#网络结构:输入层(4)+中间层(25)+中间层(25)+输出层(3)
#中间层激活函数:relu,输出层激活函数:softmax
#损失函数:交叉熵误差
#随机梯度下降
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
#鸢尾花数据读入
iris_data=datasets.load_iris()
input_data=iris_data.data
correct=iris_data.target
n_data=len(correct)
#对数据进行预处理
#标准化
ave_input=np.average(input_data,axis=0)
std_input=np.std(input_data,axis=0)
input_data=(input_data-ave_input)/std_input
print(input_data)
#标签转化为独热编码
correct_data=np.zeros((n_data,3))
for i in range(n_data):
correct_data[i,correct[i]]=1.0
print(correct_data)
#切分训练集和测试集
index=np.arange(n_data)
index_train=index[index%2==0]
index_test=index[index%2!=0]
input_train=input_data[index_train,:]
input_test=input_data[index_test,:]
correct_train=correct_data[index_train,:]
corre_test=correct_data[index_test,:]
n_train=input_train.shape[0]
n_test=input_test.shape[0]
#设置参数
n_in=4
n_mid=10
n_out=3
wb_width=0.1
eta=0.1
epoch=100
batch_size=8
interval=100
#实现网络层
class Baselayer:
def __init__(self,n_upper,n):
self.w=wb_width*np.random.randn(n_upper,n)
self.b=wb_width*np.random.randn(n)
def updata(self,eta):
self.w=self.w-eta*self.grad_w
self.b=self.b-eta*self.grad_b
class MiddleLayer(Baselayer):
def forward(self, x):
self.x=x
self.u=np.dot(x,self.w)+self.b
self.y=np.where(self.u<=0,0,self.u)#relu函数
def backward(self, grad_y):
delta = grad_y*np.where(self.u<=0,0,1.0)#relu函数的求导--!!
self.grad_w = np.dot(self.x.T, delta)
self.grad_b = np.sum(delta, axis=0)
self.grad_x = np.dot(delta, self.w.T)
class OutputLayer(Baselayer):
def forward(self,x):
self.x=x
u=np.dot(x,self.w)+self.b
self.y=np.exp(u)/np.sum(np.exp(u),axis=1,keepdims=True)#SoftMax函数
def backward(self,t):
delta=self.y-t
self.grad_w=np.dot(self.x.T,delta)
self.grad_b=np.sum(delta,axis=0)
self.grad_x=np.dot(delta,self.w.T)
#实例化
middle_layer_1=MiddleLayer(n_in,n_mid)
middle_layer_2=MiddleLayer(n_mid,n_mid)
output_layer=OutputLayer(n_mid,n_out)
#定义函数
def forward_propagation(x):
middle_layer_1.forward(x)
middle_layer_2.forward(middle_layer_1.y)
output_layer.forward(middle_layer_2.y)
def back_propagation(t):
output_layer.backward(t)
middle_layer_2.backward(output_layer.grad_x)
middle_layer_1.backward(middle_layer_2.grad_x)
def update_wb():
middle_layer_1.updata(eta)
middle_layer_2.updata(eta)
output_layer.updata(eta)
def get_error(t,batch_size):
return -np.sum(t*np.log(output_layer.y+1e-7))/batch_size
train_error_x=[]
train_error_y=[]
test_error_x=[]
test_error_y=[]
#学习过程
n_batch=n_train//batch_size
for i in range(epoch):
#统计误差
forward_propagation(input_train)
error_train=get_error(correct_train,n_train)
forward_propagation(input_test)
error_test=get_error(corre_test,n_test)
train_error_x.append(i)
train_error_y.append(error_train)
test_error_x.append(i)
test_error_y.append(error_test)
index_random=np.arange(n_train)
np.random.shuffle(index_random)
for j in range(n_batch):
mb_index=index_random[j*batch_size:(j+1)*batch_size]
x=input_train[mb_index,:]
t=correct_train[mb_index,:]
forward_propagation(x)
back_propagation(t)
update_wb()
plt.plot(train_error_x,train_error_y,label="Train")
plt.plot(test_error_x,test_error_y,label="Test")
plt.legend()
plt.xlabel("epoch")
plt.ylabel("error")
plt.show()
首先我设置的层数为四层,输入层为四个神经元,两个中间层都为二十五个神经元,输出层为三个神经元。学习率为0.1,权重和偏置的缩放为0.01。
最后运行,输出的不同优化轮次的误差结果图像为:
可以看到优化20轮时训练集和测试集误差已经较低但趋势不完全一致,进行到30轮左右时训练集和测试集的结果误差趋势已经非常吻合接近。
选择 AdaGrad (自适应梯度下降) 优化程序
更改代码如图:
自适应梯度下降法需要更改参数更新函数,我加了两个中间变量,并且优化了更新算法。
运行结果如下:
可以看到40轮左右之前过拟合现象明显且误差较大,60轮之后误差明显降低且模型训练效果较好。
选择 dropout 算法优化程序
我在中间层使用dropout算法,加了三行代码,引入了mask和rate参数,通过运算将每个计算完成的u赋予一个权重,可能为0可能为1-rate。这里我设定的rate为0.5。
运行结果如下:
可以看到使用dropout算法优化后前期轮次的过拟合问题得到了较好的改善,但是误差波动趋势较为明显。