深度学习入门-基于python
Contents
Python入门
NumPy
-
NumPy数组可以和单一数值(标量)组合起来运算,此时NumPy数组的各个元素和标量之间进行运算。(广播)
1 2 3
>>> x = np.array([1.0,2.0,3.0]) >>> x/2.0 array([0.5, 1. , 1.5])
-
广播:
1 2 3 4 5 6 7
>>> A = np.array([[1,2],[3,4]]) >>> B = np.array([10,20]) >>> A * B array([[10, 40], [30, 80]]) # 一维数组B被“巧妙地”变成了和二位数组A相同的形状,然后再以对应元素的方式进行运算。 # 因为NumPy有广播功能,所以不同形状的数组之间也可以顺利地进行运算
-
访问元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
# 元素的索引从0开始 >>> X = np.array([[51, 55], [14, 19], [0, 4]]) >>> print(X) [[51 55] [14 19] [ 0 4]] >>> X[0] # 第0行 array([51, 55]) >>> X[0][1] # (0,1)的元素 55 # 以使用for语句访问各个元素 >>> for row in X: ... print(row) ... [51 55] [14 19] [0 4] # 使用数组访问各个元素 >>> X = X.flatten() # 将X转换为一维数组 >>> print(X) [51 55 14 19 0 4] >>> X[np.array([0, 2, 4])] # 获取索引为0、2、4的元素 array([51, 14, 0]) # 运用这个标记法,可以获取满足一定条件的元素 >>> X > 15 array([ True, True, False, True, False, False], dtype=bool) >>> X[X>15] array([51, 55, 19]) #对NumPy数组使用不等号运算符等(上例中是X > 15),结果会得到一个布尔型的数组。上例中就是使用这个布尔型数组取出了数组的各个元素(取出True对应的元素)
Matplotlib
|
|
感知机
感知机接收多个输入信号,输出一个信号。这里所说的“信号”可以想 象成电流或河流那样具备“流动性”的东西。像电流流过导线,向前方输送 电子一样,感知机的信号也会形成流,向前方输送信息。但是,和实际的电 流不同的是,感知机的信号只有“流/不流”(1/0)两种取值。在本书中,0 对应“不传递信号”,1对应“传递信号”。
x1、x2是输入信号, y是输出信号,w1、w2是权重(w是weight的首字母)。图中的○称为“神 经元”或者“节点”。输入信号被送往神经元时,会被分别乘以固定的权重(w1x1、w2x2)。神经元会计算传送过来的信号的总和,只有当这个总和超过 了某个界限值时,才会输出1。这也称为“神经元被激活”。这里将这个界 限值称为阈值,用符号θ表示。
神经网络
神经网络的一个重要性质是它可以自动地从数据中学习到合适的权重参数。
h(x)函数会将输入信号的总和转换为输出信号,这种函数 一般称为激活函数(activation function)。如“激活”一词所示,激活函数的 作用在于决定如何来激活输入信号的总和。表示神经元的○中明确显示了激活函数的计算过程,即 信号的加权总和为节点a,然后节点a被激活函数h()转换成节点y。
激活函数
-
以阈值为界,一旦输入超过阈值,就切换输出。 这样的函数称为“阶跃函数”。
-
sigmoid函数:h(x)=1/(1+exp(-x))
神经网络中用sigmoid函数作为激活函数,进行信号的转换,转换后的信号被传送给下一个神经元。
-
上一章介绍的感知机和接下来要介绍 的神经网络的主要区别就在于这个激活函数。
阶跃函数的实现
|
|
|
|
对NumPy数组进行不等号运算后,数组的各个元素都会进行不等号运算,
生成一个布尔型数组
。这里,数组x中大于0的元素被转换为True,小于等 于0的元素被转换为False,从而生成一个新的数组y。数组y是一个布尔型数组,但是我们想要的阶跃函数是会输出int型的0 或1的函数。因此,需要把数组y的元素类型从布尔型转换为int型
用
astype()
方法转换NumPy数组的类型。astype()方 法通过参数指定期望的类型,这个例子中是np.int型。Python中将布尔型 转换为int型后,True会转换为1,False会转换为0。
|
|
np.arange(-5.0, 5.0, 0.1)在−5.0到5.0的范围内,以0.1为单位,生成 NumPy数组([-5.0, -4.9, …, 4.9])。step_function()以该NumPy数组为 参数,对数组的各个元素执行阶跃函数运算,并以数组形式返回运算结果。
阶跃函数以0为界,输出从0切换为1(或者从1切换为0)。 它的值呈阶梯式变化,所以称为阶跃函数。
sigmoid函数的实现
|
|
|
|
参数x为NumPy数组时,结果也能被正确计算.
之所以sigmoid函数的实现能支持NumPy数组,秘密就在于NumPy的 广播功能
根据NumPy 的广播功能,如果在标量和NumPy数组 之间进行运算,则标量会和NumPy数组的各个元素进行运算。
|
|
sigmoid函数和阶跃函数的比较
- sigmoid函数是一条平滑的曲线,输出随着输入发生连续性的变化。而阶跃函数以0为界,输出发生急剧性的变化。sigmoid函数的平滑性对神经网络的学习具有重要意义
- 相对于阶跃函数只能返回0或1,sigmoid函数可以返回0.731 …、0.880 …等实数(这一点和刚才的平滑性有关)。也就是说,感知机中神经元之间流动的是0或1的二元信号,而神经网络中流动的是连续的实数值信号。
- 从宏观视角看,可以发现它们具有相似的形状。实际上,两者的结构均是“输入小时,输出接近0(为0); 随着输入增大,输出向1靠近(变成1)”。也就是说,当输入信号为重要信息时,阶跃函数和sigmoid函数都会输出较大的值;当输入信号为不重要的信息时,两者都输出较小的值。还有一个共同点是,不管输入信号有多小,或者有多大,输出信号的值都在0到1之间。
非线性函数
阶跃函数和sigmoid函数还有其他共同点,就是两者均为非线性函数。 sigmoid函数是一条曲线,阶跃函数是一条像阶梯一样的折线,两者都属于非线性的函数。
神经网络的激活函数必须使用非线性函数。换句话说,激活函数不能使用线性函数。为什么不能使用线性函数呢?因为使用线性函数的话,加深神经网络的层数就没有意义了。
线性函数的问题在于,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。这里我们考虑把线性函数 h(x) = cx 作为激活 函数,把y(x) = h(h(h(x)))的运算对应3层神经网络A。这个运算会进行 y(x) = c × c × c × x的乘法运算,但是同样的处理可以由y(x) = ax(注意, a = c 3 )这一次乘法运算(即没有隐藏层的神经网络)来表示。
ReLU函数
ReLU函数在输入大于0时,直接输出该值;在输入小于等于0时,输出0。
|
|
多维数组的运算
-
数组的维数可以通过np.dim()函数获得。此外,数组的形状可以通过实例变量shape获得
-
A是一维数组,由4个元素 构成。注意,这里的A.shape的结果是个元组(tuple)。(4,)
-
np.dot(A,B)函数计算矩阵A,B的乘积(乘积也称为点积)。
-
当A是二维矩阵、B是一维数组时。也可进行计算。A3*2.B2 = C3
[[1,2], [3, 4], [5,6]]*[7,8]=[23, 53, 83]
-
神经网络的内积:
3层神经网络的实现
a1^(1)=ω11^(1)x1+ω12^(1)x2+b1^(1)
A^(1)=XW^(1)+B^(1)
现在看一下从输入层到第1层的第1个神经元的信号传递过程
|
|
接下来,我们观察第1层中激活函数的计算过程。隐藏层的加权和(加权信号和偏置的总和)用a表示,被激活函数转换后的信号用z表示。此外,图中h()表示激活函数,这里我们使用的是sigmoid函数。
|
|
下面,我们来实现第1层到第2层的信号传递。除了第1层的输出(Z1)变成了第2层的输入这一点以外,这个实现和刚才的代码完全相同。由此可知,通过使用NumPy数组,可以将层到层的信 号传递过程简单地写出来
|
|
最后是第2层到输出层的信号传递,输出层的实现也和之前的实现基本相同。不过,最后的激活函数和之前的隐藏层有所不同。这里我们定义了identity_function()函数(也称为“恒等函数”),并将其作为输出层的激活函数。恒等函数会将输入按原样输出,因此,这个例子中没有必要特意定义identity_function()。这里这样实现只是为了和之前的流程保持统一。
另外,输出层的激活函数用σ()表示,不同于隐藏层的激活函数h()。输出层所用的激活函数,要根据求解问题的性质决定。一般地,回归问题可以使用恒等函数,二元分类问题可以使用 sigmoid函数,多元分类问题可以使用 softmax函数。
|
|
代码实现小结
|
|
输出层的设计
😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂
神经网络可以用在分类问题和回归问题上,不过需要根据情况改变输出层的激活函数。一般而言,回归问题用恒等函数,分类问题用softmax函数。
机器学习的问题大致可以分为分类问题和回归问题。分类问题是数据属于哪一个类别的问题。比如,区分图像中的人是男性还是女性的问题就是分类问题。而回归问题是根据某个输入预测一个(连续的)数值的问题。比如,根据一个人的图像预测这个人的体重的问题就是回归问题(类似“57.4kg”这样的预测)。
恒等函数会将输入按原样输出,对于输入的信息,不加以任何改动地直接输出。因此,在输出层使用恒等函数时,输入信号会原封不动地被输出。
softmax函数
式(3.10)表示假设输出层共有n个神经元,计算第k个神经元的输出yk。softmax函数的分子是输入信号ak的指数函数,分母是所有输入信号的指数函数的和。softmax函数的输出通过箭头与所有的输入信号相连。这是因为,从式(3.10)可以看出,输出层的各个神经元都受到所有输入信号的影响。
|
|
|
|
计算机的运算上有一定的缺陷。这个缺陷就是溢出问题。softmax函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大。
改进:
在进行softmax的指数函数的运算时,加上(或者减去)某个常数并不会改变运算的结果。这里的C‘ 可以使用任何值,但是为了防止溢出,一般会使用输入信号中的最大值。
|
|
|
|
特征:
|
|
如上所示,softmax函数的输出是0.0到1.0之间的实数。并且,softmax 函数的输出值的总和是1。输出总和为1是softmax函数的一个重要性质。正因为有了这个性质,我们才可以把softmax函数的输出解释为“概率”。
比如,上面的例子可以解释成y[0]的概率是0.018(1.8 %),y[1]的概率是0.245(24.5 %),y[2]的概率是0.737(73.7 %)。从概率的结果来看,可以说“因为第2个元素的概率最高,所以答案是第2个类别”。
即便使用了softmax函数,各个元素之间的大小关系也不会改变。这是因为指数函数(y = exp(x))是单调递增函数。实际上,上例中a的各元素的大小关系和y的各元素的大小关系并没有改变。比如,a 的最大值是第2个元素,y的最大值也仍是第2个元素。
一般而言,神经网络只把输出值最大的神经元所对应的类别作为识别结果。并且,即便使用softmax函数,输出值最大的神经元的位置也不会变。因此,神经网络在进行分类时,输出层的softmax函数可以省略。在实际的问题中,由于指数函数的运算需要一定的计算机运算量,因此输出层的softmax函数一般会被省略。
输出层神经元数量:输出层的神经元数量需要根据待解决的问题来决定。对于分类问题,输出层的神经元数量一般设定为类别的数量。比如,对于某个输入图像,预测是图中的数字0到9中的哪一个的问题(10类别分类问题),将输出层的神经元设定为10个。
手写数字识别
假设学习已经全部结束,我们使用学习到的参数,先实现神经网络的“推理处理”。这个推理处理也称为神经网络的前向传播(forward propagation)
和求解机器学习问题的步骤(分成学习和推理两个阶段进行)一样,使用神经网络解决问题时,也需要首先使用训练数据(学习数据)进行权重参数的学习;进行推理时,使用刚才学习到的参数,对输入数据进行分类。
MNIST数据集
MNIST数据集是由0到9的数字图像构成的(图3-24)。训练图像有6万张,测试图像有1万张,这些图像可以用于学习和推理。MNIST数据集的一般使用方法是,先用训练图像进行学习,再用学习到的模型度量能在多大程度上对测试图像进行正确的分类。
MNIST的图像数据是28像素 × 28像素的灰度图像(1通道),各个像素的取值在0到255之间。每个图像数据都相应地标有“7”“2”“1”等标签。
|
|
首先,为了导入父目录中的文件,进行相应的设定A。然后,导入 dataset/mnist.py中的 load_mnist函数。最后,使用 load_mnist函数,读入 MNIST数据集。第一次调用load_mnist函数时,因为要下载MNIST数据集,所以需要接入网络。第2次及以后的调用只需读入保存在本地的文件(pickle 文件)即可,因此处理所需的时间非常短。
用来读入MNIST图像的文件在本书提供的源代码的dataset目录下。并且,我们假定了这个MNIST数据集只能从ch01、ch02、 ch03、…、ch08目录中使用,因此,使用时需要从父目录(dataset 目录)中导入文件,为此需要添加sys.path.append(os.pardir)语句。
观察本书源代码可知,上述代码在mnist_show.py文件中。mnist_show.py文件的当前目录是ch03, 但包含load_mnist()函数的mnist.py文件在dataset目录下。因此,mnist_show.py文件不能跨目 录直接导入mnist.py文件。sys.path.append(os.pardir)语句实际上是把父目录deep-learningfrom-scratch加入到sys.path(Python的搜索模块的路径集)中,从而可以导入deep-learning-from-scratch下的任何目录(包括dataset目录)中的任何文件。
load_mnist函数以“(训练图像 ,训练标签),(测试图像,测试标签)”的 形式返回读入的MNIST数据。
还可以像load_mnist(normalize=True, flatten=True, one_hot_label=False) 这 样,设 置 3 个 参 数。第 1 个参数 normalize设置是否将输入图像正规化为0.0~1.0的值。如果将该参数设置 为False,则输入图像的像素会保持原来的0~255。第2个参数flatten设置 是否展开输入图像(变成一维数组)。如果将该参数设置为False,则输入图像为1 × 28 × 28的三维数组;若设置为True,则输入图像会保存为由784个元素构成的一维数组。第3个参数one_hot_label设置是否将标签保存为one-hot表示(one-hot representation)。one-hot表示是仅正确解标签为1,其余 皆为0的数组,就像[0,0,1,0,0,0,0,0,0,0]这样。当one_hot_label为False时, 只是像7、2这样简单保存正确解标签;当one_hot_label为True时,标签则 保存为one-hot表示。
Python有 pickle这个便利的功能。这个功能可以将程序运行中的对象保存为文件。如果加载保存过的 pickle文件,可以立刻复原之前程序运行中的对象。用于读入MNIST数据集的load_mnist()函数内部也使用了 pickle功能。
|
|
图像的显示使用PIL(Python Image Library)模块。执行下述代码后,训练图像的第一张就会显示出来。
这里需要注意的是,flatten=True时读入的图像是以一列(一维)NumPy数组的形式保存的。因此,显示图像时,需要把它变为原来的28像素 × 28 像素的形状。可以通过reshape()方法的参数指定期望的形状,更改NumPy 数组的形状。此外,还需要把保存为NumPy数组的图像数据转换为PIL用的数据对象,这个转换处理由Image.fromarray()来完成。
神经网络的推理处理
我们对这个MNIST数据集实现神经网络的推理处理。神经网络的输入层有784个神经元,输出层有10个神经元。输入层的784这个数字来源于图像大小的28 × 28 = 784,输出层的10这个数字来源于10类别分类(数字0到9,共10类别)。
这个神经网络有2个隐藏层,第1个隐藏层有 50个神经元,第2个隐藏层有100个神经元。这个50和100可以设置为任何值。
|
|
Python中的续行符是反斜杠("\"),它可以将一行代码分成多行来写,便于代码的编写和阅读,使用方式是在行尾加上反斜杠,注意反斜杠后面不能加空格,必须直接换行.
因为之前我们假设学习已经完成,所以学习到的参数被保存下来。假设保存在sample_weight.pkl 文件中,在推理阶段,我们直接加载这些已经学习到的参数。
|
|
首先获得MNIST数据集,生成网络。接着,用for语句逐一取出保存 在x中的图像数据,用predict()函数进行分类。predict()函数以NumPy数 组的形式输出各个标签对应的概率。然后,我们取出这个概率列表中的最大值的索引(第几个元素的概率最高),作为预测结果。
可以用np.argmax(x)函数取出数组中的最大值的索引,np.argmax(x)将获取被赋给参数x的数组中的最大值元素的索引。
最后,比较神经网络所预 测的答案和正确解标签,将回答正确的概率作为识别精度。
在这个例子中,我们把load_mnist函数的参数normalize设置成了 True。将normalize设置成True后,函数内部会进行转换,将图像的各个像素值除以255,使得数据的值在0.0~1.0的范围内。像这样把数据限定到某个范围内的处理称为正规化(normalization)。此外,对神经网络的输入数据进行某种既定的转换称为预处理(pre-processing)。这里,作为对输入图像的 一种预处理,我们进行了正规化。
实际上,很多预处理都会考虑到数据的整体分布。比如,利用数据整体的均值或标准差,移动数据,使数据整体以 0为中心分布,或者进行正规化,把数据的延展控制在一定范围内。除此之外,还有将数据整体的分布形状均匀化的方法,即数据白化(whitening等
|
|
我们通过上述结果来确认一下多维数组的对应维度的元素个数是否一致(省略了偏置)。可以确认最终的结果是输出了元素个数为10 的一维数组。
从整体的处理流程来看,输入一个由784个元素(原本是一个28 × 28的二维数组)构成的一维数组后,输出一个有10个元素的一维数组。 这是只输入一张图像数据时的处理流程。
现在我们来考虑打包输入多张图像的情形。比如,我们想用predict()函数一次性打包处理100张图像。为此,可以把x的形状改为100 × 784,将 100张图像打包作为输入数据。
输入数据的形状为 100 × 784,输出数据的形状为 100 × 10。这表示输入的100张图像的结果被一次性输出了。比如,x[0]和y[0]中保存了第0张图像及其推理结果。这种打包式的输入数据称为批(batch)。
批处理对计算机的运算大有利处,可以大幅缩短每张图像的处理时间。那么为什么批处理可以缩短处理时间呢?这是因为大多数处理 数值计算的库都进行了能够高效处理大型数组运算的最优化。并且,在神经网络的运算中,当数据传送成为瓶颈时,批处理可以减轻数 据总线的负荷(严格地讲,相对于数据读入,可以将更多的时间用在计算上)。也就是说,批处理一次性计算大型数组要比分开逐步计算各个小型数组速度更快。
|
|
range()函数若指定为range(start, end),则会生成一个由start到end-1之间的整数构成的列表。若像range(start, end, step)这样指定3个整数,则生成的列表中的 下一个元素会增加step指定的值。
在range()函数生成的列表的基础上,通过x[i:i+batch_size]从输入数据中抽出批数据。x[i:i+batch_n]会取出从第i个到第i+batch_n个之间的数据。本例中是像x[0:100]、x[100:200]……这样,从头开始以100为单位将数据提取为批数据。
然后,通过argmax()获取值最大的元素的索引。不过这里需要注意的是,我们给定了参数axis=1。这指定了在100 × 10的数组中,沿着第1维方向(以第1维为轴)找到值最大的元素的索引.
矩阵的第0维是列方向,第1维是行方向。
https://blog.csdn.net/sky_kkk/article/details/79725646
我们比较一下以批为单位进行分类的结果和实际的答案。为此,需要在NumPy数组之间使用比较运算符(==)生成由True/False构成的布尔型数组,并计算True的个数。
|
|
神经网络的学习
Author kong
LastMod 2022-03-16