深度学习入门——神经网络

发布于:2024-07-17 ⋅ 阅读:(73) ⋅ 点赞:(0)

前言

image-20240713161717914

神经网络可以帮助自动化设定权重

具体地讲,神经网络的一个重要性质是它可以自动地从数据中学习到合适的权重参数

从感知机到神经网络

神经网络的例子

image-20240713162915788

中间层aka隐藏层

复习感知机

image-20240713164325276

偏置b 并没有被画出来。如果要明确地表示出b,可以像图3-3那样做

我们用一个函数来表示这种分情况的动作(超过0 则输出1,否则输出0)

y = h(b + w1x1 + w2x2)

image-20240713164410998

image-20240713164317059

激活函数登场

如h(x)函数将输入信号的综合转换为输出信号一般称为激活函数

激活函数的作用在于决定如何来激活输入信号的总和

先计算输入信号的加权总和,然后用激活函数转换这一总和,可以将y = h(b + w1x1 + w2x2)分成两个式子

image-20240713165352839

image-20240713165430718

激活函数是连接感知机和神经网络的桥梁

一般而言,“朴素感知机”是指单层网络,指的是激活函数使用了阶跃函数的模型。

(阶跃函数是指一旦输入超过阈值,就切换输出的函数)

“多层感知机”是指神经网络,即使用sigmoid函数(后述)等平滑的激活函数的多层网络。

激活函数

实际上,如果将激活函数从阶跃函数换成其他函数,就可以进入神经网络的世界了

sigmoid函数

image-20240713165954309

神经网络中经常使用的一个激活函数就是式(3.6)表示的sigmoid 函数(sigmoid function)

exp(−x)表示e^−x 的意思

阶跃函数的实现

def step_function(x):
	if x > 0:
		return 1
	else:
		return 0

这个实现简单、易于理解,但是参数x只能接受实数(浮点数)

为了便于后面的操作,我们把它修改为支持NumPy数组的实现

def step_function(x):
	y = x > 0
	return y.astype(np.int)

可以用astype()方法转换NumPy数组的类型,通过参数指定期望的类型

Python 中将布尔型转换为int型后,True会转换为1,False会转换为0

阶跃函数的图形

image-20240713172731720

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

sigmoid函数的实现

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

根据NumPy 的广播功能,如果在标量和NumPy数组之间进行运算,则标量会和NumPy数组的各个元素进行运算

sigmoid函数的图形

image-20240713173129398

sigmoid函数和阶跃函数的比较

有点类似于数字信号和模拟信号

image-20240713173208866

不同点

  • 首先注意到的是“平滑性”的不同。sigmoid 函数是一条平滑的曲线,输出随着输入发生连续性的变化。而阶跃函数以0 为界,输出发生急剧性的变化。sigmoid 函数的平滑性对神经网络的学习具有重要意义。
  • 另一个不同点是,相对于阶跃函数只能返回0 或1,sigmoid 函数可以返回0.731 . . .、0.880 . . . 等实数(这一点和刚才的平滑性有关)。也就是说,感知机中神经元之间流动的是0 或1 的二元信号,而神经网络中流动的是连续的实数值信号。

相同点

  • 两者的结构均是“输入小时,输出接近0(为0);随着输入增大,输出向1 靠近(变成1)”。也就是说,当输入信号为重要信息时,阶跃函数和sigmoid函数都会输出较大的值;当输入信号为不重要的信息时,两者都输出较小的值
  • 不管输入信号有多小,或者有多大,输出信号的值都在0 到1 之间。

非线性函数

阶跃函数和sigmoid函数均为非线性函数

神经网络的激活函数必须使用非线性函数

这里可能会产生疑惑

前面的多层感知器和神经网络之间的关系是什么?是同一个东西

也就是说异或门的实现是一个神经网络吗?是的

Q:为什么不能使用线性函数呢?

A:因为使用线性函数的话,加深神经网络的层数就没有意义了。使用线性函数时,无法发挥多层网络带来的优势。因此,为了发挥叠加层所带来的优势,激活函数必须使用非线性函数

ReLU函数

在神经网络发展的历史上,sigmoid 函数很早就开始被使用了,而最近则主要使用ReLU(Rectified Linear Unit)函数。

