一.深度学习简介
机器学习和深度学习的核心问题在于有意义地变换数据,换句话说,在于学习输入数据的有用表示(representation)——这种表示可以让数据更接近预期输出。深度学习是机器学习的一个分支领域:它是从数据中学习表示的一种新方法,强调从连续的层(layer)中进行学习,这些层对应于越来越有意义的表示。可以将深度网络看作多级信息蒸馏操作:信息穿过连续的过滤器,其纯度越来越高(即对任务的帮助越来越大)。现代深度学习通常包含数十个甚至上百个连续的表示层,这些表示层全都是从训练数据中自动学习的。与此相反,其他机器学习方法的重点往往是仅仅学习一两层的数据表示,因此有时也被称为浅层学习(shallow learning)。
用三张图理解深度学习的工作原理:
神经网络中每层对输入数据所做的具体操作保存在该层的权重(weight)中,其本质是一串数字。用术语来说,每层实现的变换由其权重来参数化(parameterize,见图1-7)。权重有时也被称为该层的参数(parameter)。在这种语境下,学习的意思是为神经网络的所有层找到一组权重值,使得该网络能够将每个示例输入与其目标正确地一一对应。但重点来了:一个深度神经网络可能包含数千万个参数。找到所有参数的正确取值可能是一项非常艰巨的任务,特别是考虑到修改某个参数值将会影响其他所有参数的行为。
想要控制一件事物,首先需要能够观察它。想要控制神经网络的输出,就需要能够衡量该输出与预期值之间的距离。这是神经网络损失函数(loss function)的任务,该函数也叫目标函数(objective function)。损失函数的输入是网络预测值与真实目标值(即你希望网络输出的结果),然后计算一个距离值,衡量该网络在这个示例上的效果好坏(见图 1-8)。
深度学习的基本技巧是利用这个距离值作为反馈信号来对权重值进行微调,以降低当前示例对应的损失值(见图 1-9)。这种调节由优化器(optimizer)来完成,它实现了所谓的反向传播(backpropagation)算法,这是深度学习的核心算法。
一开始对神经网络的权重随机赋值,因此网络只是实现了一系列随机变换。其输出结果自然也和理想值相去甚远,相应地,损失值也很高。但随着网络处理的示例越来越多,权重值也在向正确的方向逐步微调,损失值也逐渐降低。这就是训练循环(training loop),将这种循环重复足够多的次数(通常对数千个示例进行数十次迭代),得到的权重值可以使损失函数最小。具有最小损失的网络,其输出值与目标值尽可能地接近,这就是训练好的网络。再次强调,这是一个简单的机制,一旦具有足够大的规模,将会产生魔法般的效果。
深度学习发展得如此迅速,主要原因在于它在很多问题上都表现出更好的性能。但这并不是唯一的原因。深度学习还让解决问题变得更加简单,因为它将特征工程完全自动化,而这曾经是机器学习工作流程中最关键的一步。
深度学习的变革性在于,模型可以在同一时间共同学习所有表示层,而不是依次连续学习(这被称为贪婪学习)。通过共同的特征学习,一旦模型修改某个内部特征,所有依赖于该特征的其他特征都会相应地自动调节适应,无须人为干预。一切都由单一反馈信号来监督:模型中的每一处变化都是为了最终目标服务。这种方法比贪婪地叠加浅层模型更加强大,因为它可以通过将复杂、 抽象的表示拆解为很多个中间空间(层)来学习这些表示,每个中间空间仅仅是前一个空间的简单变换。
深度学习从数据中进行学习时有两个基本特征:第一,通过渐进的、逐层的方式形成越来越复杂的表示;第二,对中间这些渐进的表示共同进行学习,每一层的变化都需要同时考虑上下两层的需要。总之,这两个特征使得深度学习比先前的机器学习方法更加成功。
二.数学基础
Keras:Python 的一个深度学习库(框架)
MNIST 数据集:手写数字的灰度图像(28 像素×28 像素),包含 60 000 张训练图像和 10 000 张测试图像
导入:from keras.datasets import mnist
张量:一般来说,当前所有机器学习系统都使用张量作为基本数据结构。它是一个数据容器,包含的数据几乎总是数值数据,因此它是数字的容器。张量是矩阵向任意维度的推广。
标量(0D 张量),向量(1D 张量),矩阵(2D 张量),3D 张量与更高维张量...
张量运算:逐元素(element-wise)的运算;广播(broadcast);张量点积(np.dot);张量变形(reshape、转置等)
基于梯度下降的训练过程:
发生在一个训练循环(training loop)内,其具体过程如下。必要时一直重复这些步骤。
(1) 抽取训练样本x和对应目标y组成的数据批量。
(2) 在 x 上运行网络[这一步叫作前向传播(forward pass)],得到预测值 y_pred。
(3) 计算网络在这批数据上的损失,用于衡量y_pred和y之间的距离。
(4) 计算损失相对于网络参数的梯度[一次反向传播(backward pass)]。
(5) 将参数沿着梯度的反方向移动一点,比如 W -= step * gradient,从而使这批数据上的损失减小一点。step,也叫learning rate
最终得到的网络在训练数据上的损失非常小,即预测值 y_pred 和预期目标 y 之间的距离非常小。网络“学会”将输入映射到正确的目标。
带动量的随机梯度下降方法,更新参数 w 时不仅要考虑当前的梯度值,还要考虑上一次的参数更新,可以避免优化过程中陷入局部极小点,导致无法找到全局最小点。
链式求导:反向传播算法
三.神经网络入门
神经网络的基本数据结构是层,层是一个数据处理模块,将一个或多个输入张量转换为一个或多个输出张量。有些层是无状态的,但大多数的层是有状态的, 即层的权重。
不同的张量格式与不同的数据处理类型需要用到不同的层。例如,简单的向量数据保存在形状为 (samples, features) 的 2D 张量中,通常用密集连接层[densely connected layer,也叫全连接层(fully connected layer)或密集层(dense layer),对应于 Keras 的 Dense 类]来处理。序列数据保存在形状为 (samples, timesteps, features) 的 3D 张量中,通常用循环层(recurrent layer,比如 Keras 的 LSTM 层)来处理。图像数据保存在 4D 张量中,通常用二维卷积层(Keras 的 Conv2D)来处理。
常用卷积神经网络解决图像处理问题,用循环神经网络解决序列处理问题。
具有多个输出的神经网络可能具有多个损失函数(每个输出对应一个损失函数)。但是,梯度下降过程必须基于单个标量损失值。因此,对于具有多个损失函数的网络,需要将所有损失函数取平均,变为一个标量值。
Keras 简介:
Keras 是一个模型级(model-level)的库,为开发深度学习模型提供了高层次的构建模块。 它不处理张量操作、求微分等低层次的运算。相反,它依赖于一个专门的、高度优化的张量库来完成这些运算,这个张量库就是 Keras 的后端引擎(backend engine)。Keras 没有选择单个张量库并将 Keras 实现与这个库绑定,而是以模块化的方式处理这个问题。因此,几个不同的后端引擎都可以无缝嵌入到 Keras 中。目前,Keras 有三个后端实现:TensorFlow 后端、 Theano 后端和微软认知工具包(CNTK,Microsoft cognitive toolkit)后端。用 Keras 写的每一段代码都可以在这三个后端上运行,无须任何修改。对于特定任务,某个后端的速度更快,那么我们就可以无缝切换过去。推荐使用 TensorFlow 后端作为大部分深度学习任务的默认后端。
使用 Keras 开发时,定义模型有两种方法:一种是使用 Sequential 类(仅用于层的线性堆叠,这是目前最常见的网络架构),另一种是函数式API(functional API,用于层组成的有向无环图,让你可以构建任意形式的架构)。
电影评论分类:二分类问题
IMDB 数据集:它包含来自互联网电影数据库(IMDB)的 50 000 条严重两极分化的评论。数据集被分为用于训练的 25 000 条评论与用于测试的 25 000 条评论,训练集和测试集都包含 50% 的正面评论和 50% 的负面评论。它已经过预处理:评论(单词序列) 已经被转换为整数序列,其中每个整数代表字典中的某个单词。from keras.datasets import imdb
不能将IMDB 数据集的整数序列直接输入神经网络。你需要将列表转换为张量。转换方法有以下两种。
- 填充列表,使其具有相同的长度,再将列表转换成形状为 (samples, word_indices) 的整数张量,然后网络第一层使用能处理这种整数张量的层(即 Embedding 层)。
- 对列表进行 one-hot 编码,将其转换为 0 和 1 组成的向量。举个例子,序列 [3, 5] 将会被转换为 10 000 维向量,只有索引为 3 和 5 的元素是 1,其余元素都是 0。然后网络第一层可以用 Dense 层,它能够处理浮点数向量数据。
需要手动选择激活函数(relu、sigmoid、tanh等)、损失函数(又叫目标函数,mse、mae、hinge、binary_crossentropy、categorical_crossentropy等)和优化器(sgd、rmsprop等)。
小结:
-
带有 relu 激活的 Dense 层堆叠,可以解决很多种问题(包括情感分类),会经常用到这种模型。
-
对于二分类问题(两个输出类别),网络的最后一层应该是只有一个单元并使用 sigmoid激活的 Dense 层,网络输出应该是 0~1 范围内的标量,表示预测为正类的概率值。
-
对于二分类问题的 sigmoid 标量输出,应该使用 binary_crossentropy 作为损失函数。
新闻分类:多分类问题
因为每个数据点只能划分到一个类别, 所以更具体地说,这是单标签、多分类(single-label, multiclass classification)问题的一个例 子。如果每个数据点可以划分到多个类别(主题),那它就是一个多标签、多分类(multilabel, multiclass classification)问题。
路透社数据集:它包含许多短新闻及其对应的主题,包括 了46 个不同的主题,from keras.datasets import reuters
Keras 内置方法可以实现one-hot 编码方式的标签(y)向量化:
from keras.utils.np_utils import to_categorical
one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
网络的最后一层为model.add(layers.Dense(46, activation='softmax')),使用了 softmax 激活函数,网络将输出在 46个不同输出类别上的概率分布——对于每一个输入样本,网络都会输出一个 46 维向量,其中 output[i] 是样本属于第 i 个类别的概率。46 个概率的总和为 1。
预测:predictions = model.predict(x_test)
print(np.argmax(predictions[0])) # 最大的元素就是预测类别,即概率最大的类别
小结:
-
如果要对 N 个类别的数据点进行分类,网络的最后一层应该是大小为 N 的 Dense 层。
-
对于单标签、多分类问题,网络的最后一层应该使用 softmax 激活,这样可以输出在 N 个输出类别上的概率分布。
- 这种问题的损失函数几乎总是应该使用分类交叉熵(categorical_crossentropy),它将网络输出的概率分布与目标的真实分布之间的距离最小化。
-
如果你需要将数据划分到许多类别中,应该避免使用太小的中间层(最好比输出类别的个数大),以免在网络中造成信息瓶颈。
-
处理多分类问题的标签向量化问题有两种方法:通过分类编码(也叫 one-hot 编码)对标签进行编码,然后使用categorical_ crossentropy 作为损失函数;将标签编码为整数,然后使用 sparse_categorical_crossentropy 作为损失函数。
预测房价:回归问题
波士顿房价数据集:from keras.datasets import boston_housing
将取值范围差异很大的数据输入到神经网络中,学习将变得更加困难。对于这种数据,普遍采用的最佳实践是对每个特征做标准化,即对于输入数据的每个特征(输入数据矩阵中的列),减去特征平均值,再除以标准差,这样得到的特征均值为 0,标准差为 1。
注意,用于测试数据标准化的均值和标准差都是在训练数据上计算得到的。在工作流程中,你不能使用在测试数据上计算得到的任何结果,即使是像数据标准化这么简单的事情也不行。
一般来说,训练数据越少,过拟合会越严重,而较小的网络可以降低过拟合。
网络的最后一层只有一个单元,没有激活,是一个线性层:model.add(layers.Dense(1)),这是标量回归(标量回归是预测单一连续值的回归)的典型设置。添加激活函数将会限制输出范围。例如,如果向最后一层添加 sigmoid 激活函数,网络只能学会预测 0~1 范围内的值。这里最后一层是纯线性的,所以网络可以学会预测任意范围内的值。
小结:
-
回归问题使用的损失函数与分类问题不同,回归常用的损失函数是均方误差(MSE)。
-
回归问题使用的评估指标也与分类问题不同,精度的概念不适用于回归问题。常见的回归指标是平均绝对误差(MAE)。
-
如果输入数据的特征具有不同的取值范围,应该先进行预处理,对每个特征单独进行缩放。
-
如果可用的数据很少,使用 K 折交叉验证可以可靠地评估模型。
-
如果可用的训练数据很少,最好使用隐藏层较少(通常只有一到两个)的小型网络,以避免严重的过拟合。
四.机器学习基础
机器学习算法大致可分为四大类:监督学习(分类、回归问题及其变体)、无监督学习(降维和聚类是众所周知的无监督学习方法)、自监督学习(自编码器(autoencoder))和强化学习(在强化学习中,智能体(agent)接收有关其环境的信息,并学会选择使某种奖励最大化的行动)。
标量回归(scalar regression):目标是连续标量值的任务。预测房价就是一个很好的例子,不同的目标价格形成一个连续的空间。
向量回归(vector regression):目标是一组连续值(比如一个连续向量)的任务。如果对多个值(比如图像边界框的坐标)进行回归,那就是向量回归。
小批量(mini-batch)或批量(batch):模型同时处理的一小部分样本(样本数通常 为 8~128)。样本数通常取 2 的幂,这样便于 GPU 上的内存分配。训练时,小批量用来为模型权重计算一次梯度下降更新。
将数据划分为三个集合:训练集、验证集和测试集。在训练数据上训练模型,在验证数据上评估模型。一旦找到了最佳参数,就在测试数据上最后测试一次。为什么要分验证集?原因在于开发模型时总是需要调节模型配置,比如选择层数或每层大小[这叫作模型的超参数(hyperparameter),以便与模型参数(即权重)区分开]。这个调节过程需要使用模型在验证数据上的性能作为反馈信号。
评估机器学习模型:留出法;K 折交叉验证;自助法
注意事项:
-
数据代表性:在将数据划分为训练集和测试集之前,通常应该随机打乱数据。
-
时间箭头:如果想要根据过去预测未来(比如明天的天气、股票走势等),那么在划分数据前你不应该随机打乱数据,因为这么做会造成时间泄露(temporal leak):你的模型将在未来数据上得到有效训练。在这种情况下,你应该始终确保测试集中所有数据的时间都晚于训练集数据。
-
数据冗余:如果数据中的某些数据点出现了两次(这在现实中的数据里十分常见),那么打乱数据并划分成训练集和验证集会导致训练集和验证集之间的数据冗余。从效果上来看,你是在部分训练数据上评估模型,这是极其糟糕的!一 定要确保训练集和验证集之间没有交集。
数据预处理的目的是使原始数据更适于用神经网络处理,包括向量化、标准化、处理缺失值和特征提取。
特征工程(feature engineering)是指将数据输入模型之前,利用你自己关于数据和机器学习算法(这里指神经网络)的知识对数据进行硬编码的变换(不是模型学到的),以改善模型的效果。多数情况下,一个机器学习模型无法从完全任意的数据中进行学习。呈现给模型的数据应该便于模型进行学习。假设你想开发一个模型,输入一个时钟图像,模型能够输出对应的时间:
虽然对于现代深度学习,大部分特征工程都是不需要的,因为神经网络能够从原始数据中自动提取有用的特征。但是,特征工程对于深度学习仍然非常重要,原因有两点:良好的特征仍然可以让你用更少的资源更优雅地解决问题。例如,使用卷积神经网络来读取钟面上的时间是非常可笑的;良好的特征可以让你用更少的数据解决问题。深度学习模型自主学习特征的能力依赖于大量的训练数据。如果只有很少的样本,那么特征的信息价值就变得非常重要。
解决过拟合问题可尝试的方法:最优解决方法是获取更多的训练数据,模型的训练数据越多,泛化能力自然也越好;正则化
常见的正则化方法:
- 减小(神经)网络大小(容量)。即减少模型中可学习参数的个数(这由层数和每层的单元个数决定)。要找到合适的模型大小,一般的工作流程是开始时选择相对较少的层和参数,然后逐渐增加层的大小或增加新层,直到这种增加对验证损失的影响变得很小。
-
添加权重正则化。奥卡姆剃刀(Occam’s razor)原理:如果一件事情有两种解释,那么最可能正确的解释就是最简单的那个,即假设更少的那个。对于神经网络学到的模型,简单模型比复杂模型更不容易过拟合,这里的简单模型(simple model)是指参数值分布的熵更小的模型或参数更少的模型。包括L1 正则化和L2 正则化(神经网络的 L2 正则化也叫权重衰减)。注意,由于惩罚项(正则项)只在训练时添加,所以这个网络的训练损失会比测试损失大很多。
-
添加 dropout 正则化。dropout 是神经网络最有效也最常用的正则化方法之一,对某一层使用 dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为 0)。dropout 比率(dropout rate)是被设为 0 的特征所占的比例,通常在 0.2~0.5 范围内。测试时没有单元被舍弃,而该层的输出值需要按 dropout 比率缩小,因为这时比训练时有更多的单元被激活,需要加以平衡。为了实现这一过程,还可以让两个运算都在训练时进行,而测试时输出保持不变, 这通常也是实践中的实现方式(比如训练时,先舍弃 50%的输出单元,然后成比例放大数据2倍(注意不是成比例缩小))。其核心思想是在层的输出值中引入噪声, 打破不显著的偶然模式(Hinton 称之为"阴谋")。如果没有噪声的话,网络将会记住这些偶然模式。
请记住,机器学习只能用来记忆训练数据中存在的模式。你只能识别出曾经见过的东西。 在过去的数据上训练机器学习来预测未来,这里存在一个假设,就是未来的规律与过去相同。但事实往往并非如此。
机器学习的通用工作流程:
-
定义问题,收集数据集
注意你在这一阶段所做的假设:假设输出是可以根据输入进行预测的;假设可用的数据包含足够多的信息,足以学习输入和输出之间的关系。
-
选择衡量成功的指标
对于平衡分类问题(每个类别的可能性相同),ROC和AUC是常用的指标。对于类别不平衡的问题,你可以使用精确率和召回率。对于排序问题或多标签分类,你可以使用平均精确率均值(mean average precision)。
-
确定评估方法
留出法;K 折交叉验证;自助法
-
准备数据
即数据预处理的一些操作:向量化、标准化、处理缺失值和特征提取
-
开发比基准更好的模型
开发一个小型模型,使它能够打败纯随机的基准。重点要确定损失函数、最后一层的激活函数(见上面的表格)以及优化配置(使用哪种优化器?学习率是多少?大多数情况下,使用 rmsprop 及其默认的学习率是稳妥的)
-
扩大模型规模:开发过拟合的模型
要搞清楚你需要多大的模型,就必须开发一个过拟合的模型,这很简单:
(1) 添加更多的层
(2) 让每一层变得更大
(3) 训练更多的轮次
要始终监控训练损失和验证损失,以及你所关心的指标的训练值和验证值。如果你发现模型在验证数据上的性能开始下降,那么就出现了过拟合。 -
模型正则化与调节超参数
这一步是最费时间的:你将不断地调节模型、训练、在验证数据上评估(这里不是测试数据)、 再次调节模型,然后重复这一过程,直到模型达到最佳性能。请注意:每次使用验证过程的反馈来调节模型,都会将有关验证过程的信息泄露到模型中。如果只重复几次,那么无关紧要,但如果系统性地迭代许多次,最终会导致模型对验证过程过拟合(即使模型并没有直接在验证数据上训练),这会降低验证过程的可靠性。一旦开发出令人满意的模型配置,你就可以在所有可用数据(训练数据 + 验证数据)上训练最终的生产模型,然后在测试集上最后评估一次。