1.第一个神经网络示例
我们来看一个具体的神经网络示例,使用Python的Keras库来学习手写数字分类。
这里要解决的问题是,将手写数字的灰度图像(28像素x28像素)划分到10个类别中(0~9)。我们将使用MNIST数据集(MNIST数据集预先加载在Keras库中,包括4个Numpy数组),它是机器学习领域的一个经典数据集,包含60000张训练图像和10000张测试图像。
接下来的工作流程如下:
- 首先,加载Keras中的MNIST数据集,得到训练集和测试集。
- 其次,将训练数据输入神经网络。
- 然后,网络学习将数据和标签(数据对应的类别)关联在一起。
- 最后,网络对输入的测试数据生成预测,并验证这些预测值与真实值是否匹配。
from keras.datasets import mnist
(train_images,train_labels),(test_images,test_labels) = mnist.load_data()
本例的网络包含2个Dense层,它们是密集连接的神经层。第二层是一个10路的softmax层,它将返回一个由10个概率值(总和为1)组成的数组。每个概率值表示当前数字图像属于10个数字类别中某一个的概率。
Dense层完成的具体操作后续会介绍到。
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'))
编译时需要人为设置的三个参数:
- 损失函数loss
- 优化器optimizer
- 在训练和测试过程中需要监控的指标metric
后续两章会详细解释损失函数和优化器的确切用途。
network.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
train_images = train_images.reshape((60000,28*28))
train_image = train_images.astype('float32')/255
test_images = test_images.reshape((60000,28*28))
test_image = test_images.astype('float32')/255
from keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
network.fit(train_images,train_labels,epochs=5,batch_size=128)
test_loss,test_acc=network.evaluate(test_images,test_labels)
print('test_acc:',test_acc)
模型经过训练,在训练集上的精度达到98.9%,而测试集上的精度为97.8%,比训练集精度低不少,为什么会这样呢?
这其实是过拟合造成的。过拟合是指机器学习模型在新数据上的性能往往比在训练数据上差,这是因为模型过于复杂、迭代次数过多,将训练集数据学习的太好了,以至于将训练集单个样本的细微特点都能捕捉到,并将其学到的分类方法作为“规律”运用在没有见过的新样本上,导致分类错误,模型泛化能力下降。
2.张量与张量运算
前面例子使用的数据存储在多维Numpy数组中,也叫张量(tensor)。
张量这一概念的核心在于,它是一个数据容器。它包含的数据几乎总是数值数据,它是数字的容器。张量的维度(dimension)通常叫作轴(axis)。张量轴的个数也叫作阶(rank)。
张量(输入网络的数据存储对象)
- 标量(0D张量):仅包含一个数字的张量。标量有0个轴(ndim==0)。一个数字就是一个标量,例如
import numpy as np
x=np.array(12)
- 向量(1D张量):数字组成的数组。向量只有1个轴。例如
x=np.array([12,3,6,14,7])
这个向量有5个元素,所以又称为5D向量。注意5D向量≠5D张量。
- 矩阵(2D张量):向量组成的数组。矩阵有2个轴(通常叫作行和列),第一个轴上的元素叫行,第二个轴上的元素叫列。例如
x=np.array([5,78,2,34,0],
[6,79,3,35,1],
[7,80,4,36,2])
- 3D张量与更高维张量
3D张量可以直观理解为数字组成的立方体,将多个3D张量组合成数组,可以创建4D张量,以此类推。深度学习处理的一般是0D到4D的张量,但处理视频时可能会遇到5D张量。
张量具有以下三个关键属性:
- 轴的个数(ndim):例如3D张量有3个轴,矩阵有2个。
- 形状(shape):表示张量沿每个轴的元素个数(维度大小),是一个整元数组。例如标量的形状为空();向量的形状(5,);矩阵的形状(3,5)。
- 数据类型(dtype):张量中所包含数据的类型。注意Numpy(以及大多数库)中不存在字符型张量。
在前面的例子中,我们可以使用语法train_images[i]来选择沿着第一个轴的特定数字。选择张量的特定元素叫作张量切片。
张量运算(层的组成要素)
深度神经网络学到的所有变换都可以简化为数值数据张量上的一些张量运算。
在最开始的例子中,我们通过叠加Dense层来构建网络。
keras.layers.Dense(512,activation='relu')
这个层可以理解为一个激活函数relu,输入一个2D张量,返回另一个2D张量,即输入张量的新表示。
激活函数relu(dot(W,input)+b)中的w是一个2D张量,b是一个向量,这里有三个张量运算:输入张量和张量w之间的点积(dot)、得到的2D张量与向量b之间的加法运算、最后的relu运算,relu(x)是max(x,0)。
关于激活函数relu的介绍,可以学习以下链接: https://www.jianshu.com/p/338afb1389c9 简言之,激活函数能帮助我们引入非线性因素,使得神经网络能够更好地解决更加复杂的问题。
逐元素运算
relu运算和加法都是逐元素运算,即该运算独立地应用于张量中的每个元素。
import numpy as np
z = x + y
z = np.maximum(z, 0.)
广播
上述逐元素运算仅支持两个形状相同的2D张量相加。如果将两个形状不同的张量相加,例如前面说到的Dense层中2D张量与向量相加,会发生什么?
如果没有歧义的话,较小的张量会被广播,以匹配较大张量的形状。广播包含以下两步:
- 向较小的张量添加轴(叫作广播轴),使其 ndim 与较大的张量相同。
- 将较小的张量沿着新轴重复,使其形状与较大的张量相同。
来看一个具体的例子。假设 X 的形状是 (32, 10),y 的形状是 (10,)。首先,我们给 y添加空的第一个轴,这样 y 的形状变为 (1, 10)。然后,我们将 y 沿着新轴重复 32 次,这样 得到的张量 Y 的形状为 (32, 10)。现在, 我们可以将 X 和 Y 相加,因为它们的形状相同。
在实际的实现过程中并不会创建新的2D张量,因为那样做非常低效。重复的操作完全是虚拟的,它只出现在算法中,而没有发生在内存中。
下面这个例子利用广播将逐元素的maximum运算应用于两个形状不同的张量:
import numpy as np
x = np.random.random((64,3,32,10))
y = np.random.random((32,10))
z = np.maximum(x,y)
张量点积
点积(dot)运算,也叫张量积,与逐元素的运算不同,它将输入张量的元素合并在一起。
import numpy as np
z=np.dot(x,y)
- 两个向量之间的点积结果是一个标量,且只有元素个数相同的向量之间才能做点积。
- 你还可以对一个矩阵和一个向量做点积,返回值是一个向量,其中每个元素是y和x的每一行之间的点积。
- 最常见的应用是两个矩阵之间的点积。 对于两个矩阵 x 和 y,当且仅当 x.shape[1] == y.shape[0] 时,你才可以对它们做点积(dot(x, y))。得到的结果是一个形状为 (x.shape[0], y.shape[1]) 的矩阵。
图2-5中,x、y和z都用矩形表示。x的行必须和y的列大小相同。
更一般地说,你可以对更高维的张量做点积,只要其形状匹配遵循与前面 2D 张量相同的原则: (a, b, c, d) . (d,) -> (a, b, c) (a, b, c, d) . (d, e) -> (a, b, c, e)
注意:如果两个张量中有一个的ndim大于1,那么dot运算就不再是对称的,dot(x,y)不等于dot(y,x)。
张量变形
虽然前面神经网络第一个例子的Dense层中没有用到它,但在将图像数据输入神经网络之前,我们在预处理时用到了这个运算。
train_images=train_images.reshape((60000,28*28))
张量变形是改变张量的行和列,以得到想要的形状。变形后的张量的元素总个数与初始张量相同。
一种特殊的张量变形是转置。
x=np.zeros((300,20))
x=np.transpose(x)
3.神经网络如何通过反向传播与梯度下降进行学习
我们的第一个神经网络示例中,每个神经层都用下述方法对输入数据进行变换。
output = relu(dot(W, input) + b)
在这个表达式中,w和b都是张量,被称为该层的权重或可训练参数,分别对应kernal和bias属性。
一开始,这些权重矩阵取较小的随机值,这一步叫作随机初始化。下一步则是根据反馈信号调节这些权重。
上述过程发生在一个训练循环内,
- 提取数据:抽取训练样本 x 和对应目标 y 组成的数据批量。
- 前向传播:在 x 上运行网络(这一步叫作前向传播(forward pass)),得到预测值 y_pred。
- 计算损失:计算网络在这批数据上的损失,用于衡量 y_pred 和 y 之间的距离。
- 反向传播:更新网络的所有权重,使网络在这批数据上的损失略微下降。
降低损失可以使用梯度下降的方法。
梯度是张量运算的导数。多元函数是以张量作为输入的函数。
假设网络中所有运算都是可微的,有一个输入向量x、一个矩阵w、一个目标y和一个损失函数loss。你可以用w来计算预测值y_pred,然后计算损失,或者说预测值y_pred和目标y之间的距离。
y_pred = dot (w,x)
loss_value = loss(y_pred, y)
如果输入数据x和y保持不变,那么这可以看作将w映射到损失值的函数。
loss_value = f(w)
假设w的当前值为w0。f在w0点的导数是一个张量gradient(f)(w0),其形状与w相同。张量gradient (f) (w0)是函数f(w) = loss_value在w0的导数。
对于一个函数f(x),你可以通过将x向导数的反方向移动一小步来减小f(x)的值。同样,对于张量的函数f(w),你也可以通过将w向梯度的反方向移动来减小f(w),比如w1 = w0 - step *gradient (f)(w0),其中step是一个很小的比例因子,又叫学习率。也就是说,沿着曲率的反方向移动,直观上来看在曲线上的位置会更低。
注意,step是人为设置的,范围为0~1,刚开始训练时学习率以 0.01 ~ 0.001 为宜,这是为了防止步长太长而错过损失函数loss的最低点。
随机梯度下降
随机梯度中的随机是什么意思? 术语随机(stochastic)是指每批数据都是随机抽取的。
- 小批量随机梯度下降(SGD)是每次抽取批量数据。
小批量随机梯度下降: (1)抽取训练样本x和对应目标y组成的数据批量。 (2)在x上运行网络,得到预测值y_pred。 (3)计算网络在这批数据上的损失,用于衡量y_pred和y之间的距离。 (4)计算损失相对于网络参数的梯度(一次反向传播) (5)将参数沿着梯度的反方向移动一点,比如w -= step * gradient,从而使这批数据上的损失减小一点。 - 真 SGD(有别于小批量 SGD)是小批量SGD 算法的一个变体,是每次迭代时只抽取一个样本和目标,而不是抽取一批数据。
- 还有另一种极端,每一次迭代都在所有数据上 运行,这叫作批量 SGD。这样做的话,每次更新都更加准确,但计算代价也高得多。
SGD还有很多变体,其区别在于计算下一次权重更新时还要考虑上一次权重更新,而不是仅仅考虑当前梯度值,比如带动量的变体。
动量解决了SGD的两个问题:收敛速度和局部极小点。动量方法的实现过程是每一步更新w不仅要考虑当前的梯度值,还要考虑上一次参数的更新。如图所示,在某个参数值附近,有一个局部极小点,使用动量的方法可以避免陷入局部极小点。
链式求导:反向传播算法
在前面的算法中,我们假设函数是可微的,因此可以明确计算其导数。在实践中,神经网络函数包含许多连接在一起的张量运算,每个运算都有简单的、已知的导数。例如,下面这个网络f包含3个张量运算a、b和c,还有3个权重矩阵w1、w2和 w3。
f(w1,w2,w3) = a(w1,b(W2,c(W3)))
根据微积分的知识,这种函数链可以利用下面这个恒等式进行求导,它称为链式法则:
(f(g(x) ) ) ’ = f '(g(x)) * g '(x)
将链式法则应用于神经网络梯度值的计算,得到的算法叫作反向传播( backpropagation)。反向传播从最终损失值开始,从最顶层反向作用至最底层,利用链式法则计算每个参数对损失值的贡献大小。
反向传播算法原理: https://www.jianshu.com/p/964345dddb70
现在,你应该对神经网络背后的原理有了大致的了解,可以回头再看一下第一个例子,重新阅读每一段代码。
|