2022Fall_人工智能笔记

人工智能笔记

机器学习概览

总结一下机器学习善于

  • 需要进行大量手工调整或需要拥有长串规则才能解决的问题机器学习算法通常可以简化代码提高性能
  • 问题复杂传统方法难以解决最好的机器学习方法可以找到解决方案
  • 环境有波动机器学习算法可以适应新数据
  • 洞察复杂问题和大量数据

监督学习和非监督学习

在监督学习中用来训练算法的训练数据包含了答案称为标签图 1-5

图 1-5 用于监督学习比如垃圾邮件分类的加了标签的训练集

一个典型的监督学习任务是分类垃圾邮件过滤器就是一个很好的例子用许多带有归类垃圾邮件或普通邮件的邮件样本进行训练过滤器必须还能对新邮件进行分类

另一个典型任务是预测目标数值例如给出一些特征里程数车龄品牌等等称作预测值来预测一辆汽车的价格这类任务称作回归图 1-6要训练这个系统你需要给出大量汽车样本包括它们的预测值和标签它们的价格

下面是一些重要的监督学习算法本书都有介绍

  • K 近邻算法
  • 线性回归
  • 逻辑回归
  • 支持向量机SVM
  • 决策树和随机森林
  • 神经网络

在非监督学习中你可能猜到了训练数据是没有加标签的图 1-7系统在没有老师的条件下进行学习

下面是一些最重要的非监督学习算法我们会在第 8 章介绍降维

  • 聚类
    K 均值
    层次聚类分析Hierarchical Cluster AnalysisHCA
    期望最大值
  • 可视化和降维
    主成分分析Principal Component AnalysisPCA
    核主成分分析
    局部线性嵌入Locally-Linear EmbeddingLLE
    t-分布邻域嵌入算法t-distributed Stochastic Neighbor Embeddingt-SNE
  • 关联性规则学习
    Apriori 算法
    Eclat 算法

一些算法可以处理部分带标签的训练数据通常是大量不带标签数据加上小部分带标签数据这称作半监督学习图 1-11

一些图片存储服务比如 Google Photos是半监督学习的好例子一旦你上传了所有家庭相片它就能自动识别到人物 A 出现在了相片 1511 中另一个人 B 出现在了相片 257 中这是算法的非监督部分聚类现在系统需要的就是你告诉它这两个人是谁只要给每个人一个标签算法就可以命名每张照片中的每个人特别适合搜索照片

强化学习

强化学习非常不同学习系统在这里被称为智能体agent可以对环境进行观察选择和执行动作并获得奖励作为回报负奖励是惩罚见图 1-12然后它必须自己学习哪个是最佳方法称为策略policy以得到长久的最大奖励策略决定了智能体在给定情况下应该采取的行动

批量和在线学习

批量学习

在批量学习中系统不能进行持续学习必须用所有可用数据进行训练这通常会占用大量时间和计算资源所以一般是线下做的首先是进行训练然后部署在生产环境且停止学习它只是使用已经学到的策略这称为离线学习

如果你想让一个批量学习系统明白新数据例如垃圾邮件的新类型就需要从头训练一个系统的新版本使用全部数据集不仅有新数据也有老数据然后停掉老系统换上新系统

幸运的是训练评估部署一套机器学习的系统的整个过程可以自动进行见图 1-3所以即便是批量学习也可以适应改变只要有需要就可以方便地更新数据训练一个新版本

这个方法很简单通常可以满足需求但是用全部数据集进行训练会花费大量时间所以一般是每 24 小时或每周训练一个新系统如果系统需要快速适应变化的数据比如预测股价变化就需要一个响应更及时的方案

在线学习

在在线学习中是用数据实例持续地进行训练可以一次一个或一次几个实例称为小批量每个学习步骤都很快且廉价所以系统可以动态地学习收到的最新数据见图 1-13

图 1-13 在线学习

在线学习很适合系统接收连续流的数据比如股票价格且需要自动对改变作出调整如果计算资源有限在线学习是一个不错的方案一旦在线学习系统学习了新的数据实例它就不再需要这些数据了所以扔掉这些数据除非你想滚回到之前的一个状态再次使用数据这样可以节省大量的空间

在线学习算法也适用于在超大数据集一台计算机不足以用于存储它上训练系统这称作核外学习out-of-core learning算法每次只加载部分数据用这些数据进行训练然后重复这个过程直到使用完所有数据见图 1-14

警告这个整个过程通常是离线完成的不在部署的系统上所以在线学习这个名字会让人疑惑可以把它想成持续学习

图 1-14 使用在线学习处理大量数据集

在线学习系统的一个重要参数是它们可以多快地适应数据的改变这被称为学习速率如果你设定一个高学习速率系统就可以快速适应新数据但是也会快速忘记老数据你可不想让垃圾邮件过滤器只标记最新的垃圾邮件种类相反的如果你设定的学习速率低系统的惰性就会强它学的更慢但对新数据中的噪声或没有代表性的数据点结果不那么敏感

在线学习的挑战之一是如果坏数据被用来进行训练系统的性能就会逐渐下滑如果这是一个部署的系统用户就会注意到例如坏数据可能来自失灵的传感器或机器人或某人向搜索引擎传入垃圾信息以提高搜索排名要减小这种风险你需要密集监测如果检测到性能下降要快速关闭或是滚回到一个之前的状态你可能还要监测输入数据对反常数据做出反应比如使用异常检测算法

线性回归

在第一章我们介绍了一个简单的生活满意度回归模型:

这个模型仅仅是输入量GDP_per_capita的线性函数θ[0]θ[1]是这个模型的参数线性模型更一般化的描述指通过计算输入变量的加权和并加上一个常数偏置项截距项来得到一个预测值如公式 4-1

公式 4-1线性回归预测模型

  • y_hat表示预测结果

  • n表示特征的个数

  • x[i]表示第i个特征的值

  • θ[j]表示第j个参数包括偏置项θ[0]和特征权重值θ[1], θ[2], ..., θ[nj]

上述公式可以写成更为简洁的向量形式如公式 4-2

公式 4-2线性回归预测模型向量形式

  • θ表示模型的参数向量包括偏置项θ[0]和特征权重值θ[1]θ[n]

  • θ^T表示向量θ的转置行向量变为了列向量

  • x为每个样本中特征值的向量形式包括x[1]x[n]而且x[0]恒为 1

  • θ^T · x表示θ^Tx的点积

  • h[θ]表示参数为θ的假设函数

怎么样去训练一个线性回归模型呢好吧回想一下训练一个模型指的是设置模型的参数使得这个模型在训练集的表现较好

为此我们首先需要找到一个衡量模型好坏的评定方法在第二章我们介绍到在回归模型上最常见的评定标准是均方根误差RMSE详见公式 2-1因此为了训练一个线性回归模型你需要找到一个θ它使得均方根误差标准误差达到最小值

实践过程中最小化均方误差比最小化均方根误差更加的简单这两个过程会得到相同的θ因为函数在最小值时候的自变量同样能使函数的方根运算得到最小值

在训练集X上使用公式 4-3 来计算线性回归假设h[θ]的均方差MSE

公式 4-3线性回归模型的 MSE 损失函数

公式中符号的含义大多数都在第二章详见符号进行了说明不同的是为了突出模型的参数向量θ使用h[θ]来代替h以后的使用中为了公式的简洁使用MSE(θ)来代替MSE(X, h[θ])

正规方程求解MSE

为了找到最小化损失函数的θ可以采用公式解换句话说就是可以通过解正规方程直接得到最后的结果

公式 4-4正规方程

  • θ_hat指最小化损失θ的值
  • y是一个向量其包含了y^(1)y^(m)的值

正规方程求解的方法问题在于:

正规方程需要计算矩阵X^T · X的逆它是一个n * n的矩阵n是特征的个数这样一个矩阵求逆的运算复杂度大约在O(n^2.4)O(n^3)之间具体值取决于计算方式换句话说如果你将你的特征个数翻倍的话其计算时间大概会变为原来的 5.32^2.4到 82^3

提示

当特征的个数较大的时候例如特征数量为 100000正规方程求解将会非常慢

有利的一面是这个方程在训练集上对于每一个实例来说是线性的其复杂度为O(m)因此只要有能放得下它的内存空间它就可以对大规模数据进行训练同时一旦你得到了线性回归模型通过解正规方程或者其他的算法进行预测是非常快的因为模型中计算复杂度对于要进行预测的实例数量和特征个数都是线性的 换句话说当实例个数变为原来的两倍多的时候或特征个数变为原来的两倍多预测时间也仅仅是原来的两倍多

如何训练模型?梯度下降

梯度下降是一种非常通用的优化算法它能够很好地解决一系列问题梯度下降的整体思路是通过的迭代来逐渐调整参数使得损失函数达到最小值

假设浓雾下你迷失在了大山中你只能感受到自己脚下的坡度为了最快到达山底一个最好的方法就是沿着坡度最陡的地方下山这其实就是梯度下降所做的它计算误差函数关于参数向量Θ的局部梯度同时它沿着梯度下降的方向进行下一次迭代当梯度值为零的时候就达到了误差函数最小值

