机器学习算法高级教程-全-

机器学习算法高级教程(全)

原文:Pro Machine Learning Algorithms

协议:CC BY-NC-SA 4.0

一、机器学习基础

机器学习可以大致分为监督学习和非监督学习。根据定义,术语“受监督的”意味着“机器”(系统)在某种东西的帮助下学习——通常是有标签的训练数据。

训练数据(或数据集)是系统学习推断的基础。这个过程的一个例子是向系统显示一组猫和狗的图像以及这些图像的相应标签(这些标签表示该图像是猫的还是狗的),并让系统破译猫和狗的特征。

类似地,无监督学习是将数据分组到相似类别的过程。这样的一个例子是将一组狗和猫的图像输入到系统中,而不提及哪个图像属于哪个类别,并且让系统基于图像的相似性将这两种类型的图像分组到不同的桶中。

在本章中,我们将讨论以下内容:

  • 回归和分类的区别
  • 培训、验证和测试数据的需求
  • 精确度的不同衡量标准

回归和分类

让我们假设我们正在预测某个地区在夏季销售的可乐的数量。该值介于某些值之间,比如说每周 100 万到 120 万台。通常,回归是预测这种连续变量的一种方式。

另一方面,分类或预测是对没有明显结果的事件进行预测,例如,一天是晴天还是雨天。

线性回归是预测连续变量的技术的典型例子,而逻辑回归是预测离散变量的典型技术。还有许多其他技术,包括决策树、随机森林、GBM、神经网络等等,可以帮助预测连续和离散的结果。

培训和测试数据

典型地,在回归中,我们处理泛化/过度拟合的问题。当模型如此复杂以至于它完美地拟合所有的数据点,导致最小可能的错误率时,过度拟合问题就出现了。过度拟合数据集的典型示例如图 1-1 所示。

A463052_1_En_1_Fig1_HTML.jpg

图 1-1

An overfitted dataset

从图中的数据集,您可以看到直线并不完全符合所有的数据点,而曲线则完全符合这些点,因此曲线在数据点上的误差最小。

但是,与新数据集中的曲线相比,直线更有可能具有更高的概化能力。因此,在实践中,回归/分类是模型的可推广性和模型的复杂性之间的权衡。

模型的概化程度越低,“看不见的”数据点的错误率就越高。

这种现象可以在图 1-2 中观察到。随着模型复杂性的增加,看不见的数据点的错误率不断降低,直到某一点,之后它又开始增加。然而,随着模型复杂性的增加,训练数据集的错误率不断降低,最终导致过度拟合。

A463052_1_En_1_Fig2_HTML.jpg

图 1-2

Error rate in unseen data points

看不见的数据点是不用于训练模型,但用于测试模型的准确性的点,因此被称为测试数据或测试数据。

对验证数据集的需求

拥有固定的训练和测试数据集的主要问题是,测试数据集可能与训练数据集非常相似,而新的(未来的)数据集可能与训练数据集不太相似。未来数据集与训练数据集不相似的结果是,未来数据集的模型精度可能非常低。

对问题的直觉通常出现在数据科学竞赛和像 Kaggle ( www.kaggle.com )这样的黑客马拉松中。公共排行榜并不总是与私人排行榜相同。通常,对于测试数据集,竞赛组织者不会告诉用户测试数据集的哪些行属于公共排行榜,哪些属于私人排行榜。本质上,随机选择的测试数据集子集进入公共排行榜,其余的进入私有排行榜。

人们可以将私人排行榜视为用户不知道其准确性的测试数据集,而对于公共排行榜,用户被告知模型的准确性。

潜在的情况是,人们在公共排行榜的基础上过度适应,而私人排行榜可能是略有不同的数据集,不能高度代表公共排行榜的数据集。

问题可以从图 1-3 中看出。

A463052_1_En_1_Fig3_HTML.jpg

图 1-3

The problem illustrated

在这种情况下,您会注意到,在公共排行榜和私人排行榜之间进行比较时,用户从排名 17 下降到排名 47。交叉验证是一种有助于避免这个问题的技术。让我们详细检查一下工作情况。

如果我们只有一个训练和测试数据集,假设模型看不到测试数据集,除非我们有第三个数据集,否则我们将无法提出超参数的组合(超参数可以被认为是一个旋钮,我们可以通过改变它来提高模型的准确性)。验证是第三个数据集,可用于查看超参数改变时模型的准确性。通常,在数据集中 100%的数据点中,60%用于训练,20%用于验证,其余 20%用于测试数据集。

验证数据集的另一个想法是这样的:假设您正在构建一个模型来预测客户是否可能在未来两个月内流失。数据集的大部分将用于训练模型,其余部分可用于测试数据集。但是在我们将在后续章节中讨论的大多数技术中,你会注意到它们涉及到超参数。

随着我们不断改变超参数,模型的精度变化很大,但除非有另一个数据集,否则我们无法确定精度是否在提高。原因如下:

  1. 我们不能在训练模型的数据集上测试模型的准确性。
  2. 我们不能使用测试数据集精度的结果来最终确定理想的超参数,因为实际上,模型看不到测试数据集。

因此,需要第三个数据集——验证数据集。

精确度的测量

在典型的线性回归(预测连续值)中,有几种方法可以测量模型的误差。通常,在测试数据集上测量误差,因为在训练数据集(构建模型的数据集)上测量误差会产生误导,因为模型已经看到了数据点,如果我们只在训练数据集上测试模型的准确性,我们将无法对未来数据集的准确性发表任何意见。这就是为什么总是在不用于构建模型的数据集上测量误差。

绝对误差

绝对误差定义为预测值和实际值之间的差值的绝对值。让我们想象一个如下的场景:

|   | 实际价值 | 预报值 | 错误 | 绝对误差 | | :-- | :-- | :-- | :-- | :-- | | 数据点 1 | One hundred | One hundred and twenty | Twenty | Twenty | | 数据点 2 | One hundred | Eighty | –20 | Twenty | | 全部的 | Two hundred | Two hundred | Zero | Forty |

在这种情况下,我们可能会错误地看到总误差为 0(因为一个误差为+20,另一个误差为–20)。如果我们假设模型的总体误差为 0,我们就忽略了这样一个事实,即该模型在单个数据点上效果不佳。

为了避免正误差和负误差相互抵消从而导致最小误差的问题,我们考虑模型的绝对误差,在这种情况下是 40,绝对误差率是 40 / 200 = 20%

均方根误差

解决误差符号不一致问题的另一种方法是求误差的平方(负数的平方是正数)。上面讨论的场景可以翻译如下:

|   | 实际价值 | 预报值 | 错误 | 平方误差 | | :-- | :-- | :-- | :-- | :-- | | 数据点 1 | One hundred | One hundred and twenty | Twenty | four hundred | | 数据点 2 | One hundred | Eighty | –20 | four hundred | | 全部的 | Two hundred | Two hundred | Zero | eight hundred |

现在总的平方误差是 800,均方根误差(RMSE)是(800 / 2)的平方根,也就是 20。

混淆矩阵

绝对误差和 RMSE 适用于预测连续变量。然而,预测具有离散结果的事件是一个不同的过程。离散事件预测以概率的形式发生-模型的结果是某个事件发生的概率。在这种情况下,尽管理论上可以使用绝对误差和 RMSE,但还有其他相关的度量标准。

混淆矩阵计算模型预测事件结果的实例数量,并将其与实际值进行比较,如下所示:

  • 灵敏度或真阳性率或回忆=真阳性/(总阳性)= TP/ (TP + FN)
  • 特异性或真阴性率=真阴性/(总阴性)= TN / (FP + TN)
  • 精度或正预测值= TP / (TP + FP)
  • 召回= TP / (TP+FN)
  • 准确度= (TP + TN) / (TP + FN + FP + TN)
  • F1 得分= 2TP/ (2TP + FP + FN)
|   | 预期欺诈 | 预测非欺诈 | | :-- | :-- | :-- | | 实际欺诈 | 真阳性(TP) | 假阴性(FN) | | 实际无欺诈 | 假阳性 | 真阴性(TN) |

AUC 值和 ROC 曲线

假设您正在为一个运营团队提供咨询,该团队手动审查电子商务交易,以确定它们是否是欺诈。

  • 与这一过程相关的成本是审查所有交易所需的人力。
  • 与成本相关的好处是,由于人工审查,欺诈性交易的数量被预先阻止。
  • 与上述设置相关的总体利润是通过防止欺诈节省的资金减去人工审查的成本。

在这种情况下,模型可以如下派上用场:我们可以提出一个模型,为每项交易打分。每笔交易都根据欺诈的可能性进行评分。这样,所有不太可能是欺诈的交易都不需要由人工审查者进行审查。因此,该模式的好处是减少需要审查的交易数量,从而减少审查交易所需的人力资源,并降低与审查相关的费用。但是,由于一些交易没有经过审查,无论欺诈的可能性有多小,仍然可能有一些欺诈没有被发现,因为一些交易没有经过审查。

在这种情况下,如果模型通过减少要审查的交易(希望是不太可能是欺诈交易的交易)的数量来提高整体利润,那么它可能是有帮助的。

计算曲线下面积(AUC)的步骤如下:

  1. 对每笔交易进行评分,以计算欺诈的可能性。(评分基于预测模型——在第三章中有更多详细信息。)
  2. 按概率降序排列事务。

在有序数据集的顶部应该有非常少的非欺诈数据点,在有序数据集的底部应该有非常少的欺诈数据点。AUC 值因数据集中存在此类异常而受到惩罚。

现在,让我们假设总共有 1,000,000 笔交易需要审查,根据历史记录,平均有 1%的交易是欺诈性的。

  • 受试者工作特征(ROC)曲线的 x 轴是考虑的累计点数(交易)。
  • y 轴是捕获的欺诈交易的累计数量。

一旦我们对数据集进行排序,直观上所有的高概率交易都是欺诈交易,低概率交易不是欺诈交易。当我们查看最初的几笔交易时,捕获的欺诈累积数量会增加,在某个点之后,它会饱和,因为交易的进一步增加不会增加欺诈交易。

x 轴上审查的累计交易和 y 轴上捕获的累计欺诈的图表类似于图 1-4 。

A463052_1_En_1_Fig4_HTML.jpg

图 1-4

Cumulative frauds captured when using a model

在这种情况下,我们总共有 1,000,000 笔交易中的 10,000 笔欺诈交易。这是平均 1%的欺诈率,也就是说,每 100 笔交易中就有一笔是欺诈性的。

如果我们没有任何模型,我们的随机猜测会慢慢增加,如图 1-5 所示。

A463052_1_En_1_Fig5_HTML.jpg

图 1-5

Cumulative frauds captured when transactions are randomly sampled

在图 1-5 中,可以看到这条线将整个数据集分成了大致相等的两部分——线下的面积等于总面积的 0.5 倍。为方便起见,如果我们假设地块的总面积为 1 个单位,那么由随机猜测模型生成的线下总面积将为 0.5。

基于预测模型和随机猜测捕获的累积欺诈的比较如图 1-6 所示。

A463052_1_En_1_Fig6_HTML.jpg

图 1-6

Comparison of cumulative frauds

注意,在这种情况下,由预测模型产生的曲线下面积(AUC)大于 0.5。

因此,AUC 越高,模型的预测能力越好。

无监督学习

到目前为止,我们已经看到了监督学习,其中有一个因变量(我们试图预测的变量)和一个自变量(我们用来预测因变量值的变量)。

然而,在某些情况下,我们只有独立变量——例如,在我们必须根据某些特征对客户进行分组的情况下。在这些情况下,无监督学习技术就派上了用场。

有两种主要类型的无监督技术:

  • 基于聚类的方法
  • 主成分分析

聚类是对行进行分组的方法,而 PCA 是对列进行分组的方法。我们可以认为聚类有助于将给定的客户分配到一个或另一个组中(因为每个客户通常代表数据集中的一行),而 PCA 有助于对列进行分组(或者,减少数据的维度/变量)。

尽管聚类有助于细分客户,但它也是我们建模过程中一个强大的预处理步骤(你将在第十一章中读到更多相关内容)。主成分分析可以通过减少维数来帮助加速模型建立过程,从而减少要估计的参数的数量。

在本书中,我们将讨论大多数监督和非监督算法,如下所示:

  1. 我们首先在 Excel 中手工编码它们。
  2. 我们在 r 中实现。
  3. 我们用 Python 实现。

附录中概述了 Excel、R 和 Python 的基础知识。

构建模型的典型方法

在上一节中,我们看到了一个运营团队在真实场景中实现预测模型的成本效益分析场景。在这一节中,我们将探讨在构建预测模型时应该考虑的一些要点。

数据是从哪里获取的?

通常,数据在数据库、CSV 或文本文件的表格中可用。在数据库中,不同的表可能捕获不同的信息。例如,为了理解欺诈性交易,我们可能会将交易表与客户人口统计表连接起来,以便从数据中获得洞察力。

需要提取哪些数据?

预测练习的输出与进入模型的输入一样好。获得正确输入的关键部分是更好地理解我们试图预测的驱动因素/特征——在我们的案例中,是更好地理解欺诈交易的特征。

在这里,数据科学家通常会戴上管理顾问的帽子。他们研究可能推动他们试图预测的事件的因素。他们可以通过接触一线工作人员(例如,手动审查交易的欺诈风险调查人员)来了解他们在调查交易时关注的关键因素。

数据预处理

输入数据并不总是每次都是干净的。在构建模型之前,可能需要处理多个问题:

  • 数据中缺少值:当变量(数据点)未被记录时,或者当跨不同表的联接导致不存在的值时,数据中缺少值。缺失值可以通过几种方式进行估算。最简单的方法是用该列的平均值/中值替换缺失值。替换缺失值的另一种方法是基于事务中可用的其余变量添加一些智能。这种方法被称为识别 K-最近邻(更多信息见第十三章)。
  • 数据中的异常值:输入变量中的异常值会导致基于回归技术的低效优化(第章第二部分详细讨论了异常值的影响)。通常异常值是通过将变量限制在某个百分点值(例如 95%)来处理的。
  • 变量转换:可用的变量转换如下:
    • 缩放变量:在基于梯度下降的技术的情况下缩放变量通常会导致更快的优化。
    • 对数/平方变换:对数/平方变换在输入变量与因变量具有非线性关系的情况下非常方便。

特征交互

考虑这样一种情况,当一个人是男性并且年龄很小的时候,他在泰坦尼克号上幸存的几率很高。典型的基于回归的技术不会考虑这样的特征交互,而基于树的技术会。特征交互是基于变量组合创建新变量的过程。请注意,通常情况下,通过更好地理解业务(我们试图预测的事件),可以了解特性交互。

特征生成

要素生成是从数据集中查找附加要素的过程。例如,用于预测欺诈性交易的特征将是自给定交易的最后一次交易以来的时间。这样的特性不是直接可用的,而只能通过理解我们试图解决的问题来获得。

构建模型

一旦数据到位,预处理步骤完成,下一步就是建立预测模型。多种机器学习技术将有助于建立预测模型。关于主要机器学习技术的细节将在其余章节中探讨。

生产模型

一旦最终的模型就位,模型的生产就因用例而异。例如,数据科学家可以进行离线分析,查看客户的历史购买记录,并得出一个产品列表,通过电子邮件发送给特定客户,作为推荐。在另一种情况下,在线推荐系统实时工作,数据科学家可能必须将模型提供给数据工程师,然后数据工程师在生产中实现该模型,以实时生成推荐。

构建、部署、测试和迭代

总的来说,建立一个模型不是一次性的工作。你需要展示从前一个流程转移到一个新流程的价值。在这样的场景中,您通常会遵循 A/B 测试或测试/控制方法,在这种方法中,模型仅针对全部可能的事务/客户中的一小部分进行部署。然后将这两个组进行比较,以查看模型的部署是否确实导致了企业想要实现的度量的改进。一旦模型显示出前景,它就扩展到更多的总可能交易/客户。一旦达成共识,认为该模型是有前途的,它被接受为最终解决方案。否则,数据科学家会使用来自之前 A/B 测试实验的新信息进行重复。

摘要

在这一章中,我们研究了机器学习的基本术语。我们还讨论了在评估模型时可以使用的各种误差度量。我们讨论了利用机器学习算法解决业务问题的各个步骤。

二、线性回归

为了理解线性回归,让我们来解析它:

  • 直线的:排列成直线的或沿直线延伸的,如“直线运动”
  • 回归:一种确定两个或多个变量之间统计关系的技术,其中一个变量的变化是由另一个变量的变化引起的。

综合这些因素,我们可以将线性回归定义为两个变量之间的关系,其中一个变量的增加会影响另一个变量成比例地增加或减少(即线性增加或减少)。

在本章中,我们将学习以下内容:

  • 线性回归如何工作
  • 构建线性回归时要避免的常见陷阱
  • 如何在 Excel、Python 和 R 中构建线性回归

引入线性回归

线性回归有助于根据已知值对未知变量(连续变量)的值进行插值。它的一个应用可以是,“当产品的价格变化时,对产品的需求是多少?”在这个应用程序中,我们将不得不根据历史价格查看需求,并根据新的价格点对需求进行估计。

考虑到我们是为了估计一个新的价格点而查看历史,这就变成了一个回归问题。价格和需求彼此线性相关的事实(价格越高,需求越低,反之亦然)使其成为线性回归问题。

变量:因变量和自变量

因变量是我们预测的值,自变量是我们用来预测因变量的变量。

例如,温度与购买冰淇淋的数量成正比。随着温度的升高,购买冰淇淋的数量也会增加。在这里,温度是独立变量,并根据它来预测购买冰淇淋的数量(因变量)。

相互关系

从前面的例子中,我们可能会注意到冰淇淋的购买量与温度直接相关(也就是说,它们与独立变量温度的方向相同或相反)。在这个例子中,相关性是正的:随着温度的升高,冰淇淋的销量增加。在其他情况下,相关性可能是负的:例如,一个项目的销售可能会随着该项目的价格下降而增加。

原因

我们来翻转一下冰淇淋销量随着温度升高而增加的场景(高+ ve 相关性)。相反,随着冰淇淋销量的增加,温度也会升高(也是高+ ve 相关性)。

然而,凭直觉我们可以自信地说,温度不受冰淇淋销售的控制,尽管事实正好相反。这就引出了因果关系的概念——也就是说,哪个事件影响另一个事件。温度影响冰淇淋的销售,但不是相反。

简单与多元线性回归

我们已经讨论了两个变量(因变量和自变量)之间的关系。然而,因变量不仅受一个变量的影响,还受多个变量的影响。例如,冰淇淋的销售受温度的影响,但也受冰淇淋的销售价格以及其他因素如位置、冰淇淋品牌等的影响。

在多元线性回归的情况下,一些变量将与因变量正相关,一些变量将与因变量负相关。

形式化简单线性回归

现在我们已经有了基本的术语,让我们深入线性回归的细节。简单的线性回归表示为:

$$ Y=a+{b}^{\ast }X $$

  • y 是我们预测的因变量。
  • x 是独立变量。
  • a 是偏差项。
  • b 是变量的斜率(分配给自变量的权重)。

y 和 X,因变量和自变量现在应该够清楚了。让我们介绍一下偏差和权重项(上式中的 a 和 b)。

偏差项

我们通过一个例子来看偏差项:用婴儿的月龄来估计婴儿的体重。我们假设婴儿的体重完全取决于婴儿几个月大。婴儿出生时重 3 公斤,体重以每月 0.75 公斤的恒定速度增加。

在年末,婴儿体重的图表看起来像图 2-1 。

A463052_1_En_2_Fig1_HTML.jpg

图 2-1

Baby weight over time in months

在图 2-1 中,婴儿的体重从 3 (a,偏差)开始,每月线性增加 0.75 (b,斜率)。注意,当所有自变量都为 0 时,偏差项是因变量的值。

斜坡

直线的斜率是直线长度上直线两端的 x 和 y 坐标之差。在前面的示例中,斜率(b)的值如下:

(两端 y 坐标之差)/(两端 x 坐标之差)

$$ b=\frac{12-3\ }{\left(12-0\right)}=9/12=0.75 $$

求解简单的线性回归

我们已经看到了一个简单的线性回归输出的简单例子(求解偏差和斜率)。在本节中,我们将采取第一步,提出一种更通用的方法来生成回归线。提供的数据集如下:

| 月龄 | 重量(千克) | | :-- | :-- | | Zero | three | | one | Three point seven five | | Two | Four point five | | three | Five point two five | | four | six | | five | Six point seven five | | six | Seven point five | | seven | Eight point two five | | eight | nine | | nine | Nine point seven five | | Ten | Ten point five | | Eleven | Eleven point two five | | Twelve | Twelve |

数据的可视化如图 2-2 所示。

A463052_1_En_2_Fig2_HTML.jpg

图 2-2

Visualizing baby weight

在图 2-2 中,x 轴是婴儿的月龄,y 轴是婴儿在给定月份的体重。比如宝宝第一个月的体重是 3.75 斤。

让我们从基本原则来解决这个问题。我们将假设数据集只有 2 个数据点,而不是 13 个,但是,只有前 2 个数据点。数据集将如下所示:

| 月龄 | 重量(千克) | | :-- | :-- | | Zero | three | | one | Three point seven five |

假设我们根据婴儿的年龄来估计婴儿的体重,那么可以建立如下的线性回归:

$$ 3=a+{b}^{\ast }(0) $$

$$ 3.75=a+{b}^{\ast }(1) $$

解决这个问题后,我们看到 a = 3,b = 0.75。

让我们将 a 和 b 的值应用于上面剩余的 11 个数据点。结果将如下所示:

| 月龄 | 重量(千克) | 重量估计 | 估计的平方误差 | | :-- | :-- | :-- | :-- | | Zero | three | three | Zero | | one | Three point seven five | Three point seven five | Zero | | Two | Four point five | Four point five | Zero | | three | Five point two five | Five point two five | Zero | | four | six | six | Zero | | five | Six point seven five | Six point seven five | Zero | | six | Seven point five | Seven point five | Zero | | seven | Eight point two five | Eight point two five | Zero | | eight | nine | nine | Zero | | nine | Nine point seven five | Nine point seven five | Zero | | Ten | Ten point five | Ten point five | Zero | | Eleven | Eleven point two five | Eleven point two five | Zero | | Twelve | Twelve | Twelve | Zero | |   |   | 总平方误差 | Zero |

如您所见,只需解决前两个数据点,就能以最小的错误率解决问题。然而,实际情况可能并非如此,因为大多数真实数据并不像表中所示的那样清晰。

求解简单线性回归的更一般的方法

在前面的场景中,我们看到系数是通过仅使用总数据集中的两个数据点获得的,也就是说,我们在得出最佳 a 和 b 时没有考虑大多数观察值。为了避免在建立方程时遗漏大多数数据点,我们可以将目标修改为最小化所有数据点的总体平方误差(普通最小二乘法)。

最小化误差平方和

总平方误差定义为所有观测值的实际值和预测值之间的平方差之和。我们考虑平方误差值而不是实际误差值的原因是,我们不希望某些数据点中的正误差抵消其他数据点中的负误差。例如,三个数据点中的+5 误差抵消了其他三个数据点中的–5 误差,导致六个数据点的总误差为 0。平方误差将后三个数据点的–5 误差转换为正数,因此总平方误差变为 6 × 5 2 = 150。

这就带来了一个问题:我们为什么要最小化整体平方误差?原理如下:

  1. 如果每个单独的数据点都预测正确,则总误差最小。
  2. 一般来说,5%的高估和 5%的低估一样糟糕,因此我们考虑平方误差。

让我们把这个问题公式化:

| 月龄 | 重量(千克) | 公式 | 当 a = 3,b = 0.75 时的重量估计 | 估计的平方误差 | | :-- | :-- | :-- | :-- | :-- | | Zero | three | 3 = a + b × (0) | three | Zero | | one | Three point seven five | 3.75 = a + b × (1) | Three point seven five | Zero | | Two | Four point five | 4.5 = a + b × (2) | Four point five | Zero | | three | Five point two five | 5.25 = a + b × (3) | Five point two five | Zero | | four | six | 6 = a + b × (4) | six | Zero | | five | Six point seven five | 6.75 = a + b × (5) | Six point seven five | Zero | | six | Seven point five | 7.5 = a + b × (6) | Seven point five | Zero | | seven | Eight point two five | 8.25 = a + b × (7) | Eight point two five | Zero | | eight | nine | 9 = a + b × (8) | nine | Zero | | nine | Nine point seven five | 9.75 = a + b × (9) | Nine point seven five | Zero | | Ten | Ten point five | 10.5 = a + b × (10) | Ten point five | Zero | | Eleven | Eleven point two five | 11.25 = a + b × (11) | Eleven point two five | Zero | | Twelve | Twelve | 12 = a + b × (12) | Twelve | Zero | |   |   |   | 总平方误差 | Zero |

线性回归方程在上表的公式列中表示。

将数据集(前两列)转换为公式(第 3 列)后,线性回归是一个求解公式列中 a 和 b 值的过程,以使估计的总平方误差(所有数据点的平方误差之和)最小化。

求解公式

求解公式的过程非常简单,只需对 a 和 b 值的多个组合进行迭代,就能尽可能地降低整体误差。请注意,最佳 a 和 b 值的最终组合是通过使用一种称为梯度下降的技术获得的,这将在第七章中探讨。

简单线性回归的工作细节

求解 a 和 b 可以理解为 Excel 中的目标搜索问题,其中 Excel 帮助确定 a 和 b 的值,使总值最小化。

要了解其工作原理,请查看以下数据集(在 github 中以“线性回归 101.xlsx”的形式提供):

A463052_1_En_2_Figa_HTML.jpg

通过检查数据集中的以下内容,您应该了解以下内容:

  1. H3 和 H4 单元格与 D 列(估计重量)的关系

  2. E 列的公式

  3. 单元格 E15,每个数据点的误差平方和

  4. To obtain the optimal values of a and b (in cells H3 and H4)—go to Solver in Excel and add the following constraints:

    A463052_1_En_2_Figb_HTML.jpg

    1. 最小化单元格 E15 中的值
    2. 通过改变 H3 和 H4 的牢房

简单的线性回归有点复杂

在前面的例子中,我们从一个值完全吻合的场景开始:a = 3,b = 0.75。

零差错率的原因是我们先定义了场景,再定义了方法——即一个婴儿出生时 3 kg,体重每月增加 0.75 kg。然而,实际情况是不同的:“每个婴儿都是不同的。”

让我们通过一个数据集(在 github 中以“婴儿年龄与体重的关系. xlsx”提供)来可视化这个新场景。这里,我们测量了两个不同婴儿的年龄和体重。

年龄-体重关系图现在看起来像图 2-3 。

A463052_1_En_2_Fig3_HTML.jpg

图 2-3

Age-to-weight reltionship

体重的值随着年龄的增长而增加,但并不是从 3 kg 开始,每个月增加 0.75 kg 的确切趋势,如最简单的例子所示。

为了解决这个问题,我们要经历和之前一样的严格过程:

A463052_1_En_2_Figc_HTML.jpg

  1. 用 a 和 b 的任意值初始化(例如,每个值都等于 1)。
  2. 为预测创建一个新列,值为 a+b×X–列 c。
  3. 做一个新的平方误差栏,d 栏。
  4. 计算单元格 G7 中的总误差。
  5. 调用规划求解,通过更改单元格 a 和 b(即 G3 和 G4)来最小化单元格 G7。

前面场景中的单元连接如下:

A463052_1_En_2_Figd_HTML.jpg

使总误差最小的 G3 和 G4 的单元值是 a 和 b 的最佳值。

达到最佳系数值

使用一种称为梯度下降的技术来获得系数的最佳值。第七章详细讨论了梯度下降是如何工作的,但现在,让我们通过以下步骤开始理解梯度下降:

  1. 随机初始化系数(a 和 b)的值。
  2. 计算成本函数,即训练数据集中所有数据点的误差平方和。
  3. 稍微改变系数的值,比如说,其值的+1%。
  4. 通过稍微改变系数值,检查总体平方误差是减少还是增加。
  5. 如果通过将系数的值改变+1%来降低总平方误差,则进一步进行,否则将系数降低 1%。
  6. 重复步骤 2-4,直到总平方误差最小。

引入均方根误差

到目前为止,我们已经看到,总误差是每个数据点的预测值和实际值之差的平方和。请注意,一般来说,随着数据点数量的增加,总平方误差也会增加。

为了对数据中的观察值数量进行归一化,也就是说,有一个有意义的误差度量,我们将考虑误差的均方根(因为我们在计算误差时已经对差值进行了平方)。均方根误差(RMSE)计算如下(在单元格 G9 中):

A463052_1_En_2_Fige_HTML.jpg

请注意,在前面的数据集中,我们必须求解 a 和 b(单元格 G3 和 G4)的最佳值,以使总误差最小。

在 R 中运行简单的线性回归

为了理解前面几节中介绍的实现细节,我们将在 R(作为“简单线性回归”提供)中运行线性回归。github 中的 r”)。

# import file
data=read.csv("D:/Pro ML book/linear_reg_example.csv")
# Build model
lm=glm(Weight~Age,data=data)
# summarize model
summary(lm)

函数lm代表线性模型,一般语法如下:

lm(y~x,data=data)

其中y是因变量,x是自变量,data是数据集。

summary(lm)给出了模型的概要,以及重要的变量和一些自动化测试。让我们一次解析一个:

A463052_1_En_2_Figf_HTML.jpg

残差

残差只不过是误差值(实际值和预测值之差)。汇总函数自动给出残差的分布。例如,考虑模型在我们训练的数据集上的残差。

使用模型的残差分布计算如下:

#Extracting prediction
data$prediction=predict(lm,data)
# Extracting residuals
data$residual = data$Weight - data$prediction
# summarizing the residuals
summary(data$residual)

在前面的代码片段中,predict函数将要实现的模型和要处理的数据集作为输入,并生成预测作为输出。

Note

summary函数的输出是残差列中的各种四分位数值。

系数

输出的系数部分给出了获得的截距和偏差的汇总版本。(Intercept)是偏差项(a),而Ageindependent变量:

  • Estimate是 a 和 b 各自的值。
  • 如果我们从总人口中随机抽取样本,会给我们一种 a 和 b 值变化的感觉。标准误差与截距的比率越低,模型越稳定。

让我们来看一种可以可视化/计算标准误差值的方法。以下步骤提取标准误差值:

  1. 随机抽取总数据集的 50%。
  2. 对采样数据拟合一个lm模型。
  3. 提取对采样数据拟合的模型的自变量系数。
  4. 重复整个过程超过 100 次迭代。

在代码中,上述内容将转换如下:

# Initialize an object that stores the various coefficient values
samp_coef=c()
# Repeat the experiment 100 times
for(i in 1:100){
  # sample 50% of total data
  samp=sample(nrow(data),0.5*nrow(data))
  data2=data[samp,]
  # fit a model on the sampled data
  lm=lm(Weight~Age,data=data2)
  # extract the coefficient of independent variable and store it
  samp_coef=c(samp_coef,lm$coefficients['Age'])
}
sd(samp_coef)

注意,标准差越低,样本数据的系数值越接近原始数据。这表明,无论选择何种样本,系数值都是稳定的。

t 值是除以标准误差的系数。t 值越高,模型稳定性越好。

考虑下面的例子:

A463052_1_En_2_Figg_HTML.jpg

对应于变量Age的 t 值将等于 0.47473/0.01435。(Pr>|t|)给出了对应于 t 值的 p 值。p 值越低,模型越好。让我们来看看从 t 值推导出 p 值的方法。t 值到 p 值的查找可在此链接获得: http://www.socscistatistics.com/pvalues/tdistribution.aspx

在我们的例子中,对于Age变量,t 值是 33.09。

自由度=数据集中的行数-(模型中独立变量的数量+1)= 22-(1+1)= 20

注意,前面公式中的+1来自于包含截距项。

我们将检查双尾假设,并将 t 的值和自由度输入到查找表中,输出将是相应的 p 值。

根据经验,如果一个变量的 p 值小于 0.05,则它被认为是预测因变量的重要变量。我们来看看原因。

如果 p 值很高,这是因为相应的 t 值很低,这是因为与估计值相比,标准误差很高,这最终意味着从总体中随机抽取的样本没有相似的系数。

在实践中,我们通常将 p 值视为决定是否在模型中包含独立变量的指导指标之一。

残差的 SSE(残差偏差)

残差的平方和计算如下:

# SSE of residuals
data$prediction = predict(lm,data)
sum((data$prediction-data$Weight)²)

剩余偏差表示建立模型后可以预期的偏差量。理想情况下,剩余偏差应该与零偏差进行比较,也就是说,由于建立了一个模型,偏差减少了多少。

null deviation(零偏差)

零偏差是指在建立模型时没有使用独立变量时的预期偏差。

当没有自变量时,预测的最佳猜测是因变量本身的平均值。例如,如果我们说,平均每天有 1000 美元的销售额,那么一个人对未来销售额的最佳猜测(当没有提供其他信息时)是 1000 美元。

因此,零偏差可以计算如下:

#Null deviance
data$prediction = mean(data$Weight)
sum((data$prediction-data$Weight)²)

请注意,在计算零偏差时,预测只是因变量的平均值。

r 的平方

r 平方是预测值和实际值之间相关性的度量。其计算方法如下:

  1. 找出实际因变量和预测因变量之间的相关性。
  2. 对步骤 1 中获得的相关性求平方,即 R 的平方值。

r 的平方也可以这样计算:

$$ 1-\left( Residual\ deviance/ Null\ deviance\right) $$

零偏差——在预测因变量时不使用任何自变量(而是偏差/常数)时的偏差——计算如下:

$$ null\ deviance=\sum {\left(Y\hbox{--} \widehat{Y}\right)}² $$

其中是因变量,$$ \widehat{Y} $$是因变量的平均值。

剩余偏差是我们用自变量预测因变量时的实际偏差。计算如下:

$$ residual\ deviance=\sum {\left(Y\hbox{--} \overline{y}\right)}² $$

其中 Y 为实际因变量,$$ \overline{y} $$为因变量的预测值。

本质上,当剩余偏差比零偏差低得多时,R 的平方是高的。

f 统计量

f 统计量为我们提供了一个类似于 R 平方的度量。F 统计量的计算方式如下:

$$ F=\left(\frac{\frac{SSE(N)- SSE(R)}{d{f}_N-d{f}_R}}{\frac{SSE(R)}{d{f}_R}}\right) $$

其中 SSE(N)是零偏差,SSE(R)是残差偏差,df N 是零偏差的自由度,df R 是残差偏差的自由度。F 统计值越高,模型越好。从零偏差到剩余偏差的偏差减少越多,在模型中使用独立变量的可预测性就越高。

在 Python 中运行简单的线性回归

可以使用以下代码在 Python 中运行线性回归(在 github 中以“线性回归 Python code.ipynb”的形式提供):

# import relevant packages
# pandas package is used to import data
# statsmodels is used to invoke the functions that help in lm
import pandas as pd
import statsmodels.formula.api as smf

# import dataset
data = pd.read_csv('D:/Pro ML book/Linear regression/linear_reg_example.csv')

# run least squares regression
est = smf.ols(formula='Weight~Age',data=data)
est2=est.fit()
print(est2.summary())

上述代码的输出如下所示:

A463052_1_En_2_Figh_HTML.jpg

请注意,R 和 Python 的系数部分输出非常相似。然而,这个包给了我们更多的度量来研究默认的预测水平。我们将在后面的部分中更详细地研究这些问题。

简单线性回归的常见陷阱

到目前为止,简单的例子是为了说明线性回归的基本工作原理。让我们考虑失败的情况:

  • 当因变量和自变量之间始终不是线性相关时:随着婴儿年龄的增长,体重增加,但在某一阶段增加趋于平稳,此后两个值不再线性相关。另一个例子是个人的年龄和身高之间的关系。
  • 当自变量的值中有异常值时:假设在婴儿的年龄中有一个极值(人工输入错误)。因为我们的目标是在获得简单线性回归的 a 和 b 值时使总误差最小化,所以独立变量中的极值会对参数产生相当大的影响。通过改变任何年龄值的值并计算使总误差最小化的 a 和 b 的值,可以看到这种工作。在这种情况下,您会注意到,即使给定的 a 和 b 值的总体误差较低,但它会导致大多数其他数据点的误差较高。

为了避免刚刚提到的第一个问题,分析师通常会看到两个变量之间的关系,并确定我们可以应用线性回归的临界值(段)。例如,当根据年龄预测身高时,有不同的时间段:0-1 岁、2-4 岁、5-10 岁、10-15 岁、15-20 岁和 20 岁以上。每个阶段的年龄-身高关系都有不同的斜率。例如,与 2-4 阶段相比,0-1 阶段的身高增长率较高,2-4 阶段比 5-10 阶段好,依此类推。

为了解决提到的第二个问题,分析师通常会执行以下任务之一:

  • 将异常值标准化为第 99 百分位值:标准化为第 99 百分位值可以确保异常高的值不会对结果产生很大影响。例如,在前面的示例场景中,如果年龄被错误地输入为 1200 而不是 12,它将被规范化为 12(这是年龄列中的最高值之一)。
  • Normalize,但创建一个标记,指出特定变量已被规范化:有时极值中有很好的信息。例如,在预测信用额度时,让我们考虑这样一个场景:九个人收入为 500,000 美元,第十个人收入为 5,000,000 美元,申请信用卡,每个人的信用额度为 5,000,000 美元。让我们假设给予一个人的信用额度至少是其收入的 10 倍或 500 万美元。对此进行线性回归将导致斜率接近 10,但小于 10,因为一个人获得了 500 万美元的信贷限额,尽管他们的收入是 500 万美元。在这种情况下,如果我们有一个标志,说明收入为 500 万美元的人是一个异常值,斜率将接近 10。

