python深度学习入门-神经网络

深度学习入门-神经网络

博主微信公众号(左)、Python+智能大数据+AI学习交流群(右):欢迎关注和加群,大家一起学习交流,共同进步!

目录

摘要

1、从感知机到神经网络

1.1 神经网络的例子

1.2 复习感知机

1.3 激活函数登场

2、激活函数

2.1 sigmoid 函数(sigmoid function)

2.2 阶跃函数的实现

2.3 阶跃函数的图形

2.4 sigmoid 函数的实现

2.5 sigmoid 函数的图形

2.6 sigmoid 函数和阶跃函数的比较

2.7 非线性函数

2.8 ReLU 函数

2.9 ReLU 函数图形

3、多维数组的运算

3.1 多维数组

3.2 矩阵乘法

3.3 神经网络的内积

4、3 层神经网络的实现

4.1 符号确认

4.2 各层间信号传递的实现

4.3 代码实现

5、输出层的设计

5.1 恒等函数和 softmax 函数

5.2 实现 softmax 函数时的注意事项

5.3 softmax 函数的特征

5.4 输出层的神经元数量

6、手写数字识别

6.1 MNIST 数据集

6.2 神经网络的推理

6.3 批处理


摘要

  • 神经网络中的激活函数使用平滑变化的 sigmoid 函数或 ReLU 函数。
  • 通过巧妙地使用 NumPy 多维数组,可以高效地实现神经网络。
  • 机器学习的问题大体上可以分为回归问题和分类问题。
  • 关于输出层的激活函数,回归问题中一般用恒等函数,分类问题中一般用 softmax 函数。
  • 分类问题中,输出层的神经元的数量设置为要分类的类别数。
  • 输入数据的集合称为批。通过以批为单位进行推理处理,能够实现高速的运算。

1、从感知机到神经网络

1.1 神经网络的例子

    用图来表示神经网络的话,如图 2-1 所示。

    我们把最左边的一列称为输入层,最右边的一列称为输出层,中间的一列称为中间层

    中间层有时也称为隐藏层。“隐藏” 一词的意思是,隐藏层的神经元(和输入层、输出层不同)肉眼不可见。

图 2-1    神经网络的例子

1.2 复习感知机

图 2-2    感知机

    图 2-2 中的感知机接收 x_{1} 和 x_{2} 两个输入信号,输出 y。如果用数学公式来表示图 2-2 的感知机,则如式 (2.1) 所示。

        y=\left\{\begin{matrix} 0 & (b + w_{1}x_{1} + w_{2}x_{2} \leqslant 0 ) \\ 1 & (b+ w_{1}x_{1} + w_{2}x_{2} > 0 ) \end{matrix}\right. \ \ \ \ \ \ \ \ (2.1)

         b:偏置的参数,用于控制神经元被激活的容易程度;

        w_{1}w_{2}:各个信号的权重的参数,用于控制各个信号的重要性。

    明确表示出偏置 b 的感知机,如图 2.3 所示。

图 2-3    明确表示出偏置

        图 2-3 中添加了权重 b 的输入信号 1。

        这个感知机将  x_{1} 、x_{2}1 三个信号作为神经元的输入,将其和各自的权重相乘后,传送至下一个神经元。

        在下一个神经元中,计算这些加权信号的总和。如果这个总和超过 0,则输出 1,否则输出 0。

    引入新函数 h(x),将式 (2.1) 改写成更加简洁的形式:式 (2.2) 和 式 (2.3)

         y=h(b+w_{1}x_{1}+w_{2}x_{2}) \ \ \ \ \ \ \ \ (2.2)

        h(x)=\left\{\begin{matrix} 0 & (x\leq 0)\\ 1 & (x>0) \end{matrix}\right. \ \ \ \ \ \ \ \ (2.3)

        式 (2.2) 中,输入信号的总和会被函数 h(x) 转换,转换后的值就是输出 y。然后,式 (3.3) 所示的函数 h(x),在输入超过 0 时返回 1,否则返回 0。因此,式 (2.1) 和式 (2.2)、式 (2.3) 做的是相同的事。

1.3 激活函数登场

    上面提到的 h(x) 函数会将输入信号的总和转换为输出信号,这种函数一般称为激活函数(activation function)。如 “激活” 一词所示,激活函数的作用在于决定如何来激活输入信号的总和。

    将式 (2.2) 分两个阶段进行处理:先计算输入信号的加权总和,然后用激活函数转换这一总和。

        a=b+w_{1}x_{1} + w_{2}x_{2} \ \ \ \ \ \ \ \ (2.4)

         y=h(a) \ \ \ \ \ \ \ \ (2.5)

        (1)式 (2.4) 计算输入信号和偏置的加权总和,记为 a;

        (2)式 (2.5) 用 h() 函数将 a 转换为输出 y。

    明确显示激活函数的计算过程:

图 2-4    明确显示激活函数的计算过程

        (1)信号的加权总和为节点 a;

        (2)节点 a 被激活函数 h() 转换成输出节点 y。

2、激活函数

2.1 sigmoid 函数(sigmoid function)

    sigmoid 函数(sigmoid function)数学公式:  exp(-x) :e^{-x} 的意思,e 是纳皮尔常数 2.7182...。

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

    函数:给定某个输入后,会返回某个输出的转换器。比如,向 sigmoid 函数输入 1.0 或 2.0 后,就会有某个值被输出,类似 h(1.0)=0.731...、h(2.0)=0.880...这样。

    神经网络中用 sigmoid 函数作为激活函数,进行信号的转换,转换后的信号被传送给下一个神经元。

2.2 阶跃函数的实现

    阶跃函数:当输入超过 0 时,输出 1,否则输出 0。

    python 实现:

        (1)浮点数入参:

import numpy as np


def step_function(x):
    """
    阶跃函数
    :param x: 入参,只能接受实数(浮点数)
    :return: 
    """
    if x > 0:
        return 1
    else:
        return 0


if __name__ == "__main__":
    # 正确传参调用
    step_function(3.0)
    # 错误传参调用
    step_function(np.array([1.0, 2.0]))
    

        (2)Numpy 数组入参:    

import numpy as np


def step_function(x):
    """
    阶跃函数
    :param x: 入参,array([-1.0, 1.0, 2.0])
    :return:
    """
    y = x > 0   # array([False, True, True])
    return y.astype(np.int)     # array([0, 1, 1])


if __name__ == "__main__":
    x = np.array([-1.0, 1.0, 2.0])
    step_function(x)

2.3 阶跃函数的图形

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

import numpy as np
from matplotlib import pyplot as plt


def step_function(x):
    """
    阶跃函数
    :param x: 入参
    :return:
    """
    return np.array(x > 0, dtype=np.int)


if __name__ == "__main__":
    # 在-5.0到5.0的范围内,以0.1位单位,生成Numpy数组array([-5.0, -4.9, ... , 4.8, 4.9])
    x = np.arange(-5.0, 5.0, 0.1)
    y = step_function(x)
    plt.plot(x, y)
    plt.ylim(-0.1, 1.1)     # 指定y轴的范围
    plt.show()
图 2-5    阶跃函数的图形

2.4 sigmoid 函数的实现

"""
sigmoid 函数:1 / (1 - exp(-x))
"""
import numpy as np


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


if __name__ == "__main__":
    x = np.array([-1.0, 1.0, 2.0])
    y = sigmoid(x)
    print(y)
[0.26894142 0.73105858 0.88079708]

2.5 sigmoid 函数的图形

"""
sigmoid 函数:1 / (1 - exp(-x))
"""
import numpy as np
from matplotlib import pyplot as plt


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


if __name__ == "__main__":
    # 在-5.0到5.0的范围内,以0.1位单位,生成Numpy数组array([-5.0, -4.9, ... , 4.8, 4.9])
    x = np.arange(-5.0, 5.0, 0.1)
    y = sigmoid(x)
    plt.plot(x, y)
    plt.ylim(-0.1, 1.1)  # 指定y轴的范围
    plt.show()
图 2-6    sigmoid 函数的图形

2.6 sigmoid 函数和阶跃函数的比较

"""
sigmoid 函数与阶跃函数图形
"""
import numpy as np
from matplotlib import pyplot as plt


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


def step_function(x):
    """
    阶跃函数
    :param x: 入参
    :return:
    """
    return np.array(x > 0, dtype=np.int)


if __name__ == "__main__":
    # 在-5.0到5.0的范围内,以0.1位单位,生成Numpy数组array([-5.0, -4.9, ... , 4.8, 4.9])
    x = np.arange(-5.0, 5.0, 0.1)
    y1 = sigmoid(x)
    y2 = step_function(x)
    plt.plot(x, y1, label='sigmoid function')
    plt.plot(x, y2, label='step function')
    plt.ylim(-0.1, 1.1)  # 指定y轴的范围
    plt.legend(loc='best')
    plt.show()
图 2-7    阶跃函数与sigmoid函数

    不同点:

        (1)“平滑性” 的不同。sigmoid 函数是一条平滑的曲线,输出随着输入发生连续性的变化;阶跃函数以 0 为界,输出发生急剧性的变化。

        (2)返回值的不同。sigmoid 函数返回 0.731....、0.880... 等实数;阶跃函数只能返回 0 或 1。

         注:感知机中神经元之间流动的是 0 或 1 的二元信号;神经网络中流动的是连续的实数值信号。

    相同点:

        (1)sigmoid 函数 和阶跃函数具有相似的形状。两者的结构均是 “输入小时,输出接近0(为0);随着输入增大,输出向 1 靠近(变为1)”。

        (2)不管输入信号有多小,或者有多大,输出信号的值都在 0 到 1 之间。

        (3)sigmoid 函数和阶跃函数都是非线性函数。

        注:当输入信号为重要信息时,阶跃函数和sigmoid函数都会输出较大的值;当输入信号为不重要的信息时,两者都输出较小的值。

2.7 非线性函数

    函数:输入某个值后会返回一个值的转换器。

     线性函数:向函数中输入某个值后,输出值是输入值的常数倍的函数称为线性函数(用数学式表示为 h(x)=cx,c 为常数)。线性函数是一条笔直的直线。

    非线性函数:不像线性函数那样呈现出一条直线的函数。

    神经网络的激活函数必须使用非线性函数。因为使用线性函数的话,加深神经网络的层数就没有意义了。

    神经网络的问题在于,不管如何加深层数,总是存在与之等效的 “无隐藏层的神经网络”。下面举一个简单的例子加以说明:

        (1)考虑把线性函数 h(x)=cx 作为激活函数;

        (2)把 y(x)=h(h(h(x))) 的运算对应 3 层神经网络;

        (3)这个运算会进行 y(x)=c*c*c*x 的乘法运算;

        (4)但是,同样的处理可以由 y(x)=ax(a=c^{3}) 这个一次乘法运算(即没有隐藏层的神经元)来表示。

2.8 ReLU 函数

    ReLU(Rectified Linear Unit)函数:在输入值大于 0 时,直接输出该值;输入值小于 0 时,输出 0 。

    ReLU 函数可以表示为下面的式 (2.7)。

         h(x)\left\{\begin{matrix} x & (x>0)\\ 0 & (x\leq 0) \end{matrix}\right. \ \ \ \ \ \ \ \ (2.7)

2.9 ReLU 函数图形

"""
ReLU 函数
"""

import numpy as np
from matplotlib import pyplot as plt


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


if __name__ == "__main__":
    x = np.arange(-5.0, 5.0, 0.1)
    y = relu(x)
    plt.plot(x, y, label="relu")
    plt.ylim(-1, 5.0)
    plt.legend(loc='best')
    plt.show()
图 2-8    ReLU 函数

3、多维数组的运算

3.1 多维数组

    多维数组:“数字的集合”。数字排成一列的集合、排成长方形的集合、排成三维状或者(更加一般化的)N 维状的集合都成为多维数组。

     一维数组:

>>> import numpy as np
>>> A = np.array([1, 2, 3, 4, 5])
>>> print(A)
[1 2 3 4 5]
>>> np.ndim(A)
1
>>> A.shape
(5,)
>>> A.shape[0]
5

    二维数组:

>>> B = np.array([[1, 2], [3, 4], [5, 6]])
>>> print(B)
[[1 2]
 [3 4]
 [5 6]]
>>> np.ndim(B)
2
>>> B.shape
(3, 2)
>>> B.shape[1]
2

    np.ndim():获取数组维度。

    np.shape:获取数组形状。

3.2 矩阵乘法

    矩阵:二维数组也称为矩阵(matrix)。如图 2-9 所示,数组的横向排列称为(row),纵向排列称为(column)。

图2-9    矩阵

    矩阵乘积(点积)的计算方法:图 2-10 为 2 x 2 的矩阵。

图 2-10    矩阵乘积计算方法

    矩阵的乘积是通过左边矩阵的行(横向)和右边矩阵的列(纵向)以对应元素怒的方式相乘后再求和而得到的。并且,运算的结果保存为新的多维数组的元素。

    比如:

        A 的第 1 行和 B 的第 1 列的乘积结果保存为新数组的第 1 行第 1 列的元素;

        的第 2 行和 B 的第 1 列的乘积结果保存为新数组的第 2 行第 1 列的元素;

    矩阵乘法代码实现:

"""
矩阵乘法
"""

import numpy as np


def matrix_multiplication(x1, x2):
    return np.dot(x1, x2)


if __name__ == "__main__":
    A = np.array([[1, 2], [3, 4]])
    B = np.array([[5, 6], [7, 8]])
    product1 = matrix_multiplication(A, B)
    product2 = matrix_multiplication(B, A)
    print(f"A.shape: {A.shape}")
    print(f"B.shape: {B.shape}")
    print(f"A * B: \n{product1}")
    print(f"B * A: \n{product2}")
A.shape: (2, 2)
B.shape: (2, 2)
A * B: 
[[19 22]
 [43 50]]
B * A: 
[[23 34]
 [31 46]]

    注意:

        (1)np.dot(A, B) 和 np.dot(B, A) 的值可能不一样。和一般的运算(+ 或 * 等)不同,矩阵的乘积运算中,操作数(A、B)的顺序不同,结果也会不同。

        (2)矩阵 A 的第 1 维的元素个数(列数)必须和矩阵 B 的第 0 维的元素个数(行数)相等。 

图 2-11    矩阵乘积运算,对应维度的元素个数保持一致
图 2-12    矩阵乘积运算,对应维度的元素个数保持一致

3.3 神经网络的内积

图 2-13    通过矩阵的乘积进行神经网络的运算
"""
神经网络的点积
"""

import numpy as np


def matrix_multiplication(x1, x2):
    return np.dot(x1, x2)


if __name__ == "__main__":
    X = np.array([1, 2])
    W = np.array([[1, 3, 5], [2, 4, 6]])
    product = matrix_multiplication(X, W)
    print(f"X.shape: {X.shape}")
    print(f"W.shape: {W.shape}")
    print(f"X * W: \n{product}")
X.shape: (2,)
W.shape: (2, 3)
X * W: 
[ 5 11 17]

4、3 层神经网络的实现

图 2-13    3层神经网络

4.1 符号确认

图 2-14    权重的符号

4.2 各层间信号传递的实现

        (1)从输入层到第 1 层的信号传递:a — 隐藏层的加权求和;z — 被激活函数转换后的信号;h() — 激活函数。

图 2-15    从输入层到第1层的信号传递

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

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

    使用矩阵的乘法运算,可以将第 1 层的加权和表示成下面的式 (2.8)。

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

        Z1 = sigmoid(A1) \ \ \ \ \ \ \ \ (2.9)

    其中 A^{(1)}XW^{(1)}B^{(1)} 如下所示:

        A^{(1)}=\begin{pmatrix} a_{1}{(1)} & a_{2}{(1)} & a_{3}{(1)} \end{pmatrix}

        X=\begin{pmatrix} x_{1} & x_{2} \end{pmatrix}

        W^{(1)} = \begin{pmatrix} w_{11}^{(1)} & w_{21}^{(1)} & w_{31}^{(1)} \\ w_{12}^{(1)} & w_{22}^{(1)} & w_{32}^{(1)} \end{pmatrix}

        B^{(1)}=\begin{pmatrix} b_{1}^{(1)} & b_{2}^{(1)} & b_{3}^{(1)} \end{pmatrix}

    (2)从第 1 层到第 2 层的信号传递:a — 隐藏层的加权求和;z — 被激活函数转换后的信号;h() — 激活函数。

图 2-16    第1层到第2层的信号传递

    (3)从第 2 层到输出层的信号传递:a — 隐藏层的加权求和;y — 输出层信号;\sigma() — 激活函数。

图 2-17    从第2层到输出层的信号传递

4.3 代码实现

"""
神经网络各层间信号传递的实现
"""

import numpy as np


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


def identity_function(x):
    return x


def init_network():
    network = {}
    # 输入层到第1层的权重和偏置
    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])
    # 第1层到第2层的权重和偏置
    network["w2"] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
    network["b2"] = np.array([0.1, 0.2])
    # 第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"]

    # 从输入层到第1层的信号传递
    a1 = np.dot(x, w1) + b1
    z1 = sigmoid(a1)
    # 第1层到第2层的信号传递
    a2 = np.dot(z1, w2) + b2
    z2 = sigmoid(a2)
    # 第2层到输出层的信号传递
    a3 = np.dot(z2, w3) + b3
    y = identity_function(a3)

    return y


network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y)    # [0.31682708 0.69627909]

    (1)输出层激活函数的选择

        输出层激活函数的选择,要根据求解问题的性质决定,一般地,回归问题可以使用恒等函数,二分类问题可以使用 sigmoid 函数,多元分类问题可以使用softmax 函数。

    (2)前向传播

        前向:表示的是从输入到输出方向的传递处理。

