用python从零实现神经网络-第2期 实现神经网络

目录

一、前言

二、从感知机到神经网络

1. 神经网络的例子

2. 复习感知机

3. 激活函数

三、激活函数

1. sigmoid函数

2.阶跃函数的实现

3. 阶跃函数的图像

4. sigmoid函数实现

5. sigmoid函数与阶跃函数对比

6.非线性函数

7.ReLU函数

四、3层神经网络的实现

1.符号确认

 2.各层级按信号传递的实现

   3.代码实现小结

五、输出层的设计

1.恒等函数和softmax函数

2.softmax函数的注意事项

        3.softmax函数的特征

4. 输出层的神经元数量

六、手写数字的识别

1.MNIST数据集

2.神经网络的推理处理

3.批处理

七、总结


一、前言

       本文以及此系列文章都是《深度学习入门-基于Python的理论与实现》的学习笔记。非常推荐去看这本书!本文会实现非常简单的只能前向传播的神经网络。一共五期就可以实现最基础的神经网络了。

        不用担心基础不好,实现最基础的神经网络的只需要:

  • python代码基础。(放心,代码真的不难,不需要很了解python)
  • 高数基础,例如导数,梯度。(不熟悉也可以实现,但是最好去复习,了解原理不是死记硬背更好)
  • 大致了解神经网络。(介绍神经网络的帖子、视频太多了,这里不再介绍了。如果不了解只需要去理解个大概,理解神经网络是什么,能做什么就可以了,此系列会很详细地边讲原理边实现的)

        下面就来一起学习实现神经网络。欢迎大家提出任何的建议,指出文章的错误!

二、从感知机到神经网络

        上篇文章我们已经学习了感知机。关于非常复杂的情况,例如表示一个计算机,理论上感知机都可以做到。但是设定权重的工作,即确定合适的、能符合预期的输入与输出的权重,还是有人工进行的。回想上篇文章中的与门,与非门。

        神经网络的出现就是为了解决上述问题。神经网络的一个重要性质是它可以自动地从数据中学习到合适的参数。本文会先学习神经网络的结构与神经网络在识别时的处理。下篇文章我们就会学习怎么从数据中学到参数。

        神经网络和上一章的感知机有很多共同点。大体上,多层感知机与神经网络的结构是相同的。你可以认为神经网络是由感知机构成的。接下来会具体介绍两者差异,介绍神经网络的结构。

1. 神经网络的例子

        用图来表示神经网络的话,如图1所示。最左边叫输入层,最右边是输出层,中间的一列叫中间层。或者叫隐藏层。因为隐藏的神经元(感知机)外界看不见。在此系列中,输入层为第0层,中间层为第1层,输出层为第2层。

图1 神经网络的例子

        只看这张图的话,会发现非常像上篇文章提到的多层感知机。那么神经网络到底有什么不同呢?

2. 复习感知机

        在观察神经网络的信号传递方法之前,我们先复习一下感知机。先来思考图2中的网络结构。

图2 感知机

        图2中的感知机表示接受x1和x2两个输入信号,输出y。如果用公式来表达,如下式(1)。   

                                             y=\begin{cases}0\quad(b+w_{1}x_{1}+w_{2}x_{2}\le0)\\1\quad(b+w_{1}x_{1}+w_{2}x_{2}>0)\end{cases}           

                                                                        (1)

        b是叫做偏置的参数,用于控制神经元容易被激活的程度,类似y=kx+b的b。w1和w2是表示各个信号的权重参数,用于控制各个信号的重要性。

        在图2中,偏置b并没有被画出来。如果要明确地画出b,可以像图3一样。为了区别其他神经元可以将偏置的输入信号涂成灰色。

图3 画出偏置的感知机

        现在将式1改成另一写法 式2与式3。

                                                t=b+w_{1}x_{1}+w_{2}x_{2}

                                                                式2

                                                y=h(t)=\begin{cases}0\quad(t\le0)\\1\quad(t>0) \end{cases}

                                                                式3

        这样的写法其实就是将感知机划分为两步。第一步就是将输入信号与权重相乘(别忘了偏置),第二步就是将上步的结果传入一个函数(显然可以不再局限于与0进行比较得到0或1了,直接将上一步结果输出,或者与1比较输出0或1都可以),也就是下面要提到的激活函数。