具体来说开始时需要选定一个随机的Θ这个值称为随机初始值然后逐渐去改进它每一次变化一小步每一步都试着降低损失函数例如均方差损失函数直到算法收敛到一个最小值如图4-3

学习率learning rate

在梯度下降中一个重要的参数是步长超参数学习率的值决定了步长的大小如果学习率太小必须经过多次迭代算法才能收敛这是非常耗时的如图4-4

另一方面如果学习率太大你将跳过最低点到达山谷的另一面可能下一次的值比上一次还要大这可能使的算法是发散的函数值变得越来越大永远不可能找到一个好的答案如图4-5

最后并不是所有的损失函数看起来都像一个规则的碗它们可能是洞山脊高原和各种不规则的地形使它们收敛到最小值非常的困难 图4-6 显示了梯度下降的两个主要挑战如果随机初始值选在了图像的左侧则它将收敛到局部最小值这个值要比全局最小值要大 如果它从右侧开始那么跨越高原将需要很长时间如果你早早地结束训练你将永远到不了全局最小值

事实上损失函数的图像呈现碗状但是不同特征的取值范围相差较大的时这个碗可能是细长的图4-7 展示了梯度下降在不同训练集上的表现在左图中特征 1 和特征 2 有着相同的数值尺度在右图中特征 1 比特征 2 的取值要小的多由于特征 1 较小因此损失函数改变时Θ[1]会有较大的变化于是这个图像会在Θ[1]轴方向变得细长

当我们使用梯度下降的时候应该确保所有的特征有着相近的尺度范围例如使用 Scikit Learn 的 StandardScaler否则它将需要很长的时间才能够收敛

批量梯度下降

使用梯度下降的过程中你需要计算每一个Θ[j]下损失函数的梯度换句话说你需要计算当Θ[j]变化一点点时损失函数改变了多少这称为偏导数它就像当你面对东方的时候问“我脚下的坡度是多少然后面向北方的时候问同样的问题如果你能想象一个超过三维的宇宙可以对所有的方向都这样做公式 4-5 计算关于Θ[j]的损失函数的偏导数记为∂MSE/∂θ[j]

为了避免单独计算每一个梯度你也可以使用公式 4-6 来一起计算它们梯度向量记为ᐁ[θ]MSE(θ)其包含了损失函数所有的偏导数每个模型参数只出现一次

随机梯度下降

批量梯度下降的最要问题是计算每一步的梯度时都需要使用整个训练集这导致在规模较大的数据集上其会变得非常的慢与其完全相反的随机梯度下降在每一步的梯度计算上只随机选取训练集中的一个样本很明显由于每一次的操作都使用了非常少的数据这样使得算法变得非常快由于每一次迭代只需要在内存中有一个实例这使随机梯度算法可以在大规模训练集上使用

虽然随机性可以很好的跳过局部最优值但同时它却不能达到最小值解决这个难题的一个办法是逐渐降低学习率 开始时走的每一步较大这有助于快速前进同时跳过局部最小值然后变得越来越小从而使算法到达全局最小值 这个过程被称为模拟退火因为它类似于熔融金属慢慢冷却的冶金学退火过程 决定每次迭代的学习率的函数称为learning schedule 如果学习速度降低得过快你可能会陷入局部最小值甚至在到达最小值的半路就停止了 如果学习速度降低得太慢你可能在最小值的附近长时间摆动同时如果过早停止训练最终只会出现次优解

由于每个实例的选择是随机的有的实例可能在每一代中都被选到这样其他的实例也可能一直不被选到如果你想保证每一代迭代过程算法可以遍历所有实例一种方法是将训练集打乱重排然后选择一个实例之后再继续打乱重排以此类推一直进行下去但是这样收敛速度会非常的慢

小批量梯度下降

最后一个梯度下降算法我们将介绍小批量梯度下降算法一旦你理解了批量梯度下降和随机梯度下降再去理解小批量梯度下降是非常简单的在迭代的每一步批量梯度使用整个训练集随机梯度时候用仅仅一个实例在小批量梯度下降中它则使用一个随机的小型实例集它比随机梯度的主要优点在于你可以通过矩阵运算的硬件优化得到一个较好的训练表现尤其当你使用 GPU 进行运算的时候

学习曲线/过拟合/欠拟合

如果你使用一个高阶的多项式回归你可能发现它的拟合程度要比普通的线性回归要好的多例如图4-14 使用一个 300 阶的多项式模型去拟合之前的数据集并同简单线性回归2 阶的多项式回归进行比较注意 300 阶的多项式模型如何摆动以尽可能接近训练实例

当然这种高阶多项式回归模型在这个训练集上严重过拟合了线性模型则欠拟合在这个训练集上二次模型有着较好的泛化能力那是因为在生成数据时使用了二次模型但是一般我们不知道这个数据生成函数是什么那我们该如何决定我们模型的复杂度呢你如何告诉我你的模型是过拟合还是欠拟合

  • 如果一个模型在训练集上表现良好通过交叉验证指标却得出其泛化能力很差那么你的模型就是过拟合了如果在这两方面都表现不好那么它就是欠拟合了这种方法可以告诉我们你的模型是太复杂还是太简单了

  • 画出模型在训练集上的表现同时画出以训练集规模为自变量的训练集函数为了得到图像需要在训练集的不同规模子集上进行多次训练下面的代码定义了一个函数用来画出给定训练集后的模型学习曲线

上面的曲线表现了一个典型的欠拟合模型两条曲线都到达高原地带并趋于稳定并且最后两条曲线非常接近同时误差值非常大

这幅图值得我们深究首先我们观察训练集的表现当训练集只有一两个样本的时候模型能够非常好的拟合它们这也是为什么曲线是从零开始的原因但是当加入了一些新的样本的时候训练集上的拟合程度变得难以接受出现这种情况有两个原因一是因为数据中含有噪声另一个是数据根本不是线性的因此随着数据规模的增大误差也会一直增大直到达到高原地带并趋于稳定在之后继续加入新的样本模型的平均误差不会变得更好或者更差我们继续来看模型在验证集上的表现当以非常少的样本去训练时模型不能恰当的泛化也就是为什么验证误差一开始是非常大的当训练样本变多的到时候模型学习的东西变多验证误差开始缓慢的下降但是一条直线不可能很好的拟合这些数据因此最后误差会到达在一个高原地带并趋于稳定最后和训练集的曲线非常接近

这幅图像和之前的有一点点像但是其有两个非常重要的不同点

  • 在训练集上误差要比线性回归模型低的多

  • 图中的两条曲线之间有间隔这意味模型在训练集上的表现要比验证集上好的多这也是模型过拟合的显著特点当然如果你使用了更大的训练数据这两条曲线最后会非常的接近

误差来源分析

改善模型过拟合的一种方法是提供更多的训练数据直到训练误差和验证误差相等
在统计和机器学习领域有个重要的理论一个模型的泛化误差由三个不同误差的和决定

  • 偏差泛化误差的这部分误差是由于错误的假设决定的例如实际是一个二次模型你却假设了一个线性模型一个高偏差的模型最容易出现欠拟合

  • 方差这部分误差是由于模型对训练数据的微小变化较为敏感一个多自由度的模型更容易有高的方差例如一个高阶多项式模型因此会导致模型过拟合

  • 不可约误差这部分误差是由于数据本身的噪声决定的降低这部分误差的唯一方法就是进行数据清洗例如修复数据源修复坏的传感器识别和剔除异常值


正则化

降低模型的过拟合的好方法是正则化这个模型即限制它模型有越少的自由度就越难以拟合数据例如正则化一个多项式模型一个简单的方法就是减少多项式的阶数

对于一个线性模型正则化的典型实现就是约束模型中参数的权重 接下来我们将介绍三种不同约束权重的方法Ridge 回归Lasso 回归和 Elastic Net

岭回归

岭回归也称为 Tikhonov 正则化是线性回归的正则化版在损失函数上直接加上一个正则项α Σ θ[i]^2, i = 1 -> n这使得学习算法不仅能够拟合数据而且能够使模型的参数权重尽量的小注意到这个正则项只有在训练过程中才会被加到损失函数当得到完成训练的模型后我们应该使用没有正则化的测量方法去评价模型的表现

训练过程使用的损失函数和测试过程使用的评价函数是不一样的除了正则化还有一个不同训练时的损失函数应该在优化过程中易于求导

岭回归损失函数:

超参数α决定了你想正则化这个模型的强度如果α = 0那此时的岭回归便变为了线性回归如果α非常的大所有的权重最后都接近于零最后结果将是一条穿过数据平均值的水平直线对于梯度下降来说仅仅在均方差梯度向量公式 4-6加上一项αw


图4-17 展示了在相同线性数据上使用不同α值的岭回归模型最后的表现左图中使用简单的岭回归模型最后得到了线性的预测右图中的数据首先使用 10 阶的PolynomialFearures进行扩展然后使用StandardScaler进行缩放最后将岭模型应用在处理过后的特征上这就是带有岭正则项的多项式回归注意当α增大的时候导致预测曲线变得扁平即少了极端值多了一般值这样减少了模型的方差却增加了模型的偏差