5、输出层的设计

    神经网络可以用在分类问题和回归问题上,不过需要根据情况改变输出层的激活函数。

    一般而言,回归问题用恒等函数,分类问题用 softmax 函数。

    分类问题:数据属于哪一个类别的问题。比如,区分图像中的人是男性还是女性。

    回归问题:根据某个输入预测一个(连续的)数值的问题。比如,根据一个人的图像预测这个人的体重(类似 “57.4kg” 这样的预测)。

5.1 恒等函数和 softmax 函数

    (1)恒等函数

        恒等函数会将输入按原样输出,对于输入的信息,不加以任何改动地直接输出。因此,在输入层使用恒等函数时,输入信号会原封不动地被输出。

        将恒等函数的处理过程用神经网络的图来表示,如图 2-18 所示。

图 2-18    恒等函数

    (2)softmax 函数

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

        exp(x) :表示 e^{x} 的指数函数(e 是纳皮尔常数 2.7182...)。

        式 (2.10) 表示假设输出层共有 n 个神经元,计算第 k 个神经元的输出 y_{k}。softmax 函数的分子是输入信号 a_{k} 的指数函数,分母是所有输入信号的指数函数的和。

        用图表示 softmax 函数,如图 2-19 所示。

图 2-19    softmax 函数

        使用代码实现 softmax 函数:

"""
softmax 函数
"""

import numpy as np


def softmax(a):
    exp_a = np.exp(a)   # 指数函数
    sum_exp_a = np.sum(exp_a)   # 指数函数的和
    y = exp_a / sum_exp_a

    return y


if __name__ == "__main__":
    a = np.array([0.3, 2.9, 4.0])
    y = softmax(a)  # [0.01821127 0.24519181 0.73659691]
    print(y)

5.2 实现 softmax 函数时的注意事项

    上面 softmax 函数的实现,在计算机的运算上有一定的缺陷。这个缺陷就是溢出问题。softmax 函数的实现中要进行指数函数的运算,但是此时指数函数的值容易变得非常大。比如,e^{10} 的值会超过20000,e^{100} 会变成一个后面有 40 多个 0 的超大值,e^{1000} 的结果会返回一个表示无穷大的 inf。如果在这些超大值之间进行除法运算,结果会出现 “不确定” 的情况。

    溢出:计算机处理 “数” 时,数值必须在 4 字节或 8 字节的有限数据宽度内。

               这意味着数存在有效位数,也就是说,可以表示的数值范围是有限的。

               因此,会出现超大值无法表示的问题。这个问题称为溢出

    结果 “不确定” 的情况:   

>>> import numpy as np
>>> a = np.array([1010, 1000, 990])
>>> np.exp(a) / np.sum(np.exp(a))
array([nan, nan, nan])

    对 softmax 函数进行改进:在进行 softmax 的指数函数的运算时,加上(或者减去)某个常数并不会改变运算的结果。

        y_{k}=\frac{exp(a_{k})}{\sum_{i=1}^{n}exp(a_{i})} =\frac{Cexp(a_{k})}{C\sum_{i=1}^{n}exp(a_{i})}=\frac{exp(a_{k}+logC)}{\sum_{i=1}^{n}exp(a_{i}+logC)} =\frac{exp(a_{k}+{C}')}{\sum_{i=1}^{n}exp(a_{i}+{C}')} \ \ \ \ \ \ \ \ (2.11)

        a、在分子和分母上都乘上 C 这个任意的常数(因为同时对分母和分子乘以相同的常数,所以计算结果不变);

        b、把这个 C 移动到指数函数(exp)中,记为 logC;

        c、把 logC 替换为另一个符号 {C}'

        注:这里的 {C}' 可以使用任何值,但是为了防止溢出,一般会使用输入信号中的最大值。

    改进后的情况:

>>> import numpy as np
>>> a = np.array([1010, 1000, 990])
>>> np.exp(a) / np.sum(np.exp(a))
array([nan, nan, nan])
>>> c = np.max(a)
>>> a - c
array([  0, -10, -20])
>>> np.exp(a-c) / np.sum(np.exp(a - c))
array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09])
"""
softmax 函数
"""

import numpy as np


def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)   # 指数函数,溢出对策:-c
    sum_exp_a = np.sum(exp_a)   # 指数函数的和
    y = exp_a / sum_exp_a

    return y


if __name__ == "__main__":
    # a = np.array([0.3, 2.9, 4.0])
    a = np.array([1010, 1000, 999])
    y = softmax(a)  # [9.99937902e-01 4.53971105e-05 1.67006637e-05]
    print(y)

5.3 softmax 函数的特征

    使用 softmax 函数,计算神经网络的输出:softmax 函数的输出是 0.0 到 1.0 之间的实数。并且,softmax 函数的输出值的总和是 1(因为这个性质,我们才可以把 softmax 函数的输出解释为 “概率”)。

"""
softmax 函数
"""

import numpy as np


def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)   # 指数函数,溢出对策:-c
    sum_exp_a = np.sum(exp_a)   # 指数函数的和
    y = exp_a / sum_exp_a

    return y


