本文内容提炼于《Python深度学习》一书,整合了前 4 章的内容。
人工智能包含机器学习,而深度学习是机器学习的一个分支。机器学习只能用来记忆训练数据中存在的模式。只能识别出曾经见过的东西。在过去的数据上训练机器学习来预测未来,这里存在一个假设,就是未来的规律与过去相同。
在经典的程序设计(即符号主义人工智能的范式)中,人们输入的是规则(即程序)和需要根据这些规则进行处理的数据,系统输出的是答案。而利用机器学习,人们输入的是数据和从这些数据中预期得到的答案,系统输出的则是规则。这些规则随后可应用于新的数据,并使计算机自主生成答案。
一、深度学习
深度学习是机器学习的一个分支领域,它是从数据中学习表示的一种新方法,强调从连续的层(layer)中进行学习,这些层对应于越来越有意义的表示。在深度学习中,这些分层表示几乎总是通过叫作神经网络(neural network)的模型来学习得到的,神经网络的结构是逐层堆叠。如下图所示,一个多层网络如何对数字图像进行变换,以便识别图像中所包含的数字。
在下图中,这个网络将数字图像转换成与原始图像差别越来越大的表示,而其中关于最终结果的信息却越来越丰富。你可以将深度网络看作多级信息蒸馏操作:信息穿过连续的过滤器,其纯度越来越高(即对任务的帮助越来越大)。
深度学习从数据中进行学习时有两个基本特征:第一,通过渐进的、逐层的方式形成越来越复杂的表示;第二,对中间这些渐进的表示共同进行学习,每一层的变化都需要同时考虑上下两层的需要。
1)工作原理
下图描绘了深度学习的工作原理,神经网络中每层对输入数据所做的具体操作保存在该层的权重(weight)中,其本质是一串数字。想要控制神经网络的输出,就需要能够衡量该输出与预期值之间的距离。这是神经网络损失函数(loss function)的任务,该函数也叫目标函数(objective function)。损失函数的输入是网络预测值与真实目标值(即你希望网络输出的结果),然后计算一个距离值,衡量该网络在这个示例上的效果好坏。深度学习的基本技巧是利用这个距离值作为反馈信号来对权重值进行微调,以降低当前示例对应的损失值。这种调节由优化器(optimizer)来完成,它实现了所谓的反向传播(backpropagation)算法,这是深度学习的核心算法。
一开始对神经网络的权重随机赋值,因此网络只是实现了一系列随机变换。其输出结果自然也和理想值相去甚远,相应地,损失值也很高。但随着网络处理的示例越来越多,权重值也在向正确的方向逐步微调,损失值也逐渐降低。这就是训练循环(training loop),将这种循环重复足够多的次数(通常对数千个示例进行数十次迭代),得到的权重值可以使损失函数最小。具有最小损失的网络,其输出值与目标值尽可能地接近,这就是训练好的网络。这虽然是一个简单的机制,但是一旦具有足够大的规模,将会产生魔法般的效果。
2)突破
深度学习已经取得了以下突破,它们都是机器学习历史上非常困难的领域:
- 接近人类水平的图像分类
- 接近人类水平的语音识别
- 接近人类水平的手写文字转录
- 更好的机器翻译
- 更好的文本到语音转换
- 接近人类水平的自动驾驶
- 更好的广告定向投放,Google、百度、必应都在使用
- 更好的网络搜索结果
- 能够回答用自然语言提出的问题
- 在围棋上战胜人类
二、数学基础
要理解深度学习,需要熟悉很多简单的数学概念:张量、张量运算、微分、梯度下降等。我们来看一个具体的神经网络示例,使用 Python 的 Keras 库来学习手写数字分类。这里要解决的问题是,将手写数字的灰度图像(28 像素×28 像素)划分到 10 个类别中(0~9)。我们将使用 MNIST 数据集,它是机器学习领域的一个经典数据集。
Keras 是一个模型级(model-level)的库,为开发深度学习模型提供了高层次的构建模块。它不处理张量操作、求微分等低层次的运算。相反,它依赖于一个专门的、高度优化的张量库来完成这些运算,这个张量库就是 Keras 的后端引擎(backend engine)。目前,Keras 有三个后端实现:TensorFlow、Theano 和微软认知工具包(CNTK,Microsoft cognitive toolkit)。
首先由 train_images 和 train_labels 组成训练集(training set),模型将从这些数据中进行学习。然后在测试集(test set,即 test_images 和 test_labels)上对模型进行测试。
from keras.datasets import mnist (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
接下来的工作流程如下:首先,将训练数据(train_images 和 train_labels)输入神经网络;其次,网络学习将图像和标签关联在一起;最后,网络对 test_images 生成预测,而我们将验证这些预测与 test_labels 中的标签是否匹配。下面我们来构建网络。
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'))
神经网络的核心组件是层(layer),它是一种数据处理模块,你可以将它看成数据过滤器。进去一些数据,出来的数据变得更加有用。大多数深度学习都是将简单的层链接起来,从而实现渐进式的数据蒸馏(data distillation)。深度学习模型就像是数据处理的筛子,包含一系列越来越精细的数据过滤器(即层)。
本例中的网络包含 2 个 Dense 层,它们是密集连接(也叫全连接)的神经层。第二层(也是最后一层)是一个 10 路 softmax 层,它将返回一个由 10 个概率值(总和为 1)组成的数组。 每个概率值表示当前数字图像属于 10 个数字类别中某一个的概率。
要想训练网络,我们还需要选择编译(compile)步骤的三个参数:
- 损失函数(loss function):网络如何衡量在训练数据上的性能,即网络如何朝着正确的方向前进。
- 优化器(optimizer):基于训练数据和损失函数来更新网络的机制。
- 在训练和测试过程中需要监控的指标(metric):本例只关心精度,即正确分类的图像所占的比例。
network.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
在开始训练之前,我们将对数据进行预处理,将其变换为网络要求的形状,并缩放到所 有值都在 [0, 1] 区间。比如,之前训练图像保存在一个 uint8 类型的数组中,其形状为(60000, 28, 28),取值区间为[0, 255]。我们需要将其变换为一个 float32 数组,其形状为(60000, 28 * 28),取值范围为 0~1。
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 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%)的精度。测试集精度为 97.8%,比训练集精度低不少。训练精度和测试精度之间的这种差距是过拟合(overfit)造成的。过拟合是指机器学习模型在新数据上的性能往往比在训练数据上要差。
训练好网络之后,你希望将其用于实践。你可以用 predict 方法对未见过的数据进行预测。
network.predict(x_test)
你刚刚看到了如何构建和训练一个神经网络,用不到 20 行的 Python 代码对手写数字进行分类。
1)张量
当前所有机器学习系统都使用张量(tensor)作为基本数据结构。张量对这个领域非常重要,重要到 Google 的 TensorFlow 都以它来命名。张量这一概念的核心在于,它是一个数据容器。它包含的数据几乎总是数值数据,因此它是数字的容器。你可能对矩阵很熟悉,它是二维张量。
- 仅包含一个数字的张量叫作标量(scalar),也叫标量张量、零维张量、0D 张量。
- 数字组成的数组叫作向量(vector)或一维张量(1D 张量),一维张量只有一个轴。
- 向量组成的数组叫作矩阵(matrix)或二维张量(2D 张量),矩阵有 2 个轴(通常叫作行和列),形状为:
- (samples, features)
- 人口统计数据集,其中包括每个人的年龄、邮编和收入。每个人可以表示为包含 3 个值的向量,而整个数据集包含 100 000 个人,因此可以存储在形状为 (100000, 3) 的 2D 张量中。
- (samples, features)
- 将多个矩阵组合成一个新的数组,可以得到一个 3D 张量,可以将其直观地理解为数字组成的立方体,形状为:
- (samples, timesteps, features)
- 每条推文编码为 280 个字符组成的序列,而每个字符又来自于 128 个字符组成的字母表,包含 100 万条推文的数据集则可以存储在一个形状为 (1000000, 280, 128) 的张量中。
- (samples, timesteps, features)
- 将多个 3D 张量组合成一个数组,可以创建一个 4D 张量,形状为:
- (samples, height, width, channels)
- 如果图像大小为 256×256,那么 128 张灰度图像将保存在形状为 (128, 256, 256, 1) 的张量
- (samples, channels, height, width)
- (samples, height, width, channels)
- 以此类推。深度学习处理的一般 是 0D 到 4D 的张量,但处理视频数据时可能会遇到 5D 张量,形状为:
- (samples, frames, height, width, channels)
- 一个以每秒 4 帧采样的 60 秒视频片段,尺寸为 144×256,共有 240 帧,4 个这样的视频片段将保存在形状为 (4, 240, 144, 256, 3) 的张量中
- (samples, frames, channels, height, width)
- (samples, frames, height, width, channels)
张量是由以下三个关键属性来定义的:
- 轴的个数(阶)。例如,3D 张量有 3 个轴,矩阵有 2 个轴。这在 Numpy 等 Python 库中也叫张量的 ndim。
- 形状。这是一个整数元组,表示张量沿每个轴的维度大小(元素个数)。例如,前面矩阵示例的形状为 (3, 5),3D 张量示例的形状为 (3, 3, 5)。向量的形状只包含一个元素,比如 (5,),而标量的形状为空,即 ()。
- 数据类型(在 Python 库中通常叫作 dtype)。这是张量中所包含数据的类型,例如,张量的类型可以是 float32、uint8、float64 等。在极少数情况下,你可能会遇到字符(char)张量。
2)张量运算
深度神经网络学到的所有变换也都可以简化为数值数据张量上的一些张量运算(tensor operation),例如加上张量、乘以张量等。在最开始的例子中,我们通过叠加 Dense 层来构建网络。
keras.layers.Dense(512, activation='relu')
这个层可以理解为一个函数,输入一个 2D 张量,返回另一个 2D 张量,即输入张量的新表示。具体而言,这个函数如下所示,其中 W 是一个 2D 张量,b 是一个向量,二者都是该层的属性。
output = relu(dot(W, input) + b)
这里有三个张量运算:输入张量和张量 W 之间的点积运算(dot)、得到的 2D 张量与向量 b 之间的加法运算(+)、最后的 relu 运算。relu(x) 是 max(x, 0)。relu 运算和加法都是逐元素(element-wise)的运算,即该运算独立地应用于张量中的每个元素。
W 和 b 都是张量,均为该层的属性。它们被称为该层的权重(weight)或可训练参数(trainable parameter),分别对应 kernel 和 bias 属性。一开始,这些权重矩阵取较小的随机值,这一步叫作随机初始化(random initialization)。虽然得到的表示是没有意义的,但这是一个起点。下一步则是根据反馈信号逐渐调节这些权重,这 个逐渐调节的过程叫作训练,也就是机器学习中的学习。
3)张量变形
张量变形(tensor reshaping)是指改变张量的行和列,以得到想要的形状。变形后的张量的元素总个数与初始 张量相同。
train_images = train_images.reshape((60000, 28 * 28))
对于张量运算所操作的张量,其元素可以被解释为某种几何空间内点的坐标,因此所有的张量运算都有几何解释。假设有这样两个点:A = [0.5, 1] 和 B = [1, 0.25],将 B 与 A 相加。从几何上来看,相当于将两个向量箭头连在一起,得到的位置表示两个向量之和对应的向量。
通常来说,仿射变换、旋转、缩放等基本的几何操作都可以表示为张量运算。你可以将神经网络解释为高维空间中非常复杂的几何变换,这种变换可以通过许多简单的步骤来实现。
神经网络(或者任何机器学习模型)要做的就是找到可以让纸球恢复平整的变换,从而能够再次让两个类别明确可分。通过深度学习,这一过程可以用三维空间中一系列简单的变换来实现,比如你用手指对纸球做的变换,每次做一个动作。深度网络的每一层都通过变换使数据解开一点点——许多层堆叠在一起,可以实现非常复杂的解开过程。
三、机器学习基础
二分类问题、多分类问题和标量回归问题。这三者都是监督学习(supervised learning)的例子,其目标是学习训练输入与训练目标之间的关系。监督学习是目前最常见的机器学习类型。给定一组样本(通常由人工标注),它可以学会将输入数据映射到已知目标,也叫标注(annotation)。一般来说,近年来广受关注的深度学习应用几乎都属于监督学习,比如光学字符识别、语音识别、图像分类和语言翻译。
1)数据预处理
数据预处理的目的是使原始数据更适于用神经网络处理,包括向量化、标准化、处理缺失值和特征提取。
神经网络的所有输入和目标都必须是浮点数张量(在特定情况下可以是整数张量)。无论处理什么数据(声音、图像还是文本),都必须首先将其转换为张量,这一步叫作数据向量化(data vectorization)。
一般来说,将取值相对较大的数据(比如多位整数,比网络权重的初始值大很多)或异质数据(heterogeneous data,比如数据的一个特征在 0~1 范围内,另一个特征在 100~200 范围内)输入到神经网络中是不安全的。这么做可能导致较大的梯度更新,进而导致网络无法收敛。为了让网络的学习变得更容易,输入数据应该具有以下特征:
- 取值较小:大部分值都应该在 0~1 范围内。
- 同质性(homogenous):所有特征的取值都应该在大致相同的范围内。
此外,下面这种更严格的标准化方法也很常见,而且很有用,虽然不一定总是必需的(例如,对于数字分类问题就不需要这么做)。
- 将每个特征分别标准化,使其平均值为 0。
- 将每个特征分别标准化,使其标准差为 1。
一般来说,对于神经网络,将缺失值设置为 0 是安全的,只要 0 不是一个有意义的值。网络能够从数据中学到 0 意味着缺失数据,并且会忽略这个值。
2)过拟合与欠拟合
机器学习的根本问题是优化和泛化之间的对立。优化(optimization)是指调节模型以在训练数据上得到最佳性能(即机器学习中的学习),而泛化(generalization)是指训练好的模型在前所未见的数据上的性能好坏。机器学习的目的当然是得到良好的泛化,但你无法控制泛化,只能基于训练数据调节模型。
训练开始时,优化和泛化是相关的:训练数据上的损失越小,测试数据上的损失也越小。这时的模型是欠拟合(underfit)的,即仍有改进的空间,网络还没有对训练数据中所有相关模式建模。但在训练数据上迭代一定次数之后,泛化不再提高,验证指标先是不变,然后开始变差,即模型开始过拟合。这时模型开始学习仅和训练数据有关的模式,但这种模式对新数据来说是错误的或无关紧要的。这种降低过拟合的方法叫作正则化(regularization)。
3)特征工程
特征工程(feature engineering)是指将数据输入模型之前,利用你自己关于数据和机器学习算法(这里指神经网络)的知识对数据进行硬编码的变换(不是模型学到的),以改善模型的效果。多数情况下,一个机器学习模型无法从完全任意的数据中进行学习。呈现给模型的数据应该便于模型进行学习。
假设你想开发一个模型,输入一个时钟图像,模型能够输出对应的时间。
如果你选择用图像的原始像素作为输入数据,那么这个机器学习问题将非常困难。如果将 (x, y) 坐标转换为相对于图像中心的极坐标,这样输入就变成了每个时钟指针的角度 theta,那么现在的特征能使问题变得非常简单,根本不需要机器学习,因为简单的舍入运算和字典查找就足以给出大致的时间。
这就是特征工程的本质:用更简单的方式表述问题,从而使问题变得更容易。它通常需要深入理解问题。
4)通用工作流程
本节将介绍一种可用于解决任何机器学习问题的通用模板。
(1)定义问题,收集数据集,明确了输入、输出以及所使用的数据。
- 假设输出是可以根据输入进行预测的。
- 假设可用数据包含足够多的信息,足以学习输入和输出之间的关系。
你收集了包含输入 X 和目标 Y 的很多样例,并不意味着 X 包含足够多的信息来预测 Y。例如,如果你想根据某支股票最近的历史价格来预测其股价走势,那你成功的可能性不大,因为历史价格并没有包含很多可用于预测的信息。请记住,机器学习只能用来记忆训练数据中存在的模式。你只能识别出曾经见过的东西。在过去的数据上训练机器学习来预测未来,这里存在一个假设,就是未来的规律与过去相同,但事实往往并非如此。
(2)选择衡量成功的指标
衡量成功的指标将指引你选择损失函数,即模型要优化什么。它应该直接与你的目标(如业务成功)保持一致。
对于平衡分类问题(每个类别的可能性相同),精度和接收者操作特征曲线下面积(area under the receiver operating characteristic curve,ROC AUC)是常用的指标。对于类别不平衡的问题,你可以使用准确率和召回率。对于排序问题或多标签分类,你可以使用平均准确率均值(mean average precision)。
(3)确定评估方法
一旦明确了目标,你必须确定如何衡量当前的进展,推荐三种常见的评估方法:
- 留出验证集。数据量很大时可以采用这种方法。
- K 折交叉验证。如果留出验证的样本量太少,无法保证可靠性,那么应该选择这种方法。
- 重复的 K 折验证。如果可用的数据很少,同时模型评估又需要非常准确,那么应该使用这种方法。
只需选择三者之一。大多数情况下,第一种方法足以满足要求。
(4)准备数据
首先你应该将数据格式化,使其可以输入到机器学习模型中(这里假设模型为深度神经网络)。
- 如前所述,应该将数据格式化为张量。
- 这些张量的取值通常应该缩放为较小的值,比如在 [-1, 1] 区间或 [0, 1] 区间。
- 如果不同的特征具有不同的取值范围(异质数据),那么应该做数据标准化。
- 你可能需要做特征工程,尤其是对于小数据问题。
(5)开发比基准更好的模型
这一阶段的目标是获得统计功效(statistical power),即开发一个小型模型,它能够打败纯随机的基准(dumb baseline)。注意,不一定总是能获得统计功效。如果你尝试了多种合理架构之后仍然无法打败随机基准,那么原因可能是问题的答案并不在输入数据中。要记住你所做的两个假设。
- 假设输出是可以根据输入进行预测的。
- 假设可用的数据包含足够多的信息,足以学习输入和输出之间的关系。
如果一切顺利,你还需要选择三个关键参数来构建第一个工作模型。
- 最后一层的激活。它对网络输出进行有效的限制。例如,IMDB 分类的例子在最后一层使用了 sigmoid,回归的例子在最后一层没有使用激活,等等。
- 损失函数。它应该匹配你要解决的问题的类型。例如,IMDB 的例子使用 binary_ crossentropy、回归的例子使用 mse,等等。
- 优化配置。你要使用哪种优化器?学习率是多少?大多数情况下,使用 rmsprop 及其 默认的学习率是稳妥的。
(6)扩大模型规模:开发过拟合的模型
一旦得到了具有统计功效的模型,问题就变成了:模型是否足够强大?它是否具有足够多的层和参数来对问题进行建模?
请记住,机器学习中无处不在的对立是优化和泛化的对立,理想的模型是刚好在欠拟合和过拟合的界线上,在容量不足和容量过大的界线上。要搞清楚你需要多大的模型,就必须开发一个过拟合的模型,这很简单:
- 添加更多的层。
- 让每一层变得更大。
- 训练更多的轮次。
(7)模型正则化与调节超参数
这一步是最费时间的:你将不断地调节模型、训练、在验证数据上评估(这里不是测试数据)、 再次调节模型,然后重复这一过程,直到模型达到最佳性能。你应该尝试以下几项:
- 添加 dropout。
- 尝试不同的架构:增加或减少层数。
- 添加 L1 和 / 或 L2 正则化。
- 尝试不同的超参数(比如每层的单元个数或优化器的学习率),以找到最佳配置。
- (可选)反复做特征工程:添加新特征或删除没有信息量的特征。
请注意,每次使用验证过程的反馈来调节模型,都会将有关验证过程的信息泄露到模型中。如果只重复几次,那么无关紧要;但如果系统性地迭代许多次,最终会导致模型对验证过程过拟合(即使模型并没有直接在验证数据上训练),这会降低验证过程的可靠性。
延伸资料: