《机器学习》学习笔记——BP网络算法实践


  在《机器学习》第五章学习了反向传播算法与BP网络的结构,在此试着用Python搭建一个BP网络。数据集分别使用之前所涉及的西瓜数据集和马疝病数据集:

1. 机器学习数据集——西瓜数据集.zip
2. 机器学习经典二分类数据集——马疝病数据集.zip

同时附上本项目完整文件:
BP网络Python实践.zip

关于具体的算法实现逻辑参考《机器学习》书上的流程:

在这里插入图片描述

1 载入数据

  由于数据集文件中的内容的每一行都代表一条数据,其中最后面的元素代表样本的正/负,即标签。前面其他的元素都是属性取值,故我们需要将数据集文件进行读取并将属性与标签分开存放

fp = open(filename)
# 存放数据
dataset = []
# 存放标签
labelset = []
for i in fp.readlines():
	a = i.strip().split()
	# 每个数据行的最后一个数据作为标签
	dataset.append([float(j) for j in a[:len(a)-1]])
	labelset.append(int(float(a[-1])))
return dataset, labelset

  在 f o r for for循环中,我们依次对文件内容的每一行进行处理。包括对其进行划分,然后取前 n − 1 n-1 n1个元素作为特征值添加到 d a t a s e t dataset dataset中,而最后一个元素作为标签添加到 l a b e l s e t labelset labelset中,并最后返回这两个列表。

2 网络参数初始化

  在这里我定义了一个 B P N e t BPNet BPNet类,用于初始化网络、网络训练以及测试。另外我还在该类里面定义了 S i g m o i d Sigmoid Sigmoid函数与均方损失计算函数,方便后续的训练与测试过程。
  在对这个类生成对象的时候,我们需要传入输入层的神经元数量、隐层神经元数量、输出层神经元数量、迭代训练的轮数、学习率,以及训练集与测试集的文件路径。

class BPNet:
	# BP网络初始化,输入层神经元数量,隐层神经元数量,输出层神经元数量,迭代训练轮数,学习率,训练集文件路径,测试集文件路径
	def __init__(self, x_num, y_num, z_num, epochs, lr, trainning_set_path, testing_set_path):
		self.x_num = x_num
		self.y_num = y_num
		self.z_num = z_num
		self.epochs = epochs
		self.lr = lr
		self.trainning_set_path = trainning_set_path
		self.testing_set_path = testing_set_path

	# sigmoid函数
	def sigmoid(self, inx):
		return 1 / (1 + np.exp(-inx))

	# 计算均方误差
	def SquareError(self, y, label):
		return np.sum((y-label)**2) / 2

	# BP网络的训练以及测试
	def GradDescent(self):
		# 载入训练/测试数据集与标签集
		trainning_dataset, trainning_labelset = self.loaddataset(self.trainning_set_path)
		testing_dataset, testing_labelset = self.loaddataset(self.testing_set_path)
		# 初始化参数
		# 输入层与隐层的连接权重
		weight1 = np.random.randint(-5, 5, (self.x_num, self.y_num)).astype(np.float64)
		# 隐层与输出层的连接权重
		weight2 = np.random.randint(-5, 5, (self.y_num, self.z_num)).astype(np.float64)
		# 隐层阈值
		value1 = np.random.randint(-5, 5, (1, self.y_num)).astype(np.float64)
		# 输出层阈值
		value2 = np.random.randint(-5, 5, (1, self.z_num)).astype(np.float64)

  可以看到,上面这个代码是不完整的,其只展示到参数初始化就结束了。剩下的部分会在下面进行详述。
  在 G r a d D e s c e n t ( ) GradDescent() GradDescent()函数中,我们先将训练集与测试集的数据载入程序,然后根据输入层、隐层以及输出层的神经元数量构建输入层与隐层的连接权、隐层与输出层的连接权、隐层阈值以及输出层阈值的数据结构并对其随机赋值。

3 开始训练

# BP网络的训练以及测试
def GradDescent(self):
	# 载入训练/测试数据集与标签集
	trainning_dataset, trainning_labelset = self.loaddataset(self.trainning_set_path)
	testing_dataset, testing_labelset = self.loaddataset(self.testing_set_path)
	# 初始化参数
	# 输入层与隐层的连接权重
	weight1 = np.random.randint(-5, 5, (self.x_num, self.y_num)).astype(np.float64)
	# 隐层与输出层的连接权重
	weight2 = np.random.randint(-5, 5, (self.y_num, self.z_num)).astype(np.float64)
	# 隐层阈值
	value1 = np.random.randint(-5, 5, (1, self.y_num)).astype(np.float64)
	# 输出层阈值
	value2 = np.random.randint(-5, 5, (1, self.z_num)).astype(np.float64)
	# 迭代训练
	for epoch in range(self.epochs):
		# 依次训练每条数据
		for i in range(len(trainning_dataset)):
			# 输入数据
			train_input = np.mat(trainning_dataset[i]).astype(np.float64)
			# 数据标签
			train_label = np.mat(trainning_labelset[i]).astype(np.float64)
			# 隐层输入
			input1 = np.dot(train_input, weight1).astype(np.float64)
			# 隐层输出
			output1 = self.sigmoid(input1 - value1).astype(np.float64)
			# 输出层输入
			input2 = np.dot(output1, weight2).astype(np.float64)
			# 输出层输出
			output2 = self.sigmoid(input2 - value2).astype(np.float64)

			# 由链式法则更新参数
			a = np.multiply(output2, 1 - output2)
			g = np.multiply(a, train_label - output2)
			b = np.dot(g, np.transpose(weight2))
			c = np.multiply(output1, 1 - output1)
			e = np.multiply(b, c)
			weight1_change = self.lr * np.dot(np.transpose(train_input), e)
			weight2_change = self.lr * np.dot(np.transpose(output1), g)
			value1_change = -self.lr * e
			value2_change = -self.lr * g
			# 更新参数
			weight1 += weight1_change
			weight2 += weight2_change
			value1 += value1_change
			value2 += value2_change
		if epoch % 25 == 0:
			print(self.SquareError(output2, train_label))

  这里只展示了 G r a d D e s c e n t ( ) GradDescent() GradDescent()函数中对于参数初始化与模型训练的过程。在模型训练的过程中,我们先是将各层的前向传播的输入输出结果计算出来,然后根据链式法则对各个参数进行更新。具体的参数更新算法如下
Δ w h j = η g j b h \Delta w_{hj}=\eta g_jb_h Δwhj=ηgjbh

Δ θ j = − η g j \Delta \theta_j=-\eta g_j Δθj=ηgj

Δ v i h = η e h x i \Delta v_{ih}=\eta e_hx_i Δvih=ηehxi

Δ γ h = − η e h \Delta \gamma_h=-\eta e_h Δγh=ηeh

其中 e h = b h ( 1 − b h ) ∑ j = 1 l w h j g j e_h=b_h(1-b_h)\sum_{j=1}^lw_{hj}g_j eh=bh(1bh)j=1lwhjgj g j = y ^ j k ( 1 − y ^ j k ) ( y j k − y ^ j k ) g_j=\hat y_j^k(1-\hat y_j^k)(y_j^k-\hat y_j^k) gj=y^jk(1y^jk)(yjky^jk)

  并且每训练25轮就输出一次平方损失结果。

3 开始测试

# 记录正确分类的数量
rightcount = 0
# 开始测试
for i in range(len(testing_dataset)):
	#计算每一个样例通过该神经网路后的预测值
	test_input = np.mat(testing_dataset[i]).astype(np.float64)
	test_label = np.mat(testing_labelset[i]).astype(np.float64)
	# 隐层输出
	output1 = self.sigmoid(np.dot(test_input, weight1) - value1)
	# 输出层输出
	output2 = self.sigmoid(np.dot(output1, weight2) - value2)
	#确定其预测标签
	if output2 > 0.5:
		flag = 1
	else:
		flag = 0
	if test_label == flag:
		rightcount += 1
	print('预测结果:' + str(flag) + '	标签:' + str(int(test_label)))
print('正确率:' + str(rightcount / len(testing_dataset)))

  这里只展示了测试过程。测试过程就比较简单,直接计算最终的结果,然后与样本标签进行比对即可知道结果的正确与否。最后统计输出测试过程的正确率。

4 测试结果

  首先对于西瓜数据集,每个西瓜数据共有8个属性值与一个标签值,故

WM_TRAINNING_PATH = 'C:/Users/Desktop/西瓜训练集.txt'
WM_TESTING_PATH = 'C:/Users/Desktop/西瓜测试集.txt'

BPNet(8, 10, 1, 100, 0.02, WM_TRAINNING_PATH, WM_TESTING_PATH).GradDescent()

我定义输出层神经元数量为8,隐层神经元数量为10,输出层神经元数量为1,共训练100轮,学习率为0.02。输出结果如下
在这里插入图片描述
可以看到均方损失确实一直在下降,预测结果错了一个,所以正确率为0.8。

  对于马疝病数据集的话

TRAINNING_SET_PATH = 'C:/Users/waao_wuyou/Desktop/horseColicTraining.txt'
TESTING_SET_PATH = 'C:/Users/waao_wuyou/Desktop/horseColicTraining.txt'

BPNet(21, 45, 1, 1400, 0.05, TRAINNING_SET_PATH, TESTING_SET_PATH).GradDescent()

我定义其输入层神经元数量是21,隐层神经元数量是45,输出层神经元数量是1,共训练1400轮,学习率为0.05,其部分结果如下
在这里插入图片描述