if __name__ == "__main__":
    a = np.array([0.3, 2.9, 4.0])
    y = softmax(a)  # [0.01821127 0.24519181 0.73659691]
    print(np.sum(y))    # 1.0

    示例解释:因为第 2 个元素的概率最高,所以答案是第 2 个类别。

        a、y[0] 的概率是0.018(1.8%),y[1] 的概率是 0.245(24.5%),y[2] 的概率是 0.737(73.7%);

        b、有 73.7% 的概率是第 2 个类别,有 24.5% 的概率是第 1 个类别,有 1.8% 的概率是第 0 个类别。

    注意:即便使用了 softmax 函数,各个元素之间的大小关系也不会发生改变。这是因为指数函数(y=exp(x))是单调递增函数。比如 a 的最大值是第 2 个元素,y 的最大值也仍然是第 2 个元素。

5.4 输出层的神经元数量

    输出层的神经元数量需要根据待解决的问题来决定。对于分类问题,输出层的神经元数量一般设定为类别的数量。

    比如,对于某个输入图像,预测是图中的数字 0 到 9 中的哪一个的的问题(10 类别分类问题),可以像图 2-20 这样,将输出层的神经元设定为 10个。

    如图 2-10 所示,在这个例子中,输出层的神经元从上往下依次对应数字 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 。此外,图中输出层的神经元的值用不同的灰度表示。这个例子中的神经元 y_{2} 颜色最深,输出的值最大。这表明这个神经网络预测的是 y_{2} 对应的类别,也就是 “2”。