3. 激活函数

        刚才的h(x)函数能将输入信号与权重相乘与偏置的总和转换为输出信号,这个函数一般称为激活函数。不再局限于与0进行比较了。

         回顾上文式2和式3,将感知机划分为了两步。那么可以画另一种形式的感知机图。像图4。(感知机的画法多种多样,一般来说感知机的内容比较多,没必要每次都画出全部的参数与详细过程,更重要的是结构图,也就是有几层,每层有几个神经元)

        

图4 明确激活函数的计算过程

        这张图就是非常标准的神经网络中的感知机了(有的图偏置不会画出来)。再重新回顾下。

        感知机的术语:输入信号,权重,偏置,激活函数,输出信号。

        感知机的流程:

                1.先将输入信号分别与权重相乘求和,加上偏置,得到了一个结果。

                2.将结果代入到激活函数,得到输出。

        对照着图1,神经网络的术语与感知机相同。假设有一个包含输入层,1层隐藏层,输出层的2层的神经网络(如果你愿意你也可以叫3层),输入层有5个神经元,隐藏层有3个神经元,输出层有2个神经元。可以尝试自己画出这个神经元的结构图。

        而这个2层的神经网络的运算流程如下。

                1.隐藏层:相当于多个感知机。从上到下依次按照感知机的流程计算就好。例如输入层有5个输入,隐藏层有一层,这一层有3个节点(神经元),那么就相当于有3个5输入1输出的感知机。

                2.输出层:隐藏层全部计算好了之后,还是从上到下依次按照感知机的流程计算(但其实这里的感知机使用的不是激活函数了,不过这里不重要,目前可以认为使用的激活函数)。

三、激活函数

     式3的激活函数以阈值为界,超过一个数例如0就会切换输出。这样的函数称为阶跃函数。实际上,如果将阶跃函数换成其他函数就进入神经网络的世界了。

1. sigmoid函数

        神经网络经常使用的一个激活函数就是式4表示的sigmoid函数。

                                                        h(x)=\dfrac{1}{1+exp(-x)}

                                                                        式4

        式4表示的exp(-x)表示e^{_{-x}}的意思。下面通过阶跃函数对比学习激活函数。

        sigmoid函数看起来有点让人捉摸不透,但它仅仅是个函数,给定输入就会输出。

2.阶跃函数的实现

        下面用python来实现阶跃函数。

import numpy as np


def step_function(x):
    y = x > 0
    return y.astype(int)
#   return y.astype(np.int64)
#   return y.astype(np.int) 此写法被弃用

print(step_function(np.array([1, 2, -1, -2])))
#输出1,1,0,0

3. 阶跃函数的图像

        代码实现如下。结果如图5所示。

import matplotlib.pyplot as plt
X=np.arange(-5,5,0.1)
y=step_function(X)
plt.plot(X,y)
#plt.savefig('阶跃函数图')
plt.show()
图5 阶跃函数图像

        阶跃函数以0为界,输出从0切换到1(或者从1切换到0),它的值呈阶梯式变化,所以称为阶跃函数。

4. sigmoid函数实现

         下面是sigmoid函数实现。

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

        同样画出图像。

X=np.arange(-5,5,0.1)
y=sigmoid(X)
plt.plot(X,y)
#plt.savefig('sigmoid函数图像')
plt.show()

        

图6 sigmoid函数图像

              

5. sigmoid函数与阶跃函数对比

        将两个函数放到同一张图。

图7 阶跃函数与sigmoid函数

        我们先来找一下两者的相同点。

  • 从宏观角度来看,两者结构是相似的。输入小的时候输出接近0(为0),输入大的时候输出接近1(为1)。也就是说输入越重要,输出越接近1,越不重要,越接近0。
  • 值域是相同的。输出都在0-1之间。

        这里重点说说不同。再结合下个小标题非线性函数来思考思考多层感知机与神经网络的区别。

  • 首先平滑性不同。sigmoid函数是一条平滑的曲线,输出随着输入的改变发生连续性变化。而阶跃函数会发生急剧性的变化。 sigmoid函数的平滑对神经网络的学习时非常重要的。
  • 另一个不同是,sigmoid函数会输出0-1之间的数字值,而阶跃函数只有0和1。显然0和1这两种数字能带给后层的神经元信息太少了。

6.非线性函数

        阶跃函数和sigmoid函数还有一个共同点就是都是非线性函数。

        神经网络的激活函数必须使用非线性函数。如果使用线性函数加深神经网络层数就没有意义了。为什么?

        我们假设激活函数为h(x)=cx,把y(x)=h(h(h(x)))看成3层神经网络(为了计算简单),那么y=c*c*c*x,显然能找到a=c^{_{3}},所以3层神经网络就没意义了。1层就可以替代了。所以线性函数的问题在于无论怎样加深层数,总是存在与之等效的“无隐藏层的神经网络”了。

        所以为了发挥多层的优势,必须使用非线性函数。

7.ReLU函数

        在神经网络的发展史上,sigmoid函数早就被使用了。最近主要使用ReLU函数。

        ReLU函数在输入大于0时输出该值,在输入小于0时,输出0。公式如下。

                                                h(x)=\begin{cases}x\quad(x>0)\\0\quad(x\le0) \end{cases}

                                                                式5

        ReLU的实现如同这个函数一样简单:

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

        图像如下:

图8 ReLU函数

        注意:本文剩余内容仍将使用sigmoid函数为激活函数,但之后主要使用ReLU函数。

  • 每个激活函数都有自己的优缺点,但是阶跃函数不连续,导数为0或者不存在,无法进行梯度下降优化,sigmoid函数容易出现梯度消失等等,所以神经网络不会使用阶跃函数作为激活函数,sigmoid函数又被逐渐淘汰,之后会再详细介绍这一点的。

四、3层神经网络的实现

        现在我们来进行神经网络的实现。以图9的3层神经网络为例。一共有三层。输入层(第0层)有2个神经元,第一个隐藏层(第1层)有3个神经元,第二个隐藏层(第2层)有2个神经元,输出层(第3层)有2个神经元。

图9 3层神经网络

1.符号确认

        首先正式地介绍下权重的符号。w_{12}^{_{(1)}} :(1)代表第1层的权重,w_{12}代表前一层第2个神经元到后一层地1个神经元的权重。如图。不过记不住也没关系,理解含义就好。因为神经网络中权重是以矩阵的形式存在,都是整体进行运算的。

        

图10 权重的符号

        

 2.各层级按信号传递的实现

        现在看一下从输入层到第1层的第1个神经元的信号传递过程。如图11。

图11 从输入层到第1层的信号传递

        现在用数学式表示a_{1}^{(1)}

                                                        a_{1}^{(1)}=w_{11}^{(1)}x_{1}+w_{12}^{(1)}x_{2}+b_{1}^{(1)}

                                                                        式6

        可以尝试自己写一下a_{2}^{(1)}的表达式。

        此时,如果用矩阵的运算。那么输入层到第1层的加权和就可以表示成下面的式子。

                                                               A^{(1)}=XW^{(1)}+B^{(1)}

                                                                        式7

        其中,A^{(1)}、X、B^{(1)}W^{(1)}如下所示。

        A^{(1)}=\left(a_{1}^{(1)}\quad a_{2}^{(1)}\quad a_{3}^{(1)}\right)X=\left(x_{1}\quad x_{2} \right )B^{(1)}=\left(b_{1}^{(1)}\quad b_{2}^{(1)}\quad b_{3}^{(1)}\right)

        W^{(1)}=\left( \begin{array}{ccc} w_{11}^{(1)} & w_{21}^{(1)} & w_{31}^{(1)} \\\\ w_{12}^{(1)} & w_{22}^{(1)} & w_{32}^{(1)} \end{array} \right)

        先来看看这几个矩阵什么意思。对于初学者,我强烈建议时刻关注矩阵的形状与意义。

        A^{(1)}矩阵:(1)表示是隐藏层第一层的神经元的结果。同时是个行向量。假如隐藏层第一层有5个神经元,那么A^{(1)}应该的形状应该是1x5。

        X矩阵:表示输入层的输入。同时也是行向量。假如输入层有3个输入,那么X的形状应该是1x3。

        B^{(1)}矩阵:表示隐藏层第一层每个神经元对应的偏置。形状应该和A^{(1)}一样的。

        W^{(1)}矩阵:表示神经网络第一层的权重。也就是输入层到隐藏层。还记得w_{12}^{_{(1)}}的含义吗?表示第一层的从前一层的第二个神经元的后一层的第一个神经元之间的权重。在这里就是输入层第二个神经元到隐藏层第一个神经元的权重。所以这个矩阵的第一列的含义就是,输入层所有的神经元到隐藏层第一层的第一个神经元的权重。显然隐藏层有几个神经元,这个矩阵就有多少列,输入层有多少个神经元就有多少行。

        一定要搞清楚这几个矩阵的含义,以及形状。如果你愿意,将X和W的相乘顺序交换也可以,但是两者的形状一定要转置。所以其实你不必在意权重矩阵的形状是2x3还是3x2,你只要知道如果你写的是XW,那么因为X是1x2的行向量,为了能相乘,W的列必须是2。查阅资料发现X和W的相乘顺序其实影响不大,不过还是按照书里的顺序吧。

        下面用Python来实现式7。

        假设输入层的输入为1.0,0.5,输入层第一个神经元的权重为0.1,0.3,0.5,第二个神经元的权重为0.2,0.4,0.6,偏置分别为0.1,0.2,0.3,可以先试着自己实现一下,注意矩阵的形状以及矩阵每个元素的意义。