Lasso回归

Lasso 回归也称 Least Absolute Shrinkage或者 Selection Operator Regression是另一种正则化版的线性回归就像岭回归那样它也在损失函数上添加了一个正则化项但是它使用权重向量的l1范数而不是权重向量l2范数平方的一半如公式 4-10

Lasso 回归的损失函数:


图4-18 展示了和图4-17 相同的事情在相同线性数据上使用不同α值的岭回归模型最后的表现左图中使用简单的岭回归模型最后得到了线性的预测右图中的数据首先使用 10 阶的PolynomialFearures进行扩展然后使用StandardScaler进行缩放最后用 Lasso 模型代替了 Ridge 模型同时调小了α的值

Lasso回归的一个重要特征是它倾向于完全消除最不重要的特征的权重即将它们设置为零例如右图中的虚线所示α = 10^(-7)曲线看起来像一条二次曲线而且几乎是线性的这是因为所有的高阶多项特征都被设置为零换句话说Lasso 回归自动的进行特征选择同时输出一个稀疏模型

弹性网络ElasticNet

弹性网络介于 Ridge 回归和 Lasso 回归之间它的正则项是 Ridge 回归和 Lasso 回归正则项的简单混合同时你可以控制它们的混合率rr = 0弹性网络就是 Ridge 回归r = 1其就是 Lasso 回归具体表示如公式 4-12

弹性网络损失函数

那么我们该如何选择线性回归岭回归Lasso 回归弹性网络呢一般来说有一点正则项的表现更好因此通常你应该避免使用简单的线性回归岭回归是一个很好的首选项但是如果你的特征仅有少数是真正有用的你应该选择 Lasso 和弹性网络就像我们讨论的那样它两能够将无用特征的权重降为零一般来说弹性网络的表现要比 Lasso 好因为当特征数量比样本的数量大的时候或者特征之间有很强的相关性时Lasso 可能会表现的不规律

_EarlyStopping

对于迭代学习算法有一种非常特殊的正则化方法就像梯度下降在验证错误达到最小值时立即停止训练那样我们称为早期停止法图4-20 表示使用批量梯度下降来训练一个非常复杂的模型一个高阶多项式回归模型随着训练的进行算法一直学习它在训练集上的预测误差RMSE自然而然的下降然而一段时间后验证误差停止下降并开始上升这意味着模型在训练集上开始出现过拟合一旦验证错误达到最小值便提早停止训练

机器学习基础

支持向量机SVM

线性SVM分类

支持向量机SVM是个非常强大并且有多种功能的机器学习模型能够做线性或者非线性的分类回归甚至异常值检测机器学习领域中最为流行的模型之一是任何学习机器学习的人必备的工具SVM 特别适合应用于复杂但中小规模数据集的分类问题

SVM 的基本思想能够用一些图片来解释得很好图 5-1 展示了我们在第 4 章结尾处介绍的鸢尾花数据集的一部分这两个种类能够被非常清晰非常容易的用一条直线分开即线性可分的左边的图显示了三种可能的线性分类器的判定边界其中用虚线表示的线性模型判定边界很差甚至不能正确地划分类别另外两个线性模型在这个数据集表现的很好但是它们的判定边界很靠近样本点在新的数据上可能不会表现的很好相比之下右边图中 SVM 分类器的判定边界实线不仅分开了两种类别而且还尽可能地远离了最靠近的训练数据点你可以认为 SVM 分类器在两种类别之间保持了一条尽可能宽敞的街道图中平行的虚线其被称为最大间隔分类注意到添加更多的样本点在街道外并不会影响到判定边界因为判定边界是由位于街道边缘的样本点确定的这些样本点被称为支持向量

SVM 对特征缩放比较敏感可以看到图 5-2左边的图中垂直的比例要更大于水平的比例所以最宽的街道接近水平但对特征缩放后例如使用 Scikit-Learn 的 StandardScaler判定边界看起来要好得多如右图

软间隔分类

如果我们严格地规定所有的数据都不在街道都在正确地两边称为硬间隔分类.
硬间隔分类有两个问题

  1. 只对线性可分的数据起作用
  2. 对异常点敏感
    图 5-3 显示了只有一个异常点的鸢尾花数据集左边的图中很难找到硬间隔右边的图中判定边界和我们之前在图 5-1 中没有异常点的判定边界非常不一样它很难一般化

为了避免上述的问题我们更倾向于使用更加软性的模型目的在保持街道尽可能大和避免间隔违规例如数据点出现在街道中央或者甚至在错误的一边之间找到一个良好的平衡这就是软间隔分类

在 Scikit-Learn 库的 SVM 类你可以用C超参数惩罚系数来控制这种平衡较小的C会导致更宽的街道但更多的间隔违规图 5-4 显示了在非线性可分隔的数据集上两个软间隔 SVM 分类器的判定边界左边图中使用了较大的C导致更少的间隔违规但是间隔较小右边的图使用了较小的C间隔变大了但是许多数据点出现在了街道然而第二个分类器似乎泛化地更好事实上在这个训练数据集上减少了预测错误因为实际上大部分的间隔违规点出现在了判定边界正确的一侧

如果你的 SVM 模型过拟合你可以尝试通过减小超参数C去调整

非线性SVM分类

尽管线性 SVM 分类器在许多案例上表现得出乎意料的好但是很多数据集并不是线性可分的一种处理非线性数据集方法是增加更多的特征例如多项式特征正如你在第 4 章所做的那样在某些情况下可以变成线性可分的数据在图 5-5 的左图中它只有一个特征x1的简单的数据集正如你看到的该数据集不是线性可分的但是如果你增加了第二个特征 x2=(x1)^2产生的 2D 数据集就能很好的线性可分

为了实施这个想法通过 Scikit-Learn你可以创建一个流水线Pipeline去包含多项式特征PolynomialFeatures变换在 121 页的Polynomial Regression中讨论然后一个StandardScalerLinearSVC

from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures

polynomial_svm_clf = Pipeline((
        ("poly_features", PolynomialFeatures(degree=3)),
        ("scaler", StandardScaler()),
        ("svm_clf", LinearSVC(C=10, loss="hinge"))
    ))

polynomial_svm_clf.fit(X, y)

多项式核

添加多项式特征很容易实现不仅仅在 SVM在各种机器学习算法都有不错的表现但是低次数的多项式不能处理非常复杂的数据集而高次数的多项式却产生了大量的特征会使模型变得慢

幸运的是当你使用 SVM 时你可以运用一个被称为核技巧kernel trick的神奇数学技巧它可以取得就像你添加了许多多项式甚至有高次数的多项式一样好的结果所以不会大量特征导致的组合爆炸因为你并没有增加任何特征