图 2-20    输出层的神经元对应各个数字

6、手写数字识别

    使用神经网络解决问题时,需要首先使用训练数据(学习数据)进行权重参数的学习;进行推理时,使用刚才学习到的参数,对输入数据进行分类。

6.1 MNIST 数据集

    提供 6w 张 28*28 像素点(像素的取值在 0 到 255 之间)的 0~9 手写数字图片和标签,用于训练。

    提供 1w 张 28*28 像素点(像素的取值在 0 到 255 之间)的 0~9 手写数字图片个标签,用于测试。

图 2-21    MNIST 图像数据集的例子

    (1)下载 MNIST 数据集,并且将这些数据转换成 NumPy 数组(dataset.mnist.py)

"""
手写数字识别
"""
import os
import sys
import gzip
import pickle
import urllib
import numpy as np


base_url = "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 progressbar(cur, total=100):
    percent = '{:.2%}'.format(cur / total)
    sys.stdout.write('\r')
    sys.stdout.write("[%-100s] %s" % ('=' * int(cur), percent))
    sys.stdout.flush()


def schedule(block_num, block_size, total_size):
    """
    下载百分比
    :param block_num: 当前已经下载的块
    :param block_size: 每次传输的块大小
    :param total_size: 每次传输的块大小
    :return:
    """
    if total_size == 0:
        percent = 0
    else:
        percent = block_num * block_size / total_size
    if percent > 1.0:
        percent = 1.0
    percent = percent * 100
    progressbar(percent)