X=np.array([1.0,0.5])
W1=np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
B1=np.array([0.1,0.2,0.3])
print(X.shape)  #(2,)
print(W1.shape) #(2,3)
print(B1.shape) #(3,)
A1=np.dot(X,W1)+B1
print(A1)

        不要忘了上面提到每层完整的运算是两步。目前我们已经实现了第一步:输入层到隐藏层第一层的权重与输入乘积和。第二步就是加上激活函数。如图12。

图12 从输入层到第1层的信号传递

        

X=np.array([1.0,0.5])
W1=np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
B1=np.array([0.1,0.2,0.3])
print(X.shape)  #(2,)
print(W1.shape) #(2,3)
print(B1.shape) #(3,)
A1=np.dot(X,W1)+B1
print(A1)   #[0.3 0.7 1.1]
Z1=sigmoid(A1)
print(Z1)   #[0.57444252 0.66818777 0.75026011]

          下面来实现第1层到第2层的信号传递。如图13。

图13 第1层到第2层的信号传递

        可以思考如何实现,其实和刚才第0层到第1层的信号传递没有大的区别。(接着上边的代码)

W2=np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]])
B2=np.array([0.1,0.2])
print(Z1.shape)    #(3,)
print(W2.shape)    #(3,2)
print(B2.shape)    #(2,)
A2=np.dot(Z1,W2)+B2
print(A2)
Z2=sigmoid(A2)
print(Z2)

        可以发现,除了输入变成了上层的Z1以外,只是改动了参数的值,其他都一样。

        最后来实现第2层到最后一层的传递。不过最后的激活函数有点不同。如图14。

图14 第2层到输出层的信号传递

        

def identity_function(x):
    return x


W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3)
print(Y)

      identity_function()函数(或者叫恒等函数),作为输出层的激活函数。另外,如图14,输出层的激活函数用\sigma()来表示。

  • 为什么最后要更换激活函数?输出层使用的函数要根据实际问题决定。如果是回归问题可以使用恒等函数,二元分类问题可以使用sigmoid()函数,多元分类可以使用softmax()函数。下一节会详细介绍。        

   3.代码实现小结

     至此,我们已经实现了3层神经网络的实现。现在把所有的代码整理一下,并且按照习惯,除了权重用大写字母W表示,其他一律小写。

        Function文件是存放之前所有写的函数,例如sigmoid()等等。

from Function import *
import numpy as np


def init_network():
    network = {}
    #思考每个矩阵的形状
    network['W1']=np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
    network['b1']=np.array([0.1,0.2,0.3])
    network['W2']=np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]])
    network['b2']=np.array([0.1,0.2])
    network['W3']=np.array([[0.1,0.3],[0.2,0.4]])
    network['b3']=np.array([0.1,0.2])
    return network

