【神经网络】:手写数字识别,一文带你掌握!

155 阅读12分钟

要做什么

我们的要做的是,训练出一个人工神经网络(ANN),使它能够识别手写数字(如下图所示)。

image.png

背景

人工神经网络是什么

人工神经网络(Artificial Neural Network,简称ANN),也被称为神经网络(NN),是受生物神经网络(Biological Neural Networks)启发的计算系统

神经网络是一种重要的人工智能技术,其在图像识别、自然语言处理、医疗和金融等领域得到了广泛应用。

image.png

手写数字从哪来

这些手写数字,来自大名鼎鼎的 MNIST 手写数字图像集。

MNIST 是机器学习领域最有名的数据集之一,也称作计算机视觉领域的 hello world 数据集。

image.png

基础

由于人工神经网络(Artificial Neural Network)的灵感来源是生物神经网络(Biological Neural Networks),那么接下来,我将按照以下思路讲解:

  1. 生物神经网络 的基本结构和特性。
  2. 人工神经网络 如何对其进行模拟。

生物神经网络

生物神经网络一般指,生物的大脑神经元、细胞、触突等结构组成的一个大型网络结构,用来帮助生物进行思考和行动。

【神经元】

神经元即神经元细胞,是神经系统最基本的结构和功能单位

神经元包括胞体突起两大部分,突起又分为轴突树突

image.png

下面从功能角度,对生物神经元进行进行介绍:

  • 神经信号的输入:

    • 神经元的树突,负责接收其他神经元传来的神经信号,并传给胞体
    • 一个神经元通常有多个树突,即一个神经元可以接收来自多个神经元的输入
  • 神经元的激活:

    • 胞体是神经元新陈代谢的中心,负责接收整合从树突传来的神经信号。

    • 胞体接收到神经信号输入之后,不会立即做出反应,而是要等输入积累到超过一个阈值,才会被激活

      • 积累不同时间通过同一突触传入的神经信号。
      • 积累同一时间通过不同突触传人的神经信号。
  • 神经信号的输出:

    • 胞体被激活后,它会沿轴突向其它神经元发出神经信号。
    • 轴突比树突长而细,也叫神经纤维,末端处有很多细的分支称为神经末梢,每一条神经末梢可以向四面八方传出信号,神经末梢和和另一个神经元进行通信的结构叫突触,用于输出神经元的神经信号。
    • 需要注意的是,相同的同神经信号可能对接受它的不同神经元有不同的效果。这一效果主要由突触决定:突触的“联结强度”越大,接受的信号就越强,反之,就越弱。

image.png

【神经网络】

生物神经系统由大量神经元构成,每个神经元都可能连接到数千个其他神经元,这会形成一个复杂的神经网络。这个网络使得我们可以感知环境、思考、记忆、学习等。我们的每一个思想、感觉和行动都是由这种神经元间的连接和交流驱动的。

image.png

【知识学习】

1949 年 Hebb 提出了突触学习的模型(Hebb定律)。该理论认为,神经网络是具有可塑性的,重复性的经验可以加强或削弱神经元间的连接,即:学习,在脑分子层次上,导致的是神经元间突触连接的变化。这使得我们的大脑可以适应新的信息、经历和学习。

image.png

人工神经网络

神经网络是一种对生物神经网络组织结构和运行机制的抽象、简化和模拟,以期能够实现类人工智能的机器学习技术。

下面还是从功能的角度,以对比的方式,描述人工神经网络如何实现对生物神经网络的抽象、简化和模拟。