异常值标记是多元回归的一种特殊情况,在这种情况下,我们的数据集中可能有多个独立变量。

多元线性回归

多元回归,顾名思义,涉及多个变量。

到目前为止,在一个简单的线性回归中,我们观察到因变量是根据单个自变量预测的。在实践中,多个变量通常会影响一个因变量,这意味着多元变量比简单的线性回归更常见。

在第一部分中提到的相同的冰淇淋销售问题可以转化为如下的多变量问题:

冰淇淋销售额(因变量)取决于以下因素:

  • 温度
  • 周末与否
  • 冰淇淋的价格

这个问题可以通过以下方式转化为数学模型:

$$ Y=a+{w_1}^{\ast }{X}_1+{w_2}^{\ast }{X}_2 $$

在该等式中,w 1 是与第一自变量相关联的权重,w 2 是与第二自变量相关联的权重(系数),a 是偏差项。

a、w 1 和 w 2 的值将按照我们在简单线性回归(Excel 中的求解器)中求解 a 和 b 的方式求解。

多元线性回归汇总的结果和解释与我们在前面章节中看到的简单线性回归相同。

对上述场景的示例解释如下:

冰淇淋销量= 2 + 0.1 ×温度+ 0.2 ×周末旗帜-0.5×冰淇淋价格

前面的等式解释如下:如果温度上升 5 度,而其他参数保持不变(即在某一天,价格保持不变),冰淇淋的销售额增加 0.5 美元。

多元线性回归的工作细节

要了解多元线性回归是如何计算的,我们来看下面的例子(在 github 中以“linear_multi_reg_example.xlsx”的形式提供):

A463052_1_En_2_Figi_HTML.jpg

对于前面的数据集,其中 Weight 是因变量,Age、New 是自变量,我们将按如下方式初始化估计值和随机系数:

A463052_1_En_2_Figj_HTML.jpg

在这种情况下,我们将迭代 a、b 和 c 的多个值,即最小化总体平方误差值的像元 H3、H4 和 H5。

R 中多元线性回归

多元线性回归可以在 R 中如下执行(可作为“多元线性回归”获得。github 中的 r”)。

A463052_1_En_2_Figk_HTML.jpg

# import file
data=read.csv("D:/Pro ML book/Linear regression/linear_multi_reg_example.csv")
# Build model
lm=glm(Weight~Age+New,data=data)
# summarize model
summary(lm)

注意,我们已经通过在独立变量之间使用+符号为回归指定了多个变量。

我们可以在输出中注意到一个有趣的方面,即New变量的 p 值大于 0.05,因此是一个无关紧要的变量。

通常,当 p 值较高时,我们测试变量转换或变量封顶是否会导致获得较低的 p 值。如果前面的技术都不起作用,我们最好排除这些变量。

我们在这里可以看到的其他细节的计算方式类似于前面几节中的简单线性回归计算。

Python 中的多元线性回归

与 R 类似,Python 在公式部分也有一个小的增加,以适应简单线性回归之上的多元线性回归:

# import relevant packages
# pandas package is used to import data
# statsmodels is used to inoke the functions that help in lm
import pandas as pd
import statsmodels.formula.api as smf
# import dataset
data = pd.read_csv('D:/Pro ML book/Linear regression/linear_multi_reg_example.csv')
# run least squares regression
est = smf.ols(formula='Weight~Age+New',data=data)
est2=est.fit()
print(est2.summary())

模型中有不重要变量的问题

当 p 值较高时,变量不重要。与系数值相比,当标准误差较高时,p 值通常较高。当标准误差较高时,表示为多个样本生成的多个系数中存在较高的方差。当我们有一个新数据集(即测试数据集,在构建模型时模型看不到它)时,系数不一定会针对新数据集进行归纳。

当模型中包含非显著变量时,这将导致测试数据集的 RMSE 较高,而当模型构建中不包含非显著变量时,RMSE 通常较低。

多重共线性问题

构建多变量模型时要注意的一个主要问题是自变量何时可能彼此相关。这种现象称为多重共线性。例如,在冰淇淋示例中,如果冰淇淋价格在周末上涨 20%,则两个独立变量(价格和周末标志)相互关联。在这种情况下,在解释结果时需要小心——其余变量保持不变的假设不再成立。

例如,我们不能再假设在周末唯一变化的变量是周末标志;我们还必须考虑到价格在周末也会变化。这个问题转化为,在给定的温度下,如果这一天恰好是周末,由于是周末,销售额增加了 0.2 个单位,但是由于价格在周末期间增加了 20%,销售额减少了 0.1 个单位,因此,周末销售额的净效应是+0.1 个单位。

多重共线性的数学直觉

为了对自变量之间存在相互关联的变量所涉及的问题有所了解,请考虑以下示例(代码可作为“相关自变量的问题”。github 中的 r”)。

A463052_1_En_2_Figl_HTML.jpg

# import dataset
data=read.csv("D:/Pro ML book/linear_reg_example.csv")
# Creating a correlated variable
data$correlated_age = data$Age*0.5 + rnorm(nrow(data))*0.1
cor(data$Age,data$correlated_age)
# Building a linear regression
lm=glm(Weight~Age+correlated_age,data=data)
summary(lm)

请注意,尽管在前面的示例中,年龄是预测体重的一个重要变量,但当数据集中存在相关变量时,年龄会变成一个不重要的变量,因为它具有较高的 p 值。

数据样本的年龄和相关年龄系数差异较大的原因是,尽管年龄和相关年龄变量相关,但年龄和相关年龄的组合(当作为单个变量处理时,即两个变量的平均值)的系数差异较小。

假设我们使用两个变量,根据样本的不同,Age 可能具有较高的系数,而 correlated_age 可能具有较低的系数,对于其他一些样本则相反,从而导致所选样本的两个变量的系数差异较大。

多元线性回归中需要考虑的其他问题

  • 回归系数过高是不可取的:虽然回归在某些情况下可以有很高的系数,但一般来说,系数的高值会导致预测值的巨大摆动,即使自变量变化 1 个单位。例如,如果销售是价格的函数,其中销售= 1,000,000–100,000 x 价格,价格的单位变化会大幅减少销售。在这种情况下,为了避免这个问题,建议通过将销售额改为 log(sales)而不是 sales 来降低销售额,或者归一化销售额变量,或者通过 L1 和 L2 正则化来惩罚具有高权重的模型(更多关于 L1/ L2 正则化的信息,请参见第七章)。这样,等式中的 a 和 b 值保持较小。
  • 回归应该建立在大量观察的基础上:一般来说,数据点的数量越多,模型越可靠。而且,自变量的数量越多,需要考虑的数据点就越多。如果我们只有两个数据点和两个独立变量,我们总能得出一个适合这两个数据点的方程。但是仅仅建立在两个数据点上的等式的一般化是有问题的。实际上,建议数据点的数量至少是自变量数量的 100 倍。

行数少或列数多,或者两者都有,这就引出了调整后的 R 平方的问题。如前所述,一个方程中的自变量越多,它最接近因变量的可能性就越大,因此 R 的平方越高,即使自变量不显著。因此,应该有一种方法来惩罚在一组固定的数据点上有大量的独立变量。调整后的 R 平方考虑了方程中使用的独立变量的数量,并因拥有更多独立变量而受到惩罚。调整后的 R 平方的公式如下:

$$ {R}_{adj}²=1-\left[\frac{\left(1-{R}²\right)\left(n-1\right)}{n-k-1}\right] $$

其中 n 是数据集中数据点的个数,k 是数据集中自变量的个数。

具有最小调整的 R 平方的模型通常是更好的模型。

线性回归的假设

线性回归的假设如下:

  • 自变量必须与因变量呈线性关系:如果线性水平随区段而变化,则为每个区段建立一个线性模型。

  • 独立变量之间的值不应该有任何异常值:如果有异常值,它们应该被限制,或者需要创建一个新的变量来标记异常值的数据点。

  • Error values should be independent of each other: In a typical ordinary least squares method, the error values are distributed on both sides of the fitted line (that is, some predictions will be above actuals and some will be below actuals), as shown in Figure 2-4. A linear regression cannot have errors that are all on the same side, or that follow a pattern where low values of independent variable have error of one sign while high values of independent variable have error of the opposite sign.

    A463052_1_En_2_Fig4_HTML.jpg

    图 2-4

    Errors on both sides of the line

  • Homoscedasticity: Errors cannot get larger as the value of an independent variable increases. Error distribution should look more like a cylinder than a cone in linear regression (see Figure 2-5). In a practical scenario, we can think of the predicted value being on the x-axis and the actual value being on the y-axis.

    A463052_1_En_2_Fig5_HTML.jpg

    图 2-5

    Comparing error distributions

  • Errors should be normally distributed: There should be only a few data points that have high error. A majority of data points should have low error, and a few data points should have positive and negative error—that is, errors should be normally distributed (both to the left of overforecasting and to the right of underforecasting), as shown in Figure 2-6.

    A463052_1_En_2_Fig6_HTML.jpg

    图 2-6

    Comparing curves

Note

在图 2-6 中,如果我们稍微调整了右边图表中的偏差(截距),现在更多的观察值将围绕零误差。

摘要

在本章中,我们学习了以下内容:

  • 误差平方和(SSE)是计算线性回归系数的优化基础。
  • 当多个独立变量相互关联时,多重共线性就是一个问题。
  • p 值是预测因变量时变量显著性的指标。
  • 为了使线性回归起作用,应该满足五个假设,即,因变量和自变量之间的线性关系、没有异常值、误差值独立性、同方差性、误差的正态分布。

三、逻辑回归

在第二章中,我们看了基于自变量估计变量的方法。我们估计的因变量是连续的(冰淇淋的销量,婴儿的体重)。然而,在大多数情况下,我们需要对离散变量进行预测——例如,客户是否会流失,或者是否会赢得比赛。这些事件没有太多独特的价值。它们只有 1 或 0 的结果——无论事件是否发生。

虽然线性回归有助于预测变量的值(大小),但它在预测只有两个不同类别(1 或 0)的变量时有局限性。逻辑回归有助于解决这类问题,因为因变量的不同值数量有限。

在本章中,我们将学习以下内容:

  • 线性回归和逻辑回归的区别
  • 在 Excel、R 和 Python 中构建逻辑回归
  • 衡量逻辑回归模型性能的方法

为什么线性回归对离散结果会失败?

为了理解这一点,让我们举一个假设的例子:根据棋手之间的 Elo 评分差异来预测一场象棋比赛的结果。

| 黑白棋子玩家之间的等级差异 | 白棋赢了? | | :-- | :-- | | Two hundred | Zero | | –200 | one | | Three hundred | Zero |

在前面的简单例子中,如果我们应用线性回归,我们将得到下面的等式:

白色韩元= 0.55–0.00214×(黑白评级差异)

让我们使用该公式来推断上表:

| 黑白之间的评级差异 | 白棋赢了? | 线性回归预测 | | :-- | :-- | :-- | | Two hundred | Zero | Zero point one one | | –200 | one | Zero point nine seven | | Three hundred | Zero | –0.1 |

如您所见,300 的差异导致预测值小于 0。类似地,对于–300 的差异,线性回归的预测将超过 1。但是,在这种情况下,超过 0 或 1 的值没有意义,因为 win 是一个离散值(0 或 1)。

因此,预测应该仅绑定到 0 到 1-任何高于 1 的预测应该上限为 1,任何低于 0 的预测应该下限为 0。

这转化为一条拟合线,如图 3-1 所示。

A463052_1_En_3_Fig1_HTML.jpg

图 3-1

The fitted line

图 3.1 显示了线性回归在预测离散变量(本例中为二元变量)时的以下主要局限性:

  • 线性回归假设变量是线性相关的:然而,随着玩家实力差异的增加,获胜的机会呈指数变化。
  • 线性回归没有给出失败的机会:在实践中,即使有 500 分的差异,劣势玩家也有获胜的外部机会(假设有 1%的机会)。但是如果用线性回归封顶,另一个玩家就没有机会赢了。一般来说,线性回归不能告诉我们某一事件在一定范围后发生的概率。
  • 线性回归假设概率随着自变量的增加而成比例增加:无论评分差异是+400 还是+500(因为差异显著),获胜的概率都很高。同样,无论差额是–400 还是–500,获胜的概率都很低。

更一般的解决方案:s 形曲线

如上所述,线性回归的主要问题是它假设所有的关系都是线性的,尽管实际上很少是线性的。

为了解决线性回归的局限性,我们将探索一种称为 sigmoid 曲线的曲线。曲线如图 3-2 所示。

A463052_1_En_3_Fig2_HTML.jpg

图 3-2

A sigmoid curve

s 形曲线的特征如下:

  • 它在值 0 和 1 之间变化
  • 它在某个阈值之后(在图 3-2 中的值 3 或-3 之后)稳定下来

sigmoid 曲线将帮助我们解决线性回归面临的问题,即无论黑白棋子玩家之间的评分差异是+400 还是+500,赢的概率都很高,无论差异是–400 还是–500,赢的概率都很低。

形式化 s 形曲线(s 形激活)

我们已经看到,sigmoid 曲线比线性回归更能解释离散现象。

sigmoid 曲线可以用如下数学公式表示:

$$ S(t)=\frac{1}{1+{e}^{-t}} $$

在该等式中,t 的值越高,$$ {e}^{-t}, $$的值越低,因此 S(t)接近 1。并且 t 的值越低(比如说–100),$$ {e}^{-t} $$的值越高,【1 +】的值也越高,因此 S(t)非常接近 0。

从 Sigmoid 曲线到逻辑回归

线性回归假设因变量和自变量之间存在线性关系。它被写成 Y = a + b × X。逻辑回归通过应用 sigmoid 曲线,摆脱了所有关系都是线性的约束。

逻辑回归的数学模型如下:

$$ Y=\frac{1}{\left(1+{e}{-\left(a+{b}{\ast }X\right)}\right)} $$

我们可以看到,逻辑回归以与线性回归相同的方式使用独立变量,但通过 sigmoid 激活传递它们,以便输出介于 0 和 1 之间。

在存在多个独立变量的情况下,该方程转化为通过 sigmoid 激活的多元线性回归。

解读逻辑回归

线性回归可以用一种直截了当的方式来解释:自变量的值增加 1 个单位,产出(因变量)增加 b 个单位。

要了解逻辑回归中的输出如何变化,让我们看一个例子。让我们假设我们构建的逻辑回归曲线(我们将在接下来的章节中研究如何构建逻辑回归)如下:

$$ Y=\frac{1}{\left(1+{e}{-\left(2+{3}{\ast }X\right)}\right)} $$

  • 如果 X = 0,Y 的值= 1/(1+exp(–(2)))= 0.88。
  • 如果 X 增加 1 个单位(即 X = 1),则 Y 的值为 Y = 1/(1+exp(–(2+3×1)))= 1/(1+exp(–(5)))= 0.99。

如你所见,当 X 从 0 变到 1 时,Y 的值从 0.88 变到 0.99。类似地,如果 X 是–1,Y 应该是 0.27。如果 X 是 0,Y 应该是 0.88。当 X 从-1 到 0 时,Y 从 0.27 到 0.88 有一个剧烈的变化,但当 X 从 0 到 1 时,变化不那么剧烈。

因此,X 单位变化对 Y 的影响取决于等式。

X = 0 时的值 0.88 可以解释为概率。换句话说,平均在 88%的情况下,当 X = 0 时,Y 的值是 1。

逻辑回归的工作细节

为了了解逻辑回归是如何工作的,我们将进行与上一章学习线性回归相同的练习:我们将在 Excel 中建立一个逻辑回归方程。在本练习中,我们将使用虹膜数据集。挑战在于能够根据一些变量(萼片、花瓣长度和宽度)预测该物种是否为刚毛藻。

以下数据集包含我们将要执行的练习的自变量和因变量的值(在 github 中作为“iris sample estimation.xlsx”数据集提供):

  1. 将自变量的权重初始化为随机值(假设每个 1)。

  2. Once the weights and the bias are initialized, we’ll estimate the output value (the probability of the species being Setosa) by applying sigmoid activation on the multivariate linear regression of independent variables. The next table contains information about the (a + b × X) part of the sigmoid curve and ultimately the sigmoid activation value.

    A463052_1_En_3_Figb_HTML.jpg

    The formula for how the values in the preceding table are obtained is given in the following table:

    A463052_1_En_3_Figc_HTML.jpg

    The ifelse condition in the preceding sigmoid activation column is used only because Excel has limitations in calculating any value greater than exp(500)—hence the clipping.

A463052_1_En_3_Figa_HTML.jpg

估计误差

在第二章中,我们考虑了实际值和预测值之间的最小二乘法(平方差)来估计总体误差。在逻辑回归中,我们将使用不同的误差度量,称为交叉熵。

交叉熵是两种不同分布(实际分布和预测分布)之间差异的度量。为了理解交叉熵,让我们看一个例子:两个政党在一次选举中竞争,其中甲方获胜。在一种情况下,每一方获胜的机会是 0.5——换句话说,很少能得出结论,信息也很少。但是,如果甲方有 80%的机会获胜,而乙方有 20%的机会获胜,我们就可以得出关于选举结果的结论,因为实际值和预测值的分布更接近。

交叉熵的公式如下:

$$ -\left( ylo{g}_2p+\left(1-y\right) lo{g}_2\left(1-p\right)\right) $$

其中 y 是事件的实际结果,p 是事件的预测结果。

让我们把这两种选举情景代入这个等式。

场景 1

在这种情况下,模型预测甲方获胜的概率为 0.5,而甲方的实际结果是 1:

| 甲方模型预测 | 甲方的实际成果 | | :-- | :-- | | Zero point five | one |

这个模型的交叉熵如下:

$$ -\left(1 lo{g}_20.5+\left(1-1\right) lo{g}_2\left(1-0.5\right)\right)=1 $$

场景 2

在这种情况下,模型预测甲方获胜的概率为 0.8,而甲方的实际结果是 1:

| 甲方模型预测 | 甲方的实际成果 | | :-- | :-- | | Zero point eight | one |

这个模型的交叉熵如下:

$$ -\left(1 lo{g}_20.8+\left(1-1\right) lo{g}_2\left(1-0.8\right)\right)=0.32 $$

我们可以看到,与场景 1 相比,场景 2 具有更低的交叉熵。

最小二乘法与线性假设

假设在前面的例子中,当概率为 0.8 时,交叉熵比概率为 0.5 时更低,我们是否可以不使用预测概率和实际值之间的最小二乘方差,并以类似于线性回归的方式进行处理?本节讨论选择交叉熵误差而不是最小二乘法。

逻辑回归的一个典型例子是其在基于某些属性预测癌症是良性还是恶性中的应用。

让我们比较一下因变量(恶性肿瘤)为 1 的情况下的两种成本函数(最小二乘法和熵成本):

A463052_1_En_3_Figd_HTML.jpg

获取上表的公式如下:

A463052_1_En_3_Fige_HTML.jpg

请注意,与平方误差相比,交叉熵对高预测误差的惩罚较重:较低的误差值在平方误差和交叉熵误差方面具有相似的损失,但是对于实际值和预测值之间的较高差异,交叉熵的惩罚比平方误差方法更大。因此,我们将坚持交叉熵误差作为我们的误差度量,而不是离散变量预测的平方误差。

对于前面提到的 Setosa 分类问题,我们用交叉熵误差代替平方误差,如下:

A463052_1_En_3_Figf_HTML.jpg

现在我们已经建立了我们的问题,让我们以这样一种方式改变参数,使总误差最小化。这一步也是通过梯度下降来执行的,梯度下降可以通过使用 Excel 中的规划求解功能来完成。

在 R 中运行逻辑回归

现在我们已经有了一些逻辑回归的背景知识,我们将深入研究 R(可作为“逻辑回归”使用)中相同内容的实现细节。github 中的 r”)。

# import dataset
data=read.csv("D:/Pro ML book/Logistic regression/iris_sample.csv")
# build a logistic regression model
lm=glm(Setosa~.,data=data,family=binomial(logit))
# summarize the model
summary(lm)

上述代码的第二行指定我们将使用glm(广义线性模型),其中考虑了二项式家族。请注意,通过指定“~”我们确保所有的变量都被认为是独立变量。

逻辑模型的summary给出了一个高层次的总结,类似于我们在线性回归中得到总结结果的方式:

A463052_1_En_3_Figg_HTML.jpg

在 Python 中运行逻辑回归

现在让我们看看如何在 Python 中构建逻辑回归方程(在 github 中以“逻辑回归. ipynb”的形式提供):

# import relevant packages
# pandas package is used to import data
# statsmodels is used to invoke the functions that help in lm
import pandas as pd
import statsmodels.formula.api as smf

一旦我们导入了包,我们就在逻辑回归的情况下使用logit方法,如下所示:

# import dataset
data = pd.read_csv('D:/Pro ML book/Logistic regression/iris_sample.csv')
# run regression
est = smf.logit(formula='Setosa~Slength+Swidth+Plength+Pwidth',data=data)
est2=est.fit()
print(est2.summary())

前面代码中的summary函数给出了模型的摘要,类似于我们在线性回归中获得摘要结果的方式。

确定感兴趣的度量

在线性回归中,我们将均方根误差(RMSE)视为一种测量误差的方法。

在逻辑回归中,我们衡量模型性能的方式不同于我们在线性回归中衡量模型性能的方式。让我们探讨一下为什么线性回归误差度量不能用于逻辑回归。

我们将研究如何构建一个模型来预测欺诈交易。假设总交易量的 1%是欺诈交易。我们希望预测交易是否可能是欺诈。在这种特殊情况下,我们使用逻辑回归通过使用一组独立变量来预测因变量欺诈交易。

为什么我们不能用一个精确度来衡量呢?假设所有交易中只有 1%是欺诈,让我们考虑一个所有预测都为 0 的场景。在这个场景中,我们的模型有 99%的准确率。但该模型在减少欺诈交易方面毫无用处,因为它预测每笔交易都不是欺诈。

在典型的现实场景中,我们会构建一个模型来预测交易是否可能是欺诈,并且只标记欺诈可能性高的交易。被标记的交易随后被发送给运营团队进行人工审查,从而降低欺诈交易率。

虽然我们通过让运营团队审查高可能性交易来降低欺诈交易率,但我们会产生额外的人力成本,因为需要人工来审查交易。

欺诈交易预测模型可以帮助我们减少需要人工(运营团队)审查的交易数量。假设总共有 1,000,000 笔交易。在这 100 万笔交易中,1%是欺诈性的,因此,总共有 10,000 笔交易是欺诈性的。

在这种特殊情况下,如果没有模型,平均每 100 笔交易中就有 1 笔是欺诈性的。下表显示了随机猜测模型的性能:

A463052_1_En_3_Figh_HTML.png

如果我们要绘制这些数据,它看起来会如图 3-3 所示。

A463052_1_En_3_Fig3_HTML.jpg

图 3-3

Cumulative frauds captured by random guess model

现在让我们来看看构建一个模型有什么帮助。我们将创建一个简单的示例来得出误差度量:

  1. 以数据集为输入,计算每个交易 id 的概率:欺诈的可能性

    | 交易 id | 实际欺诈 | | :-- | :-- | | 一 | 一 | 零点五六 | | 二 | 零点七 | | | 三 | 一 | 零点三九 | | 四 | 一 | | 零点五五 | | 五 | 一 | 零点零三 | | 六 | 零 | 零点八四 | | 七 | 零 | 零点零五 |
  2. 按照欺诈概率从高到低对数据集进行排序。直觉是,当在按概率降序排序后,数据集顶部有更多“实际欺诈”1 时,模型表现良好:欺诈的可能性

    | 交易 id | 实际欺诈 | | :-- | :-- | | 九 | 零点八六 | | 六 | 零点八四 | | 两 | 零点 | 零点七 | | 一 | 一 | 零点五六 | | 四 | 一 | 零点五五五 | | 八 | 零点四六 | | | 零点零五 | | 五 | 一 | 零点零三 |
  3. 计算从排序后的表中捕获的累计交易数:

    | 交易 id | 实际欺诈 | 欺诈的可能性 | 已审核的累计交易 | 捕获的累积欺诈 | | :-- | :-- | :-- | :-- | :-- | | 九 | 零点八六 | 一 | 零点 | | 六 | 零点 | 零点八四 | 两 | 零点 | | 两个 | 零个 | 零点七个 | 三个 | | 一个 | 一个 | 零点五六个 | 四个 | 一个 | | 四个 | 一个 | 一 | 零点三九 | 七 | 三 | | 十 | 一 | 零点一一 | 八 | 四 | | 七 | 零 | |

在这种情况下,假设 10 笔交易中有 5 笔是欺诈性的,平均每 2 笔交易中就有 1 笔是欺诈性的。因此,与使用随机猜测相比,使用模型捕获的累积欺诈如下:

| 交易 id | 实际欺诈 | 已审核的累计交易 | 捕获的累积欺诈 | 通过随机猜测捕获的累积欺诈 | | :-- | :-- | :-- | :-- | :-- | | nine | Zero | one | Zero | Zero point five | | six | Zero | Two | Zero | one | | Two | Zero | three | Zero | One point five | | one | one | four | one | Two | | four | one | five | Two | Two point five | | eight | Zero | six | Two | three | | three | one | seven | three | Three point five | | Ten | one | eight | four | four | | seven | Zero | nine | four | Four point five | | five | one | Ten | five | five |

我们可以绘制随机模型和逻辑回归模型捕获的累积欺诈,如图 3-4 所示。

A463052_1_En_3_Fig4_HTML.jpg

图 3-4

Comparing the models

在这种特殊情况下,对于上面的例子,随机猜测比逻辑回归模型更好-在最初的几次猜测中,随机猜测比模型做出更好的预测。

现在,让我们回到之前的欺诈交易示例场景,假设模型的结果如下所示:

| 捕获的累积欺诈 | | :-- | | 审查的交易数量 | 通过随机猜测捕获的累积欺诈 | 按模型捕获的累积欺诈 | | :-- | :-- | :-- | | - | - | Zero | | One hundred thousand | One thousand | Four thousand | | Two hundred thousand | Two thousand | Six thousand | | Three hundred thousand | Three thousand | 7600 | | Four hundred thousand | Four thousand | Eight thousand one hundred | | Five hundred thousand | Five thousand | Eight thousand five hundred | | Six hundred thousand | Six thousand | Eight thousand eight hundred and fifty | | Seven hundred thousand | Seven thousand | Nine thousand one hundred and fifty | | Eight hundred thousand | Eight thousand | Nine thousand four hundred and fifty | | Nine hundred thousand | Nine thousand | Nine thousand seven hundred and fifty | | One million | Ten thousand | ten thousand |

我们展示了随机猜测捕获的累积欺诈与模型捕获的累积欺诈之间的图表,如图 3-5 所示。

A463052_1_En_3_Fig5_HTML.jpg

图 3-5

Comparing the two approaches

请注意,随机猜测线和模型线之间的面积越大,模型性能越好。衡量模型线下覆盖面积的指标称为曲线下面积(AUC)。

因此,AUC 指标是帮助我们评估逻辑回归模型性能的更好指标。

在实践中,当评分的数据集根据概率被分成十个桶(组)(代码在 github 中提供为“credit default prediction.ipynb”)时,稀有事件建模的输出看起来如下:

A463052_1_En_3_Figi_HTML.jpg

上表中的prediction_rank表示概率的十分位数,即每笔交易按概率排序,然后根据其所属的十分位数分组到存储桶中。注意,第三列(total_observations)在每个十分位数中有相同数量的观察值。

第二列——prediction avg_default——代表我们建立的模型得出的平均违约概率。第四列—SeriousDlqin2yrs avg_default—代表每个时段的平均实际违约。最后一列表示每个时段中捕获的实际违约数量。

请注意,在理想的情况下,所有的默认值都应该被捕获到最有可能的桶中。另请注意,在上表中,该模型在最高概率时段中捕获了大量欺诈。

常见陷阱

本节讨论分析师在构建分类模型时应该小心的一些常见陷阱:

预测和事件发生之间的时间

让我们来看一个案例研究:预测一个客户的违约。

我们应该说,今天预测明天有人很可能信用卡违约是没有用的。在预测某人会违约的时间和事件实际发生的时间之间应该有一些时间差。原因是运营团队需要一些时间来干预并帮助减少违约交易的数量。

独立变量中的异常值

类似于独立变量中的异常值如何影响线性回归中的总体误差,最好对异常值进行封顶,以便它们不会对逻辑回归中的回归产生太大影响。注意,与线性回归不同,当有异常值输入时,逻辑回归不会有巨大的异常值输出;在逻辑回归中,输出总是被限制在 0 和 1 之间,并且相应的交叉熵损失与之相关联。

但是有异常值的问题仍然会导致很高的交叉熵损失,所以限制异常值是一个更好的主意。

摘要

在本章中,我们经历了以下内容:

  • 逻辑回归用于预测二元(分类)事件,线性回归用于预测连续事件。
  • 逻辑回归是线性回归的扩展,其中线性方程通过 sigmoid 激活函数。
  • 逻辑回归中使用的主要损失度量之一是交叉熵误差。
  • sigmoid 曲线有助于限制 0 到 1 之间的输出值,从而估计与事件相关的概率。
  • AUC 度量是评估逻辑回归模型的更好的度量。

四、决策树

在前几章中,我们已经考虑了基于回归的算法,这些算法通过改变系数或权重来优化某个指标。决策树形成了基于树的算法的基础,该算法帮助识别规则以分类或预测我们感兴趣的事件或变量。此外,与针对回归或分类进行优化的线性或逻辑回归不同,决策树能够同时执行这两种操作。

决策树的主要优势来自于它们对业务用户友好的事实——也就是说,决策树的输出对业务用户来说是直观且易于解释的。

在本章中,我们将学习以下内容:

  • 决策树如何在分类和回归练习中工作
  • 当自变量是连续或离散时,决策树如何工作
  • 提出最佳决策树所涉及的各种技术
  • 各种超参数对决策树的影响
  • 如何在 Excel、Python 和 R 中实现决策树

决策树是一种算法,有助于对事件进行分类或预测变量的输出值。您可以将决策树想象为一组规则,基于这些规则可以预期不同的结果。比如看图 4-1 。

A463052_1_En_4_Fig1_HTML.jpg

图 4-1

An example decision tree

在图 4-1 中,我们可以看到一个数据集(左边的表格)同时使用连续变量(应税收入)和分类变量(退税、婚姻状况)作为独立变量来分类某人是否在偷税漏税(分类因变量)。

右边的树有几个组成部分:根节点、决策节点和叶节点(我将在下一节详细讨论这些)来分类某人是否会作弊(是/否)。

从所示的树中,用户可以导出以下规则:

  1. 婚姻状况为“是”的人通常不是骗子。
  2. 一个离了婚但较早拿到退款的人也不会出轨。
  3. 一个离了婚,没有得到退款,但应税收入少于 80K 的人也不是骗子。
  4. 那些不属于任何上述类别的人在这个特定的数据集中是骗子。

与回归相似,我们推导出一个等式(例如,根据客户特征预测信用违约),决策树也可以根据客户特征(例如,上例中的婚姻状况、退款和应税收入)来预测或预报事件。

当一个新客户申请信用卡时,规则引擎(在后端运行的决策树)将检查该客户在通过决策树的所有规则后是属于风险类别还是非风险类别。通过规则后,系统将根据用户所处的阶段批准或拒绝信用卡。

决策树的明显优势是直观的输出和可视化,可以帮助业务用户做出决策。在分类的情况下,决策树比典型的回归技术对异常值更不敏感。此外,就构建模型、解释模型甚至实现模型而言,决策树是最简单的算法之一。

决策树的组成部分

决策树的所有组成部分如图 4-2 所示。

A463052_1_En_4_Fig2_HTML.png

图 4-2

Components of a decision tree

这些组件包括以下内容:

  • 根节点:该节点代表整个总体或样本,并被分成两个或多个同类集合。
  • 拆分:根据一定的规则将一个节点划分为两个或多个子节点的过程。
  • 决策节点:当一个子节点分裂成更多的子节点时,它被称为决策节点。
  • 叶/终端节点:决策树中的最后一个节点。
  • 修剪:从决策节点中删除子节点的过程,与拆分相反。
  • 分支/子树:整个树的一个子部分称为分支或子树。
  • 父节点和子节点:被划分为子节点的节点称为这些子节点的父节点,子节点是父节点的子节点。

存在多个离散自变量时的分类决策树

根据因变量是连续变量还是分类变量,在根节点进行分割的标准因我们预测的变量类型而异。在这一节中,我们将通过一个例子来看看从根节点到决策节点的分裂是如何发生的。在这个例子中,我们试图根据几个独立变量(教育、婚姻状况、种族和性别)来预测雇员的工资(emp_sal)。

以下是数据集(在 github 中以“分类相关和独立变量. xlsx”的形式提供):

A463052_1_En_4_Figa_HTML.jpg

这里,Emp_sal为因变量,其余变量为自变量。

分割根节点(原始数据集)时,首先需要确定进行第一次分割所基于的变量,例如,是否基于教育程度、婚姻状况、种族或性别进行分割。为了想出一种方法来筛选出一个独立变量,我们使用信息增益标准。

信息增益

将信息增益与不确定性联系起来可以更好地理解它。让我们假设有两个政党在两个不同的州竞选。在一个州,双方获胜的机会是 50:50,而在另一个州,甲方获胜的机会是 90%,而乙方获胜的机会是 10%。

如果我们要预测选举的结果,后一种状态比前一种状态更容易预测,因为在这种状态下不确定性最小(甲方获胜的概率为 90%)。因此,信息增益是分裂节点后不确定性的度量。

计算不确定性:熵

不确定性,也称为熵,由公式

$$ -\left( plo{g}_2p+ qlo{g}_2q\right) $$

来度量

其中 p 是事件 1 发生的概率,q 是事件 2 发生的概率。

让我们考虑一下双方的双赢方案:

| 方案 | 甲方不确定性 | 乙方不确定性 | 总体不确定性 | | :-- | :-- | :-- | :-- | | 获胜的机会均等 | 0.5log 2 (0.5) = 0.5 | 0.5log 2 (0.5) = 0.5 | 0.5 + 0.5 =1 | | 甲方有 90%的胜算 | 0.9log 2 (0.9) = 0.1368 | 0.1log 2 (0.1) = 0.3321 | 0.1368 + 0.3321 = 0.47 |

我们看到,根据前面的等式,第二种情况比第一种情况具有更少的总体不确定性,因为第二种情况有 90%的机会使甲方获胜。

计算信息增益

我们可以把根节点想象成存在最大不确定性的地方。随着我们明智地进一步分裂,不确定性会减少。因此,选择分割(分割应基于的变量)取决于哪些变量最能减少不确定性。

为了查看计算是如何进行的,让我们基于数据集构建一个决策树。

原始数据集中的不确定性

在原始数据集中,九个观测值的薪水为<= 50K, while five have a salary > 50K:

A463052_1_En_4_Figb_HTML.jpg

让我们计算 p 和 q 的值,以便计算总不确定度:

A463052_1_En_4_Figc_HTML.jpg

p 和 q 的公式如下:

A463052_1_En_4_Figd_HTML.jpg

因此,根节点的总体不确定性如下:

| 不确定度< =50K | 不确定性大于 50K | 总体不确定性 | | –0.64×log2(0.64)= 0.41 | 0.36 × log 2 (0.36) = 0.53 | 0.41 + 0.53 = 0.94 |

根节点中的总体不确定性为 0.94。

为了完成第一步,查看筛选变量的过程,我们将计算出如果我们在第一次拆分中考虑所有四个独立变量,总体不确定性降低的量。我们将考虑第一次分裂的教育(我们将计算不确定性的改善),接下来我们将以同样的方式考虑婚姻状况,然后是种族,最后是员工的性别。最能减少不确定性的变量将是我们在第一次分割时应该使用的变量。

测量不确定度的改善

要了解不确定性的改善是如何计算的,请考虑以下示例。让我们考虑一下是否要按员工的性别来划分变量:

A463052_1_En_4_Fige_HTML.jpg

我们计算每个变量的每个不同值的不确定性—(plog2p+qlog2q)。其中一个变量(Sex)的不确定度计算表如下:

| 性 | P | Q | ——(plog〔??〕2〔??〕p) | -(qlog2q) | ——(plog2p+qlog2q) | 加权不确定性 | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | 女性的 | 4/5 | 1/5 | Zero point two five seven | Zero point four six | Zero point seven two | 0.72 × 5/14 = 0.257 | | 男性的 | 5/9 | 4/9 | Zero point four seven one | Zero point five two | Zero point nine nine | 0.99 × 9/14 = 0.637 | | 全部的 |   |   |   |   |   | Zero point eight nine four |

将进行类似的计算来测量所有变量的总体不确定性。如果我们用变量Sex分割根节点,信息增益如下:

(原始熵-熵,如果我们除以变量Sex)= 0.94–0.894 = 0.046

基于总的不确定性,最大化信息增益(不确定性的减少)的变量将被选择用于分裂树。

在我们的示例中,变量方面的总体不确定性如下:

| 可变的 | 总体不确定性 | 从根节点减少不确定性 | | :-- | :-- | :-- | | 教育 | Zero point six seven nine | 0.94 – 0.679 = 0.261 | | 婚姻状况 | Zero point eight zero three | 0.94 – 0.803 = 0.137 | | 人种 | Zero point eight zero three | 0.94 – 0.803 = 0.137 | | 性 | Zero point eight nine four | 0.94 – 0.894 = 0.046 |