正确率可达83%

5 完整代码

#coding=utf8
import numpy as np


# 训练集
TRAINNING_SET_PATH = 'C:/Users/Desktop/horseColicTraining.txt'
# 测试集
TESTING_SET_PATH = 'C:/Users/Desktop/horseColicTraining.txt'

class BPNet:
	# BP网络初始化,输入层神经元数量,隐层神经元数量,输出层神经元数量,迭代训练轮数,学习率,训练集文件路径,测试集文件路径
	def __init__(self, x_num, y_num, z_num, epochs, lr, trainning_set_path, testing_set_path):
		self.x_num = x_num
		self.y_num = y_num
		self.z_num = z_num
		self.epochs = epochs
		self.lr = lr
		self.trainning_set_path = trainning_set_path
		self.testing_set_path = testing_set_path
	
	# 从指定路径文件生成数据集与标签
	def loaddataset(self, filename):
		fp = open(filename)
		# 存放数据
		dataset = []
		# 存放标签
		labelset = []
		for i in fp.readlines():
			a = i.strip().split()
			# 每个数据行的最后一个数据作为标签
			dataset.append([float(j) for j in a[:len(a)-1]])
			labelset.append(int(float(a[-1])))
		return dataset, labelset

	# sigmoid函数
	def sigmoid(self, inx):
		return 1 / (1 + np.exp(-inx))

	# 计算均方误差
	def SquareError(self, y, label):
		return np.sum((y-label)**2) / 2

	# BP网络的训练以及测试
	def GradDescent(self):
		# 载入训练/测试数据集与标签集
		trainning_dataset, trainning_labelset = self.loaddataset(self.trainning_set_path)
		testing_dataset, testing_labelset = self.loaddataset(self.testing_set_path)
		# 初始化参数
		# 输入层与隐层的连接权重
		weight1 = np.random.randint(-5, 5, (self.x_num, self.y_num)).astype(np.float64)
		# 隐层与输出层的连接权重
		weight2 = np.random.randint(-5, 5, (self.y_num, self.z_num)).astype(np.float64)
		# 隐层阈值
		value1 = np.random.randint(-5, 5, (1, self.y_num)).astype(np.float64)
		# 输出层阈值
		value2 = np.random.randint(-5, 5, (1, self.z_num)).astype(np.float64)
		# 迭代训练
		for epoch in range(self.epochs):
			# 依次训练每条数据
			for i in range(len(trainning_dataset)):
				# 输入数据
				train_input = np.mat(trainning_dataset[i]).astype(np.float64)
				# 数据标签
				train_label = np.mat(trainning_labelset[i]).astype(np.float64)
				# 隐层输入
				input1 = np.dot(train_input, weight1).astype(np.float64)
				# 隐层输出
				output1 = self.sigmoid(input1 - value1).astype(np.float64)
				# 输出层输入
				input2 = np.dot(output1, weight2).astype(np.float64)
				# 输出层输出
				output2 = self.sigmoid(input2 - value2).astype(np.float64)

				# 由链式法则更新参数
				a = np.multiply(output2, 1 - output2)
				g = np.multiply(a, train_label - output2)
				b = np.dot(g, np.transpose(weight2))
				c = np.multiply(output1, 1 - output1)
				e = np.multiply(b, c)
				weight1_change = self.lr * np.dot(np.transpose(train_input), e)
				weight2_change = self.lr * np.dot(np.transpose(output1), g)
				value1_change = -self.lr * e
				value2_change = -self.lr * g
				# 更新参数
				weight1 += weight1_change
				weight2 += weight2_change
				value1 += value1_change
				value2 += value2_change
			if epoch % 25 == 0:
				print(self.SquareError(output2, train_label))
		# 记录正确分类的数量
		rightcount = 0
		# 开始测试
		for i in range(len(testing_dataset)):
			#计算每一个样例通过该神经网路后的预测值
			test_input = np.mat(testing_dataset[i]).astype(np.float64)
			test_label = np.mat(testing_labelset[i]).astype(np.float64)
			# 隐层输出
			output1 = self.sigmoid(np.dot(test_input, weight1) - value1)
			# 输出层输出
			output2 = self.sigmoid(np.dot(output1, weight2) - value2)
			#确定其预测标签
			if output2 > 0.5:
				flag = 1
			else:
				flag = 0
			if test_label == flag:
				rightcount += 1
			print('预测结果:' + str(flag) + '	标签:' + str(int(test_label)))
		print('正确率:' + str(rightcount / len(testing_dataset)))
		#返回正确率
		return rightcount / len(testing_dataset)


if __name__ == '__main__':
	BPNet(21, 45, 1, 1400, 0.05, TRAINNING_SET_PATH, TESTING_SET_PATH).GradDescent()

参考:机器学习 BP神经网络(Python实现)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诺亚方包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值