我们先对前面所讲的,生物神经元、神经网络最核心的特性,进行提炼:

  • 神经信号的输入:
    • 一个神经元可以接收来自多个神经元的输入。【核心1:多个输入
  • 神经元的激活:
    • 神经元会积累输入的神经信号。【核心2:积累输入
    • 积累的神经信号,超过一个阈值,神经元才会被激活【核心3:输入超过阈值才激活】。
  • 神经元的输出:
    • 被激活的神经元,会沿着轴突发出神经信号。【核心4:激活后产生输出
    • 轴突的神经末梢会向四面八方传出信号。【核心5:一个输出可以作用到多个后级神经元
    • 相同的输出可能会对后级神经元产生不同的效果。【核心5:一个输出,可能会对不同的后级神经元,产生不同的影响
  • 核心6:海量神经元相互连接,形成神经网络】。
  • 核心7:重复的经验可以加强、削弱神经元之间的联结强度。】。

所以人工神经元的抽象模型,如下图所示:

image.png

image.png

image.png

运行

接下来,我们从运行的角度,描述人工神经网络是如何工作的。

一些记号

由于后面几节中会反复使用一些记号,所以在这里把后面会用到的记号以及含义写在这里,方便查阅。

记号含义备注
I=(input1input2input3)I = \begin{pmatrix} input_1 \\ input_2 \\ input_3 \end{pmatrix}输入信号
wi,jw_{i,j}权重,前一层节点 i 与下一层节点 j 间的联结强度。
W=[w1,1w2,1w3,1wn,1w1,2w2,2w3,2wn,2w1,3w2,3w3,3wn,3w1,nw2,nw3,nwn,n]W=\begin{bmatrix}w_{1,1}&w_{2,1}&w_{3,1}&\cdots &w_{n,1} \\w_{1,2}&w_{2,2}&w_{3,2} & \cdots &w_{n,2}\\w_{1,3}&w_{2,3}&w_{3,3}&\cdots &w_{n,3} \\ \vdots &\vdots& \vdots&\ddots &\vdots \\ w_{1,n}&w_{2,n}&w_{3,n}&\dots &w_{n,n} \end{bmatrix}权重矩阵
WT=W^{T} = [w1,1w1,2w1,3w1,nw2,1w2,2w2,3w2,nw3,1w3,2w3,3w3,nwn,1wn,2wn,3wn,n]\begin{bmatrix}w_{1,1}&w_{1,2}&w_{1,3}&\cdots &w_{1,n} \\w_{2,1}&w_{2,2}&w_{2,3} & \cdots &w_{2,n}\\w_{3,1}&w_{3,2}&w_{3,3}&\cdots &w_{3,n} \\ \vdots &\vdots& \vdots&\ddots &\vdots \\ w_{n,1}&w_{n,2}&w_{n,3}&\dots &w_{n,n} \end{bmatrix}权重矩阵的转置
sigmoid(x)=11+exsigmoid(x) = \dfrac{1}{1 + e^{-x}} 激活函数
Winput_hiddenW_{input\_hidden}输入层和隐藏层之间的权重矩阵
XhiddenX_{hidden} 隐藏层聚合后的输入
Ohidden=sigmoid(Xhidden)O_{hidden} = sigmoid(X_{hidden}) 隐藏层的输出结果
Whidden_outputW_{hidden\_output}隐藏层和输出层之间的权重矩阵
XoutputX_{output} 输出层聚合后的输入
Ooutput=sigmoid(Xoutput)O_{output} = sigmoid(X_{output}) 输出层的输出结果
erroroutput error_{output}输出层误差
errorhiddenerror_{hidden}隐藏层误差
ei=tioie_i = t_i - o_i误差 = 真是结果 - 输出结果
E=n(tnon)2E = \sum_n(t_n - o_n)^2神经网络误差函数
α\alpha 学习率
Ewij\dfrac{∂E}{∂w_{ij}} 权重变化时,网络误差的变化方式

神经信号在网络中的正向传播

我们使用一个有3层、每层3个神经元的神经网络,讲解神经信号在神经网络中的正向传播过程。

image.png

