《Deep Learning with Python 》由Keras之父、现任Google人工智能研究员的弗朗索瓦•肖莱(François Chollet)执笔,详尽介绍了用Python和Keras进行深度学习的探索实践,涉及计算机视觉、自然语言处理、生成式模型等应用。
本书以读书笔记的形式摘抄书中的重点,会加入一些自己的备注和理解(标红及斜体)。
编程语言:Python
深度学习框架:Keras
书籍下载:《Python 深度学习》
注: 这本书,偏重于快速上手, 本身用keras写深度学习代码,就是类似搭积木一般,对于深度学习原理各部分讲解不多。
如果想稍深入的了解深度学习中的反向传播算法、归一化、优化算法等问题, 还需要学习吴恩达的机器学习教程:
吴恩达老师的深度学习系列视频
吴恩达老师深度学习笔记整理
要理解深度学习,需要熟悉很多简单的数学概念:张量、张量运算、微分、梯度下降等。本章目的是用不那么技术化的文字帮你建立对这些概念的直觉。特别地,我们将避免使用数学符号,因为数学符号可能会令没有任何数学背景的人反感,而且对解释问题也不是绝对必要的。
神经网络示例,使用 Python 的 Keras 库来学习手写数字分类MNIST:
#1.加载 Keras 中的 MNIST 数据集
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
#查看训练数据
print(train_images.shape) #(60000, 28, 28)
print(len(train_labels)) #60000
print(train_labels) #array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)
#查看测试数据
print(test_images.shape) #(10000, 28, 28)
print(len(test_labels)) #10000
print(test_labels) #array([7, 2, 1, ..., 4, 5, 6], dtype=uint8)
#2.构建网络架构
from keras import models
from keras import layers
#本例中的网络包含 2 个 Dense 层,它们是密集连接(也叫全连接)的神经层。
#第二层(也是最后一层)是一个 10 路 softmax 层,
#它将返回一个由 10 个概率值(总和为 1)组成的数组。
#每个概率值表示当前数字图像属于 10 个数字类别中某一个的概率
network = models.Sequential()
network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,)))
network.add(layers.Dense(10, activation='softmax'))
要想训练网络,我们还需要选择编译(compile)步骤的三个参数。
#3.编译步骤
network.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy'])
在开始训练之前,我们将对数据进行预处理,将其变换为网络要求的形状,并缩放到所有值都在 [0, 1] 区间。比如,之前训练图像保存在一个 uint8 类型的数组中,其形状为(60000, 28, 28),取值区间为 [0, 255]。我们需要将其变换为一个 float32 数组,其形状为 (60000, 28 * 28),取值范围为 0~1。 即归一化处理
#4.准备图像数据
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
#5.准备标签
from keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
现在我们准备开始训练网络,在 Keras 中这一步是通过调用网络的 fit 方法来完成的——我们在训练数据上拟合(fit)模型。
#6.训练数据
network.fit(train_images, train_labels, epochs=5, batch_size=128)
output:
Epoch 1/5
60000/60000 [=============================] - 9s - loss: 0.2524 - acc: 0.9273
Epoch 2/5
51328/60000 [=======================>.....] - ETA: 1s - loss: 0.1035 - acc: 0.9692
#训练过程中显示了两个数字:
#一个是网络在训练数据上的损失(loss),另一个是网络在训练数据上的精度(acc)
等训练完成,在训练数据上达到了 0.989(98.9%)的精度。
test_loss, test_acc = network.evaluate(test_images, test_labels)
print('test_acc:', test_acc)
output:test_acc: 0.9785
测试集精度为 97.8%,比训练集精度低不少。训练精度和测试精度之间的这种差距是过拟合(overfit)造成的。
一般来说,当前所有机器学习系统都使用张量作为基本数据结构。张量对这个领域非常重要,重要到 Google 的TensorFlow 都以它来命名。
张量这一概念的核心在于,它是一个数据容器。它包含的数据几乎总是数值数据,因此它是数字的容器。你可能对矩阵很熟悉,它是二维张量。张量是矩阵向任意维度的推广。
注意,张量的维度(dimension)通常叫作轴(axis),张量轴的个数也叫做阶(rank),这在 Numpy 等 Python 库中也叫张量的 ndim,可通过ndim属性查看轴的个数。
print(train_images.ndim) #3
print(train_images.shape) # (60000, 28, 28)
print(train_images.dtype) #uint8
选择张量的特定元素叫作张量切片(tensor slicing):
print(train_images[10:100]) #(90, 28, 28)
通常来说,深度学习中所有数据张量的第一个轴(0 轴,因为索引从 0 开始)都是样本轴(samples axis,有时也叫样本维度)
#第 n 个批量。
batch = train_images[128 * n:128 * (n + 1)]
对于这种批量张量,第一个轴(0 轴)叫作批量轴(batch axis)或批量维度(batch dimension)。
relu 运算和加法都是逐元素的运算,即该运算独立地应用于张量中的每个元素,也就是说,这些运算非常适合大规模并行实现(向量化实现),避免使用for循环。
在实践中处理 Numpy 数组时,这些运算都是优化好的 Numpy 内置函数,这些函数将大量运算交给安装好的基础线性代数子程序(BLAS, basic linear algebra subprograms)实现,BLAS 是低层次的、高度并行的、高效的张量操作程序,通常用 Fortran或 C 语言来实现。
如果将两个形状不同的张量相加,会发生什么?
如果没有歧义的话,较小的张量会被广播(broadcast),以匹配较大张量的形状。广播包含以下两步。
(1) 向较小的张量添加轴(叫作广播轴),使其 ndim 与较大的张量相同。
(2) 将较小的张量沿着新轴重复,使其形状与较大的张量相同。
点积运算,也叫张量积(tensor product,不要与逐元素的乘积弄混),是最常见也最有用的张量运算。
在 Numpy 和 Keras 中,都是用标准的 dot 运算符来实现点积:
import numpy as np
z = np.dot(x, y)
数学符号中的点(.)表示点积运算:
z=x.y
点积可以推广到具有任意个轴的张量。最常见的应用可能就是两个矩阵之间的点积。
对于两个矩阵 x 和 y,当且仅当 x.shape[1] == y.shape[0] 时,你才可以对它们做点积(dot(x, y))。得到的结果是一个形状为 (x.shape[0], y.shape[1]) 的矩阵,其元素为 x的行与 y 的列之间的点积。如下:
更一般地说,你可以对更高维的张量做点积,只要其形状匹配遵循与前面 2D 张量相同的原则:
(a, b, c, d) . (d,) -> (a, b, c)
(a, b, c, d) . (d, e) -> (a, b, c, e)
张量变形是指改变张量的行和列,以得到想要的形状。变形后的张量的元素总个数与初始张量相同。
x = np.array([[0., 1.],[2., 3.],[4., 5.]])
print(x.shape) #(3, 2)
x = x.reshape((6, 1))
print(x)
# array([[ 0.],[ 1.],[ 2.],[ 3.],[ 4.],[ 5.]])
x = x.reshape((2, 3))
print(x)
#array([[ 0., 1., 2.],[ 3., 4., 5.]])
一种特殊的张量变形是转置(transposition)。对矩阵做转置是指将行和列互换,使 x[i, :] 变为 x[:, i]。常用,尤其线性回归的正规方程
x = np.zeros((300, 20))
x = np.transpose(x)
print(x.shape) #(20, 300)
对于张量运算所操作的张量,其元素可以被解释为某种几何空间内点的坐标,因此所有的张量运算都有几何解释。如两个张量相加:
通常来说,仿射变换、旋转、缩放等基本的几何操作都可以表示为张量运算。
神经网络完全由一系列张量运算组成,而这些张量运算都只是输入数据的几何变换。因此,你可以将神经网络解释为高维空间中非常复杂的几何变换,这种变换可以通过许多简单的步骤来实现。
想象有两张彩纸:一张红色,一张蓝色。将其中一张纸放在另一张上。现在将两张纸一起揉成小球。这个皱巴巴的纸球就是你的输入数据,每张纸对应于分类问题中的一个类别。神经网络(或者任何机器学习模型)要做的就是找到可以让纸球恢复平整的变换,从而能够再次让两个类别明确可分。
深度学习将复杂的几何变换逐步分解为一长串基本的几何变换,这与人类展开纸球所采取的策略大致相同。深度网络的每一层都通过变换使数据解开一点点——许多层堆叠在一起,可以实现非常复杂的解开过程。
如果按照sklearn中网格搜索的方式,设定参数的值,进行神经网络的前向传播,计算损失值,可这样计算量巨大,并且需要人工参与。
一种更好的方法是利用网络中所有运算都是可微(differentiable)的这一事实,计算损失相对于网络系数的梯度(gradient),然后向梯度的反方向改变系数,从而使损失降低。
对于每个可微函数 f(x)(可微的意思是“可以被求导”。例如,光滑的连续函数可以被求导),都存在一个导数函数 f’(x),将 x 的值映射为 f 在该点的局部线性近似的斜率(导数)。例如, cos(x)的导数是 -sin(x), f(x) = a * x 的导数是 f’(x) = a,等等。 严格的数学定义,是采用双边逼近的思想
梯度(gradient)是张量运算的导数。它是导数这一概念向多元函数导数的推广。多元函数是以张量作为输入的函数。
单变量函数 f(x) 的导数可以看作函数 f 曲线的斜率。同样, gradient(f)
(W0) 也可以看作表示 f(W) 在 W0 附近曲率(curvature)的张量。
用解析法求出最小损失函数对应的所有权重值。可以通过对方程 gradient(f)(W) = 0 求解 W 来实现这一方法。
为 step 因子(步长)选取合适的值是很重要的。如果取值太小,则沿着曲线的下降需要很多次迭代,而且可能会陷入局部极小点。如果取值太大,则更新权重值之后可能会出现在曲线上完全随机的位置。
吴恩达教程中每批量b建议在[2,100],一般取10,但是在实际操作中,一般用batch size表示,GPU对2的幂次的batch可以发挥更佳的性能,一般都取64等,具体要视具体情况而定
此外, SGD 还有多种变体,其区别在于计算下一次权重更新时还要考虑上一次权重更新,而不是仅仅考虑当前梯度值,比如带动量的 SGD、 Adagrad、 RMSProp 等变体。这些变体被称为优化方法(optimization method)或优化器(optimizer)。其中动量的概念尤其值得关注,它在许多变体中都有应用。动量解决了 SGD 的两个问题:收敛速度和局部极小点。
动量的思想:灵感来源于物理学。有一种有用的思维图像,就是将优化过程想象成一个小球从损失函数曲线上滚下来。如果小球的动量足够大,那么它不会卡在峡谷里,最终会到达全局最小点。动量方法的实现过程是每一步都移动小球,不仅要考虑当前的斜率值(当前的加速度),还要考虑当前的速度(来自于之前的加速度)。这在实践中的是指,更新参数 w 不仅要考虑当前的梯度值,还要考虑上一次的参数更新。
优化算法在吴恩达的深度学习课程中有专门细讲,感兴趣的朋友可以参考:
优化算法
链式法则(chain rule): (f(g(x)))’ = f’(g(x)) * g’(x)。将链式法则应用于神经网络梯度值的计算,得到的算法叫作反向传播(backpropagation,有时也叫反式微分, reverse-mode differentiation)。反向传播从最终损失值开始,从最顶层反向作用至最底层,利用链式法则计算每个参数对损失值的贡献大小。
人们将使用能够进行符号微分(symbolic differentiation)的现代框架来实现神经网络,比如 TensorFlow。也就是说,给定一个运算链,并且已知每个运算的导数,这些框架就可以利用链式法则来计算这个运算链的梯度函数,将网络参数值映射为梯度值。