第二章神经网络的数学基础

初识神经网络

我们使用Python的Keras库来学习手写数字分类。如果你没有用过Keras或类似的库,可能无法立刻搞懂这个例子中的全部内容,没关系,下一章会详细介绍,这一节大家大致了解就好。
我们要解决的问题是:将手写数字的灰度图像(28*28)划分到10个类别中(0-9)。我们将使用MNIST,它是机器学习领域的经典数据集,历史几乎和这个领域一样长,这个数据集包含60000张训练图像和10000张测试图像。在机器学习中,分类问题中的某个类别叫做类(class)。数据点叫做样本(sample)。某个样本对应的类叫做标签(label)。
不需要现在就运行这个例子,如果你想的话首先安装Keras,安装方法在3.3节。
MNIST数据预先加载在Keras中,其中包括四个numpy数组。

from keras.datasets import mnist
(train_images,train_labels),(test_images,test_labels)=mnist.load_data()

train_images训练集图像和train_labels训练集标签组成训练集,模型将从这些数据中进行学习,然后在测试集中对模型进行测试

/*训练数据*/
train_images.shape  * 训练集的形状,60000张28像素*28像素的图片 *
(60000, 28, 28)
len(train_images)   *训练集的长度*
60000
train_labels       * 训练集标签表示,是一个数组,第一个数字5第二个数字0...类型是unit8*
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)
/*测试数据*/
test_images.shape    * 测试集的形状,10000张28像素*28像素的图片 *
(10000, 28, 28)
len(test_labels)     *测试集的长度*
10000
test_labels           *测试集标签表示,是一个数组,第一个数字7第二个数字2...类型是unit8*
array([7, 2, 1, ..., 4, 5, 6], dtype=uint8)

接下来的工作步骤如下:
1.将训练数据输入神经网络
2.网络学习将图像和标签关联在一起
3.网络对测试集图像生成预测,我们将验证这些预测与测试集标签中的标签是否匹配。
再说一遍不需要现在就运行这个例子,也不需要理解这个例子全部内容。
神经网络的核心组件是层(layer),它是一种数据处理模块,可以看成是过滤器,进去一些数据,出来的数据变得更加有用。具体来说,层从数据中提取表示----这种表示有助于解决手头的问题。

from keras import models
from keras import layers
network = models.Sequential()
network.add(layers.Dense(512,activation='relu',input_shape=(28*28,)))
network.add(layers.Dense(10,activation='softmax'))

本例中的网络包含2个全连接(dense)层,它们是密集连接的神经层。第二层(也是最后一层)是一个10路softmax层,它返回一个由10个概率值(总和为1)组成的数组。
注意:softmax函数使得数据输出符合概率分布。
想训练网络,还需要编译(compile)步骤的三个参数
损失函数(loss function):衡量在训练数据上的性能,使得网络朝着正确的方向前进。
优化器(optimizer):基于训练数据和损失函数来更新网络的机制。
在训练和测试过程中需要监控的指标(metric):正确分类的图像占所有的比例。
后续两章会详细介绍,看不懂可跳过。

network.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy'])

编译好以后,在开始训练之前对数据进行预处理,将类型改为float32数组,取值缩放到0-1之间,原来取值区间是0-255

/*准备图像数据*/
train_images = train_images.reshape((60000,28*28))
train_images = train_images.astype('float32')/255

test_images = test_images.reshape((10000,28*28))
test_images = test_images.astype('float32')/255
/*准备标签*/
from keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

第三章会详细解释
然后开始准备训练网络,在Keras中这一步是通过调用网络的fit方法来完成的,我们在训练数据上拟合(fit)模型。

network.fit(train_images,train_labels,epochs=5,batch_size=128)
Epoch 1/5
469/469 [==============================] - 3s 5ms/step - loss: 0.4278 - accuracy: 0.8751
Epoch 2/5
469/469 [==============================] - 2s 5ms/step - loss: 0.1138 - accuracy: 0.9672
Epoch 3/5
469/469 [==============================] - 2s 5ms/step - loss: 0.0703 - accuracy: 0.9796
Epoch 4/5
469/469 [==============================] - 2s 5ms/step - loss: 0.0502 - accuracy: 0.9855
Epoch 5/5
469/469 [==============================] - 2s 5ms/step - loss: 0.0350 - accuracy: 0.9900
<tensorflow.python.keras.callbacks.History at 0x2538e98e670>

结果中,loss是损失,acc是精度,从结果中看我们已经达到了0.990的精度,现在我们看一下模型在测试集上的性能。