第一步,计算隐藏层各神经元的神经信号输入

  • x1=input1w1,1+input2w2,1+input3w3,1x_{1} = input_1 * w_{1,1} + input_2 * w_{2,1} + input_3 * w_{3,1}
  • x2=input1w1,2+input2w2,2+input3w3,2x_{2} = input_1 * w_{1,2} + input_2 * w_{2,2} + input_3 * w_{3,2}
  • x3=input1w1,3+input2w2,3+input3w3,3x_{3} = input_1 * w_{1,3} + input_2 * w_{2,3} + input_3 * w_{3,3}

用矩阵来表达:

Xhidden=(w1,1w2,1w3,1w1,2w2,2w3,2w1,3w2,3w3,3)IX_{hidden} = \begin{pmatrix} w_{1,1} & w_{2,1} & w_{3,1} \\ w_{1,2} & w_{2,2} & w_{3,2} \\ w_{1,3} & w_{2,3} & w_{3,3} \end{pmatrix} \cdot I

再简化一下:

Xhidden=Winput_hiddenIX_{hidden} = W_{input\_hidden} \cdot I

第二步:计算隐藏层各神经元的神经信号输出

即,对隐藏层各神经单元的输入,施加激活函数sigmoidsigmoid

  • o1=sigmoid(x1)o_1 = sigmoid(x_1)
  • o2=sigmoid(x2)o_2 = sigmoid(x_2)
  • o3=sigmoid(x3)o_3 = sigmoid(x_3)

用矩阵来表达:

Ohidden=sigmoid(Xhidden)O_{hidden} = sigmoid(X_{hidden})

同理:

第三步输出层各神经元的神经信号输入

Xoutput=Whidden_outputOhiddenX_{output} = W_{hidden\_output} \cdot O_{hidden}

第四步输出层各神经元的神经信号输出

Ooutput=sigmoid(Xoutput)O_{output} = sigmoid(X_{output})

以上就是神经信号在神经网络中的正向传播过程。

神经网络误差的反向传播

从这一节开始,就进入了神经网络的训练过程,即神经网络积累知识的过程。

上一节描述了,神经信号如何输入神经网络并得到输出的过程。从我们要做的事情“训练出一个人工神经网络,使它能够识别手写数字”角度说,我们最终的期望就是,把一张手写数字7的图片输入神经网络,神经网络的输出是数字7,把一张手写数字3的图片输入神经网络,神经网络的输出是数字3。

所谓“训练”,就是事先准备好一堆有正确答案的数据(比如:图1是3,图2是6,图2是9 ...),把这些数据和答案输入神经网络,让神经网络自己从这堆数据中把数据中潜藏的“知识”提炼出来,这些提炼出来的知识表现为神经网络中确定下来的权重。

由于还未经过训练的神经网络无法完成我们期望它做的事情(例如:输入手写数字3,输出是6;输入手写数字1,输出是9 ...)。也就是说,对于给定神经网络的输入,我们期望神经网络应该输出的结果 和 神经网络的实际输出结果之间存在误差

所以,针对这个误差,我们要做的工作是:

  1. 将神经网络的输出误差反向传播到神经网络中的各个神经元上;
  2. 找到某种方式,能让我们利用这个误差来指导我们调整神经网络中的权重,以达到最小化神经网络输出误差的目的。

我们先解决第一个问题:将神经网络的输出误差反向传播到神经网络中的各个神经元上。

image.png

第一步,计算隐藏层各神经元的误差。