def forward(network,x):
    W1,W2,W3=network['W1'],network['W2'],network['W3']
    b1,b2,b3=network['b1'],network['b2'],network['b3']

    #还记得吗,每层计算就是两步:加权和,激活函数。
    a1=np.dot(x,W1)+b1
    z1=sigmoid(a1)

    a2=np.dot(z1,W2)+b2
    z2=sigmoid(a2)

    a3=np.dot(z2,W3)+b3
    y=identity_function(a3)

    return y

network=init_network()
x=np.array([0.1,0.5])
y=forward(network,x)
print(y)   #[0.31234736 0.6863161 ] 可能会有点偏差

        init_network()函数很明显,就是初始化网络。forword(前向)表示输入到输出方向的传递处理,也就是前向传播。之后会出现backward(后向,从输出到输入)的处理。

        至此,神经网络的前向传播已经实现了。

五、输出层的设计

1.恒等函数和softmax函数

        恒等函数会将输入的信息按原样输出。在回归问题上,一般使用恒等函数。(或者这样想,sigmoid()函数输出范围是0-1,一会介绍的softmax函数输出和是1,sigmoid()函数和softmax函数的输出范围不如恒等函数自由)用图来表示的话如图15。

图15 恒等函数

        分类问题中使用的softmax函数可以使用下面的式8来表示。

                                                        y_k=\frac{exp(a_{k})}{\sum_{i=1}^{n}exp(a_{i})}

                                                                        式8

        式8表示假设输出层有n个神经元,计算第k个神经元的输出y_{k}。可以很明显的发现,使用softmax函数得到的所有输出和为1。用图表示就是图16。

图17 softmax函数

        现在来实现softmax函数。

def softmax(a):
    exp_a=np.exp(a)
    print(exp_a)   
    y=exp_a/np.sum(exp_a)
    print(y)       
    return y


a=np.array([0.3,2.9,4.0])
print(softmax(a))
'''
[ 1.34985881 18.17414537 54.59815003]
[0.01821127 0.24519181 0.73659691]
[0.01821127 0.24519181 0.73659691]
'''

2.softmax函数的注意事项

        在计算机实际运行softmax函数时,会出现溢出问题。因为要进行指数运算,所以指数函数的值会变得很大。例如e^{1000}就会出现inf无法计算的情况。所以可以按照下列公式修改。

                                \begin{aligned}y_{k}=\frac{\exp(a_{k})}{\sum_{i=1}^{n}\exp(a_{k})}=&\frac{C\exp(a_{k})}{C\sum_{i=1}^{n}\exp(a_{i})}\\=&\frac{\exp(a_{k})+\log{C}}{\sum_{i=1}^{n}\exp(a_{i}+\log{C})}\\=&\frac{\exp(a_{k}+C^{'})}{\sum_{i=1}^{n}\exp(a_{i}+C^{'})}\end{aligned}

                                                                        式9

        C^{'}表示将\log C换成一个常数。这个式子说明在进行softmax函数运算时,加上或者减去某个常数不会改变运算结果。这里C^{'}可以使用任何值。不过为了防止溢出,使用输入信号的最大值。来看一个具体例子。

>>> a=np.array([1010,1000,990])
>>> np.exp(a)
<stdin>:1: RuntimeWarning: overflow encountered in exp
array([inf, inf, inf])
>>> c=np.max(a)
>>> a-c
array([  0, -10, -20])
>>> np.exp(a-c)
array([1.00000000e+00, 4.53999298e-05, 2.06115362e-09])

        通过减去最大值在进行计算就可以避免这样的问题。那么将之前的softmax函数改成如下实现。    

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)
    sum_exp_a = np.sum(exp_a)
    return exp_a / sum_exp_a

        3.softmax函数的特征

        下面用softmax实现一个简单的例子。想想为什么softmax函数被用作多分类。