由此我们可以观察到,分割决策应该基于Education而不是任何其他变量,因为它是最大程度地降低总体不确定性的变量(从 0.94 到 0.679)。

一旦做出了关于分割的决定,下一步(对于具有两个以上不同值的变量—在我们的示例中为 education)将是确定哪个唯一值应该进入右决策节点,哪个唯一值应该进入根节点之后的左决策节点。

让我们看看教育的所有独特价值,因为它是最能减少不确定性的变量:

| 独特的价值 | obs 的百分比。< =50K | | :-- | :-- | | 第 11 | 100% | | 第九届 | 100% | | Assoc-acdm | 100% | | 学士 | 67% | | HS 度 | 50% | | 主人 | 50% | | 某大学 | 0% | | 全部的 | 64% |

哪些不同的值位于左侧和右侧节点

在前面的部分中,我们得出结论,教育是第一个分裂的变量。下一个要做的决定是,哪些不同的教育价值观属于左边的节点,哪些不同的价值观属于右边的节点。

在这种情况下,基尼系数就派上了用场。

基尼杂质

基尼系数是指一个节点内的不平等程度。如果一个节点的所有值都属于一个类而不属于另一个类,那么它就是最纯粹的节点。如果一个节点有一个类的 50%的观测值,其余的是另一个类的观测值,那么它就是节点最不纯的形式。

基尼系数被定义为$$ 1-\left({p}²+{q}²\right) $$,其中 p 和 q 是与每一类相关的概率。

考虑以下场景:

| P | Q | 基尼指数值 | | :-- | :-- | :-- | | Zero | one | 1–02–12= 0 | | one | Zero | 1–12–02= 0 | | Zero point five | Zero point five | 1–0.52–0.52= 0.5 |

我们用基尼杂质来做员工工资预测问题:从上一节的信息增益计算中,我们观察到Education是用作第一次拆分的变量。

为了确定哪些不同的值进入左侧节点,哪些进入右侧节点,让我们进行以下计算:

|   | 观察次数 |   | 左侧节点 | 右节点 | 不纯 | obs 的数量 |   | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | |   | < =50K | > 50K | 大共计 | p | q | p | q | 左侧节点 | 右节点 | 左侧节点 | 右节点 | 加权杂质 | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | 第 11 | one |   | one | 100% | 0% | 62% | 38% | - | Zero point four seven | one | Thirteen | Zero point four four | | 第九届 | one |   | one | 100% | 0% | 58% | 42% | - | Zero point four nine | Two | Twelve | Zero point four two | | Assoc-acdm | one |   | one | 100% | 0% | 55% | 45% | - | Zero point five | three | Eleven | Zero point three nine | | 学士 | four | Two | six | 78% | 22% | 40% | 60% | Zero point three five | Zero point four eight | nine | five | Zero point three nine | | HS 度 | one | one | Two | 73% | 27% | 33% | 67% | Zero point four | Zero point four four | Eleven | three | Zero point four one | | 主人 | one | one | Two | 69% | 31% | 0% | 100% | Zero point four three | - | Thirteen | one | Zero point four | | 某大学 |   | one | one |   |   |   |   |   |   |   |   |   |