这里采用的是按照权重的比例,将误差反向传播到隐藏层的结点上。

  • ehidden,1=w1,1eoutput,1w1,1+w2,1+w3,1+w1,2eoutput,2w1,2+w2,2+w3,2+w1,3eoutput,3w1,3+w2,3+w3,3e_{hidden,1} = \dfrac{w_{1,1} * e_{output,1}}{w_{1,1} + w_{2,1} + w_{3,1}} + \dfrac{w_{1,2}* e_{output,2}}{w_{1,2} + w_{2,2} + w_{3,2}} + \dfrac{w_{1,3}* e_{output,3}}{w_{1,3} + w_{2,3} + w_{3,3}}
  • ehidden,2=w2,1eoutput,1w1,1+w2,1+w3,1+w2,2eoutput,2w1,2+w2,2+w3,2+w2,3eoutput,3w1,3+w2,3+w3,3e_{hidden,2} = \dfrac{w_{2,1}* e_{output,1}}{w_{1,1} + w_{2,1} + w_{3,1}} + \dfrac{w_{2,2}* e_{output,2}}{w_{1,2} + w_{2,2} + w_{3,2}} + \dfrac{w_{2,3}* e_{output,3}}{w_{1,3} + w_{2,3} + w_{3,3}}
  • ehidden,3=w3,1eoutput,1w1,1+w2,1+w3,1+w3,2eoutput,2w1,2+w2,2+w3,2+w3,3eoutput,3w1,3+w2,3+w3,3e_{hidden,3} = \dfrac{w_{3,1}* e_{output,1}}{w_{1,1} + w_{2,1} + w_{3,1}} + \dfrac{w_{3,2}* e_{output,2}}{w_{1,2} + w_{2,2} + w_{3,2}} + \dfrac{w_{3,3}* e_{output,3}}{w_{1,3} + w_{2,3} + w_{3,3}}

用矩阵来表达就是

errorhidden=(w1,1w1,1+w2,1+w3,1w1,2w1,2+w2,2+w3,2w1,3w1,3+w2,3+w3,3w2,1w1,1+w2,1+w3,1w2,2w1,2+w2,2+w3,2w2,3w1,3+w2,3+w3,3w3,1w1,1+w2,1+w3,1w3,2w1,2+w2,2+w3,2w3,3w1,3+w2,3+w3,3)erroroutputerror_{hidden} = \begin{pmatrix} \dfrac{w_{1,1}}{w_{1,1} + w_{2,1} + w_{3,1}} & \dfrac{w_{1,2}}{w_{1,2} + w_{2,2} + w_{3,2}} & \dfrac{w_{1,3}}{w_{1,3} + w_{2,3} + w_{3,3}} \\ \dfrac{w_{2,1}}{w_{1,1} + w_{2,1} + w_{3,1}} & \dfrac{w_{2,2}}{w_{1,2} + w_{2,2} + w_{3,2}} & \dfrac{w_{2,3}}{w_{1,3} + w_{2,3} + w_{3,3}} \\ \dfrac{w_{3,1}}{w_{1,1} + w_{2,1} + w_{3,1}} & \dfrac{w_{3,2}}{w_{1,2} + w_{2,2} + w_{3,2}} & \dfrac{w_{3,3}}{w_{1,3} + w_{2,3} + w_{3,3}} \end{pmatrix} \cdot error_{output}

其实上面这个式子中,最重要的部分就是输出误差与链接权重 wi,jw_{i,j} 的乘法,较大的权重就意味着携带较多的输出误差给隐藏层。这些分数的分母是一种归一化因子。如果我们忽略了这个因子,那么我们仅仅失去后馈误差的大小。也就是说,我们可以使用简单得多的 w1,1eoutput,1w_{1,1} * e_{output,1} 来代替 w1,1eoutput,1w1,1+w2,1+w3,1\dfrac{w_{1,1} * e_{output,1}}{w_{1,1} + w_{2,1} + w_{3,1}}

这样矩阵演变为

errorhidden=(w1,1w1,2w1,3w2,1w2,2w2,3w3,1w3,2w3,3)erroroutputerror_{hidden} = \begin{pmatrix} w_{1,1} & w_{1,2} & w_{1,3} \\ w_{2,1} & w_{2,2} & w_{2,3} \\ w_{3,1} & w_{3,2} & w_{3,3} \end{pmatrix} \cdot error_{output}

注意一下这个矩阵和权重矩阵的关系,可以得到,

errorhidden=Winput_outputTerroroutputerror_{hidden} = W_{input\_output}^T \cdot error_{output}

至此,我们完成了神经网络输出误差的反向传播。

image.png

神经网络权重的调整

在完成了第一件事(即,将神经网络的输出误差反向传播到神经网络中的各个神经元上)后

我们再完成第二件事情(即,找到某种方式,能让我们利用这个误差来指导我们调整神经网络中的权重,以达到最小化神经网络输出误差的目的),就可以实现对神经网络的训练。

既然要让神经网络的输出误差最小,那么我们需要先构建神经网络的输出误差函数

E=n(tnon)2E = \sum_n(t_n - o_n)^2
ok=Sigmoid(jwj,kSigmoid(iwi,jinputi))o_k = Sigmoid(\sum_j w_{j,k} * Sigmoid(\sum_i w_{i,j} * input_i))
Sigmoid(x)=11+exSigmoid(x) = \dfrac{1}{1 + e^{-x}}

直接求误差函数的最小值很难,所以我们采用梯度下降法,以小步迭代的方式,找到目标函数(即我们的)的最小值。

image.png

因为我们最终要实现通过调整权重 wijw_{ij} 找到误差函数 EE 的最小值。

而采用“梯度下降法”后,这个问题就转变为:搞清楚“误差对链接权重的改变有多敏感?”。

所以我们的目标就变成了,求解

Ewj,k\dfrac{∂E}{∂w_{j,k}}

展开误差函数,得到:

Ewj,k=n(tnon)2wj,k\dfrac{∂E}{∂w_{j,k}} = \dfrac{∂\sum_n(t_n - o_n)^2}{∂w_{j,k}}

这是对目标值和实际值之差的平方进行求和,这是针对所有 n 个输出节点的和。

image.png

再仔细观察一下上面这张图,当权重 wj,kw_{j,k} 发现变化的时候,其实只会影响到输出结点 oko_k,所以这个误差函数可以进一步简化为:

Ewj,k=(tkok)2wj,k\dfrac{∂E}{∂w_{j,k}} = \dfrac{∂(t_k - o_k)^2}{∂w_{j,k}}

利用链式法则,得到:

Ewj,k=(tkok)2okokwj,k\dfrac{∂E}{∂w_{j,k}} = \dfrac{∂(t_k - o_k)^2}{∂o_k} * \dfrac{∂o_k}{∂w_{j,k}}
Ewj,k=2(tkok)okwj,k\dfrac{∂E}{∂w_{j,k}} = -2(t_k - o_k) * \dfrac{∂o_k}{∂w_{j,k}}

展开 oko_k

Ewj,k=2(tkok)sigmoid(wj,koj)wj,k\dfrac{∂E}{∂w_{j,k}} = -2(t_k - o_k) * \dfrac{∂sigmoid(\sum w_{j,k}*o_j)}{∂w_{j,k}}

由于:

sigmoid(x)x=sigmoid(x)(1sigmoid(x))\dfrac{∂sigmoid(x)}{∂x} = sigmoid(x)(1- sigmoid(x))

所以:

Ewj,k=2(tkok)sigmoid(wj,koj)(1sigmoid(wj,koj))wj,kojwj,k=2(tkok)sigmoid(wj,koj)(1sigmoid(wj,koj))oj=2(tkok)ok(1ok)oj=2ekok(1ok)oj\dfrac{∂E}{∂w_{j,k}} = -2(t_k - o_k) * sigmoid(\sum w_{j,k}*o_j) * (1 - sigmoid(\sum w_{j,k}*o_j)) \dfrac{∂\sum w_{j,k}*o_j}{∂w_{j,k}} \\ = -2(t_k - o_k) * sigmoid(\sum w_{j,k}*o_j) * (1 - sigmoid(\sum w_{j,k}*o_j)) * o_j \\ = -2(t_k - o_k) * o_k * (1 - o_k) * o _j \\ = -2e_k * o_k * (1 - o_k) * o _j

由于我们只对斜率的方向感兴趣,所以可以把 2 去掉:

Ewj,k=ekok(1ok)oj\dfrac{∂E}{∂w_{j,k}} = -e_k * o_k * (1 - o_k) * o _j

