纯用NumPy实现神经网络的示例代码


Posted in Python onOctober 24, 2018

摘要: 纯NumPy代码从头实现简单的神经网络。

纯用NumPy实现神经网络的示例代码

Keras、TensorFlow以及PyTorch都是高级别的深度学习框架,可用于快速构建复杂模型。前不久,我曾写过一篇文章,对神经网络是如何工作的进行了简单的讲解。该文章侧重于对神经网络中运用到的数学理论知识进行详解。本文将利用NumPy实现简单的神经网络,在实战中对其进行深层次剖析。最后,我们会利用分类问题对模型进行测试,并与Keras所构建的神经网络模型进行性能的比较。

Note:源码可在我的GitHub中查看。

纯用NumPy实现神经网络的示例代码

在正式开始之前,需要先对所做实验进行构思。我们想要编写一个程序,使其能够创建一个具有指定架构(层的数量、大小以及激活函数)的神经网络,如图一所示。总之,我们需要预先对网络进行训练,然后利用它进行预测。

纯用NumPy实现神经网络的示例代码

上图展示了神经网络在被训练时的工作流程。从中我们可以清楚的需要更新的参数数量以及单次迭代的不同状态。构建并管理正确的数据架构是其中最困难的一环。由于时间限制,图中所示的参数不会一一详解,有兴趣可点击此处进行了解。

纯用NumPy实现神经网络的示例代码

神经网络层的初始化

首先,对每一层的权重矩阵W及偏置向量b进行初始化。在上图中,上标[l]表示目前是第几层(从1开始),n的值表示一层中的神经元数量。描述神经网络架构的信息类似于Snippet 1中所列内容。每一项都描述了单层神经网络的基本参数:input_dim,即输入层神经元维度;output_dim,即输出层神经元维度;activation,即使用的激活函数。

nn_architecture = [
  {"input_dim": 2, "output_dim": 4, "activation": "relu"},
  {"input_dim": 4, "output_dim": 6, "activation": "relu"},
  {"input_dim": 6, "output_dim": 6, "activation": "relu"},
  {"input_dim": 6, "output_dim": 4, "activation": "relu"},
  {"input_dim": 4, "output_dim": 1, "activation": "sigmoid"},
]

Snippet 1.

从Snippet 1可看出,每一层输出神经元的维度等于下一层的输入维度。对权重矩阵W及偏置向量b进行初始化的代码如下:

def init_layers(nn_architecture, seed = 99):
  np.random.seed(seed)
  number_of_layers = len(nn_architecture)
  params_values = {}

  for idx, layer in enumerate(nn_architecture):
    layer_idx = idx + 1
    layer_input_size = layer["input_dim"]
    layer_output_size = layer["output_dim"]

    params_values['W' + str(layer_idx)] = np.random.randn(
      layer_output_size, layer_input_size) * 0.1
    params_values['b' + str(layer_idx)] = np.random.randn(
      layer_output_size, 1) * 0.1

  return params_values

Snippet 2.

在本节中,我们利用NumPy将权重矩阵W及偏置向量b初始化为小的随机数。特别注意的是,初始化权重值不能相同,否则网络会变为对称的。也就是说,如果权重初始化为同一值,则对于任何输入X,每个隐藏层对应的每个神经元的输出都是相同的,这样即使梯度下降训练,无论训练多少次,这些神经元都是对称的,无论隐藏层内有多少个结点,都相当于在训练同一个函数。

初始化的值较小能够使得算法第一次迭代的时候效率更高。Sigmoid函数图像如下图所示,它对中央区的信号增益较大,对两侧区的信号增益小。

纯用NumPy实现神经网络的示例代码

激活函数(Activation functions)

激活函数在神经网络中至关重要,其原理简单但功能强大,给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,从而应用于众多的非线性模型。“如果没有激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合。”激活函数种类众多,本文选取了最常用的两种——ReLU及Sigmoid函数,代码如下:

def sigmoid(Z):
  return 1/(1+np.exp(-Z))

def relu(Z):
  return np.maximum(0,Z)