我们可以通过以下步骤来理解上表:

  1. 等级按属于某一类的观察值的百分比对不同的值进行排序。这将导致不同的值被重新排序如下:第 11

    | 独特的价值 | obs 的百分比< = 50K | | :-- | :-- | | | | 100%第九届 | | | 100% | | | 学士 | 67% | | HS 度 | 50% | | 主人 | 50% | | 某大学 | |

    0%

  2. 第一步,我们假设只有对应于11th的 distinct 值到左节点,其余的 distinct 观察值对应于右节点。左侧节点中的杂质为 0,因为它只有一个观测值,右侧节点中会有一些杂质,因为八个观测值属于一个类,五个属于另一个类。

  3. 总杂质计算如下:((obs 左侧节点中的杂质× #)。左侧节点中)+(右侧节点中的杂质×# OBS。右侧节点中)obs 总数。)

  4. 我们重复第 2 步和第 3 步,但是这一次我们在左侧节点中包含了11th9th,在右侧节点中包含了其余的不同值。

  5. 重复该过程,直到在左节点中考虑了所有不同的值。

  6. 具有最小总加权杂质的组合是将在左节点和右节点中选择的组合。

在我们的示例中,{ 11th,9th,Assoc-acdm }的组合去往左侧节点,其余的去往右侧节点,因为该组合具有最小的加权杂质。

进一步分裂子节点

根据目前的分析,我们将原始数据分为以下几部分:

A463052_1_En_4_Figf_HTML.jpg

现在有机会进一步拆分正确的节点。让我们看看下一个分裂的决定是怎样的。要分割的数据是属于右侧节点的所有数据点,如下所示:

A463052_1_En_4_Figg_HTML.png

根据前面的数据,我们将执行以下步骤:

  1. 使用信息增益度量来确定用于分割数据的变量。

  2. Use the Gini index to figure out the distinct values within that variable that should belong to the left node and the ones that should belong to the right node. The overall impurity in the parent node for the preceding dataset is as follows:

    A463052_1_En_4_Figi_HTML.jpg

    A463052_1_En_4_Figh_HTML.jpg

现在整体杂质是~0.99,让我们看看减少整体杂质最多的变量。我们将经历的步骤将与对整个数据集的上一次迭代相同。请注意,当前版本与先前版本之间的唯一区别在于,在根节点中考虑了整个数据集,而在子节点中仅考虑了数据的子集。

让我们分别计算每个变量获得的信息增益(类似于上一节我们计算的方式)。以婚姻状况为变量的总杂质计算如下:

A463052_1_En_4_Figj_HTML.jpg

同样,与员工种族相关的杂质如下:

A463052_1_En_4_Figk_HTML.jpg

与性别相关的杂质计算如下:

A463052_1_En_4_Figl_HTML.jpg

与员工教育相关的杂质计算如下:

A463052_1_En_4_Figm_HTML.jpg

从上面,我们注意到员工教育程度作为一个变量是从父节点减少杂质最多的一个,也就是说,从员工教育程度变量获得的信息增益最高。

注意,巧合的是,同一个变量在父节点和子节点中两次拆分了数据集。这种模式可能不会在不同的数据集上重复。

分裂过程什么时候停止?

理论上,分裂过程可以发生,直到决策树的所有终端(叶/最后)节点都是纯的(它们都属于一个类或另一个类)。

然而,这种方法的缺点是它过度拟合数据,因此可能不具有普遍性。因此,决策树是树的复杂性(树中终端节点的数量)和准确性之间的折衷。在终端节点较多的情况下,训练数据的准确率可能较高,但验证数据的准确率可能不高。

这让我们想到了树的复杂性参数和开箱验证的概念。随着树的复杂性增加,也就是说,树的深度变得更高,训练数据集的准确性会不断增加,但测试数据集的准确性可能会在超过树的特定深度后开始变低。

分割过程应在验证数据集精度不再提高时停止。

连续自变量的分类决策树

到目前为止,我们认为自变量和因变量都是分类变量。但实际上,我们可能也在处理连续变量,作为独立变量。本节讨论如何为连续变量构建决策树。

在接下来的章节中,我们将使用以下数据集(github 中的“分类相关连续独立变量. xlsx ”)来研究如何为分类相关变量和连续独立变量构建决策树:

A463052_1_En_4_Fign_HTML.png

在这个数据集中,我们将尝试根据Age变量来预测某人是否会幸存。

因变量为Survived,自变量为Age

  1. Sort the dataset by increasing independent variable. The dataset thus transforms into the following:

    A463052_1_En_4_Figo_HTML.png

  2. 测试多个规则。例如,当年龄小于 7、小于 10 等等直到年龄小于 94 时,我们可以测试左右节点中的杂质。

  3. 计算基尼杂质:左侧节点 ??右节点 ??总杂质 右节点 pq不纯 十六 零点二四 【55%

    | | | | OBS 的数量 | | :-- | :-- | :-- | :-- | | 幸存 幸存人数 2 | 左侧节点 | p | q | 不纯 | | :-- | :-- | :-- | :-- | :-- | | 三 | 一 | 一 | | 十一 | | | | | | | | | 七 | 一 | 一 | | 三 | 九 | 100% | 0% | - | 33% | 67% | 零点四四 | 零点三六 | | 十五 | 一 | 一 | 五 | 七 | 100% | 0% | - | 14% | 86% | 零点二四 | 零点一六 | 二十六 | 零点 | 一 | 七 | 五 | 86% | 14% | 零点二四 | 0% | 100% | - | | 三十四 | 零点 | 一 | 九 | 三 | 67% | 33% | 零点四四 | 0% | 100% | | - | 零点三九 | | 七十六 | 零 | 一 | 十一 | 一 | 45% | 零 | | |

    从上表中我们应该注意到,当自变量取值小于 26 时,基尼杂质最少。 因此,我们将选择小于 26 的Age作为分割原始数据集的规则。注意,Age > 20 和Age < 26 将数据集分割到相同程度的错误率。在这种情况下,我们需要想出一种方法在两个规则中选择一个规则。我们将取这两个规则的平均值,因此Age < = 23 将是介于这两个规则之间的规则,因此比这两个规则中的任何一个都好。

有多个自变量时的分类决策树

为了了解当多个独立变量连续时决策树是如何工作的,我们来看一下下面的数据集(github 中的“分类相关的多个连续独立变量. xlsx”):

| 幸存 | 年龄 | 未知的 | | :-- | :-- | :-- | | one | Thirty | Seventy-nine | | one | seven | Sixty-seven | | one | One hundred | Fifty-three | | one | Fifteen | Thirty-three | | one | Sixteen | Thirty-two | | one | Twenty | five | | Zero | Twenty-six | Fourteen | | Zero | Twenty-eight | Sixteen | | Zero | Thirty-four | Seventy | | Zero | Sixty-two | Thirty-five | | Zero | Seventy-six | Sixty-six | | Zero | Ninety-four | Twenty-two |

到目前为止,我们已经按照以下顺序进行了计算:

  1. 通过使用信息增益,首先确定应该用于分割的变量。
  2. 一旦确定了变量,在离散变量的情况下,确定应该属于左节点和右节点的唯一值。
  3. 在连续变量的情况下,测试所有的规则,并列出导致最小总杂质的规则。

在这种情况下,我们将反转场景—也就是说,如果我们要对任何变量进行拆分,我们将首先找出拆分到左侧节点和右侧节点的规则。一旦确定了左右节点,我们将计算通过分割获得的信息增益,从而列出应该分割整个数据集的变量。

首先,我们将计算两个变量的最优分割。我们将从第一个变量Age开始:

|   |   |   | obs 的数量 | 左侧节点 |   | 右节点 |   |   | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | 年龄 | 幸存 | 幸存人数 2 | 左侧节点 | 右节点 | p | q | 不纯 | p | q | 不纯 | 总杂质 | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | seven | one | one |   | Eleven |   |   |   |   |   |   |   | | Fifteen | one | one | Two | Ten | 100% | 0% | - | 40% | 60% | Zero point four eight | Zero point four | | Sixteen | one | one | three | nine | 100% | 0% | - | 33% | 67% | Zero point four four | Zero point three three | | Twenty | one | one | four | eight | 100% | 0% | - | 25% | 75% | Zero point three eight | Zero point two five | | Twenty-six | Zero | one | five | seven | 80% | 20% | Zero point three two | 29% | 71% | Zero point four one | Zero point three seven | | Twenty-eight | Zero | one | six | six | 67% | 33% | Zero point four four | 33% | 67% | Zero point four four | Zero point four four | | Thirty | one | one | seven | five | 71% | 29% | Zero point four one | 20% | 80% | Zero point three two | Zero point three seven | | Thirty-four | Zero | one | eight | four | 63% | 38% | Zero point four seven | 25% | 75% | Zero point three eight | Zero point four four | | Sixty-two | Zero | one | nine | three | 56% | 44% | Zero point four nine | 33% | 67% | Zero point four four | Zero point four eight | | Seventy-six | Zero | one | Ten | Two | 50% | 50% | Zero point five | 50% | 50% | Zero point five | Zero point five | | Ninety-four | Zero | one | Eleven | one | 45% | 55% | Zero point five | 100% | 0% | - | Zero point four five | | One hundred | one | one | Twelve |   |   |   |   |   |   |   |   | | 年龄 | 幸存 | | :-- | :-- | | seven | one | | Fifteen | one | | Sixteen | one | | Twenty | one | | Twenty-six | Zero | | Twenty-eight | Zero | | Thirty | one | | Thirty-four | Zero | | Sixty-two | Zero | | Seventy-six | Zero | | Ninety-four | Zero | | One hundred | one |

从数据中我们可以看出,推导出的规律应该是Age < = 20 或者Age > = 26。所以我们还是用中间值:Age < = 23。

现在我们已经推导出了规则,让我们来计算与分割相对应的信息增益。在计算信息增益之前,我们将计算原始数据集中的熵:

A463052_1_En_4_Figp_HTML.jpg

假设 0 和 1 都是 6(各有 50%的概率),总熵就是 1。

根据下面的内容,我们注意到,如果我们首先使用Age变量分割数据集,熵从值 1 减少到 0.54:

|   | 幸存 |   |   |   |   |   | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | |   | Zero | one | 大共计 | p | q | -(plogp+qlogq) | 加权熵 | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | 年龄< =23 岁 |   | four | four | Zero | one | Zero | Zero | | 年龄> 23 岁 | six | Two | eight | Zero point seven five | Zero point two five | 0.811278 | 0.540852 | | 大共计 | six | six | Twelve |   | 总熵 |   | 0.540852 |

类似地,如果我们按照名为Unknown的列分割数据集,当Unknown <的值= 22 时出现最小值,如下所示:

|   |   |   | obs 的数量 | 左侧节点 |   | 右节点 |   |   | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | 未知的 | 幸存 | 幸存人数 2 | 左侧节点 | 右节点 | p | q | 不纯 | p | q | 不纯 | 总杂质 | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | five | one | one |   | Eleven |   |   |   |   |   |   |   | | Fourteen | Zero | one | Two | Ten | 50% | 50% | Zero point five | 50% | 50% | Zero point five | Zero point five | | Sixteen | Zero | one | three | nine | 33% | 67% | Zero point four four | 56% | 44% | Zero point four nine | Zero point four eight | | Twenty-two | Zero | one | four | eight | 25% | 75% | Zero point three eight | 63% | 38% | Zero point four seven | Zero point four four | | Thirty-two | one | one | five | seven | 40% | 60% | Zero point four eight | 57% | 43% | Zero point four nine | Zero point four nine | | Thirty-three | one | one | six | six | 50% | 50% | Zero point five | 50% | 50% | Zero point five | Zero point five | | Thirty-five | Zero | one | seven | five | 43% | 57% | Zero point four nine | 60% | 40% | Zero point four eight | Zero point four nine | | Fifty-three | one | one | eight | four | 50% | 50% | Zero point five | 50% | 50% | Zero point five | Zero point five | | Sixty-six | Zero | one | nine | three | 44% | 56% | Zero point four nine | 67% | 33% | Zero point four four | Zero point four eight | | Sixty-seven | one | one | Ten | Two | 50% | 50% | Zero point five | 50% | 50% | Zero point five | Zero point five | | Seventy | Zero | one | Eleven | one | 45% | 55% | Zero point five | 100% | 0% | - | Zero point four five | | Seventy-nine | one | one | Twelve |   |   |   |   |   |   |   |   |

因此,所有小于或等于 22 的值属于一个组(左节点),其余的属于另一个组(右节点)。注意,实际上我们会选择 22 到 32 之间的中间值。

在通过Unknown变量进行分割的情况下,总熵如下:

|   | 幸存 |   |   |   |   | | :-- | :-- | :-- | :-- | :-- | :-- | |   | Zero | one | 大共计 | p | q | -(plogp+qlogq)我们给出了熵 | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | 未知< =22 | three | one | four | Zero point seven five | Zero point two five | Zero point eight one | Zero point two seven | | 未知> 22 | three | five | eight | Zero point three seven five | Zero point six two five | Zero point nine five | Zero point six four | | 大共计 | six | six | Twelve |   | 总熵 | Zero point nine one |

从数据中我们看到,由于被Unknown变量分割,信息增益仅为 0.09。因此,分割将基于Age,而不是Unknown

存在连续和离散自变量时的分类决策树

我们已经看到了当所有独立变量都是连续的和当所有独立变量都是离散的时候,构建分类决策树的方法。

如果一些独立变量是连续的,其余的是离散的,那么我们构建决策树的方式与我们在前面章节中构建的方式非常相似:

  1. 对于连续的自变量,我们计算最优分裂点。
  2. 一旦计算出最佳分裂点,我们就计算与之相关的信息增益。
  3. 对于离散变量,我们通过计算基尼系数来确定独立变量中不同值的分组。
  4. 最大化信息增益的变量是首先分裂决策树的变量。
  5. 我们继续前面的步骤,进一步构建树的子节点。

如果响应变量是连续的呢?

如果响应变量是连续的,我们在上一节中构建决策树的步骤保持不变,只是我们不是计算基尼系数杂质或信息增益,而是计算平方误差(类似于我们在回归技术中最小化平方误差和的方法)。减少数据集总均方误差的变量将是分割数据集的变量。

为了查看决策树在连续因变量和自变量的情况下如何工作,我们将通过以下数据集作为示例(在 github 中以“连续变量因变量和自变量. xlsx”的形式提供):

| 可变的 | 反应 | | :-- | :-- | | -0.37535 | One thousand five hundred and ninety | | -0.37407 | Two thousand three hundred and nine | | -0.37341 | Eight hundred and fifteen | | -0.37316 | Two thousand two hundred and twenty-nine | | -0.37263 | Eight hundred and thirty-nine | | -0.37249 | Two thousand two hundred and ninety-five | | -0.37248 | One thousand nine hundred and ninety-six |

这里自变量命名为variable,因变量命名为response。第一步是按照自变量对数据集进行排序,就像我们在分类决策树示例中所做的那样。

一旦数据集按感兴趣的独立变量排序,我们的下一步是确定将数据集分成左右节点的规则。我们可能会提出多种可能的规则。我们将执行的练习将有助于筛选出一个最佳分割数据集的规则。

A463052_1_En_4_Figq_HTML.jpg

A463052_1_En_4_Figz_HTML.jpg

从前面我们可以看出,当variable < -0.37249 时,总误差最小。因此,属于左侧节点的点的平均响应为 1,556,属于右侧节点的点的平均响应为 2,146。请注意,1,556 是小于我们之前得出的阈值的所有变量值的平均响应。同样,2,146 是大于或等于我们得出的阈值(0.37249)的所有变量值的平均响应。

连续因变量和多个连续自变量

在分类中,我们将信息增益作为一种度量来决定应该首先分割原始数据集的变量。类似地,在连续变量预测中有多个相互竞争的独立变量的情况下,我们将列出导致总体误差最小的变量。

我们将在之前考虑的数据集中添加一个额外的变量:

A463052_1_En_4_Figr_HTML.png

在前一节中,我们已经计算了variable的各种可能规则的总误差。让我们计算一下var2的各种可能规则的总体误差。

第一步是通过增加var2的值对数据集进行排序。因此,我们现在将处理的数据集将转换为以下内容:

| var2 | 反应 | | :-- | :-- | | Zero point one | One thousand nine hundred and ninety-six | | Zero point three | Eight hundred and thirty-nine | | Zero point four four | Two thousand two hundred and twenty-nine | | Zero point five one | Two thousand three hundred and nine | | Zero point seven five | Eight hundred and fifteen | | Zero point seven eight | Two thousand two hundred and ninety-five | | Zero point eight four | One thousand five hundred and ninety |

使用var2开发的各种可能规则的总误差计算如下:

A463052_1_En_4_Figs_HTML.jpg

注意,当var2 <为 0.44 时,总体误差最小。然而,当我们比较由variable产生的最小总体误差和由var2产生的最小总体误差时,variable产生最小总体误差,因此应该是分割数据集的变量。

连续因变量和离散自变量

为了了解如何使用离散自变量预测连续因变量,我们将使用以下数据集作为示例,其中“var”是自变量,“response”是因变量:

| 定义变量 | 反应 | | :-- | :-- | | `a` | One thousand five hundred and ninety | | `b` | Two thousand three hundred and nine | | `c` | Eight hundred and fifteen | | `a` | Two thousand two hundred and twenty-nine | | `b` | Eight hundred and thirty-nine | | `c` | Two thousand two hundred and ninety-five | | `a` | One thousand nine hundred and ninety-six |

让我们按如下方式透视数据集:

A463052_1_En_4_Figt_HTML.jpg

我们将通过增加平均response值对数据集进行排序:

A463052_1_En_4_Figu_HTML.jpg

现在我们将计算最佳的左节点和右节点组合。在第一种情况下,只有c在左节点,而a,b在右节点。左侧节点的平均响应将是 1555,右侧节点的平均响应将是{1574,1938} = {1756}的平均值。

这种情况下的总体误差计算如下:

A463052_1_En_4_Figv_HTML.png

在第二个场景中,我们将认为{c,b}属于左侧节点,而{a}属于右侧节点。在这种情况下,左侧节点的平均响应将是{1555,1574} = {1564.5}的平均值

这种情况下的总误差计算如下:

A463052_1_En_4_Figw_HTML.png

我们可以看到,后一种左右节点的组合与前一种组合相比,总误差较小。因此,在这种情况下,理想的分割应该是{b,c}属于一个节点,而{a}属于另一个节点。

连续因变量和离散、连续自变量

在有多个独立变量的情况下,有些变量是离散的,有些是连续的,我们遵循与前面相同的步骤:

  1. 分别确定每个变量的最佳分界点。
  2. 了解最能减少不确定性的变量。

要遵循的步骤与前面几节中的步骤相同。

用 R 语言实现决策树

分类的实现不同于回归(连续变量预测)的实现。因此,需要模型类型的规范作为输入。

下面的代码片段展示了我们如何在 R 中实现一个决策树。github 中的 r”)。

# import dataset
t=read.csv("D:/Pro ML book/Decision tree/dt_continuous_dep_indep.csv")
library(rpart)
# fit a decision tree using rpart function
fit=rpart(response~variable,method="anova", data=t
      ,control=rpart.control(minsplit=1,minbucket=2,maxdepth=2))

决策树是使用名为rpart的包中的可用函数实现的。帮助构建决策树的函数也叫做rpart。注意,在rpart中,我们指定了method参数。

因变量连续时使用方法anova,因变量离散时使用方法class

您还可以指定附加参数:minsplitminbucketmaxdepth(下一节将详细介绍)。

用 Python 实现决策树

分类问题的 Python 实现将利用sklearn包中的DecisionTreeClassifier函数(代码在 github 中以“Decision tree.ipynb”的形式提供):

from sklearn.tree import DecisionTreeClassifier
depth_tree = DecisionTreeClassifier()
depth_tree.fit(X, y)

对于回归问题,Python 实现将利用sklearn包中的DecisionTreeRegressor函数:

from sklearn.tree import DecisionTreeRegressor
depth_tree = DecisionTreeRegressor()
depth_tree.fit(X, y)

树木建造的常用技术

我们之前看到,复杂性参数(终端节点的数量)可以是我们在检查开箱验证时进行优化的一个参数。其他常用技术包括:

  • 将每个终端节点中的观测值数量限制到最小数量(例如,在一个节点中至少 20 个观测值)
  • 手动指定树的最大深度
  • 指定节点中的最小观察数,以便算法考虑进一步拆分

我们做以上所有事情是为了避免树过度适应我们的数据。为了理解过度拟合的问题,让我们来看下面的场景:

  1. 该树总共有 90 个深度级别(maxdepth = 90)。在这种情况下,构建的树会有太多的分支,以至于在训练数据集上过度拟合,但不一定在测试数据集上一般化。
  2. 类似于maxdepth的问题,终端节点中最小数量的观察也可能导致过度拟合。如果我们不指定maxdepth并且在终端节点参数中的最小观察数量中具有较小的数量,则结果树同样可能是巨大的,具有多个分支,并且将同样可能导致对训练数据集的过度拟合,而不是对测试数据集的一般化。
  3. 要进一步分割的节点中的最小观测值数量是一个与节点中的最小观测值非常相似的参数,只是该参数限制了父节点而不是子节点中的观测值数量。

可视化树构建

在 R 中,可以使用rpart包中的plot函数来绘制树结构。plot函数绘制了树的框架,text函数编写了在树的各个部分派生的规则。下面是可视化决策树的一个示例实现:

# import dataset
t=read.csv("D:/Pro ML book/Decision tree/dt_continuous_dep_discrete_indep.csv")
library(rpart)
# fit a decision tree using rpart function
fit=rpart(response~variable,method="anova",data=t
      ,control=rpart.control(minsplit=1,minbucket=2,maxdepth=2))
plot(fit, margin=0.2)
text(fit, cex=.8)

上述代码的输出如下所示:

A463052_1_En_4_Figy_HTML.jpg

从这个图我们可以推导出,当varb或者c中的一个时,那么输出就是 1564。如果不是,则输出 1938。

在 Python 中,可视化决策树的一种方法是使用一组有助于显示的函数的包:Ipython.displaysklearn.externals.sixsklearn.treepydotos

from IPython.display import Image  
from sklearn.externals.six import StringIO  
from sklearn.tree import export_graphviz
import pydot
features = list(data.columns[1:])

import os
os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz2.38/bin/'
dot_data = StringIO()  
export_graphviz(depth_tree, out_file=dot_data,feature_names=features,filled=True,rounded=True)
graph = pydot.graph_from_dot_data(dot_data.getvalue())  
Image(graph[0].create_png())

在前面的代码中,您必须更改数据框名称来代替我们在第一个代码片段中使用的(data.columns[1:])。本质上,我们提供独立变量名作为特性。

在第二段代码中,您必须指定安装graphviz的文件夹位置,并将决策树的名称更改为用户在第四行给出的名称(用您为DecisionTreeRegressorDecisionTreeClassifier创建的变量名替换dtree)。

前面代码片段的输出如图 4-3 所示。

A463052_1_En_4_Fig3_HTML.jpg

图 4-3

The output of the code

离群值对决策树的影响

在前面的章节中,我们已经看到异常值对线性回归有很大的影响。然而,在决策树中,离群值对分类几乎没有影响,因为我们会查看多个可能的规则,并在对感兴趣的变量进行排序后列出最大化信息增益的规则。假设我们按照自变量对数据集进行排序,那么自变量中的异常值就没有影响。

然而,如果数据集中存在异常值,那么在连续变量预测的情况下,因变量中的异常值将是具有挑战性的。这是因为我们使用总体平方误差作为最小化的指标。如果因变量包含异常值,它会导致类似的问题,就像我们在线性回归中看到的那样。

摘要

决策树构建简单,理解直观。当因变量是分类变量时,用于构建决策树的突出方法是信息增益和基尼不纯的组合,当因变量是连续变量时,是总体平方误差的组合。

五、随机森林

在第四章中,我们看了构建决策树的过程。在某些情况下,决策树可能会过度拟合数据,例如,当因变量中存在异常值时。具有相关的独立变量也可能导致选择不正确的变量来分割根节点。

随机森林通过构建多个决策树来克服这些挑战,其中每个决策树处理一个数据样本。让我们来分解一下这个术语:随机指的是从原始数据集中随机抽取数据,而森林指的是构建多个决策树,每个决策树对应一个随机数据样本。(懂了吗?它是多棵树的组合,因此被称为森林。)

在本章中,您将学习以下内容:

  • 随机森林的工作细节
  • 随机森林优于决策树
  • 各种超参数对决策树的影响
  • 如何用 Python 和 R 实现随机森林

随机森林场景

为了说明为什么随机森林是对决策树的改进,我们来看一个场景,在这个场景中,我们尝试对你是否喜欢一部电影进行分类:

  1. 你向一个人寻求建议。
    1. a 问了一些问题来了解你的喜好。
      1. 我们假设只有 20 个详尽的问题。
      2. 我们将添加一个约束,任何人都可以从 20 个问题中随机选择 10 个问题来提问。
      3. 给定 10 个问题,这个人最好以这样的方式排列这些问题,以便他们能够从你那里获取最大的信息。
    2. 根据你的回答,A 提出了一系列电影推荐。
  2. 你向另一个人寻求建议,b。
    1. 和之前一样,B 提问是为了了解你的喜好。
      1. b 也只能从 20 个问题的详尽清单中提出 10 个问题。
      2. 基于 10 个随机选择的问题,B 再次对它们排序,以最大化从你的偏好中获得的信息。
      3. 请注意,A 和 B 之间的问题集可能不同,尽管有一些重叠。
    2. 根据你的回答,B 提出了建议。
  3. 为了达到某种随机性,你可能会跟 A 说《教父》是你看过的最好的电影。但你只是告诉 B 你“非常”喜欢看《教父》
    1. 这样虽然原始信息没有变化,但是两个不同的人学习的方式是不一样的。
  4. 你和你的 n 个朋友做了同样的实验。
    1. 通过前面的内容,您基本上构建了一个决策树集合(这里的集合是指树或森林的组合)。
  5. 最后的推荐会是所有 n 个好友的平均推荐。

制袋材料

Bagging 是引导聚合的缩写。术语 bootstrap 指的是随机选择几行(原始数据集中的一个样本),而 aggregating 指的是从基于数据集样本构建的所有决策树中获取预测的平均值。

这样,预测就不太可能因少数异常情况而出现偏差(因为可能会有一些使用样本数据构建的树,其中样本数据没有任何异常)。Random forest 在构建预测时采用了 bagging 方法。

随机目录林的工作细节

构建随机林的算法如下:

  1. 对原始数据进行子集划分,以便决策树仅基于原始数据集的样本构建。
  2. 在构建决策树时,也要对独立变量(特征)进行子集划分。
  3. 基于子集数据构建决策树,其中行和列的子集用作数据集。
  4. 根据测试或验证数据集进行预测。
  5. 重复步骤 1 到 3 n 次,其中 n 是构建的树的数量。
  6. 测试数据集上的最终预测是所有 n 棵树的预测的平均值。

R 中的以下代码构建了前面的算法(可作为“rf_code”。github 中的 r”)。

t=read.csv("train_sample.csv")

所描述的数据集具有 140 列和 10,000 行。前 8,000 行用于训练模型,其余的用于测试:

train=t[1:8000,]
test=t[8001:9999,]

假设我们使用决策树来构建我们的随机森林,让我们使用rpart包:

library(rpart)

初始化测试数据集中名为prediction的新列:

test$prediction=0

for(i in 1:1000){ # we are running 1000 times - i.e., 1000 decision trees
  y=0.5
  x=sample(1:nrow(t),round(nrow(t)*y))  # Sample 50% of total rows, as y is 0.5
t2=t[x, c(1,sample(139,5)+1)]     # Sample 5 columns randomly, leaving the first column which is the dependent variable
dt=rpart(response~.,data=t2)   # Build a decision tree on the above subset of data (sample rows and columns)
 pred1=(predict(dt,test))  # Predict based on the tree just built
 test$prediction=(test$prediction+pred1)  # Add predictions of all the iterations of previously built decision trees
}
test$prediction = (test$prediction)/1000  # Final prediction of the value is the average of predictions of all the iterations

在 R 中实现随机森林

使用randomForest包可以在 R 中实现随机森林。在下面的代码片段中,我们尝试预测一个人是否会在泰坦尼克号数据集中幸存(代码为“rf_code2”。github 中的 r”)。

为简单起见,我们不处理缺失值,只考虑那些没有缺失值的行:

A463052_1_En_5_Figa_HTML.jpg

在这段代码中,我们构建了一个有 10 棵树的随机森林来提供预测。代码片段的输出如下所示:

A463052_1_En_5_Figb_HTML.jpg

请注意,上面的错误消息指定了两件事:

  • 某些分类自变量中不同值的数量很大。
  • 此外,它假设我们必须指定回归而不是分类。

让我们看看,当分类变量具有大量不同值时,随机森林为什么可能会给出错误。请注意,随机森林是多个决策树的实现。在决策树中,当有更多的不同值时,大多数不同值的频率计数非常低。当频率较低时,独特值的纯度可能较高(或杂质较低)。但是这是不可靠的,因为数据点的数量可能很少(在一个变量的不同值的数量很多的情况下)。因此,当分类变量中不同值的数量很大时,随机森林不会运行。

考虑输出中的警告消息:“响应只有五个或更少的唯一值。确定要做回归吗?”注意,名为Survived的列在类中是数值型的。因此,该算法默认假设这是一个需要执行的回归。

为了避免这种情况,您需要将因变量转换为分类变量或因子变量:

A463052_1_En_5_Figc_HTML.jpg

现在我们可以期待随机森林预测了。

如果我们将输出与决策树进行对比,一个主要缺点是决策树输出可以可视化为一棵树,但随机森林输出不能可视化为一棵树,因为它是多个决策树的组合。理解变量重要性的一种方法是通过观察总杂质减少的多少是由于不同变量的分裂。

变量重要性可以通过使用函数importance在 R 中计算:

A463052_1_En_5_Figd_HTML.jpg

考虑如何计算变量SexMeanDecreaseGini:

A463052_1_En_5_Fige_HTML.jpg

在前面的代码片段中,选择了原始数据集的一个样本。该样本被视为训练数据集,其余的被视为测试数据集。

基于训练数据集构建决策树。预测基于袋外数据,即测试数据集:

A463052_1_En_5_Figf_HTML.jpg

让我们计算前面输出的熵:

A463052_1_En_5_Figg_HTML.png

从表中,我们可以看到总熵从 0.9857 减少到 0.7755。

同样,让我们考虑另一个极端,最不重要的变量:Embarked:

A463052_1_En_5_Figh_HTML.png

从上表中,我们看到熵从 0.9857 减少到只有 0.9326。因此,与变量Sex相比,熵的减少要少得多。这意味着作为一个变量,SexEmbarked更重要。

变量重要性图也可以通过图 5-1 所示的函数得到。

A463052_1_En_5_Fig1_HTML.jpg

图 5-1

Variable importance plot

要在随机林中调整的参数

在刚才讨论的场景中,我们注意到随机森林是基于决策树的,但是基于多个树运行来产生平均预测。因此,我们在随机森林中调整的参数将与用于调整决策树的参数非常相似。

因此,主要参数如下:

  • 树的数量
  • 树的深度

为了查看树的数量对测试数据集 AUC 的影响,我们将浏览一些代码。使用以下代码片段计算 AUC:

A463052_1_En_5_Figi_HTML.jpg

在下面的代码片段中,我们以 10 为步长增加树的数量,并查看 AUC 值如何随着树的数量而变化:

A463052_1_En_5_Figj_HTML.jpg

在前两行中,我们已经初始化了空向量,我们将在不同数量的树的for循环中继续填充这些空向量。初始化之后,我们运行一个循环,其中每一步树的数量增加 10。运行随机森林后,我们计算测试数据集上预测的 AUC 值,并继续追加 AUC 值。

图 5-2 绘制了不同数量树木的最终 AUC 图。

A463052_1_En_5_Fig2_HTML.jpg

图 5-2

AUC over different number of trees

从图 5-2 中我们可以看到,随着树数的增加,测试数据集的 AUC 值总体上是增加的。但是在更多的迭代之后,AUC 可能不会进一步增加。

AUC 随树深的变化

在上一节中,我们注意到 AUC 的最大值出现在树的数量接近 200 时。

在本节中,我们将考虑树的深度对精确度量(AUC)的影响。在第四章中,我们看到节点的大小直接影响树的最大深度。例如,如果最小可能节点大小为高,则深度自动为低,反之亦然。让我们将节点大小作为一个参数来调整,看看 AUC 如何随节点大小而变化:

A463052_1_En_5_Figk_HTML.jpg

请注意,前面的代码与我们为树的数量变化编写的代码非常相似。唯一增加的是参数nodesize

前面代码片段的输出如图 5-3 所示。

A463052_1_En_5_Fig3_HTML.jpg

图 5-3

AUC over different node sizes

在图 5-3 中,请注意,随着节点大小大量增加,测试数据集的 AUC 减少。

用 Python 实现随机森林

随机森林是用 Python 和scikit-learn库实现的。随机森林的实现细节如下所示(github 中的“random forest.ipynb”):

from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier(n_estimators=100,max_depth=5,min_samples_leaf=100,random_state=10)
rfc.fit(X_train, y_train)

预测如下:

rfc_pred=rfc.predict_proba(X_test)

一旦做出预测,AUC 可以如下计算:

from sklearn.metrics import roc_auc_score
roc_auc_score(y_test, rfc_pred[:,1])

摘要

在这一章中,我们看到了随机森林是如何通过采用平均预测方法来改进决策树的。我们还看到了在随机森林中需要调整的主要参数:树的深度和树的数量。从本质上来说,随机森林是一种 bagging (bootstrap aggregating)算法,它结合了多个决策树的输出来进行预测。

六、梯度提升器

到目前为止,我们已经考虑了决策树和随机森林算法。我们看到随机森林是一种 bagging (bootstrap aggregating)算法,它结合了多个决策树的输出来进行预测。通常,在 bagging 算法中,并行生长树以获得所有树的平均预测,其中每棵树都建立在原始数据的样本上。

另一方面,梯度提升使用不同的格式进行预测。boosting 采用顺序方法来获得预测,而不是并行化树构建过程。在梯度提升中,每个决策树预测前一个决策树的误差,从而提升(改善)误差(梯度)。

在本章中,您将学习以下内容:

  • 梯度提升的工作细节
  • 梯度提升与随机森林有何不同
  • AdaBoost 的工作细节
  • 各种超参数对增压的影响
  • 如何在 R 和 Python 中实现梯度提升

梯度提升器

梯度是指建立模型后获得的误差或残差。助推指的是提高。这项技术被称为梯度提升器,简称 GBM。梯度提升是一种逐渐改善(减少)误差的方法。

为了了解 GBM 是如何工作的,让我们从一个简单的例子开始。假设给你一个 M 模型(基于决策树)来改进。假设当前模型准确率为 80%。我们希望在这方面有所改进。

我们将模型表述如下:

Y = M(x) + error

y 是因变量,M(x)是使用 x 个自变量的决策树。

现在我们将预测上一个决策树的错误:

error = G(x) + error2

G(x)是另一个决策树,它试图使用 x 个独立变量来预测误差。

在下一步中,与上一步类似,我们构建一个模型,尝试使用 x 个独立变量来预测error2:

error2 = H(x) + error3

现在我们将所有这些结合在一起:

Y = M(x) + G(x) + H(x) + error3

前面的等式可能具有大于 80%的准确度,因为单独的模型 M(单个决策树)具有 80%的准确度,而在上面的等式中,我们考虑 3 个决策树。

下一节将探讨 GBM 如何工作的细节。在后面的部分中,我们将了解 AdaBoost(自适应增强)算法是如何工作的。

GBM 的工作细节

以下是梯度提升的算法:

  1. 用简单的决策树初始化预测。
  2. 计算残差,即(实际预测)值。
  3. 构建另一个基于所有独立值预测残差的浅层决策树。
  4. 用新预测乘以学习率来更新原始预测。
  5. 重复第 2 步到第 4 步一定次数的迭代(迭代次数就是树的数量)。

实现上述算法的代码如下(github 中的代码和数据集为“GBM working details.ipynb”):

import pandas as pd
# importing dataset
data=pd.read_csv('F:/course/Logistic regression/credit_training.csv')
# removing irrelevant variables
data=data.drop(['Unnamed: 0'],axis=1)
# replacing null values
data['MonthlyIncome']=data['MonthlyIncome'].fillna(value=data['MonthlyIncome'].median())
data['NumberOfDependents']=data['NumberOfDependents'].fillna(value=data['NumberOfDependents'].median())
from sklearn.model_selection import train_test_split
# creating independent variables
X = data.drop('SeriousDlqin2yrs',axis=1)
# creating dependent variables
y = data['SeriousDlqin2yrs']
# creating train and test datasets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=42)

在前面的代码中,我们将数据集分为 70%的训练数据集和 30%的测试数据集。

# Build a decision tree
from sklearn.tree import DecisionTreeClassifier
depth_tree = DecisionTreeClassifier(criterion = "gini",max_depth=4, min_samples_leaf=10)
depth_tree.fit(X_train, y_train)

在前面的代码中,我们在原始数据上构建了一个简单的决策树,将SeriousDlqin2yrs作为因变量,其余变量作为自变量。

#Get the predictions on top of train dataset itself
dt_pred = depth_tree.predict_proba(X_train)
X_train['prediction']=dt_pred[:,1]

在前面的代码中,我们预测了第一个决策树的输出。这将有助于我们得出残差。

#Get the predictions on top of test dataset
X_test['prediction']=depth_tree.predict_proba(X_test)[:,1]

在前面的代码中,虽然我们计算了测试数据集中的输出概率,但请注意,我们不能计算残差,因为实际上,我们不允许查看测试数据集的因变量。作为前面代码的延续,我们将在下面的代码中构建 20 个残差决策树:

from sklearn.tree import DecisionTreeRegressor
import numpy as np
from sklearn.metrics import roc_auc_score
depth_tree2 = DecisionTreeRegressor(criterion = "mse",max_depth=4, min_samples_leaf=10)
for i in range(20):
    # Calculate residual
    train_errorn=y_train-X_train['prediction']
    # remove prediction variable that got appended to independent variable earlier
    X_train2=X_train.drop(['prediction'],axis=1)
    X_test2=X_test.drop(['prediction'],axis=1)

在前面的代码中,注意我们正在计算第 n 棵决策树的残差。我们将从X_train2数据集中删除prediction列,因为prediction列不能是在for循环的下一次迭代中构建的后续模型中的独立变量之一。

    # Build a decision tree to predict the residuals using independent variables
    dt2=depth_tree2.fit(X_train2, train_errorn)
    # predict the residual
    dt_pred_train_errorn = dt2.predict(X_train2)

在前面的代码中,我们正在拟合决策树,其中因变量是残差,自变量是数据集的原始自变量。

一旦决策树被拟合,下一步就是预测残差(它是因变量):

    # update the predictions based on predicted residuals
    X_train['prediction']=(X_train['prediction']+dt_pred_train_errorn*1)
    # Calculate AUC
    train_auc=roc_auc_score(y_train,X_train['prediction'])
    print("AUC on training data set is: "+str(train_auc))

在这段代码中,原始预测(存储在X_train数据集中)用我们在上一步中获得的预测残差进行了更新。

注意,我们是通过残差的预测(dt_pred_train_errorn)来更新我们的预测。我们在前面的代码中明确给出了一个*1,因为收缩率或学习率的概念将在下一节解释(?? 将被替换为*learning_rate)。

一旦预测被更新,我们计算训练数据集的 AUC:

    # update the predictions based on predicted residuals for test dataset
    dt_pred_test_errorn = dt2.predict(X_test2)
    X_test['prediction']=(X_test['prediction']+dt_pred_test_errorn)
    # Calculate AUC
    test_auc=roc_auc_score(y_test,X_test['prediction'])
    print("AUC on test data set is: "+str(test_auc))

在这里,我们更新了测试数据集上的预测。我们不知道测试数据集的残差,但是我们基于为预测训练数据集的残差而构建的决策树来更新对测试数据集的预测。理想情况下,如果测试数据集没有残差,预测的残差应该接近 0,如果测试数据集的原始决策树有一些残差,那么预测的残差将远离 0。

一旦测试数据集的预测被更新,我们打印出测试数据集的 AUC。

让我们看看前面代码的输出:

A463052_1_En_6_Figb_HTML.jpg

A463052_1_En_6_Figa_HTML.jpg

注意,训练数据集的 AUC 随着更多的树而持续增加。但是测试数据集的 AUC 在某次迭代后降低。

收缩

GBM 是基于决策树的。因此,就像随机森林算法一样,GBM 的准确性取决于所考虑的树的深度、建立的树的数量和终端节点中的最小观察数量。收缩率是 GBM 中的一个附加参数。让我们看看,如果我们改变学习率/收缩,训练和测试数据集 AUC 的输出会发生什么。我们将学习率初始化为 0.05,并运行更多的树:

from sklearn.model_selection import train_test_split
# creating independent variables
X = data.drop('SeriousDlqin2yrs',axis=1)
# creating dependent variables
y = data['SeriousDlqin2yrs']
# creating train and test datasets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=42)

from sklearn.tree import DecisionTreeClassifier
depth_tree = DecisionTreeClassifier(criterion = "gini",max_depth=4, min_samples_leaf=10)
depth_tree.fit(X_train, y_train)

#Get the predictions on top of train and test datasets
dt_pred = depth_tree.predict_proba(X_train)
X_train['prediction']=dt_pred[:,1]
X_test['prediction']=depth_tree.predict_proba(X_test)[:,1]
from sklearn.tree import DecisionTreeRegressor
import numpy as np
from sklearn.metrics import roc_auc_score
depth_tree2 = DecisionTreeRegressor(criterion = "mse",max_depth=4, min_samples_leaf=10)
learning_rate = 0.05
for i in range(20):
    # Calculate residual
    train_errorn=y_train-X_train['prediction']
    # remove prediction variable that got appended to independent variable earlier
    X_train2=X_train.drop(['prediction'],axis=1)
    X_test2=X_test.drop(['prediction'],axis=1)
    # Build a decision tree to predict the residuals using independent variables
    dt2=depth_tree2.fit(X_train2, train_errorn)
    # predict the residual
    dt_pred_train_errorn = dt2.predict(X_train2)
    # update the predictions based on predicted residuals
    X_train['prediction']=(X_train['prediction']+dt_pred_train_errorn*learning_rate)
    # Calculate AUC
    train_auc=roc_auc_score(y_train,X_train['prediction'])
    print("AUC on training data set is: "+str(train_auc))
    # update the predictions based on predicted residuals for test dataset
    dt_pred_test_errorn = dt2.predict(X_test2)
    X_test['prediction']=(X_test['prediction']+dt_pred_test_errorn*learning_rate)
    # Calculate AUC
    test_auc=roc_auc_score(y_test,X_test['prediction'])
    print("AUC on test data set is: "+str(test_auc))

上述代码的输出是:

下面是前几棵树的输出:

A463052_1_En_6_Figc_HTML.jpg

这是最后几棵树的产量:

A463052_1_En_6_Figd_HTML.jpg

与前面的情况不同,其中learning_rate = 1,较低的learning_rate导致测试数据集 AUC 随着训练数据集 AUC 一致增加。

adaboost 算法

在讨论其他增强方法之前,我想和我们在前面章节中看到的做一个比较。在计算逻辑回归的误差度量时,我们可以使用传统的平方误差。但是我们转向了熵误差,因为它对大量的误差惩罚更大。

以类似的方式,残差计算可以根据因变量的类型而变化。对于连续的因变量,残差计算可以是高斯型的(因变量的(实际-预测)),而对于离散变量,残差计算可以不同。

AdaBoost 理论

AdaBoost 是 adaptive boosting 的缩写。下面是高级算法:

  1. 仅使用几个独立变量构建一个弱学习器(在这种情况下是决策树技术)。
    1. 注意,在构建第一个弱学习者时,与每个观察相关联的权重是相同的。
  2. 识别基于弱学习者的不正确分类的观察。
  3. 以这样的方式更新观察值的权重,使得先前弱学习器中的错误分类被给予更大的权重,而先前弱学习器中的正确分类被给予更小的权重。
  4. 根据预测的准确性为每个弱学习者分配一个权重。
  5. 最终预测将基于多个弱学习者的加权平均预测。

自适应潜在地指的是根据先前的分类是正确的还是不正确的来更新观察值的权重。提升可能指的是给每个弱学习者分配权重。

AdaBoost 的工作细节

让我们看一个 AdaBoost 的例子:

  1. Build a weak learner. Let’s say the dataset is the first two columns in the following table (available as “adaboost.xlsx” in github):

    A463052_1_En_6_Fige_HTML.jpg

    Once we have the dataset, we build a weak learner (decision tree) according to the steps laid out to the right in the preceding table. From the table we can see that X <= 4 is the optimal splitting criterion for this first decision tree.

  2. Calculate the error metric (the reason for having “Changed Y” and “Yhat” as new columns in the below table will be explained after step 4):

    A463052_1_En_6_Figf_HTML.jpg

    The formulae used to obtain the preceding table are as follows:

    A463052_1_En_6_Figg_HTML.jpg

  3. 计算应该与第一个弱学习者关联的权重:0.5×log((1-误差)/误差)= 0.5×log(0.9/0.1)= 0.5×log(9)= 0.477

  4. Update the weights associated with each observation in such a way that the previous weak learner’s misclassifications have high weight and the correct classifications have low weight (essentially, we are tuning the weights associated with each observation in such a way that, in the new iteration, we try and make sure that the misclassifications are predicted more accurately):

    A463052_1_En_6_Figh_HTML.jpg

    Note that the updated weights are calculated by the following formula:

     $$ {original\kern0.34em weight}^{\ast}\kern0.125em {e}^{\left(- weightage\ oflearner\ast yhat\ast changedy\right)} $$

    That formula should explain the need for changing the discrete values of y from {0,1} to {–1,1}. By changing 0 to –1, we are in a position to perform the multiplication better. Also note that in the preceding formula, weightage associated with the learner in general would more often than not be positive. When yhat and changed_y are the same, the exponential part of formula would be a lower number (as the – weightage of learner × yhat × changed_y part of the formula would be negative, and an exponential of a negative is a small number). When yhat and changed_y are different values, that’s when the exponential would be a bigger number, and hence the updated weight would be more than the original weight.

  5. We observe that the updated weights we obtained earlier do not sum up to 1. We update each weight in such a way that the sum of weights of all observations is equal to 1. Note that, the moment weights are introduced, we can consider this as a regression exercise. Now that the weight for each observation is updated, we repeat the preceding steps until the weight of the misclassified observations increases so much that it is now correctly classified:

    A463052_1_En_6_Figi_HTML.jpg

    Note that the weights in the third column in the preceding table are updated based on the formula we derived earlier post normalization (ensuring that the sum of weights is 1). You should be able to see that the weight associated with misclassification (the eighth observation with the independent variable value of 8) is more than any other observation. Note that although everything is similar to a typical decision tree till the prediction columns, error calculation gives emphasis to weights of observations. error in the left node is the summation of the weight of each observation that was misclassified in the left node and similarly for the right node. overall error is the summation of error across both nodes (error in left node + error in right node). In this instance, overall error is still the least at the fourth observation. The updated weights based on the previous step are as follows:

    A463052_1_En_6_Figj_HTML.jpg

    Continue the process one more time:

    A463052_1_En_6_Figk_HTML.jpg

    From the preceding table, note that overall error in this iteration is minimum at X <= 8, as the weight associated with the eighth observation is a lot more than other observations, and hence overall error came up as the least at the eighth observation this time. However, note that the weightage associated with the preceding tree would be low, because the accuracy of that tree is low when compared to the previous two trees.

  6. 一旦做出所有预测,观察的最终预测被计算为与每个弱学习者相关联的权重的总和乘以每个观察的概率输出。

GBM 的附加功能

在上一节中,我们看到了如何手工构建 GBM。在本节中,我们将了解可以内置的其他参数:

  • 行抽样:在随机森林中,我们看到对随机选择的行进行抽样会产生一个更通用、更好的模型。在 GBM 中,我们也可以对行进行采样,以进一步提高模型性能。
  • 列采样:与行采样类似,通过对每个决策树的列进行采样,可以避免一定程度的过度拟合。

随机森林和 GBM 技术都是基于决策树的。然而,随机森林可以被认为是并行构建多棵树,最终我们将所有多棵树的平均值作为最终预测。在 GBM 中,我们构建多棵树,但是是按顺序的,其中每棵树都试图预测其前一棵树的残差。

用 Python 实现 GBM

GBM 可以使用如下的scikit-learn库在 Python 中实现(代码在 github 中以“GBM.ipynb”的形式提供):

from sklearn import ensemble
gb_tree = ensemble.GradientBoostingClassifier(loss='deviance',learning_rate=0.05,n_estimators=100,min_samples_leaf=10,max_depth=13,max_features=2,subsample=0.7,random_state=10)
gb_tree.fit(X_train, y_train)

注意,关键输入参数是损失函数(无论是正常残差方法还是基于 AdaBoost 的方法)、学习速率、树的数量、每棵树的深度、列采样和行采样。

一旦建立了 GBM,就可以进行如下预测:

from sklearn.metrics import roc_auc_score
gb_pred=gb_tree.predict_proba(X_test)
roc_auc_score(y_test, gb_pred[:,1])

在 R 中实现 GBM

R 中的 GBM 和 Python 中的 GBM 有相似的参数。GBM 可以按如下方式实现:

A463052_1_En_6_Figl_HTML.jpg

在该公式中,我们以下列方式指定因变量和自变量:dependent_variable ~要使用的自变量集合。

该分布指定它是高斯、伯努利还是 AdaBoost 算法。

library(gbm)
gb=gbm(SeriousDlqin2yrs~.,data=train,n.trees=10,interaction.depth=5,shrinkage=0.05)

  • n. trees指定要建造的树的数量。
  • interaction.depth是树木的max_depth
  • n.minobsinnode是一个节点中的最小观察次数。
  • shrinkage是学习率。
  • bag.fraction是随机选择的训练集观察值的一部分,用于提出扩展中的下一棵树。
  • R 中的 GBM 算法运行如下:

可以做出如下预测:

pred=predict(gb,test,n.trees=10,type="response")

摘要

在本章中,您学习了以下内容:

  • GBM 是一种基于决策树的算法,它试图预测给定决策树中前一个决策树的残差。
  • 收缩和深度是 GBM 中需要调整的一些更重要的参数。
  • 梯度提升和自适应增强的区别。
  • 调整学习率参数如何提高 GBM 中的预测精度。

七、人工神经网络

人工神经网络是一种监督学习算法,它利用多个超参数的混合来帮助逼近输入和输出之间的复杂关系。人工神经网络中的一些超参数包括:

  • 隐藏层数
  • 隐藏单元的数量
  • 激活功能
  • 学习率

在本章中,您将学习以下内容:

  • 神经网络的工作细节
  • 各种超参数对神经网络的影响
  • 前馈和反向传播
  • 学习率对权重更新的影响
  • 避免神经网络中过拟合的方法
  • 如何在 Excel、Python 和 R 中实现神经网络

神经网络源于这样一个事实,即并非所有东西都可以用线性/逻辑回归来近似,数据中可能存在只能用复杂函数近似的潜在复杂形状。函数越复杂(以某种方式处理过拟合),预测的准确性就越好。我们将从研究神经网络如何将数据拟合到模型中开始。

神经网络的结构

神经网络的典型结构如图 7-1 所示。

A463052_1_En_7_Fig1_HTML.jpg

图 7-1

Neural network structure

图中的输入级别/层通常是用于预测输出(因变量)级别/层的独立变量。通常,在回归问题中,输出层只有一个节点,而在分类问题中,输出层包含的节点数量与因变量中存在的类(不同值)的数量一样多。隐藏级别/层用于将输入变量转换为高阶函数。隐藏层转换输出的方式如图 7-2 所示。

A463052_1_En_7_Fig2_HTML.jpg

图 7-2

Transforming the output

在图 7.2 中,x1 和 x2 是独立变量,b0 是偏差项(类似于线性/逻辑回归中的偏差)。w1 和 w2 是赋予每个输入变量的权重。若 a 是隐藏层中的一个单元/神经元,则等于:

$$ a=f\left(\sum \limits_{i=0}^N{w}_i{x}_i\right) $$

前面等式中的函数是我们在求和之上应用的激活函数,以便我们获得非线性(我们需要非线性,以便我们的模型现在可以学习复杂的模式)。不同的激活功能将在后面的章节中详细讨论。

此外,具有一个以上的隐藏层有助于实现高度非线性。我们希望实现高度非线性,因为没有它,神经网络将是一个巨大的线性函数。

当神经网络必须理解非常复杂、有上下文关系或不明显的东西(如图像识别)时,隐藏层是必要的。深度学习这个术语来源于拥有许多隐藏层。这些图层被称为隐藏图层,因为它们在网络输出中不可见。

训练神经网络的工作细节

训练一个神经网络基本上意味着通过重复两个关键步骤来校准所有的权重:前向传播和反向传播。

在前向传播中,我们将一组权重应用于输入数据并计算输出。对于第一次正向传播,随机初始化该组权重值。

在反向传播中,我们测量输出的误差幅度,并相应地调整权重以减小误差。

神经网络重复前向和反向传播,直到权重被校准以准确预测输出。

正向传播

让我们通过一个训练神经网络作为异或(XOR)运算的简单例子来说明训练过程中的每个步骤。XOR 函数可以通过输入和输出的映射来表示,如下表所示,我们将使用它作为训练数据。给定 XOR 函数可接受的任何输入,它应该提供正确的输出。

| 投入 | 输出 | | :-- | :-- | | (0,0) | Zero | | (0,1) | one | | (1,0) | one | | (1,1) | Zero |

让我们使用上表的最后一行,(1,1) => 0 来演示正向传播,如图 7-3 所示。请注意,虽然这是一个分类问题,但我们仍然将它视为回归问题,只是为了理解向前和向后传播是如何工作的。

A463052_1_En_7_Fig3_HTML.jpg

图 7-3

Applying a neural network

我们现在给所有的突触分配权重。请注意,这些权重是随机选择的(最常见的方式是基于高斯分布),因为这是我们第一次正向传播。初始权重随机分配在 0 和 1 之间(但注意最终权重不需要在 0 和 1 之间),如图 7-4 所示。

A463052_1_En_7_Fig4_HTML.jpg

图 7-4

Weights on the synapses

我们将输入与其对应的一组权重的乘积相加,得出隐藏层的第一个值(图 7-5 )。您可以将权重视为输入节点对输出的影响度量:

A463052_1_En_7_Fig5_HTML.jpg

图 7-5

Values for the hidden layer

  • 1 × 0.8 + 1 × 0.2 = 1
  • 1 × 0.4 + 1 × 0.9 = 1.3
  • 1 × 0.3 + 1 × 0.5 = 0.8

应用激活功能

激活函数应用于神经网络的隐藏层。激活功能的目的是将输入信号转换成输出信号。它们对于神经网络建模复杂的非线性模式是必要的,而简单的模型可能会遗漏这些模式。

一些主要的激活函数如下:

$$ \mathrm{Sigmoid}=1/\left(1+{e}^{-x}\right) $$

$$ Tanh=\frac{ex-{e}{-x}}{ex+{e}{-x}} $$

整流线性单位= x 如果 x > 0,否则 0

对于我们的例子,让我们使用 sigmoid 函数来激活。并将 Sigmoid(x)应用于三个隐藏层和,我们得到图 7-6 :

A463052_1_En_7_Fig6_HTML.jpg

图 7-6

Applying sigmoid to the hidden layer sums

  • Sigmoid(1.0) = 0.731
  • Sigmoid(1.3) = 0.785
  • Sigmoid(0.8) = 0.689

然后,我们将隐藏层结果与第二组权重(第一次也是随机确定的)的乘积求和,以确定输出和:

  • 0.73 × 0.3 + 0.79 × 0.5 + 0.69 × 0.9 = 1.235

A463052_1_En_7_Fig7_HTML.jpg

图 7-7

Applying the activation function

因为我们使用了一组随机的初始权重,所以输出神经元的值偏离了目标值—在本例中,偏离了 1.235(因为目标值是 0)。

在 excel 中,上述内容如下所示(在 github 中,Excel 的名称为“NN.xlsx”):

A463052_1_En_7_Figa_HTML.jpg

  1. 输入层有两个输入(1,1),因此输入层的维数为 1 × 2(因为每个输入有两个不同的值)。
  2. 1 × 2 隐藏层乘以 2 × 3 维的随机初始化矩阵。
  3. 隐藏层的输入输出是一个 1 × 3 矩阵:

上述输出的公式如下:

A463052_1_En_7_Figc_HTML.jpg

A463052_1_En_7_Figb_HTML.jpg

激活函数的输出乘以 3 × 1 维随机初始化矩阵,得到 1 × 1 维的输出:

A463052_1_En_7_Figd_HTML.jpg

获得前面输出的方法是按照下面的公式:

A463052_1_En_7_Fige_HTML.jpg

同样,虽然这是一个分类练习,其中我们使用交叉熵误差作为损失函数,但我们仍将使用平方误差损失函数,只是为了使反向传播计算更容易理解。我们将在后面的章节中了解分类在神经网络中是如何工作的。

一旦我们有了输出,我们计算平方误差(总误差),即(1.233-0) 2 ,如下所示:

A463052_1_En_7_Figf_HTML.jpg

从输入层获得平方误差所涉及的各个步骤共同形成了前向传播。

反向传播

在前向传播中,我们从输入到隐藏到输出迈出了一步。在反向传播中,我们采用相反的方法:本质上,从最后一层开始少量改变每个权重,直到达到最小可能误差。当重量改变时,总误差要么减小,要么增大。根据误差是增加还是减少,决定权重更新的方向。此外,在一些情况下,对于重量的小变化,误差增加/减少相当多,并且在一些情况下,误差仅改变很小的量。

总而言之,通过少量更新权重并测量误差变化,我们能够做到以下几点:

  1. 决定权重需要更新的方向
  2. 决定权重需要更新的幅度

在继续在 Excel 中实现反向传播之前,让我们看看神经网络的另一个方面:学习速率。学习率有助于我们建立对权重更新决策的信任。例如,在决定权重更新的幅度时,我们可能不会一次改变所有的东西,而是采取更谨慎的方法来更慢地更新权重。这导致在我们的模型中获得稳定性。后面的部分将讨论学习速度如何有助于稳定性。

计算反向传播

为了了解反向传播是如何工作的,让我们来看看上一节中更新随机初始化的权重值。

具有随机初始化的权重值的网络的总误差是 1.52。让我们将隐藏层与输出层之间的权重从 0.3 更改为 0.29,并查看对整体误差的影响:

A463052_1_En_7_Figg_HTML.jpg

请注意,随着重量的小幅下降,总误差从 1.52 降至 1.50。因此,根据前面提到的两点,我们得出结论,0.3 需要降低到一个更低的数字。在决定了权重需要更新的方向之后,我们需要回答的问题是:“权重更新的幅度是多少?”

如果通过少量(0.01)改变权重,误差减少了很多,那么潜在地,权重可以更新更大的量。但是如果当权重被少量更新时,误差仅减少少量,那么权重需要被缓慢地更新。因此,隐藏层与输出层之间值为 0.3 的权重更新如下:

  • 0.3–0.05×(因重量变化而减少误差)

0.05 是学习参数,由用户输入——下一节将详细介绍学习率。因此,权重值更新为 0.3–0.05×((1.52-1.50)/0.01)= 0.21。

类似地,其他权重更新为 0.403 和 0.815:

A463052_1_En_7_Figh_HTML.jpg

注意,通过改变连接隐藏层和输出层的权重,整体误差减少了很多。

既然我们已经更新了一个层中的权重,我们将更新网络早期部分中存在的权重,即输入层和隐藏层激活之间的权重。让我们改变重量值并计算误差的变化:

| 原重 | 更新重量 | 误差减少 | | :-- | :-- | :-- | | Zero point eight | 0.7957 | 0.0009 | | Zero point four | 0.3930 | 0.0014 | | Zero point three | 0.2820 | 0.0036 | | Zero point two | 0.1957 | 0.0009 | | Zero point nine | 0.8930 | 0.0014 | | Zero point five | 0.4820 | 0.0036 |

假设每次当权重减小一个小值时,误差都在减小,我们将把所有权重减小到上面计算的值。

现在权重已更新,请注意总误差从 1.52 降至 1.05。我们不断重复向前和向后传播,直到总误差尽可能最小。

随机梯度下降

梯度下降是在刚刚讨论的场景中误差最小化的方式。梯度代表差异(实际和预测之间的差异),下降意味着减少。random 代表训练数据的子集,用于计算误差,从而更新权重(在后面的部分中会详细介绍数据子集)。

深入梯度下降

为了加深我们对梯度下降神经网络的理解,让我们从一个已知的函数开始,看看如何导出权重:现在,我们将已知的函数设为 y = 6 + 5x。

数据集如下所示(在 github 中以“gradient descent batch size.xlsx”的形式提供):

| x | y | | :-- | :-- | | one | Eleven | | Two | Sixteen | | three | Twenty-one | | four | Twenty-six | | five | Thirty-one | | six | Thirty-six | | seven | Forty-one | | eight | Forty-six | | nine | Fifty-one | | Ten | fifty-six |

让我们将参数 a 和 b 随机初始化为值 2 和 3(其理想值为 5 和 6)。权重更新的计算如下:

A463052_1_En_7_Figx_HTML.png

注意,我们是从用 2 和 3(第 1 行,第 3 列和第 4 列)随机初始化a_estimateb_estimate估计值开始的。

我们计算如下:

  • 使用 a 和 b 的随机初始化值计算 y 的估计值:5。
  • 计算对应于 a 和 b 的值的平方误差(第 1 行中的 36)。
  • 稍微改变 a 的值(增加 0.01),并计算与改变后的 a 值相对应的平方误差。这被存储为error_change_a列。
  • 计算delta_error_a中的误差变化(误差变化/ 0.01)。注意,如果我们对 a 的损失函数进行微分,δ将非常相似。
  • 根据:new_a = a_estimate + ( delta_error_a ) × learning_rate更新 a 的值。

在本分析中,我们认为学习率为 0.01。对 b 的更新估计值进行相同的分析。下面是与刚才描述的计算相对应的公式:

A463052_1_En_7_Figk_HTML.jpg

A463052_1_En_7_Figj_HTML.jpg

一旦更新了 a 和 b 的值(new_a 和 new_b 在第一行中计算),对第 2 行执行相同的分析(注意,我们从第 2 行开始,使用从上一行获得的 a 和 b 的更新值。)我们不断更新 a 和 b 的值,直到覆盖了所有的数据点。最后,a 和 b 的更新值分别为 2.75 和 5.3。

既然我们已经运行了整个数据集,我们将用 2.75 和 5.3 重复整个过程,如下所示:

A463052_1_En_7_Figl_HTML.png

a 和 b 的值从 2.75 和 5.3 开始,到 2.87 和 5.29 结束,比上一次迭代精确一点。随着更多的迭代,a 和 b 的值将收敛到最优值。

我们已经研究了基本梯度下降的工作细节,但是其他优化器也执行类似的功能。其中一些如下:

  • RMSprop
  • 阿达格拉德
  • 阿达德尔塔
  • 圣经》和《古兰经》传统中)亚当(人类第一人的名字
  • 阿达玛斯
  • 那达慕

为什么要有学习率?

在刚才讨论的场景中,由于学习率为 0.01,我们将权重从 2.3 移动到 2.75 和 5.3。让我们看看学习率为 0.05 时权重会如何变化:

A463052_1_En_7_Figm_HTML.png

请注意,在学习率从 0.01 变为 0.05 的时刻,在这种特定情况下,a 和 b 的值在后面的数据点上开始出现异常变化。因此,较低的学习率总是首选。但是,请注意,较低的学习速率会导致获得最佳结果的时间较长(迭代次数较多)。

批量训练

到目前为止,我们已经看到 a 和 b 的值在数据集的每一行都得到更新。然而,这可能不是一个好主意,因为变量值会显著影响 a 和 b 的值。因此,误差计算通常是对一批数据进行的,如下所示。假设批量大小为 2(在前面的例子中,批量大小为 1):

| x | y | 估计值 | b _ 估计 | y _ 估计值 | 平方误差 | 错误 _ 改变 _a | 增量 _ 误差 _a | 新 _a | 错误 _ 更改 _b | 增量 _ 误差 _b | 新 _b | | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | | one | Eleven | Two | three | five | Thirty-six | 35.8801 | Eleven point nine nine |   | 35.8801 | Eleven point nine nine |   | | Two | Sixteen | Two | three | eight | Sixty-four | 63.8401 | Fifteen point nine nine |   | 63.6804 | Thirty-one point nine six |   | |   |   |   |   | 全部的 | One hundred | 99.7202 | Twenty-seven point nine eight | 2.2798 | 99.5605 | Forty-three point nine five | 3.4395 |

现在对于下一批,a 和 b 的更新值是 2.28 和 3.44:

| x | y | 估计值 | b _ 估计 | y _ 估计值 | 平方误差 | 错误 _ 改变 _a | 增量 _ 误差 _a | 新 _a | 错误 _ 更改 _b | 增量 _ 误差 _b | 新 _b | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | three | Twenty-one | Two point two eight | Three point four four | Twelve point six | Seventy point five nine | Seventy point four two | Sixteen point seven nine |   | Seventy point zero nine | Fifty point three two |   | | four | Twenty-six | Two point two eight | Three point four four | Sixteen point zero four | Ninety-nine point two five | Ninety-nine point zero five | Nineteen point nine one |   | Ninety-eight point four five | Seventy-nine point five four |   | |   |   |   |   | 全部的 | One hundred and sixty-nine point eight three | One hundred and sixty-nine point four seven | Thirty-six point seven one | Two point six five | One hundred and sixty-eight point five four | One hundred and twenty-nine point eight six | Four point seven four |

a 和 b 的更新值现在是 2.65 和 4.74,并且迭代继续。请注意,在实践中,批量大小至少为 32。

Softmax 的概念

到目前为止,在 Excel 实现中,我们已经执行了回归,而不是分类。当我们执行分类时要注意的关键区别是输出被限制在 0 和 1 之间。在二进制分类的情况下,输出图层将有两个结点,而不是一个。一个节点对应于输出 0,另一个对应于输出 1。

现在,我们来看看当输出层有两个节点时,我们的计算如何根据上一节的讨论而变化(其中输入为 1,1,预期输出为 0)。假设输出为 0,我们将对输出进行如下单热编码:[1,0],其中第一个索引值对应于输出 0,第二个索引值对应于输出 1。

连接隐藏层和输出层的权重矩阵更改如下:它不是 3 × 1 矩阵,而是 3 × 2 矩阵,因为隐藏层现在连接到两个输出节点(与回归练习不同,它连接到 1 个节点):

A463052_1_En_7_Fign_HTML.jpg

请注意,因为输出节点是 to,所以我们的输出层也包含两个值,如下所示:

A463052_1_En_7_Figo_HTML.jpg

上述输出的一个问题是它的值大于 1(在其他情况下,这些值也可能小于 0)。

在输出超出 0 到 1 之间的期望值的情况下,Softmax 激活就派上了用场。上述输出的 Softmax 计算如下:

在下面的 Softmax 步骤 1 中,输出被提升到其指数值。请注意,3.43 是 1.233 的指数:

A463052_1_En_7_Figp_HTML.jpg

在下面的 Softmax 步骤 2 中,softmax 输出被归一化以获得概率,使得两个输出的概率之和为 1:

A463052_1_En_7_Figq_HTML.jpg

注意 0.65 的值是由 3.43 / (3.43 + 1.81)得到的。

现在我们有了概率值,而不是计算总平方误差,我们计算交叉熵误差,如下所示:

  1. The final softmax step is compared with actual output:

    A463052_1_En_7_Figr_HTML.jpg

  2. The cross entropy error is calculated based on the actual values and the predicted values (which are obtained from softmax step 2):

    A463052_1_En_7_Figs_HTML.jpg

请注意公式面板中的交叉熵误差公式。

现在我们有了最终的误差度量,我们再次部署梯度下降来最小化总交叉熵误差。

不同的损失优化函数

可以针对不同的度量进行优化,例如,回归中的平方误差和分类中的交叉熵误差。可以优化的其他损失函数包括:

  • 均方误差
  • 平均绝对百分比误差
  • 均方对数误差
  • 方形铰链
  • 关键
  • 分类铰链
  • 对数曲线
  • 范畴交叉熵
  • 稀疏分类交叉熵
  • 二元交叉熵
  • 库尔贝克莱布勒散度
  • 泊松
  • 余弦接近度

缩放数据集

通常,当我们缩放输入数据集时,神经网络表现良好。在这一节中,我们将了解缩放的原因。为了了解缩放对输出的影响,我们将对比两个场景。

不缩放输入的场景

A463052_1_En_7_Figt_HTML.jpg

在上表中,计算了各种方案,其中输入总是相同的 255,但是在每个方案中乘以输入的权重是不同的。请注意,即使重量变化很大,sigmoid 输出也不会改变。那是因为权重乘以一个大数,其输出也是一个大数。

输入缩放的场景

在这种情况下,我们将不同的权重值乘以一个小的输入数,如下所示:

A463052_1_En_7_Figu_HTML.jpg

现在权重乘以一个较小的数,对于不同的权重值,sigmoid 输出会有很大的不同。

由于权重需要缓慢调整以达到最佳权重值,因此独立变量数量较大的问题非常严重。假设权重调整缓慢(按照梯度下降中的学习速率),当输入是高数量级时,可能需要相当长的时间来达到最佳权重。因此,要获得最佳权重值,最好首先缩放数据集,这样我们就可以将输入作为一个小数字。

用 Python 实现神经网络

在 Python 中实现神经网络有几种方法。在这里,我们将看看使用keras框架实现神经网络。你必须安装tensorflow / theano,和keras才能实现神经网络。

A463052_1_En_7_Figw_HTML.jpg

  1. Download the dataset and extract the train and test dataset (code available as “NN.ipynb” in github)

    A463052_1_En_7_Fig8_HTML.jpg

    图 7-8

    The output

    from keras.datasets import mnist
    import matplotlib.pyplot as plt
    %matplotlib inline
    # load (downloaded if needed) the MNIST dataset
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    # plot 4 images as gray scale
    plt.subplot(221)
    plt.imshow(X_train[0], cmap=plt.get_cmap('gray'))
    plt.subplot(222)
    plt.imshow(X_train[1], cmap=plt.get_cmap('gray'))
    plt.subplot(223)
    plt.imshow(X_train[2], cmap=plt.get_cmap('gray'))
    plt.subplot(224)
    plt.imshow(X_train[3], cmap=plt.get_cmap('gray'))
    # show the plot
    plt.show()
    
    
  2. 导入相关包:

    import numpy as np
    from keras.datasets import mnist
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from keras.utils import np_utils
    
    
  3. 预处理数据集:

    num_pixels = X_train.shape[1] * X_train.shape[2]
    # reshape the inputs so that they can be passed to the vanilla NN
    X_train = X_train.reshape(X_train.shape[0],num_pixels ).astype('float32')
    X_test = X_test.reshape(X_test.shape[0],num_pixels).astype('float32')
    # scale inputs
    X_train = X_train / 255
    X_test = X_test / 255
    # one hot encode the output
    y_train = np_utils.to_categorical(y_train)
    y_test = np_utils.to_categorical(y_test)
    num_classes = y_test.shape[1]
    
    
  4. Build a model:

    A463052_1_En_7_Figv_HTML.jpg

    # building the model
    model = Sequential()
    # add 1000 units in the hidden layer
    # apply relu activation in hidden layer
    model.add(Dense(1000, input_dim=num_pixels,activation='relu'))
    # initialize the output layer
    model.add(Dense(num_classes, activation="softmax"))
    # compile the model
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
    # extract the summary of model
    model.summary()
    
    
  5. 运行模型:

    model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)
    
    