所以,根据梯度下降的策略,权重 wj,kw_{j,k} 应该这么调整:

wj,k_new=wj,k_oldαEwj,kw_{j,k\_new} = w_{j,k\_old} - \alpha * \dfrac{∂E}{∂w_{j,k}}

所以

Δwj,k=αekok(1ok)oj\Delta w_{j,k} = \alpha * e_k * o_k * (1 - o_k) * o _j

我们再从矩阵角度看,权重矩阵 Whidden_outputW_{hidden\_output} 如何更新

(Δw1,1Δw2,1Δw3,1Δw1,2Δw2,2Δw3,2Δw1,3Δw2,3Δw3,3)=α(e1o1(1o1)e2o2(1o2)e3o3(1o3))(o1o2o3)\begin{pmatrix} \Delta w_{1,1} & \Delta w_{2,1} & \Delta w_{3,1} \\ \Delta w_{1,2} & \Delta w_{2,2} & \Delta w_{3,2} \\ \Delta w_{1,3} & \Delta w_{2,3} & \Delta w_{3,3} \end{pmatrix} = \alpha \cdot \begin{pmatrix} e_1 * o_1 * (1 - o_1) \\ e_2 * o_2 * (1 - o_2) \\ e_3 * o_3 * (1 - o_3) \end{pmatrix} \cdot \begin{pmatrix} o_1 & o_2 & o_3 \end{pmatrix}

所以

ΔWj,k=αEkOk(1Ok)OjT\Delta W_{j,k} = \alpha * E_k * O_k * (1 - O_k) \cdot O_j^T

同理

ΔWi,j=αEjOj(1Oj)OiT\Delta W_{i,j} = \alpha * E_j * O_j * (1 - O_j) \cdot O_i^T

至此,我们就实现了通过前一层(jj)的输出(OjO_j)、后一层(kk)的输出(OkO_k)以及后一层的误差(EkE_k),微调神经网络两层(jkj、k)间权重(Wj,kW_{j,k}),降低后一层(kk)输出误差(EkE_k)的目的。

梳理一下

现在让我们把上面这一套流程串起来梳理一下,加深一下理解。

第1步:初始化神经网络

  • 构建一个3层(输入层(ii)、隐藏层(jj)、输出层(kk))、每层3个神经元的神经网络。
  • 输入层、隐藏层间的权重(Winput_hiddenW_{input\_hidden})和隐藏层、输出层间的权重(Whidden_outputW_{hidden\_output})先赋随机值。
    • 注:这时候,这个神经网络还干不了什么有意义的事情,只是一个随机的神经网络。
    • 注:这些随机值将在后续的训练过程中得到逐步调整。

第2步:准备训练数据

  • 准备 1000 张(只是举个例子)手写数字图片,并且我们知道这些图片对应的数字是多少(即:每一张图片都被标记了正确的答案)。

第3步:训练神经网络

  1. 拿出一张图片(假设:是一张手写数字 7 的图片)。
  2. 把这实际是 7 的手写数字图片,转换为神经网络的输入信号(II)输入神经网络,经过神经网络的正向传播,得到神经网络的输出信号(OkO_k)(假设:输出信号此时代表数字 3)。
  3. 根据神经网络的实际输出(3)和期望输出(7),计算出神经网络输出层(kk)神经元的误差 EkE_k
  4. 将输出层误差(EkE_k)反向传播到隐藏层,得到隐藏层(jj)神经元的误差 EjE_j
  5. 采用梯度下降法,使用隐藏层输出(OjO_j)、输出层输出(OkO_k)和输出层误差(EkE_k),对隐藏层、输出层间的权重(Whidden_outputW_{hidden\_output})进行一次微调(ΔWj,k=αEkOk(1Ok)OjT\Delta W_{j,k} = \alpha * E_k * O_k * (1 - O_k) \cdot O_j^T)。
  6. 采用梯度下降法,使用输入(OiO_i)、隐藏层输出(OjO_j)和隐藏层误差(EjE_j),对输入层、隐藏层间的权重(Winput_hiddenW_{input\_hidden})进行一次微调(ΔWi,j=αEjOj(1Oj)OiT\Delta W_{i,j} = \alpha * E_j * O_j * (1 - O_j) \cdot O_i^T)。