from sklearn.svm import SVC
poly_kernel_svm_clf = Pipeline((
        ("scaler", StandardScaler()),
        ("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5))
    ))
poly_kernel_svm_clf.fit(X, y)

3 阶的多项式核训练了一个 SVM 分类器即图 5-7 的左图右图是使用了 10 阶的多项式核 SVM 分类器很明显如果你的模型过拟合你可以减小多项式核的阶数相反的如果是欠拟合你可以尝试增大它超参数coef0控制了高阶多项式与低阶多项式对模型的影响

通用的方法是用网格搜索grid search 见第 2 章去找到最优超参数首先进行非常粗略的网格搜索一般会很快然后在找到的最佳值进行更细的网格搜索

增加相似特征

另一种解决非线性问题的方法是使用相似函数similarity funtion计算每个样本与特定地标landmark的相似度例如让我们来看看前面讨论过的一维数据集并在x1=-2x1=1之间增加两个地标图 5-8 左图接下来我们定义一个相似函数即高斯径向基函数Gaussian Radial Basis FunctionRBF设置γ = 0.3见公式 5-1

RBF高斯径向基函数

你可能想知道如何选择地标最简单的方法是在数据集中的每一个样本的位置创建地标这将产生更多的维度从而增加了转换后数据集是线性可分的可能性但缺点是m个样本n个特征的训练集被转换成了m个实例m个特征的训练集假设你删除了原始特征这样一来如果你的训练集非常大你最终会得到同样大的特征

高斯 RBF 核

就像多项式特征法一样相似特征法对各种机器学习算法同样也有不错的表现但是在所有额外特征上的计算成本可能很高特别是在大规模的训练集上然而 技巧再一次显现了它在 SVM 上的神奇之处高斯核让你可以获得同样好的结果成为可能就像你在相似特征法添加了许多相似特征一样但事实上你并不需要在 RBF 添加它们我们使用 SVC 类的高斯 RBF 核来检验一下

rbf_kernel_svm_clf = Pipeline((
        ("scaler", StandardScaler()),
        ("svm_clf", SVC(kernel="rbf", gamma=5, C=0.001))
    ))
rbf_kernel_svm_clf.fit(X, y)

这个模型在图 5-9 的左下角表示其他的图显示了用不同的超参数gamma (γ)C训练的模型增大γ使钟型曲线更窄图 5-8 左图导致每个样本的影响范围变得更小即判定边界最终变得更不规则在单个样本周围环绕相反的较小的γ值使钟型曲线更宽样本有更大的影响范围判定边界最终则更加平滑所以γ是可调整的超参数如果你的模型过拟合你应该减小γ若欠拟合则增大γ与超参数C相似

计算复杂度

LinearSVC类基于liblinear它实现了线性 SVM 的优化算法它并不支持核技巧但是它样本和特征的数量几乎是线性的训练时间复杂度大约为O(m × n)

如果你要非常高的精度这个算法需要花费更多时间这是由容差值超参数ϵ在 Scikit-learn 称为tol控制的大多数分类任务中使用默认容差值的效果是已经可以满足一般要求

SVC 类基于libsvm它实现了支持核技巧的算法训练时间复杂度通常介于 O(m^2 × n)O(m^3 × n)之间. 不幸的是这意味着当训练样本变大时它将变得极其慢例如成千上万个样本这个算法对于复杂但小型或中等数量的数据集表现是完美的然而它能对特征数量很好的缩放尤其对稀疏特征来说sparse features即每个样本都有一些非零特征在这个情况下算法对每个样本的非零特征的平均数量进行大概的缩放

表 5-1 对 Scikit-learn 的 SVM 分类模型进行比较

SVM回归

SVM 算法应用广泛不仅仅支持线性和非线性的分类任务还支持线性和非线性的回归任务SVM回归核心在于逆转我们的目标限制间隔违规的情况下不是试图在两个类别之间找到尽可能大的街道即间隔SVM 回归任务是限制间隔违规情况下尽量放置更多的样本在街道街道的宽度由超参数ϵ控制图 5-10 显示了在一些随机生成的线性数据上两个线性 SVM 回归模型的训练情况一个有较大的间隔ϵ=1.5另一个间隔较小ϵ=0.5

你可以使用 Scikit-Learn 的LinearSVR类去实现线性 SVM 回归下面的代码产生的模型在图 5-10 左图训练数据需要被中心化和标准化

from sklearn.svm import LinearSVR
svm_reg = LinearSVR(epsilon=1.5)
svm_reg.fit(X, y)

处理非线性回归任务你可以使用核化的 SVM 模型比如图 5-11 显示了在随机二次方的训练集使用二次方多项式核函数的 SVM 回归左图是较小的正则化即更大的C右图则是更大的正则化即小的C

from sklearn.svm import SVR

svm_poly_reg = SVR(kernel="poly", degree=2, C=100, epsilon=0.1)
svm_poly_reg.fit(X, y)

决策树

和支持向量机一样 决策树是一种多功能机器学习算法 即可以执行分类任务也可以执行回归任务 甚至包括多输出multioutput任务.它是一种功能很强大的算法可以对很复杂的数据集进行拟合例如在第二章中我们对加利福尼亚住房数据集使用决策树回归模型进行训练就很好的拟合了数据集实际上是过拟合决策树也是随机森林的基本组成部分见第 7 章而随机森林是当今最强大的机器学习算法之一

分类

理解决策树我们需要先构建一个决策树并亲身体验它到底如何进行预测 接下来的代码就是在我们熟知的鸢尾花数据集上进行一个决策树分类器的训练

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
iris = load_iris()
X = iris.data[:, 2:] # petal length and width 
y = iris.target
tree_clf = DecisionTreeClassifier(max_depth=2)
tree_clf.fit(X, y)

我们的第一个决策树如图 6-1


决策树的众多特性之一就是 它不需要太多的数据预处理 尤其是不需要进行特征的缩放或者归一化

现在让我们来看看在图 6-1 中的树是如何进行预测的假设你找到了一朵鸢尾花并且想对它进行分类你从根节点开始深度为 0顶部该节点询问花朵的花瓣长度是否小于 2.45 厘米如果是您将向下移动到根的左侧子节点深度为 1左侧 在这种情况下它是一片叶子节点即它没有任何子节点所以它不会问任何问题你可以方便地查看该节点的预测类别决策树预测你的花是 Iris-Setosaclass = setosa

现在假设你找到了另一朵花但这次的花瓣长度是大于 2.45 厘米的你必须向下移动到根的右侧子节点深度为 1右侧而这个节点不是叶节点所以它会问另一个问题花瓣宽度是否小于 1.75 厘米 如果是那么你的花很可能是一个 Iris-Versicolor深度为 2 如果不是那很可能一个 Iris-Virginica深度为 2


图 6-2 显示了决策树的决策边界粗的垂直线代表根节点深度为 0的决定边界花瓣长度为 2.45 厘米由于左侧区域是纯的只有 Iris-Setosa所以不能再进一步分裂然而右边的区域是不纯的所以深度为 1 的右边节点在花瓣宽度为 1.75 厘米处分裂用虚线表示又由于max_depth设置为 2决策树在那里停了下来但是如果将max_depth设置为 3两个深度为 2 的节点每个都将会添加另一个决策边界用虚线表示

决策树非常直观他们的决定很容易被解释这种模型通常被称为白盒模型相反随机森林或神经网络通常被认为是黑盒模型他们能做出很好的预测并且您可以轻松检查它们做出这些预测过程中计算的执行过程然而人们通常很难用简单的术语来解释为什么模型会做出这样的预测

_CART训练算法

Scikit-Learn 用分裂回归树Classification And Regression Tree简称 CART算法训练决策树也叫增长树这种算法思想真的非常简单首先使用单个特征k和阈值t[k]例如花瓣长度≤2.45cm将训练集分成两个子集它如何选择kt[k]它寻找到能够产生最纯粹的子集一对(k, t[k])然后通过子集大小加权计算算法会尝试最小化成本函数

当它成功的将训练集分成两部分之后 它将会继续使用相同的递归式逻辑继续的分割子集然后是子集的子集当达到预定的最大深度之后将会停止分裂max_depth超参数决定或者是它找不到可以继续降低不纯度的分裂方法的时候几个其他超参数min_samples_splitmin_samples_leafmin_weight_fraction_leafmax_leaf_nodes控制了其他的停止生长条件

找到最优树是一个 NP 完全问题自行百度它需要O(exp^m)时间即使对于相当小的训练集也会使问题变得棘手 这就是为什么我们必须设置一个合理的而不是最佳的解决方案

在建立好决策树模型后 做出预测需要遍历决策树 从根节点一直到叶节点决策树通常近似左右平衡因此遍历决策树需要经历大致O(log2(m)) 个节点由于每个节点只需要检查一个特征的值因此总体预测复杂度仅为O(log2(m))与特征的数量无关 所以即使在处理大型训练集时预测速度也非常快

然而训练算法的时候训练和预测不同需要比较所有特征如果设置了max_features会更少一些

在每个节点的所有样本上就有了O(n×m log(m))的训练复杂度对于小型训练集少于几千例Scikit-Learn 可以通过预先设置数据presort = True来加速训练但是这对于较大训练集来说会显着减慢训练速度

正则化

如果不添加约束树结构模型通常将根据训练数据调整自己使自身能够很好的拟合数据而这种情况下大多数会导致模型过拟合

DecisionTreeClassifier类还有一些其他的参数用于限制树模型的形状:

min_samples_split节点在被分裂之前必须具有的最小样本数min_samples_leaf叶节点必须具有的最小样本数min_weight_fraction_leafmin_samples_leaf相同但表示为加权总数的一小部分实例max_leaf_nodes叶节点的最大数量和 max_features在每个节点被评估是否分裂的时候具有的最大特征数量增加min_* hyperparameters或者减少max_* hyperparameters会使模型正则化

一些其他算法的工作原理是在没有任何约束条件下训练决策树模型让模型自由生长然后再对不需要的节点进行剪枝

图 6-3 显示了对moons数据集在第 5 章介绍过进行训练生成的两个决策树模型左侧的图形对应的决策树使用默认超参数生成没有限制生长条件右边的决策树模型设置为min_samples_leaf=4很明显左边的模型过拟合了而右边的模型泛用性更好

回归

决策树也能够执行回归任务让我们使用 Scikit-Learn 的DecisionTreeRegressor类构建一个回归树让我们用max_depth = 2在具有噪声的二次项数据集上进行训练

from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(max_depth=2)
tree_reg.fit(X, y)

这棵树看起来非常类似于你之前建立的分类树它的主要区别在于它不是预测每个节点中的样本所属的分类而是预测一个具体的数值例如假设您想对x[1] = 0.6的新实例进行预测从根开始遍历树最终到达预测值等于 0.1106 的叶节点该预测仅仅是与该叶节点相关的 110 个训练实例的平均目标值而这个预测结果在对应的 110 个实例上的均方误差MSE等于 0.0151


CART 算法的工作方式与之前处理分类模型基本一样不同之处在于现在不再以最小化不纯度的方式分割训练集而是试图以最小化 MSE 的方式分割训练集公式 6-4 显示了成本函数该算法试图最小化这个成本函数


和处理分类任务时一样决策树在处理回归问题的时候也容易过拟合如果不添加任何正则化默认的超参数你就会得到图 6-6 左侧的预测结果显然过度拟合的程度非常严重而当我们设置了min_samples_leaf = 10相对就会产生一个更加合适的模型了就如图 6-6 所示的那样

不稳定性

它很容易理解和解释易于使用且功能丰富而强大然而它也有一些限制首先你可能已经注意到了==决策树很喜欢设定正交化的决策边界==所有边界都是和某一个轴相垂直的这使得它对训练数据集的旋转很敏感例如图 6-7 显示了一个简单的线性可分数据集在左图中决策树可以轻易的将数据分隔开但是在右图中当我们把数据旋转了 45° 之后决策树的边界看起来变的格外复杂尽管两个决策树都完美的拟合了训练数据右边模型的泛化能力很可能非常差

解决这个难题的一种方式是使用 PCA 主成分分析第八章这样通常能使训练结果变得更好一些



更加通俗的讲==决策时的主要问题是它对训练数据的微小变化非常敏感==举例来说我们仅仅从鸢尾花训练数据中将最宽的 Iris-Versicolor 拿掉花瓣长 4.8 厘米宽 1.8 厘米然后重新训练决策树模型你可能就会得到图 6-8 中的模型正如我们看到的那样决策树有了非常大的变化原来的如图 6-2事实上由于 Scikit-Learn 的训练算法是非常随机的即使是相同的训练数据你也可能得到差别很大的模型除非你设置了随机数种子

集成学习/随机森林

假设你去随机问很多人一个很复杂的问题然后把它们的答案合并起来通常情况下你会发现这个合并的答案比一个专家的答案要好这就叫做_群体智慧_同样的如果你合并了一组分类器的预测像分类或者回归你也会得到一个比单一分类器更好的预测结果这一组分类器就叫做集成因此这个技术就叫做集成学习一个集成学习算法就叫做集成方法

你可以训练一组决策树分类器每一个都在一个随机的训练集上为了去做预测你必须得到所有单一树的预测值然后通过投票例如第六章的练习来预测类别例如一种决策树的集成就叫做随机森林它除了简单之外也是现今存在的最强大的机器学习算法之一

投票(分类)

假设你已经训练了一些分类器每一个都有 80% 的准确率你可能有了一个逻辑斯蒂回归或一个 SVM或一个随机森林或者一个 KNN或许还有更多详见图 7-1一个非常简单去创建一个更好的分类器的方法就是去整合每一个分类器的预测然后经过投票去预测分类这种分类器就叫做硬投票分类器详见图 7-2
令人惊奇的是这种投票分类器得出的结果经常会比集成中最好的一个分类器结果更好事实上即使每一个分类器都是一个弱学习器意味着它们也就比瞎猜好点集成后仍然是一个强学习器高准确率只要有足够数量的弱学习者他们就足够多样化


>>> from sklearn.ensemble import RandomForestClassifier 
>>> from sklearn.ensemble import VotingClassifier 
>>> from sklearn.linear_model import LogisticRegression 
>>> from sklearn.svm import SVC
>>> log_clf = LogisticRegression() 
>>> rnd_clf = RandomForestClassifier() 
>>> svm_clf = SVC()
>>> voting_clf = VotingClassifier(estimators=[('lr', log_clf), ('rf', rnd_clf), 
>>>   ('svc', svm_clf)],voting='hard') 
>>> voting_clf.fit(X_train, y_train)
>>> from sklearn.metrics import accuracy_score 
>>> for clf in (log_clf, rnd_clf, svm_clf, voting_clf): 
>>>     clf.fit(X_train, y_train) 
>>>     y_pred = clf.predict(X_test) 
>>>     print(clf.__class__.__name__, accuracy_score(y_test, y_pred)) 
LogisticRegression 0.864 
RandomForestClassifier 0.872 
SVC 0.888 
VotingClassifier 0.896 

如果所有的分类器都能够预测类别的概率例如他们有一个predict_proba()方法那么你就可以让 sklearn 以最高的类概率来预测这个类平均在所有的分类器上这种方式叫做软投票他经常比硬投票表现的更好因为它给予高自信的投票更大的权重你可以通过把voting="hard"设置为voting="soft"来保证分类器可以预测类别概率

Bagging & Pasting

可以通过使用不同的训练算法去得到一些不同的分类器另一种方法就是对每一个分类器都使用相同的训练算法但是在不同的训练集上去训练它们有放回采样被称为装袋Baggingbootstrap aggregating 的缩写无放回采样称为粘贴pasting Bagging 和 Pasting 都允许在多个分类器上对训练集进行多次采样但只有 Bagging 允许对同一种分类器上对训练集进行进行多次采样采样和训练过程如图 7-4 所示

当所有的分类器被训练后集成可以通过对所有分类器结果的简单聚合来对新的实例进行预测聚合函数通常对分类是_统计模式_例如硬投票分类器或者对回归是平均每一个单独的分类器在如果在原始训练集上都是高偏差但是聚合降低了偏差和方差通常情况下集成的结果是有一个相似的偏差但是对比与在原始训练集上的单一分类器来讲有更小的方差

分类器可以通过不同的 CPU 核或其他的服务器一起被训练相似的分类器也可以一起被制作这就是为什么 Bagging 和 Pasting 是如此流行的原因之一它们的可扩展性很好

sklearn 为 Bagging 和 Pasting 提供了一个简单的 APIBaggingClassifier或者对于回归可以是BaggingRegressor接下来的代码训练了一个 500 个决策树分类器的集成每一个都是在数据集上有放回采样 100 个训练实例下进行训练这是 Bagging 的例子如果你想尝试 Pasting就设置bootstrap=Falsen_jobs参数告诉 sklearn 用于训练和预测所需要 CPU 核的数量-1 代表着 sklearn 会使用所有空闲核

>>>from sklearn.ensemble import BaggingClassifier 
>>>from sklearn.tree import DecisionTreeClassifier
>>>bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,        
>>>  max_samples=100, bootstrap=True, n_jobs=-1) 
>>>bag_clf.fit(X_train, y_train) 
>>>y_pred = bag_clf.predict(X_test)