a = np.array([0.3, 2.9, 4.0])
print(softmax(a))  #[0.01821127 0.24519181 0.73659691]
print(np.sum(softmax(a)))   #1.0

        可以发现输出是0到1之前的实数,并且总和是1。(这点从公式就可以看出) 而就是因为softmax函数输出总和为1,我们才可以将softmax函数解释为“概率”。所以用作多分类。

        比如上述例子可以解释为y[0]概率是0.018(1.8%)等等,从概率结果角度来看,y[2]的概率最高,所以分类的结果是第2个类别。或者回答有74%概率是第2个类别,25%概率是第一个类别等等。也就是说,softmax可以处理概率问题。

        回顾softmax的计算公式,式8或者式9,分母相等,所以\exp(a_{k})越大结果越大,又知exp()为单调递增函数,所以softmax函数并没有改变大小关系。所以其实不使用softmax函数,只根据输入信号的大小,也可以排序得到想要的结果。

  • 求解机器学习问题的步骤可以分为“学习”(训练)和“推理”两个阶段。首先,通过数据在学习阶段调整参数,进行模型的学习,然后在推理阶段用学习的参数进行数据的分类(推理)。所以如前所述,输出层可以省略softmax函数。也可以减少计算机的运算量。不过在输出层使用softmax函数是因为它的神经网络的学习有关系(之后会介绍)。

4. 输出层的神经元数量

        对于分类问题,输出层的神经元数量一般根据待类别数量来决定。比如,对于某个输入图像,预测是数字0-9中哪个数字,输出层就可以设置10个神经元。10个输出哪个更高显然就表示哪个概率更高。

六、手写数字的识别

        现在来试着解决实际问题。这里我们来进行手写数字图片的分类。那么假设学习已经结束,也就是说我们已经有了完美的参数,下面来实现神经网络的推理过程。也就是前向传播

1.MNIST数据集

        MNIST手写数字图像数据集是机器学习领域有名的数据集之一。MNIST数据集是由0-9的数字图像构成的。首先创建一个mnist.py文件。然后将下面的代码直接复制过去(如果有书,可以从附带的资源下载)。记得联网。

# coding: utf-8
try:
    import urllib.request
except ImportError:
    raise ImportError('You should use Python 3.x')
import os.path
import gzip
import pickle
import os
import numpy as np


url_base = 'http://yann.lecun.com/exdb/mnist/'
key_file = {
    'train_img':'train-images-idx3-ubyte.gz',
    'train_label':'train-labels-idx1-ubyte.gz',
    'test_img':'t10k-images-idx3-ubyte.gz',
    'test_label':'t10k-labels-idx1-ubyte.gz'
}

dataset_dir = os.path.dirname(os.path.abspath(__file__))
save_file = dataset_dir + "/mnist.pkl"

train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784


def _download(file_name):
    file_path = dataset_dir + "/" + file_name

    if os.path.exists(file_path):
        return

    print("Downloading " + file_name + " ... ")
    urllib.request.urlretrieve(url_base + file_name, file_path)
    print("Done")

def download_mnist():
    for v in key_file.values():
        _download(v)

def _load_label(file_name):
    file_path = dataset_dir + "/" + file_name

    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
        labels = np.frombuffer(f.read(), np.uint8, offset=8)
    print("Done")

    return labels

def _load_img(file_name):
    file_path = dataset_dir + "/" + file_name

    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
        data = np.frombuffer(f.read(), np.uint8, offset=16)
    data = data.reshape(-1, img_size)
    print("Done")

    return data

def _convert_numpy():
    dataset = {}
    dataset['train_img'] =  _load_img(key_file['train_img'])
    dataset['train_label'] = _load_label(key_file['train_label'])
    dataset['test_img'] = _load_img(key_file['test_img'])
    dataset['test_label'] = _load_label(key_file['test_label'])

    return dataset

def init_mnist():
    download_mnist()
    dataset = _convert_numpy()
    print("Creating pickle file ...")
    with open(save_file, 'wb') as f:
        pickle.dump(dataset, f, -1)
    print("Done!")

def _change_one_hot_label(X):
    T = np.zeros((X.size, 10))
    for idx, row in enumerate(T):
        row[X[idx]] = 1

    return T


def load_mnist(normalize=True, flatten=True, one_hot_label=False):
    """读入MNIST数据集
    
    Parameters
    ----------
    normalize : 将图像的像素值正规化为0.0~1.0
    one_hot_label : 
        one_hot_label为True的情况下,标签作为one-hot数组返回
        one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组
    flatten : 是否将图像展开为一维数组
    
    Returns
    -------
    (训练图像, 训练标签), (测试图像, 测试标签)
    """
    if not os.path.exists(save_file):
        init_mnist()

    with open(save_file, 'rb') as f:
        dataset = pickle.load(f)

    if normalize:
        for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].astype(np.float32)
            dataset[key] /= 255.0

    if one_hot_label:
        dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_one_hot_label(dataset['test_label'])

    if not flatten:
        for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

    return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label'])


