在"一文彻底搞懂BP算法:原理推导+数据演示+项目实战(上篇)"中我们详细介绍了BP算法的原理和推导过程,并且用实际的数据进行了计算演练。在下篇中,我们将自己实现BP算法(不使用第三方的算法框架),并用来解决鸢尾花分类问题。

图1 鸢尾花

鸢尾花数据集如图2所示,总共有三个品种的鸢尾花(setosa、versicolor和virginica),每个类别50条样本数据,每个样本有四个特征(花萼长度、花萼宽度、花瓣长度以及花瓣宽度)。

图2 鸢尾花数据集

首先我们导入需要的包:

from csv import readerimport numpy as npfrom sklearn.preprocessing import MinMaxScalerimport randomimport matplotlib.pyplot as pltimport math接下来我们实现一个数据集的加载和预处理的函数"load_dataset":

def load_dataset(dataset_path, n_train_data): """加载数据集,对数据进行预处理,并划分训练集和验证集 :param dataset_path: 数据集文件路径 :param n_train_data: 训练集的数据量 :return: 划分好的训练集和验证集 """ dataset = [] label_dict = {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2} with open(dataset_path, 'r') as file: # 读取CSV文件,以逗号为分隔符 csv_reader = reader(file, delimiter=',') for row in csv_reader: # 将字符串类型的特征值转换为浮点型 row[0:4] = list(map(float, row[0:4])) # 将标签替换为整型 row[4] = label_dict[row[4]] # 将处理好的数据加入数据集中 dataset.append(row) # 对数据进行归一化处理 dataset = np.array(dataset) mms = MinMaxScaler() for i in range(dataset.shape[1] - 1): dataset[:, i] = mms.fit_transform(dataset[:, i].reshape(-1, 1)).flatten() # 将类标转为整型 dataset = dataset.tolist() for row in dataset: row[4] = int(row[4]) # 打乱数据集 random.shuffle(dataset) # 划分训练集和验证集 train_data = dataset[0:n_train_data] val_data = dataset[n_train_data:] return train_data, val_data在"load_dataset"函数中,我们实现了数据集的读取、数据的归一化处理以及对数据集进行了"shuffle"操作等,最后函数返回了划分好的训练集和验证集。

实现数据预处理之后,接下来我们开始实现BP算法的关键部分(如果读者对算法原理有不清楚的地方,可以查看"一文彻底搞懂BP算法:原理推导+数据演示+项目实战(上篇)")。首先我们实现神经元的计算部分、激活函数以及激活函数的求导部分。

def fun_z(weights, inputs): """计算神经元的输入:z = weight * inputs + b :param weights: 网络参数(权重矩阵和偏置项) :param inputs: 上一层神经元的输出 :return: 当前层神经元的输入 """ bias_term = weights[-1] z = 0 for i in range(len(weights)-1): z += weights[i] * inputs[i] z += bias_term return zdef sigmoid(z): """激活函数(Sigmoid):f(z) = Sigmoid(z) :param z: 神经元的输入 :return: 神经元的输出 """ return 1.0 / (1.0 + math.exp(-z))def sigmoid_derivative(output): """Sigmoid激活函数求导 :param output: 激活函数的输出值 :return: 求导计算结果 """ return output * (1.0 - output)函数"fun_z"实现了公式"z = weight * inputs + b",其中inputs是上一层网络的输出,weight是当前层的权重矩阵,b是当前层的偏置项,计算得到的z是当前层的输入。

函数"sigmoid"是Sigmoid激活函数的实现,将z作为激活函数的输入,计算得到当前层的输出,并传递到下一层。

函数"sigmoid_derivative"是Sigmoid函数求导的实现,在误差反向传播的时候需要用到。

接下来我们实现BP网络的前向传播:

def forward_propagate(network, inputs): """前向传播计算 :param network: 神经网络 :param inputs: 一个样本数据 :return: 前向传播计算的结果 """ for layer in network: # 循环计算每一层 new_inputs = [] for neuron in layer: # 循环计算每一层的每一个神经元 z = fun_z(neuron['weights'], inputs) neuron['output'] = sigmoid(z) new_inputs.append(neuron['output']) inputs = new_inputs return inputs前向计算的过程比较简单,和我们在上篇中介绍的计算过程一致。稍微麻烦一点的是误差反向传播的计算:

def backward_propagate_error(network, actual_label): """误差进行反向传播 :param network: 神经网络 :param actual_label: 真实的标签值 :return: """ for i in reversed(range(len(network))): # 从最后一层开始计算误差 layer = network[i] errors = list() if i != len(network)-1: # 不是输出层 for j in range(len(layer)): # 计算每一个神经元的误差 error = 0.0 for neuron in network[i + 1]: error += (neuron['weights'][j] * neuron['delta']) errors.append(error) else: # 输出层 for j in range(len(layer)): # 计算每一个神经元的误差 neuron = layer[j] errors.append(actual_label[j] - neuron['output']) # 计算误差项 delta for j in range(len(layer)): neuron = layer[j] neuron['delta'] = errors[j] * sigmoid_derivative(neuron['output'])误差反向传播过程中,我们首先需要根据模型的输出来计算得到误差,然后计算输出层的误差项。得到输出层的误差项之后,我们就可以根据上篇中介绍的"第k层神经元的误差项是由第k+1层的误差项乘以第k+1层的权重,再乘以第k层激活函数的导数得到"来计算其它层的误差项。

在计算得到每一层的误差项之后,我们根据上篇中介绍的权重矩阵和偏置项的更新公式来更新参数:

def update_parameters(network, row, l_rate): """利用误差更新神经网络的参数(权重矩阵和偏置项) :param network: 神经网络 :param row: 一个样本数据 :param l_rate: 学习率 :return: """ for i in range(len(network)): inputs = row[:-1] if i != 0: # 获取上一层网络的输出 inputs = [neuron['output'] for neuron in network[i - 1]] for neuron in network[i]: # 更新权重矩阵 for j in range(len(inputs)): neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j] # 更新偏置项 neuron['weights'][-1] += l_rate * neuron['delta']到这里所有的关键部分我们都已经实现了,接下来我们实现网络的初始化以及网络的训练部分,首先实现网络的初始化:

def initialize_network(n_inputs, n_hidden, n_outputs): """初始化BP网络(初始化隐藏层和输出层的参数:权重矩阵和偏置项) :param n_inputs: 特征列数 :param n_hidden: 隐藏层神经元个数 :param n_outputs: 输出层神经元个数,即分类的总类别数 :return: 初始化后的神经网络 """ network = list() # 隐藏层 hidden_layer = [{'weights': [random.random() for i in range(n_inputs + 1)]} for i in range(n_hidden)] network.append(hidden_layer) # 输出层 output_layer = [{'weights': [random.random() for i in range(n_hidden + 1)]} for i in range(n_outputs)] network.append(output_layer) return network这里我们初始化了一个两层神经网络(一个隐藏层和一个输出层)。在初始化参数的时候,我们将权重矩阵和偏置项放在了一个数组中("weights"),数组的最后一个元素是偏置项,前面的元素是权重矩阵。

接下来我们实现模型的训练部分:

def train(train_data, l_rate, epochs, n_hidden, val_data): """训练神经网络(迭代n_epoch个回合) :param train_data: 训练集 :param l_rate: 学习率 :param epochs: 迭代的回合数 :param n_hidden: 隐藏层神经元个数 :param val_data: 验证集 :return: 训练好的网络 """ # 获取特征列数 n_inputs = len(train_data[0]) - 1 # 获取分类的总类别数 n_outputs = len(set([row[-1] for row in train_data])) # 初始化网络 network = initialize_network(n_inputs, n_hidden, n_outputs) acc = [] for epoch in range(epochs): # 训练epochs个回合 for row in train_data: # 前馈计算 _ = forward_propagate(network, row) # 处理一下类标,用于计算误差 actual_label = [0 for i in range(n_outputs)] actual_label[row[-1]] = 1 # 误差反向传播计算 backward_propagate_error(network, actual_label) # 更新参数 update_parameters(network, row, l_rate) # 保存当前epoch模型在验证集上的准确率 acc.append(validation(network, val_data)) # 绘制出训练过程中模型在验证集上的准确率变化 plt.xlabel('epochs') plt.ylabel('accuracy') plt.plot(acc) plt.show() return network我们总共训练了epochs个回合,这里我们使用随机梯度下降来优化模型,因此每次都用一个样本来更新参数。接下来我们实现一个函数用来验证模型的效果:

def validation(network, val_data): """测试模型在验证集上的效果 :param network: 神经网络 :param val_data: 验证集 :return: 模型在验证集上的准确率 """ # 获取预测类标 predicted_label = [] for row in val_data: prediction = predict(network, row) predicted_label.append(prediction) # 获取真实类标 actual_label = [row[-1] for row in val_data] # 计算准确率 accuracy = accuracy_calculation(actual_label, predicted_label) # print("测试集实际类标:", actual_label) # print("测试集上的预测类标:", predicted_label) return accuracy训练过程中的每一个回合,我们都用模型对验证集进行一次预测,并将预测的结果保存,用来绘制训练过程中模型在验证集上的准确率的变化过程。准确率的计算以及使用模型进行预测的实现如下:

def accuracy_calculation(actual_label, predicted_label): """计算准确率 :param actual_label: 真实类标 :param predicted_label: 模型预测的类标 :return: 准确率(百分制) """ correct_count = 0 for i in range(len(actual_label)): if actual_label[i] == predicted_label[i]: correct_count += 1 return correct_count / float(len(actual_label)) * 100.0def predict(network, row): """使用模型对当前输入的数据进行预测 :param network: 神经网络 :param row: 一个数据样本 :return: 预测结果 """ outputs = forward_propagate(network, row) return outputs.index(max(outputs))最后我们运行代码:

if __name__ == "__main__": file_path = './iris.csv' # 参数设置 l_rate = 0.2 # 学习率 epochs = 300 # 迭代训练的次数 n_hidden = 5 # 隐藏层神经元个数 n_train_data = 130 # 训练集的大小(总共150条数据,训练集130条,验证集20条) # 加载数据并划分训练集和验证集 train_data, val_data = load_dataset(file_path, n_train_data) # 训练模型 network = train(train_data, l_rate, epochs, n_hidden, val_data)训练过程如图3所示:

layer output 激活函数_一文彻底搞懂BP算法:原理推导+数据演示+项目实战(下篇)...相关推荐

  1. 一文彻底搞懂BP算法:原理推导+数据演示+项目实战(下篇)

    在"一文彻底搞懂BP算法:原理推导+数据演示+项目实战(上篇)"中我们详细介绍了BP算法的原理和推导过程,并且用实际的数据进行了计算演练.在下篇中,我们将自己实现BP算法(不使用第 ...

  2. cas无法使用_一文彻底搞懂CAS实现原理

    本文导读: 前言 如何保障线程安全 CAS原理剖析 CPU如何保证原子操作 解密CAS底层指令 小结 前言 日常编码过程中,基本不会直接用到 CAS 操作,都是通过一些JDK 封装好的并发工具类来使用 ...

  3. layer output 激活函数_深入理解YOLO v3实现细节 - 第3篇 构建v3的Loss_layer

    深入理解YOLO v3实现细节系列文章,是本人根据自己对YOLO v3原理的理解,结合开源项目tensorflow-yolov3,写的学习笔记.如有不正确的地方,请大佬们指出,谢谢! 目录 第1篇 数 ...

  4. python异步读写文件_一文彻底搞懂python文件读写

    Python文件读写 一,I/O操作 I/O在计算机中是指Input/Output,也就是Stream(流)的输入和输出.这里的输入和输出是相对于内存来说的,Input Stream(输入流)是指数据 ...

  5. cookie代码加时间多久出现一次_一文彻底搞懂Cookie、Session、Token到底是什么

    前言 在了解这三个概念之前我们先要了解HTTP是无状态的Web服务器,什么是无状态呢?就像上面夏洛特烦恼中经典的一幕对话一样,一次对话完成后下一次对话完全不知道上一次对话发生了什么.如果在Web服务器 ...

  6. 访问网址 token的格式_一文彻底搞懂Cookie、Session、Token到底是什么

    欢迎关注文章系列 ,关注我 <提升能力,涨薪可待> <面试知识,工作可待> <实战演练,拒绝996> 如果此文对你有帮助.喜欢的话,那就点个赞呗,点个关注呗! Co ...

  7. mysql三次握手_一文彻底搞懂 TCP三次握手、四次挥手过程及原理

    原创文章首发于公众号:「码农富哥」,欢迎收藏和关注,如转载请注明出处! TCP 协议简述 TCP 提供面向有连接的通信传输,面向有连接是指在传送数据之前必须先建立连接,数据传送完成后要释放连接. 无论 ...

  8. redis 集群搭建_一文轻松搞懂redis集群原理及搭建与使用

    转载:https://juejin.im/post/5ad54d76f265da23970759d3 作者:SnailClimb 这里总结一下redis集群的搭建以便日后所需同时也希望能对你有所帮助. ...

  9. mysql怎么实现事务序列化_一文快速搞懂MySQL InnoDB事务ACID实现原理(转)

    这一篇主要讲一下 InnoDB 中的事务到底是如何实现 ACID 的: 原子性(atomicity) 一致性(consistency) 隔离性(isolation) 持久性(durability) 隔 ...

最新文章

  1. Linux wait() 和 waitpid()函数介绍
  2. Xamarin.Forms的ActivityIndicator和ProgressBar比较
  3. windows2003 DHCP中批处理绑定IP与MAC
  4. Android 之 LogDog
  5. github开源项目免费使用Azure PipeLine
  6. 营销 客户旅程模板_我如何在国外找到开发人员的工作:我从营销到技术的旅程...
  7. linux gpio设备驱动程序,嵌入式Linux设备驱动开发之:GPIO驱动程序实例-嵌入式系统-与非网...
  8. Linux配置本地yum源(RHEL8)
  9. Linux下vi替换字符命令操作实例
  10. 好用的文件对比工具Beyond Compare 4 for Mac 4.4.2
  11. 用CSS3制作50个超棒动画效果教程
  12. 高通MSM8937芯片参考资料免费下载
  13. MySql Workbench 8.0汉化插件分享
  14. Dest0g3 520迎新赛部分WP
  15. C语言解题——从今天开始入坑C语言
  16. windows 2012下安装.NET框架时出现组件的文件跟组件清单中的验证信息不匹配,无法安装
  17. 服务器电脑用哪个系统好,电脑系统哪个好用?电脑系统有几种版本
  18. 如何做人做事?方与圆的为人处世之道
  19. [从零开始]用python制作识图翻译器·四
  20. python中,Microsoft Visual C++ 14.0 or greater is required问题解决方案

热门文章

  1. 在window和linux上通用的SprtLock类头实现文件
  2. 关于学习过程中走过的弯路
  3. USANavyElectronicsCourse-Excerlent
  4. C# Revert 单词反转字符串!『测试通过』
  5. Generics and Linq demo
  6. Fatal error: connect ECONNREFUSED Fatal error: socket hang up
  7. oscache.properties文件配置
  8. IE11 F12 开发人员工具 查看 Cookie
  9. 《赢道:成功创业者的28条戒律》成都签售会魅力登场(背景)
  10. 转:ASP.NET程序中常用小技巧