ReLU函数在输入大于0 时,直接输出该值;在输入小于等于0 时,输出0

image-20240713174941407

image-20240713174952053

实现

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

多维数组的运算

掌握了NumPy多维数组的运算,就可以高效地实现神经网络

数组的维数累哦通过 np.dim() 函数获得

数组的形状可以通过实例变量shape获得

矩阵乘法

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

image-20240713180103448

可以通过NumPy 的np.dot()函数计算(乘积也称为点积)

[!IMPORTANT]

盲区:当A是二维矩阵、B是一维数组时,对应维度的元素个数要保持一致的原则依然成立。

从结果来看,一维数组的转置不影响点乘的结果,但是二维矩阵和一维数组的位置会影响点乘的结果

当一维数组在右边时,不论是否转置,会默认变成p行1列的矩阵,即p1
当一维数组在左边时,不论是否转置,会默认变成1行p列的矩阵,即1
p
不管B进行.T运算与否,B被看成什么样子完全由乘法的顺序和a的形状决定

image-20240713191810691

神经网络的内积

image-20240713195131193

下面我们使用NumPy矩阵来实现神经网络。这里我们以图3-14 中的简单神经网络为对象。这个神经网络省略了偏置和激活函数,只有权重。

通过矩阵的乘积一次性完成计算的技巧,在实现的层面上可以说是非常重要的

3层神经网络的实现

image-20240713200548279

以图3-15 的3 层神经网络为对象,实现从输入到输出的(前向)处理。在代码实现方面,使用上一节介绍的NumPy多维数组

符号确认

神经网络的运算可以作为矩阵运算打包进行

image-20240713203416516

各层间信号传递的实现

任何前一层的偏置神经元“1”都只有一个

偏置权重的数量取决于后一层的神经元的数量(不包括后一层的偏置神经元“1”)

image-20240713203904754

用数学式表示

image-20240713204010125

矩阵乘法表示

image-20240713204211014

image-20240713204256031

1层计算过程

观察第1 层中激活函数的计算过程。如果把这个计算过程用图来表示的话,则如图3-18 所示。

image-20240713205041805

隐藏层的加权和(加权信号和偏置的总和)用a表示,被激活函数转换后的信号用z 表示

1->2层信号传递

通过使用NumPy数组,可以将层到层的信号传递过程简单地写出来

image-20240713205626885

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

我们定义了identity_function()函数(也称为“恒等函数”),并将其作为输出层的激活函数

这里这样实现只是为了和之前的流程保持统一

另外,图3-20 中,输出层的激活函数用σ() 表示,不同于隐藏层的激活函数h()(σ读作sigma)

image-20240713210609159

输出层所用的激活函数,要根据求解问题的性质决定

[!IMPORTANT]

一般地,回归问题可以使用恒等函数,

二元分类问题可以使用sigmoid 函数,

多元分类问题可以使用softmax 函数

代码实现小结

我们按照神经网络的实现惯例,只把权重记为大写字母W1,其他的(偏置或中间结果等)都用小写字母表示

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([1.0, 0.5])
y = forward(network, x)
print(y) # [0.31682708 0.69627909]

init_network()函数会进行权重和偏置的初始化,并将它们保存在字典变量network中

forward()函数中则封装了将输入信号转换为输出信号的处理过程。

forward(前向)一词,它表示的是从输入到输出方向的传递处理

后面在进行神经网络的训练时,我们将介绍后向(backward,从输出到输入方向)的处理。

输出层的设计

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

机器学习的问题大致可以分为分类问题回归问题。分类问题是数据属于哪一个类别的问题。比如,区分图像中的人是男性还是女性的问题就是分类问题。而回归问题是根据某个输入预测一个(连续的)数值的问题

恒等函数和softmax函数

image-20240714105054534

image-20240714105212891

输出层的各个神经元都受到所有输入信号的影响

image-20240714105333875

#虽然正确描述了式(3.10),但在计算机的运算上有溢出问题
def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

实现softmax函数时的注意事项

softmax函数中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大,在这些超大值之间进行除法运算,结果会出现“不确定”的情况

[!NOTE]

计算机可以表示的数值范围是有限的(32位或64位),会出现超大值无法表示的问题,这个问题称为溢出

image-20240714110252522