请注意,随着历元数量的增加,测试数据集的准确性也会增加。此外,在keras中,我们只需要指定第一层的输入尺寸,它会自动计算其余层的尺寸。

使用正则化避免过拟合

即使我们已经缩放了数据集,神经网络也可能在训练数据集上过度拟合,因为损失函数(平方误差或交叉熵误差)确保损失随着时期数量的增加而最小化。

然而,在训练损失不断减少的同时,测试数据集上的损失并不一定也在减少。神经网络中的权重(参数)越多,在训练数据集上过度拟合从而无法在看不见的测试数据集上推广的可能性就越大。

让我们在 MNIST 数据集上对比使用相同神经网络架构的两个场景,其中在场景 A 中,我们考虑 5 个历元,因此过拟合的机会较少,而在场景 B 中,我们考虑 100 个历元,因此过拟合的机会较多(代码在 github 中可作为“neural network.ipynb 中的正则化需求”获得)。

我们应该注意到,在最初的几个时期,训练和测试数据集精度之间的差异较小,但是随着时期数量的增加,训练数据集的精度增加,而测试数据集的精度在一些时期之后可能不会增加。

在我们的运行中,我们看到了以下准确性指标:

| 方案 | 训练数据集 | 测试数据集 | | 5 个时代 | 97.57% | 97.27% | | 100 个时代 | 100% | 98.28% |

一旦我们绘制了权重直方图(在本例中,权重将隐藏层连接到输出层),我们将会注意到,与 5 个时段的权重相比,100 个时段的权重具有更高的分布(范围),如下图所示:

A463052_1_En_7_Figy_HTML.jpg

100 个时期的方案具有更高的权重范围,因为它试图在后面的时期中针对训练数据集中的边缘情况进行调整,而 5 个时期的方案没有机会针对边缘情况进行调整。虽然训练数据集的边缘案例被权重更新覆盖,但是测试数据集的边缘案例没有必要表现类似,因此可能没有被权重更新覆盖。此外,请注意,训练数据集中的边缘情况可以通过给予特定像素非常高的权重来覆盖,从而快速移动到 sigmoid 曲线的饱和 1 或 0。

因此,具有高权重值对于一般化目的来说是不理想的。在这种情况下,正则化就派上了用场。

正则化对具有高数量级的权重不利。使用的主要正则化类型是 L1 & L2 正则化

L2 正则化将附加成本项添加到误差(损失函数)中,如$$ \sum {w}_i² $$

L1 正则化将附加成本项添加到误差(损失函数)中,如$$ \sum \mid {w}_i\mid $$

这样,我们确保权重不能被调整为具有高值,以便它们仅在训练数据集中用于极端边缘情况。

给正则项分配权重

我们注意到,在 L2 正则化的情况下,我们修改的损失函数如下:

总损失= $$ \sum {\left(y-\widehat{y}\right)}²+\lambda \sum {w}_i² $$

其中$$ \lambda $$是与正则化项相关联的权重,并且是需要调整的超参数。类似地,L1 正则化情况下的总损耗如下:

总损失= $$ \sum {\left(y-\widehat{y}\right)}²+\lambda \sum \mid {w}_i\mid $$

L1/ L2 正则化在 Python 中实现如下:

from keras import regularizers
model3 = Sequential()
model3.add(Dense(1000, input_dim=784, activation="relu", kernel_regularizer=regularizers.l2(0.001)))
model3.add(Dense(10, activation="softmax", kernel_regularizer=regularizers.l2(0.001)))
model3.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
model3.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=1024, verbose=2)

注意,上面涉及到调用一个额外的超参数——“kernel _ regulator”,然后指定它是否是 L1 / L2 正则化。此外,我们还指定了赋予正则化权重的$$ \lambda $$值。

我们注意到,正则化后,训练和测试数据集的精度彼此相似,其中训练数据集的精度为 97.6%,而测试数据集的精度为 97.5%。L2 正则化后的权重直方图如下:

A463052_1_En_7_Figz_HTML.jpg

我们注意到,与前两种情况相比,大多数权重现在更接近于 0,从而避免了由于分配给边缘情况的高权重值而导致的过拟合问题。在 L1 正则化的情况下,我们会看到类似的趋势。

因此,L1 和 L2 正则化帮助我们避免了在训练数据集上过度拟合但在测试数据集上不泛化的问题。

用 R 语言实现神经网络

类似于我们在 Python 中实现神经网络的方式,我们将使用keras框架在 R 中实现神经网络。

为了构建神经网络模型,我们将在 R 中使用kerasR包。考虑到kerasR包对 Python 的所有依赖性,以及创建虚拟环境的需要,我们将在云中执行 R 实现,如下所示(代码可作为“NN。github 中的 r”)。

  1. 安装kerasR包:

    install.packages("kerasR")
    
    
  2. 加载已安装的软件包:

    library(kerasR)
    
    
  3. 使用 MNIST 数据集进行分析:

    mnist <- load_mnist()
    
    
  4. 检查mnist对象的结构:

    str(mnist)
    
    

    注意,默认情况下,MNIST 数据集将训练和测试数据集分开。

  5. 提取训练和测试数据集:

    mnist <- load_mnist()
    X_train <- mnist$X_train
    Y_train <- mnist$Y_train
    X_test <- mnist$X_test
    Y_test <- mnist$Y_test
    
    
  6. 重塑数据集。假设我们正在执行一个正常的神经网络操作,我们的输入数据集的维数应该是(60000,784),而X_train的维数是(60000,28,28):

    X_train <- array(X_train, dim = c(dim(X_train)[1], 784))
    X_test <- array(X_test, dim = c(dim(X_test)[1], 784))
    
    
  7. 缩放数据集:

    X_train <- X_train/255
    X_test <- X_test/255
    
    
  8. 将因变量(Y_trainY_test)转换为分类变量:

    Y_train <- to_categorical(mnist$Y_train, 10)
    Y_test <- to_categorical(mnist$Y_test, 10)
    
    
  9. 建立模型:

    model <- Sequential()
    model$add(Dense(units = 1000, input_shape = dim(X_train)[2],activation = "relu"))
    model$add(Dense(10,activation = "softmax"))
    model$summary()
    
    
  10. 编译模型:

```py
keras_compile(model,  loss = 'categorical_crossentropy', optimizer = Adam(),metrics='categorical_accuracy')

```
  1. 拟合模型:
```py
keras_fit(model, X_train, Y_train,batch_size = 1024, epochs = 5,verbose = 1, validation_data = list(X_test,Y_test))

```

我们刚刚经历的过程应该给我们一个大约 98%的测试数据集准确度。

摘要

在本章中,您学习了以下内容:

  • 神经网络可以逼近复杂的函数(因为隐藏层中的激活)
  • 前向和后向传播构成了神经网络功能的构建模块
  • 正向传播有助于我们估计误差,而反向传播有助于减少误差
  • 每当梯度下降涉及到达到最佳权重值时,缩放输入数据集总是一个更好的主意
  • L1/ L2 正则化通过惩罚高权重幅度来帮助避免过拟合

八、 Word2vec

Word2vec 是一种基于神经网络的方法,在传统的文本挖掘分析中非常方便。

传统文本挖掘方法的一个问题是数据的维度问题。考虑到典型文本中有大量不同的单词,构建的列数可能会非常多(其中每列对应一个单词,列中的每个值指定该单词是否存在于与该行相对应的文本中——本章稍后将详细介绍)。

Word2vec 有助于以更好的方式表示数据:彼此相似的单词具有相似的向量,而彼此不相似的单词具有不同的向量。在这一章中,我们将探索计算单词向量的不同方法。

为了了解 Word2vec 如何有用,让我们探索一个问题。假设我们有两个输入句子:

A463052_1_En_8_Figa_HTML.png

直觉上,我们知道享受和喜欢是相似的词。然而,在传统的文本挖掘中,当我们对单词进行一次热编码时,我们的输出看起来像这样:

A463052_1_En_8_Figb_HTML.png

注意,一键编码导致每个单词被分配一列。one-hot 编码的主要问题是单词{I,enjoy}之间的欧几里德距离与单词{enjoy,like}之间的距离相同。但是我们知道{我,享受}之间的距离应该大于{享受,喜欢}之间的距离,因为享受和喜欢彼此更同义。

手工构建单词向量

在构建单词向量之前,我们将假设表述如下:

"相关的词周围会有相似的词."

例如,“国王”和“王子”这两个词周围经常会出现类似的词。本质上,单词的上下文(周围的单词)是相似的。

有了这个假设,让我们把每个单词看做输出,把所有的上下文单词(周围的单词)看做输入。因此,我们的数据集翻译如下(在 github 中以“word2vec.xlsx”的形式提供):

A463052_1_En_8_Figc_HTML.jpg

通过使用上下文单词作为输入,我们试图预测给定单词作为输出。

前面的输入和输出单词的矢量化形式如下所示(注意,第 3 行中给出的列名{I,enjoy,playing,TT,like}仅供参考):

A463052_1_En_8_Figd_HTML.jpg

注意,给定输入单词——{享受,演奏,TT }——向量形式是{0,1,1,1,0},因为输入不包含 I 和 like,所以第一个和最后一个索引是 0(注意在第一页中完成的 one-hot 编码)。

现在,假设我们想将 5 维输入向量转换成 3 维向量。在这种情况下,我们的隐藏层有三个相关的神经元。我们的神经网络看起来会如图 8-1 所示。

A463052_1_En_8_Fig1_HTML.png

图 8-1

Our neural network

每层的尺寸如下:

| 层 | 大小 | 评论 | | :-- | :-- | :-- | | 输入层 | 8 × 5 | 因为有 8 个输入和 5 个索引(唯一字) | | 隐藏层的权重 | 5 × 3 | 因为 3 个神经元各有 5 个输入 | | 隐藏层输出 | 8 × 3 | 输入和隐藏层的矩阵乘法 | | 从隐藏到输出的权重 | 3 × 5 | 隐藏层的 3 个输出列映射到 5 个原始输出列 | | 输出层 | 8 × 5 | 隐藏层输出与从隐藏层到输出层的权重之间的矩阵乘法 |

下面显示了每种方法的工作原理:

A463052_1_En_8_Fige_HTML.jpg

注意,输入向量乘以随机初始化的隐藏层权重矩阵,以获得隐藏层的输出。给定输入大小为 8 × 5,隐藏层大小为 5 × 3,矩阵乘法的输出为 8 × 3。而且,与传统的神经网络不同,在 Word2vec 方法中,我们不在隐藏层上应用任何激活:

A463052_1_En_8_Figf_HTML.jpg

一旦我们有了隐藏层的输出,我们就把它们乘以一个从隐藏层到输出层的权重矩阵。假设隐藏层的输出大小为 8 × 3,要输出的隐藏层大小为 3 × 5,则我们的输出层为 8 × 5。但是请注意,输出图层有一系列数字,包括正数和负数,以及大于 1 或的数字

因此,正如我们在神经网络中所做的那样,我们通过 softmax 将数字转换为 0 到 1 之间的数字:

A463052_1_En_8_Figg_HTML.jpg

为了方便起见,我将 softmax 分解为两个步骤:

  1. 对数字应用指数。
  2. 将步骤 1 的输出除以步骤 1 输出的行和。

在前面的输出中,我们看到第一列的输出非常接近第一行的 1,第二列的输出是第二行的 0.5,依此类推。

获得预测值后,我们将它们与实际值进行比较,以计算整个批次的交叉熵损失,如下所示:

A463052_1_En_8_Figh_HTML.jpg

交叉熵损失=–∑实际值× Log(概率,2)

既然我们已经计算了总体交叉熵误差,我们的任务是通过使用选择的优化器改变随机初始化的权重来减少总体交叉熵误差。一旦我们达到了最佳的权重值,我们剩下的隐藏层看起来像这样:

A463052_1_En_8_Figi_HTML.jpg

既然我们已经计算了输入单词和隐藏层权重,现在可以通过将输入单词乘以隐藏层表示来在较低的维度中表示单词。

输入层(每个单词 1 × 5)和隐藏层(5 × 3 权重)的矩阵乘法是一个大小为(1 × 3)的向量:

A463052_1_En_8_Figj_HTML.jpg

如果我们现在考虑单词{enjoy,like},我们应该注意到这两个单词的向量彼此非常相似(也就是说,这两个单词之间的距离很小)。

这样,我们将原始输入的 one-hot-encoded 向量(其中{enjoy,like}之间的距离较大)转换为转换后的单词向量(其中{enjoy,like}之间的距离较小)。

构建单词向量的方法

我们在前面的部分中采用的构建单词向量的方法被称为连续单词包(CBOW)模型。

取一句话“那只敏捷的棕色狐狸跳过了那只狗。”CBOW 模型是这样处理这句话的:

| 输入单词 | 输出字 | | :-- | :-- | | {那个,快,狐狸,跳了} | {布朗} | | {快速,棕色,跳跃,结束} | {fox} | | {布朗,福克斯,完毕,该} | {jumped} | | {狐狸,跳了,狗} | {结束} |
  1. 固定窗口大小。也就是说,选择给定单词左边和右边的 n 个单词。例如,假设窗口大小是给定单词左右各 2 个单词。
  2. 给定窗口大小,输入和输出向量如下所示:

另一种建立单词向量的方法叫做跳格模型。在 skip-gram 模型中,前面的步骤是相反的,如下所示:

| 输入单词 | 输出字 | | :-- | :-- | | {布朗} | {那个,快,狐狸,跳了} | | {fox} | {快速,棕色,跳跃,结束} | | {jumped} | {布朗,福克斯,完毕,该} | | {结束} | {狐狸,跳了,狗} |

无论是 skip-gram 模型还是 CBOW 模型,获得隐藏层向量的方法都是相同的。

在 Word2vec 模型中需要注意的问题

对于到目前为止所讨论的计算方法,本节着眼于我们可能面临的一些常见问题。

常用词

像 the 这样的典型常用词在词汇中经常出现。在这种情况下,输出中出现的单词更频繁。如果不进行处理,这可能会导致大部分输出是最常用的单词,比如 the,而不是其他单词。我们需要有一种方法来惩罚一个频繁出现的单词在训练数据集中出现的次数。

在典型的 Word2vec 分析中,我们惩罚频繁出现的单词的方式如下。选择一个单词的概率是这样计算的:

$$ P\left({w}_i\right)=\left(\sqrt{\frac{z\left({w}_i\right)}{0.001}}+1\right)\cdot \frac{0.001}{z\left({w}_i\right)} $$

z(w)是一个单词在任何单词的总出现次数中出现的次数。该公式的绘图显示了图 8-2 中的曲线。

A463052_1_En_8_Fig2_HTML.jpg

图 8-2

The resultant curve

请注意,随着 z(w) (x 轴)的增加,选择概率(y 轴)急剧下降。

负采样

让我们假设在我们的数据集中总共有 10,000 个唯一的单词,也就是说,每个向量有 10,000 个维度。我们还假设我们正在从原始的 10,000 维向量创建一个 300 维向量。这意味着,从隐藏层到输出层,总共有 300×10000 = 3000000 个权重。

权重数量如此之大的一个主要问题是,它可能会导致数据上的过度拟合。这也可能导致更长的训练时间。

负采样是克服这个问题的一种方法。假设不是检查所有 10,000 个维度,而是选择输出为 1(正确标签)的索引和标签为 0 的五个随机索引。这样,我们将在单次迭代中更新的权重数量从 300 万减少到 300 × 6 = 1800 个权重。

我说过负索引的选择是随机的,但是在 Word2vec 的实际实现中,选择是基于一个单词与其他单词相比的频率。与频率较低的单词相比,频率较高的单词被选中的几率更高。

选择五个否定词的概率如下:

$$ P\left({w}_i\right)=\frac{f{\left({w}_i\right)}^{3/4}}{\sum \limits_{j=0}n\left(f{\left({w}_j\right)}{3/4}\right)} $$

f(w)是给定词的出现频率。

一旦计算出每个单词的概率,单词的选择过程如下:频率较高的单词重复频率较高,频率较低的单词重复频率较低,并存储在一个表中。鉴于高频词出现的频率更高,从表格中随机选择五个词时,它们被选中的几率更高。

用 Python 实现 Word2vec

Word2vec 可以使用gensim包在 Python 中实现(Python 实现在 github 中的名称是“word2vec.ipynb”)。

第一步是初始化软件包:

import nltk
import gensim
import pandas as pd

导入包后,我们需要提供前面几节中讨论的参数:

  • logging本质上帮助我们跟踪单词向量计算完成的程度。
  • num_features是隐含层的神经元个数。
  • min_word_count是被接受用于计算的单词频率的截止值。
  • context是窗口大小
  • downsampling有助于降低选择更常用单词的概率。
import logging
logging.basicConfig(format=’%(asctime)s : %(levelname)s : %(message)s’,\
    level=logging.INFO)

# Set values for various parameters
num_features = 100    # Word vector dimensionality  
min_word_count = 50   # Minimum word count    
num_workers = 4       # Number of threads to run in parallel
context = 9           # Context window size 
downsampling = 1e-4   # Downsample setting for frequent words

模型的输入词汇应该如下所示:

A463052_1_En_8_Figk_HTML.jpg

注意,所有输入的句子都被标记化了。

Word2vec 模型训练如下:

from gensim.models import word2vec
print(“Training model...”)
w2v_model = word2vec.Word2Vec(t2, workers=num_workers,
            size=num_features, min_count = min_word_count,
            window = context, sample = downsampling)

一旦模型被训练,满足指定标准的词汇表中任何单词的权重向量可以如下获得:

model['word'] # replace the "word" with the word of your interest

类似地,与给定单词最相似的单词可以如下获得:

model.most_similar('word')

摘要

在本章中,您学习了以下内容:

  • Word2vec 是一种可以帮助将文本单词转换成数字向量的方法。
  • 这对下游的多种方法来说是一个强大的第一步——例如,我们可以在构建模型时使用单词 vectors。
  • Word2vec 使用一种 CBOW 或 skip-gram 模型来提供向量,这种模型具有神经网络架构,有助于提供向量。
  • 神经网络中的隐含层是生成单词向量的关键。

九、卷积神经网络

在第七章中,我们看了一个传统的神经网络(NN)。传统神经网络的局限性之一是它不是平移不变的,也就是说,图像右上角的猫图像与图像中心有猫的图像的处理方式不同。卷积神经网络(CNN)被用来处理这样的问题。

鉴于 CNN 可以处理图像中的转换,它被认为更有用,并且 CNN 架构事实上是当前对象分类/检测中最先进的技术之一。

在本章中,您将学习以下内容:

  • CNN 的工作细节
  • CNN 如何克服神经网络的缺点
  • 卷积和池对解决图像翻译问题的影响
  • 如何用 Python 实现 CNN,以及 R

为了进一步更好地理解 CNN 的必要性,让我们从一个例子开始。比方说,我们想对一幅图像中是否有垂直线进行分类(也许是为了判断该图像是否代表 1)。为了简单起见,我们假设图像是 5 × 5 的图像。书写垂直线(或 1)的几种方法如下:

A463052_1_En_9_Figa_HTML.png

我们还可以检查数字 1 在 MNIST 数据集中的不同书写方式。图 9-1 中显示了一个为写入的 1 突出显示的像素图像。

A463052_1_En_9_Fig1_HTML.jpg

图 9-1

Image of pixels corresponding to images with label 1

在图像中,像素越红,越经常有人在上面写字;越蓝意味着像素被写入的次数越少。中间的像素是最红的,很可能是因为大多数人会在那个像素上写字,不管他们用来写 1 的角度是什么——垂直线还是向左或向右倾斜。在下一节中,您会注意到,当图像平移几个单位时,神经网络预测不准确。在后面的章节中,我们将了解 CNN 如何解决图像翻译的问题。

传统神经网络的问题是

在刚才提到的场景中,只有当中间周围的像素被高亮显示而图像中的其余像素没有被高亮显示时,传统的神经网络才会将图像高亮显示为 1(因为大多数人都高亮显示了中间的像素)。

为了更好地理解这个问题,让我们来看一下我们在第七章中看过的代码(在 github 中代码可以作为“传统 NN.ipynb 的问题”获得):

A463052_1_En_9_Figc_HTML.jpg

  1. Download the dataset and extract the train and test datasets:

    A463052_1_En_9_Figb_HTML.jpg

    from keras.datasets import mnist
    import matplotlib.pyplot as plt
    %matplotlib inline
    # load (downloaded if needed) the MNIST dataset
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    # plot 4 images as gray scale
    plt.subplot(221)
    plt.imshow(X_train[0], cmap=plt.get_cmap('gray'))
    plt.subplot(222)
    plt.imshow(X_train[1], cmap=plt.get_cmap('gray'))
    plt.subplot(223)
    plt.imshow(X_train[2], cmap=plt.get_cmap('gray'))
    plt.subplot(224)
    plt.imshow(X_train[3], cmap=plt.get_cmap('gray'))
    # show the plot
    plt.show()
    
    
  2. 导入相关包:

    import numpy as np
    from keras.datasets import mnist
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from keras.layers import Flatten
    from keras.layers.convolutional import Conv2D
    from keras.layers.convolutional import MaxPooling2D
    from keras.utils import np_utils
    from keras import backend as K
    
    
  3. 只取标签 1 对应的训练集:

    X_train1 = X_train[y_train==1]
    
    
  4. 对数据集进行整形和归一化:

    num_pixels = X_train.shape[1] * X_train.shape[2]
    X_train = X_train.reshape(X_train.shape[0],num_pixels ).astype('float32')
    X_test = X_test.reshape(X_test.shape[0],num_pixels).astype('float32')
    
    X_train = X_train / 255
    X_test = X_test / 255
    
    
  5. 一次热编码标签:

    y_train = np_utils.to_categorical(y_train)
    y_test = np_utils.to_categorical(y_test)
    num_classes = y_train.shape[1]
    
    
  6. 构建模型并运行:

    model = Sequential()
    model.add(Dense(1000, input_dim=num_pixels, activation="relu"))
    model.add(Dense(num_classes, activation="softmax"))
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=[''accuracy'])
    model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)
    
    

让我们画出平均 1 个标签的样子:

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
plt.imshow(pic)

图 9-2 显示结果。

A463052_1_En_9_Fig2_HTML.jpg

图 9-2

Average 1 image

场景 1

在这种情况下,创建了一个新图像(图 9-3 ,其中原始图像向左平移了 1 个像素:

A463052_1_En_9_Fig3_HTML.jpg

图 9-3

Average 1 image translated by 1 pixel to the left

for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+1]
plt.imshow(pic)

让我们继续使用构建的模型预测图 9-3 中图像的标签:

A463052_1_En_9_Figd_HTML.jpg

model.predict(pic.reshape(1,784))

我们将错误的预测 8 视为输出。

场景 2

创建了一个新图像(图 9-4 ),其中的像素不是从原始平均 1 图像转换而来的:

A463052_1_En_9_Fig4_HTML.jpg

图 9-4

Average 1 image

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
plt.imshow(pic)

该图像的预测如下:

A463052_1_En_9_Fige_HTML.jpg

model.predict(pic.reshape(1,784))

我们看到输出为 1 的正确预测。

场景 3

创建新图像(图 9-5 ),其中原始平均 1 图像的像素向右移动 1 个像素:

A463052_1_En_9_Fig5_HTML.jpg

图 9-5

Average 1 image translated by 1 pixel to the right

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
pic2=np.copy(pic)
for i in range(pic.shape[0]):
  if ((i>6) and (i<26)):
    pic[:,i]=pic2[:,(i-1)]
plt.imshow(pic)

让我们继续使用构建的模型预测上面图像的标签:

A463052_1_En_9_Figf_HTML.jpg

model.predict(pic.reshape(1,784))

我们有一个输出为 1 的正确预测。

场景 4

创建新图像(图 9-6 ),其中原始平均 1 图像的像素向右移动 2 个像素:

A463052_1_En_9_Fig6_HTML.jpg

图 9-6

Average 1 image translated by 2 pixels to the right

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
pic2=np.copy(pic)
for i in range(pic.shape[0]):
  if ((i>6) and (i<26)):
    pic[:,i]=pic2[:,(i-2)]
plt.imshow(pic)

我们将使用构建的模型预测图像的标签:

A463052_1_En_9_Figg_HTML.jpg

model.predict(pic.reshape(1,784))

我们看到一个错误的预测 3 作为输出。

从前面的场景中,您可以看到传统的神经网络在数据转换时无法产生良好的结果。这些场景需要不同的网络处理方式来解决翻译差异。这就是卷积神经网络(CNN)派上用场的地方。

理解 CNN 中的卷积

你已经对典型的神经网络的工作原理有了很好的了解。在这一节,我们来探讨一下 CNN 中卷积这个词是什么意思。卷积是两个矩阵之间的乘法,其中一个矩阵较大,另一个较小。

要查看卷积,请考虑以下示例。

矩阵 A 如下:

A463052_1_En_9_Figh_HTML.png

矩阵 B 如下:

A463052_1_En_9_Figi_HTML.png

在执行卷积时,可以把它想象成在较大矩阵上滑动较小矩阵:当较小矩阵在较大矩阵的整个区域上滑动时,我们可能会得到 9 个这样的乘法。注意,它不是矩阵乘法:

  1. 较大矩阵的{1,2,5,6}乘以较小矩阵的{1,2,3,4}。1 × 1 + 2 × 2 + 5 × 3 + 6 × 4 = 44
  2. 较大矩阵的{2,3,6,7}乘以较小矩阵的{1,2,3,4 }:2×1+3×2+6×3+7×4 = 54
  3. 较大矩阵的{3,4,7,8}乘以较小矩阵的{1,2,3,4 }:3×1+4×2+7×3+8×4 = 64
  4. 较大矩阵的{5,6,9,10}乘以较小矩阵的{1,2,3,4 }:5×1+6×2+9×3+10×4 = 84
  5. 较大矩阵的{6,7,10,11}乘以较小矩阵的{1,2,3,4 }:6×1+7×2+10×3+11×4 = 94
  6. 较大矩阵的{7,8,11,12}乘以较小矩阵的{1,2,3,4 }:7×1+8×2+11×3+12×4 = 104
  7. 较大矩阵的{9,10,13,14}乘以较小矩阵的{1,2,3,4 }:9×1+10×2+13×3+14×4 = 124
  8. 较大矩阵的{10,11,14,15}乘以较小矩阵的{1,2,3,4 }:10×1+11×2+14×3+15×4 = 134
  9. 较大矩阵的{11,12,15,16}乘以较小矩阵的{1,2,3,4 }:11×1+12×2+15×3+16×4 = 144

前面步骤的结果将是一个矩阵,如下所示:

A463052_1_En_9_Figj_HTML.png

按照惯例,较小的矩阵被称为滤波器或内核,并且较小的矩阵值是通过梯度下降(稍后将详细介绍梯度下降)在统计上得到的。过滤器内的值可以被认为是成分权重。

从卷积到激活

在传统的神经网络中,隐藏层不仅将输入值乘以权重,还将非线性应用于数据—它通过激活函数传递值。类似的活动也发生在典型的 CNN 中,卷积通过激活函数传递。CNN 支持我们目前看到的传统激活函数:sigmoid,ReLU,Tanh。

对于前面的输出,请注意,当通过 ReLU 激活函数时,输出保持不变,因为所有的数字都是正数。

从卷积激活到池化

到目前为止,我们已经了解了卷积是如何工作的。在本节中,我们将考虑卷积后的下一个典型步骤:池化。

假设卷积步骤的输出如下(我们不考虑前面的例子,这是一个说明池的新例子,基本原理将在后面的部分解释):

A463052_1_En_9_Figk_HTML.jpg

在这种情况下,卷积步骤的输出是一个 2 × 2 矩阵。最大池考虑 2 × 2 块并给出最大值作为输出,同样,如果卷积步骤的输出是一个更大的矩阵,如下所示:

A463052_1_En_9_Figl_HTML.jpg

最大池将大矩阵分成大小为 2 × 2 的非重叠块,如下所示:

A463052_1_En_9_Figm_HTML.jpg

从每个块中,只选择具有最高值的元素。因此,对前面矩阵的最大池化操作的输出如下:

A463052_1_En_9_Fign_HTML.jpg

注意,实际上,没有必要总是使用 2 × 2 滤波器。

涉及的其他类型的池是总和和平均。同样,在实践中,与其他类型的池相比,我们看到了很多最大池。

卷积和池化有什么帮助?

在我们之前看到的 MNIST 例子中,传统神经网络的缺点之一是每个像素都与不同的权重相关联。因此,如果除了原始像素之外的相邻像素变得高亮,输出将不会非常准确(场景 1 的示例,其中 1 稍微位于中间的左侧)。

现在解决这种情况,因为像素共享在每个滤波器内构成的权重。所有像素乘以构成过滤器的所有权重,并且在汇集层中,仅选择被激活最高的值。这样,无论高亮显示的像素是在中心还是稍微远离中心,输出通常都不是预期值。然而,当突出显示的像素远离中心时,问题仍然存在。

用代码创建 CNN

从前面的传统神经网络场景中,我们看到,如果像素向左平移 1 个单位,神经网络将不起作用。实际上,我们可以认为卷积步骤是识别模式的步骤,合并步骤是导致翻译差异的步骤。

N 个汇集步骤导致至少 N 个单位的平移不变性。考虑下面的例子,我们在卷积后应用一个池化步骤(代码在 github 中作为“使用 CNN.ipynb 的改进”提供):

  1. Import and reshape the data to fit a CNN:

    A463052_1_En_9_Figo_HTML.jpg

    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    X_train = X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[1],1 ).astype('float32')
    X_test = X_test.reshape(X_test.shape[0],X_test.shape[1],X_test.shape[1],1).astype('float32')
    
    X_train = X_train / 255
    X_test = X_test / 255
    
    y_train = np_utils.to_categorical(y_train)
    y_test = np_utils.to_categorical(y_test)
    num_classes = y_test.shape[1]
    Step 2: Build a model
    model = Sequential()
    model.add(Conv2D(10, (3,3), input_shape=(28, 28,1), activation="relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())
    model.add(Dense(1000, activation="relu"))
    model.add(Dense(num_classes, activation="softmax"))
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
    model.summary()
    
    
  2. Fit the model:

    A463052_1_En_9_Figp_HTML.jpg

    model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)
    
    

对于前一个卷积,其中一个卷积后跟一个池层,如果像素向左或向右平移 1 个单位,则输出预测效果良好,但当像素平移超过 1 个单位时,输出预测效果不佳(图 9-7 ):

A463052_1_En_9_Fig7_HTML.jpg

图 9-7

Average 1 image translated by 1 pixel to the left

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+1]
plt.imshow(pic)

让我们继续预测图 9-7 的标签:

A463052_1_En_9_Figq_HTML.jpg

model.predict(pic.reshape(1,28,28,1))

我们看到输出为 1 的正确预测。

在下一个场景中(图 9-8 ,我们将像素向左移动 2 个单位:

A463052_1_En_9_Fig8_HTML.jpg

图 9-8

Average 1 image translated by 2 pixels to the left

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+2]
plt.imshow(pic)

让我们根据之前构建的 CNN 模型来预测图 9-8 的标签:

A463052_1_En_9_Figr_HTML.jpg

model.predict(pic.reshape(1,28,28,1))

当图像向左平移 2 个像素时,我们有不正确的预测。

请注意,当模型中卷积池层的数量与图像中的平移量相同时,预测是正确的。但是,如果与图像中的转换相比,卷积池层较少,则预测更有可能不正确。

CNN 的工作细节

让我们用 Python 构建 toy CNN 代码,然后在 Excel 中实现输出,这样可以加强我们的理解(代码在 github 中以“CNN simple example.ipynb”的形式提供):

  1. 导入相关包:

    # import relevant packages
    from keras.datasets import mnist
    import matplotlib.pyplot as plt
    %matplotlib inline
    import numpy as np
    from keras.datasets import mnist
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from keras.utils import np_utils
    from keras.layers import Flatten
    from keras.layers.convolutional import Conv2D
    from keras.layers.convolutional import MaxPooling2D
    from keras.utils import np_utils
    from keras import backend as K
    from keras import regularizers
    
    
  2. 创建一个简单的数据集:

    # Create a simple dataset
    X_train=np.array([[[1,2,3,4],[2,3,4,5],[5,6,7,8],[1,3,4,5]],[[-1,2,3,-4],[2,-3,4,5],[-5,6,-7,8],[-1,-3,-4,-5]]])
    y_train=np.array([0,1])
    
    
  3. 通过将每个值除以数据集中的最大值来归一化输入:

    X_train = X_train / 8
    
    
  4. 对输出进行一次热编码:

    y_train = np_utils.to_categorical(y_train)
    
    
  5. 一旦只有两个大小为 4 × 4 的输入和两个输出的简单数据集就绪,让我们首先将输入整形为所需的格式(即:样本数、图像高度、图像宽度、图像通道数):

    X_train = X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[1],1 ).astype('float32')
    
    
  6. Build a model:

    A463052_1_En_9_Figs_HTML.jpg

    model = Sequential()
    model.add(Conv2D(1, (3,3), input_shape=(4,4,1), activation="relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())
    model.add(Dense(10, activation="relu"))
    model.add(Dense(2, activation="softmax"))
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
    model.summary()
    
    
  7. Fit the model:

    A463052_1_En_9_Figt_HTML.jpg

    model.fit(X_train, y_train, epochs=100, batch_size=2, verbose=1)
    
    

上述模型的各层如下:

A463052_1_En_9_Figu_HTML.jpg

model.layers

各层对应的名称和形状如下:

A463052_1_En_9_Figv_HTML.jpg

names = [weight.name for layer in model.layers for weight in layer.weights]
weights = model.get_weights()

for name, weight in zip(names, weights):
    print(name, weight.shape)

对应于给定层的权重可以提取如下:

A463052_1_En_9_Figw_HTML.jpg

model.layers[0].get_weights()

第一个输入的预测可以计算如下:

A463052_1_En_9_Figx_HTML.jpg

model.predict(X_train[0].reshape(1,4,4,1))

现在我们知道前面预测的概率为 0 是 0.89066,让我们通过在 Excel 中匹配前面的预测(在 github 中作为“CNN simple example.xlsx”提供)来验证我们对 CNN 的直觉。

第一个输入及其相应的缩放版本,以及卷积权重和偏差(来自模型),如下所示:

A463052_1_En_9_Figy_HTML.jpg

卷积的输出如下(请检查“CNN simple example.xlsx”文件中 L4 至 M5 的单元格):

A463052_1_En_9_Figz_HTML.jpg

卷积的计算按照以下公式:

A463052_1_En_9_Figaa_HTML.jpg

在卷积层之后,我们如下执行最大池化:

A463052_1_En_9_Figab_HTML.jpg

一旦执行了池化,所有的输出都是扁平的(按照我们模型中的规范)。然而,假设我们的池层只有一个输出,扁平化也会产生一个输出。

在下一步中,展平的层连接到隐藏的密集层(在我们的模型规范中有 10 个神经元)。对应于每个神经元的权重和偏差如下:

A463052_1_En_9_Figac_HTML.jpg

矩阵乘法和乘法后的 ReLU 激活如下:

A463052_1_En_9_Figad_HTML.jpg

上述输出的公式如下:

A463052_1_En_9_Figae_HTML.jpg

现在让我们看看从隐藏层到输出层的计算。请注意,每个输入有两个输出(每行的输出在维度上有两列:概率为 0 和概率为 1)。从隐藏层到输出层的权重如下:

A463052_1_En_9_Figaf_HTML.jpg

现在,每个神经元都连接到两个权重(其中每个权重都将其连接到两个输出),让我们看看从隐藏层到输出层的计算:

A463052_1_En_9_Figag_HTML.jpg

输出层的计算如下:

A463052_1_En_9_Figah_HTML.jpg

现在我们有了一些输出值,让我们来计算输出的 softmax 部分:

A463052_1_En_9_Figai_HTML.jpg

现在,输出将与我们在 keras 模型的输出中看到的完全相同:

A463052_1_En_9_Figaj_HTML.jpg

因此,我们对前面章节中的直觉进行了验证。

深入研究卷积/内核

为了了解内核/过滤器是如何帮助我们的,让我们来看另一个场景。从 MNIST 数据集,让我们以这样的方式修改目标,即我们只对预测图像是 1 还是不是 1 感兴趣:

(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[1],1 ).astype('float32')
X_test = X_test.reshape(X_test.shape[0],X_test.shape[1],X_test.shape[1],1).astype('float32')

X_train = X_train / 255
X_test = X_test / 255

X_train1 = X_train[y_train==1]

y_train = np.where(y_train==1,1,0)
y_test = np.where(y_test==1,1,0)
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]

我们将提出一个简单的 CNN,其中只有两个卷积滤波器:

A463052_1_En_9_Figak_HTML.jpg

model = Sequential()
model.add(Conv2D(2, (3,3), input_shape=(28, 28,1), activation="relu"))
model.add(Flatten())
model.add(Dense(1000, activation="relu"))
model.add(Dense(num_classes, activation="softmax"))
model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
model.summary()

现在,我们将继续运行模型,如下所示:

A463052_1_En_9_Figal_HTML.jpg

model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)

我们可以通过以下方式提取对应于滤波器的权重:

model.layers[0].get_weights()

让我们使用上一步中获得的权重手动卷积并应用激活(图 9-9 ):

A463052_1_En_9_Fig9_HTML.jpg

图 9-9

Average filter activations when 1 label images are passed