Bootstrap 在每个预测器被训练的子集中引入了更多的分集所以 Bagging 结束时的偏差比 Pasting 更高但这也意味着预测因子最终变得不相关从而减少了集合的方差总体而言Bagging 通常会导致更好的模型这就解释了为什么它通常是首选的

对于 Bagging 来说一些实例可能被一些分类器重复采样但其他的有可能不会被采样BaggingClassifier默认采样BaggingClassifier默认是有放回的采样m个实例 bootstrap=True其中m是训练集的大小这意味着平均下来只有 63% 的训练实例被每个分类器采样剩下的 37% 个==没有被采样的训练实例就叫做 Out-of-Bag 实例==

在 sklearn 中你可以在训练后需要创建一个BaggingClassifier来自动评估时设置oob_score=True来自动评估接下来的代码展示了这个操作评估结果通过变量oob_score_来显示

>>> bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,bootstrap=True, n_jobs=-1, oob_score=True)
>>> bag_clf.fit(X_train, y_train) 
>>> bag_clf.oob_score_ 
0.93066666666666664 

随机森林

正如我们所讨论的随机森林是决策树的一种集成通常是通过 bagging 方法有时是 pasting 方法进行训练通常用max_samples设置为训练集的大小与建立一个BaggingClassifier然后把它放入DecisionTreeClassifier相反, 你可以使用更方便的也是对决策树优化够的RandomForestClassifier对于回归是RandomForestRegressor接下来的代码训练了带有 500 个树每个被限制为 16 叶子结点的决策森林

>>>from sklearn.ensemble import RandomForestClassifier
>>>rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1) 
>>>rnd_clf.fit(X_train, y_train)
>>>y_pred_rf = rnd_clf.predict(X_test)
>>>from sklearn.ensemble import RandomForestClassifier
>>>rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1) 
>>>rnd_clf.fit(X_train, y_train)
>>>y_pred_rf = rnd_clf.predict(X_test)

除了一些例外RandomForestClassifier使用DecisionTreeClassifier的所有超参数决定数怎么生长BaggingClassifier的超参数加起来来控制集成本身
随机森林算法在树生长时引入了额外的随机与在节点分裂时需要找到最好分裂特征相反详见第六章它在一个随机的特征集中找最好的特征它导致了树的差异性并且再一次用高偏差换低方差总的来说是一个更好的模型

  • 极随机树
    当你在随机森林上生长树时在每个结点分裂时只考虑随机特征集上的特征正如之前讨论过的一样相比于找到更好的特征我们可以通过使用对特征使用随机阈值使树更加随机像规则决策树一样
    这种极随机的树被简称为 Extremely Randomized Trees极随机树或者更简单的称为 Extra-Tree再一次用高偏差换低方差它还使得 Extra-Tree 比规则的随机森林更快地训练因为在每个节点上找到每个特征的最佳阈值是生长树最耗时的任务之一

最后如果你观察一个单一决策树重要的特征会出现在更靠近根部的位置而不重要的特征会经常出现在靠近叶子的位置因此我们可以通过计算一个特征在森林的全部树中出现的平均深度来预测特征的重要性sklearn 在训练后会自动计算每个特征的重要度你可以通过feature_importances_变量来查看结果