test_loss,test_acc = network.evaluate(test_images,test_labels)
print('test_acc:',test_acc)
test_acc: 0.9794999957084656

测试集精度为0.9794,比训练集精度低。
训练精度和测试精度这种差距是过拟合(overfit)造成的,过拟合是机器学习模型在新数据上的性能往往比在训练数据上要差。这是第三章的核心主题。
第一个例子到这里就结束了,看不懂也不要着急哟,后面会详细介绍这个过程。

神经网络的数据表示

张量(tensor)是矩阵向任意维度的推广【注意:张量的维度(dimension)通常叫做轴(axis)】
张量这一概念的核心在于,它是一个数据容器,它包含的数据几乎总是数值数据,因此它是数字的容器。

标量(0D【0维】张量)

仅包含一个数字的张量叫做标量(scalar),在numpy中,一个float32/64的数字就是一个标量张量,可以用ndim属性查看numpy张量轴的个数。标量张量有0个轴(ndim==0)张量轴的个数也叫做阶(rank)。下面是一个numpy标量。

import numpy as np
x = np.array(12)
x
array(12)
x.ndim  *张量轴的个数*
0

向量(1D【1维】张量)

数字组成的数组叫做向量(vector)或一维张量,只有一个轴,下面是一个numpy向量。

x = np.array([12,3,6,14,7])
x
array([12,  3,  6, 14,  7])
x.ndim
1

这个向量有5个元素,是5D向量,注意是5D向量不是5D张量,前者只有一个轴五个元素,后者有五个轴

矩阵(2D张量)

向量组成的数组叫矩阵(matrix)或者二维张量(2D张量),矩阵有两个轴,通常叫行和列。下面是一个 numpy矩阵。

x = np.array([[5,78,2,34,0],
             [6,79,3,35,1],
             [7,80,4,36,2]])
x.ndim
2

3D张量与更高维张量

将多个矩阵组合成一个新的数组,可以得到一个3D张量,可以直观理解为立方体。下面是一个 numpy3D张量。

x = np.array([[[5,78,2,34,0],
               [6,35,7,34,7],
               [7,80,4,36,6]],
              [[5,78,2,34,0],
               [6,79,3,35,1],
               [7,80,4,36,2]],
              [[5,78,2,34,0],
               [6,79,3,35,1],
               [7,80,4,36,2]]])
x.ndim
3

注意:几维张量就有几个方括号。

关键属性

**1.轴的个数(阶)。**例如,3D张量有3个轴,矩阵有两个轴,这在numpy等python库中也叫张量的ndim。
**2.形状。**这是一个整数元组,表示张量沿每个轴的维度大小(元素个数)。前面矩阵示例的形状为(3,5)3行5列;3D张量示例的形状为(3,3,5)3个矩阵3行5列;向量表示为(5,)表示有五个元素,是一维数组;标量的形状为空()表示0维。
**3.数据类型(在python中叫做dtype)。**例如类型可以是float32、float64、uint8等。
为了具体说明,我们回头看一下MNIST例子中处理的数据,首先加载数据,然后给出张量train_images的轴的个数(ndim),形状,数据类型(dtype)。

from keras.datasets import mnist
(train_images,train_labels),(test_images,test_labels) = mnist.load_data()
print(train_images.ndim)
print(train_images.shape)
print(train_images.dtype)
3
(60000, 28, 28)
uint8

train_images是一个8位整数组成的3D张量,更确切的说是由60000个矩阵组成的数组,每个矩阵由28*28个整数组成,每个这样的矩阵都是一张灰度图,取值范围是0-255.
我们用matplotlib来显示3D张量中第四个数字

digit = train_images[4]  /*选择第一个轴的第四张图片*/
import matplotlib.pyplot as plt
plt.imshow(digit)
plt.show()

第四个数字

在Numpy中操作张量

前面例子中,我们使用语法train_images[i]来选择沿着第一个轴的特定数字,选择张量的特定元素叫做张量切片(tensor slicing),我们来看一下numpy数组上的张量切片运算。
我们选择10-100个数字,并将其放在形状为(90,28,28)的数组中。

 my_slice = train_images[10:100]
print(my_slice.shape)
(90, 28, 28)

等同于下面这个复杂的写法。

my_slice = train_images[10:100,:,:]
my_slice.shape
(90, 28, 28)
my_slice = train_images[10:100,0:28,0:28]
my_slice.shape
(90, 28, 28)

可以沿着每个张量轴在任意两个索引之间进行选择,例如,你可以在所有图像的右下角选出14*14像素的区域:

my_slice = train_images[:,14:,14:]