from scipy import signal
from scipy import misc
import numpy as np
import pylab
for j in range(2):
    gradd=np.zeros((30,30))
    for i in range(6000):
        grad = signal.convolve2d(X_train1[i,:,:,0], model.layers[0].get_weights()[0].T[j][0])+model.layers[0].get_weights()[1][j]
        grad = np.where(grad<0,0,grad)
        gradd=grad+gradd
    grad2=np.where(gradd<0,0,gradd)
    pylab.imshow(grad2/6000)
    pylab.gray()
    pylab.show()

在图中,请注意左边的滤镜比右边的滤镜更能激活 1 图像。本质上,第一个过滤器有助于预测更多的标签 1,而第二个过滤器增强了对其余标签的预测。

从卷积和汇集到扁平化:全连接层

到目前为止,我们看到的输出是图像。在传统的神经网络中,我们将每个像素视为一个独立的变量。这正是我们将要在展平过程中执行的操作。

图像的每个像素都是展开的,因此这个过程被称为展平。例如,卷积和合并后的输出图像如下所示:

A463052_1_En_9_Figam_HTML.jpg

展平的输出如下所示:

A463052_1_En_9_Figan_HTML.jpg

从一个完全连接的层到另一个

在典型的神经网络中,输入层连接到隐藏层。以类似的方式,在 CNN 中,全连接层连接到通常具有更多单元的另一个全连接层。

从全连接层到输出层

与传统的神经网络架构类似,隐藏层连接到输出层,并通过 sigmoid 激活来获得作为概率的输出。根据要解决的问题,还选择了适当的损失函数。

连接点:前馈网络

以下是我们到目前为止所执行的步骤的回顾:

  1. 盘旋
  2. 联营
  3. 变平
  4. 隐蔽层
  5. 计算输出概率

典型的 CNN 外观如图 9-10 所示(最著名的——以发明人自己开发的 LeNet 为例):

图 9-10 中的子样本相当于我们之前看到的最大池步骤。

CNN 的其他细节

在图 9-10 中,我们看到 conv1 步骤有六个通道或原始图像的卷积。让我们详细看看这个:

  1. 假设我们有一个 28 × 28 的灰度图像。六个大小为 3 × 3 的过滤器将生成大小为 26 × 26 的图像。因此,我们只剩下六幅大小为 26 × 26 的图像。
  2. 典型的彩色图像有三个通道(RGB)。为了简单起见,我们可以假设第一步中的输出图像有六个通道——六个滤镜一个通道(尽管我们不能像三通道版本那样将它们命名为 RGB)。在这一步中,我们将分别对六个通道中的每一个通道执行最大池化。这将产生六个尺寸为 13 × 13 的图像(通道)。
  3. 在下一个卷积步骤中,我们将 13 × 13 图像的六个通道乘以维度为 3 × 3 × 6 的权重。这是一个在三维图像上卷积的三维权重矩阵(其中图像的尺寸为 13 × 13 × 6)。这将导致每个滤波器的图像尺寸为 11 × 11。假设我们已经考虑了十种不同的权重矩阵(准确地说是立方体)。这将产生尺寸为 11 × 11 × 10 的图像。
  4. 每个 11 × 11 图像(数量为 10)的最大池将产生一个 5 × 5 的图像。请注意,当对具有奇数维的图像执行最大池化时,池化会产生向下舍入的图像,也就是说,11/2 向下舍入为 5。

A463052_1_En_9_Fig10_HTML.png

图 9-10

A LeNet

跨距是对原始图像进行卷积的滤波器从一个步骤移动到下一个步骤的量。例如,如果跨距值为 2,则两个连续卷积之间的距离为 2 个像素。当跨距值为 2 时,乘法将如下进行,其中 A 是较大的矩阵,B 是滤波器:

A463052_1_En_9_Figao_HTML.jpg

第一个卷积将介于:

A463052_1_En_9_Figap_HTML.jpg

第二个卷积将介于:

A463052_1_En_9_Figaq_HTML.png

第三个卷积将介于:

A463052_1_En_9_Figar_HTML.png

最终卷积将在以下两者之间:

A463052_1_En_9_Figas_HTML.png

注意,对于这里给定维数的矩阵,当步长为 2 时,卷积的输出是 2 × 2 矩阵。

Padding

请注意,当在图像上执行卷积时,结果图像的大小会减小。解决尺寸缩小问题的一种方法是在原始图像的四个边框上填充零。这样,28 × 28 的图像将被转换成 30 × 30 的图像。因此,当 30 × 30 图像通过 3 × 3 滤波器进行卷积时,结果图像将是 28 × 28 图像。

CNN 中的反向传播

CNN 中的反向传播以类似于典型 NN 的方式进行,其中计算少量改变权重对总权重的影响。但是在 NN 中,我们用权重的过滤器/矩阵来代替权重,这些过滤器/矩阵需要更新以最小化总损失。

有时,假设 CNN 中通常有数百万个参数,进行正则化会有所帮助。CNN 中的正则化可以使用丢弃方法或 L1 和 L2 正则化来实现。通过选择不更新某些权重(通常是随机选择的总权重的 20%)并在整个数量的时期内训练整个网络来完成丢弃。

把所有的放在一起

以下代码实现了一个三卷积池层,然后是展平和一个完全连接的层:

 (X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[1],1 ).astype('float32')
X_test = X_test.reshape(X_test.shape[0],X_test.shape[1],X_test.shape[1],1).astype('float32')

X_train = X_train / 255
X_test = X_test / 255

y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]

在下一步中,我们构建模型,如下所示:

A463052_1_En_9_Figat_HTML.jpg

model = Sequential()
model.add(Conv2D(32, (3,3), input_shape=(28, 28,1), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3,3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(1000, activation="relu"))
model.add(Dense(num_classes, activation="softmax"))
model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
model.summary()

最后,我们拟合模型,如下所示:

model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)

请注意,使用前面的代码训练的模型的准确性约为 98.8%。但是请注意,尽管该模型在测试数据集上工作得最好,但是从测试 MNIST 数据集平移或旋转的影像将不会被正确分类(通常,CNN 只能在影像按照卷积池层数平移时提供帮助)。这可以通过查看预测来验证,此时平均 1 幅图像向左平移 2 个像素一次,在另一种情况下,向左平移 3 个像素,如下所示:

A463052_1_En_9_Figau_HTML.jpg

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:,0]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+2]
model.predict(pic.reshape(1,28,28,1))

请注意,在这种情况下,图像向左平移 2 个单位,预测是准确的:

A463052_1_En_9_Figav_HTML.jpg

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:,0]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+3]
model.predict(pic.reshape(1,28,28,1))

请注意,在这里,当图像平移的像素比卷积池层多时,预测不准确。这个问题可以通过使用数据扩充来解决,这是下一节的主题。

日期增加

从技术上讲,翻译后的图像与从原始图像生成的新图像是一样的。可以使用 keras 中的ImageDataGenerator功能生成新数据:

from keras.preprocessing.image import ImageDataGenerator
shift=0.2
datagen = ImageDataGenerator(width_shift_range=shift)
datagen.fit(X_train)
i=0
for X_batch,y_batch in datagen.flow(X_train,y_train,batch_size=100):
  i=i+1
  print(i)
  if(i>500):
    break
  X_train=np.append(X_train,X_batch,axis=0)
  y_train=np.append(y_train,y_batch,axis=0)
print(X_train.shape)

根据这段代码,我们从原始数据中生成了 50,000 次随机洗牌,其中像素被洗牌 20%。

当我们现在绘制 1 的图像时(图 9-11 ),请注意图像有一个更宽的分布:

A463052_1_En_9_Fig11_HTML.jpg

图 9-11

Average 1 post data augmentation

y_train1=np.argmax(y_train,axis=1)
X_train1=X_train[y_train1==1]
pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:,0]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
plt.imshow(pic)

现在,即使我们不对中心左侧或右侧的几个像素进行卷积合并,预测也会起作用。但是,对于远离中心的像素,一旦使用卷积和合并图层构建模型,正确的预测就会出现。

因此,当使用 CNN 模型时,数据扩充有助于进一步概括图像边界上的图像变化,即使卷积池图层较少。

在 R 中实现 CNN

为了在 R 中实现 CNN,我们将利用我们在 R 中用来实现神经网络的同一个包— kerasR(在 github 中代码为“kerasr_cnn_code.r”):

# Load, split, transform and scale the MNIST dataset
mnist <- load_mnist()

X_train <- array(mnist$X_train, dim = c(dim(mnist$X_train), 1)) / 255
Y_train <- to_categorical(mnist$Y_train, 10)
X_test <- array(mnist$X_test, dim = c(dim(mnist$X_test), 1)) / 255
Y_test <- to_categorical(mnist$Y_test, 10)

# Build the model
model <- Sequential()
model$add(Conv2D(filters = 32, kernel_size = c(3, 3),input_shape = c(28, 28, 1)))
model$add(Activation("relu"))
model$add(MaxPooling2D(pool_size=c(2, 2)))
model$add(Flatten())
model$add(Dense(128))
model$add(Activation("relu"))
model$add(Dense(10))
model$add(Activation("softmax"))
# Compile and fit the model
keras_compile(model,  loss = 'categorical_crossentropy', optimizer = Adam(),metrics='categorical_accuracy')
keras_fit(model, X_train, Y_train, batch_size = 1024, epochs = 5, verbose = 1,validation_data = list(X_test,Y_test))

前面的代码产生了大约 97%的准确率。

摘要

在本章中,我们看到了卷积如何帮助我们识别感兴趣的结构,以及合并如何帮助确保图像被识别,即使在原始图像中发生了转换。鉴于 CNN 能够通过卷积和池化来适应图像转换,它能够提供比传统神经网络更好的结果。

十、循环神经网络

在第九章中,我们看了卷积神经网络(CNN)如何改进传统的图像分类神经网络架构。尽管细胞神经网络在处理图像平移和旋转的图像分类中表现很好,但它们不一定有助于识别时间模式。本质上,人们可以认为 CNN 是识别静态模式。

循环神经网络(RNNs)旨在解决识别时间模式的问题。

在本章中,您将学习以下内容:

  • RNN 的工作细节
  • 在 RNN 使用嵌入
  • 使用 RNN 生成文本
  • 使用 RNN 进行情感分类
  • 从 RNN 搬到 LSTM

RNN 可以用多种方式来设计。一些可能的方法如图 10-1 所示。

A463052_1_En_10_Fig1_HTML.png

图 10-1

RNN examples

在图 10-1 中注意以下几点:

  • 底部的方框是输入
  • 中间的方框是隐藏层
  • 顶部的方框是输出

所示一对一架构的一个例子是我们在第七章中看到的典型神经网络,在输入和输出层之间有一个隐藏层。一对多 RNN 架构的一个例子是输入图像并输出图像的标题。多对一 RNN 架构的一个例子可以是作为输入给出的电影评论和作为输出的电影情感(正面、负面或中性评论)。最后,多对多 RNN 架构的一个例子是从一种语言到另一种语言的机器翻译。

理解架构

让我们看一个例子,更仔细地看看 RNN 建筑。我们的任务如下:“给定一串单词,预测下一个单词。”我们将尝试预测“这是一个 _____”后面的单词。假设实际的句子是“这是一个例子。”

传统的文本挖掘技术将以下列方式解决这个问题:

  1. 对每个单词进行编码,如果需要,为额外的单词留出空间:

    This: {1,0,0,0}
    is: {0,1,0,0}
    an: {0,0,1,0}
    
    
  2. 对句子进行编码:

    "This is an": {1,1,1,0}
    
    
  3. 创建训练数据集:

    Input --> {1,1,1,0}
    Output --> {0,0,0,1}
    
    
  4. 建立一个有输入和输出的模型。

这里的一个主要缺点是,如果输入句子是“这是一个”或“这是一个”或“这是一个”,输入表示不会改变。我们知道,其中每一个都非常不同,不能用数学上的相同结构来表示。

这种认识要求有不同的架构,一个看起来更像图 10-2 的架构。

A463052_1_En_10_Fig2_HTML.png

图 10-2

A change in the architecture

在图 10-2 所示的架构中,句子中的每个单词都放在三个输入框中的一个单独的框中。此外,由于“this”进入第一个框,“is”进入第二个框,“an”进入第三个框,因此句子的结构得以保留。

输出“示例”应该在顶部的输出框中。

解读 RNN

我们可以认为 RNN 是一种保存记忆的机制,其中记忆包含在隐藏层中。如图 10-3 所示。

A463052_1_En_10_Fig3_HTML.png

图 10-3

Memory in the hidden layer

图 10-3 中右边的网络是左边网络的展开版本。左边的网络是一个传统的网络,有一个变化:隐藏层在连接到输入的同时连接到自身(隐藏层是图中的圆)。

请注意,当隐藏层与输入层一起连接到自身时,它将连接到隐藏层的“以前版本”和当前输入层。我们可以把这种隐藏层被连接回自身的现象看作是 RNN 创造记忆的机制。

权重 U 表示将输入层连接到隐藏层的权重,权重 W 表示隐藏层到隐藏层的连接,权重 V 表示隐藏层到输出层的连接。

Why Store Memory?

需要存储记忆,因为在前面的例子中和一般的文本生成中,下一个单词不一定依赖于前面的单词,而是依赖于该单词前面的几个单词的上下文来预测。

鉴于我们正在看前面的单词,应该有一种方法将它们保存在内存中,这样我们就可以更准确地预测下一个单词。此外,我们还应该按顺序进行记忆——通常情况下,在预测下一个单词时,较新的单词比距离被预测的单词较远的单词更有用。

RNN 的工作细节

请注意,典型的神经网络有一个输入层,然后在隐藏层激活,然后在输出层激活 softmax。RNN 是相似的,但是有记忆。我们再来看另一个例子:“这是一个例子”。给定一个输入“This”,我们被期望预测“is”,类似地,对于一个输入“is”,我们被期望得出一个“an”的预测和一个作为输入的“an”的“example”的预测。该数据集在 github 中以“RNN 维度直觉. xlsx”的名称提供。

编码的输入和输出字如下:

A463052_1_En_10_Figa_HTML.jpg

RNN 的结构如图 10-4 所示。

A463052_1_En_10_Fig4_HTML.png

图 10-4

The RNN structure

让我们来解构每个相关权重矩阵的维度:

A463052_1_En_10_Figb_HTML.jpg

wxh 随机初始化,维数为 4 × 3。每个输入的尺寸为 1 × 4。因此,隐藏层是输入和 wxh 之间的矩阵乘法,每个输入行的维数为 1 × 3。预期的输出是句子中紧挨着输入单词的单词的一次性编码版本。请注意,最后一个预测“空白”是不准确的,因为我们的预期输出都是 0。理想情况下,我们应该在一键编码版本中有一个新的列来处理所有看不见的单词。然而,为了理解 RNN 的工作细节,我们将保持简单,在预期输出中有 4 列。

正如我们前面看到的,在 RNN,一个隐藏层在展开时会连接到另一个隐藏层。假设一个隐藏层连接到下一个隐藏层,与前一个隐藏层和当前隐藏层之间的连接相关联的权重(whh)在维度上将是 3 × 3,因为 1 × 3 矩阵乘以 3 × 3 矩阵将产生 1 × 3 矩阵。下图中的最终隐藏层计算将在后续页面中解释。

A463052_1_En_10_Figc_HTML.jpg

注意,wxh 和 whh 是随机初始化,而隐藏层和最终隐藏层是计算出来的。在接下来的几页中,我们将看看这些计算是如何完成的。

隐藏层在不同时间步的计算如下:

$$ {h}{(t)}={\phi}_h\left({z}_h{(t)}\right)={\phi}_h\left({W}_{xh}{x}{(t)}+{W}_{hh}{h}{\left(t-1\right)}\right) $$

其中A463052_1_En_10_Figd_HTML.gif是执行的激活(一般为 tanh 激活)。

从输入层到隐藏层的计算包括两个部分:

  • 输入层和 wxh 的矩阵乘法。
  • 隐藏层和 whh 的矩阵乘法。

给定时间步长的隐藏层值的最终计算将是前面两个矩阵乘法的总和,并将结果传递给双曲正切激活函数。

输入层和 wxh 的矩阵乘法如下所示:

A463052_1_En_10_Fige_HTML.jpg

以下部分介绍了不同时间步长下隐藏层值的计算。

时间步长 1

第一个时间步长的隐藏层值将是输入层和 wxh 之间的矩阵乘法的值(因为在前一个时间步长中没有隐藏层值):

A463052_1_En_10_Figf_HTML.jpg

时间步长 2

开始第二次输入,隐藏层由当前时间步的隐藏层分量和来自前一时间步的隐藏层分量组成:

A463052_1_En_10_Figh_HTML.jpg

A463052_1_En_10_Figg_HTML.jpg

时间步长 3

类似地,在第三时间步,输入将是当前时间步的输入和来自前一时间步的隐藏单位值。注意,前一时间步(t-1)中的隐藏单元也受到来自(t-2)的隐藏值的影响。

A463052_1_En_10_Figi_HTML.jpg

类似地,在第四个时间步计算隐藏层值。

现在我们已经计算出了隐藏层,我们让它通过一个激活,就像我们在传统 NN 中做的那样:

A463052_1_En_10_Figj_HTML.jpg

假设对于每个输入,隐藏层激活的输出大小为 1 × 3,为了获得大小为 1 × 4 的输出(因为预期输出“示例”的一次热编码版本的大小为 4 列),隐藏层的大小为什么应该为 3 × 4:

A463052_1_En_10_Figk_HTML.jpg

根据中间输出,我们执行 softmax 激活,如下所示:

A463052_1_En_10_Figl_HTML.jpg

softmax 的第二步是归一化每个像元值以获得概率值:

A463052_1_En_10_Figm_HTML.jpg

一旦获得概率,就可以通过计算预测和实际输出之间的交叉熵损失来计算损失。

最后,我们将以类似于 NN 的方式,通过前向和后向传播时期的组合来最小化损耗。

实现 RNN:简单

要了解 RNN 在 keras 中是如何实现的,我们先通过一个简单化的例子(只了解 RNN 的 keras 实现,然后通过在 Excel 中实现来巩固我们的理解):对两个句子进行分类(这两个句子有三个单词的穷举列表)。通过这个玩具示例,我们应该能够更好地快速理解输出(在 github 中代码为“simpleRNN.ipynb”):

from keras.preprocessing.text import one_hot
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers.recurrent import SimpleRNN
from keras.layers.embeddings import Embedding
from keras.layers import LSTM
import numpy as np

初始化文档并对与这些文档相对应的单词进行编码:

# define documents
docs = ['very good',
             'very bad']
# define class labels
labels = [1,0]
from collections import Counter
counts = Counter()
for i,review in enumerate(docs):
    counts.update(review.split())
words = sorted(counts, key=counts.get, reverse=True)
vocab_size=len(words)
word_to_int = {word: i for i, word in enumerate(words, 1)}
encoded_docs = []
for doc in docs:
    encoded_docs.append([word_to_int[word] for word in doc.split()])

将文档填充到两个单词的最大长度,这是为了保持一致性,以便所有输入都具有相同的大小:

A463052_1_En_10_Fign_HTML.jpg

# pad documents to a max length of 2 words
max_length = 2
padded_docs = pad_sequences(encoded_docs, maxlen=max_length, padding="pre")
print(padded_docs)

编译模型

SimpleRNN 函数的输入形状应为(时间步长数,每个时间步长的要素数)的形式。此外,一般来说,RNN 使用双曲正切作为激活函数。以下代码将输入形状指定为(2,1),因为每个输入都基于两个时间步长,并且每个时间步长只有一列表示它。unroll=True表示我们正在考虑以前的时间步骤:

# define the model
embed_length=1
max_length=2
model = Sequential()
model.add(SimpleRNN(1,activation='tanh', return_sequences=False,recurrent_initializer='Zeros',input_shape=(max_length,embed_length),unroll=True))
model.add(Dense(1, activation="sigmoid"))
# compile the model
model.compile(optimizer='adam', loss="binary_crossentropy", metrics=['acc'])
# summarize the model
print(model.summary())

SimpleRNN (1,)表示隐藏层中有一个神经元。return_sequences为假,因为我们没有返回任何输出序列,而且是单个输出:

A463052_1_En_10_Figo_HTML.jpg

模型编译完成后,让我们继续拟合模型,如下所示:

A463052_1_En_10_Figp_HTML.jpg

model.fit(padded_docs.reshape(2,2,1),np.array(labels).reshape(max_length,1),epochs=500)

注意我们已经重塑了padded_docs。这是因为我们需要在拟合时将训练数据集转换为如下格式:{数据大小,时间步长数,每个时间步长的要素}。此外,标签应该采用数组格式,因为编译模型中的最终密集层需要一个数组。

验证 RNN 的输出

现在我们已经安装好了玩具模型,让我们来验证之前创建的 Excel 计算。请注意,我们将输入作为原始编码{1,2,3 }—实际上,我们绝不会照原样采用原始编码,而是对输入进行一次热编码或创建嵌入。我们在本节中采用原始输入,只是为了比较 keras 的输出和我们将在 Excel 中进行的手工计算。

model.layers指定模型中的层,weights让我们了解与模型相关的层:

A463052_1_En_10_Figq_HTML.jpg

model.weights为我们提供了与模型中的权重相关的名称指示:

A463052_1_En_10_Figr_HTML.jpg

model.get_weights()给出了与模型相关的权重的实际值:

A463052_1_En_10_Figs_HTML.jpg

请注意,权重是有序的,即第一个权重值对应于kernel:0。换句话说,它与 wxh 相同,wxh 是与输入相关联的权重。

recurrent_kernel:0与 whh 相同,whh 是与先前的隐藏层和当前时间步的隐藏层之间的连接相关联的权重。bias:0是与输入相关的偏置。dense_2/kernel:0就是为什么——也就是连接隐藏层和输出的权重。dense_2/bias:0是与隐藏层和输出之间的连接相关的偏差。

让我们验证输入[1,3]的预测:

A463052_1_En_10_Figt_HTML.jpg

padded_docs[0].reshape(1,2,1)

A463052_1_En_10_Figu_HTML.jpg

import numpy as np
model.predict(padded_docs[0].reshape(1,2,1))

假设输入[1,3]的预测值为 0.53199(按此顺序),让我们在 Excel 中进行验证(在 github 中以“简单 RNN 工作验证. xlsx”的形式提供):

A463052_1_En_10_Figv_HTML.jpg

两个时间步长的输入值如下:

A463052_1_En_10_Figw_HTML.jpg

输入和权重之间的矩阵乘法计算如下:

A463052_1_En_10_Figx_HTML.jpg

现在矩阵乘法已经完成,我们将继续计算时间步长 0 中的隐藏层值:

A463052_1_En_10_Figy_HTML.jpg

时间步长 1 中的隐藏层值如下:

tanh(时间步长 1 中的隐藏层值×与隐藏层到隐藏层连接相关的权重(whh) +前一个隐藏层值)

让我们先计算一下双曲正切函数的内部:

A463052_1_En_10_Figz_HTML.jpg

现在我们将计算时间步骤 1 的最终隐藏层值:

A463052_1_En_10_Figaa_HTML.jpg

一旦计算出最终的隐藏层值,它将通过一个 sigmoid 层,因此最终的输出计算如下:

A463052_1_En_10_Figab_HTML.jpg

我们从 Excel 得到的最终输出与从 keras 得到的输出相同,因此是对我们之前看到的公式的验证:

A463052_1_En_10_Figac_HTML.jpg

实现 RNN:文本生成

现在我们已经看到了典型的 RNN 是如何工作的,让我们看看如何使用 keras 为 RNN 提供的 API(在 github 中以“RNN 文本生成. ipynb”的形式提供)来生成文本。

对于这个例子,我们将处理爱丽丝数据集( www.gutenberg.org/ebooks/11 ):

  1. 导入包:

    from keras.models import Sequential
    from keras.layers import Dense,Activation
    from keras.layers.recurrent import SimpleRNN
    import numpy as np
    
    
  2. 读取数据集:

    fin=open('/home/akishore/alice.txt',encoding='utf-8-sig')
    lines=[]
    for line in fin:
      line = line.strip().lower()
      line = line.decode("ascii","ignore")
      if(len(line)==0):
        continue
      lines.append(line)
    fin.close()
    text = " ".join(lines)
    
    
  3. Normalize the file to have only small case and remove punctuation, if any:

    A463052_1_En_10_Figad_HTML.jpg

    text[:100]
    
    
    # Remove punctuations in dataset
    import re
    text = text.lower()
    text = re.sub('[⁰-9a-zA-Z]+',' ',text)
    
    
  4. 对单词进行一次热编码:

    from collections import Counter
    counts = Counter()
    counts.update(text.split())
    words = sorted(counts, key=counts.get, reverse=True)
    chars = words
    total_chars = len(set(chars))
    nb_chars = len(text.split())
    char2index = {word: i for i, word in enumerate(chars)}
    index2char = {i: word for i, word in enumerate(chars)}
    
    
  5. Create the input and target datasets :

    A463052_1_En_10_Figae_HTML.jpg

    SEQLEN = 10
    STEP = 1
    input_chars = []
    label_chars = []
    text2=text.split()
    for i in range(0,nb_chars-SEQLEN,STEP):
        x=text2[i:(i+SEQLEN)]
        y=text2[i+SEQLEN]
        input_chars.append(x)
        label_chars.append(y)
    print(input_chars[0])
    print(label_chars[0])
    
    
  6. Encode the input and output datasets:

    A463052_1_En_10_Figaf_HTML.jpg

    X = np.zeros((len(input_chars), SEQLEN, total_chars), dtype=np.bool)
    y = np.zeros((len(input_chars), total_chars), dtype=np.bool)
    # Create encoded vectors for the input and output values
    for i, input_char in enumerate(input_chars):
        for j, ch in enumerate(input_char):
            X[i, j, char2index[ch]] = 1
        y[i,char2index[label_chars[i]]]=1
    print(X.shape)
    print(y.shape)
    
    

    Note that, the shape of X indicates that we have a total 30,407 rows that have 10 words each, where each of the 10 words is expressed in a 3,028-dimensional space (since there are a total of 3,028 unique words).

  7. Build the model:

    A463052_1_En_10_Figag_HTML.jpg

    HIDDEN_SIZE = 128
    BATCH_SIZE = 128
    NUM_ITERATIONS = 100
    NUM_EPOCHS_PER_ITERATION = 1
    NUM_PREDS_PER_EPOCH = 100
    model = Sequential()
    model.add(SimpleRNN(HIDDEN_SIZE,return_sequences=False,input_shape=(SEQLEN,total_chars),unroll=True))
    model.add(Dense(nb_chars, activation="sigmoid"))
    model.compile(optimizer='rmsprop', loss="categorical_crossentropy")
    model.summary()
    
    
  8. 运行该模型,我们随机生成一个种子文本,并尝试根据给定的种子单词集预测下一个单词:

    for iteration in range(150):
        print("=" * 50)
        print("Iteration #: %d" % (iteration))
        # Fitting the values
        model.fit(X, y, batch_size=BATCH_SIZE, epochs=NUM_EPOCHS_PER_ITERATION)
    
        # Time to see how our predictions fare
        # We are creating a test set from a random location in our dataset
        # In the code below, we are selecting a random input as our seed value of words
        test_idx = np.random.randint(len(input_chars))
        test_chars = input_chars[test_idx]
        print("Generating from seed: %s" % (test_chars))
        print(test_chars)
        # From the seed words, we are tasked to predict the next words
        # In the code below, we are predicting the next 100 words (NUM_PREDS_PER_EPOCH) after the seed words
        for i in range(NUM_PREDS_PER_EPOCH):
            # Pre processing the input data, just like the way we did before training the model
            Xtest = np.zeros((1, SEQLEN, total_chars))
            for i, ch in enumerate(test_chars):
                Xtest[0, i, char2index[ch]] = 1
            # Predict the next word
            pred = model.predict(Xtest, verbose=0)[0]
            # Given that, the predictions are probability values, we take the argmax to fetch the location of highest probability
            # Extract the word belonging to argmax
            ypred = index2char[np.argmax(pred)]
            print(ypred,end=' ')
            # move forward with test_chars + ypred so that we use the original 9 words + prediction for the next prediction
            test_chars = test_chars[1:] + [ypred]
    
    

初始迭代中的输出只是一个词——always!

150 次迭代结束时的输出如下(注意,下面只是部分输出):

A463052_1_En_10_Figah_HTML.jpg

前面的输出几乎没有损失。如果您在执行代码后仔细查看输出,经过一些迭代后,它会重新生成数据集中存在的精确文本—这是一个潜在的过拟合问题。另外,请注意我们输入的形状:大约 30K 个输入,其中有 3028 列。鉴于行与列的比率较低,有可能会过度匹配。随着输入样本数量的增加,这种方法可能会更有效。

拥有大量列的问题可以通过使用嵌入来解决,这与我们计算单词向量的方式非常相似。本质上,嵌入表示一个低得多的维度空间中的单词。

RNN 的嵌入层

为了了解嵌入是如何工作的,让我们来看一个数据集,该数据集试图根据客户推文预测航空公司的客户情绪(代码在 github 中为“RNNsentiment.ipynb”):

  1. As always, import the relevant packages :

    A463052_1_En_10_Figai_HTML.jpg

     #import relevant packages
    from keras.layers import Dense, Activation
    from keras.layers.recurrent import SimpleRNN
    from keras.models import Sequential
    from keras.utils import to_categorical
    from keras.layers.embeddings import Embedding
    from sklearn.cross_validation import train_test_split
    import numpy as np
    import nltk
    from nltk.corpus import stopwords
    import re
    import pandas as pd
    #Let us go ahead and read the dataset:
    t=pd.read_csv('/home/akishore/airline_sentiment.csv')
    t.head()
    
    
    import numpy as np
    t['sentiment']=np.where(t['airline_sentiment']=="positive",1,0)
    
    
  2. 鉴于文本有噪声,我们将通过删除标点符号并将所有单词转换成小写字母来对其进行预处理:

     def preprocess(text):
        text=text.lower()
        text=re.sub('[⁰-9a-zA-Z]+',' ',text)
        words = text.split()
        #words2=[w for w in words if (w not in stop)]
        #words3=[ps.stem(w) for w in words]
        words4=' '.join(words)
        return(words4)
    t['text'] = t['text'].apply(preprocess)
    
    
  3. Similar to how we developed in the previous section, we convert each word into an index value as follows:

    A463052_1_En_10_Figaj_HTML.jpg

    from collections import Counter
    counts = Counter()
    for i,review in enumerate(t['text']):
        counts.update(review.split())
    words = sorted(counts, key=counts.get, reverse=True)
    words[:10]
    
    
    chars = words
    nb_chars = len(words)
    word_to_int = {word: i for i, word in enumerate(words, 1)}
    int_to_word = {i: word for i, word in enumerate(words, 1)}
    word_to_int['the']
    #3
    int_to_word[3]
    #the
    
    
  4. Map each word in a review to its corresponding index :

    A463052_1_En_10_Figak_HTML.jpg

     mapped_reviews = []
    for review in t['text']:
        mapped_reviews.append([word_to_int[word] for word in review.split()])
    t.loc[0:1]['text']
    
    

    A463052_1_En_10_Figal_HTML.jpg

    mapped_reviews[0:2]
    
    

    Note that, the index of virginamerica is the same in both reviews (104).

  5. 初始化长度为 200 的零序列。请注意,我们选择了 200 作为序列长度,因为没有评论超过 200 个单词。此外,以下代码的第二部分确保对于所有小于 200 个单词的评论,所有开始的索引都被零填充,只有最后的索引被填充了与评论中出现的单词相对应的索引:

     sequence_length = 200
    sequences = np.zeros((len(mapped_reviews), sequence_length),dtype=int)
    for i, row in enumerate(mapped_reviews):
        review_arr = np.array(row)
        sequences[i, -len(row):] = review_arr[-sequence_length:]
    
    
  6. 我们进一步将数据集分为训练和测试数据集,如下:

     y=t['sentiment'].values
    X_train, X_test, y_train, y_test = train_test_split(sequences, y, test_size=0.30,random_state=10)
    y_train2 = to_categorical(y_train)
    y_test2 = to_categorical(y_test)
    
    
  7. Once the datasets are in place, we go ahead and create our model, as follows. Note that embedding as a function takes in as input the total number of unique words, the reduced dimension in which we express a given word, and the number of words in an input:

    A463052_1_En_10_Figam_HTML.jpg

     top_words=12679
    embedding_vecor_length=32
    max_review_length=200
    model = Sequential()
    model.add(Embedding(top_words, embedding_vecor_length, input_length=max_review_length))
    model.add(SimpleRNN(1, return_sequences=False,unroll=True))
    model.add(Dense(2, activation="softmax"))
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
    print(model.summary())
    model.fit(X_train, y_train2, validation_data=(X_test, y_test2), epochs=50, batch_size=1024)
    
    

现在让我们看看前面模型的汇总输出。数据集中总共有 12,679 个唯一单词。嵌入层确保我们在 32 维空间中表示每个单词,因此在嵌入层中有 405,728 个参数。

现在我们有 32 个嵌入的维度输入,每个输入现在都连接到一个隐藏层单元——因此有 32 个权重。加上这 32 个砝码,我们就会有偏差。对应于该层的最终权重将是连接先前的隐藏单位值和当前隐藏单位的权重。因此总共有 34 个砝码。

注意,假设有一个来自嵌入层的输出,我们不需要在 SimpleRNN 层中指定输入形状。一旦模型运行,输出分类准确率接近 87%。

传统 RNN 的问题

图 10-5 显示了一个传统的 RNN,它考虑了多个时间步长来进行预测。

A463052_1_En_10_Fig5_HTML.png

图 10-5

An RNN with multiple time steps

请注意,随着时间步长的增加,来自更早层的输入对后面层的输出的影响会小得多。这可以从以下内容中看出(现在,我们将忽略偏差项):

  • h 1 = Wx 1
  • h2= Wx2+Uh1= Wx2+UWx1
  • h3= Wx3+Uh2= Wx3+UWx2+U2Wx1
  • h4= Wx4+Uh3= Wx4+UWX3+U2WX2+U3WX1
  • h5= Wx5+Uh4= Wx5+UWX4+U2WX3+U3WX2+U4WX1

注意,随着时间戳的增加,如果 U > 1,隐藏层的值高度依赖于 X 1 ,如果 U < 1,则稍微依赖于 X 1

消失梯度问题

U 4 相对于 U 的斜率为 4 × U 3 。在这种情况下,请注意,如果 U <为 1,则梯度非常小,因此,如果在更晚的时间步长的输出取决于给定时间步长的输入,则达到理想权重需要很长时间。这导致了一个问题,即在一些句子中,对时间步长中出现得更早的单词存在依赖性。比如“我来自印度。我说一口流利的 ____”在这种情况下,如果我们不考虑第一句话,第二句话的输出“我说流利的 ____”可能是任何语言的名称。因为我们在第一句中提到了国家,我们应该能够将事情缩小到特定于印度的语言。

爆炸梯度的问题

在前面的场景中,如果 U > 1,则梯度增加的量要大得多。这将导致在时间步长中较早出现的输入具有非常高的权重,而在我们试图预测的单词附近出现的输入具有低的权重。

因此,根据 U(隐藏层的权重)的值,权重要么更新得非常快,要么需要很长时间。

鉴于消失/爆炸梯度是一个问题,我们应该以稍微不同的方式处理 rnn。

-什么

长短期记忆(LSTM)是一种架构,有助于克服我们之前看到的消失或爆炸梯度问题。在这一节,我们将看看 LSTM 的建筑,看看它如何帮助克服传统 RNN 的问题。

LSTM 如图 10-6 所示。

A463052_1_En_10_Fig6_HTML.png

图 10-6

LSTM

注意,尽管隐藏层的输入 X 和输出(h)保持不变,但是隐藏层内发生的激活是不同的。不像传统的 RNN,有 tanh 激活,在 LSTM 有不同的激活发生。我们将逐一介绍。

在图 10-7 中,X 和 h 代表输入和隐藏层,正如我们前面看到的。

A463052_1_En_10_Fig7_HTML.png

图 10-7

Various components of LSTM

c 表示单元状态。您可以将单元格状态视为获取长期依赖关系的一种方式。

f 代表了忘门:

$$ {f}_t=\sigma \left({W}_{xf}{x}{(t)}+{W}_{hf}{h}{\left(t-1\right)}+{b}_f\right) $$

请注意,乙状结肠给了我们一种机制来指定需要忘记什么。这样 h(t–1)中捕捉到的一些历史词汇就被选择性遗忘了。

一旦我们发现需要忘记什么,单元格状态就会更新如下:

$$ {c}_t=\left({c}_{t-1}\otimes f\right) $$

注意,A463052_1_En_10_Figan_HTML.jpg代表元素间的乘法。

想象一下,一旦我们填写了“我住在印度”中的空白。我说 ____”有了一个印度语的名字,我们就不再需要“我住在印度”的语境了。这就是“遗忘之门”有助于有选择地遗忘不再需要的信息的地方。

一旦我们弄清楚了单元格状态中需要忘记什么,我们就可以根据当前输入更新单元格状态。

在下一步中,需要更新单元状态的输入通过输入之上的 sigmoid 应用程序实现,更新幅度(正或负)通过 tanh 激活获得。

输入可以指定如下:

$$ {i}_t=\sigma \left({W}_{xi}{x}{(t)}+{W}_{hi}{h}{\left(t-1\right)}+{b}_i\right) $$

调制可以这样指定:

$$ {g}_t=\tanh \left({W}_{xg}{x}{(t)}+{W}_{hg}{h}{\left(t-1\right)}+{b}_g\right) $$

因此,单元状态最终被更新如下:

$$ {C}{(t)}=\left({C}{\left(t-1\right)}\odot {f}_t\right)\oplus \left({i}_t\odot {g}_t\right) $$