至此,就用一张图片,完成了一轮对神经网络的训练。也可以说,神经网络从这张图片中提取到了部分知识,体现在神经网络中的权重里。

那么,我们用这 1000 张图片,对神经网络训练 1000 轮,那么我们有理由相信,神经网络从这 1000 张图片中提取到了足够的知识,来完成手写数字图片的识别任务。😄

实战

下面使用 Python 把上述过程实现出来。

NeuralNetwork.py:神经网络

import scipy.special

class NeuralNetwork:
    def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate) -> None:
        self.input_nodes = input_nodes
        self.hidden_nodes = hidden_nodes
        self.output_nodes = output_nodes

        self.weight_input_hidden = np.random.normal(0.0, pow(self.hidden_nodes, -0.5), (self.hidden_nodes, self.input_nodes))
        self.weight_hidden_output = np.random.normal(0.0, pow(self.output_nodes, -0.5), (self.output_nodes, self.hidden_nodes))

        self.learning_rate = learning_rate

        self.activation_function = lambda x: scipy.special.expit(x)
        pass

    def train(self, input_list, target_list):
        inputs = np.array(input_list, ndmin=2).T
        targets = np.array(target_list, ndmin=2).T

        hidden_inputs = np.dot(self.weight_input_hidden, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)

        final_inputs = np.dot(self.weight_hidden_output, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)

        output_errors = targets - final_outputs
        hidden_errors = np.dot(self.weight_hidden_output.T, output_errors)

        self.weight_hidden_output += self.learning_rate * np.dot(output_errors * final_outputs * (1.0 - final_outputs), np.transpose(hidden_outputs))
        self.weight_input_hidden += self.learning_rate * np.dot(hidden_errors * hidden_outputs * (1.0 - hidden_outputs), np.transpose(inputs))

        pass

    def query(self, input_list):
        inputs = np.array(input_list, ndmin=2).T

        hidden_inputs = np.dot(self.weight_input_hidden, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)

        final_inputs = np.dot(self.weight_hidden_output, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)

        return final_outputs

main.py:神经网络训练与测试:

import numpy as np
import matplotlib.pyplot
import pylab
from NeuralNetwork import *

input_nodes = 784
hidden_nodes = 100
output_nodes = 10
learning_rate = 0.1

n = NeuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
training_data_file = open("mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()

epochs = 5
total_progress = 5 * len(training_data_list)
current_progress = 0
for e in range(epochs):
    for record in training_data_list:
        all_values = record.split(',')
        inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
        targets = np.zeros(output_nodes) + 0.01
        targets[int(all_values[0])] = 0.99
        n.train(inputs, targets)
        current_progress += 1
        print("\rtraining progress: {:3}%".format((current_progress / total_progress) * 100), end="")
        pass
    pass

test_data_file = open("mnist_test.csv", 'r')
test_data_list = test_data_file.readlines()
test_data_file.close()

scorecard = []
for record in test_data_list:
    all_values = record.split(',')
    correct_label = int(all_values[0])
    inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
    outputs = n.query(inputs)
    label = np.argmax(outputs)
    if (label == correct_label):
        scorecard.append(1)
    else:
        scorecard.append(0)
    pass

scorecard_array = np.asarray(scorecard)
print ("\nperformance = ", scorecard_array.sum() / scorecard_array.size)

image.png

参考

《Python神经网络编程》

MNIST in CSV:pjreddie.com/projects/mn…

THE MNIST DATABASE of handwritten digits:yann.lecun.com/exdb/mnist/

NN-SVG:alexlenail.me/NN-SVG/LeNe…

NeuroMorpho.Org:neuromorpho.org/

image.png