>>> from sklearn.datasets import load_iris 
>>> iris = load_iris() 
>>> rnd_clf = RandomForestClassifier(n_estimators=500, n_jobs=-1) 
>>> rnd_clf.fit(iris["data"], iris["target"]) 
>>> for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_): 
>>>     print(name, score) 
sepal length (cm) 0.112492250999
sepal width (cm) 0.0231192882825 
petal length (cm) 0.441030464364 
petal width (cm) 0.423357996355 

相似的如果你在 MNIST 数据及上训练随机森林分类器在第三章上介绍然后画出每个像素的重要性你可以得到图 7-6 的图片

Boosting

提升Boosting最初称为 假设增强 指的是可以将几个弱学习者组合成强学习者的集成方法对于大多数的提升方法的思想就是按顺序去训练分类器每一个都要尝试修正前面的分类 现如今已经有很多的提升方法了但最著名的就是 Adaboost适应性提升Adaptive Boosting 的简称Gradient Boosting梯度提升让我们先从 Adaboost 说起

AdaBoost

使一个新的分类器去修正之前分类结果的方法就是对之前分类结果不对的训练实例多加关注这导致新的预测因子越来越多地聚焦于这种情况这是 Adaboost 使用的技术举个例子去构建一个 Adaboost 分类器第一个基分类器例如一个决策树被训练然后在训练集上做预测在误分类训练实例上的权重就增加了第二个分类机使用更新过的权重然后再一次训练权重更新以此类推详见图 7-7

一旦所有的分类器都被训练后除了分类器根据整个训练集上的准确率被赋予的权重外集成预测就非常像 Bagging 和 Pasting 了序列学习技术的一个重要的缺点就是它不能被并行化只能按步骤因为每个分类器只能在之前的分类器已经被训练和评价后再进行训练因此它不像 Bagging 和 Pasting 一样

让我们详细看一下 Adaboost 算法

每一个实例的权重wi初始都被设为1/m第一个分类器被训练然后他的权重误差率r1在训练集上算出详见公式 7-1其中y_tilde[j]^(i)是第j个分类器对于第i实例的预测

  • 实例的权重
    实例权重

分类器的权重α[j]随后用公式 7-2 计算出来其中η是超参数学习率默认为 1分类器准确率越高它的权重就越高如果它只是瞎猜那么它的权重会趋近于 0然而如果它总是出错比瞎猜的几率都低它的权重会使负数

  • 分类器的权重

接下来实例的权重会按照公式 7-3 更新误分类的实例权重会被提升

  • 权重更新规则

随后所有实例的权重都被归一化例如被Σ w[i], i = 1 -> m整除
最后一个新的分类器通过更新过的权重训练整个过程被重复新的分类器权重被计算实例的权重被更新随后另一个分类器被训练以此类推当规定的分类器数量达到或者最好的分类器被找到后算法就会停止
为了进行预测Adaboost 通过分类器权重α[j]简单的计算了所有的分类器和权重预测类别会是权重投票中主要的类别详见公式 7-4其中N是分类器的数量

  • AdaBoost 分类器

    下来的代码训练了使用 sklearn 的AdaBoostClassifier基于 200 个决策树桩 Adaboost 分类器正如你说期待的对于回归也有AdaBoostRegressor一个决策树桩是max_depth=1的决策树 是一个单一的决策节点加上两个叶子结点这就是AdaBoostClassifier的默认基分类器
>>>from sklearn.ensemble import AdaBoostClassifier
>>>ada_clf = AdaBoostClassifier(DecisionTreeClassifier(max_depth=1), n_estimators=200,algorithm="SAMME.R", learning_rate=0.5) 
>>>ada_clf.fit(X_train, y_train)

梯度提升

另一个非常著名的提升算法是梯度提升与 Adaboost 一样梯度提升也是通过向集成中逐步增加分类器运行的每一个分类器都修正之前的分类结果然而它并不像 Adaboost 那样每一次迭代都更改实例的权重这个方法是去使用新的分类器去拟合前面分类器预测的 残差

GradientBoostingRegressor也支持指定用于训练每棵树的训练实例比例的超参数subsample例如如果subsample=0.25那么每个树都会在 25% 随机选择的训练实例上训练你现在也能猜出

Stacking

本章讨论的最后一个集成方法叫做 Stackingstacked generalization 的缩写这个算法基于一个简单的想法不使用琐碎的函数如硬投票来聚合集合中所有分类器的预测我们为什么不训练一个模型来执行这个聚合图 7-12 展示了这样一个在新的回归实例上预测的集成底部三个分类器每一个都有不同的值3.12.7 和 2.9然后最后一个分类器叫做 blender 或者_元学习器_把这三个分类器的结果当做输入然后做出最终决策3.0

Maxout

更快的优化器

训练一个非常大的深度神经网络可能会非常缓慢 到目前为止我们已经看到了四种加速训练的方法并且达到更好性能的方法对连接权重应用良好的初始化策略使用良好的激活函数使用批归一化以及重用预训练网络的部分 使用辅助任务或无监督学习 另一个速度提升的方法是使用更快的优化器 而不是常规的梯度下降优化器

动量优化Nesterov 加速梯度AdaGradRMSProp最后是 Adam 和 Nadam 优化

回想一下梯度下降只是通过直接减去损失函数J(θ)相对于权重θ的梯度∇θJ(θ)乘以学习率η来更新权重θ 等式是θ ← θ – η ∇[θ]J(θ)它不关心早期的梯度是什么 如果局部梯度很小则会非常缓慢

动量优化很关心以前的梯度在每次迭代时它将动量向量m乘以学习率η与局部梯度相加并且通过简单地减去该动量向量来更新权重参见公式 11-4 换句话说梯度用作加速度不用作速度 为了模拟某种摩擦机制避免动量过大该算法引入了一个新的超参数β简称为动量它必须设置在 0高摩擦和 1无摩擦之间 典型的动量值是 0.9

可以很容易验证如果梯度保持不变则最终速度权重更新的最大大小等于该梯度乘以学习率η乘以1/(1-β) 例如如果β = 0.9则最终速度等于学习率的梯度乘以 10 倍因此动量优化比梯度下降快 10 倍 这使动量优化比梯度下降快得多 特别是我们在第四章中看到当输入量具有非常不同的尺度时损失函数看起来像一个细长的碗见图 4-7 梯度下降速度很快但要花很长的时间才能到达底部 相反动量优化会越来越快地滚下山谷底部直到达到底部最佳在不使用批归一化的深度神经网络中较高层往往会得到具有不同的尺度的输入所以使用动量优化会有很大的帮助 它也可以帮助滚过局部最优值

Yurii Nesterov 在 1983 年提出的动量优化的一个小变体几乎总是比普通的动量优化更快 Nesterov 动量优化或 Nesterov 加速梯度Nesterov Accelerated GradientNAG的思想是测量损失函数的梯度不是在局部位置而是在动量方向稍微靠前见公式 11-5 与普通的动量优化的唯一区别在于梯度是在θ+βm而不是在θ处测量的

再次考虑细长碗的问题梯度下降从最陡峭的斜坡快速下降然后缓慢地下到谷底 如果算法能够早期检测到这个问题并且纠正它的方向来指向全局最优点那将是非常好的AdaGrad 算法通过沿着最陡的维度缩小梯度向量来实现这一点见公式 11-6

第一步将梯度的平方累加到向量s⊗符号表示元素级别相乘 这个向量化形式相当于向量s的每个元素s[i]计算s[i] ← s[i] + (∂J(θ)/∂θ[i])^2换一种说法每个s[i]累加损失函数对参数θ[i]的偏导数的平方 如果损失函数沿着第i维陡峭则在每次迭代时s[i]将变得越来越大

第二步几乎与梯度下降相同但有一个很大的不同梯度向量按比例(s+ε)^0.5缩小 符号表示元素分割ε是避免被零除的平滑项通常设置为10^(-10) 这个向量化的形式相当于所有θ[i]同时计算

前面看到AdaGrad 的风险是降速太快可能无法收敛到全局最优RMSProp 算法通过仅累积最近迭代而不是从训练开始以来的所有梯度的梯度来修正这个问题 它通过在第一步中使用指数衰减来实现见公式 11-7

如果你只看步骤 1, 2 和 5你会注意到 Adam 与动量优化和 RMSProp 的相似性 唯一的区别是第 1 步计算指数衰减的平均值而不是指数衰减的和但除了一个常数因子衰减平均值只是衰减和的1 - β1之外它们实际上是等效的动量衰减超参数β1通常初始化为 0.9而缩放衰减超参数β2通常初始化为 0.999 如前所述平滑项ε通常被初始化为一个很小的数例如10^(-7)

实际上由于 Adam 是一种自适应学习率算法如 AdaGrad 和 RMSProp所以对学习率超参数η的调整较少 您经常可以使用默认值η= 0.001使 Adam 相对于梯度下降更容易使用

表 11-2 比较了讨论过的优化器*是差**是平均***是好

正则化Regularization

有四个参数我可以拟合一个大象五个我可以让他摆动他的象鼻—— John von Neumann,cited by Enrico Fermi in Nature 427

有数千个参数甚至可以拟合整个动物园深度神经网络通常具有数以万计的参数有时甚至是数百万 有了这么多的参数网络拥有难以置信的自由度可以适应各种复杂的数据集 但是这个很大的灵活性也意味着它很容易过拟合训练集所以需要正则第 10 章用过了最好的正则方法之一早停_EarlyStopping