def sigmoid_backward(dA, Z):
  sig = sigmoid(Z)
  return dA * sig * (1 - sig)

def relu_backward(dA, Z):
  dZ = np.array(dA, copy = True)
  dZ[Z <= 0] = 0;
  return dZ;

Snippet 3.

前向传播算法(Forward propagation)

本文所设计的神经网络结构简单,信息流只有一个方向:以X矩阵的形式传递,穿过所有隐藏层单元,最终输出预测结构Y_hat。

def single_layer_forward_propagation(A_prev, W_curr, b_curr, activation="relu"):
  Z_curr = np.dot(W_curr, A_prev) + b_curr

  if activation is "relu":
    activation_func = relu
  elif activation is "sigmoid":
    activation_func = sigmoid
  else:
    raise Exception('Non-supported activation function')

  return activation_func(Z_curr), Z_curr

Snippet 4.

前向传播就是上层处理完的数据作为下一层的输入数据,然后进行处理(权重),再传给下一层,这样逐层处理,最后输出。给定上一层的输入信号,计算仿射变换(affine transformation)Z,然后应用选定的激活函数。

纯用NumPy实现神经网络的示例代码

纯用NumPy实现神经网络的示例代码

前向传播算法代码如下,该函数不仅进行预测计算,还存储中间层A和Z矩阵的值:

def full_forward_propagation(X, params_values, nn_architecture):
  memory = {}
  A_curr = X

  for idx, layer in enumerate(nn_architecture):
    layer_idx = idx + 1
    A_prev = A_curr

    activ_function_curr = layer["activation"]
    W_curr = params_values["W" + str(layer_idx)]
    b_curr = params_values["b" + str(layer_idx)]
    A_curr, Z_curr = single_layer_forward_propagation(A_prev, W_curr, b_curr, activ_function_curr)

    memory["A" + str(idx)] = A_prev
    memory["Z" + str(layer_idx)] = Z_curr

  return A_curr, memory

Snippet 5.

损失函数(Loss function)

损失函数是用来估量模型的预测值与真实值的不一致程度,它是一个非负实值函数。损失函数由我们想要解决的问题所决定。在本文中,我们想要测试神经网络模型区分两个类别的能力,所以选择了交叉熵损失函数(binary_crossentropy),其定义如下:

纯用NumPy实现神经网络的示例代码

为了更加清楚的了解学习过程,我增添了一个用于计算精度的函数:

def get_cost_value(Y_hat, Y):
  m = Y_hat.shape[1]
  cost = -1 / m * (np.dot(Y, np.log(Y_hat).T) + np.dot(1 - Y, np.log(1 - Y_hat).T))
  return np.squeeze(cost)

def get_accuracy_value(Y_hat, Y):
  Y_hat_ = convert_prob_into_class(Y_hat)
  return (Y_hat_ == Y).all(axis=0).mean()

Snippet 6.

反向传播算法(Backward propagation)

许多缺乏经验的深度学习爱好者认为反向传播是一种复杂且难以理解的算法。

def single_layer_backward_propagation(dA_curr, W_curr, b_curr, Z_curr, A_prev, activation="relu"):
  m = A_prev.shape[1]

  if activation is "relu":
    backward_activation_func = relu_backward
  elif activation is "sigmoid":
    backward_activation_func = sigmoid_backward
  else:
    raise Exception('Non-supported activation function')

  dZ_curr = backward_activation_func(dA_curr, Z_curr)
  dW_curr = np.dot(dZ_curr, A_prev.T) / m
  db_curr = np.sum(dZ_curr, axis=1, keepdims=True) / m
  dA_prev = np.dot(W_curr.T, dZ_curr)

  return dA_prev, dW_curr, db_curr

Snippet 7.

其实,他们困惑的也就是反向传播算法中的梯度下降问题,但二者并不可混为一谈。前者旨在有效地计算梯度,而后者是利用计算得到的梯度进行优化。梯度下降可以应对带有明确求导函数的情况,我们可以把它看作没有隐藏层的网络;但对于多隐藏层的神经网络,应先将误差反向传播至隐藏层,然后再应用梯度下降,其中将误差从最末层往前传递的过程需要链式法则,反向传播算法可以说是梯度下降在链式法则中的应用。对于单层的神经网络,该过程如下所示:

纯用NumPy实现神经网络的示例代码

本文省略的推导过程,但从上面的公式仍可看出A和Z矩阵值的重要性。

纯用NumPy实现神经网络的示例代码

Snippet 7中所示代码仅编写了神经网络中某层的反向传播算法,Snippet 8将展示神经网络中完整的反向传播算法。

def full_backward_propagation(Y_hat, Y, memory, params_values, nn_architecture):
  grads_values = {}
  m = Y.shape[1]
  Y = Y.reshape(Y_hat.shape)

  dA_prev = - (np.divide(Y, Y_hat) - np.divide(1 - Y, 1 - Y_hat));

  for layer_idx_prev, layer in reversed(list(enumerate(nn_architecture))):
    layer_idx_curr = layer_idx_prev + 1
    activ_function_curr = layer["activation"]

    dA_curr = dA_prev

    A_prev = memory["A" + str(layer_idx_prev)]
    Z_curr = memory["Z" + str(layer_idx_curr)]
    W_curr = params_values["W" + str(layer_idx_curr)]
    b_curr = params_values["b" + str(layer_idx_curr)]

    dA_prev, dW_curr, db_curr = single_layer_backward_propagation(
      dA_curr, W_curr, b_curr, Z_curr, A_prev, activ_function_curr)

    grads_values["dW" + str(layer_idx_curr)] = dW_curr
    grads_values["db" + str(layer_idx_curr)] = db_curr

  return grads_values

Snippet 8.

参数更新(Updating parameters values)

该部分旨在利用计算得到梯度更新网络中的参数,同时最小化目标函数。我们会使用到params_values,它存放当前的参数值,以及grads_values,它存放存储关于这些参数的损失函数的导数。现在只需要在神经网络的每层应用如下公式即可:

纯用NumPy实现神经网络的示例代码

def update(params_values, grads_values, nn_architecture, learning_rate):
  for layer_idx, layer in enumerate(nn_architecture):
    params_values["W" + str(layer_idx)] -= learning_rate * grads_values["dW" + str(layer_idx)]    
    params_values["b" + str(layer_idx)] -= learning_rate * grads_values["db" + str(layer_idx)]

  return params_values;

Snippet 9.

整合(Putting things together)

现在我们只需将准备好的函数按照正确的顺序整合到一起,若对正确的顺序有疑问请参见图2。

def train(X, Y, nn_architecture, epochs, learning_rate):
  params_values = init_layers(nn_architecture, 2)
  cost_history = []
  accuracy_history = []

  for i in range(epochs):
    Y_hat, cashe = full_forward_propagation(X, params_values, nn_architecture)
    cost = get_cost_value(Y_hat, Y)
    cost_history.append(cost)
    accuracy = get_accuracy_value(Y_hat, Y)
    accuracy_history.append(accuracy)

    grads_values = full_backward_propagation(Y_hat, Y, cashe, params_values, nn_architecture)
    params_values = update(params_values, grads_values, nn_architecture, learning_rate)

  return params_values, cost_history, accuracy_history

Snippet 10.

对比分析(David vs Goliath)

接下来,我们将利用所构建的模型解决简单的分类问题。如图7所示,本次实验使用的数据集包含两个类别。我们将训练模型对两个不同的类别进行区分。此外,我们还准备了一个由Keras所构建的神经网络模型以进行对比。两个模型具有相同的架构和学习速率。虽然我们的模型很简单,但结果表明,NumPy和Keras模型在测试集上均达到了95%的准确率。只是我们的模型耗费了更多的时间,未来工作可通过加强优化改善时间开销问题。

纯用NumPy实现神经网络的示例代码

