鸢尾花数据集是UCI中最常被利用的数据集。结构简单,共150条数据,不需要清洗。 每条数据包括四个属性特征和一个标签。
属性的特征分别是【花萼长度、花萼宽度、花瓣长度、花瓣宽度】,
属性标签有三类:【山鸢尾(Iris-setosa)、变色鸢尾(Iris-versicolor)和维吉尼亚鸢尾(Iris-virginica)】
从UCI:UCI Machine Learning Repository下载的一般是.data结尾的数据集,用pycharm打开数据集如下所示,由逗号分隔。
神经网络作为最常用的人工智能算法之一,已经有非常多的封装库和运用,本文没有调用sklearn的库,通过自己编程实现四层BP神经网络,加深对神经网络的理解,提高小白的编程能力具有非常重要的意义。所谓BP误差逆传播是训练多层网络的多种办法之一,是迄今为止的最成功的网络学习算法。神经网络编程基本的坑我也是都进了一遍,在此一定叮嘱大家注意到理解神经网络的原理,本文最后附有手算草稿。
网络拓扑结构如图所示:
首先要初始化权重和截距,各层节点的数量,也都很容易理解:
class neuralNetwork(object):
def __init__(self, InputNodes, hiddenNodes_1, hiddenNodes_2, OutputNodes, learningRate=0.5):
# 设置节点数
self.InputNodes = InputNodes
self.hiddenNodes_1 = hiddenNodes_1
self.hiddenNodes_2 = hiddenNodes_2
self.OutputNodes = OutputNodes
self.wi1 = np.random.normal(0.0, 0.01, (self.hiddenNodes_1, self.InputNodes))
self.wi2 = np.random.normal(0.0, 0.01, (self.hiddenNodes_2, self.hiddenNodes_1))
self.wo = np.random.normal(0.0, 0.01, (self.OutputNodes, self.hiddenNodes_2))
# 阈值随机生成
self.b1 = np.random.normal(0.0, 0.01, (self.hiddenNodes_1, 1))
self.b2 = np.random.normal(0.0, 0.01, (self.hiddenNodes_2, 1))
self.b3 = np.random.normal(0.0, 0.01, (self.OutputNodes, 1))
self.learnRate = learningRate
激励函数选择sigmoid函数,sigmoid函数的优势在其导数的形式很好。
sigmoid函数的图像:
def sigmoid_activation(self, x):
return 1 / (1 + np.exp(-x))
BP算法属于监督学习算法,通过调整网络连接权重来体现学习的效果。并且使用梯度下降法去更新参数,也是最重要的一环,推导公式如下:
def train(self, inputs_list, targets_list):
X_train = np.array(inputs_list, ndmin=2).T
Y_train = np.array(targets_list, ndmin=2).T
L1_IN = np.dot(self.wi1, X_train) - self.b1
L1_OUT = self.sigmoid_activation(L1_IN)
L2_IN = np.dot(self.wi2, L1_OUT) - self.b2
L2_OUT = self.sigmoid_activation(L2_IN)
Out_IN = np.dot(self.wo, L2_OUT) - self.b3
Output = self.sigmoid_activation(Out_IN)
# 误差计算
output_err = Y_train - Output # d*1
hidden_2_err = np.dot(self.wo.T, Output * (1 - Output) * output_err) # k*d*d*1=k*1
hidden_1_err = np.dot(self.wi2.T, L2_OUT * (1 - L2_OUT) * hidden_2_err) # m*k*k*1=m*1
# 更新权重和阈值
# d*1*1*k =d*k
delt_wo = self.learnRate * np.dot(output_err * Output * (1 - Output), np.transpose(L2_OUT))
# k*1*1*m = k*m
delt_wi2 = self.learnRate * np.dot(hidden_2_err * L2_OUT * (1 - L2_OUT), np.transpose(L1_OUT))
# m*1*1*n = m*n
delt_wi1 = self.learnRate * np.dot(hidden_1_err * L1_OUT * (1 - L1_OUT), np.transpose(X_train))
# d*1-=d*1=d*N
self.b3 -= self.learnRate * output_err * Output * (1 - Output)
self.b2 -= self.learnRate * hidden_2_err * L2_OUT * (1 - L2_OUT)
self.b1 -= self.learnRate * hidden_1_err * L1_OUT * (1 - L1_OUT)
self.wo = self.wo + delt_wo
self.wi2 = self.wi2 + delt_wi2
self.wi1 = self.wi1 + delt_wi1
pass
数据集处理这里包括数据的读取、重新打乱数据行(分训练集和测试集方法的一种)以及数据归一化。首先一般下载的数据都是.data文件,可以通过重新保存的方式修改文件格式。其次,对于将数据集打乱顺序并不是唯一方法,也可以通过python里的其他命令完成训练集和测试集的划分。数据标签的转换是必备的环节,利用一个三列的矩阵来表示鸢尾花三类(【1,0,0】)数据集归一化的必要性,本文没有加以探索,以后探索了可以再出博文。
if __name__ == '__main__':
# 数据处理
iris = open("iris.txt", "r")
iris_lines = iris.readlines()
rows = len(iris_lines)
#print(rows)
iris.close()
# 打乱行
out = open("out.txt", "w")
lines = []
with open("iris.txt", 'r') as infile:
for line in infile:
lines.append(line)
random.shuffle(lines)
for line in lines:
out.write(line)
#print(out)
row = 0
X_train = np.zeros((rows, 4))
Y_train = np.zeros((rows, 3))
for line in lines:
line = line.strip().split(',')
#print(line[4])
if line[4] == 'Iris-setosa':
Y_train[row, 0] = 1
if line[4] == 'Iris-versicolor':
Y_train[row, 1] = 1
if line[4] == 'Iris-virginica':
Y_train[row, 2] = 1
X_train[row, 0:4] = line[0:4]
row += 1
print(Y_train)
iris.close()
#print(rows)
# 数据进行归一化处理
nrow,ncol = X_train.shape
#print(nrow,ncol)
datamatrix = np.zeros((nrow, ncol))
for k in range(ncol):
eeeee = X_train[:, k]
# print(k)
maxVals = np.max(eeeee, axis=0)
# print(maxVals)
minVals = np.min(eeeee, axis=0)
# print(minVals)
cols1 = X_train[:, k]
ranges = maxVals - minVals
b = cols1 - minVals
normcols = b / ranges
datamatrix[:, k] = normcols
#print(datamatrix)
X_train = datamatrix[:]
'''训练网络'''
InputNodes = 4
OutputNode = 3
hiddenNodes_1 = 100
hiddenNodes_2 = 100
learnRate = 0.5
n = neuralNetwork(InputNodes, hiddenNodes_1, hiddenNodes_2, OutputNode, learnRate)
ncols = 30
inputs_list = X_train[:ncols]
targets_list = Y_train[:ncols]
#print(inputs_list, targets_list)
maxIter = 7500
while (maxIter > 0):
maxIter -= 1
for i in range(ncols):
n.train(inputs_list[i], targets_list[i])
因为数据集已经重新打乱顺序,选取了前ncols条数据作为训练集,剩下的作为测试集。
将训练集投入已经训练好的网络中:
def query(self, X_test):
# ndmin=2 表示指定最小维数为2
X_test = np.array(X_test, ndmin=2).T
# Y_test = np.array(Y_test, ndmin=2).T
L1_IN = np.dot(self.wi1, X_test) - self.b1
L1_OUT = self.sigmoid_activation(L1_IN)
L2_IN = np.dot(self.wi2, L1_OUT) - self.b2
L2_OUT = self.sigmoid_activation(L2_IN)
Out_IN = np.dot(self.wo, L2_OUT) - self.b3
Output = self.sigmoid_activation(Out_IN)
return Output
# 预测
X_test = X_train[-(rows-ncols):]
#print(X_test)
Y_test = Y_train[-(rows-ncols):]
#print(Y_test)
Output = n.query(X_test)
Output = Output.T
下面对得到的结果进行预测,也许胜利就在眼前。
# 训练集准确率
# test准确率
CategorySet = ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
P = []
for i in range(len(Output)):
Predict = Output[i]
#print(i)
Predict = Predict.tolist()
Index = Predict.index(max(Predict, key=abs))
Real = Y_test[i]
Real = Real.tolist()
Category = Real.index(max(Real, key=abs))
if Index == Category:
P.append(1)
print(Y_test[i], ' ', '实际类别', ':', CategorySet[Category], ' ', '预测类别', ':',
CategorySet[Index],
' ', '预测正确', Predict)
else:
P.append(0)
print(Y_test[i], ' ', '实际类别', ':', CategorySet[Category], ' ', '预测类别', ':',
CategorySet[Index],
' ', '预测错误', Predict)
print('准确率', ':', sum(P) / len(P))
得到的预测结果如下所示,可以得的准确率。
感谢观看到最后,源码可以通过私信找我,第一次发博客,反复斟酌的目的是看到底哪些思考对大家有用,可以帮到你。编程的确是一个提高思维逻辑分析能力的过程,我在编写上述代码最大的一个坑是将连接权和阈值的更新放到了train()函数里,导致每次迭代都重新生成连接权和阈值,也是非常无脑的错误,在此希望大家代码顺利,没有bug。