这一节会介绍其它一些最流行的神经网络正则化技术ℓ1 和 ℓ2 正则丢弃和最大范数正则

Dropout

丢弃是深度神经网络最流行的正则化方法之一 它由 Geoffrey Hinton 于 2012 年提出并在 Nitish Srivastava 等人的 2014 年论文中进一步详细描述并且已被证明是非常成功的即使是最先进的神经网络仅仅通过增加丢弃就可以提高 1-2% 的准确度

这是一个相当简单的算法在每个训练步骤中每个神经元包括输入神经元但不包括输出神经元都有一个暂时丢弃的概率p这意味着在这个训练步骤中它将被完全忽略 在下一步可能会激活见图 11-9 超参数p称为丢弃率通常设为 10% 到 50% 之间循环神经网络之间接近 20-30%在卷积网络中接近 40-50% 训练后神经元不会再丢失

这个具有破坏性的方法竟然行得通这是相当令人惊讶的如果一个公司的员工每天早上被告知要掷硬币来决定是否上班公司的表现会不会更好呢那么谁知道也许会公司显然将被迫适应这样的组织构架它不能依靠任何一个人操作咖啡机或执行任何其他关键任务所以这个专业知识将不得不分散在几个人身上员工必须学会与其他的许多同事合作而不仅仅是其中的一小部分该公司将变得更有弹性如果一个人离开了并没有什么区别目前还不清楚这个想法是否真的可以在公司实行但它确实对于神经网络是可行的神经元被丢弃训练不能与其相邻的神经元共适应他们必须尽可能让自己变得有用他们也不能过分依赖一些输入神经元;他们必须注意他们的每个输入神经元他们最终对输入的微小变化会不太敏感最后你会得到一个更稳定的网络泛化能力更强

CNN-CV

卷积神经网络CNN起源于人们对大脑视神经的研究自从 1980 年代CNN 就被用于图像识别了最近几年得益于算力提高训练数据大增以及第 11 章中介绍过的训练深度网络的技巧CNN 在一些非常复杂的视觉任务上取得了超出人类表现的进步CNN 支撑了图片搜索无人驾驶汽车自动视频分类等等另外CNN 也不再限于视觉比如语音识别和自然语言处理但这一章只介绍视觉应用

David H. Hubel 和 Torsten Wiesel 在 1958 年和 1959 年在猫的身上做了一系列研究对视神经中枢做了研究并在 1981 年荣获了诺贝尔生理学或医学奖特别的他们指出视神经中的许多神经元都有一个局部感受域local receptive field也就是说这些神经元只对有限视觉区域的刺激作反应

卷积层ConvalutionalLayer

卷积层是 CNN 最重要的组成部分第一个卷积层的神经元不是与图片中的每个像素点都连接而是只连着局部感受野的像素见图 14-2同理第二个卷积层中的每个神经元也只是连着第一层中一个小方形内的神经元这种架构可以让第一个隐藏层聚焦于小的低级特征然后在下一层组成大而高级的特征

神经元的权重可以表示为感受野大小的图片例如图 14-5 展示了两套可能的权重称为权重或卷积核第一个是黑色的方形中央有垂直白线7 × 7的矩阵除了中间的竖线都是 1其它地方是 0使用这个矩阵神经元只能注意到中间的垂直线因为其它地方都乘以 0 了第二个过滤器也是黑色的方形但是中间是水平的白线使用这个权重的神经元只会注意中间的白色水平线

如果卷积层的所有神经元使用同样的垂直过滤器和同样的偏置项给神经网络输入图 14-5 中最底下的图片卷积层输出的是左上的图片可以看到图中垂直的白线得到了加强其余部分变模糊了相似的右上的图是所有神经元都是用水平线过滤器的结果水平的白线加强了其余模糊了因此一层的全部神经元都用一个过滤器就能输出一个特征映射feature map特征映射可以高亮图片中最为激活过滤器的区域当然不用手动定义过滤器卷积层在训练中可以自动学习对任务最有用的过滤器上面的层则可以将简单图案组合为复杂图案

简单起见前面都是将每个卷积层的输出用 2D 层来表示的但真实的卷积层可能有多个过滤器过滤器数量由你确定每个过滤器会输出一个特征映射所以表示成 3D 更准确

下面代码使用 Scikit-Learn 的`load_sample_image()`加载了两张图片<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>一张是中国的寺庙<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>另一张是花<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>创建了两个过滤器<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>应用到了两张图片上<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>最后展示了一张特征映射<span class="bd-box"><h-char class="bd bd-beg"><h-inner>:</h-inner></h-char></span>

from sklearn.datasets import load_sample_image

# 加载样本图片
china = load_sample_image("china.jpg") / 255
flower = load_sample_image("flower.jpg") / 255
/images = np.array([china, flower])
batch_size, height, width, channels = /images.shape

# 创建两个过滤器
filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
filters[:, 3, :, 0] = 1  # 垂直线
filters[3, :, :, 1] = 1  # 水平线

outputs = tf.nn.conv2d(/images, filters, strides=1, padding="same")

plt.imshow(outputs[0, :, :, 1], cmap="gray") # 画出第 1 张图的第 2 个特征映射
plt.show() 

这个例子中<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>我们手动定义了过滤器<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>但在真正的 CNN 中<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>一般将过滤器定义为可以训练的变量<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>好让神经网络学习哪个过滤器的效果最好<span class="bd-box"><h-char class="bd bd-beg"><h-inner>。</h-inner></h-char></span>使用`keras.layers.Conv2D`层<span class="bd-box"><h-char class="bd bd-beg"><h-inner>:</h-inner></h-char></span>

conv = keras.layers.Conv2D(filters=32, kernel_size=3, strides=1,
                           padding="same", activation="relu") 

tf.nn.conv2d()函数这一行再多说说

  • /images是一个输入的小批次4D 张量

  • filters是过滤器的集合也是 4D 张量

  • strides等于 1也可以是包含 4 个元素的 1D 数组中间的两个元素是垂直和水平步长s[h]s[w]第一个和最后一个元素现在必须是 1以后可以用来指定批次步长跳过实例和通道步长跳过前一层的特征映射或通道

  • padding必须是"same""valid"

  • 如果设为"same"卷积层会使用零填充输出的大小是输入神经元的数量除以步长再取整例如如果输入大小是 13步长是 5见图 14-7则输出大小是 313 / 5 = 2.6再向上圆整为 3零填充尽量在输入上平均添加strides=1层的输出会和输入有相同的空间维度宽和高这就是same的来历

  • 如果设为"valid"卷积层就不使用零填充取决于步长可能会忽略图片的输入图片的底部或右侧的行和列见图 14-7简单举例只是显示了水平维度这意味着每个神经元的感受野位于严格确定的图片中的位置不会越界这就是valid的来历

CNN 的另一个问题是卷积层需要很高的内存特别是在训练时因为反向传播需要所有前向传播的中间值

池化层

明白卷积层的原理了池化层就容易多了池化层的目的是对输入图片做降采样收缩以降低计算负载内存消耗和参数的数量降低过拟合

和卷积层一样池化层中的每个神经元也是之和前一层的感受野里的有限个神经元相连和前面一样必须定义感受野的大小步长和填充类型但是池化神经元没有权重它所要做的是使用聚合函数比如最大或平均对输入做聚合图 14-8 展示了最为常用的最大池化层在这个例子中使用了一个2 × 2的池化核步长为 2没有填充只有感受野中的最大值才能进入下一层其它的就丢弃了

除了可以减少计算内存消耗参数数量最大池化层还可以带来对小偏移的不变性

在 CNN 中每隔几层就插入一个最大池化层可以带来更大程度的平移不变性另外最大池化层还能带来一定程度的旋转不变性和缩放不变性当预测不需要考虑平移旋转和缩放时比如分类任务不变性可以有一定益处

要创建平均池化层则使用AvgPool2D平均池化层和最大池化层很相似但计算的是感受野的平均值平均池化层在过去很流行但最近人们使用最大池化层更多因为最大池化层的效果更好

池化层还可以沿着深度方向做计算这可以让 CNN 学习到不同特征的不变性比如CNN 可以学习多个过滤器每个过滤器检测一个相同的图案的不同旋转比如手写字见图 14-10深度池化层可以使输出相同CNN 还能学习其它的不变性厚度明亮度扭曲颜色等等

数据增强

数据增强是通过生成许多训练实例的真实变种来人为增大训练集因为可以降低过拟合成为了一种正则化方法 生成出来的实例越真实越好最理想的情况人们无法区分增强图片是原生的还是增强过的简单的添加白噪声没有用增强修改要是可以学习的白噪声不可学习

例如==可以轻微偏移旋转缩放原生图再添加到训练集中==见图 14-12这么做可以使模型对位置方向和物体在图中的大小有更高的容忍度如果想让模型对不同光度有容忍度可以生成对比度不同的照片通常==还可以水平翻转图片文字不成不对称物体也不成==通过这些变换可以极大的增大训练集