纯用NumPy实现神经网络的示例代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python生成随机验证码(中文验证码)示例
Apr 03 Python
Python接收Gmail新邮件并发送到gtalk的方法
Mar 10 Python
Python的字典和列表的使用中一些需要注意的地方
Apr 24 Python
Python实现快速排序算法及去重的快速排序的简单示例
Jun 26 Python
Python实现读写sqlite3数据库并将统计数据写入Excel的方法示例
Aug 07 Python
python timestamp和datetime之间转换详解
Dec 11 Python
Python tkinter实现的图片移动碰撞动画效果【附源码下载】
Jan 04 Python
python pandas消除空值和空格以及 Nan数据替换方法
Oct 30 Python
Anaconda详细安装步骤图文教程
Nov 12 Python
Python django框架 web端视频加密的实例详解
Nov 20 Python
用python-webdriver实现自动填表的示例代码
Jan 13 Python
Python生成九宫格图片的示例代码
Apr 14 Python
Django添加KindEditor富文本编辑器的使用
Oct 24 #Python
Django使用paginator插件实现翻页功能的实例
Oct 24 #Python
Python将8位的图片转为24位的图片实现方法
Oct 24 #Python
Python SMTP发送邮件遇到的一些问题及解决办法
Oct 24 #Python
使用PyCharm创建Django项目及基本配置详解
Oct 24 #Python
python实现在图片上画特定大小角度矩形框
Oct 24 #Python
python多进程使用及线程池的使用方法代码详解
Oct 24 #Python
You might like
受疫情影响 动画《Re从零开始的异世界生活》第二季延期至7月
2020/03/10 日漫
php网页标题中文乱码的有效解决方法
2014/03/05 PHP
php按百分比生成缩略图的代码分享
2014/05/10 PHP
PHP学习笔记之session
2018/05/06 PHP
jQuery的学习步骤
2011/02/23 Javascript
JS图片无缝滚动(简单利于使用)
2013/06/17 Javascript
js获取控件位置以及不同浏览器中的差别介绍
2013/08/08 Javascript
SOSO地图JS画出标注和中心点以html形式运行
2013/08/09 Javascript
通过pjax实现无刷新翻页(兼容新版jquery)
2014/01/31 Javascript
javascript的动态加载、缓存、更新以及复用(一)
2014/06/09 Javascript
详解jquery中$.ajax方法提交表单
2014/11/03 Javascript
JS折半插入排序算法实例
2015/12/02 Javascript
javascript实现图片轮播效果
2016/01/20 Javascript
AngularJS ng-mousedown 指令
2016/08/02 Javascript
JavaScript实战之菜单特效
2016/08/16 Javascript
localStorage实现便签小程序
2016/11/28 Javascript
javaScript和jQuery自动加载简单代码实现方法
2017/11/24 jQuery
如何去除富文本中的html标签及vue、react、微信小程序中的过滤器
2018/11/21 Javascript
原生JS forEach()和map()遍历的区别、兼容写法及jQuery $.each、$.map遍历操作
2019/02/27 jQuery
浅谈JavaScript中this的指向问题
2020/07/28 Javascript
JavaScript 实现轮播图特效的示例
2020/11/05 Javascript
Python中类型检查的详细介绍
2017/02/13 Python
python 读写excel文件操作示例【附源码下载】
2019/06/19 Python
Anaconda和ipython环境适配的实现
2020/04/22 Python
Win10用vscode打开anaconda环境中的python出错问题的解决
2020/05/25 Python
使用scrapy ImagesPipeline爬取图片资源的示例代码
2020/09/28 Python
python 实现音频叠加的示例
2020/10/29 Python
size?德国官方网站:英国伦敦的球鞋精品店
2018/03/17 全球购物
维德科技C#面试题笔试题
2015/12/09 面试题
大三学生做职业规划:给未来找个方向
2014/02/24 职场文书
小学英语课后反思
2014/04/26 职场文书
python神经网络编程之手写数字识别
2021/05/08 Python
理解python中装饰器的作用
2021/07/21 Python
mongodb清除连接和日志的正确方法分享
2021/09/15 MongoDB
Python识别花卉种类鉴定网络热门植物并自动整理分类
2022/04/08 Python
zabbix如何添加监控主机和自定义监控项
2022/08/14 Servers