if __name__ == '__main__':
    init_mnist()

        之后再创建个文件,然后导入数据集。

from mnist import load_mnist

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
print(x_train.shape)  # (60000, 784)
print(t_train.shape)  # (60000,)
print(x_test.shape)  # (10000, 784)
print(t_test.shape)  # (10000,)

        load_mnist函数以(训练图像(x),训练标签(可以理解为y)),(测试图像,测试标签)的形式返回。此外,load_mnist函数具有三个参数。如果一些词看不懂没关系,之后卷积神经网络时会详细介绍。

  1. normalize=True  表示将图像正规化为0.0-1.0的值,如果不,那么会保存原来的像素0-255。
  2. flatten=True 表示展开图像为784个元素的一维数组,否则是1x28x28的三维数组。
  3. one_hot_label=True 表示将图片设置为one_hot。也就是是否仅将正确标签表示为1,其余皆为0的数组。如[0,0,1,0,0,0,0,0,0,0]这样。否则只是像7,2这样简单保存标签。

       下面来试着显示MNIST图像,同时也确认一下数据。

from mnist import load_mnist

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

import numpy as np
from PIL import Image


def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()

img=x_train[0]
label=t_train[0]
print(label)

print(img.shape)
img=img.reshape(28,28)
print(img.shape)
img_show(img)
图18 显示MNIST图像

2.神经网络的推理处理

        下面来实现神经网络的推理处理。神经网络输入层有784个神经元,输出层有10个神经元。输入层的784来源于图像大小28*28=784。输出层的10来源于数字的10个类别(数字0-9)。此外,神经网络有2个隐藏层,第1个隐藏层有50个神经元,第2个隐藏层有100个神经元。下面先定义get_data()、init_network()、predict()这3个函数。   

        init_work()会读取保存在pickle文件sample_weight.pkl中的学习到的权重参数。现在用这3个函数来实现神经网络的推理处理。然后评价它的识别精度,也就是能在多大程度上识别分类。

        最后会输出识别精度。另外,这里把load_mnist函数的参数normalize设置成了True,将图像的每个像素值除以255,使得数据在0.0~1.0的范围内。像这样把数据限定到某个范围的处理称为正规化。

import pickle
from Function import *
import numpy as np

from mnist import load_mnist


def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=True)
    # 这次使用的参数已经准备好了,所以就不需要训练集了
    return x_test, t_test


def init_network():
    with open("sample_weight.pkl", 'rb') as f:
        network = pickle.load(f)
    return network


def predict(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)

    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)

    a3 = np.dot(z2, W3) + b3
    y = softmax(a3)

    return y


X,T=get_data()
accuracy_cnt=0
network=init_network()
for i in range(0,len(X)):
    result=predict(network,X[i])
    p=np.argmax(result) #输出层有10个神经元,获取概率最高的
    if p==T[i]:
        accuracy_cnt+=1

print("Accuracy",float(accuracy_cnt)/len(X))#0.9352

3.批处理

        回顾上面计算识别精度的过程,是一张一张图片输入输出的。其实可以通过一次性输入多张图片,得到多输出再计算。这种方式就叫批处理。批处理的好处主要有以下两点:

  1. 大多数处理数值计算的库都进行了能够高效处理大型数组运算的最优化。
  2. 节省了总体的读取数据时间,可以更多时间用来在计算上。

        用代码实现的话如下,只是有一些改动。

X,T=get_data()
accuracy_cnt=0
bacth_size=100
network=init_network()
for i in range(0,len(X),bacth_size):
    result_batch=predict(network,X[i:i+bacth_size])
    p=np.argmax(result_batch,axis=1) #输出层有10个神经元,获取概率最高的
    accuracy_cnt+=np.sum(p==T[i:i+bacth_size])

print("Accuracy",float(accuracy_cnt)/len(X)) #0.9352 

七、总结

        到这里就实现了神经网络的前向传播,下一章会介绍神经网络如何学习参数的。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值