CNN的典型架构

CNN 的典型架构是将几个卷积层叠起来每个卷积层后面跟着一个 ReLU 层然后再叠一个池化层然后再叠几个卷积层+ReLU接着再一个池化层以此类推图片在流经神经网络的过程中变得越来越小但得益于卷积层却变得越来越深特征映射变多了 见图 14-11在 CNN 的顶部还有一个常规的前馈神经网络由几个全连接层+ReLU组成最终层输出预测比如一个输出类型概率的 softmax 层

model = keras.models.Sequential([
    keras.layers.Conv2D(64, 7, activation="relu", padding="same",
                        input_shape=[28, 28, 1]),
    keras.layers.MaxPooling2D(2),
    keras.layers.Conv2D(128, 3, activation="relu", padding="same"),
    keras.layers.Conv2D(128, 3, activation="relu", padding="same"),
    keras.layers.MaxPooling2D(2),
    keras.layers.Conv2D(256, 3, activation="relu", padding="same"),
    keras.layers.Conv2D(256, 3, activation="relu", padding="same"),
    keras.layers.MaxPooling2D(2),
    keras.layers.Flatten(),
    keras.layers.Dense(128, activation="relu"),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(64, activation="relu"),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(10, activation="softmax")
]) 

我们先看看经典的 LeNet-5 架构1998然后看看三个 ILSVRC 竞赛的冠军AlexNet2012GoogLeNet2014ResNet2015

LeNet-5 也许是最广为人知的 CNN 架构前面提到过它是由 Yann LeCun 在 1998 年创造出来的被广泛用于手写字识别MNIST它的结构如下

AlexNet 和 LeNet-5 很相似只是更大更深是首个将卷积层堆叠起来的网络而不是在每个卷积层上再加一个池化层为了降低过拟合作者使用了两种正则方法首先F8 和 F9 层使用了丢弃丢弃率为 50%其次他们通过随机距离偏移训练图片水平翻转改变亮度做了数据增强

GoogLeNet 架构能取得这么大的进步很大的原因是它的网络比之前的 CNN 更深见图 14-14这归功于被称为创始模块inception module的子网络它可以让 GoogLeNet 可以用更高的效率使用参数

ResNet 的使用了极深的卷积网络共 152 层其它的变体有 1450 或 152 层反映了一个总体趋势模型变得越来越深参数越来越少训练这样的深度网络的方法是使用跳连接也被称为快捷连接输入信号添加到更高层的输出上

训练神经网络时目标是使网络可以对目标函数h(x)建模如果将输入x添加给网络的输出添加一个跳连接则网络就要对f(x) = h(x) – x建模而不是h(x)这被称为残差学习见图 14-15

目标检测

分类并定位图片中的多个物体的任务被称为目标检测几年之前使用的方法还是用定位单一目标的 CNN然后将其在图片上滑动

用这个简单的方法来做目标检测的效果相当不错但需要运行 CNN 好几次所以很慢幸好有一个更快的方法来滑动 CNN使用全卷积网络fully convolutional networkFCN

YOLO 是一个非常快且准确的目标检测框架是 Joseph Redmon 在 2015 年的一篇论文中提出的2016 年优化为 YOLOv22018 年优化为 YOLOv3速度快到甚至可以在实时视频中运行

语义分割

在语义分割中每个像素根据其所属的目标来进行分类例如汽车行人建筑物等等见图 14-26注意相同类的不同目标是不做区分的例如分割图片的右侧的所有自行车被归类为一坨像素这个任务的难点是当图片经过常规 CNN 时会逐渐丢失空间分辨率因为有的层的步长大于 1因此常规的 CNN 可以检测出图片的左下有一个人但不知道准确的位置

RNN

RNN 不是唯一能处理序列数据的神经网络对于小序列常规紧密网络也可以对于长序列比如音频或文本卷积神经网络也可以我们会讨论这两种方法本章最后会实现一个 WaveNet这是一种 CNN 架构可以处理上万个时间步的序列在第 16 章还会继续学习 RNN如何使用 RNN 来做自然语言处理和基于注意力机制的新架构

我们主要关注的是前馈神经网络激活仅从输入层到输出层的一个方向流动附录 E 中的几个网络除外 循环神经网络看起来非常像一个前馈神经网络除了它也有连接指向后方 让我们看一下最简单的 RNN由一个神经元接收输入产生一个输出并将输出发送回自己如图 15-1所示

每个循环神经元有两组权重一组用于输入x[t]另一组用于前一时间步长y[t - 1]的输出 我们称这些权重向量为w[x]w[y]如果考虑的是整个循环神经元层可以将所有权重向量放到两个权重矩阵中W[x]W[y]整个循环神经元层的输出可以用公式 15-1 表示b是偏差项φ(·)是激活函数例如 ReLU

一般情况下时间步t的单元状态记为h[t]h代表隐藏是该时间步的某些输入和前一时间步状态的函数h[t] = f(h[t - 1], x[t]) 其在时间步t的输出表示为y[t]也和前一状态和当前输入的函数有关

RNN 可以同时输入序列并输出序列见图 15-4左上角的网络这种序列到序列的网络可以有效预测时间序列如股票价格输入过去N天价格则输出向未来移动一天的价格N - 1天前到明天

或者你可以向网络输入一个序列忽略除最后一项之外的所有输出图 15-4 右上角的网络 换句话说这是一个序列到向量的网络 例如你可以向网络输入与电影评论相对应的单词序列网络输出情感评分例如-1 [讨厌]+1 [喜欢]

相反可以向网络一遍又一遍输入相同的向量见图 15-4 的左下角输出一个序列这是一个向量到序列的网络 例如输入可以是图像或是 CNN 的结果输出是该图像的标题

给网络输入一种语言的一句话编码器会把这个句子转换成单一的向量表征然后解码器将这个向量解码成另一种语言的句子 这种称为编码器 - 解码器的两步模型比用单个序列到序列的 RNN 实时地进行翻译要好得多因为句子的最后一个单词可以影响翻译的第一句话所以你需要等到听完整个句子才能翻译第 16 章还会介绍如何实现编码器-解码器会比图 15-4 中复杂

训练 RNN 诀窍是在时间上展开就像我们刚刚做的那样然后只要使用常规反向传播见图 15-5 这个策略被称为时间上的反向传播BPTT

假设你在研究网站每小时的活跃用户数或是所在城市的每日气温或公司的财务状况用多种指标做季度衡量在这些任务中数据都是一个序列每步有一个或多个值这被称为时间序列

使用 RNN 之前最好有基线指标否则做出来的模型可能比基线模型还糟例如最简单的方法是预测每个序列的最后一个值这个方法被称为朴素预测有时很难被超越在这个例子中它的均方误差为 0.020

另一个简单的方法是使用全连接网络因为结果要是打平的特征列表需要加一个Flatten使用简单线性回归模型使预测值是时间序列中每个值的线性组合

>>> y_pred = X_valid[:, -1]
>>> np.mean(keras.losses.mean_squared_error(y_valid, y_pred))
0.020211367 

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[50, 1]),
    keras.layers.Dense(1)
]) 

model = keras.models.Sequential([
  keras.layers.SimpleRNN(1, input_shape=[None, 1])
]) 

将多个神经元的层堆起来见图 15-7就形成了深度 RNN

在训练长序列的 RNN 模型时必须运行许多时间步展开的 RNN 变成了一个很深的网络正如任何深度神经网络一样它面临不稳定梯度问题第 11 章讨论过使训练无法停止或训练不稳定另外当 RNN 处理长序列时RNN 会逐渐忘掉序列的第一个输入下面就来看看这两个问题先是第一个问题

很多之前讨论过的缓解不稳定梯度的技巧都可以应用在 RNN 中好的参数初始化方式更快的优化器丢弃等等但是非饱和激活函数如 ReLU的帮助不大事实上它会导致 RNN 更加不稳定为什么呢假设梯度下降更新了权重可以令第一个时间步的输出提高因为每个时间步使用的权重相同第二个时间步的输出也会提高这样就会导致输出爆炸 —— 不饱和激活函数不能阻止这个问题要降低爆炸风险可以使用更小的学习率更简单的方法是使用一个饱和激活函数比如双曲正切函数这就解释了为什么 tanh 是默认选项

另外批归一化也没什么帮助事实上不能在时间步骤之间使用批归一化只能在循环层之间使用更加准确点技术上可以将 BN 层添加到记忆单元上后面会看到这样就可以应用在每个时间步上了既对输入使用也对前一步的隐藏态使用
使用tf.keras在一个简单记忆单元中实现层归一化
另一种归一化的形式效果好些层归一化它是由 Jimmy Lei Ba 等人在 2016 年的一篇论文中提出的它跟批归一化很像但不是在批次维度上做归一化而是在特征维度上归一化这么做的一个优势是可以独立对每个实例实时计算所需的统计量这还意味着训练和测试中的行为是一致的这点和 BN 相反且不需要使用指数移动平均来估计训练集中所有实例的特征统计

由于数据在 RNN 中流动时会经历转换每个时间步都损失了一定信息一定时间后第一个输入实际上会在 RNN 的状态中消失