BP神经网络

简介

本文主要通过在MNIST数据集上使用全连接神经网络对比有无归一化、有无Dropout以及有无Batch Normalization进行训练,可视化分析常用的深度网络训练技巧的原因及效果。

网络结构

网络结构类似下图,输入层定为784(输入图片特征数),隐藏层1有512个神经元(tanh激活),隐藏层2有512个神经元(tanh激活),输出层有10个神经元(softmax激活,得到10个类别的概率分布)。

训练Pipeline

输入数据

将输入的一个batch的数据的x处理为[b, 784],y通过onehot编码处理为[b, 10],

前向传播

前向计算,得到各层的输入和输出。

反向传播

按照BP规则,计算各个参数的梯度。

参数优化

按照Adam算法,对参数进行更新。

源码

由于模块较多,这里直接给出最为核心的model源码,其他模块可以在文末的Github找到。

"""
Author: Zhou Chen
Date: 2019/12/4
Desc: 构建模型
"""
import numpy as np
from initializers import xavier, zero
from utils import onehot
from activations import tanh, softmax, softmax_gradient, tanh_gradient
from losses import cross_entropy
from optimizers import SGD, Adamdef dropout(x, p):"""以概率p丢弃神经元连接,为了处理方便采用反向Dropout思路,该方法无需修改测试网络"""keep_prob = 1 - p# z这里写的时候犯了一个错误,就是不应该批量生成概率矩阵,而是生成的概率矩阵批量重复d_temp = np.random.binomial(1, keep_prob, size=x.shape[1:]) / keep_probd_temp = d_temp.reshape(-1)x_dropout = x * d_tempreturn x_dropout, d_tempclass Model(object):def __init__(self, num_layers, units_list=None, initializer=None, optimizer='adam'):self.weight_num = num_layers - 1# 根据传入的初始化方法初始化参数,本次实验只实现xavier和全0初始化self.params = xavier(num_layers, units_list) if initializer == 'xavier' else zero(num_layers, units_list)self.optimizer = Adam(weights=self.params, weight_num=self.weight_num) if optimizer == 'adam' else SGD()self.bn_param = {}def forward(self, x, dropout_prob=None):"""前向传播,针对一个mini-batch处理"""net_inputs = []  # 各层的输入net_outputs = []  # 各层激活后的输出net_d = []# 为了层号对应,将输入层直接添加net_inputs.append(x)net_outputs.append(x)net_d.append(np.ones(x.shape[1:]))  # 输入层无丢弃概率for i in range(1, self.weight_num):  # 参数数量比层数少1x = x @ self.params['w'+str(i)].Tnet_inputs.append(x)x = tanh(x)if dropout_prob:# 训练阶段丢弃x, d_temp = dropout(x, dropout_prob)net_d.append(d_temp)net_outputs.append(x)out = x @ self.params['w'+str(self.weight_num)].Tnet_inputs.append(out)out = softmax(out)net_outputs.append(out)return {'net_inputs': net_inputs, 'net_outputs': net_outputs, 'd': net_d}, outdef backward(self, nets, y, pred, dropout_prob=None):"""dz[out] = out - ydw[out] = dz[out] @ outputs[out-1].Tdb[out] = dz[out]dz[i] = W[i+1]dz[i+1] * grad(z[i])dw[i] = dz[i] @ outputs[i-1]db[i] = dz[i]sa"""grads = dict()grads['dz'+str(self.weight_num)] = (pred - y)  # [b, 10]grads['dw'+str(self.weight_num)] = grads['dz'+str(self.weight_num)].T @ nets['net_outputs'][self.weight_num-1]  #[10, 512]for i in reversed(range(1, self.weight_num)):temp = grads['dz' + str(i + 1)] @ self.params['w' + str(i + 1)] * tanh_gradient(nets['net_inputs'][i])if dropout_prob:temp = temp * nets['d'][i] / (1-dropout_prob)grads['dz'+str(i)] = temp   # [b, 128]grads['dw'+str(i)] = grads['dz'+str(i)].T @ nets['net_outputs'][i-1]return gradsdef train(self, data_loader, valid_loader, epochs, learning_rate, dropout_prob=None):losses_train = []losses_valid = []for epoch in range(epochs):print("epoch", epoch)# 训练部分epoch_loss_train = 0for step, (x, y) in enumerate(data_loader):# x:[b, 28, 28] -> [b, 784] , y:[b, 1] -> [b, 10]x = x.reshape(-1, 28 * 28)y = onehot(y, 10)nets, pred = self.forward(x, dropout_prob)loss = cross_entropy(y, pred)epoch_loss_train += lossgrads = self.backward(nets, y, pred, dropout_prob)# SGD更新参数# self.params = optimizer.optimize(self.weight_num, self.params, grads, y.shape[0])self.params = self.optimizer.optimize(self.weight_num, self.params, grads, y.shape[0])if step % 100 == 0:print("epoch {} training step {} loss {:.4f}".format(epoch, step, loss))losses_train.append(epoch_loss_train)print(epoch_loss_train)data_loader.restart()# 验证部分,只进行前向传播epoch_loss_valid = 0for step, (x, y) in enumerate(valid_loader):x = x.reshape(-1, 28 * 28)y = onehot(y, 10)nets, pred = self.forward(x, dropout_prob)loss = cross_entropy(y, pred)epoch_loss_valid += lossif step % 100 == 0:print("epoch {} validation step {} loss {:.4f}".format(epoch, step, loss))losses_valid.append(epoch_loss_valid)valid_loader.restart()his = {'train_loss': losses_train, 'valid_loss': losses_valid}return hisdef batch_norm(self, x, layer_index, mode):epsilon = 1e-6momentum = 0.9N, D = x.shapeglobal_mean = self.bn_param.get('global_mean' + str(layer_index), np.zeros(D, dtype=x.dtype))global_var = self.bn_param.get('global_var' + str(layer_index), np.zeros(D, dtype=x.dtype))cache = Noneif mode == 'train':# 计算当前batch的均值和方差sample_mean = np.mean(x, axis=0)sample_var = np.var(x, axis=0)x_hat = (x - sample_mean) / np.sqrt(sample_var + epsilon)out = self.params['gamma' + str(layer_index)] * x_hat + self.params['beta' + str(layer_index)]  # bn结束global_mean = momentum * global_mean + (1 - momentum) * sample_meanglobal_var = momentum * global_var + (1 - momentum) * sample_varcache = {'x': x, 'x_hat': x_hat, 'sample_mean': sample_mean, 'sample_var': sample_var}else:# 测试模式,使用全局均值和方差标准化x_hat = (x - global_mean) / np.sqrt(global_var + epsilon)out = self.params['gamma' + str(layer_index)] * x_hat + self.params['beta' + str(layer_index)]self.bn_param['global_mean' + str(layer_index)] = global_meanself.bn_param['global_var' + str(layer_index)] = global_varreturn out, cachedef forward_bn(self, x, bn_mode='train'):"""带BN层的前向传播"""net_inputs = []net_outputs = []caches = []net_inputs.append(x)net_outputs.append(x)caches.append(x)for i in range(1, self.weight_num):# 所有隐层的输入都进行BN,输入层和输出层不进行BNx = x = x @ self.params['w'+str(i)].Tnet_inputs.append(x)x, cache = self.batch_norm(x, i, bn_mode)  # 可以将BN理解为加在隐藏层神经元输入和输出间可训练的一层caches.append(cache)x = tanh(x)net_outputs.append(x)out = x @ self.params['w' + str(self.weight_num)].Tnet_inputs.append(out)out = softmax(out)net_outputs.append(out)return {'net_inputs': net_inputs, 'net_outputs': net_outputs, 'cache': caches}, outdef backward_bn(self, nets, y, pred):"""加入BN层的反向传播"""epsilon = 1e-6momentum = 0.9grads = dict()# 求解输出层梯度,依据链式法则,无BNgrads['dz' + str(self.weight_num)] = (pred - y)grads['dw' + str(self.weight_num)] = grads['dz' + str(self.weight_num)].T @ nets['net_outputs'][self.weight_num - 1]for i in reversed(range(1, self.weight_num)):N = nets['cache'][i]['x'].shape[0]grads['dz'+str(i)] = grads['dz' + str(i + 1)] @ self.params['w' + str(i + 1)]grads['dgamma'+str(i)] = np.sum(grads['dz'+str(i)] * nets['cache'][i]['x_hat'])grads['dbeta'+str(i)] = np.sum(grads['dz'+str(i)], axis=0)dx_hat = grads['dz'+str(i)] * self.params['gamma'+str(i)]dsigma = -0.5 * np.sum(dx_hat * (nets['cache'][i]['x'] - nets['cache'][i]['sample_mean']), axis=0) * np.power(nets['cache'][i]['sample_var'][i] + epsilon, -1.5)dmu = -np.sum(dx_hat / np.sqrt(nets['cache'][i]['sample_var'] + epsilon), axis=0) - 2 * dsigma * np.sum(nets['cache'][i]['x'] - nets['cache'][i]['sample_mean'], axis=0) / Ndx = dx_hat / np.sqrt(nets['cache'][i]['sample_var'] + epsilon) + 2.0 * dsigma * (nets['cache'][i]['x'] - nets['cache'][i]['sample_mean']) / N + dmu / Ntemp = dx * tanh_gradient(nets['net_inputs'][i])grads['dw'+str(i)] = temp.T @ nets['net_outputs'][i-1]return gradsdef train_bn(self, data_loader, valid_loader, epochs, learning_rate):losses_train = []losses_valid = []for epoch in range(epochs):print("epoch", epoch)epoch_loss_train = 0# 重置全局均值和方差# 批量训练for step, (x, y) in enumerate(data_loader):# x:[b, 28, 28] -> [b, 784] , y:[b, 1] -> [b, 10]x = x.reshape(-1, 28 * 28)y = onehot(y, 10)nets, pred = self.forward_bn(x, bn_mode='train')grads = self.backward_bn(nets, y, pred)self.optimizer.optimize(self.weight_num, self.params, grads, y.shape[0])loss = cross_entropy(y, pred)epoch_loss_train += lossif step % 100 == 0:print("epoch {} step {} loss {:.4f}".format(epoch, step, loss))losses_train.append(epoch_loss_train)data_loader.restart()print(epoch_loss_train)# 验证集测试epoch_loss_valid = 0for step, (x, y) in enumerate(valid_loader):x = x.reshape(-1, 28 * 28)y = onehot(y, 10)nets, pred = self.forward_bn(x, bn_mode='test')loss = cross_entropy(y, pred)epoch_loss_valid += lossif step % 100 == 0:print("epoch {} step {} loss {:.4f}".format(epoch, step, loss))losses_valid.append(epoch_loss_valid)valid_loader.restart()his = {'train_loss': losses_train, 'valid_loss': losses_valid}return hisdef predict(self, data_loader, bn=False):labels = []pred = []losses = 0for (x, y) in data_loader:x = x.reshape(-1, 28 * 28)y = onehot(y, 10)if bn:_, out = self.forward_bn(x, 'test')else:_, out = self.forward(x)loss = cross_entropy(y, out)losses += lossout = list(np.argmax(out, axis=-1).flatten())y = list(np.argmax(y, axis=1).flatten())labels += ypred += outreturn np.array(pred).astype('int'), np.array(labels).astype('int')

