我们来看一个具体的神经网络示例,使用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()
# print(train_images.shape)
# print(len(train_labels))
# print(train_labels)
本例的网络包含2个Dense层,它们是密集连接的神经层。第二层是一个10路的softmax层,它将返回一个由10个概率值(总和为1)组成的数组。每个概率值表示当前数字图像属于10个数字类别中某一个的概率。
Dense层完成的具体操作后续会介绍到。
#搭建网络架构
from keras import models
from keras import layers
network = models.Sequential()
#该网络包含2个Dense层(全连接层)
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'])
#准备图像数据
#数据预处理,将unit8类型的图像变换为float32数组类型
#取值区间[0,255]→[0,1],形状(60000,28,28)→(60000,28*28)
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)
#训练模型
#调用网络的fit方法,在训练集上拟合(fit)模型
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%,比训练集精度低不少,为什么会这样呢?
这其实是过拟合造成的。过拟合是指机器学习模型在新数据上的性能往往比在训练数据上差,这是因为模型过于复杂、迭代次数过多,将训练集数据学习的太好了,以至于将训练集单个样本的细微特点都能捕捉到,并将其学到的分类方法作为“规律”运用在没有见过的新样本上,导致分类错误,模型泛化能力下降。
前面例子使用的数据存储在多维Numpy数组中,也叫张量(tensor)。
张量这一概念的核心在于,它是一个数据容器。它包含的数据几乎总是数值数据,它是数字的容器。张量的维度(dimension)通常叫作轴(axis)。张量轴的个数也叫作阶(rank)。
import numpy as np
x=np.array(12)#x是一个标量,x.ndim=0
x=np.array([12,3,6,14,7])#x是一个向量,x.ndim=1
这个向量有5个元素,所以又称为5D向量。注意5D向量≠5D张量。
x=np.array([5,78,2,34,0],
[6,79,3,35,1],
[7,80,4,36,2])#x是矩阵,x.ndim=2
张量具有以下三个关键属性:
在前面的例子中,我们可以使用语法train_images[i]来选择沿着第一个轴的特定数字。选择张量的特定元素叫作张量切片。
深度神经网络学到的所有变换都可以简化为数值数据张量上的一些张量运算。
在最开始的例子中,我们通过叠加Dense层来构建网络。
keras.layers.Dense(512,activation='relu')
#relu(dot(W,input)+b)
这个层可以理解为一个激活函数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.) #逐元素的 relu
#这里的maximum函数作用是X和Y逐位进行比较,选择最大值,最少接受两个参数
上述逐元素运算仅支持两个形状相同的2D张量相加。如果将两个形状不同的张量相加,例如前面说到的Dense层中2D张量与向量相加,会发生什么?
如果没有歧义的话,较小的张量会被广播,以匹配较大张量的形状。广播包含以下两步:
来看一个具体的例子。假设 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))#随机张量(64,3,32,10)
y = np.random.random((32,10))
z = np.maximum(x,y)#输出z的形状是(64,3,32,10),与x形状相同
点积(dot)运算,也叫张量积,与逐元素的运算不同,它将输入张量的元素合并在一起。
import numpy as np
z=np.dot(x,y)
图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)
我们的第一个神经网络示例中,每个神经层都用下述方法对输入数据进行变换。
output = relu(dot(W, input) + b)
在这个表达式中,w和b都是张量,被称为该层的权重或可训练参数,分别对应kernal和bias属性。
一开始,这些权重矩阵取较小的随机值,这一步叫作随机初始化。下一步则是根据反馈信号调节这些权重。
上述过程发生在一个训练循环内,
降低损失可以使用梯度下降的方法。
梯度是张量运算的导数。多元函数是以张量作为输入的函数。
假设网络中所有运算都是可微的,有一个输入向量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还有很多变体,其区别在于计算下一次权重更新时还要考虑上一次权重更新,而不是仅仅考虑当前梯度值,比如带动量的变体。
动量解决了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
现在,你应该对神经网络背后的原理有了大致的了解,可以回头再看一下第一个例子,重新阅读每一段代码。