def _download(file_name):
    file_path = dataset_dir + "/" + file_name
    if os.path.exists(file_path):
        return

    print("Downloading " + file_name + " ... ")
    urllib.request.urlretrieve(base_url + file_name, file_path, schedule)
    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(f"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(f"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数据集
    :param normalize: 将图像的像素值正规化为 0.0~1.0 的值
    :param flatten: 是否将图像展开为一维数组
    :param ont_hot_label: 为True的情况下,标签作为one-hot数组返回(one-hot数组:指[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]这样的数组)
    :return: (训练图像, 训练标签), (测试图像, 测试标签)
    """
    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()

    (2)显示 MNIST 图像

"""
显示MNIST图像
"""
import os
import sys
import numpy as np
from PIL import Image

sys.path.append(os.pardir)
from dataset.mnist import load_mnist


def img_show(img):
    # Image.fromarray() 将保存为 NumPy 数组的图像数据转换为 PIL 用的数据对象
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()

# (训练图像, 训练标签), (测试图像, 测试标签)
(x_train, y_train), (x_test, y_test) = load_mnist(normalize=False, flatten=True)
img = x_train[0]
label = y_train[0]
print(label)    # 5

print(img.shape)    # (784,)
img = img.reshape(28, 28)   # 把图像的形状变成原来的尺寸
print(img.shape)    # (28, 28)

img_show(img)
图 2-22    显示 MINIST 图像

6.2 神经网络的推理

    正规化(normalization):将数据限定到某个范围内的处理。

    预处理(pre-processing):对神经网络的输入数据进行某种既定的转换。

    数据白化(whitening):将数据整体的分布状态均匀化。

"""
神经网络的推理处理
"""

import os
import sys
import pickle
import numpy as np

from datetime import datetime

sys.path.append(os.pardir)
from dataset.mnist import load_mnist


def sigmoid(x):
    """
    sigmoid 激活函数
    :param x:
    :return:
    """
    return 1 / (1 + np.exp(-x))


def softmax(x):
    """
    softmax 激活函数
    :param x:
    :return:
    """
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T

    x = x - np.max(x)   # 溢出对策
    return np.exp(x) / np.sum(np.exp(x))


def get_data():
    """
    获取 MNIST 数据集
    :return:
    """
    (x_trian, y_train), (x_test, y_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, y_test


def init_network():
    """
    生成网络
    :return:
    """
    with open("dataset/sample_weight.pkl", "rb") as f:
        network = pickle.load(f)

    return network


def predict(network, x):
    """
    图像分类
    :param network:
    :param x:
    :return:
    """
    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_test, y_test = get_data()
network = init_network()

accuracy_cnt = 0

start_time = datetime.now()
# for 循环逐一取出数据集中的图像数据,使用 predict() 函数进行分类
for i in range(len(x_test)):
    y = predict(network, x_test[i])
    p = np.argmax(y)    # 获取概率最高的元素的索引

    if p == y_test[i]:
        accuracy_cnt += 1

print(f"Accuracy: {float(accuracy_cnt) / len(x_test)}")

end_time = datetime.now()
print(f"run time: {end_time - start_time}")


# Accuracy: 0.9352
# run time: 0:00:00.781155

6.3 批处理

>>> x_test, y_test = get_data()
>>> network = init_network()
>>> w1, w2, w3 = network["W1"], network["W2"], network["W3"]
>>>
>>> x_test.shape
(10000, 784)
>>> x_test[0].shape
(784, 0)
>>> w.shape
(784, 50)
>>> w2.shape
(50, 100)
>>> w3.shape
(100, 10)
图 2-23    数组形状的变化

 

图 2-24    批处理中数组形状的变化

 

"""
神经网络的推理处理
"""

import os
import sys
import pickle
import numpy as np

from datetime import datetime

sys.path.append(os.pardir)
from dataset.mnist import load_mnist


def sigmoid(x):
    """
    sigmoid 激活函数
    :param x:
    :return:
    """
    return 1 / (1 + np.exp(-x))


def softmax(x):
    """
    softmax 激活函数
    :param x:
    :return:
    """
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T

    x = x - np.max(x)   # 溢出对策
    return np.exp(x) / np.sum(np.exp(x))


def get_data():
    """
    获取 MNIST 数据集
    :return:
    """
    (x_trian, y_train), (x_test, y_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, y_test


def init_network():
    """
    生成网络
    :return:
    """
    with open("dataset/sample_weight.pkl", "rb") as f:
        network = pickle.load(f)

    return network


def predict(network, x):
    """
    图像分类
    :param network:
    :param x:
    :return:
    """
    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_test, y_test = get_data()
network = init_network()

batch_size = 100    # 批处理
accuracy_cnt = 0

start_time = datetime.now()
# for 循环逐一取出数据集中的图像数据,使用 predict() 函数进行分类
# for i in range(len(x_test)):
#     y = predict(network, x_test[i])
#     p = np.argmax(y)    # 获取概率最高的元素的索引
#
#     if p == y_test[i]:
#         accuracy_cnt += 1

# 批处理
for i in range(0, len(x_test), batch_size):
    x_batch = x_test[i: i + batch_size]
    y_batch = predict(network, x_batch)
    p = np.argmax(y_batch, axis=1)    # 获取概率最高的元素的索引

    accuracy_cnt += np.sum(p == y_test[i: i + batch_size])

print(f"Accuracy: {float(accuracy_cnt) / len(x_test)}")

end_time = datetime.now()
print(f"run time: {end_time - start_time}")

# Accuracy: 0.9352
# run time: 0:00:00.369021

    可以看出,批处理可以缩短处理时间(批处理一次性计算大型数组要比分开逐步计算各个小型数组速度更快),这是为什么呢?

        (1)大多数处理数值的计算的库都进行了能够高效处理大型数组运算的最优化;

        (2)在神经网络的运算中,当数据传送成为瓶颈时,批处理可以减轻数据总线的负荷(严格地讲,相对于数据读入,可以将更多的时间用在计算上)。

  • 7
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值