在最终的 gate 中,我们需要指定输入和单元格状态组合的哪一部分需要输出到下一个隐藏层:

$$ {o}_t=\sigma \left({W}_{xo}{x}{(t)}+{W}_{ho}{h}{\left(t-1\right)}+{b}_o\right) $$

最后隐藏的图层是这样表示的:

$$ {h}^{(t)}={o}_t\odot \tanh \left({C}^{(t)}\right) $$

鉴于细胞状态可以记住稍后需要的值,LSTM 在预测下一个单词方面提供了比传统 RNN 更好的结果,通常是在情感分类方面。这在有长期依赖关系需要处理的场景中特别有用。

在 keras 实现基本 LSTM

为了了解到目前为止提出的理论如何转化为行动,让我们重新看看我们之前看到的玩具示例(在 github 中代码为“LSTM 玩具示例. ipynb”):

  1. 导入相关包:

    from keras.preprocessing.text import one_hot
    from keras.preprocessing.sequence import pad_sequences
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Flatten
    from keras.layers.recurrent import SimpleRNN
    from keras.layers.embeddings import Embedding
    from keras.layers import LSTM
    import numpy as np
    
    
  2. 定义文件和标签:

    # define documents
    docs = ['very good',
                 'very bad']
    # define class labels
    labels = [1,0]
    
    
  3. One-hot-encode the documents:

    A463052_1_En_10_Figao_HTML.jpg

    from collections import Counter
    counts = Counter()
    for i,review in enumerate(docs):
        counts.update(review.split())
    words = sorted(counts, key=counts.get, reverse=True)
    vocab_size=len(words)
    word_to_int = {word: i for i, word in enumerate(words, 1)}
    encoded_docs = []
    for doc in docs:
        encoded_docs.append([word_to_int[word] for word in doc.split()])
    encoded_docs
    
    
  4. Pad documents to a maximum length of two words :

    A463052_1_En_10_Figap_HTML.jpg

    max_length = 2
    padded_docs = pad_sequences(encoded_docs, maxlen=max_length, padding="pre")
    print(padded_docs)
    
    
  5. Build the model :

    A463052_1_En_10_Figaq_HTML.jpg

    model = Sequential()
    model.add(LSTM(1,activation='tanh', return_sequences=False,recurrent_initializer='Zeros',recurrent_activation='sigmoid',
                   input_shape=(2,1),unroll=True))
    model.add(Dense(1, activation="sigmoid"))
    model.compile(optimizer='adam', loss="binary_crossentropy", metrics=['acc'])
    print(model.summary())
    
    

请注意,在前面的代码中,我们将递归初始化器和递归激活初始化为某些值,只是为了让这个玩具示例在 Excel 中实现时更容易理解。目的是帮助您理解后端发生了什么。

一旦模型如所讨论的那样被初始化,让我们继续拟合模型:

A463052_1_En_10_Figar_HTML.jpg

model.fit(padded_docs.reshape(2,2,1),np.array(labels).reshape(max_length,1),epochs=500)

该模型的各层如下。这里是model.layers:

A463052_1_En_10_Figas_HTML.jpg

权重和权重的顺序可以如下获得:

A463052_1_En_10_Figaw_HTML.jpg

model.layers[1].trainable_weights

A463052_1_En_10_Figav_HTML.jpg

model.layers[1].get_weights()

A463052_1_En_10_Figau_HTML.jpg

model.layers[0].trainable_weights

A463052_1_En_10_Figat_HTML.jpg

model.layers[0].get_weights()

从前面的代码中,我们可以看到,首先获得输入的权重(kernel),然后是对应于隐藏层的权重(recurrent_kernel),最后是 LSTM 层中的偏差。

同样,在dense层(连接隐藏层和输出的层)中,要与隐藏层相乘的权重排在第一位,其次是偏差。

另请注意,权重和偏差在 LSTM 图层中出现的顺序如下:

  1. 输入门
  2. 忘记大门
  3. 调制门(单元门)
  4. 输出门

现在我们有了输出,让我们继续计算输入的预测。注意,就像上一节一样,我们使用原始编码输入(1,2,3)而不对它们进行进一步处理,只是为了看看计算是如何进行的。

在实践中,我们将进一步处理输入,可能将它们编码成向量以获得预测,但在本例中,我们感兴趣的是通过在 Excel 中复制 LSTM 的预测来巩固我们对 LSTM 如何运作的知识:

A463052_1_En_10_Figax_HTML.jpg

padded_docs[1].reshape(1,2,1)

A463052_1_En_10_Figay_HTML.jpg

model.predict(padded_docs[1].reshape(1,2,1))

现在,我们已经从模型中获得了 0.4485 的预测概率,让我们在 Excel(github 中以“LSTM 工作细节. xlsx”的名称提供)中手工计算这些值:

A463052_1_En_10_Figaz_HTML.jpg

注意,这里的值取自 keras 的model.layers[0].get_weights()输出。

在继续计算各个门的值之前,请注意,我们已经将 recurrent layer (h t-1 )的值初始化为 0。在第一个时间步长中,输入值为 1。让我们计算一下各个关口的价值:

A463052_1_En_10_Figba_HTML.jpg

获得上述输出的计算如下:

A463052_1_En_10_Figbb_HTML.jpg

现在已经计算了各个门的所有值,我们将计算输出(隐藏层):

A463052_1_En_10_Figbc_HTML.jpg

刚刚显示的隐藏层值是输入为 1 的时间步长的隐藏层输出。

现在,我们将继续计算输入为 2 时的隐藏层值(这是我们之前在代码中预测的数据点的第二个时间步长的输入):

A463052_1_En_10_Figbd_HTML.jpg

让我们看看如何获得第二个输入的各种门和隐藏层的值。这里需要注意的关键是,第一个时间步长输出的隐藏层是第二个输入中所有门的计算输入:

A463052_1_En_10_Figbe_HTML.jpg

最后,假设我们已经计算了第二时间步的隐藏层输出,我们计算输出如下:

A463052_1_En_10_Figbf_HTML.jpg

前面计算的最终输出如下所示:

A463052_1_En_10_Figbg_HTML.jpg

请注意,我们得到的输出与我们在 keras 输出中看到的相同。

实现用于情感分类的 LSTM

在最后一节中,我们使用 keras 中的 RNN 实现了情感分类。在这一节中,我们将看看如何使用 LSTM 来实现这一点。我们在上面看到的代码中唯一的变化将是模型编译部分,我们将使用 LSTM 代替SimpleRNN——其他一切都将保持不变(代码可在 github 的“RNN 情绪. ipynb”文件中获得):

top_words=nb_chars
embedding_vecor_length=32
max_review_length=200
model = Sequential()
model.add(Embedding(top_words, embedding_vecor_length, input_length=max_review_length))
model.add(LSTM(10))
model.add(Dense(2, activation="softmax"))
model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
print(model.summary())
model.fit(X_train, y_train2, validation_data=(X_test, y_test2), epochs=50, batch_size=1024)

一旦你实现了这个模型,你会发现 LSTM 的预测精度比 RNN 的略高。实际上,对于我们之前看到的数据集,LSTM 给出了 91%的准确率,而 RNN 给出了 87%的准确率。这可以通过调整函数提供的各种超参数来进一步微调。

在 R 中实现 RNN

为了了解如何在 R 中实现 RNN/LSTM,我们将使用随kerasR包一起预构建的 IMDB 情感分类数据集(在 github 中代码为“kerasR_code_RNN.r”):

# Load the dataset
library(kerasR)
imdb <- load_imdb(num_words = 500, maxlen = 100)

注意,我们通过将num_words指定为一个参数,只获取前 500 个单词。我们也只获取那些长度不超过 100 字的 IMDB 评论。

让我们探索一下数据集的结构:

str(imdb)

我们应该注意到,在随kerasR包一起提供的预构建的 IMDB 数据集中,每个单词都被它默认表示的索引所替换。因此我们不必执行单词到索引的映射步骤:

# Build the model with an LSTM
model <- Sequential()

model$add(Embedding(500, 32, input_length = 100, input_shape = c(100)))

model$add(LSTM(32)) # Use SimpleRNN, if we were to perform a RNN function
model$add(Dense(256))
model$add(Activation('relu'))
model$add(Dense(1))
model$add(Activation('sigmoid'))
# Compile and fit the model
keras_compile(model,  loss = 'binary_crossentropy', optimizer = Adam(),metrics='binary_accuracy')
keras_fit(model, X_train, Y_train, batch_size = 1024, epochs = 50, verbose = 1,validation_data = list(X_test,Y_test))

上述结果使测试数据集预测的准确率接近 79%。

摘要

在本章中,您学习了以下内容:

  • rnn 在处理具有时间相关性的数据时非常有用。
  • 在处理数据的长期依赖性时,rnn 面临梯度消失或爆炸的问题。
  • 在这种情况下,LSTM 和其他最新的建筑就派上了用场。
  • LSTM 的工作原理是将信息存储在单元状态中,忘记不再有帮助的信息,根据当前输入选择信息以及需要添加到单元状态的信息量,最后选择需要输出到下一个状态的信息。

十一、聚类

聚类的字典含义是分组。在数据科学中,聚类也是一种无监督的学习技术,有助于对我们的数据点进行分组。

对数据点(行)进行分组的好处包括:

  • 为了让企业用户了解客户中的各种类型的用户
  • 在集群(集团)层面而非整体层面做出商业决策
  • 有助于提高预测的准确性,因为不同的群体表现出不同的行为,因此可以为每个群体建立单独的模型

在本章中,您将学习以下内容:

  • 不同类型的聚类
  • 不同类型的集群如何工作
  • 集群的使用案例

聚类直觉

让我们考虑一个有 4,000 个销售点的零售店的例子。中央规划团队必须对所有门店的店长进行年终评估。评估商店经理的主要指标是商店一年的总销售额。

  • 场景 1:商店经理仅根据销售额进行评估。我们将根据商店的销售额对所有商店经理进行排名,销售额最高的商店经理将获得最高奖励。缺点:这种方法的主要缺点是,我们没有考虑到一些商店位于城市,与农村商店相比,城市的销售额通常很高。城市商店销售额高的最大原因可能是人口多和/或城市消费者的购买力更高。
  • 场景 2:如果我们可以将商店分为城市和农村,或者高购买力客户经常光顾的商店,或者特定人群(比如年轻家庭)经常光顾的商店,并将每个商店称为一个集群,那么只有属于同一个集群的商店经理可以进行比较。例如,如果我们将所有商店分为城市商店和农村商店,那么城市商店的所有商店经理都可以相互比较,农村商店经理也是如此。缺点:虽然我们可以更好地比较不同商店经理的表现,但商店经理仍然会受到不公平的比较。例如,两个城市商店可能不同,一个商店位于上班族经常光顾的中央商务区,另一个位于城市的住宅区。很有可能中央商业区的商店在城市商店中有更高的销售额。

尽管场景 2 仍然有一个缺点,但它并不像场景 1 那样糟糕。因此,将商店分为两类有助于更准确地衡量商店经理的表现。

构建商店集群以进行性能比较

在场景 2 中,我们看到商店经理仍然可以质疑比较过程不公平——因为商店仍然可以在一个或多个参数上有所不同。

商店经理可能会列举出他们的商店与其他商店不同的多种原因:

  • 不同商店出售的产品的差异
  • 光顾商店的顾客年龄组的差异
  • 光顾商店的顾客生活方式的差异

现在,为了简单起见,让我们定义我们所有的组:

  • 城市商店对农村商店
  • 高端产品商店与低端产品商店
  • 高年龄组客户商店与低年龄组客户商店的对比
  • 高端购物者商店与经济型购物者商店

我们可以仅基于列出的因素创建总共 16 个不同的聚类(组)(所有组合的详尽列表产生 16 个组)。

我们讨论了区分商店的四个重要因素,但仍然有许多其他因素可以区分商店。例如,位于该州多雨地区的商店与位于阳光充足地区的商店相比。

本质上,商店在多个维度/因素上彼此不同。但由于某些因素,商店经理的结果可能会有很大差异,而其他因素的影响可能很小。

理想聚类

到目前为止,我们看到每个商店都是独一无二的,可能会有这样一种情况,商店经理总能举出一个或另一个原因来说明为什么他们不能与同一个集群中的其他商店进行比较。例如,商店经理可以说,虽然属于该组的所有商店都是城市商店,大多数高端客户都在中年类别,但他们的商店表现不如其他商店,因为它靠近一直在大力促销的竞争对手商店,因此,与同一组的其他商店相比,该商店的销售额没有那么高。

因此,如果我们把所有的原因都考虑进去,我们最终可能会得到一个细粒度的分段,以至于每个集群中只有一个商店。这将产生一个很好的聚类输出,其中每个商店都是不同的,但是这个结果是没有用的,因为现在我们不能跨商店进行比较。

在没有聚类和太多聚类之间取得平衡:K-means 聚类

我们已经看到,有多少个商店就有多少个分类,这是一个区分每个商店的很好的分类,但这是一个无用的分类,因为我们无法从中得出任何有意义的结果。同时,没有集群也是不好的,因为商店经理可能会与完全不同的商店的商店经理进行不准确的比较。

因此,使用聚类过程,我们努力达到平衡。我们希望尽可能多地确定区分商店的几个因素,只考虑那些用于评估的因素,而忽略其他因素。使用 k-means 聚类可以尽可能多地识别商店之间的少数差异因素。

为了了解 k-means 聚类是如何工作的,让我们考虑一个场景:您是一家披萨连锁店的老板。你有预算在一个社区开三家新店。你如何想出开设三个分店的最佳地点?

现在,我们假设所有道路上的交通流量都是相同的。假设我们的邻居看起来像图 11-1 。

A463052_1_En_11_Fig1_HTML.jpg

图 11-1

Each marker represents a household

理想情况下,我们会想出三个插座,它们彼此相距很远,但总的来说离大多数邻居最近。比如类似图 11-2 的东西。

A463052_1_En_11_Fig2_HTML.jpg

图 11-2

The circles represent potential outlet locations

这看起来不错,但是我们能找到更好的出口位置吗?让我们做一个练习。我们将尝试执行以下操作:

  1. 尽量缩短每家每户到最近的披萨店的距离
  2. 最大化每个披萨店之间的距离

假设一家披萨店只能给两家外卖。如果两家的需求是一致的,那么比萨饼店是正好位于两家之间,还是靠近一家或另一家更好呢?如果送货到房屋 A 需要 15 分钟,送货到房屋 B 需要 45 分钟,凭直觉,我们似乎最好将配送中心设在送货时间为 30 分钟的地方,也就是说,正好在两个房屋之间。如果不是这样,经销店可能经常无法兑现其在 45 分钟内为 B 家送货的承诺,但却永远无法兑现其对 a 家的承诺。

聚类的过程

在目前的情况下,我们如何想出一个更科学的方法来确定正确的比萨饼外卖店?该过程或算法如下:

  1. Randomly come up with three places where we can start outlets, as shown in Figure 11-3.

    A463052_1_En_11_Fig3_HTML.jpg

    图 11-3

    Random locations

  2. Measure the distance of each house to the three outlet locations. The outlet that is closest to the household delivers to the household. The scenario would look like Figure 11-4.

    A463052_1_En_11_Fig4_HTML.jpg

    图 11-4

    Better informed locations

  3. As we saw earlier, we are better off if the delivery outlet is in the middle of households than far away from the majority of the households. Thus, let’s change our earlier planned outlet location to be in the middle of the households (Figure 11-5).

    A463052_1_En_11_Fig5_HTML.jpg

    图 11-5

    Locations in the middle

  4. We see that the delivery outlet locations have changed to be more in the middle of each group. But because of the change in location, there might be some households that are now closer to a different outlet. Let’s reassign households to stores based on their distance to different stores (Figure 11-6).

    A463052_1_En_11_Fig6_HTML.jpg

    图 11-6

    Reassigning households

  5. Now that some households (comparing Figures 11-5 and 11-6) have a different outlet that serves them, let’s recompute the middle point of that group of households (Figure 11-7).

    A463052_1_En_11_Fig7_HTML.jpg

    图 11-7

    Recomputing the middles

  6. 既然聚类中心已经改变,现在又有一个机会,家庭需要重新分配到一个不同的出路,而不是目前的出路。

这些步骤继续进行,直到不再将家庭重新分配到不同的群,或者达到最大的特定迭代次数。

正如你所看到的,事实上我们可以想出一个更科学的/分析性的方法来找到可以打开出口的最佳位置。

K 均值聚类算法的工作细节

我们将开设三家分店,所以我们想出了三组家庭,每组由不同的分店提供服务。

k-means 聚类中的 k 代表我们将在现有数据集中创建的组的数量。在我们在上一节中经历的算法的一些步骤中,一旦一些家庭从一个组改变到另一个组,我们就不断更新中心。我们更新中心的方法是取所有数据点的平均值,因此是 k-means。

最后,在经历了所描述的步骤之后,我们从原始数据集中获得了三组三个数据点或三个聚类。

对数据集应用 K-means 算法

让我们看看如何在数据集上实现 k-means 聚类(在 github 中以“clustering process.xlsx”的形式提供),如下所示:

| X | Y | 串 | | :-- | :-- | :-- | | five | Zero | one | | five | Two | Two | | three | one | one | | Zero | four | Two | | Two | one | one | | four | Two | Two | | Two | Two | one | | Two | three | Two | | one | three | one | | five | four | Two |

假设 X 和 Y 是我们希望聚类所基于的独立变量。假设我们想将这个数据集分成两个集群。

第一步,我们随机初始化聚类。因此,上表中的集群列是随机初始化的。

让我们计算每个集群的质心:

| 图心 | one | Two | | :-- | :-- | :-- | | X | Two point six | Three point two | | Y | One point four | three |

注意,值 2.6 是属于聚类 1 的所有 X 值的平均值。类似地,计算其他中心。

现在让我们计算每个点到两个聚类中心的距离。离数据点最近的聚类中心是数据点应该属于的聚类:

A463052_1_En_11_Figa_HTML.jpg

在 L & M 列中,我们计算了每个点到两个星团中心的距离。通过查看与数据点具有最小距离的聚类来更新列 O 中的聚类中心。

注意到一个数据点的聚类中有一个变化,我们再次继续前面的步骤,但是现在使用更新的中心。对于我们所做的两次迭代,总体计算如下所示:

A463052_1_En_11_Figb_HTML.jpg

我们不断重复这个过程,直到数据点所属的聚类不再发生变化。如果一个数据点簇持续变化,我们可能会在几次迭代后停止。

K-均值聚类算法的性质

如前所述,聚类练习的目标是以满足以下条件的方式创建不同的组:

  1. 属于同一组的所有点尽可能彼此靠近
  2. 每个组的中心尽可能远离另一个组的中心

有一些方法可以帮助评估基于这些目标的聚类输出的质量。

让我们通过一个样本数据集(在 github 中以“clustering output interpretation . xlsx”的名称提供)来巩固我们对这些属性的理解。假设我们有一个数据集如下:两个独立变量(x 和 y)和它们所属的对应聚类(一共四个聚类,只是这个例子):

A463052_1_En_11_Figc_HTML.jpg

四个聚类中心如下:

A463052_1_En_11_Figd_HTML.png

我们计算每个点相对于其对应的聚类中心的距离如下:

A463052_1_En_11_Fige_HTML.jpg

注意 withinss 中的列计算每个点到其对应的聚类中心的距离。让我们来看看用来得出上述结果的公式:

A463052_1_En_11_Figg_HTML.jpg

A463052_1_En_11_Figf_HTML.jpg

总平方和

在原始数据集本身被视为聚类的情况下,原始数据集的中点被视为中心。Totss 是所有点到数据集中心的距离的平方之和。

让我们看看公式:

A463052_1_En_11_Figh_HTML.jpg

总平方和是从单元格 B15 到单元格 C24 的所有值的总和。

聚类中心

每个聚类的聚类中心将是落在同一聚类中的所有点的中点(平均值)。例如,在 Excel 工作表中,聚类中心是在列 I 和 j 中计算的,请注意,这只是属于同一聚类的所有点的平均值。

-再见-再见

Tot.withinss 是所有点到其相应聚类中心的距离平方的总和。

在...之间

betwess 是 totss 和 tot.withinss 的区别。

在 R 中实现 K-means 聚类

R 中的 K-means 聚类是通过使用kmeans函数实现的,如下所示(作为“clustering_code”提供。github 中的 r”)。

# Lets generate dataset randomly
x=runif(1000)
y=runif(1000)

data=cbind(x,y)
# One would have to specify the dataset along with the number of clusters in input
km=kmeans(data,2)

km的输出是前面讨论的主要指标:

A463052_1_En_11_Figi_HTML.jpg

在 Python 中实现 K-means 聚类

Python 中的 K-means 集群是通过scikit-learn库中可用的函数实现的,如下所示(在 github 中可用为“clustering.ipynb”):

# import packages and dataset
import pandas as pd
import numpy as np
data2=pd.read_csv('D:/data.csv')
# fit k-means with 2 clusters
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2)
kmeans.fit(data2)

在这段代码中,我们从名为data2的原始数据集中提取了两个集群。可以通过指定kmeans.labels_来提取每个数据点所属聚类的结果标签。

主要指标的重要性

如前所述,执行聚类的目的是将所有彼此非常接近的数据点归入一个组,并使这些组彼此尽可能远离。

这是另一种说法:

  1. 最小化簇内距离
  2. 最大化集群间距离

让我们看看前面讨论的指标如何帮助实现目标。当没有聚类时(即所有数据集被视为一个聚类),每个点到聚类中心(有一个聚类的地方)的总距离是 totss。当我们在数据集中引入聚类时,聚类内每个点到相应聚类中心的距离之和是 tot.withinss。注意,随着聚类数量的增加,tot.withinss 不断减少。

考虑一种情况,其中聚类的数量等于数据点的数量。在那种情况下 Tot.withinss 等于 0,因为每个点到聚类中心(也就是点本身)的距离是 0。

因此,tot.withinss 是一个衡量簇内距离的指标。tot . withinss/tots 的比率越低,聚类过程的质量越高。

但是我们也需要注意,tot.withinss = 0 的场景就是聚类变得无用的场景,因为每个点本身就是一个聚类。

在下一节中,我们将以稍微不同的方式使用指标 tot.withinss / totss。

确定最佳 K

我们尚未回答的一个主要问题是如何获得最佳 k 值?换句话说,数据集内聚类的最佳数量是多少?

为了回答这个问题,我们将使用上一节中使用的度量标准:tot.withinss / totss 的比率。要查看当我们改变集群数量(k)时度量如何变化,请参见以下代码:

A463052_1_En_11_Figj_HTML.jpg

我们正在用 10,000 个随机初始化的 x 和 y 值创建一个数据集data

现在,当我们改变 k 值时,我们将探究度量的值。该图如图 11-8 所示。

A463052_1_En_11_Fig8_HTML.jpg

图 11-8

Variation in tot.withinss/totss over different values of k

注意,随着 k 值从 k = 1 增加到 k = 2,度量急剧减小,类似地,当 k 从 2 减少到 4 时,度量减小。

然而,随着 k 的进一步降低,度量的值不会降低很多。因此,谨慎的做法是将 k 值保持在接近 7 的水平,因为在这一点上获得了最大的下降,度量(tot.withinss / totss)的任何进一步下降都与 k 的增加没有很好的相关性。

鉴于曲线看起来像一个肘部,它有时被称为肘部曲线。

自顶向下与自底向上聚类

到目前为止,在 k-means 聚类的过程中,我们不知道最佳的聚类数目,所以我们不断尝试具有多个 k 值的各种场景。这是自底向上方法的相对较小的问题之一,其中我们从假设没有聚类开始,慢慢地不断建立多个聚类,一次一个,直到我们根据肘形曲线找到最佳 k。

自顶向下聚类从另一个角度看待同一过程。它假设每个点本身就是一个聚类,并尝试根据点与其他点的距离来组合点。

分层聚类

分层聚类是自顶向下聚类的经典形式。在此过程中,计算每个点到其余点的距离。一旦计算出距离,最接近所考虑的点的点被组合以形成聚类。这个过程在所有点上重复,从而获得最终的聚类。

层次部分来源于我们从一个点开始,与另一个点结合,然后将这个点的结合与第三个点结合,不断重复这个过程。

让我们通过一个例子来看看如何实现层次聚类。假设我们有六个不同的数据点——A、B、C、D、E、f。不同数据点相对于其他点的欧几里德距离如图 11-9 所示。

A463052_1_En_11_Fig9_HTML.png

图 11-9

Distance of data points

我们看到最小距离在 D 和 f 之间。因此,我们将 D 和 f 组合在一起。得到的矩阵现在看起来如图 11-10 所示。

A463052_1_En_11_Fig10_HTML.png

图 11-10

The resulting matrix

我们如何填充图 11-10 中缺失的值?参见下面的等式:

$$ {d}_{\left(D,F\right)\to A}=\min \left({d}_{DA},{d}_{FA}\right)=\min \left(3.61,3.20\right)=3.20 $$

注意,基于前面的计算,我们用 d A 和 FA 之间的最小距离替换{D,F}和 A 之间的距离中的缺失值。同样,我们会用其他缺失值进行估算。我们继续这样进行,直到剩下图 11-11 。

A463052_1_En_11_Fig11_HTML.png

图 11-11

The final matrix

产生的集群现在可以表示为图 11-12 。

A463052_1_En_11_Fig12_HTML.jpg

图 11-12

Representing the cluster

分层聚类的主要缺点

层次聚类的主要缺点之一是需要执行大量的计算。

比方说,如果数据集中有 100 个点,那么第一步是确定最接近点 1 的点,以此类推,进行 99 次计算。第二步,我们需要比较第二个点与其余 98 个点的距离。当有 n 个数据点时,总共需要进行 99 × 100 / 2 或 n×(n–1)/2 次计算,以便识别所有数据点组合中距离最小的数据点组合。

随着数据点的数量从 100 增加到 1,000,000,整个计算变得极其复杂。因此,层次聚类仅适用于小型数据集。

K 均值聚类的行业用例

我们已经使用 tot.withinss / totss 指标的肘形曲线计算了 k 的最佳值。让我们用一个类似的计算来建立一个模型。

假设我们正在使用逻辑回归拟合一个模型来预测交易是否是欺诈性的。假设我们将一起处理所有的数据点,这将转化为一个聚类练习,其中 k = 1 在整个数据集上。假设它有 90%的准确率。现在,让我们使用 k = 2 来拟合相同的逻辑回归,其中我们对每个聚类都有不同的模型。我们将测量在测试数据集上使用两个模型的准确性。

我们通过增加 k 值——也就是说,通过增加聚类的数量——来不断重复这个练习。最佳 k 是指我们有 k 个不同的模型,每个聚类一个,并且在测试数据集上达到最高精度的模型。类似地,我们将使用聚类来理解数据集中出现的各种片段。

摘要

在本章中,您学习了以下内容:

  • K-means 聚类有助于对彼此更相似的数据点进行分组,并以彼此更不相似的方式形成组。
  • 聚类可以成为细分、运筹学和数学建模的重要输入。
  • 层次聚类采用与 k-means 聚类相反的方法来形成聚类。
  • 当数据点的数量很大时,生成分层聚类的计算量更大。

十二、主成分分析

当数据点数量与变量数量的比率较高时,回归通常效果最佳。然而,在一些场景中,例如临床试验,数据点的数量是有限的(考虑到从许多个体中收集样本的难度),并且收集的信息量是高的(想想实验室基于收集的少量血液样本给我们提供了多少信息)。

在这些情况下,数据点与变量的比率较低,由于以下原因,人们在使用传统技术时面临困难:

  • 大多数变量很有可能是相互关联的。
  • 运行回归所需的时间可能非常长,因为需要预测的权重数量很大。

在这种情况下,像主成分分析(PCA)这样的技术可以派上用场。PCA 是一种无监督的学习技术,有助于将多个变量分组为更少的变量,而不会丢失原始变量集的太多信息。

在这一章中,我们将看看 PCA 是如何工作的,并了解执行 PCA 的好处。我们也将用 Python 和 r 实现它。

主成分分析的直觉

PCA 是一种通过使用比原始数据集更少的特征或变量来重构原始数据集的方法。要了解其工作原理,请考虑以下示例:

| Var 部门 | Var 1 | Var 2 | | :-- | :-- | :-- | | Zero | one | Ten | | Zero |   2 |   20 | | Zero | three | Thirty | | Zero | four |   40 | | Zero | five |   50 | | one | six |   60 | | one | seven | Seventy | | one | eight |   80 | | one | nine |   90 | | one | Ten | One hundred |

我们将假设 Var 1 和 Var 2 都是用于预测因变量(Dep Var)的自变量。我们可以看到 Var 2 与 Var 1 高度相关,其中 Var 2 = (10) × Var 1。

图 12-1 显示了它们之间的关系。

A463052_1_En_12_Fig1_HTML.jpg

图 12-1

Plotting the relation

在图中,我们可以清楚地看到变量之间有很强的联系。这意味着独立变量的数量可以减少。

该等式可以表示如下:

Var2 = 10 × Var1

换句话说,不是使用两个不同的独立变量,我们可以只使用一个变量 Var1,它会解决这个问题。

此外,如果我们能够通过稍微不同的角度(或者,我们旋转数据集)来观察这两个变量,如图 12-2 中的箭头所示,我们会看到水平方向上有很多变化,而垂直方向上变化很小。

A463052_1_En_12_Fig2_HTML.jpg

图 12-2

Viewpoint/angle from which data points should be looked at

让我们把数据集变得复杂一点。考虑 v1 和 v2 之间的关系如图 12-3 所示的情况。

A463052_1_En_12_Fig3_HTML.jpg

图 12-3

Plotting two variables

同样,这两个变量彼此高度相关,尽管不像前一种情况那样完美相关。

在这种情况下,第一个主成分是解释数据集中最大方差的线/变量,并且是多个独立变量的线性组合。类似地,第二主成分是与第一主成分完全不相关(相关性接近于 0)的线,它解释了数据集中的其余方差,同时也是多个独立变量的线性组合。

通常,第二主成分是垂直于第一主成分的线(因为下一个最高变化发生在垂直于主成分线的方向上)。

一般来说,数据集的第 n 个主成分垂直于同一数据集的第(n–1)个主成分。

PCA 的工作细节

为了理解 PCA 是如何工作的,让我们看另一个例子(在 github 中以“PCA_2vars.xlsx”的形式提供),其中 x1 和 x2 是两个彼此高度相关的独立变量:

A463052_1_En_12_Figa_HTML.png

假设主成分是变量的线性组合,我们将表示如下:

PC 1 = w×x1+w×2

类似地,第二主分量垂直于原始直线,如下所示:

PC 2 =–w2x 1+w1x 2

权重 w 1 和 w 2 被随机初始化,并且应该被进一步迭代以获得最优的权重。

让我们在求解 w 1 和 w 2 时,重新审视一下我们的目标和约束条件:

  • 目标:最大化 PC1 方差。
  • 约束:主成分的总方差应该等于原始数据集中的总方差(因为数据点没有改变,只是我们观察数据点的角度改变了)。

让我们初始化之前创建的数据集中的主要组件:

A463052_1_En_12_Figb_HTML.jpg

PC1 和 PC2 的公式如下所示:

A463052_1_En_12_Figc_HTML.jpg

既然我们已经初始化了主成分变量,我们将引入目标和约束:

A463052_1_En_12_Figd_HTML.jpg

注意,PC 方差= PC1 方差+ PC2 方差。

原始方差= x1 方差+ x2 方差

我们计算原始方差和 PC 方差之间的差异,因为我们的约束是在主成分变换的数据集中保持与原始数据集相同的方差。以下是他们的公式:

A463052_1_En_12_Fige_HTML.jpg

一旦数据集被初始化,我们将继续识别满足我们的目标和约束的 w 1 和 w 2 的最佳值。

让我们看看如何通过 Excel 的规划求解加载项实现这一点:

A463052_1_En_12_Figf_HTML.jpg

请注意,我们之前指定的目标和标准已经达到:

  • PC1 方差最大化。
  • 原始数据集方差和主成分数据集方差之间几乎没有任何差异。(我们只允许小于 0.01 的小差异,以便 Excel 能够解决它,因为可能存在一些舍入误差。)

请注意,PC1 和 PC2 现在高度不相关,PC1 解释了所有变量中最高的方差。此外,x2 在确定 PC1 时比 x1 具有更高的权重(从导出的权重值可以明显看出)。

实际上,一旦得到主成分,它就以相应的平均值为中心,也就是说,主成分列中的每个值都要减去原始主成分列的平均值:

A463052_1_En_12_Figg_HTML.jpg

用于推导上述数据集的公式如下所示:

A463052_1_En_12_Figh_HTML.jpg

PCA 中的缩放数据

PCA 中的主要预处理步骤之一是缩放变量。考虑以下场景:我们对两个变量执行 PCA。一个变量的取值范围为 0-100,另一个变量的取值范围为 0-1。

假设使用 PCA,我们试图捕获数据集中尽可能多的变化,第一个主成分将给予与低方差变量相比具有最大方差的变量(在我们的情况下,Var1)非常高的权重。

因此,当我们计算出主成分的 w 1 和 w 2 时,我们将最终得到接近 0 的 w 1 和接近 1 的 w 2 (其中 w 2 是 PC1 中对应于较高范围变量的权重)。为了避免这种情况,建议调整每个变量,使它们具有相似的范围,这样方差就可以比较。

将主成分分析扩展到多个变量

到目前为止,我们已经看到建立一个 PCA,其中有两个独立变量。在这一节中,我们将考虑如何在有两个以上独立变量的情况下手工构建 PCA。

考虑以下数据集(在 github 中以“PCA_3vars.xlsx”的形式提供):

A463052_1_En_12_Figi_HTML.png

与两变量 PCA 不同,在二维以上的 PCA 中,我们将以稍微不同的方式初始化权重。权重随机初始化,但采用矩阵形式,如下所示:

A463052_1_En_12_Figj_HTML.png

从这个矩阵我们可以认为 PC1 = 0.49 × x1 + 0.89 × x2 + 0.92 × x3。PC2 和 PC3 的计算方法类似。如果有四个独立变量,我们就会有一个 4 × 4 权重矩阵。

让我们看看我们可能有的目标和约束:

A463052_1_En_12_Figk_HTML.jpg

  • 目标:最大化 PC1 方差。
  • 约束:总体 PC 方差应等于总体原始数据集方差。PC1 方差应大于 PC2 方差,PC1 方差应大于 PC3 方差,PC2 方差应大于 PC3 方差。

求解上述问题将得到满足我们标准的最佳重量组合。请注意,Excel 的输出可能与您在 Python 或 R 中看到的输出略有不同,但与 Excel 的输出相比,Python 或 R 的输出可能具有更高的 PC1 方差,这是由于求解中使用的基础算法。还要注意,尽管理想情况下我们希望原始方差和 PC 方差之间的差值为 0,但出于使用 Excel 规划求解执行优化的实际原因,我们允许差值最大为 3。

类似于具有两个独立变量的 PCA 场景,在处理 PCA 之前缩放输入是一个好主意。此外,请注意,PC1 解释了求解权重后的最大变化,因此 PC2 和 PC3 可以被消除,因为它们对原始数据集变化的解释非常少。

Choosing the Number of Principal Components to Consider

选择主成分的个数没有一个统一的方法。在实践中,一个经验法则是选择最少数量的主成分,这些主成分累计解释数据集中 80%的总方差。

在 R 中实现 PCA

PCA 可以使用内置函数prcomp在 R 中实现。看看下面的实现(在 github 中以“PCA R.R”的形式提供):

t=read.csv('D:/Pro ML book/PCA/pca_3vars.csv')
pca=prcomp(t)
pca

pca的输出如下:

A463052_1_En_12_Figm_HTML.jpg

这里的标准偏差值与 PC 变量的标准偏差值相同。旋转值与我们之前初始化的权重值相同。

使用str(pca)可以获得更详细的输出版本,其输出如下所示:

A463052_1_En_12_Fign_HTML.jpg

由此,我们注意到,除了 PC 变量的标准偏差和权重矩阵之外,pca还提供了转换后的数据集。

我们可以通过指定pca$x来访问转换后的数据集。

在 Python 中实现 PCA

使用scikit learn库在 Python 中实现 PCA,如下所示(在 github 中作为“PCA.ipynb”提供):

# import packages and dataset
import pandas as pd
import numpy as np
from sklearn.decomposition import PCA
data=pd.read_csv('F:/course/pca/pca.csv')
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(data)

我们看到,我们拟合了与自变量数量一样多的成分,并在数据上拟合了 PCA。

数据拟合后,将原始数据转换为转换后的数据,如下所示:

A463052_1_En_12_Figo_HTML.jpg

x_pca = pca.transform(data)

pca.components_

components_与主成分关联的权重相同。x_pca是变换后的数据集。

A463052_1_En_12_Figp_HTML.jpg

print(pca.explained_variance_ratio_)

explained_variance_ratio_提供由每个主成分解释的差异量。这非常类似于 R 中的标准差输出,其中 R 给出了每个主成分的标准差。Python 的scikit learn中的 PCA 对其进行了轻微的转换,并给出了每个变量所解释的原始方差的方差。

将 PCA 应用于 MNIST

MNIST 是一个手写数字识别任务。展开一个 28 × 28 的图像,其中每个像素值用一列表示。基于此,我们可以预测输出是否是 0 到 9 之间的一个数字。

假设总共有 784 列,直观上我们应该观察到以下情况之一:

  • 方差为零的列
  • 差异很小的列
  • 方差大的列

在某种程度上,PCA 帮助我们尽可能地消除低方差和无方差的列,同时仍然用有限数量的列实现相当高的精度。