式(3.11)说明,在进行softmax 的指数函数的运算时,加上(或者减去)某个常数并不会改变运算的结果.

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

improved softmax 实现

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c) # 溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

softmax 函数的特征

  1. softmax 函数的输出是0.0 到1.0之间的实数
  2. softmax 函数的输出值的总和是1
  3. softmax函数的输出可以解释为“概率”
  4. 各个元素之间的大小关系不会改变

一般而言,神经网络只把输出值最大的神经元所对应的类别作为识别结果。

即便使用softmax 函数,输出值最大的神经元的位置也不会变,神经网络在进行分类时,输出层的softmax 函数可以省略

在实际的问题中,由于指数函数的运算需要一定的计算机运算量,因此输出层的softmax 函数一般会被省略

[!IMPORTANT]

求解机器学习问题的步骤可以分为“学习” 和“推理”两个阶段。首先,在学习阶段进行模型的学习,然后,在推理阶段,用学到的模型对未知的数据进行推理(分类)

推理阶段一般会省略输出层的softmax 函数。在输出层使用softmax 函数是因为它和神经网络的学习有关系

输出层的神经元数量

输出层的神经元数量需要根据待解决的问题来决定

对于分类问题,输出层的神经元数量一般设定为类别的数量

手写数字识别

假设学习已经全部结束,我们使用学习到的参数,先实现神经网络的“推理处理”。这个推理处理也称为神经网络的前向传播(forward propagation)

MNIST数据集

MNIST的图像数据是28 像素× 28 像素的灰度图像(1 通道),各个像素的取值在0 到255 之间

神经网络的推理过程

在手写数字识别中,神经网络的输入层有784个神经元,输出层有10个神经元

def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    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()
network = init_network()
accuracy_cnt = 0
for i in range(len(x)):
    y = predict(network, x[i])
    p= np.argmax(y) # 获取概率最高的元素的索引
    if p == t[i]:
        accuracy_cnt += 1
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

可以用np.argmax(x)函数取出数组中的最大值的索引(np.argmax(x)将获取被赋给参数x的数组中的最大值元素的索引)

将normalize设置成True后,函数内部会进行转换,将图像的各个像素值除以255,使得数据的值在0.0~1.0 的范围内。像这样把数据限定到某个范围内的处理称为正规化(normalization)

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

[!IMPORTANT]

预处理在神经网络(深度学习)中非常实用,其有效性已在提高识别性能和学习的效率等众多实验中得到证明。

实际上,很多预处理都会考虑到数据的整体分布。比如,利用数据整体的均值或标准差,移动数据,使数据整体以0 为中心分布,或者进行正规化,把数据的延展控制在一定范围内。除此之外,还有将数据整体的分布形状均匀化的方法,即数据白化(whitening)等。

批处理

image-20240714145413687

image-20240714145421964

打包式的输入数据称为批(batch),批有“捆”的意思,图像就如同纸币一样扎成一捆。

[!IMPORTANT]

批处理对计算机的运算大有利处,可以大幅缩短每张图像的处理时间。

大多数处理数值计算的库都进行了能够高效处理大型数组运算的最优化。并且,在神经网络的运算中,当数据传送成为瓶颈时,批处理可以减轻数据总线的负荷(严格地讲,相对于数据读入,可以将更多的时间用在计算上)。也就是说,批处理一次性计算大型数组要比分开逐步计算各个小型数组速度更快

x, t = get_data()
network = init_network()
batch_size = 100 # 批数量
accuracy_cnt = 0
for i in range(0, len(x), batch_size):
    x_batch = x[i:i+batch_size]
    y_batch = predict(network, x_batch)
    p = np.argmax(y_batch, axis=1)
    accuracy_cnt += np.sum(p == t[i:i+batch_size])
    print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

像range(start, end, step)这样指定3 个整数,则生成的列表中的下一个元素会增加step指定的值。

通过argmax()获取值最大的元素的索引。不过这里需要注意的是,我们给定了参数axis=1。这指定了在100 × 10 的数组中,沿着第1 维方向(以第1 维为轴)找到值最大的元素的索引(第0 维对应第1 个维度)

矩阵的第0 维是列方向,第1 维是行方向

使用批处理,可以实现高速且高效的运算

回顾总结

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

网站公告

今日签到

点亮在社区的每一天
去签到