也可以使用负数索引,它表示与当前轴终点的相对位置,可以在图像中心裁剪出14*14像素的区域:

my_slice = train_images[:,7:-7,7:-7]

数据批量的概念

通常来说,深度学习中所有数据张量的第一个轴(0轴)都是样本轴(samples axis),在MNIST中,样本就是数字图像。
此外,深度学习模型不会同时处理整个数据集,而是将数据拆分成小批量。具体来看,具体来看,下面是MNIST数据集的一个批量,批量大小为128。

batch = train_images[:128]  /*第一个批量*/
batch = train_images[128:256]    /*第二个批量*/

对于这种批量张量,第一个轴叫做批量轴(batch axis)或批量维度(batch dimension)。

现实世界中的数据张量

向量数据:2D张量,形状为(samples,features) 。
时间序列或序列数据:3D张量,形状为 (samples,timesteps,features) 。
图像:4D张量,形状为(samples,height,width,channels)或 (samples,channels,height,width) 。
视频:5D张量,形状为(samples,frames,height,width,channels)或(samples,frames,channels,height,width)。
下面分别介绍

向量数据

最常见的数据,第一个轴是样本轴,第二个轴是特征轴,举例子如下:
1.人口统计数据集,其中包括每个人的年龄、邮编和收入。每个人可以表示为包含3个值的向量,而整个数据集包含100000个人,因此可以存储在形状为(100000,3)的2D张量中。
2.文本文档数据集,我们将每个文档表示为每个单词在其中出现的次数(字典中包含20000个常见单词)。每个文档可以被编码为包含20000个值的向量,整个数据集包含500个文档,因此存储在形状为(500,20000)的张量中。

时间序列数据或序列数据

当时间(序列顺序)对于数据很重要时,应该将数据存储在带有时间轴的3D张量中。每个样本可以被编码为一个向量序列(即2D张量),因此一个数据批量被编码为一个3D张量。
一般来说,时间轴始终是第二个轴(索引为1的轴)
股票价格数据集。每一分钟,我们将股票的当前价格、前一分钟的最高价格和前一分钟的最低价格保存。因此每分钟被编码为一个3D向量,整个交易日被编码为一个形状为(390,3)的2D张量(一个交易日有390分钟),250天的数据则可以保存在一个形状为(250,390,3)的3D张量中,这里每个样本是一天的股票数据。

图像数据

图像通常有三个维度:高度、宽度和颜色深度。虽然灰度图像只有一个颜色通道,因此可以保存在2D张量中,但一般来说,图像张量始终都是3D张量,灰度图像的彩色通道只有一维。
若图像大小为256256,那么128张灰度图像组成的批量可以保存在一个形状为(128,256,256,1)的张量中,
若图像大小为256
256,那么128张彩色图像组成的批量可以保存在一个形状为(128,256,256,3)的张量中。

视频数据

视频数据是现实生活中需要用到5D张量的少数数据类型之一。视频可以看做一系列帧,每一帧都是一张彩色图像。由于每一帧可以保存在一个形状为(height,width,color_depth)的3D张量中,因此一系列帧可以保存在一个形状为(frames,height,width,color_depth)的4D张量中,而不同视频组成的批量则可以保存在一个5D张量中,其形状为(samples,frames,height,width,color_depth)。
举例:
每秒4帧采样的60秒视频片段,视频尺寸为144*256,这个视频共有240帧,4个这样的视频片段组成的批量将保存在形状为(4,240,144,256,3)的张量中。

神经网络的“齿轮”:张量运算

张量运算(tensor operation)例如加上张量、乘以张量。
output = relu(dot(w,input)+b)
我们将上面公式拆开来看,这里有三个张量运算:输入张量input和张量W之间的点积运算(dot)、得到的2D张量与向量b之间的加法运算(+)、最后的relu运算。relu(x)是max(x,0)。

逐元素运算

relu运算和加法都是逐元素运算,该运算独立的应用于张量中的每个元素,可以使用for循环来实现,在numpy中,这些运算都是优化好的numpy内置函数,这些函数将大量运算交给安装好的线性代数子程序(BLAS)。
因此,numpy中可以直接进行下列逐元素运算,速度较快。

import numpy as np
z = x + y
z = np.maximum(z,0.)

广播

如果两个形状不同的张量相加,如果没有歧义的话,较小的张量会被广播(broadcast)
举例:

x = np.array([[5,78,2,34,0],
                     [6,79,3,35,1],
                     [7,80,4,36,2]])
 y = np.array([6,7,8,9,10])
 z = x + y
 z
 array([[11, 85, 10, 43, 10],
           [12, 86, 11, 44, 11],
           [13, 87, 12, 45, 12]])