让我们通过下面的例子来看看如何在不损失太多差异的情况下减少列数。github 中的 r”)。

# Load dataset
t=read.csv("D:/Pro ML book/PCA/train.csv")
# Keep the independent variables only, as PCA is on indep. vars
t$Label = NULL
# scale dataset by 255, as it is the mximum possible value in pixels
t=t/255
# Apply PCA
pca=prcomp(t)
str(pca)
# Check the variance explained
cumsum((pca$sdev)²)/sum(pca$sdev²)

上述代码的输出如下:

A463052_1_En_12_Figq_HTML.jpg

由此我们可以看出,前 43 个主成分解释了原始数据集中约 80%的总方差。我们可以在前 43 个主成分上运行模型,而不是在所有 784 列上运行模型,而不会损失太多信息,因此不会损失太多准确性。

摘要

  • PCA 是一种减少数据集中独立变量数量的方法,尤其适用于数据点与独立变量的比率较低的情况。
  • 在应用主成分分析之前,先对独立变量进行缩放是一个好主意。
  • PCA 变换变量的线性组合,使得结果变量表示变量组合内的最大方差。

十三、推荐系统

我们到处都能看到建议。推荐系统旨在

  • 最小化用户搜索产品的努力
  • 提醒用户他们之前关闭的会话
  • 帮助用户发现更多产品

例如,以下是推荐系统的常见实例:

  • 电子商务网站中的推荐窗口小部件
  • 发送到电子邮件地址的推荐项目
  • 社交网站中朋友/联系人的推荐

想象一个场景,电子商务客户没有得到产品推荐。客户将无法执行以下操作:

  • 识别与他们正在查看的产品相似的产品
  • 了解产品价格是否合理
  • 寻找配件或补充产品

这就是为什么推荐系统通常会大幅提升销售额。

在本章中,您将学习以下内容:

  • 要预测用户对某个商品的评价(或用户购买该商品的可能性),请使用
    • 协同过滤
    • 矩阵分解
  • 欧几里德和余弦相似性度量
  • 如何在 Excel、Python 和 R 中实现推荐算法

推荐系统几乎就像一个朋友。它推断你的喜好,并为你提供个性化的选择。构建推荐系统有多种方式,但目标是将用户与一组其他用户关联起来,将一个项目与一组其他项目关联起来,或者两者结合起来。

鉴于推荐是将一个用户/项目与另一个用户/项目相关联,它转化为 k 个最近邻居的问题:识别非常相似的少数几个,然后基于大多数最近邻居表现出的偏好进行预测。

了解 k 近邻

最近邻是离所考虑的实体最近的实体(在数据集的情况下是数据点)。如果两个实体之间的距离很小,则它们是接近的。

考虑具有以下属性的三个用户:

| 用户 | 重量 | | :-- | :-- | | A | Sixty | | B | Sixty-two | | C | Ninety |

我们可以直观地得出结论,与 c 相比,用户 A 和 B 在权重方面更加相似。

让我们再添加一个用户属性—年龄:

| 用户 | 重量 | 年龄 | | :-- | :-- | :-- | | A | Sixty | Thirty | | B | Sixty-two | Thirty-five | | C | Ninety | Thirty |

用户 A 和 B 之间的“距离”可以测量为:

$$ \sqrt{\left({\left(62-60\right)}²+{\left(35-30\right)}²\right)} $$

这种用户间距离的计算方式类似于两点间距离的计算方式。

然而,在使用多个变量计算距离时,您需要稍微小心一点。以下示例可以突出距离计算的缺陷:

| 汽车模型 | 可达到的最高速度 | 档位数量 | | :-- | :-- | :-- | | A | One hundred | four | | B | One hundred and ten | five | | C | One hundred | five |

在上表中,如果我们使用传统的“距离”指标来衡量汽车之间的相似性,我们可能会得出结论,A 型和 C 型最相似(即使它们的档位数量不同)。然而,凭直觉我们知道,B 和 C 比 A 和 C 更相似,因为它们有相同数量的齿轮,它们的最大可达速度也相似。

这种差异突出了变量规模的问题,其中一个变量与另一个变量相比具有非常高的幅度。为了解决这个问题,我们通常会在进一步计算距离之前对变量进行归一化处理。规范化变量是一个将所有变量统一起来的过程。

标准化变量有多种方法:

  • 将每个变量除以该变量的最大值(取–1 和 1 之间的所有值)
  • 找出变量的每个数据点的 Z 值。z 得分是(数据点的值-变量的平均值)/(变量的标准偏差)。
  • 将每个变量除以该变量的(最大–最小)值(称为最小最大缩放)。

诸如此类的步骤有助于规范化变量,从而防止缩放带来的问题。

一旦获得一个数据点到其他数据点的距离——在推荐系统的情况下,也就是说,一旦识别出与给定项目最近的项目——如果系统得知用户在历史上喜欢大多数最近的邻近项目,那么它将向用户推荐这些项目。

k-nearest neighbors 中的 k 代表在对用户是否喜欢最近邻居进行多数投票时要考虑的最近邻居的数量。例如,如果用户喜欢一个项目的 10 (k)个最近邻居中的 9 个,我们将向用户推荐该项目。类似地,如果用户只喜欢 10 个最近邻中的 1 个,我们不会向用户推荐这个项目(因为喜欢的项目是少数)。

基于邻居的分析考虑了多个用户可以合作帮助预测用户是否喜欢某样东西的方式。

有了这个背景,我们将继续前进,看看推荐系统算法的演变。

基于用户的协同过滤的工作细节

基于用户当然是指以用户为基础的东西。协作意味着利用用户之间的某种关系(相似性)。而过滤是指从所有用户中过滤掉一部分用户。

为了了解基于用户的协同过滤(UBCF),考虑下面的例子(在 github 中以“ubcf.xlsx”的形式提供):

| 用户/电影 | 只是我的运气 | 水中的女士 | 飞机上的蛇 | 超人归来 | 夜间听众 | 你我和杜普利 | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | 克劳迪娅 Puig | three |   | Three point five | four | Four point five | Two point five | | 吉恩·西摩 | One point five | three | Three point five | five | three | Three point five | | 杰克·马修斯 |   | three | four | five | three | Three point five | | 丽莎·罗斯 | three | Two point five | Three point five | Three point five | three | Two point five | | 米克拉斯拉 | Two | three | four | three | three | Two | | 托比 |   |   | Four point five | four |   | one |

假设我们想知道用户 Claudia Puig 对电影《水中女士》的评价。我们将从找出与克劳迪娅最相似的用户开始。用户相似性可以用几种方法计算。以下是计算相似性的两种最常见的方法:

  • 用户之间的欧几里德距离
  • 用户之间的余弦相似度

欧几里德距离

计算 Claudia 与每个其他用户的欧几里德距离的方法如下(可在 github 中“ubcf.xlsx”文件的“欧几里德距离”表中找到):

A463052_1_En_13_Figa_HTML.jpg

由于空间和格式的限制,我们看不到完整的图片,但基本上相同的公式适用于各列。

对于每部电影,每个其他用户到 Claudia 的距离如下:

A463052_1_En_13_Figb_HTML.jpg

注意,总距离值是两个用户评价给定电影的所有距离的平均值。鉴于 Lisa Rose 是与 Claudia 整体距离最小的用户,我们将考虑 Lisa 提供的评分作为 Claudia 很可能给电影《水中女士》的评分。

在这种计算中要考虑的一个主要问题是,一些用户可能是温和的批评者,而一些用户可能是严厉的批评者。用户 A 和 B 可能隐含地具有观看给定电影的相似经历,但是显而易见地,他们的评级可能不同。

对用户进行规范化

考虑到用户的批评程度不同,我们需要确保解决这个问题。标准化在这里会有所帮助。

我们可以为用户规范化如下:

  1. 取给定用户的所有电影的平均评级。
  2. 以每部电影与用户平均评分之间的差异为例。

通过获取单个电影的评级和用户的平均评级之间的差异,我们将知道他们是喜欢一部电影多于他们观看的平均电影、少于他们观看的平均电影还是等于他们观看的平均电影。

让我们看看这是如何做到的:

A463052_1_En_13_Figc_HTML.jpg

上述公式如下(可在 github 中“ubcf.xlsx”文件的“规范化用户”表中找到):

A463052_1_En_13_Figd_HTML.jpg

现在,我们已经对给定用户进行了规范化,我们计算哪个用户与 Claudia 最相似,方法与前面计算用户相似度的方法相同。唯一的区别是,现在我们将基于标准化评级(而不是原始评级)计算距离:

A463052_1_En_13_Fige_HTML.jpg

我们可以看到,Lisa Rose 仍然是与 Claudia Puig 距离最近(或最接近,或最相似)的用户。丽莎对《水中女士》的评分比她电影的平均评分 3.00 低 0.50 个单位,这比她的平均评分低了约 8%。鉴于 Lisa 是与 Claudia 最相似的用户,我们预计 Claudia 的评分同样会比她的平均评分低 8%,计算结果如下:

3.5 × (1 – 0.5 / 3) = 2.91

考虑单一用户的问题

到目前为止,我们已经考虑了与 Claudia 最相似的单个用户。在实践中,越多越好——也就是说,确定 k 个与给定用户最相似的用户给出的加权平均评级比确定最相似用户的评级更好。

但是,我们需要注意的是,并不是所有的 k 用户都是同样相似的。有些比较像,有些不太像。换句话说,一些用户的评级应该被赋予更大的权重,而其他用户的评级应该被赋予更小的权重。但是使用基于距离的度量,没有简单的方法来得出相似性度量。

余弦相似性作为一种度量标准,在解决这个问题上派上了用场。

余弦相似性

我们可以通过一个例子来看余弦相似性。考虑以下矩阵:

|   | 电影 | 电影 2 | 电影 3 | | :-- | :-- | :-- | :-- | | 用户 1 | one | Two | Two | | 用户 2 | Two | four | four |

在上表中,我们看到两个用户的评分高度相关。但是,评级的幅度是有区别的。

如果我们要计算两个用户之间的欧几里德距离,我们会注意到这两个用户彼此非常不同。但是我们可以看到,这两个用户在评分的方向(趋势)上是相似的,虽然在评分的幅度上并不相似。使用用户之间的余弦相似性可以解决用户趋势相似但幅度不相似的问题。

两个用户之间的余弦相似度定义如下:

$$ similarity= \cos \left(\theta \right)=\frac{A\cdot B}{{\left\Vert A\right\Vert}_2;{\left\Vert B\right\Vert}_2}=\frac{{\displaystyle \sum_{i=1}^n{A}_i{B}_i}}{\sqrt{{\displaystyle \sum_{i=1}^n{A_i}²}};\sqrt{{\displaystyle \sum_{i=1}^n{B_i}²}}} $$

a 和 B 分别是对应于用户 1 和用户 2 的向量。

让我们看看如何计算前面矩阵的相似性:

  • 给定公式的分子= (1 × 2 + 2 × 4 + 2 × 4) = 18

  • Denominator of the given formula =

    $$ \sqrt{\left({1}²+{2}²+{2}²\right)} $$

    ×

    $$ \sqrt{\left({2}²+{4}²+{4}²\right)}=\sqrt{(9)}\times \sqrt{(36)}=3\times 6=18 $$

  • 相似度= 18 / 18 = 1。

基于给定的公式,我们可以看到,基于余弦相似性,我们能够将高相似性分配给方向相关但不一定在幅度上相关的用户。

我们之前最初计算(在欧几里德距离计算中)的评级矩阵上的余弦相似性将以与我们计算前述公式类似的方式进行计算。余弦相似度计算的步骤保持不变:

  1. 正常化用户。
  2. 计算给定用户的其余用户的余弦相似度。

为了说明我们如何计算余弦相似度,让我们计算 Claudia 与其他每个用户的相似度(可在 github 中“ubcf.xlsx”文件的“余弦相似度”表中找到):

  1. Normalize user ratings:

    A463052_1_En_13_Figf_HTML.jpg

  2. Calculate the numerator part of the cosine similarity calculation:

    A463052_1_En_13_Figg_HTML.jpg

    The numerator would be as follows:

    A463052_1_En_13_Figh_HTML.jpg

  3. Prepare the denominator calculator of cosine similarity:

    A463052_1_En_13_Figi_HTML.jpg

  4. Calculate the final cosine similarity, as follows:

    A463052_1_En_13_Figj_HTML.jpg

我们现在有了一个介于–1 和+1 之间的相似性值,它给出了给定用户的相似性得分。

我们现在已经克服了在预测给定用户可能给电影的评级时必须考虑多个用户给出的评级所面临的问题。现在可以计算与给定用户更相似的用户。

现在,预测克劳迪娅可能给水电影中的电影女士的评级的问题可以通过以下步骤来解决:

  1. 正常化用户。
  2. 计算余弦相似度。
  3. 计算加权平均标准化评级。

假设我们试图通过使用两个最相似的用户而不是一个来预测评级。我们将遵循以下步骤:

  1. 找出两个最相似的用户,他们也对电影《水中女士》进行了评级。
  2. 计算他们给电影的加权平均标准化评分。

在这种情况下,Lisa 和 Mick 是与 Claudia 最相似的两个用户,他们在水中评价 Lady。(注意,即使 Toby 是最相似的用户,他也没有对水中的女士进行评级,因此我们不能考虑将他用于评级预测。)

加权平均评级计算

让我们来看看给定的标准化评分和两个最相似用户的相似性:

|   | 类似 | 标准化评级 | | :-- | :-- | :-- | | 丽莎·罗斯 | Zero point four seven | –0.5 | | 米克拉斯拉 | Zero point five six | Zero point one seven |

加权平均评级现在如下:

(0.47 × –0.5 + 0.56 × 0.17) / (0.47 + 0.56) = –0.14

潜在地,克劳迪娅的平均评分现在将减少 0.14,以得出电影《水中女士》中克劳迪娅的预测评分。

得出加权平均评分的另一种方法是基于平均评分的百分比,如下所示:

|   | 类似 | 标准化评级 | 平均分 | 平均评级百分比 | | :-- | :-- | :-- | :-- | :-- | | 丽莎·罗斯 | Zero point four seven | –0.5 | three | –0.5 / 3 = –0.16 | | 米克拉斯拉 | Zero point five six | Zero point one seven | Two point eight three | 0.17 / 2.83 = 0.06 |

加权平均标准化评级百分比现在如下:

(0.47 × –0.16 + 0.56 × 0.06) / (0.47 + 0.56) = –0.04

因此,克劳迪娅的平均评级可能会降低 4%,以得出电影《水中女士》的预测评级。

选择正确的方法

在推荐系统中,没有固定的技术被证明总是有效的。这需要一个典型的训练、验证和测试场景来得出最佳的参数组合。

可以测试的参数组合如下:

  • 要考虑的相似用户的最佳数量
  • 在用户有资格被考虑用于类似的用户计算之前,由用户一起评级的共同电影的最佳数量
  • 加权平均评级计算方法(基于百分比或绝对值)

我们可以遍历参数的各种组合的多个场景,计算测试数据集的准确性,并决定给出最小错误率的组合是给定数据集的最佳组合。

计算误差

有多种计算方法,首选方法因业务应用而异。让我们看两个案例:

  • 对测试数据集进行的所有预测的均方误差(MSE)
  • 用户在下次购买时购买的推荐商品数量

请注意,虽然 MSE 有助于构建算法,但在实践中,我们可能会将模型的性能作为与业务相关的结果来衡量,如第二种情况。

与 UBCF 的问题

基于用户的协同过滤的一个问题是,每个用户必须与每个其他用户进行比较,以识别最相似的用户。假设有 100 个客户,这意味着第一个用户与 99 个用户进行比较,第二个用户与 98 个用户进行比较,第三个用户与 97 个用户进行比较,依此类推。这里的总比较如下:

99 + 98 + 97 + … + 1 + 0 = 99 × (99 + 1) / 2 = 4950

对于一百万个客户,比较的总数如下所示:

999,999 × 1,000,000 / 2 = ~500,000,000,000

大约有 5000 亿次比较。计算表明,随着客户数量的增加,识别最相似客户的比较次数呈指数增加。在生产中,这成为一个问题,因为如果每个用户与每个其他用户的相似性需要每天计算(因为用户偏好和评级每天都根据最新的用户数据更新),那么每天需要执行大约 5000 亿次比较。

为了解决这个问题,我们可以考虑基于项目的协同过滤,而不是基于用户的协同过滤。

基于项目的协同过滤

考虑到计算的数量在 UBCF 是一个问题,我们将修改这个问题,以便我们观察项目之间的相似性,而不是用户。基于项目的协同过滤(IBCF)背后的思想是,如果两个项目从相同的用户那里得到的评级是相似的,则这两个项目是相似的。鉴于 IBCF 是基于项目而不是用户相似性,它不存在执行数十亿次计算的问题。

让我们假设一个数据库中总共有 10,000 部电影,该网站吸引了 100 万客户。在这种情况下,如果我们执行 UBCF,我们将执行大约 5000 亿次相似性计算。但是使用 IBCF,我们将执行 9999×5000 = ~ 5000 万次相似性计算。

我们可以看到,随着客户数量的增长,相似性计算的数量呈指数增长。然而,考虑到项目数量(在我们的例子中是电影名称)预计不会经历与客户数量相同的增长率,一般来说,IBCF 的计算敏感度低于 UBCF。

IBCF 的计算方式和涉及的技术与 UBCF 非常相似。唯一的区别是,我们将处理前一节中看到的原始电影矩阵的转置形式。这样,这些行不是用户的,而是电影的。

注意,虽然 IBCF 在计算方面比 UBCF 好,但计算量仍然很高。

在 R 中实现协同过滤

在这一节中,我们将查看用于在 r 中实现 UBCF 的函数。我在下面的代码中实现了在recommenderlab包中可用的函数,但在实践中,建议您从头构建一个推荐函数,以便为手头的问题进行定制(代码可作为“UBCF”获得。github 中的 r”)。

# Import data and required packages
t=read.csv("D:/book/Recommender systems/movie_rating.csv")
library(reshape2)
library(recommenderlab)
# Reshape data into a pivot format
t2=acast(t,critic~title)
t2
# Convert it to a matrix
R<-as.matrix(t2)

# Convert R into realRatingMatrix structure
# realRatingMatrix is a recommenderlab sparse-matrix like data structure

r<-as(R,"realRatingMatrix")

# Implement the UBCF method
rec=Recommender(r[1:nrow(r)],method="UBCF")

# Predict the missing rating
recom<-predict(rec,r[1:nrow(r)],type="ratings")
str(recom)

在这段代码中,我们对数据进行了整形,以便将其转换成一个由Recommender函数使用的realRatingMatrix类,从而提供缺失值预测。

用 Python 实现协同过滤

我们在 R 中使用了一个预测包,但对于 Python,我们将手工构建一种方法来预测用户可能给出的评级。在下面的代码中,我们将通过仅考虑与 Claudia 最相似的用户(代码在 github 中为“UBCF.ipynb ”)来创建一种方法,以预测 Claudia 可能对水电影中的女士给出的评级。

  1. 导入数据集:

    import pandas as pd
    import numpy as np
    t=pd.read_csv("D:/book/Recommender systems/movie_rating.csv")
    
    
  2. 将数据集转换成数据透视表:

    t2 = pd.pivot_table(t,values='rating',index='critic',columns='title')
    
    
  3. 重置索引:

    t3 = t2.reset_index()
    t3=t3.drop(['critic'],axis=1)
    
    
  4. 归一化数据集:

    t4=t3.subtract(np.mean(t3,axis=1),axis=0)
    
    
  5. 删除缺少水中女士值的行:

    t5=t4.loc[t4['Lady in the Water'].dropna(axis=0).index]
    t6=t5.reset_index()
    t7=t6.drop(['index'],axis=1)
    
    
  6. 计算每个其他用户到克劳迪娅的距离:

    x=[]
    for i in range(t7.shape[0]):
        x.append(np.mean(np.square(t4.loc[0]-t7.loc[i])))
    t6.loc[np.argmin(x)]['Lady in the Water']
    
    
  7. 计算克劳迪娅的预测评分:

    np.mean(t3.loc[0]) * (1+(t6.loc[np.argmin(x)]['Lady in the Water']/np.mean(t3.loc[3])))
    
    

矩阵分解的工作细节

尽管基于用户或基于项目的协同过滤方法简单而直观,但矩阵分解技术通常更有效,因为它们允许我们发现用户和项目之间交互的潜在特征。

在矩阵分解中,如果有 U 个用户,每个用户被表示在 K 列中,因此我们有一个 U × K 用户矩阵。同样,如果有 D 项,每一项也用 K 列表示,给我们一个 D × K 的矩阵。

用户矩阵的矩阵乘法和项目矩阵的转置将产生 U × D 矩阵,其中 U 个用户可能已经对 D 个项目中的一些进行了评级。

这 K 列基本上可以转化为 K 个特征,其中一个或另一个特征中较高或较低的幅度可以给我们一个项目类型或流派的指示。这使我们能够知道用户会给哪些功能更高的权重,或者用户可能不喜欢哪些功能。本质上,矩阵分解是一种以这样的方式来表示用户和项目的方式,即如果对应于项目的特征是用户给予较高权重的特征,则用户喜欢或购买项目的概率很高。

我们将通过一个例子来看看矩阵分解是如何工作的。让我们假设我们有一个用户(U)和电影(D)的矩阵,如下(数据集在 github 中以“matrix factorization example . xlsx”的形式提供):

| 用户 | 电影 | 实际的 | | :-- | :-- | :-- | | one | one | five | | one | Two | three | | one | three |   | | one | four | one | | Two | one | four | | Two | Two |   | | Two | three |   | | Two | four | one | | three | one | one | | three | Two | one | | three | three |   | | three | four | five | | four | one | one | | four | Two |   | | four | three |   | | four | four | four | | five | one |   | | five | Two | one | | five | three | five | | five | four | four |

我们的任务是预测实际列中缺少的值,这些值表明用户还没有对电影进行评级。

在这种情况下,矩阵分解的数学计算如下:

  • 目的:改变 P 和 Q 矩阵的随机初始值,以最小化总误差。
  • 约束:任何预测都不能大于 5 或小于 1。
  1. Initialize the values of P matrix randomly, where P is a U × K matrix. We’ll assume a value of k = 2 for this example. A better way of randomly initializing the values is by limiting the values to be between 0 and 1. In this scenario, the matrix of P will be a 5 × 2 matrix, because k = 2 and there are 5 users:

    A463052_1_En_13_Figk_HTML.jpg

  2. Initialize the values of Q matrix randomly, again where Q is a K × D matrix—that is, a 2 × 4 matrix, because there are four movies, as shown in the first table. The Q matrix would be as follows:

    A463052_1_En_13_Figl_HTML.jpg

  3. Calculate the value of the matrix multiplication of P × Q matrix. Note that the Prediction column in the following is calculated by the matrix multiplication of P matrix and Q matrix (I will discuss the Constraint column in the next step):

    A463052_1_En_13_Figm_HTML.jpg

  4. Specify the optimization constraints. The predicted value (the multiplication of each element of the two matrices) should ideally be equal to the ratings of the big matrix. The error calculation is based on the typical squared error calculation and is done as follows (note that the weight values in P and matrices have varied because they are random numbers and are initialized using the randbetween function, which changes values every time Enter is pressed in Excel):

    A463052_1_En_13_Fign_HTML.jpg

上述目标和约束可以在规划求解中指定为优化方案,如下所示:

A463052_1_En_13_Figo_HTML.jpg

注意,一旦我们针对给定的目标和约束进行了优化,P 和 Q 矩阵中的权重的最优值就被得出,并且如下:

A463052_1_En_13_Figp_HTML.jpg

Insights on P AND Q MATRICES

在 P 矩阵中,用户 1 和用户 2 对于因子 1 和 2 具有相似的权重,因此他们可能被认为是相似的用户。

此外,用户 1 和用户 2 对电影进行评级的方式非常相似——用户 1 评级高的电影也具有来自用户 2 的高评级。类似地,用户 1 评价差的电影也具有来自用户 2 的低评价。

这同样适用于对 Q 矩阵(电影矩阵)的解释。电影 1 和电影 4 之间有相当大的距离。我们还可以看到,对于大多数用户来说,如果电影 1 的评分很高,那么电影 4 的评分就很低,反之亦然。

用 Python 实现矩阵分解

请注意,P 矩阵和 Q 矩阵是通过 Excel 的求解器获得的,它实际上是在后端运行梯度下降。换句话说,我们正在以一种类似于基于神经网络的方法来推导权重,在这种方法中,我们试图最小化总体平方误差。

让我们看看在 keras 中为以下数据集实现矩阵分解(代码在 github 中以“matrix factorization.ipynb”的形式提供):

| 用户 | 电影 | 实际的 | | :-- | :-- | :-- | | one | four | one | | Two | four | one | | three | one | one | | three | Two | one | | four | one | one | | five | Two | one | | one | Two | three | | Two | one | four | | four | four | four | | five | four | four | | one | one | five | | three | four | five | | five | three | five |
# Import the required packages and dataset
import pandas as pd
ratings= pd.read_csv('/content/datalab/matrix_factorization_keras.csv')
# Extract the unique users
users = ratings.User.unique()
# Extract the unique movies
articles = ratings.Movies.unique()
# Index each user and article
userid2idx = {o:i for i,o in enumerate(users)}
articlesid2idx = {o:i for i,o in enumerate(articles)}
# Apply the index created to the original dataset
ratings.Movies = ratings.Movies.apply(lambda x: articlesid2idx[x])
ratings.User = ratings.User.apply(lambda x: userid2idx[x])
# Extract the number of unique users and articles
n_users = ratings.User.nunique()
n_articles = ratings.Movies.nunique()
# Define the error metric
import keras.backend as K
def rmse(y_true,y_pred):
    score = K.sqrt(K.mean(K.pow(y_true - y_pred, 2)))
    return score
# Import relevant packages
from keras.layers import Input, Embedding, Dense, Dropout, merge, Flatten
from keras.models import Model

函数Embedding有助于创建向量,类似于我们在第八章中把一个单词转换成低维向量的方法。

通过下面的代码,我们将能够创建 P 矩阵和 Q 矩阵的初始化:

def embedding_input(name,n_in,n_out):
    inp = Input(shape=(1,),dtype='int64',name=name)
    return inp, Embedding(n_in,n_out,input_length=1)(inp)
n_factors = 2
user_in, u = embedding_input('user_in', n_users, n_factors)
article_in, a = embedding_input('article_in', n_articles, n_factors)
# Initialize the dot product between user matrix and movie matrix
x = merge.dot([u,a],axes=2)
x=Flatten()(x)
# Initialize the model specification
from keras import optimizers
model = Model([user_in,article_in],x)
sgd = optimizers.SGD(lr=0.01)
model.compile(sgd,loss='mse',metrics=[rmse])
model.summary()
# Fit the model by specifying inputs and output
model.fit([ratings.User,ratings.Movies], ratings.Actual, nb_epoch=1000, batch_size=13)

现在模型已经建立,让我们提取用户和电影矩阵(P 和 Q 矩阵)的权重:

A463052_1_En_13_Figq_HTML.jpg

# User matrix
model.get_weights()[0]

A463052_1_En_13_Figr_HTML.jpg

# Movie matrix
model.get_weights()[1]

在 R 中实现矩阵分解

虽然矩阵分解可以使用kerasR包来实现,但我们将使用recommenderlab包(与我们用于协同过滤的包相同)。

下面的代码实现了 R 中的矩阵分解(可作为“矩阵分解”使用。github 中的 r”)。

  1. 导入相关包和数据集:

    # Matrix factorization
    t=read.csv("D:/book/Recommender systems/movie_rating.csv")
    library(reshape2)
    library(recommenderlab)
    
    
  2. 数据预处理:

    t2=acast(t,critic~title)
    t2
    # Convert it as a matrix
    R<-as.matrix(t2)
    # Convert R into realRatingMatrix data structure
    # RealRatingMatrix is a recommenderlab sparse-matrix like data-structure
    r <- as(R, "realRatingMatrix")
    
    
  3. Use the funkSVD function to build the matrix factors:

    fsvd <- funkSVD(r, k=2,verbose = TRUE)
    
    p <- predict(fsvd, r, verbose = TRUE)
    p
    
    

    Note that the object p constitutes the predicted ratings of all the movies across all the users. The object fsvd constitutes the user and item matrices , and they can be obtained with the following code:

    A463052_1_En_13_Figs_HTML.jpg

    str(fsvd)
    
    

因此,用户矩阵可通过fsvd$U访问,项目矩阵可通过fsvd$V访问。这些参数是我们在第七章中了解到的学习率和纪元参数。

摘要

在本章中,您学习了以下内容:

  • 用于提供推荐的主要技术是协同过滤和矩阵分解。
  • 就大量的计算而言,协同过滤是非常禁止的。
  • 矩阵分解的计算量较小,并且通常提供更好的结果。
  • 在 Excel、Python 和 R 中构建矩阵分解和协作过滤算法的方法

十四、在云中实现算法

有时,执行一项任务所需的计算量可能非常大。当有一个大型数据集的大小大于机器的典型 RAM 大小时,通常会发生这种情况。当需要对数据进行大量处理时,通常也会发生这种情况。

在这种情况下,切换到基于云的分析是一个好主意,这有助于快速扩展更大的 RAM。它还可以避免购买扩展 RAM 来解决可能不会经常出现的问题。然而,使用云服务是有成本的,某些配置比其他配置更昂贵。您需要在选择配置时小心谨慎,并且遵守纪律,以便知道何时停止使用云服务。

三大云提供商如下:

  • 谷歌云平台(GCP)
  • 微软 Azure
  • 亚马逊网络服务(AWS)

在本章中,我们将致力于在所有三个云平台中设置一个虚拟机。一旦实例设置完毕,我们将学习如何访问 Python 和 R on cloud。

谷歌云平台

可以在 https://cloud.google.com 访问 GCP。建立帐户后,您可以使用控制台创建项目。在控制台中,点击计算引擎,然后点击虚拟机实例,如图 14-1 (VM 代表虚拟机)。

A463052_1_En_14_Fig1_HTML.jpg

图 14-1

Selecting the VM option

单击创建以创建新的虚拟机实例。您将会看到如图 14-2 所示的屏幕。

A463052_1_En_14_Fig2_HTML.jpg

图 14-2

Options to create an instance

根据数据集的大小,使用所需的内核、内存以及是否需要 GPU 来自定义机器类型。

接下来,选择您选择的操作系统(图 14-3 )。

A463052_1_En_14_Fig3_HTML.jpg

图 14-3

Selecting the OS

我们将对 PuTTY 执行一些操作。可以从 www.ssh.com/ssh/putty/windows/puttygen 下载 PuTTYgen。打开程序并单击生成以生成公钥/私钥对。将生成一个密钥,如图 14-4 所示。

A463052_1_En_14_Fig4_HTML.jpg

图 14-4

Generating a public/private key pair in PuTTYgen

单击“保存私钥”保存私钥。复制顶部的公钥,粘贴到 GCP 上的 SSH 密钥框中,如图 14-5 所示。

A463052_1_En_14_Fig5_HTML.jpg

图 14-5

Pasting in the key

单击创建。这应该会为您创建一个新实例。它还应该给你对应于实例的 IP 地址。复制 IP 地址并将其粘贴到 PuTTY 中的主机名下。

在左窗格中点击 SSH,点击 Auth,浏览到保存私钥的位置,如图 14-6 所示。

A463052_1_En_14_Fig6_HTML.jpg

图 14-6

The Auth options

在图 14-4 中生成公钥和私钥时,输入登录名作为 PuTTYgen 中显示的“密钥注释”条目。你现在应该登录到谷歌云机器。

输入 python,你应该能运行 Python 脚本了。

务必在完成工作后立即删除该实例。否则,服务可能仍然会向您收费。

微软 Azure 云平台

在微软 Azure 中创建虚拟机实例与在 GCP 的方式非常相似。访问 https://azure.microsoft.com 并设立账户。

在 Azure 中创建一个帐户并登录。然后点击“虚拟机”,如图 14-7 所示。

A463052_1_En_14_Fig7_HTML.jpg

图 14-7

Microsoft’s Virtual machines page

单击添加,然后执行以下操作:

  1. 选择所需的机器——在我们的例子中,我选择的是 Ubuntu Server 16.04 LTS 版。
  2. 单击默认的创建按钮。
  3. 在基本配置设置中输入机器级别的详细信息。
  4. 选择所需虚拟机的大小。
  5. 配置可选功能。
  6. 最后,创建实例。

一旦实例被创建,仪表板提供对应于实例的 IP 地址,如图 14-8 所示。

A463052_1_En_14_Fig8_HTML.jpg

图 14-8

The IP address you need

打开 PuTTY(有关下载和启动它的更多信息,请参见上一节)并使用 IP 地址连接到实例,方法是输入密码(如果在创建实例时选择了密码选项)或使用私钥。

您可以连接到实例,并使用 PuTTY 打开 Python,方法与我们在上一节 GCP 中所做的类似。

亚马逊网络服务

在这一部分,我们将注册 Amazon Web Services。进入 https://aws.amazon.com 并创建账户。

点击“启动虚拟机”,如图 14-9 所示。

A463052_1_En_14_Fig9_HTML.jpg

图 14-9

Launching a virtual machine in AWS

在下一个屏幕上,单击“开始”命名实例并选择所需的属性,如图 14-10 所示。

A463052_1_En_14_Fig10_HTML.jpg

图 14-10

Setting up your instance

下载。pem 文件,然后单击“创建此实例”然后点击“进入控制台”

与此同时,

A463052_1_En_14_Fig11_HTML.jpg

图 14-11

Saving the private key

  1. 打开 PuTTYgen。
  2. 加载。pem 文件。
  3. 将其转换为. ppk 文件。
  4. 保存私钥,如图 14-11 所示。

回到 AWS 控制台,屏幕如图 14-12 所示。

A463052_1_En_14_Fig12_HTML.jpg

图 14-12

The AWS console

单击连接按钮。注意弹出窗口中给出的例子(图 14-13 )。

A463052_1_En_14_Fig13_HTML.jpg

图 14-13

The example

在图 14-13 的高亮部分,@后面的字符串是主机名。复制它,打开 PuTTY,将主机名粘贴到 PuTTY 配置界面的主机名框中,如图 14-14 所示。

A463052_1_En_14_Fig14_HTML.jpg

图 14-14

Adding the host name

回到图 14-13 ,刚好在@之前的单词是用户名。在左侧面板中选择“数据”后,在 PuTTY 的“自动登录用户名”框中输入,如图 14-15 所示。

A463052_1_En_14_Fig15_HTML.jpg

图 14-15

Adding the username

单击 SSH 将其展开,单击 Auth,然后浏览到。先前创建的 ppk 文件。点击打开,如图 14-16 所示。

A463052_1_En_14_Fig16_HTML.jpg

图 14-16

Setting the private key

现在,您应该能够在 AWS 上运行 Python 了。

将文件传输到云实例

您可以使用 WinSCP 将文件从本地机器传输到所有三个平台中的云实例。如果你还没有安装,从 www.winscp.net 下载 WinSCP 并安装。打开 WinSCP,您应该会看到类似于图 14-17 的登录屏幕。

A463052_1_En_14_Fig17_HTML.jpg

图 14-17

The WinSCP login screen

输入主机名和用户名,类似于您在 PuTTY 中输入的方式。为了进入。ppk 文件详细信息,单击高级按钮。

单击 SSH 部分中的身份验证,并提供的位置。ppk 文件,如图 14-18 所示。

A463052_1_En_14_Fig18_HTML.jpg

图 14-18

Setting the private key

单击确定,然后单击登录。

现在,您应该能够将文件从本地机器传输到虚拟实例。

另一种传输文件的方法是将文件上传到其他云存储中(例如 Dropbox),获取文件位置的链接,并将其下载到虚拟实例中。

从本地机器运行 jupiter 笔记本实例

通过在 GCP、AWS 或 Azure 的 Linux 实例上运行以下代码,您可以从本地机器上运行 Jupyter 笔记本:

sudo su
wget http://repo.continuum.io/archive/Anaconda3-4.1.1-Linux-x86_64.sh
bash Anaconda3-4.1.1-Linux-x86_64.sh
jupyter notebook --generate-config
vi jupyter_notebook_config.py

通过按 I 键插入以下代码:

c = get_config()
c.NotebookApp.ip = '*';
c.NotebookApp.open_browser = False
c.NotebookApp.port = 5000

按 Escape 键,键入:wq,然后按 Enter 键。

键入以下内容:

sudo su
jupyter-notebook --no-browser --port=5000

Jupyter 笔记本打开后,转到本地机器上的浏览器,在地址栏中键入虚拟实例的 IP 地址和端口号(确保防火墙规则配置为打开端口 5000)。例如,如果 IP 地址是http://35.188.168.71,那么在屏幕顶部的浏览器地址栏中输入http://35.188.168.71:5000

您应该能够在连接到虚拟实例的本地机器上看到 Jupyter 环境(图 14-19 )。

A463052_1_En_14_Fig19_HTML.jpg

图 14-19

The Jupyter environment

在实例上安装 R

默认情况下,r 不会安装在实例上。您可以在 Linux 中安装 R,如下所示:

sudo apt-get update
sudo apt-get install r-base

现在在您的终端中键入 R:

R

现在,您应该能够在虚拟实例中运行 R 代码了。

摘要

在本章中,您学习了以下内容:

  • 如何在三大云平台上建立和打开虚拟实例
  • 如何在三个平台上运行 Python/R
  • 如何将文件传输到云环境中
posted @   绝不原创的飞龙  阅读(118)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示