训练效果

对比有无归一化

主要用途

调整输入层数值尺度,以便统一使用梯度下降优化时统一各层参数优化的学习率。(不然输入层需要使用较低学习率)

使用效果

收敛更快 即损失下降更快。

训练集

验证集

测试集

对比有无Dropout

主要用途

随机关闭神经元以减少神经元之间的相关度,从而逼迫神经网络进行更加复杂的学习,有效抑制过拟合。

使用效果

训练收敛变慢,测试效果变好。

训练集

验证集

测试集

对比有无Batch Normalization

主要用途

将各层网络的输入统一为标准正态分布,加快网络的学习,有效解决训练的相关问题。

使用效果

训练加速。

训练集


验证集

测试集

补充说明

本案例均使用Numpy手写神经网络的训练,如有疏漏之处欢迎之处。源码开源于我的Github,欢迎star和fork。

Numpy实现BP神经网络(包含Dropout、BN等训练技巧)相关推荐

  1. Pytorch学习记录(七):自定义模型 Auto-Encoders 使用numpy实现BP神经网络

    文章目录 1. 自定义模型 1.1 自定义数据集加载 1.2 自定义数据集数据预处理 1.3 图像数据存储结构 1.4 模型构建 1.5 训练模型 2. Auto-Encoders 2.1 无监督学习 ...

  2. 【BP神经网络】使用反向传播训练多层神经网络的原则+“常见问题”

    (Principles of training multi-layer neural network using backpropagation) 使用反向传播训练多层神经网络的原则 (The pro ...

  3. 【数据竞赛】Kaggle神技:一项堪比Dropout的NN训练技巧!

    ↑↑↑关注后"星标"kaggle竞赛宝典 kaggle竞赛宝典 作者:杰少 Swap Noise: 一种论文中所没有的NN神技 01 背景 本文介绍一种论文中所没有,但是却效果极佳 ...

  4. BP神经网络之鸢尾花

    转载自:https://www.cnblogs.com/418ks/p/6053689.html BP神经网络基本原理: 误差逆传播(back propagation, BP)算法是一种计算单个权值变 ...

  5. BP神经网络分类以及对算法进行改进—MATLAB实现

    文章目录 一 BP神经网络介绍 二 案例应用-语音特征信号识别 2.1 案例说明 2.2 MATLAB实现 2.2.1 数据归一化 2.2.2 编程实现 2.2.2.1数据选择和归一化 2.2.2.2 ...

  6. 使用BP神经网络进行预测(电力负荷预测)

    目录 摘要: 1.电力负荷数据导入 2.输入输出数据归一化 3.建立和训练BP神经网络 4.使用测试数据进行负荷预测 5.Matlab代码: 摘要: 使用BP神经网络实现简单的电力负荷回归预测任务.主 ...

  7. bp神经网络预测模型优点,bp神经网络缺点及克服

    BP神经网络的核心问题是什么?其优缺点有哪些? 人工神经网络,是一种旨在模仿人脑结构及其功能的信息处理系统,就是使用人工神经网络方法实现模式识别.可处理一些环境信息十分复杂,背景知识不清楚,推理规则不 ...

  8. 用Python实现BP神经网络识别MNIST手写数字数据集(带GUI)

    概述 计算机神经网络则是人工智能中最为基础的也是较为重要的部分,它使用深度学习的方式模拟了人的神经元的工作,是一种全新的计算方法.本文的目标就是通过学习神经网络的相关知识,了解并掌握BP神经网络的实现 ...

  9. 《MATLAB神经网络案例分析》学习(一)——BP神经网络基本理论

    一.BP神经网络理论基本介绍 BP(Back Propagation)是一种按误差逆传播算法训练的多层前馈网络,是应用最广泛的神经网络模型之一.BP网络能学习和存贮大量的输入-输出模式映射关系,而无需 ...

最新文章

  1. Java项目:学生管理系统(无库版)(java+打印控制台)
  2. 第十六届全国大学生智能车竞赛全国总决赛报名信息汇总
  3. RecyclerView嵌套TextView时显示文字不全的解决方法之一
  4. python的lambda函数_Python-Lambda函数的范围及其参数?
  5. asp.net 微信企业号办公系统-流程设计--保存与发布
  6. oracle的age datetime,python cx_Oracle插入TIMESTAMP字段后显示格式问题?
  7. Eclipse基金会
  8. Python_XlrdXlwt
  9. java只使用try和finally不使用catch的原因和场景
  10. 芝麻信用综合评估未通过,请选择商户支持的其他方式使用服务
  11. Spring Boot笔记-@Qualifier与@Autowired与@Bean
  12. groovy和java结合使用
  13. 《那些年啊,那些事——一个程序员的奋斗史》——37
  14. MFC的凸包实例【赶紧进来膜拜】
  15. 用户界面设计参考 (ZT)
  16. oracle判断数字为复数,oracle学习笔记(十二) 查询练习(二) 高级查询
  17. Structs框架原理
  18. CRC32绕过RAR密码
  19. 点成分享 | 麦氏比浊仪在药敏试验中的应用
  20. 使用存储过程返回结果集

热门文章

  1. 深入理解数据库行锁与表锁
  2. 缓冲区Buffer-Buffer操作基本API
  3. Lambda表达式练习2【应用】
  4. 仓库的种类和彼此关系
  5. 缓存-SpringCache-整合体验@Cacheable
  6. 使用字节流读取中文的问题
  7. 线性表及其逻辑和存储结构(二级)
  8. 深入理解Kafka(2)-Producer
  9. python组成不重复的三位数是多少_超星Python 练习实例1-组成多少个互不相同且无重复的三位数字...
  10. 在操作系统理论中,什么是饿死