文章目录
第5 章 机器学习基础
第一部分我们介绍了NumPy、Tensor、nn等内容,这些内容是继续学习PyTorch的基础。有了这些基础,学习第二部分就容易多了。第二部分我们将介绍深度学习的一些基本内容,以及如何用PyTorch解决机器学习、深度学习的一些实际问题。
深度学习是机器学习的重要分支,也是机器学习的核心,但深度学习是在机器学习基础上发展起来的,因此理解机器学习的基本概念、基本原理对理解深度学习大有裨益。
机器学习的体系很庞大,限于篇幅,本章主要介绍基本知识及与深度学习关系比较密切的内容,如果读者希望进一步学习机器学习的相关知识,建议参考周志华老师编著的《机器学习》或李航老师编著的《统计学习方法》。
本章先介绍机器学习中常用的监督学习、无监督学习等,然后介绍神经网络及相关算法,最后介绍传统机器学习中的一些不足及优化方法等,本章主要内容包括:
♦机器学习的基本任务
♦机器学习的一般流程
♦解决过拟合、欠拟合的一些方法
♦选择合适的激活函数、损失函数、优化器等
♦GPU加速
5.1 机器学习的基本任务
机器学习的基本任务一般分为四大类,监督学习、无监督学习、半监督学习和强化学习。监督学习、无监督学习比较普遍,大家也比较熟悉。常见的分类、回归等属于监督学习,聚类、降维等属于无监督学习。半监督学习和强化学习的发展历史虽没有前两者这么悠久,但发展势头非常迅猛。图5-1 说明了四种分类的主要内容。
图5-1 机器学习的基本任务
5.1.1 监督学习
监督学习是最常见的一种机器学习类型,其任务的特点就是给定学习目标,这个学习目标又称为标签或或标注或实际值等,整个学习过程就是围绕如何使预测与目标更接近。近些年,随着深度学习的发展,分类除传统的二分类、多分类、多标签分类之外,分类也出现一些新内容,如目标检测、目标识别、图像分割等是监督学习重要内容。监督学习过程如图5-2所示。
图5-2 监督学习的一般过程
5.1.2 无监督学习
监督学习的输入数据中有标签或目标值,但在实际生活中,有很多数据是没有标签的,或者标签代价很高。这些没有标签的数据也可能包含很重要规则或信息,从这类数据中学习到一个规则或规律的过程称为无监督学习。在无监督学习中,我们通过推断输入数据中的结构来建模,模型包括关联学习、降维、聚类等。
5.1.3 半监督学习
半监督是监督学习与无监督学习相结合的一种学习方法。半监督学习使用大量的未标记数据,同时由部分使用标记数据进行模式识别。半监督学习目前正越来越受到人们的重视。
自编码器是一种半监督学习,其生成的目标就是未经修改的输入。语言处理中根据给定文本中词预测下一个词,也是半监督学习的例子。
对抗生成式网络也是一种半监督学习,给定一些真图像或语音,然后,通过对抗生成网络生成一些与真图像或语音逼真的图形或语音。
5.1.4 强化学习
强化学习是机器学习的一个重要分支,是多学科多领域交叉的一个产物。强化学习主要包含四个元素,智能体(Agent),环境状态,行动,奖励, 强化学习的目标就是获得最多的累计奖励。
强化学习把学习看作试探评价过程,Agent选择一个动作作用于环境,环境在接收该动作后状态发生变化,同时产生一个强化信号(奖或惩)反馈给Agent,然后再由Agent根据强化信号和环境的当前状态选择下一个动作,选择的原则是使Agent受到正强化(奖)的概率增大。选择的动作不仅影响立即强化值,也影响下一时刻的状态和最终的强化值。
强化学习不同于监督学习,主要表现在教师信号上,强化学习中由环境提供的强化信号是Agent对所产生动作的好坏作一种评价,而不是告诉Agent如何去产生正确的动作。由于外部环境提供了很少的信息,因此Agent必须靠自身的经历进行学习。通过这种方式,Agent在行动—评价的环境中获得知识,改进行动方案以适应环境。
AlphaGo Zero带有强化学习内容,它完全摒弃了人类知识,碾压了早期版本的AlphaGo,更足显强化学习和深度学习结合的巨大威力。
5.2 机器学习一般流程
机器学习一般流程首先需要明确目标、收集数据、探索数据、预处理数据,对数据处理后,接下来开始构建和训练模型、评估模型,然后优化模型等步骤,图5-3 为机器学习一般流程图。
图5-3 机器学习一般流程图
通过这个图形可直观了解机器学习的一般步骤或整体框架,接下来我们就各部分分别加以说明。
5.2.1 明确目标
在实施一个机器学习项目之初,定义需求、明确目标、了解要解决的问题以及目标涉及的范围等非常重要,它们直接影响后续工作的质量甚至成败。明确目标,首先需要明确大方向,比如当前需求是分类问题还是预测问题或聚类问题等。清楚大方向后,需要进一步明确目标的具体含义。如果是分类问题,还需要区分是二分类、多分类或多标签分类;如果是预测问题,要区别是标量预测还是向量预测;其他方法类似。确定问题,明确目标有助于选择模型架构、损失函数及评估方法等。
当然,明确目标还包含需要了解目标的可行性,因为并不是所有问题都可以通过机器学习来解决。
5.2.2收集数据
目标明确后,接下来就是了解数据。为解决这个问题,需要哪些数据?数据是否充分?哪些数据能获取?哪些无法获取?这些数据是否包含我们学习的一些规则等等,都需要全面把握。
接下来就是收集数据,数据可能涉及不同平台、不同系统、不同部分、不同形式等,对这些问题的了解有助于确定具体数据收集方案、实施步骤等。
能收集的数据尽量实现自动化、程序化。
5.2.3 数据探索与预处理
收集到的数据,不一定规范和完整,这就需要对数据进行初步分析或探索,然后根据探索结果与问题目标,确定数据预处理方案。
对数据探索包括了解数据的大致结构、数据量、各特征的统计信息、整个数据质量情况、数据的分布情况等。为了更好体现数据分布情况,数据可视化是一个不错方法。
通过对数据探索后,可能发会现不少问题:如存在缺失数据、数据不规范、数据分布不均衡、存在奇异数据、有很多非数值数据、存在很多无关或不重要的数据等等。这些问题的存在直接影响数据质量,为此,数据预处理工作应该就是接下来的重点工作,数据预处理是机器学习过程中必不可少的重要步骤,特别是在生产环境中的机器学习,数据往往是原始、未加工和处理过,数据预处理常常占据整个机器学习过程的大部分时间。
数据预处理过程中,一般包括数据清理、数据转换、规范数据、特征选择等工作。
5.2.4 选择模型及损失函数
数据准备好以后,接下就是根据目标选择模型。模型选择上可以先用一个简单、自己比较熟悉的一些方法来实现,用这个方法开发一个原型或比基准更好一点的模型。通过这个简单模型有助于你快速了解整个项目的主要内容。
•了解整个项目的可行性、关键点
•了解数据质量、数据是否充分等
•为你开发一个更好模型奠定基础
在模型选择时,一般不存在某种对任何情况都表现很好的算法(这种现象又称为没有免费的午餐)。因此在实际选择时,一般会选用几种不同方法来训练模型,然后比较它们的性能,从中选择最优的那个。
模型选择后,还需要考虑以下几个关键点:
•最后一层是否需要添加softmax或sigmoid激活层
•选择合适损失函数
•选择合适的优化器
表5-1 列出了常见问题类型最后一层激活函数和损失函数的对应关系,供大家参考。
表5-1 根据问题类型选择损失函数
5.2.5 评估及优化模型
模型确定后,还需要确定一种评估模型性能的方法,即评估方法。评估方法大致有以下三种:
•留出法(Holdout):留出法的步骤相对简单,直接将数据集划分为两个互斥的集合,其中一个集合作为训练集,另一个作为测试。在训练集上训练出模型后,用测试集来评估测试误差,作为泛化误差的估计。使用留出法,还有一种更好的方法就是把数据分成三部分:训练数据集、验证数据集、测试数据集。训练数据集用来训练模型,验证数据集用来调优超参数,测试集用来测试模型的泛化能力。数据量较大时可采用这种方法。
•K折交叉验证:不重复地随机将训练数据集划分为k个,其中k-1个用于模型训练,剩余的一个用于测试。
•重复的K折交叉验证:当数据量比较小,数据分布不很均匀时可以采用这种方法。
使用训练数据构建模型后,通常使用测试数据对模型进行测试,测试模型对新数据的
测试。如果对模型的测试结果满意,就可以用此模型对以后的进行预测;如果测试结果不满意,可以优化模型。优化的方法很多,其中网格搜索参数是一种有效方法,当然我们也可以采用手工调节参数等方法。如果出现过拟合,尤其是回归类问题,可以考虑正则化方法来降低模型的泛化误差。
5.3 过拟合与欠拟合
前面我们介绍了机器学习的一般流程,模型确定后,开始训练模型,然后对模型进行评估和优化,这个过程往往是循环往复的。训练模型过程经常出现刚开始训练时,训练和测试精度不高(或损失值较大),通过增加迭代次数或优化,使得训练精度和测试精度继续提升的情况,如果出现这种情况,当然最好。但也会出现随着训练迭代次数增加或不模型断优化,训练精度或损失值继续改善,测试精度或损失值不降反升的情况。如图5-4 所示。
图5-4 训练误差与测试误差
出现这种情况时,说明我们的优化过头了,把训练数据中一些无关紧要甚至错误的模式也学到了。这就是我们通常说的出现过拟合了。如何解决这类问题?机器学习中有很多方法,这些方法又统称为正则化,接下来我们介绍一些常用的正则化方法。
5.3.1 权重正则化
如何解决过拟合问题呢?正则化是有效方法之一。正则化不仅可以有效降低高方差,还有利于降低偏差。何为正则化?在机器学习中,很多被显式地用来减少测试误差的策略,统称为正则化。正则化旨在减少泛化误差而不是训练误差。为使大家对正则化的作用及原理有个直观印象,先看正则化示意图(见图5-5)。
图5-5 正则化示意图
图5-5是根据房屋面积(Size)预测房价(Price)的回归模型。正则化是如何解决模型过复杂这个问题的呢?主要是通过正则化使参数变小甚至趋于原点。在图5-5c所示,其模型或目标函数是一个4次多项式,因它把一些噪声数据也包括进来了,导致模型很复杂,实际上房价与房屋面积应该是2次多项式函数,如图5-5b所示。
如果要降低模型的复杂度,可以通过缩减它们的系数来实现,如把第3次、4次项的系数缩减到接近于0即可。
在算法中如何实现呢?这个得从其损失函数或目标函数着手。假设房屋价格与面积间模型的损失函数为:
这个损失函数是我们的优化目标,也就是说我们需要尽量减少损失函数的均方误差。
对于这个函数我们对它添加一些正则项,如加上 10000乘以 的平方,再加上 10000乘以的平方,得到如下函数:
这里取10000只是用来代表它是一个“大值”,现在,如果要最小化这个新的损失函数,我们要让和尽可能小。因为如果你在原有损失函数的基础上加上 10000乘以 这一项,那么这个新的损失函数将变得很大,所以,当最小化这个新的损失函数时,将使 的值接近于 0,同样的值也接近于 0,就像我们忽略了这两个值一样。如果做到这一点( 和 接近 0 ),那么将得到一个近似的二次函数。如图5-6所示。
图5-6利用正则化提升模型泛化能力
希望通过上面的简单介绍,能给大家有个直观理解。传统意义上的正则化一般分为等。
PyTorch如何实现正则化呢?这里以实现为例,神经网络的正则化称为权重衰减(weight decay)。torch.optim集成了很多优化器,如SGD,Adadelta,Adam,Adagrad,RMSprop等,这些优化器自带的一个参数weight_decay,用于指定权值衰减率,相当于L2正则化中的参数,也就是式(5.3)中的。
5.3.2 dropout正则化
dropout是Srivastava等人在2014年的一篇论文中,提出的一种针对神经网络模型的正则化方法 “Dropout: A Simple Way to Prevent Neural Networks from Overfitting”。
dropout在训练模型中是如何实现的呢?dropout的做法是在训练过程中按一定比例(比例参数可设置)随机忽略或屏蔽一些神经元。这些神经元被随机“抛弃”,也就是说它们在正向传播过程中对于下游神经元的贡献效果暂时消失了,反向传播时该神经元也不会有任何权重的更新。所以,通过传播过程,dropout将产生和范数相同的收缩权重的效果。
随着神经网络模型的不断学习,神经元的权值会与整个网络的上下文相匹配。神经元的权重针对某些特征进行调优,会产生一些特殊化。周围的神经元则会依赖于这种特殊化,如果过于特殊化,模型会因为对训练数据过拟合而变得脆弱不堪。神经元在训练过程中的这种依赖于上下文的现象被称为复杂的协同适应(complex co-adaptation)。
加入了dropout以后,输入的特征都有可能会被随机清除,所以该神经元不会再特别依赖于任何一个输入特征,也就是说不会给任何一个输入设置太大的权重。网络模型对神经元特定的权重不那么敏感,这反过来又提升了模型的泛化能力,不容易对训练数据过拟合。
dropout训练的集成包括所有从基础网络除去非输出单元形成子网络,如图5-7所示。
图 5-7基础网络dropout为多个子网络
dropout训练所有子网络组成的集合,其中子网络是从基本网络中删除非输出单元构建的。我们从具有两个可见单元和两个隐藏单元的基本网络开始,这4个单元有16个可能的子集。右图展示了从原始网络中丢弃不同的单元子集而形成的所有16个子网络。在这个例子中,所得到的大部分网络没有输入单元或没有从输入连接到输出的路径。当层较宽时,丢弃所有从输入到输出的可能路径的概率会变小,所以,这个问题对于层较宽的网络不是很重要。
较先进的神经网络一般包括一系列仿射变换和非线性变换,我们可以将一些单元的输出乘零,就能有效地删除一些单元。这个过程需要对模型进行一些修改,如径向基函数网络、单元的状态和参考值之间存在一定区别。为简单起见, 在这里提出乘零的简单dropout算法,被简单地修改后,可以与其他操作一起工作。
dropout在训练阶段和测试阶段是不同的,一般在训练中使用,测试不使用。不过测试时没有神经元被丢弃,此时有更多的单元被激活,为平衡起见,一般将输出按dropout rate比例缩小。
如何或何时使用dropout呢?以下是一般原则。
1)通常丢弃率控制在20%~50%比较好,可以从20%开始尝试。如果比例太低则起不到效果,比例太高则会导致模型的欠拟合。
2)在大的网络模型上应用。当dropout用在较大的网络模型时,更有可能得到效果的提升,模型有更多的机会学习到多种独立的表征。
3)在输入层和隐含层都使用dropout。对于不同的层,设置的keep_prob也不同,一般来说神经元较少的层,会设keep_prob为1.0或接近于1.0的数;神经元多的层,则会将keep_prob设置的较小,如0.5或更小。
4)增加学习速率和冲量。把学习速率扩大10~100倍,冲量值调高到0.9~0.99。
5)限制网络模型的权重。较大的学习速率往往易导致大的权重值。对网络的权重值做最大范数的正则化,被证明能提升模型性能。
以下我们通过实例来比较使用dropout和不使用dropout对训练损失或测试损失的影响。
数据还是房屋销售数据,构建网络层,添加两个dropout,具体构建网络代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
net1_overfitting = torch.nn.Sequential( torch.nn.Linear(13, 16), torch.nn.ReLU(), torch.nn.Linear(16, 32), torch.nn.ReLU(), torch.nn.Linear(32, 1), ) net1_dropped = torch.nn.Sequential( torch.nn.Linear(13, 16), torch.nn.Dropout(0.5), # drop 50% of the neuron torch.nn.ReLU(), torch.nn.Linear(16, 32), torch.nn.Dropout(0.5), # drop 50% of the neuron torch.nn.ReLU(), torch.nn.Linear(32, 1), ) |
获取测试集上不同损失值的代码如下:
1 |
writer.add_scalars('test_group_loss',{'origloss':orig_loss.item(),'droploss':drop_loss.item()}, epoch) |
通过TensorBoard在Web显示运行结果,如图5-8所示。
图5-8 dropout对测试损失值的影响
从图5-8 可以看出,添加dropout层,对提升模型的性能或泛化能力,效果还是比较明显的。
5.3.3 批量归一化
前面我们介绍了数据归一化,这个一般是针对输入数据而言。但在实际训练过程中,经常出现隐含层因数据分布不均,导致梯度消失或不起作用的情况。如采用sigmoid函数或tanh函数为激活函数时,如果数据分布在两侧,这些激活函数的导数就接近于0,这样一来,BP算法得到的梯度也就消失了。如何解决这个问题?
Sergey Ioffe和Christian Szegedy两位学者提出了批量归一化(Batch Normalization,BN)方法。批量归一化不仅可以有效解决梯度消失问题,而且还可以让调试超参数更加简单,在提高训练模型效率的同时,还可让神经网络模型更加“健壮”。批量归一化是如何做到这些的呢? 首先,我们介绍一下批量归一化的算法流程:
批量归一化是对隐含层的标准化处理,它与输入的标准化处理Normalizing inputs是有区别的。Normalizing inputs使所有输入的均值为0,方差为1,而批量归一化可使各隐含层输入的均值和方差为任意值。实际上,从激活函数的角度来说,如果各隐含层的输入均值在靠近0的区域,即处于激活函数的线性区域,这样不利于训练好的非线性神经网络,而且得到的模型效果也不会太好。式(5.6)就起这个作用,当然它还有将归一化后的 x 还原的功能。批量归一化一般用在哪里呢?批量归一化应作用在非线性映射前,即对x=Wu+b做规范化时,在每一个全连接和激励函数之间。
何时使用批量归一化呢?一般在神经网络训练时遇到收敛速度很慢,或梯度爆炸等无法训练的状况时,可以尝试用批量归一化来解决。另外,在一般情况下,也可以加入批量归一化来加快训练速度,提高模型精度,还可以大大提高训练模型的效率。批量归一化的具体功能有:
1)可以选择比较大的初始学习率,让训练速度飙涨。以前还需要慢慢调整学习率,甚至在网络训练到一半的时候,还需要想着学习率进一步调小的比例选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然,这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性。
2)不用再去理会过拟合中drop out、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性。
3)再也不需要使用局部响应归一化层。
4)可以把训练数据彻底打乱。
下面还是以房价预测为例,比较添加BN层与不添加BN层,两者在测试集上的损失值比较。下例为两者网络结构代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
net1_overfitting = torch.nn.Sequential( torch.nn.Linear(13, 16), torch.nn.ReLU(), torch.nn.Linear(16, 32), torch.nn.ReLU(), torch.nn.Linear(32, 1), ) net1_nb = torch.nn.Sequential( torch.nn.Linear(13, 16), nn.BatchNorm1d(num_features=16), torch.nn.ReLU(), torch.nn.Linear(16, 32), nn.BatchNorm1d(num_features=32), torch.nn.ReLU(), torch.nn.Linear(32, 1), ) |
图5-9 为运行结果图。
图5-9 BN层对测试数据的影响
从图5-9 可以看出,添加BN层对改善模型的泛化能力有一定帮助,不过没有dropout那么明显。这个神经网络比较简单,BN在一些复杂网络中的效果会更好。
BN方法对批量大小(batchsize)比较敏感,由于每次计算均值和方差是单个节点的一个批次上,所以如果批量大小太小,则计算的均值、方差不足以代表整个数据分布。BN实际使用时需要计算并且保存某一层神经网络批次的均值和方差等统计信息,对于对一个固定深度的正向神经网络(如深度神经网络(DNN)、卷积神经网络(CNN)),使用BN很方便。但对于深度不固定的神经网络,如循环神经网络(RNN)来说,BN的计算很麻烦,而且效果也不很理想。此外,BN算法对训练和测试两个阶段不一致,也会影响模型的泛化效果。对于不定长的神经网络人们想到另一种方法,即层归一化(Layer Normalization,LN)算法。
5.3.4 层归一化
层归一化对同一层的每个样本进行正则化,不依赖于其他数据,因此可以避免 BN 中受小批量数据分布影响的问题。不同的输入样本有不同的均值和方差,它比较适合于样本是不定长或网络深度不固定的场景,如RNN、NLP等方面。
BN是纵向计算,而LN是横向计算,另外BN是对单个节点(或特征)的一个批次进行计算,而LN是基于同一层不同节点(或不同特征)的一个样本进行计算。两者之间的区别可用图5-10直观表示。
图5-10 BN与LN的异同
5.3.5权重初始化
深度学习为何要初始化?传统机器学习算法中很多不是采用迭代式优化,因此需要初始化的内容不多。但深度学习的算法一般采用迭代方法,而且参数多、层数也多,所以很多算法不同程度受到初始化的影响。
初始化对训练有哪些影响?初始化能决定算法是否收敛,如果初始化不适当,初始值过大可能会在正向传播或反向传播中产生爆炸的值;如果太小将导致丢失信息。对收敛的算法适当的初始化能加快收敛速度。初始值选择将影响模型收敛局部最小值还是全局最小值,如图5-11所示,因初始值的不同,导致收敛到不同的极值点。另外,初始化也可以影响模型的泛化。
图5-11 初始点的选择影响算法是否陷入局部最小点
如何对权重、偏移量进行初始化?初始化这些参数是否有一般性原则?常见的参数初始化有零值初始化、随机初始化、均匀分布初始、正态分布初始和正交分布初始等。一般采用正态分布或均匀分布的初始值,实践表明正态分布、正交分布、均匀分布的初始值能带来更好的效果。
继承nn.Module的模块参数都采取了较合理的初始化策略,一般情况使用其默认初始化策略就够了。当然,如果你要想修改,PyTorch也提供了nn.init模块,该模块提供了常用的初始化策略,如xavier、kaiming等经典初始化策略,使用这些初始化策略有利于激活值的分布呈现更有广度或更贴近正态分布。xavier一般用于激活函数是S型(如sigmoid、tanh)的权重初始化,kaiming更适合与激活函数为ReLU类的权重初始化。