张量点积

点积运算也叫张量积(tensor product),最常见也最有用的张量运算。
若元素个数相同可进行点积运算,61+72+83+94+10*5=130

k = np.array([ 6,  7,  8,  9, 10])
s = np.array([1,2,3,4,5])
f = np.dot(k,s)
f
130

若矩阵*向量,返回值是一个向量,其中每个元素是y和x的每一行之间的点积。

x = np.array([[5,78,2,34,0],
             [6,79,3,35,1],
             [7,80,4,36,2]])
y = np.array([6,7,8,9,10])
z = np.dot(x,y)
z
array([898, 938, 978])

张量变形(tensor reshaping)

张量变形指的是改变张量的行和列,以得到想要的形状。变形后的张量的元素总个数与初始张量相同。

train_images = train_images.reshape((60000,28*28))

举个简单的例子:

x = np.array([[0,1],
              [2,3],
              [4,5]])
x.shape
(3, 2)
x = x.reshape((6,1))     /*6行1列*/
x
array([[0],
       [1],
       [2],
       [3],
       [4],
       [5]])
x = x.reshape((2,3))         /*2行3列*/
x
array([[0, 1, 2],
       [3, 4, 5]])

张量运算的几何解释

张量运算所操作的张量,元素可以被解释为某种几何空间内点的坐标,因此所有的张量运算都有几何解释。举例子:
A= [0.5,1] B=[1,0.25] A+B如图所示
张量运算的几何解释

深度学习的几何解释

神经网络属于高维空间中比较复杂的几何变换,这种变换可以通过许多简单地步骤来实现。
举一个三维变换的例子:
想象有两张彩纸:一张红色,一张蓝色。将其中一张纸放在另一张纸上。现在将两张纸一起揉成小球。
输入数据:就是揉成的皱巴巴的纸球,每张纸对应于分类问题中的一个类别。
神经网络要做的就是找到可以让纸球恢复平整的变换,深度学习可以用三维空间中一系列简单地变换来实现,比如你用手指对纸球做的变换,每次做一个动作。让纸球恢复平整就是机器学习的内容。

神经网络的“引擎”:基于梯度的优化

output = relu(dot(w,input)+b

在这个表达式中,w和b都是张量,均为该层的属性,被称为权重(weight)或可训练参数(trainable parameter)。
随机初始化:一开始,这些权重矩阵取较小的随机值。因为是随机的,所以得不到任何有用的表示。
训练:根据反馈信号逐渐调节这些权重。
上述过程发生在一个训练循环(training loop)内,具体过程如下:
1.抽取训练样本x和对应目标y组成的数据批量。
2.在x上运行网络【前向传播(forward pass)】,得到预测值y_pred。
3.计算网络在这批数据上的损失,用于衡量y_pred和y之间的距离。
4.更新网络的所有权重,使网络在这批数据上的损失略微下降。
难点在于第四步:可以利用网络中所有运算都是可微(differentiable)的这一事实,计算损失相对于网络系数的梯度(gradient),然后向梯度的反方向改变系数,从而使损失降低。
如果你已经了解可微和梯度这一概念,可以直接跳到## 随机梯度下降,如果不了解,下面两小节有助于你理解这些概念。

什么是导数

f在p点的导数
斜率a被称为f在p点的导数(derivative)。如果a是负数,说明x在p点附近的微小变化将导致f(x)减小;如果a是正数,那么x的微小变化将导致f(x)增大,a的绝对值表示增大或减小的速度快慢。
可微的意思是可以被求导。
如果我们想要将x改变一个小因子,目的是使f(X)最小化,并且知道f的导数,那么问题解决了:导数完全描述了改变X后f(x)如何变化,如果我希望减小f(x)的值,只需将x沿着导数的反方向移动一小步。

张量运算的导数:梯度

梯度是张量运算的导数,它是导数这一概念向多元函数导数的推广。假设有一个输入向量x、一个矩阵w、一个目标y和一个损失函数loss。用w计算预测值y_pred,然后计算损失----y_pred和y之间的距离,我们希望这个距离越小越好,所以转化为数学问题就是找最小值对应的参数。

y_pred = dot(w,x)
loss_value =loss(y_pred,y)
loss_value = f(w)

假设W的当前值是w0,f 在w0的导数是一个张量grandient(f)(w0),可以看做f(w)在w0附近曲率的张量。
对于张量的函数f(w),可以通过将w向梯度的反方向移动一小步来减小f(x)的值,比如w1 = w0-stepgradient(f)(w0),为什么是w0减去梯度呢?
在这里插入图片描述
如图所示如果要求函数的最小值,若斜率是负数则需要向右移动一小步,即加上一个数,若斜率是整数则需要向左移动一小步,即减上一个数,所以是w1 = w0-step
gradient(f)(w0)。

随机梯度下降

给定一个可微函数,理论上函数的最小值是导数为0的点,我们可以用解析法求出最小值,但是实际神经网络是无法求解的,因为参数的个数不会少于几千个,所以我们可以想另一个办法,一点点的对参数进行调节,直到找到最小值。
1.抽取训练样本x和对应目标y组成的数据批量。
2.在x上运行网络【前向传播(forward pass)】,得到预测值y_pred。
3.计算网络在这批数据上的损失,用于衡量y_pred和y之间的距离。
4.计算损失相对于网络参数的梯度【一次反向传播(backward pass)】。
5.将参数沿着梯度的反方向移动一点,比如w -= step * gradient,从而使这批数据上的损失减小一点。
这叫做小批量随机梯度下降(mini-batch stochastic gradient descent),也叫做小批量SGD。
沿着一维损失函数的随机梯度下降
如图看,选择合适的步长step是很重要得,取值太小需要很多次迭代,可能会走入局部最小值,取值太大,则更新权重后可能会出现在曲线上完全随机的位置。
注意:小批量SGD一个变体是每次只抽取一个样本和目标,叫做真SGD,还有一种,一次抽取全部样本和目标,这样计算代价高的多,所以要选择合理的批量大小。
SGD还有多种变体,区别在于计算下一次权重时还要考虑上一次权重更新,而不是仅仅考虑当前梯度值,比如带动SGD,Adagrad,RMSProp,这些变体被称为优化方法(optimization method)或优化器(optimizer),动量的概念尤其值得关注,它解决了SGD的两个问题:收敛速度和局部极小点。
动量思维源于物理学,可以将优化过程想象成一个小球从损失函数曲线上滚下来,如果小球动量足够大,它不会卡到峡谷里,可以达到全局最小点。
实现方法是每一步都移动小球,不仅考虑当前斜率(当前加速度),还要考虑当前速度(之前加速度)。
这在实践中就是更新参数w不仅要考虑当前梯度值,还要考虑上一次的参数更新。

链式求导:反向传播算法、

例如,下面这个网络f包含3个张量运算a,b,c还有三个权重矩阵w1,w2,w3
f(w1,w2,w3) = a(w1,c(w3)))
这种函数链可以通过下面这个恒等式进行求导,称为链式法则(chain rule):(f(g(x)))’ = f’(g(x)) * g’(x).
将链式法则应用于神经网络梯度值的计算,称为反向传播算法。
在tensorflow中,有梯度函数,反向传播调用函数即可,我们不会浪费时间在公式推导上,只需理解基于梯度的优化方法的工作原理。

回顾第一个例子

from keras.datasets import mnist
(train_images,train_labels), (test_images,test_labels)= mnist.load_data()
train_images = train_images.reshape((60000,28*28))
train_images = train_images.astype('float32')/255

test_images = test_images.reshape((10000,28*28))
test_images = test_images.astype('float32')/255

现在我们知道,输入图像保存在float32格式的numpy张量中,形状分别为(60000,784)(训练数据)和(10000,784)(测试数据)。下面构建网络。

from keras import models
from keras import layers
network = models.Sequential()
network.add(layers.Dense(512,activation='relu',input_shape=(28*28,)))
network.add(layers.Dense(10,activation='softmax'))

这个网络包含两个层,每层都对输入数据做了一些简单地运算。
下面是编译

network.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy'])

categorical_crossentropy是损失函数,在训练时候应该使其最小化,减小损失是通过小批量随机提速下降来实现的,梯度下降的具体方法由第一个参数给定,即rmsprop优化器。
最后是训练循环

network.fit(train_images,train_labels,epochs=5,batch_size=128)

调用fit,每个小批量包含128个样本,共迭代5次。

本章小结

1.学习是指找到一组模型参数,使得给定的训练数据样本和对应目标值上的损失函数最小化。
2.学习的过程:随机选取包含数据样本和目标值的批量,并计算批量损失相对于网络参数的梯度。随后将网络参数沿着梯度的反方向稍稍移动(移动距离由学习率指定)。
3.损失是在训练过程中需要最小化的量,因此,它应该能够衡量当前任务是否已经成功解决。
4.优化器是使用损失梯度更新参数的具体方式,比如RMSProp,SGD等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值