Sklearn-秘籍第二版-全-
Sklearn 秘籍第二版(全)
原文:
annas-archive.org/md5/7039549ff2b32d189f96a3420dc66360译者:飞龙
前言
从安装和设置 scikit-learn 开始,本书包含了关于常见监督和无监督机器学习概念的高度实用的配方。获取你的分析数据;选择你的模型所需的特征;并在短时间内实施流行的技术,如线性模型、分类、回归、聚类等!本书还包含了有关评估和优化模型性能的配方。这些配方不仅包含尝试技术的潜在动机和理论,还详细介绍了所有代码。
"过早优化是万恶之源"
- 唐纳德·克努斯
scikit-learn 和 Python 允许快速原型设计,这在某种意义上与唐纳德·克努斯的过早优化相反。就个人而言,scikit-learn 让我能够原型设计我曾认为不可能的东西,包括大规模的面部识别系统和股票市场交易模拟。你可以通过 scikit-learn 获得即时洞察,并建立原型。数据科学从定义上来说是科学的,并且有许多失败的假设。幸运的是,使用 scikit-learn,你可以在接下来的几分钟内看到什么有效(和无效)。
此外,Jupyter(IPython)笔记本提供了一个非常适合初学者和专家的友好界面,鼓励新的科学软件工程思维方式。这种友好的特性很令人耳目一新,因为在创新中,我们都是初学者。
在本书的最后一章中,你可以制作自己的估计器,Python 也从一个脚本语言转变为更像面向对象的语言。Python 数据科学生态系统为你制作自己独特风格和为数据科学团队和人工智能做出重大贡献提供了基本组件。
以类比的方式,算法在堆叠器中作为一个团队运作。不同风格的多样算法投票以做出更好的预测。有些选择比其他的更好,但只要算法不同,最终的选择将是最好的。堆叠器和混合器在由 Pragmatic Chaos 团队赢得的 Netflix 100 万美元奖金竞赛中声名鹊起。
欢迎来到 scikit-learn 的世界:一个非常强大、简单和表现力强的机器学习库。我真的很兴奋看到你们的成果。
本书内容
第一章,高性能机器学习 – NumPy,展示了你的第一个支持向量机算法。我们区分分类(什么类型?)和回归(多少?)。我们对未见过的数据进行预测。
第二章,预模型工作流和预处理,展示了一个现实的工业环境,其中有大量的数据清洗和预处理。要做机器学习,你需要好的数据,这一章告诉你如何获取并为机器学习准备好数据。
第三章,降维,讨论了通过减少特征数量来简化机器学习过程,并更好地利用计算资源。
第四章,使用 scikit-learn 的线性模型,讲述了线性回归这一最古老的预测模型,从机器学习和人工智能的角度进行解析。你将使用岭回归处理相关特征,利用 LASSO 和交叉验证消除相关特征,或者通过鲁棒中位数回归消除异常值。
第五章,线性模型 – 逻辑回归,通过逻辑回归分析了重要的医疗健康数据集,如癌症和糖尿病。这一模型突出了回归和分类之间的相似性与差异性,它们是两种类型的监督学习。
第六章,基于距离度量的模型构建,将点放置在你熟悉的欧几里得空间中,因为距离与相似性是同义的。两个点之间有多近(相似)或多远?我们可以将它们归为一组吗?借助欧几里得的帮助,我们可以使用 k 均值聚类来接近无监督学习,并将未知类别的点分组。
第七章,交叉验证与模型后期工作流,讲解了如何选择一个与交叉验证兼容的有效模型:即预测结果的迭代训练与测试。同时,我们还通过 pickle 模块节省了计算工作量。
第八章,支持向量机,详细分析了支持向量机,一种强大且易于理解的算法。
第九章,树算法与集成方法,讲解了决策制定的算法:决策树。本章还介绍了元学习算法,这些多样的算法通过某种方式投票,以提高整体预测准确度。
第十章,使用 scikit-learn 进行文本和多类分类,回顾了自然语言处理的基础,采用简单的词袋模型。通常,我们将分类问题视为三类或更多类别的分类问题。
第十一章,神经网络,介绍了神经网络和感知器,神经网络的基本组件。每一层都处理过程中的一个步骤,最终达到期望的结果。由于我们并没有特别编程每一个步骤,所以我们涉足了人工智能。保存神经网络,以便稍后继续训练,或者加载它并将其作为堆叠集成的一部分使用。
第十二章,创建一个简单的估算器,帮助您制作自己的 scikit-learn 估算器,您可以将其贡献给 scikit-learn 社区,并参与数据科学与 scikit-learn 的演进。
本书适合谁
本书适合那些熟悉 Python,但不太熟悉 scikit-learn 的数据分析师,以及那些希望直接、简洁地进入机器学习世界的 Python 程序员。
本书所需内容
您需要安装以下库:
-
anaconda 4.1.1
-
numba 0.26.0
-
numpy 1.12.1
-
pandas 0.20.3
-
pandas-datareader 0.4.0
-
patsy 0.4.1
-
scikit-learn 0.19.0
-
scipy 0.19.1
-
statsmodels 0.8.0
-
sympy 1.0
约定
本书中,您将看到许多文本样式,用以区分不同种类的信息。以下是这些样式的一些示例及其含义的解释。
书中出现的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 句柄如下所示:“scikit-learn 库要求输入的表格是二维 NumPy 数组。”
任何命令行输入或输出的格式如下所示:
import numpy as np #Load the numpy library for fast array
computations
import pandas as pd #Load the pandas data-analysis library
import matplotlib.pyplot as plt #Load the pyplot visualization
library
新术语和重要词汇以粗体显示。
警告或重要提示以框体形式显示,如下所示。
提示和技巧如下所示。
读者反馈
我们欢迎读者的反馈。让我们知道您对本书的看法——喜欢什么或不喜欢什么。读者反馈对我们非常重要,因为它帮助我们开发出您能真正受益的书籍。
若要向我们发送一般反馈,只需发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书名。
如果您在某个领域有专长,并且有兴趣编写或贡献书籍,请参考我们的作者指南:www.packtpub.com/authors。
客户支持
现在,您已经成为 Packt 图书的骄傲拥有者,我们提供了很多资源帮助您充分利用您的购买。
下载示例代码
您可以从您的账户下载本书的示例代码文件,网址为www.packtpub.com。如果您是从其他地方购买的本书,您可以访问www.packtpub.com/support并注册,将文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标悬停在顶部的“支持”标签上。
-
点击“代码下载与勘误”。
-
在搜索框中输入书名。
-
选择您要下载代码文件的书籍。
-
从下拉菜单中选择您购买本书的渠道。
-
点击“代码下载”。
一旦文件下载完成,请确保使用以下最新版解压或提取文件夹:
-
WinRAR / 7-Zip for Windows
-
Zipeg / iZip / UnRarX for Mac
-
Linux 的 7-Zip / PeaZip
本书的代码包也托管在 GitHub 上,地址为 github.com/PacktPublishing/scikit-learn-Cookbook-Second-Edition。我们还有其他来自我们丰富书籍和视频目录的代码包,您可以在 github.com/PacktPublishing/ 上找到。快去查看吧!
勘误
尽管我们已尽力确保内容的准确性,但错误仍然可能发生。如果您发现我们书籍中的错误——无论是文本还是代码中的错误——我们将非常感谢您向我们报告。通过这样做,您可以帮助其他读者避免困扰,并帮助我们改进本书的后续版本。如果您发现任何勘误,请访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入勘误的详细信息。一旦您的勘误被验证,我们将接受您的提交,并将勘误上传到我们的网站或添加到该书勘误部分的现有勘误列表中。
要查看之前提交的勘误,请访问 www.packtpub.com/books/content/support,并在搜索框中输入书名。所需信息将显示在勘误部分。
盗版
网络上的版权材料盗版问题是一个跨所有媒体的持续问题。在 Packt,我们非常重视对版权和许可的保护。如果您在互联网上发现我们作品的任何非法复制品,请立即提供该位置地址或网站名称,以便我们采取相应措施。
请通过copyright@packtpub.com与我们联系,提供涉嫌盗版材料的链接。
感谢您的帮助,保护我们的作者和我们为您提供宝贵内容的能力。
问题
如果您在本书的任何部分遇到问题,可以通过questions@packtpub.com与我们联系,我们将尽力解决问题。
第一章:高性能机器学习——NumPy
在本章中,我们将涵盖以下内容:
-
NumPy 基础
-
加载鸢尾花数据集
-
查看鸢尾花数据集
-
使用 pandas 查看鸢尾花数据集
-
使用 NumPy 和 matplotlib 绘图
-
最小机器学习示例——SVM 分类
-
引入交叉验证
-
将所有内容整合在一起
-
机器学习概述——分类与回归
介绍
在本章中,我们将学习如何使用 scikit-learn 进行预测。机器学习强调衡量预测能力,而使用 scikit-learn 我们可以准确快速地进行预测。
我们将检查iris数据集,它包含三种鸢尾花类型的测量数据:Iris Setosa、Iris Versicolor 和 Iris Virginica。
为了衡量预测的强度,我们将:
-
保存一些数据以供测试
-
只使用训练数据构建模型
-
测量在测试集上的预测能力
预测——三种花卉类型中的一种是分类的。这类问题称为分类问题。
非正式地说,分类问的是,它是苹果还是橙子?与此对比,机器学习回归问题问的是,有多少个苹果?顺便说一下,回归问题的答案可以是4.5 个苹果。
通过其设计的演变,scikit-learn 主要通过四个类别来解决机器学习问题:
-
分类:
-
非文本分类,例如鸢尾花的例子
-
文本分类
-
-
回归
-
聚类
-
降维
NumPy 基础
数据科学部分处理结构化数据表。scikit-learn库要求输入表格是二维 NumPy 数组。在本节中,你将了解numpy库。
如何操作...
我们将对 NumPy 数组进行一些操作。NumPy 数组的所有元素具有相同的数据类型,并且具有预定义的形状。让我们首先查看它们的形状。
NumPy 数组的形状和维度
- 首先导入 NumPy:
import numpy as np
- 生成一个包含 10 个数字的 NumPy 数组,类似于 Python 的
range(10)方法:
np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
- 数组看起来像一个只有一对括号的 Python 列表。这意味着它是单维的。存储数组并找出它的形状:
array_1 = np.arange(10)
array_1.shape
(10L,)
- 数组具有一个数据属性,
shape。array_1.shape的类型是元组(10L,),它的长度为1,在本例中是这样。维度的数量与元组的长度相同——在本例中是1维:
array_1.ndim #Find number of dimensions of array_1
1
- 数组有 10 个元素。通过调用
reshape方法重塑数组:
array_1.reshape((5,2))
array([[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]])
- 这将把数组重塑为 5 x 2 的数据对象,类似于列表的列表(三维 NumPy 数组看起来像是列表的列表的列表)。你没有保存更改。请按以下方式保存重塑后的数组:
array_1 = array_1.reshape((5,2))
- 注意,
array_1现在是二维的。这是预期的,因为它的形状有两个数字,看起来像是一个 Python 的列表的列表:
array_1.ndim
2
NumPy 广播
- 通过广播将
1添加到数组的每个元素中。请注意,数组的更改没有保存:
array_1 + 1
array([[ 1, 2],
[ 3, 4],
[ 5, 6],
[ 7, 8],
[ 9, 10]])
广播这个术语指的是将较小的数组扩展或广播到较大的数组上。在第一个示例中,标量 1 被扩展为一个 5 x 2 的形状,并与 array_1 相加。
- 创建一个新的
array_2数组。观察当你将数组与自身相乘时会发生什么(这不是矩阵乘法;而是数组的逐元素乘法):
array_2 = np.arange(10)
array_2 * array_2
array([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81])
- 每个元素都已被平方。这里发生了逐元素乘法。下面是一个更复杂的例子:
array_2 = array_2 ** 2 #Note that this is equivalent to array_2 * array_2
array_2 = array_2.reshape((5,2))
array_2
array([[ 0, 1],
[ 4, 9],
[16, 25],
[36, 49],
[64, 81]])
- 也修改
array_1:
array_1 = array_1 + 1
array_1
array([[ 1, 2],
[ 3, 4],
[ 5, 6],
[ 7, 8],
[ 9, 10]])
- 现在通过简单地将数组之间加上加号来逐元素地将
array_1和array_2相加:
array_1 + array_2
array([[ 1, 3],
[ 7, 13],
[21, 31],
[43, 57],
[73, 91]])
- 正式的广播规则要求,当你比较两个数组的形状时,从右到左,所有的数字必须匹配或为 1。形状为5 X 2和5 X 2的两个数组从右到左都匹配。然而,形状为5 X 2 X 1与5 X 2不匹配,因为从右到左的第二个值,分别是2和5,不匹配:

初始化 NumPy 数组和数据类型
除了 np.arange,还有多种方法可以初始化 NumPy 数组:
- 使用
np.zeros初始化一个全为零的数组。np.zeros((5,2))命令创建一个 5 x 2 的零数组:
np.zeros((5,2))
array([[ 0., 0.],
[ 0., 0.],
[ 0., 0.],
[ 0., 0.],
[ 0., 0.]])
- 使用
np.ones初始化一个全为 1 的数组。引入dtype参数,设置为np.int,以确保这些 1 为 NumPy 整型。注意,scikit-learn 期望数组中的dtype为np.float类型。dtype指的是 NumPy 数组中每个元素的类型,它在整个数组中保持一致。下面数组的每个元素都是np.int类型。
np.ones((5,2), dtype = np.int)
array([[1, 1],
[1, 1],
[1, 1],
[1, 1],
[1, 1]])
- 使用
np.empty为特定大小和dtype的数组分配内存,但不初始化特定值:
np.empty((5,2), dtype = np.float)
array([[ 3.14724935e-316, 3.14859499e-316],
[ 3.14858945e-316, 3.14861159e-316],
[ 3.14861435e-316, 3.14861712e-316],
[ 3.14861989e-316, 3.14862265e-316],
[ 3.14862542e-316, 3.14862819e-316]])
- 使用
np.zeros、np.ones和np.empty为 NumPy 数组分配不同初始值的内存。
索引
- 使用索引查找二维数组的值:
array_1[0,0] #Finds value in first row and first column.
1
- 查看第一行:
array_1[0,:]
array([1, 2])
- 然后查看第一列:
array_1[:,0]
array([1, 3, 5, 7, 9])
- 查看沿两个轴的特定值。同时查看第二行到第四行:
array_1[2:5, :]
array([[ 5, 6],
[ 7, 8],
[ 9, 10]])
- 仅查看第一列的第二行到第四行:
array_1[2:5,0]
array([5, 7, 9])
布尔数组
此外,NumPy 还使用布尔逻辑来处理索引:
- 首先生成一个布尔数组:
array_1 > 5
array([[False, False],
[False, False],
[False, True],
[ True, True],
[ True, True]], dtype=bool)
- 将布尔数组加上括号,以便通过布尔数组进行过滤:
array_1[array_1 > 5]
array([ 6, 7, 8, 9, 10])
算术运算
- 使用
sum方法将数组的所有元素相加。回到array_1:
array_1
array([[ 1, 2],
[ 3, 4],
[ 5, 6],
[ 7, 8],
[ 9, 10]])
array_1.sum()
55
- 按行求和:
array_1.sum(axis = 1)
array([ 3, 7, 11, 15, 19])
- 按列求和:
array_1.sum(axis = 0)
array([25, 30])
- 以类似的方式求每一列的均值。注意,均值数组的
dtype为np.float:
array_1.mean(axis = 0)
array([ 5., 6.])
NaN 值
- Scikit-learn 不接受
np.nan值。假设有如下的array_3:
array_3 = np.array([np.nan, 0, 1, 2, np.nan])
- 使用
np.isnan函数创建一个特殊的布尔数组来查找 NaN 值:
np.isnan(array_3)
array([ True, False, False, False, True], dtype=bool)
- 通过否定布尔数组并在表达式周围加上括号来过滤 NaN 值:
array_3[~np.isnan(array_3)]
>array([ 0., 1., 2.])
- 另一种方法是将 NaN 值设置为零:
array_3[np.isnan(array_3)] = 0
array_3
array([ 0., 0., 1., 2., 0.])
它是如何工作的……
数据,在目前的最简意义上,是指由数字组成的二维表格,NumPy 对此处理得非常好。记住这一点,以防你忘记了 NumPy 的语法细节。Scikit-learn 仅接受没有缺失值(np.nan)的二维 NumPy 数组。
根据经验,最好是将 np.nan 更改为某个值,而不是丢弃数据。就个人而言,我喜欢追踪布尔掩码,并保持数据形状大致不变,因为这样可以减少编码错误并提高编码灵活性。
加载 iris 数据集
要使用 scikit-learn 进行机器学习,我们需要一些数据作为起点。我们将加载 iris 数据集,它是 scikit-learn 中几个数据集之一。
准备工作
一个 scikit-learn 程序开始时会有多个导入。在 Python 中,最好在 Jupyter Notebook 中加载 numpy、pandas 和 pyplot 库:
import numpy as np #Load the numpy library for fast array computations
import pandas as pd #Load the pandas data-analysis library
import matplotlib.pyplot as plt #Load the pyplot visualization library
如果你在 Jupyter Notebook 中,输入以下内容即可立即查看图形输出:
%matplotlib inline
如何操作…
- 从 scikit-learn 的
datasets模块中,访问iris数据集:
from sklearn import datasets
iris = datasets.load_iris()
它是如何工作的…
同样,你也可以通过以下方式导入 diabetes 数据集:
from sklearn import datasets #Import datasets module from scikit-learn
diabetes = datasets.load_diabetes()
看!你已经使用 datasets 模块中的 load_diabetes() 函数加载了 diabetes 数据集。要查看可用的数据集,输入:
datasets.load_*?
一旦你尝试这个,你可能会发现有一个名为 datasets.load_digits 的数据集。要访问它,输入 load_digits() 函数,类似于其他加载函数:
digits = datasets.load_digits()
要查看数据集的信息,输入 digits.DESCR。
查看 iris 数据集
现在我们已经加载了数据集,来看看里面有什么内容。iris 数据集涉及一个监督分类问题。
如何操作…
- 要访问观测变量,输入:
iris.data
这会输出一个 NumPy 数组:
array([[ 5.1, 3.5, 1.4, 0.2],
[ 4.9, 3\. , 1.4, 0.2],
[ 4.7, 3.2, 1.3, 0.2],
#...rest of output suppressed because of length
- 让我们检查一下 NumPy 数组:
iris.data.shape
这会返回:
(150L, 4L)
这意味着数据是 150 行 4 列。让我们看看第一行:
iris.data[0]
array([ 5.1, 3.5, 1.4, 0.2])
第一行的 NumPy 数组包含四个数字。
- 要确定它们的含义,输入:
iris.feature_names
['sepal length (cm)',
'sepal width (cm)',
'petal length (cm)',
'petal width (cm)']
特征或列名表示数据。它们是字符串,在这个例子中,它们对应于不同种类花的不同维度。综合来看,我们有 150 个花的样本,每个花有四个以厘米为单位的测量值。例如,第一个花的测量值为:萼片长度 5.1 cm,萼片宽度 3.5 cm,花瓣长度 1.4 cm,花瓣宽度 0.2 cm。现在,让我们以类似的方式查看输出变量:
iris.target
这会返回一个输出数组:0、1 和 2。只有这三种输出。输入:
iris.target.shape
你得到的形状是:
(150L,)
这指的是长度为 150 的数组(150 x 1)。我们来看看这些数字代表什么:
iris.target_names
array(['setosa', 'versicolor', 'virginica'],
dtype='|S10')
iris.target_names 变量的输出给出了 iris.target 变量中数字的英文名称。数字零对应于 setosa 花,数字一对应于 versicolor 花,数字二对应于 virginica 花。看看 iris.target 的第一行:
iris.target[0]
这产生了零,因此我们之前检查的第一行观测结果对应于setosa花。
它是如何工作的...
在机器学习中,我们经常处理数据表和二维数组,这些数组对应于样本。在iris数据集中,我们有 150 个观测值,包含三种类型的花。对于新的观测值,我们希望预测这些观测值对应的花种类。这里的观测值是厘米为单位的测量数据。观察与真实物体相关的数据非常重要。引用我高中的物理老师的话,"不要忘记单位!
"
iris数据集旨在用于监督式机器学习任务,因为它有一个目标数组,即我们希望从观测变量中预测的变量。此外,它是一个分类问题,因为我们可以从观测值中预测三个数字,每个花的类型对应一个数字。在分类问题中,我们试图区分不同的类别。最简单的情况是二分类。iris数据集有三种花类,因此它是一个多类分类问题。
还有更多...
使用相同的数据,我们可以用多种方式重新表述问题,或者提出新的问题。如果我们想要确定观测值之间的关系怎么办?我们可以将花瓣宽度定义为目标变量。我们可以将问题重新表述为回归问题,试图将目标变量预测为一个实数,而不仅仅是三个类别。从根本上说,这取决于我们想要预测什么。在这里,我们希望预测一种花的类型。
使用 Pandas 查看iris数据集
在这个示例中,我们将使用方便的pandas数据分析库来查看和可视化iris数据集。它包含了概念 o,一个数据框(dataframe),如果你使用 R 语言的数据框,可能会对它有所熟悉。
如何做到这一点...
你可以通过 Pandas 查看iris数据集,Pandas 是一个基于 NumPy 构建的库:
- 创建一个包含观测变量
iris.data的数据框,并以columns作为列名:
import pandas as pd
iris_df = pd.DataFrame(iris.data, columns = iris.feature_names)
数据框比 NumPy 数组更易于使用。
- 查看数据框中
sepal length值的快速直方图:
iris_df['sepal length (cm)'].hist(bins=30)

- 你还可以通过
target变量为直方图上色:
for class_number in np.unique(iris.target):
plt.figure(1)
iris_df['sepal length (cm)'].iloc[np.where(iris.target == class_number)[0]].hist(bins=30)
- 在这里,迭代每种花的目标数字,并为每个花绘制一个彩色直方图。考虑这一行:
np.where(iris.target== class_number)[0]
它找到了每种花类别的 NumPy 索引位置:

观察到直方图有重叠。这鼓励我们将三个直方图建模为三个正态分布。如果我们仅将训练数据建模为三个正态分布而不是整个数据集,这在机器学习中是可行的。然后,我们使用测试集来测试我们刚刚构建的三个正态分布模型。最后,我们在测试集上测试我们预测的准确性。
它是如何工作的...
数据框架数据对象是一个二维的 NumPy 数组,包含列名和行名。在数据科学中,基本的数据对象看起来像一个二维表格,这可能与 SQL 的悠久历史有关。NumPy 还支持三维数组、立方体、四维数组等,这些也经常出现。
使用 NumPy 和 matplotlib 绘图
使用 NumPy 进行可视化的一个简单方法是使用 matplotlib 库。让我们快速创建一些可视化图形。
准备工作
首先导入 numpy 和 matplotlib。你可以使用 %matplotlib inline 命令在 IPython Notebook 中查看可视化图形:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
如何操作...
- matplotlib 中的主要命令,伪代码如下:
plt.plot(numpy array, numpy array of same length)
- 通过放置两个相同长度的 NumPy 数组来绘制一条直线:
plt.plot(np.arange(10), np.arange(10))

- 绘制指数图:
plt.plot(np.arange(10), np.exp(np.arange(10)))

- 将两个图表并排放置:
plt.figure()
plt.subplot(121)
plt.plot(np.arange(10), np.exp(np.arange(10)))
plt.subplot(122)
plt.scatter(np.arange(10), np.exp(np.arange(10)))
或者从上到下:
plt.figure()
plt.subplot(211)
plt.plot(np.arange(10), np.exp(np.arange(10)))
plt.subplot(212)
plt.scatter(np.arange(10), np.exp(np.arange(10)))

子图命令中的前两个数字表示由 plt.figure() 实例化的图形中的网格大小。plt.subplot(221) 中提到的网格大小是 2 x 2,前两个数字表示网格的行列数。最后一个数字表示按阅读顺序遍历网格:从左到右,再从上到下。
- 在 2 x 2 网格中绘图,按照从一到四的阅读顺序排列:
plt.figure()
plt.subplot(221)
plt.plot(np.arange(10), np.exp(np.arange(10)))
plt.subplot(222)
plt.scatter(np.arange(10), np.exp(np.arange(10)))
plt.subplot(223)
plt.scatter(np.arange(10), np.exp(np.arange(10)))
plt.subplot(224)
plt.scatter(np.arange(10), np.exp(np.arange(10)))

- 最后,使用真实数据:
from sklearn.datasets import load_iris
iris = load_iris()
data = iris.data
target = iris.target
# Resize the figure for better viewing
plt.figure(figsize=(12,5))
# First subplot
plt.subplot(121)
# Visualize the first two columns of data:
plt.scatter(data[:,0], data[:,1], c=target)
# Second subplot
plt.subplot(122)
# Visualize the last two columns of data:
plt.scatter(data[:,2], data[:,3], c=target)
c 参数接受一个颜色数组——在此例中为 iris 目标中的颜色 0、1 和 2:

一个简化的机器学习配方 – SVM 分类
机器学习的核心是进行预测。为了进行预测,我们需要:
-
陈述要解决的问题
-
选择一个模型来解决问题
-
训练模型
-
进行预测
-
测量模型的表现
准备工作
回到鸢尾花示例,现在我们将观察数据的前两个特征(列)存储为 X,目标存储为 y,这是机器学习社区中的惯例:
X = iris.data[:, :2]
y = iris.target
如何操作...
- 首先,我们陈述问题。我们试图从一组新的观察数据中确定花卉类型类别。这是一个分类任务。可用的数据包括一个目标变量,我们将其命名为
y。这属于监督分类问题。
监督学习的任务涉及使用输入变量和输出变量训练模型,从而预测输出变量的值。
-
接下来,我们选择一个模型来解决监督分类问题。目前我们将使用支持向量分类器。由于其简单性和可解释性,它是一个常用的算法(可解释性意味着容易阅读和理解)。
-
为了衡量预测性能,我们将数据集分为训练集和测试集。训练集是我们将从中学习的数据。测试集是我们保留的数据,并假装不知道它,以便衡量我们学习过程的性能。因此,导入一个将数据集拆分的函数:
from sklearn.model_selection import train_test_split
- 将该函数应用于观察数据和目标数据:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=1)
测试集大小为 0.25,即整个数据集的 25%。一个随机状态值 1 固定了函数的随机种子,使得每次调用该函数时都能得到相同的结果,这对现在保持结果一致性非常重要。
- 现在加载一个常用的估算器——支持向量机:
from sklearn.svm import SVC
- 你已经从
svm模块导入了支持向量分类器。现在创建一个线性 SVC 的实例:
clf = SVC(kernel='linear',random_state=1)
随机状态被固定,以便以后使用相同的代码重现相同的结果。
scikit-learn 中的监督学习模型实现了一个fit(X, y)方法,用于训练模型并返回训练好的模型。X是观察数据的子集,y的每个元素对应于X中每个观察数据的目标。在这里,我们在训练数据上拟合了一个模型:
clf.fit(X_train, y_train)
此时,clf变量是已经拟合或训练好的模型。
估算器还有一个predict(X)方法,该方法会对多个未标记的观察数据X_test进行预测,并返回预测值y_pred。请注意,该函数不会返回估算器本身,而是返回一组预测值:
y_pred = clf.predict(X_test)
到目前为止,你已经完成了除最后一步以外的所有步骤。为了检查模型的表现,加载一个来自指标模块的评分器:
from sklearn.metrics import accuracy_score
使用评分器,将预测结果与保留的测试目标进行比较:
accuracy_score(y_test,y_pred)
0.76315789473684215
它是如何工作的……
即使对支持向量机的细节了解不多,我们也已经实现了一个预测模型。为了进行机器学习,我们保留了四分之一的数据,并检查了 SVC 在这些数据上的表现。最终,我们得到了一个衡量准确度的数值,或者说衡量模型表现的数值。
还有更多内容……
总结一下,我们将用不同的算法——逻辑回归——来执行所有步骤:
- 首先,导入
LogisticRegression:
from sklearn.linear_model import LogisticRegression
-
然后编写一个包含建模步骤的程序:
-
将数据分为训练集和测试集。
-
拟合逻辑回归模型。
-
使用测试观察数据进行预测。
-
使用
y_test与y_pred来衡量预测的准确性:
-
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X = iris.data[:, :2] #load the iris data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=1)
#train the model
clf = LogisticRegression(random_state = 1)
clf.fit(X_train, y_train)
#predict with Logistic Regression
y_pred = clf.predict(X_test)
#examine the model accuracy
accuracy_score(y_test,y_pred)
0.60526315789473684
这个数值较低;然而,我们无法在支持向量分类(SVC)与逻辑回归分类模型之间做出结论。我们无法比较它们,因为我们不应该查看模型的测试集。如果我们在 SVC 和逻辑回归之间做出选择,那么该选择也将成为我们模型的一部分,因此测试集不能参与选择。交叉验证,我们接下来将要介绍的,是一种选择模型的方法。
引入交叉验证
我们感谢iris数据集,但正如你所记得的,它只有 150 个观测值。为了最大限度地利用这个数据集,我们将使用交叉验证。此外,在上一部分中,我们想比较两种不同分类器的性能——支持向量分类器和逻辑回归。交叉验证将帮助我们解决这个比较问题。
准备工作
假设我们想在支持向量分类器和逻辑回归分类器之间做选择。我们不能在不可用的测试集上衡量它们的表现。
那么,如果我们改为:
-
现在忘记测试集了吗?
-
将训练集拆分成两部分,一部分用于训练,另一部分用于测试训练结果?
使用之前部分中提到的train_test_split函数将训练集拆分为两部分:
from sklearn.model_selection import train_test_split
X_train_2, X_test_2, y_train_2, y_test_2 = train_test_split(X_train, y_train, test_size=0.25, random_state=1)
X_train_2包含X_train数据的 75%,而X_test_2是剩下的 25%。y_train_2是目标数据的 75%,并与X_train_2的观测值相匹配。y_test_2是y_train中 25%的目标数据。
正如你可能预料的,你必须使用这些新的拆分来在两个模型之间做选择:SVC 和逻辑回归。通过编写一个预测程序来实现这一点。
如何做到...
- 从导入所有库并加载
iris数据集开始:
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
#load the classifying models
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
iris = datasets.load_iris()
X = iris.data[:, :2] #load the first two features of the iris data
y = iris.target #load the target of the iris data
#split the whole set one time
#Note random state is 7 now
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=7)
#split the training set into parts
X_train_2, X_test_2, y_train_2, y_test_2 = train_test_split(X_train, y_train, test_size=0.25, random_state=7)
- 创建一个 SVC 分类器实例并进行拟合:
svc_clf = SVC(kernel = 'linear',random_state = 7)
svc_clf.fit(X_train_2, y_train_2)
- 对逻辑回归做相同操作(逻辑回归的两行代码压缩成一行):
lr_clf = LogisticRegression(random_state = 7).fit(X_train_2, y_train_2)
- 现在预测并检查 SVC 和逻辑回归在
X_test_2上的表现:
svc_pred = svc_clf.predict(X_test_2)
lr_pred = lr_clf.predict(X_test_2)
print "Accuracy of SVC:",accuracy_score(y_test_2,svc_pred)
print "Accuracy of LR:",accuracy_score(y_test_2,lr_pred)
Accuracy of SVC: 0.857142857143
Accuracy of LR: 0.714285714286
- SVC 的表现更好,但我们还没有看到原始的测试数据。选择 SVC 而非逻辑回归,并尝试在原始测试集上进行测试:
print "Accuracy of SVC on original Test Set: ",accuracy_score(y_test, svc_clf.predict(X_test))
Accuracy of SVC on original Test Set: 0.684210526316
它是如何工作的...
在比较 SVC 和逻辑回归分类器时,你可能会感到疑惑(甚至有些怀疑),因为它们的得分差异很大。最终 SVC 的测试得分低于逻辑回归。为了解决这个问题,我们可以在 scikit-learn 中进行交叉验证。
交叉验证涉及将训练集拆分为多个部分,就像我们之前做的一样。为了与前面的示例相匹配,我们将训练集拆分为四个部分或折叠。我们将通过轮流选取其中一个折叠作为测试集,其他三个折叠作为训练集,来设计一个交叉验证迭代。这与之前的拆分相同,只不过进行了四次轮换,从某种意义上来说,测试集在四个折叠中轮换:

使用 scikit-learn,这个操作相对容易实现:
- 我们从一个导入开始:
from sklearn.model_selection import cross_val_score
- 然后我们在四个折叠上生成准确度评分:
svc_scores = cross_val_score(svc_clf, X_train, y_train, cv=4)
svc_scores
array([ 0.82758621, 0.85714286, 0.92857143, 0.77777778])
- 我们可以找到平均表现的均值和所有得分相对于均值的标准差来衡量得分的分布情况:
print "Average SVC scores: ", svc_scores.mean()
print "Standard Deviation of SVC scores: ", svc_scores.std()
Average SVC scores: 0.847769567597
Standard Deviation of SVC scores: 0.0545962864696
- 同样,对于逻辑回归实例,我们计算出四个得分:
lr_scores = cross_val_score(lr_clf, X_train, y_train, cv=4)
print "Average SVC scores: ", lr_scores.mean()
print "Standard Deviation of SVC scores: ", lr_scores.std()
Average SVC scores: 0.748893906221
Standard Deviation of SVC scores: 0.0485633168699
现在我们有了多个得分,这证实了我们选择 SVC 而非逻辑回归。由于交叉验证,我们多次使用训练集,并且在其中有四个小的测试集来评分我们的模型。
请注意,我们的模型是一个更大的模型,包含以下内容:
-
通过交叉验证训练 SVM
-
通过交叉验证训练逻辑回归
-
在 SVM 和逻辑回归之间做选择
最终的选择是模型的一部分。
还有更多...
尽管我们付出了很多努力,并且 scikit-learn 语法非常优雅,但最终测试集上的得分仍然令人怀疑。原因在于测试集和训练集的划分未必平衡;训练集和测试集中的各个类别的比例可能不同。
通过使用分层的测试-训练划分,可以轻松解决这个问题:
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y)
通过将目标集选择为分层参数,目标类别会被平衡。这样可以使 SVC 的得分更接近。
svc_scores = cross_val_score(svc_clf, X_train, y_train, cv=4)
print "Average SVC scores: " , svc_scores.mean()
print "Standard Deviation of SVC scores: ", svc_scores.std()
print "Score on Final Test Set:", accuracy_score(y_test, svc_clf.predict(X_test))
Average SVC scores: 0.831547619048
Standard Deviation of SVC scores: 0.0792488953372
Score on Final Test Set: 0.789473684211
此外,请注意,在前面的示例中,交叉验证过程默认会生成分层折叠:
from sklearn.model_selection import cross_val_score
svc_scores = cross_val_score(svc_clf, X_train, y_train, cv = 4)
前面的代码等价于:
from sklearn.model_selection import cross_val_score, StratifiedKFold
skf = StratifiedKFold(n_splits = 4)
svc_scores = cross_val_score(svc_clf, X_train, y_train, cv = skf)
将一切结合起来
现在,我们将执行与之前相同的步骤,只不过这次我们将重置、重新分组,并尝试一种新的算法:K 最近邻(KNN)。
如何做...
- 从
sklearn导入模型,然后进行平衡划分:
from sklearn.neighbors import KNeighborsClassifier
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state = 0)
random_state参数在train_test_split函数中固定了random_seed。在前面的示例中,random_state被设置为零,可以设置为任何整数。
- 通过改变
n_neighbors参数构建两个不同的 KNN 模型。请注意,折叠次数现在为 10。十折交叉验证在机器学习领域中非常常见,尤其是在数据科学竞赛中:
from sklearn.model_selection import cross_val_score
knn_3_clf = KNeighborsClassifier(n_neighbors = 3)
knn_5_clf = KNeighborsClassifier(n_neighbors = 5)
knn_3_scores = cross_val_score(knn_3_clf, X_train, y_train, cv=10)
knn_5_scores = cross_val_score(knn_5_clf, X_train, y_train, cv=10)
- 评分并打印出选择的得分:
print "knn_3 mean scores: ", knn_3_scores.mean(), "knn_3 std: ",knn_3_scores.std()
print "knn_5 mean scores: ", knn_5_scores.mean(), " knn_5 std: ",knn_5_scores.std()
knn_3 mean scores: 0.798333333333 knn_3 std: 0.0908142181722
knn_5 mean scores: 0.806666666667 knn_5 std: 0.0559320575496
两种最近邻类型的得分相似,但参数为n_neighbors = 5的 KNN 略微更稳定。这是一个超参数优化的例子,我们将在本书中仔细研究这一点。
还有更多...
你本来也可以简单地运行一个循环,更快地对函数进行评分:
all_scores = []
for n_neighbors in range(3,9,1):
knn_clf = KNeighborsClassifier(n_neighbors = n_neighbors)
all_scores.append((n_neighbors, cross_val_score(knn_clf, X_train, y_train, cv=10).mean()))
sorted(all_scores, key = lambda x:x[1], reverse = True)
其输出表明,n_neighbors = 4是一个不错的选择:
[(4, 0.85111111111111115),
(7, 0.82611111111111113),
(6, 0.82333333333333347),
(5, 0.80666666666666664),
(3, 0.79833333333333334),
(8, 0.79833333333333334)]
机器学习概述 – 分类与回归
在本篇中,我们将探讨如何将回归视为与分类非常相似。这是通过将回归的类别标签重新考虑为实数来实现的。在本节中,我们还将从非常广泛的角度来看待机器学习的多个方面,包括 scikit-learn 的目的。scikit-learn 使我们能够非常快速地找到有效的模型。我们不需要一开始就详细地推导出所有模型的细节,也不需要优化,直到找到一个表现良好的模型。因此,得益于 scikit-learn 的高效性,你的公司可以节省宝贵的开发时间和计算资源。
scikit-learn 的目的
正如我们之前看到的,scikit-learn 允许我们相对快速地找到有效的模型。我们尝试了 SVC、逻辑回归和一些 KNN 分类器。通过交叉验证,我们选择了表现更好的模型。在实际应用中,经过尝试 SVM 和逻辑回归后,我们可能会专注于 SVM 并进一步优化它们。多亏了 scikit-learn,我们节省了大量时间和资源,包括精力。在工作中对现实数据集优化了 SVM 后,我们可能会为提高速度而在 Java 或 C 中重新实现它,并收集更多的数据。
监督学习与无监督学习
分类和回归是监督学习,因为我们知道观测数据的目标变量。聚类——在空间中为每个类别创建区域而不给予标签,是无监督学习。
准备好
在分类中,目标变量是多个类别之一,并且每个类别必须有多个实例。在回归中,每个目标变量只能有一个实例,因为唯一的要求是目标是一个实数。
在逻辑回归的情况下,我们之前看到,算法首先执行回归并为目标估算一个实数。然后,通过使用阈值来估计目标类别。在 scikit-learn 中,有predict_proba方法,它提供概率估算,将类似回归的实数估算与逻辑回归风格的分类类别关联起来。
任何回归都可以通过使用阈值转化为分类。二分类问题可以通过使用回归器看作回归问题。产生的目标变量将是实数,而不是原始的类别变量。
如何做...
快速 SVC——一个分类器和回归器
- 从
datasets模块加载iris:
import numpy as np
import pandas as pd
from sklearn import datasets
iris = datasets.load_iris()
- 为简便起见,只考虑目标
0和1,分别对应 Setosa 和 Versicolor。使用布尔数组iris.target < 2来过滤目标2。将其放入括号中,作为筛选器在定义观测集X和目标集y时使用:
X = iris.data[iris.target < 2]
y = iris.target[iris.target < 2]
- 现在导入
train_test_split并应用它:
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state= 7)
- 通过导入 SVC 并使用交叉验证评分来准备并运行 SVC:
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score
svc_clf = SVC(kernel = 'linear').fit(X_train, y_train)
svc_scores = cross_val_score(svc_clf, X_train, y_train, cv=4)
- 和之前的部分一样,查看评分的平均值:
svc_scores.mean()
0.94795321637426899
- 通过从
sklearn.svm导入SVR,对支持向量回归执行相同操作,该模块也包含 SVC:
from sklearn.svm import SVR
- 然后编写必要的语法来拟合模型。它与 SVC 的语法几乎相同,只需将一些
c关键字替换为r:
svr_clf = SVR(kernel = 'linear').fit(X_train, y_train)
创建评分器
要创建评分器,你需要:
-
一个评分函数,它将
y_test(真实值)与y_pred(预测值)进行比较 -
确定高分是好是坏
在将 SVR 回归器传递给交叉验证之前,先通过提供两个元素来创建评分器:
- 在实际操作中,首先导入
make_scorer函数:
from sklearn.metrics import make_scorer
- 使用这个样本评分函数:
#Only works for this iris example with targets 0 and 1
def for_scorer(y_test, orig_y_pred):
y_pred = np.rint(orig_y_pred).astype(np.int) #rounds prediction to the nearest integer
return accuracy_score(y_test, y_pred)
np.rint 函数将预测值四舍五入到最接近的整数,希望它是目标值之一,即 0 或 1。astype 方法将预测值的类型更改为整数类型,因为原始目标是整数类型,并且在类型上保持一致性是更优的选择。四舍五入之后,评分函数使用你熟悉的旧版 accuracy_score 函数。
- 现在,确定较高的分数是否更好。更高的准确度更好,因此在这种情况下,较高的分数更好。在 scikit 代码中:
svr_to_class_scorer = make_scorer(for_scorer, greater_is_better=True)
- 最后,使用一个新的参数——评分参数,运行交叉验证:
svr_scores = cross_val_score(svr_clf, X_train, y_train, cv=4, scoring = svr_to_class_scorer)
- 计算均值:
svr_scores.mean()
0.94663742690058483
SVR 回归器基础的分类器与传统的 SVC 分类器在准确率评分上相似。
它是如何工作的...
你可能会问,为什么我们从目标集合中去掉了类 2?
其原因在于,为了使用回归模型,我们的目标必须是预测一个实数。类别必须具备实数性质:即它们是有序的(非正式地说,如果我们有三个有序的类别 x、y、z,并且 x < y 且 y < z,那么 x < z)。通过去掉第三个类别,剩下的花类(Setosa 和 Versicolor)根据我们发明的属性变得有序:Setosaness 或 Versicolorness。
下次遇到类别时,你可以考虑它们是否可以排序。例如,如果数据集包含鞋码,那么它们可以排序,且可以应用回归模型,尽管没有人会有 12.125 号鞋。
还有更多...
线性与非线性
线性算法涉及直线或超平面。超平面是任何 n 维空间中的平面,它们往往易于理解和解释,因为它们涉及比率(带有偏移量)。一些始终单调增加或减少的函数可以通过变换映射到线性函数。例如,指数增长可以通过对数变换映射为一条直线。
非线性算法往往更难向同事和投资者解释,但非线性决策树集成通常表现得很好。我们之前探讨过的 KNN 就是一个非线性算法。在某些情况下,为了提高准确性,某些函数的增减方式不符合直观也可以被接受。
尝试一个简单的 SVC 并使用多项式核,如下所示:
from sklearn.svm import SVC #Usual import of SVC
svc_poly_clf = SVC(kernel = 'poly', degree= 3).fit(X_train, y_train) #Polynomial Kernel of Degree 3
三次多项式核在二维空间中看起来像一条立方曲线。它能带来稍微更好的拟合效果,但请注意,它可能比在整个欧几里得空间中行为一致的线性核更难向他人解释:
svc_poly_scores = cross_val_score(svc_clf, X_train, y_train, cv=4)
svc_poly_scores.mean()
0.95906432748538006
黑箱与非黑箱
为了提高效率,我们并没有非常详细地检查所使用的分类算法。当我们比较支持向量分类器(SVC)和逻辑回归时,我们选择了 SVM。那时,这两种算法都是黑箱,因为我们并不了解任何内部细节。一旦我们决定专注于 SVM,就可以继续计算分离超平面的系数,优化 SVM 的超参数,使用 SVM 处理大数据,并进行其他处理。由于其卓越的性能,SVM 值得我们投入时间。
可解释性
一些机器学习算法比其他算法更容易理解。这些算法通常也更容易向他人解释。例如,线性回归是众所周知的,容易理解,并能向潜在的投资者解释。SVM 则更加难以完全理解。
我的总体建议是:如果 SVM 在某个特定数据集上非常有效,尝试提高你在该特定问题情境中的 SVM 可解释性。同时,考虑某种方式将算法合并,比如使用线性回归作为 SVM 的输入。这样,你可以兼得两者的优点。
然而,这真的取决于具体情况。线性 SVM 相对简单,容易可视化和理解。将线性回归与 SVM 合并可能会使事情变得复杂。你可以通过并排比较它们来开始。
然而,如果你无法理解支持向量机(SVM)的数学细节和实践过程,请对自己宽容些,因为机器学习更关注预测性能,而非传统统计学。
一个管道
在编程中,管道是一个按顺序连接的过程集,其中一个过程的输出作为下一个过程的输入:

你可以用不同的过程替换流程中的任何一个步骤,也许这个替代的步骤在某些方面更好,而不会破坏整个系统。对于中间步骤的模型,你可以使用 SVC 或逻辑回归:

你还可以跟踪分类器本身,并从分类器构建一个流程图。以下是一个跟踪 SVC 分类器的管道:

在接下来的章节中,我们将看到 scikit-learn 是如何使用管道这一直观概念的。到目前为止,我们使用了一个简单的管道:训练、预测、测试。
第二章:模型前工作流与预处理
在本章中,我们将看到以下内容:
-
为玩具分析创建样本数据
-
将数据缩放到标准正态分布
-
通过阈值创建二元特征
-
处理分类变量
-
通过各种策略填补缺失值
-
存在离群值时的线性模型
-
使用管道将所有内容整合起来
-
使用高斯过程进行回归
-
使用 SGD 进行回归
引言
什么是数据,我们对数据的处理目的是什么?
一个简单的答案是,我们试图将数据点放在纸上,将其绘制成图,思考并寻找能够很好地近似数据的简单解释。简单的几何线 F=ma(力与加速度成正比)解释了数百年的大量噪声数据。我有时倾向于将数据科学看作是数据压缩。
有时,当机器只接受输赢结果(例如玩跳棋的游戏结果)并进行训练时,我认为这就是人工智能。在这种情况下,它从未被明确教导如何玩游戏以获得胜利。
本章讨论了在 scikit-learn 中的数据预处理。你可以向数据集提问的问题如下:
-
数据集是否存在缺失值?
-
数据集中是否存在离群值(远离其他点的值)?
-
数据中的变量是什么类型的?它们是连续变量还是分类变量?
-
连续变量的分布是什么样的?数据集中的某些变量是否可以用正态分布(钟形曲线)来描述?
-
是否可以将任何连续变量转化为分类变量以简化处理?(如果数据的分布只有少数几个特定值,而不是类似连续范围的值,这种情况通常成立。)
-
涉及的变量单位是什么?你是否会在选择的机器学习算法中混合这些变量?
这些问题可能有简单或复杂的答案。幸运的是,你会多次提问,甚至在同一个数据集上,也会不断练习如何回答机器学习中的预处理问题。
此外,我们还将了解管道:一种很好的组织工具,确保我们在训练集和测试集上执行相同的操作,避免出错并且工作量相对较少。我们还将看到回归示例:随机梯度下降(SGD)和高斯过程。
为玩具分析创建样本数据
如果可能,使用你自己的一些数据来学习本书中的内容。如果你无法这样做,我们将学习如何使用 scikit-learn 创建玩具数据。scikit-learn 的伪造、理论构建的数据本身非常有趣。
准备工作
与获取内置数据集、获取新数据集和创建样本数据集类似,所使用的函数遵循 make_* 命名约定。为了明确,这些数据完全是人工合成的:
from sklearn import datasets
datasets.make_*?
datasets.make_biclusters
datasets.make_blobs
datasets.make_checkerboard
datasets.make_circles
datasets.make_classification
...
为了省略输入,导入datasets模块为d,numpy为np:
import sklearn.datasets as d
import numpy as np
如何实现...
本节将带你逐步创建几个数据集。除了示例数据集外,这些数据集将贯穿整本书,用于创建具有算法所需特征的数据。
创建回归数据集
- 首先是可靠的——回归:
reg_data = d.make_regression()
默认情况下,这将生成一个包含 100 x 100 矩阵的元组——100 个样本和 100 个特征。然而,默认情况下,只有 10 个特征负责目标数据的生成。元组的第二个成员是目标变量。实际上,也可以更深入地参与回归数据的生成。
- 例如,为了生成一个 1000 x 10 的矩阵,其中五个特征负责目标的创建,偏置因子为 1.0,并且有两个目标,可以运行以下命令:
complex_reg_data = d.make_regression(1000, 10, 5, 2, 1.0)
complex_reg_data[0].shape
(1000L, 10L)
创建一个不平衡的分类数据集
分类数据集也非常容易创建。创建一个基本的分类数据集很简单,但基本情况在实践中很少见——大多数用户不会转换,大多数交易不是欺诈性的,等等。
- 因此,探索不平衡数据集上的分类是非常有用的:
classification_set = d.make_classification(weights=[0.1])
np.bincount(classification_set[1])
array([10, 90], dtype=int64)
创建聚类数据集
聚类也会涉及到。实际上,有多个函数可以创建适用于不同聚类算法的数据集。
- 例如,blobs 非常容易创建,并且可以通过 k-means 来建模:
blobs_data, blobs_target = d.make_blobs()
- 这将看起来像这样:
import matplotlib.pyplot as plt
%matplotlib inline
#Within an Ipython notebook
plt.scatter(blobs_data[:,0],blobs_data[:,1],c = blobs_target)

它是如何工作的...
让我们通过查看源代码(做了一些修改以便清晰)来逐步了解 scikit-learn 如何生成回归数据集。任何未定义的变量假定其默认值为make_regression。
实际上,跟着做是非常简单的。首先,生成一个随机数组,大小由调用函数时指定:
X = np.random.randn(n_samples, n_features)
给定基本数据集后,接着生成目标数据集:
ground_truth = np.zeros((np_samples, n_target))
ground_truth[:n_informative, :] = 100*np.random.rand(n_informative, n_targets)
计算X和ground_truth的点积来得到最终的目标值。此时,如果有偏置,也会被加上:
y = np.dot(X, ground_truth) + bias
点积其实就是矩阵乘法。因此,我们的最终数据集将包含n_samples,即数据集的行数,和n_target,即目标变量的数量。
由于 NumPy 的广播机制,偏置可以是一个标量值,并且这个值将添加到每个样本中。最后,只需简单地加入噪声并打乱数据集。瞧,我们得到了一个非常适合回归测试的数据集。
将数据缩放至标准正态分布
推荐的预处理步骤是将列缩放至标准正态分布。标准正态分布可能是统计学中最重要的分布。如果你曾接触过统计学,你几乎肯定见过 z 分数。事实上,这就是这个方法的核心——将特征从其原始分布转换为 z 分数。
准备开始
缩放数据的操作非常有用。许多机器学习算法在特征存在不同尺度时表现不同(甚至可能出错)。例如,如果数据没有进行缩放,支持向量机(SVM)的表现会很差,因为它们在优化中使用距离函数,而如果一个特征的范围是 0 到 10,000,另一个特征的范围是 0 到 1,距离函数会出现偏差。
preprocessing模块包含了几个有用的特征缩放函数:
from sklearn import preprocessing
import numpy as np # we'll need it later
加载波士顿数据集:
from sklearn.datasets import load_boston
boston = load_boston()
X,y = boston.data, boston.target
如何实现...
- 继续使用波士顿数据集,运行以下命令:
X[:, :3].mean(axis=0) #mean of the first 3 features
array([ 3.59376071, 11.36363636, 11.13677866])
X[:, :3].std(axis=0)
array([ 8.58828355, 23.29939569, 6.85357058])
- 从一开始就可以学到很多东西。首先,第一个特征的均值最小,但变化范围比第三个特征更大。第二个特征的均值和标准差最大——它需要分布最广的数值:
X_2 = preprocessing.scale(X[:, :3])
X_2.mean(axis=0)
array([ 6.34099712e-17, -6.34319123e-16, -2.68291099e-15])
X_2.std(axis=0)
array([ 1., 1., 1.])
它是如何工作的...
中心化和缩放函数非常简单。它只是将均值相减并除以标准差。
通过图示和 pandas,第三个特征在变换之前如下所示:
pd.Series(X[:,2]).hist(bins=50)

变换后的样子如下:
pd.Series(preprocessing.scale(X[:, 2])).hist(bins=50)

x轴标签已更改。
除了函数,还有一个易于调用的中心化和缩放类,特别适用于与管道一起使用,管道在后面会提到。这个类在跨个别缩放时也特别有用:
my_scaler = preprocessing.StandardScaler()
my_scaler.fit(X[:, :3])
my_scaler.transform(X[:, :3]).mean(axis=0)
array([ 6.34099712e-17, -6.34319123e-16, -2.68291099e-15])
将特征缩放到均值为零、标准差为一并不是唯一有用的缩放类型。
预处理还包含一个MinMaxScaler类,它可以将数据缩放到某个特定范围内:
my_minmax_scaler = preprocessing.MinMaxScaler()
my_minmax_scaler.fit(X[:, :3])
my_minmax_scaler.transform(X[:, :3]).max(axis=0)
array([ 1., 1., 1.])
my_minmax_scaler.transform(X[:, :3]).min(axis=0)
array([ 0., 0., 0.])
很容易将MinMaxScaler类的最小值和最大值从默认的0和1更改为其他值:
my_odd_scaler = preprocessing.MinMaxScaler(feature_range=(-3.14, 3.14))
此外,另一种选择是归一化。归一化会将每个样本缩放到长度为 1。这与之前进行的缩放不同,之前是缩放特征。归一化的操作可以通过以下命令实现:
normalized_X = preprocessing.normalize(X[:, :3])
如果不清楚为什么这样做有用,可以考虑三组样本之间的欧几里得距离(相似度度量),其中一组样本的值为(1, 1, 0),另一组的值为(3, 3, 0),最后一组的值为(1, -1, 0)。
第一个和第三个向量之间的距离小于第一个和第二个向量之间的距离,尽管第一和第三是正交的,而第一和第二仅通过一个标量因子差异为三。由于距离通常用作相似度的度量,不对数据进行归一化可能会导致误导。
从另一个角度来看,尝试以下语法:
(normalized_X * normalized_X).sum(axis = 1)
array([ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1\.
...]
所有行都被归一化,并且由长度为 1 的向量组成。在三维空间中,所有归一化的向量都位于以原点为中心的球面上。剩下的信息是向量的方向,因为按定义,归一化是通过将向量除以其长度来完成的。然而,请始终记住,在执行此操作时,你已将原点设置为(0, 0, 0),并且你已将数组中的任何数据行转换为相对于此原点的向量。
通过阈值化创建二元特征
在上一节中,我们讨论了如何将数据转换为标准正态分布。现在,我们将讨论另一种完全不同的转换方法。我们不再通过处理分布来标准化它,而是故意丢弃数据;如果有充分的理由,这可能是一个非常聪明的举动。通常,在看似连续的数据中,会存在一些可以通过二元特征确定的间断点。
此外,请注意,在上一章中,我们将分类问题转化为回归问题。通过阈值化,我们可以将回归问题转化为分类问题。在一些数据科学的场景中,这种情况是存在的。
准备工作
创建二元特征和结果是一种非常有用的方法,但应谨慎使用。让我们使用波士顿数据集来学习如何将值转换为二元结果。首先,加载波士顿数据集:
import numpy as np
from sklearn.datasets import load_boston
boston = load_boston()
X, y = boston.data, boston.target.reshape(-1, 1)
如何实现...
与缩放类似,scikit-learn 中有两种方式可以将特征二值化:
-
preprocessing.binarize -
preprocessing.Binarizer
波士顿数据集的target变量是以千为单位的房屋中位数值。这个数据集适合用来测试回归和其他连续预测模型,但可以考虑一种情况,我们只需要预测房屋的价值是否超过整体均值。
- 为此,我们需要创建一个均值的阈值。如果值大于均值,返回
1;如果小于均值,返回0:
from sklearn import preprocessing
new_target = preprocessing.binarize(y,threshold=boston.target.mean())
new_target[:5]
array([[ 1.],
[ 0.],
[ 1.],
[ 1.],
[ 1.]])
- 这很简单,但让我们检查一下以确保它正常工作:
(y[:5] > y.mean()).astype(int)
array([[1],
[0],
[1],
[1],
[1]])
- 鉴于 NumPy 中操作的简单性,提出为什么要使用 scikit-learn 的内建功能是一个合理的问题。在将一切通过管道组合起来这一节中介绍的管道将有助于解释这一点;为了预见到这一点,让我们使用
Binarizer类:
binar = preprocessing.Binarizer(y.mean())
new_target = binar.fit_transform(y)
new_target[:5]
array([[ 1.],
[ 0.],
[ 1.],
[ 1.],
[ 1.]])
还有更多……
让我们也来了解稀疏矩阵和fit方法。
稀疏矩阵
稀疏矩阵的特殊之处在于零值并不被存储;这是为了节省内存空间。这样会为二值化器带来问题,因此,为了应对这一问题,针对稀疏矩阵,二值化器的特殊条件是阈值不能小于零:
from scipy.sparse import coo
spar = coo.coo_matrix(np.random.binomial(1, .25, 100))
preprocessing.binarize(spar, threshold=-1)
ValueError: Cannot binarize a sparse matrix with threshold < 0
fit方法
fit方法是针对二值化转换存在的,但它不会对任何东西进行拟合;它只会返回该对象。该对象会存储阈值,并准备好进行transform方法。
处理分类变量
类别变量是一个问题。一方面,它们提供了有价值的信息;另一方面,它们很可能是文本——无论是实际的文本还是与文本对应的整数——例如查找表中的索引。
所以,显然我们需要将文本表示为整数,以便于模型处理,但不能仅仅使用 ID 字段或天真地表示它们。这是因为我们需要避免类似于通过阈值创建二进制特征食谱中出现的问题。如果我们处理的是连续数据,它必须被解释为连续数据。
准备工作
波士顿数据集对于本节不适用。虽然它对于特征二值化很有用,但不足以从类别变量中创建特征。为此,鸢尾花数据集就足够了。
为了使其生效,问题需要彻底转变。设想一个问题,目标是预测花萼宽度;在这种情况下,花卉的物种可能作为一个特征是有用的。
如何做……
- 让我们先整理数据:
from sklearn import datasets
import numpy as np
iris = datasets.load_iris()
X = iris.data
y = iris.target
- 将
X和y,所有数值数据,放在一起。使用 scikit-learn 创建一个编码器来处理y列的类别:
from sklearn import preprocessing
cat_encoder = preprocessing.OneHotEncoder()
cat_encoder.fit_transform(y.reshape(-1,1)).toarray()[:5]
array([[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.]])
它是如何工作的……
编码器为每个类别变量创建附加特征,返回的值是一个稀疏矩阵。根据定义,结果是一个稀疏矩阵;新特征的每一行除了与特征类别关联的列外,其他位置都是0。因此,将这些数据存储为稀疏矩阵是合理的。现在,cat_encoder是一个标准的 scikit-learn 模型,这意味着它可以再次使用:
cat_encoder.transform(np.ones((3, 1))).toarray()
array([[ 0., 1., 0.],
[ 0., 1., 0.],
[ 0., 1., 0.]])
在上一章中,我们将一个分类问题转化为回归问题。在这里,有三列数据:
-
第一列是
1,如果花是 Setosa,则为1,否则为0。 -
第二列是
1,如果花是 Versicolor,则为1,否则为0。 -
第三列是
1,如果花是 Virginica,则为1,否则为0。
因此,我们可以使用这三列中的任何一列来创建与上一章类似的回归;我们将执行回归以确定花卉的 Setosa 程度作为一个实数。如果我们对第一列进行二分类,这就是分类中的问题陈述,判断花卉是否是 Setosa。
scikit-learn 具有执行此类型的多输出回归的能力。与多类分类相比,让我们尝试一个简单的例子。
导入岭回归正则化线性模型。由于它是正则化的,通常表现得非常稳定。实例化一个岭回归器类:
from sklearn.linear_model import Ridge
ridge_inst = Ridge()
现在导入一个多输出回归器,将岭回归器实例作为参数:
from sklearn.multioutput import MultiOutputRegressor
multi_ridge = MultiOutputRegressor(ridge_inst, n_jobs=-1)
从本食谱前面的部分,将目标变量y转换为三部分目标变量y_multi,并使用OneHotEncoder()。如果X和y是管道的一部分,管道将分别转换训练集和测试集,这是更可取的做法:
from sklearn import preprocessing
cat_encoder = preprocessing.OneHotEncoder()
y_multi = cat_encoder.fit_transform(y.reshape(-1,1)).toarray()
创建训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y_multi, stratify=y, random_state= 7)
拟合多输出估计器:
multi_ridge.fit(X_train, y_train)
MultiOutputRegressor(estimator=Ridge(alpha=1.0, copy_X=True, fit_intercept=True, max_iter=None,
normalize=False, random_state=None, solver='auto', tol=0.001),
n_jobs=-1)
在测试集上预测多输出目标:
y_multi_pre = multi_ridge.predict(X_test)
y_multi_pre[:5]
array([[ 0.81689644, 0.36563058, -0.18252702],
[ 0.95554968, 0.17211249, -0.12766217],
[-0.01674023, 0.36661987, 0.65012036],
[ 0.17872673, 0.474319 , 0.34695427],
[ 0.8792691 , 0.14446485, -0.02373395]])
使用前面配方中的 binarize 函数将每个实数转换为整数 0 或 1:
from sklearn import preprocessing
y_multi_pred = preprocessing.binarize(y_multi_pre,threshold=0.5)
y_multi_pred[:5]
array([[ 1., 0., 0.],
[ 1., 0., 0.],
[ 0., 0., 1.],
[ 0., 0., 0.],
[ 1., 0., 0.]])
我们可以使用 roc_auc_score 来衡量整体的多输出性能:
from sklearn.metrics import roc_auc_score
roc_auc_score(y_test, y_multi_pre)
0.91987179487179482
或者,我们可以逐种花朵类型、逐列进行:
from sklearn.metrics import accuracy_score
print ("Multi-Output Scores for the Iris Flowers: ")
for column_number in range(0,3):
print ("Accuracy score of flower " + str(column_number),accuracy_score(y_test[:,column_number], y_multi_pred[:,column_number]))
print ("AUC score of flower " + str(column_number),roc_auc_score(y_test[:,column_number], y_multi_pre[:,column_number]))
print ("")
Multi-Output Scores for the Iris Flowers:
('Accuracy score of flower 0', 1.0)
('AUC score of flower 0', 1.0)
('Accuracy score of flower 1', 0.73684210526315785)
('AUC score of flower 1', 0.76923076923076927)
('Accuracy score of flower 2', 0.97368421052631582)
('AUC score of flower 2', 0.99038461538461542)
还有更多……
在前面的多输出回归中,你可能会担心虚拟变量陷阱:输出之间的共线性。在不删除任何输出列的情况下,你假设存在第四种选择:即花朵可以不是三种类型中的任何一种。为了避免陷阱,删除最后一列,并假设花朵必须是三种类型之一,因为我们没有任何训练样本显示花朵不是三种类型中的一种。
在 scikit-learn 和 Python 中还有其他方法可以创建分类变量。如果你希望将项目的依赖项仅限于 scikit-learn,并且有一个相对简单的编码方案,DictVectorizer 类是一个不错的选择。然而,如果你需要更复杂的分类编码,patsy 是一个非常好的选择。
DictVectorizer 类
另一个选择是使用 DictVectorizer 类。这可以直接将字符串转换为特征:
from sklearn.feature_extraction import DictVectorizer
dv = DictVectorizer()
my_dict = [{'species': iris.target_names[i]} for i in y]
dv.fit_transform(my_dict).toarray()[:5]
array([[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.]])
通过各种策略填充缺失值
数据填充在实践中至关重要,幸运的是有很多方法可以处理它。在本配方中,我们将查看几种策略。然而,请注意,可能还有其他方法更适合你的情况。
这意味着 scikit-learn 具备执行常见填充操作的能力;它会简单地对现有数据应用一些变换并填充缺失值。然而,如果数据集缺失数据,并且我们知道这种缺失数据的原因——例如服务器响应时间在 100 毫秒后超时——那么通过其他包采用统计方法可能会更好,比如通过 PyMC 进行的贝叶斯处理,或通过 Lifelines 进行的危险模型,或是自定义的处理方法。
准备好
学习如何输入缺失值时,首先要做的就是创建缺失值。NumPy 的掩码处理将使这变得极其简单:
from sklearn import datasets
import numpy as np
iris = datasets.load_iris()
iris_X = iris.data
masking_array = np.random.binomial(1, .25,iris_X.shape).astype(bool)
iris_X[masking_array] = np.nan
为了稍微澄清一下,如果你对 NumPy 不太熟悉,在 NumPy 中可以使用其他数组来索引数组。所以,为了创建随机缺失数据,创建了一个与鸢尾花数据集形状相同的随机布尔数组。然后,可以通过掩码数组进行赋值。需要注意的是,由于使用了随机数组,因此你的 masking_array 可能与此处使用的不同。
为了确保这有效,请使用以下命令(由于我们使用了随机掩码,它可能不会直接匹配):
masking_array[:5]
array([[ True, False, False, True],
[False, False, False, False],
[False, False, False, False],
[ True, False, False, False],
[False, False, False, True]], dtype=bool)
iris_X [:5]
array([[ nan, 3.5, 1.4, nan],
[ 4.9, 3\. , 1.4, 0.2],
[ 4.7, 3.2, 1.3, 0.2],
[ nan, 3.1, 1.5, 0.2],
[ 5\. , 3.6, 1.4, nan]])
如何做……
- 本书中的一个常见主题(由于 scikit-learn 中的主题)是可重用的类,这些类能够拟合和转换数据集,随后可以用来转换未见过的数据集。如下所示:
from sklearn import preprocessing
impute = preprocessing.Imputer()
iris_X_prime = impute.fit_transform(iris_X)
iris_X_prime[:5]
array([[ 5.82616822, 3.5 , 1.4 , 1.22589286],
[ 4.9 , 3\. , 1.4 , 0.2 ],
[ 4.7 , 3.2 , 1.3 , 0.2 ],
[ 5.82616822, 3.1 , 1.5 , 0.2 ],
[ 5\. , 3.6 , 1.4 , 1.22589286]])
- 注意
[0, 0]位置的差异:
iris_X_prime[0, 0]
5.8261682242990664
iris_X[0, 0]
nan
它是如何工作的...
填充操作通过采用不同的策略进行。默认值是均值,但总共有以下几种策略:
-
mean(默认值) -
median(中位数) -
most_frequent(众数)
scikit-learn 将使用所选策略计算数据集中每个非缺失值的值,然后简单地填充缺失值。例如,要使用中位数策略重新执行鸢尾花示例,只需用新策略重新初始化填充器:
impute = preprocessing.Imputer(strategy='median')
iris_X_prime = impute.fit_transform(iris_X)
iris_X_prime[:5]
array([[ 5.8, 3.5, 1.4, 1.3],
[ 4.9, 3\. , 1.4, 0.2],
[ 4.7, 3.2, 1.3, 0.2],
[ 5.8, 3.1, 1.5, 0.2],
[ 5\. , 3.6, 1.4, 1.3]])
如果数据中缺少值,那么其他地方可能也存在数据质量问题。例如,在上面提到的 如何操作... 部分中,np.nan(默认的缺失值)被用作缺失值,但缺失值可以用多种方式表示。考虑一种情况,缺失值是 -1。除了计算缺失值的策略外,还可以为填充器指定缺失值。默认值是 nan,它会处理 np.nan 值。
要查看此示例,请将 iris_X 修改为使用 -1 作为缺失值。这听起来很疯狂,但由于鸢尾花数据集包含的是永远可能测量的数据,许多人会用 -1 来填充缺失值,以表示这些数据缺失:
iris_X[np.isnan(iris_X)] = -1
iris_X[:5]
填充这些缺失值的方法非常简单,如下所示:
impute = preprocessing.Imputer(missing_values=-1)
iris_X_prime = impute.fit_transform(iris_X)
iris_X_prime[:5]
array([[ 5.1 , 3.5 , 1.4 , 0.2 ],
[ 4.9 , 3\. , 1.4 , 0.2 ],
[ 4.7 , 3.2 , 1.3 , 0.2 ],
[ 5.87923077, 3.1 , 1.5 , 0.2 ],
[ 5\. , 3.6 , 1.4 , 0.2 ]])
还有更多...
Pandas 也提供了一种填充缺失数据的功能。它可能更加灵活,但也较少可重用:
import pandas as pd
iris_X_prime = np.where(pd.DataFrame(iris_X).isnull(),-1,iris_X)
iris_X_prime[:5]
array([[-1\. , 3.5, 1.4, -1\. ],
[ 4.9, 3\. , 1.4, 0.2],
[ 4.7, 3.2, 1.3, 0.2],
[-1\. , 3.1, 1.5, 0.2],
[ 5\. , 3.6, 1.4, -1\. ]])
为了说明其灵活性,fillna 可以传入任何类型的统计量,也就是说,策略可以更加随意地定义:
pd.DataFrame(iris_X).fillna(-1)[:5].values
array([[-1\. , 3.5, 1.4, -1\. ],
[ 4.9, 3\. , 1.4, 0.2],
[ 4.7, 3.2, 1.3, 0.2],
[-1\. , 3.1, 1.5, 0.2],
[ 5\. , 3.6, 1.4, -1\. ]])
存在离群点的线性模型
在本示例中,我们将尝试使用 Theil-Sen 估计器来处理一些离群点,而不是传统的线性回归。
准备工作
首先,创建一条斜率为 2 的数据线:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
num_points = 100
x_vals = np.arange(num_points)
y_truth = 2 * x_vals
plt.plot(x_vals, y_truth)

给数据添加噪声,并将其标记为 y_noisy:
y_noisy = y_truth.copy()
#Change y-values of some points in the line
y_noisy[20:40] = y_noisy[20:40] * (-4 * x_vals[20:40]) - 100
plt.title("Noise in y-direction")
plt.xlim([0,100])
plt.scatter(x_vals, y_noisy,marker='x')

如何操作...
- 导入
LinearRegression和TheilSenRegressor。使用原始线作为测试集y_truth对估计器进行评分:
from sklearn.linear_model import LinearRegression, TheilSenRegressor
from sklearn.metrics import r2_score, mean_absolute_error
named_estimators = [('OLS ', LinearRegression()), ('TSR ', TheilSenRegressor())]
for num_index, est in enumerate(named_estimators):
y_pred = est[1].fit(x_vals.reshape(-1, 1),y_noisy).predict(x_vals.reshape(-1, 1))
print (est[0], "R-squared: ", r2_score(y_truth, y_pred), "Mean Absolute Error", mean_absolute_error(y_truth, y_pred))
plt.plot(x_vals, y_pred, label=est[0])
('OLS ', 'R-squared: ', 0.17285546630270587, 'Mean Absolute Error', 44.099173357335729)
('TSR ', 'R-squared: ', 0.99999999928066519, 'Mean Absolute Error', 0.0013976236426276058)
- 绘制这些线条。请注意,普通最小二乘法(OLS)与真实线
y_truth相差甚远,而 Theil-Sen 则与真实线重叠:
plt.plot(x_vals, y_truth, label='True line')
plt.legend(loc='upper left')

- 绘制数据集和估计的线条:
for num_index, est in enumerate(named_estimators):
y_pred = est[1].fit(x_vals.reshape(-1, 1),y_noisy).predict(x_vals.reshape(-1, 1))
plt.plot(x_vals, y_pred, label=est[0])
plt.legend(loc='upper left')
plt.title("Noise in y-direction")
plt.xlim([0,100])
plt.scatter(x_vals, y_noisy,marker='x', color='red')

它是如何工作的...
TheilSenRegressor 是一种鲁棒估计器,在存在离群点的情况下表现良好。它使用中位数的测量,更加稳健于离群点。在 OLS 回归中,误差会被平方,因此平方误差可能会导致好的结果变差。
你可以在 scikit-learn 版本 0.19.0 中尝试几种鲁棒估计器:
from sklearn.linear_model import Ridge, LinearRegression, TheilSenRegressor, RANSACRegressor, ElasticNet, HuberRegressor
from sklearn.metrics import r2_score, mean_absolute_error
named_estimators = [('OLS ', LinearRegression()),
('Ridge ', Ridge()),('TSR ', TheilSenRegressor()),('RANSAC', RANSACRegressor()),('ENet ',ElasticNet()),('Huber ',HuberRegressor())]
for num_index, est in enumerate(named_estimators):
y_pred = est[1].fit(x_vals.reshape(-1, 1),y_noisy).predict(x_vals.reshape(-1, 1))
print (est[0], "R-squared: ", r2_score(y_truth, y_pred), "Mean Absolute Error", mean_absolute_error(y_truth, y_pred))
('OLS ', 'R-squared: ', 0.17285546630270587, 'Mean Absolute Error', 44.099173357335729)
('Ridge ', 'R-squared: ', 0.17287378039132695, 'Mean Absolute Error', 44.098937961740631)
('TSR ', 'R-squared: ', 0.99999999928066519, 'Mean Absolute Error', 0.0013976236426276058)
('RANSAC', 'R-squared: ', 1.0, 'Mean Absolute Error', 1.0236256287043944e-14)
('ENet ', 'R-squared: ', 0.17407294649885618, 'Mean Absolute Error', 44.083506446776603)
('Huber ', 'R-squared: ', 0.99999999999404421, 'Mean Absolute Error', 0.00011755074198335526)
如你所见,在存在异常值的情况下,稳健的线性估计器 Theil-Sen、随机样本一致性(RANSAC)和 Huber 回归器的表现优于其他线性回归器。
将一切整合到管道中
现在我们已经使用了管道和数据转换技术,我们将通过一个更复杂的例子,结合之前的几个实例,演示如何将它们组合成一个管道。
准备工作
在本节中,我们将展示管道的更多强大功能。当我们之前用它来填补缺失值时,只是简单体验了一下;这里,我们将把多个预处理步骤链起来,展示管道如何去除额外的工作。
让我们简单加载鸢尾花数据集,并给它添加一些缺失值:
from sklearn.datasets import load_iris
from sklearn.datasets import load_iris
import numpy as np
iris = load_iris()
iris_data = iris.data
mask = np.random.binomial(1, .25, iris_data.shape).astype(bool)
iris_data[mask] = np.nan
iris_data[:5]
array([[ nan, 3.5, 1.4, 0.2],
[ 4.9, 3\. , 1.4, nan],
[ nan, 3.2, nan, nan],
[ nan, nan, 1.5, 0.2],
[ nan, 3.6, 1.4, 0.2]])
如何实现…
本章的目标是首先填补iris_data的缺失值,然后对修正后的数据集执行 PCA。你可以想象(我们稍后会做)这个工作流程可能需要在训练数据集和保留集之间拆分;管道将使这更容易,但首先我们需要迈出小小的一步。
- 让我们加载所需的库:
from sklearn import pipeline, preprocessing, decomposition
- 接下来,创建
imputer和pca类:
pca = decomposition.PCA()
imputer = preprocessing.Imputer()
- 现在我们已经有了需要的类,我们可以将它们加载到
Pipeline中:
pipe = pipeline.Pipeline([('imputer', imputer), ('pca', pca)])
iris_data_transformed = pipe.fit_transform(iris_data)
iris_data_transformed[:5]
array([[-2.35980262, 0.6490648 , 0.54014471, 0.00958185],
[-2.29755917, -0.00726168, -0.72879348, -0.16408532],
[-0.00991161, 0.03354407, 0.01597068, 0.12242202],
[-2.23626369, 0.50244737, 0.50725722, -0.38490096],
[-2.36752684, 0.67520604, 0.55259083, 0.1049866 ]])
如果我们使用单独的步骤,这需要更多的管理。与每个步骤都需要进行拟合转换不同,这个步骤只需执行一次,更不用说我们只需要跟踪一个对象!
它是如何工作的…
希望大家已经明白,每个管道中的步骤都是通过元组列表传递给管道对象的,第一个元素是名称,第二个元素是实际的对象。在幕后,当调用像fit_transform这样的函数时,这些步骤会在管道对象上循环执行。
话虽如此,确实有一些快速且简便的方式来创建管道,就像我们之前有一种快速的方式来执行缩放操作一样,尽管我们可以使用StandardScaler来获得更强大的功能。pipeline函数将自动为管道对象创建名称:
pipe2 = pipeline.make_pipeline(imputer, pca)
pipe2.steps
[('imputer',
Imputer(axis=0, copy=True, missing_values='NaN', strategy='mean', verbose=0)),
('pca',
PCA(copy=True, iterated_power='auto', n_components=None, random_state=None,
svd_solver='auto', tol=0.0, whiten=False))]
这是在更详细的方法中创建的相同对象:
iris_data_transformed2 = pipe2.fit_transform(iris_data)
iris_data_transformed2[:5]
array([[-2.35980262, 0.6490648 , 0.54014471, 0.00958185],
[-2.29755917, -0.00726168, -0.72879348, -0.16408532],
[-0.00991161, 0.03354407, 0.01597068, 0.12242202],
[-2.23626369, 0.50244737, 0.50725722, -0.38490096],
[-2.36752684, 0.67520604, 0.55259083, 0.1049866 ]])
还有更多…
我们刚刚以很高的层次走过了管道,但不太可能希望直接应用基本的转换。因此,可以通过set_params方法访问管道中每个对象的属性,其中参数遵循<step_name>__<step_parameter>的约定。例如,假设我们想把pca对象改为使用两个主成分:
pipe2.set_params(pca__n_components=2)
Pipeline(steps=[('imputer', Imputer(axis=0, copy=True, missing_values='NaN', strategy='mean', verbose=0)), ('pca', PCA(copy=True, iterated_power='auto', n_components=2, random_state=None,
svd_solver='auto', tol=0.0, whiten=False))])
请注意,前面的输出中有n_components=2。作为测试,我们可以输出之前已经做过两次的相同变换,输出将是一个 N x 2 的矩阵:
iris_data_transformed3 = pipe2.fit_transform(iris_data)
iris_data_transformed3[:5]
array([[-2.35980262, 0.6490648 ],
[-2.29755917, -0.00726168],
[-0.00991161, 0.03354407],
[-2.23626369, 0.50244737],
[-2.36752684, 0.67520604]])
使用高斯过程进行回归
在这个例子中,我们将使用高斯过程进行回归。在线性模型部分,我们将看到如何通过贝叶斯岭回归表示系数的先验信息。
在高斯过程中,关注的是方差而非均值。然而,我们假设均值为 0,所以我们需要指定的是协方差函数。
基本设置类似于在典型回归问题中如何对系数设置先验。在高斯过程(Gaussian Process)中,可以对数据的函数形式设置先验,数据点之间的协方差用于建模数据,因此必须与数据相匹配。
高斯过程的一个大优点是它们可以进行概率预测:你可以获得预测的置信区间。此外,预测可以插值可用内核的观测值:回归的预测是平滑的,因此两个已知点之间的预测位于这两个点之间。
高斯过程的一个缺点是在高维空间中的效率较低。
准备就绪
那么,让我们使用一些回归数据,逐步了解高斯过程如何在 scikit-learn 中工作:
from sklearn.datasets import load_boston
boston = load_boston()
boston_X = boston.data
boston_y = boston.target
train_set = np.random.choice([True, False], len(boston_y),p=[.75, .25])
如何做到……
- 我们有数据,将创建一个 scikit-learn 的
GaussianProcessRegressor对象。让我们看看gpr对象:
sklearn.gaussian_process import GaussianProcessRegressor
gpr = GaussianProcessRegressor()
gpr
GaussianProcessRegressor(alpha=1e-10, copy_X_train=True, kernel=None,
n_restarts_optimizer=0, normalize_y=False,
optimizer='fmin_l_bfgs_b', random_state=None)
有几个重要的参数必须设置:
-
-
alpha:这是一个噪声参数。你可以为所有观测值指定一个噪声值,或者以 NumPy 数组的形式分配n个值,其中n是传递给gpr进行训练的训练集目标观测值的长度。 -
kernel:这是一个逼近函数的内核。在 scikit-learn 的早期版本中,默认的内核是径向基函数(RBF),我们将通过常量内核和 RBF 内核构建一个灵活的内核。 -
normalize_y:如果目标集的均值不为零,可以将其设置为 True。如果设置为 False,效果也相当不错。 -
n_restarts_optimizer:设置为 10-20 以供实际使用。该值表示优化内核时的迭代次数。
-
- 导入所需的内核函数并设置一个灵活的内核:
from sklearn.gaussian_process.kernels import RBF, ConstantKernel as CK
mixed_kernel = kernel = CK(1.0, (1e-4, 1e4)) * RBF(10, (1e-4, 1e4))
- 最后,实例化并拟合算法。请注意,
alpha对所有值都设置为5。我之所以选择这个数字,是因为它大约是目标值的四分之一:
gpr = GaussianProcessRegressor(alpha=5,
n_restarts_optimizer=20,
kernel = mixed_kernel)
gpr.fit(boston_X[train_set],boston_y[train_set])
- 将对未见数据的预测存储为
test_preds:
test_preds = gpr.predict(boston_X[~train_set])
- 绘制结果:
>from sklearn.model_selection import cross_val_predict
from matplotlib import pyplot as plt
%matplotlib inline
f, ax = plt.subplots(figsize=(10, 7), nrows=3)
f.tight_layout()
ax[0].plot(range(len(test_preds)), test_preds,label='Predicted Values');
ax[0].plot(range(len(test_preds)), boston_y[~train_set],label='Actual Values');
ax[0].set_title("Predicted vs Actuals")
ax[0].legend(loc='best')
ax[1].plot(range(len(test_preds)),test_preds - boston_y[~train_set]);
ax[1].set_title("Plotted Residuals")
ax[2].hist(test_preds - boston_y[~train_set]);
ax[2].set_title("Histogram of Residuals")

使用噪声参数进行交叉验证
你可能会想,这是否是最佳的噪声参数alpha=5?为了弄清楚这一点,尝试一些交叉验证。
- 首先,使用
alpha=5生成交叉验证得分。注意,cross_val_score对象中的评分器是neg_mean_absolute_error,因为该数据集的默认 R 方得分难以读取:
from sklearn.model_selection import cross_val_score
gpr5 = GaussianProcessRegressor(alpha=5,
n_restarts_optimizer=20,
kernel = mixed_kernel)
scores_5 = (cross_val_score(gpr5,
boston_X[train_set],
boston_y[train_set],
cv = 4,
scoring = 'neg_mean_absolute_error'))
- 查看
scores_5中的得分:
def score_mini_report(scores_list):
print "List of scores: ", scores_list
print "Mean of scores: ", scores_list.mean()
print "Std of scores: ", scores_list.std()
score_mini_report(scores_5)
List of scores: [ -4.10973995 -4.93446898 -3.78162 -13.94513686]
Mean of scores: -6.69274144767
Std of scores: 4.20818506589
注意,最后一次折叠中的得分与其他三次折叠不一样。
- 现在使用
alpha=7生成报告:
gpr7 = GaussianProcessRegressor(alpha=7,
n_restarts_optimizer=20,
kernel = mixed_kernel)
scores_7 = (cross_val_score(gpr7,
boston_X[train_set],
boston_y[train_set],
cv = 4,
scoring = 'neg_mean_absolute_error'))
score_mini_report(scores_7)
List of scores: [ -3.70606009 -4.92211642 -3.63887969 -14.20478333]
Mean of scores: -6.61795988295
Std of scores: 4.40992783912
- 这个得分看起来更好一些。现在,尝试将
alpha=7,并将normalize_y设置为True:
from sklearn.model_selection import cross_val_score
gpr7n = GaussianProcessRegressor(alpha=7,
n_restarts_optimizer=20,
kernel = mixed_kernel,
normalize_y=True)
scores_7n = (cross_val_score(gpr7n,
boston_X[train_set],
boston_y[train_set],
cv = 4,
scoring = 'neg_mean_absolute_error'))
score_mini_report(scores_7n)
List of scores: [-4.0547601 -4.91077385 -3.65226736 -9.05596047]
Mean of scores: -5.41844044809
Std of scores: 2.1487361839
- 这看起来更好,因为均值较高,标准差较低。让我们选择最后一个模型进行最终训练:
gpr7n.fit(boston_X[train_set],boston_y[train_set])
- 进行预测:
test_preds = gpr7n.predict(boston_X[~train_set])
- 可视化结果:

- 残差看起来更加集中。你也可以为
alpha传递一个 NumPy 数组:
gpr_new = GaussianProcessRegressor(alpha=boston_y[train_set]/4,
n_restarts_optimizer=20,
kernel = mixed_kernel)
- 这将产生以下图表:

数组 alphas 与 cross_val_score 不兼容,因此我无法通过查看最终图形并判断哪一个是最佳模型来选择该模型。所以,我们最终选择的模型是 gpr7n,并且设置了 alpha=7 和 normalize_y=True。
还有更多内容……
在这一切的背后,核函数计算了X中各点之间的协方差。它假设输入中相似的点应该导致相似的输出。高斯过程在置信度预测和光滑输出方面表现得非常好。(稍后我们将看到随机森林,尽管它们在预测方面非常准确,但并不会产生光滑的输出。)
我们可能需要了解我们估计值的不确定性。如果我们将 eval_MSE 参数设置为真,我们将得到 MSE 和预测值,从而可以进行预测。从机械学角度来看,返回的是预测值和 MSE 的元组:
test_preds, MSE = gpr7n.predict(boston_X[~train_set], return_std=True)
MSE[:5]
array([ 1.20337425, 1.43876578, 1.19910262, 1.35212445, 1.32769539])
如下图所示,绘制所有带误差条的预测:
f, ax = plt.subplots(figsize=(7, 5))
n = 133
rng = range(n)
ax.scatter(rng, test_preds[:n])
ax.errorbar(rng, test_preds[:n], yerr=1.96*MSE[:n])
ax.set_title("Predictions with Error Bars")
ax.set_xlim((-1, n));

在前面的代码中设置 n=20 以查看较少的点:

对于某些点,不确定性非常高。如你所见,许多给定点的估计值有很大的差异。然而,整体误差并不算太差。
使用 SGD 进行回归
在这个教程中,我们将首次体验随机梯度下降。我们将在回归中使用它。
准备就绪
SGD(随机梯度下降)通常是机器学习中的一个默默无闻的英雄。在许多算法背后,正是 SGD 在默默地执行工作。由于其简单性和速度,SGD 非常受欢迎——这两者在处理大量数据时是非常有用的。SGD 的另一个优点是,尽管它在许多机器学习算法的计算核心中扮演重要角色,但它之所以如此有效,是因为它能够简明地描述这一过程。归根结底,我们对数据应用一些变换,然后用损失函数将数据拟合到模型中。
如何实现……
- 如果 SGD 在大数据集上表现良好,我们应该尝试在一个相对较大的数据集上测试它:
from sklearn.datasets import make_regression
X, y = make_regression(int(1e6)) #1,000,000 rows
了解对象的组成和大小可能是值得的。幸运的是,我们处理的是 NumPy 数组,因此可以直接访问 nbytes。Python 内建的访问对象大小的方法对于 NumPy 数组不起作用。
- 该输出可能与系统有关,因此你可能无法得到相同的结果:
print "{:,}".format(X.nbytes)
800,000,000
- 为了获得一些人类的视角,我们可以将
nbytes转换为兆字节。大约每兆字节包含 100 万字节:
X.nbytes / 1e6
800
- 因此,每个数据点的字节数如下:
X.nbytes / (X.shape[0]*X.shape[1])
8
好吧,对于我们想要实现的目标,这样不显得有点杂乱无章吗?然而,了解如何获取你正在处理的对象的大小是很重要的。
- 所以,现在我们有了数据,我们可以简单地拟合一个
SGDRegressor:
from sklearn.linear_model import SGDRegressor
sgd = SGDRegressor()
train = np.random.choice([True, False], size=len(y), p=[.75, .25])
sgd.fit(X[train], y[train])
SGDRegressor(alpha=0.0001, average=False, epsilon=0.1, eta0=0.01,
fit_intercept=True, l1_ratio=0.15, learning_rate='invscaling',
loss='squared_loss', n_iter=5, penalty='l2', power_t=0.25,
random_state=None, shuffle=True, verbose=0, warm_start=False)
所以,我们有了另一个庞大的对象。现在要了解的主要内容是我们的损失函数是squared_loss,这与线性回归中的损失函数相同。还需要注意的是,shuffle会对数据进行随机打乱。如果你想打破一个可能存在的虚假相关性,这个功能会非常有用。使用X时,scikit-learn 会自动包含一列 1。
- 然后,我们可以像以前一样,使用 scikit-learn 一致的 API 进行预测。你可以看到我们实际上得到了一个非常好的拟合。几乎没有任何变化,且直方图看起来像是正态分布。
y_pred = sgd.predict(X[~train])
%matplotlib inline
import pandas as pd
pd.Series(y[~train] - y_pred).hist(bins=50)

它是如何工作的…
很明显,我们使用的虚拟数据集还不错,但你可以想象一些具有更大规模的数据集。例如,如果你在华尔街工作,在任何一天,某个市场上的交易量可能达到 20 亿笔。现在,想象一下你有一周或一年的数据。处理大量数据时,内存中的算法并不适用。
之所以通常比较困难,是因为要执行 SGD,我们需要在每一步计算梯度。梯度有任何高等微积分课程中的标准定义。
算法的要点是,在每一步中,我们计算一组新的系数,并用学习率和目标函数的结果更新它。在伪代码中,这可能看起来像这样:
while not converged:
w = w – learning_rate*gradient(cost(w))
相关变量如下:
-
w:这是系数矩阵。 -
learning_rate:这表示每次迭代时步长的大小。如果收敛效果不好,可能需要调整这个值。 -
gradient:这是二阶导数的矩阵。 -
cost:这是回归的平方误差。稍后我们会看到,这个代价函数可以适应分类任务。这种灵活性是 SGD 如此有用的一个原因。
这不会太难,除了梯度函数很昂贵这一点。随着系数向量的增大,计算梯度变得非常昂贵。对于每一次更新步骤,我们需要为数据中的每一个点计算一个新的权重,然后更新。SGD 的工作方式略有不同;与批量梯度下降的定义不同,我们将每次用新的数据点来更新参数。这个数据点是随机选取的,因此得名随机梯度下降。
关于 SGD 的最后一点是,它是一种元启发式方法,赋予了多种机器学习算法强大的能力。值得查阅一些关于元启发式方法在各种机器学习算法中的应用的论文。前沿解决方案可能就隐藏在这些论文中。
第三章:降维
在本章中,我们将涵盖以下食谱:
-
使用 PCA 进行降维
-
使用因子分析进行分解
-
使用核 PCA 进行非线性降维
-
使用截断 SVD 进行降维
-
使用分解进行分类与 DictionaryLearning
-
使用流形进行降维——t-SNE
-
测试通过管道减少维度的方法
介绍
在本章中,我们将减少输入到机器学习模型中的特征或输入数量。这是一个非常重要的操作,因为有时数据集有很多输入列,减少列数可以创建更简单的模型,减少计算能力的需求以进行预测。
本节使用的主要模型是主成分分析(PCA)。由于 PCA 的解释方差,您无需知道可以将数据集减少到多少特征。一个性能相似的模型是截断奇异值分解(truncated SVD)。最好首先选择一个线性模型,允许您知道可以将数据集减少到多少列,例如 PCA 或截断 SVD。
在本章后面,查看现代方法t 分布随机邻居嵌入(t-SNE),它使特征在低维度中更容易可视化。在最后一个食谱中,您可以检查一个复杂的管道和网格搜索,找到由降维与多个支持向量机组成的最佳复合估计器。
使用 PCA 进行降维
现在是时候将数学提升到一个新层次了!PCA 是本书中讨论的第一个相对高级的技术。到目前为止,所有的内容都只是简单的统计学,而 PCA 将统计学与线性代数结合起来,产生一个预处理步骤,有助于减少维度,这是简化模型的敌人。
准备工作
PCA 是 scikit-learn 中分解模块的一个成员。还有几种其他的分解方法,稍后将在本食谱中介绍。我们将使用鸢尾花数据集,但如果你使用自己的数据会更好:
from sklearn import datasets
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline
iris = datasets.load_iris()
iris_X = iris.data
y = iris.target
如何实现...
- 导入
decomposition模块:
from sklearn import decomposition
- 实例化一个默认的 PCA 对象:
pca = decomposition.PCA()
pca
PCA(copy=True, iterated_power='auto', n_components=None, random_state=None,
svd_solver='auto', tol=0.0, whiten=False)
- 与 scikit-learn 中的其他对象相比,PCA 对象所需的参数相对较少。现在 PCA 对象(一个 PCA 实例)已经创建,只需通过调用
fit_transform方法来转换数据,iris_X作为参数:
iris_pca = pca.fit_transform(iris_X)
iris_pca[:5]
array([[ -2.68420713e+00, 3.26607315e-01, -2.15118370e-02,
1.00615724e-03],
[ -2.71539062e+00, -1.69556848e-01, -2.03521425e-01,
9.96024240e-02],
[ -2.88981954e+00, -1.37345610e-01, 2.47092410e-02,
1.93045428e-02],
[ -2.74643720e+00, -3.11124316e-01, 3.76719753e-02,
-7.59552741e-02],
[ -2.72859298e+00, 3.33924564e-01, 9.62296998e-02,
-6.31287327e-02]])
- 现在 PCA 对象已经拟合完成,我们可以看到它在解释方差方面的效果如何(将在接下来的工作原理...部分中进行说明):
pca.explained_variance_ratio_
array([ 0.92461621, 0.05301557, 0.01718514, 0.00518309])
它是如何工作的...
PCA 有一个通用的数学定义,并在数据分析中有特定的应用案例。PCA 找到一组正交方向,这些方向表示原始数据矩阵。
通常,PCA 通过将原始数据集映射到一个新空间来工作,其中矩阵的每个新列向量都是正交的。从数据分析的角度来看,PCA 将数据的协方差矩阵转换为可以解释方差某些百分比的列向量。例如,使用鸢尾花数据集,92.5%的整体方差可以通过第一个分量来解释。
这非常有用,因为维度问题在数据分析中很常见。许多应用于高维数据集的算法会在初始训练时出现过拟合,从而失去对测试集的泛化能力。如果数据的绝大部分结构可以通过更少的维度忠实地表示,那么这通常被认为是值得的权衡:
pca = decomposition.PCA(n_components=2)
iris_X_prime = pca.fit_transform(iris_X)
iris_X_prime.shape
(150L, 2L)
现在我们的数据矩阵是 150 x 2,而不是 150 x 4。即便在减少维度为二之后,类别的可分性依然保持。我们可以看到这两维所表示的方差有多少:
pca.explained_variance_ratio_.sum()
0.97763177502480336
为了可视化 PCA 的效果,让我们绘制鸢尾花数据集的前两维,并展示 PCA 变换前后的对比图:
fig = plt.figure(figsize=(20,7))
ax = fig.add_subplot(121)
ax.scatter(iris_X[:,0],iris_X[:,1],c=y,s=40)
ax.set_title('Before PCA')
ax2 = fig.add_subplot(122)
ax2.scatter(iris_X_prime[:,0],iris_X_prime[:,1],c=y,s=40)
ax2.set_title('After PCA')

PCA对象也可以在一开始就考虑解释方差的数量。例如,如果我们希望至少解释 98%的方差,那么PCA对象将按如下方式创建:
pca = decomposition.PCA(n_components=.98)
iris_X_prime = pca.fit(iris_X).transform(iris_X)
pca.explained_variance_ratio_.sum()
0.99481691454981014
由于我们希望解释的方差稍微多于两个分量示例,因此包含了第三个分量。
即使最终数据的维度是二维或三维,这两三列也包含了所有四个原始列的信息。
还有更多...
建议在使用 PCA 之前进行缩放。操作步骤如下:
from sklearn import preprocessing
iris_X_scaled = preprocessing.scale(iris_X)
pca = decomposition.PCA(n_components=2)
iris_X_scaled = pca.fit_transform(iris_X_scaled)
这导致了如下图:
fig = plt.figure(figsize=(20,7))
ax = fig.add_subplot(121)
ax.scatter(iris_X_prime[:,0],iris_X_prime[:,1],c=y,s=40)
ax.set_title('Regular PCA')
ax2 = fig.add_subplot(122)
ax2.scatter(iris_X_scaled[:,0],iris_X_scaled[:,1],c=y,s=40)
ax2.set_title('Scaling followed by PCA')

看起来有点差。无论如何,如果你考虑使用 PCA,始终应该考虑使用缩放后的 PCA。最好能通过管道按如下方式进行缩放:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
pipe = Pipeline([('scaler', StandardScaler()), ('pca',decomposition.PCA(n_components=2))])
iris_X_scaled = pipe.fit_transform(iris_X)
使用管道可以防止错误,并减少复杂代码的调试工作量。
使用因子分析进行分解
因子分析是我们可以用来减少维度的另一种技术。然而,因子分析有前提假设,而 PCA 没有。基本假设是存在一些隐含特征,它们决定了数据集的特征。
这个方法将提取样本中的显式特征,以期理解独立变量和因变量。
准备就绪
为了比较 PCA 和因子分析,我们再次使用鸢尾花数据集,但我们首先需要加载FactorAnalysis类:
from sklearn import datasets
iris = datasets.load_iris()
iris_X = iris.data
from sklearn.decomposition import FactorAnalysis
如何做到这一点...
从编程角度来看,因子分析与 PCA 没有太大区别:
fa = FactorAnalysis(n_components=2)
iris_two_dim = fa.fit_transform(iris.data)
iris_two_dim[:5]
array([[-1.33125848, -0.55846779],
[-1.33914102, 0.00509715],
[-1.40258715, 0.307983 ],
[-1.29839497, 0.71854288],
[-1.33587575, -0.36533259]])
将以下图与上一节中的图进行比较:

由于因子分析是一个概率变换,我们可以检查不同的方面,例如模型下观测值的对数似然性,甚至更好的是,比较不同模型的对数似然性。
因子分析并非没有缺点。原因在于你不是在拟合一个模型来预测结果,而是将模型作为准备步骤来拟合。这并不是什么坏事,但当你在训练实际模型时,错误会被累积。
工作原理...
因子分析与之前讲解的 PCA 相似,然而它们之间有一个重要的区别。PCA 是数据的线性变换,转到一个不同的空间,在这个空间里,第一个主成分解释了数据的方差,而每个后续主成分与第一个主成分正交。
例如,你可以将 PCA 想象成将一个N维的数据集降到某个M维的空间,其中M < N。
另一方面,因子分析假设只有M个重要特征,这些特征的线性组合(加噪声)创建了N维度的数据集。换句话说,你不是在对结果变量做回归,而是在对特征做回归,以确定数据集的潜在因子。
此外,一个大缺点是你不知道可以将数据降到多少列。PCA 会提供解释方差的指标,以指导你完成这一过程。
使用核 PCA 进行非线性降维
统计学中的大多数技术本质上是线性的,因此为了捕捉非线性,我们可能需要应用一些变换。PCA 当然是线性变换。在本步骤中,我们将看一下应用非线性变换,然后应用 PCA 进行降维。
准备工作
如果数据总是线性可分,生活会变得非常简单,但不幸的是,数据并非总是如此。核主成分分析(Kernel PCA)可以帮助解决这个问题。数据首先通过核函数进行处理,将数据投影到一个不同的空间;然后,执行 PCA。
为了熟悉核函数,一个很好的练习是思考如何生成能够被核 PCA 中可用的核函数分离的数据。在这里,我们将使用余弦核来完成。这个步骤的理论内容会比之前的更多。
在开始之前,加载鸢尾花数据集:
from sklearn import datasets, decomposition
iris = datasets.load_iris()
iris_X = iris.data
如何操作...
余弦核通过比较在特征空间中表示的两个样本之间的角度来工作。当向量的大小扰动了用于比较样本的典型距离度量时,它就显得很有用。提醒一下,两个向量之间的余弦通过以下公式给出:

这意味着A和B之间的余弦是这两个向量的点积,通过各自的范数的乘积进行归一化。向量A和B的大小对这个计算没有影响。
所以,让我们回到鸢尾花数据集,使用它进行视觉对比:
kpca = decomposition.KernelPCA(kernel='cosine', n_components=2)
iris_X_prime = kpca.fit_transform(iris_X)
然后,展示结果:

结果看起来稍微好一些,尽管我们需要进行测量才能确认。
它是如何工作的...
除了余弦核,还有几种不同的核可供选择。你甚至可以编写自己的核函数。可用的核如下:
-
多项式(Poly)
-
RBF(径向基函数)
-
Sigmoid
-
余弦
-
预计算
还有一些选项依赖于核的选择。例如,degree 参数将为多项式核、RBF 核和 Sigmoid 核指定度数;此外,gamma 将影响 RBF 或多项式核。
SVM 中的食谱将更详细地介绍 RBF 核函数。
核方法非常适合创建可分性,但如果使用不当,也可能导致过拟合。确保适当进行训练和测试。
幸运的是,现有的核是平滑的、连续的且可微的函数。它们不会像回归树那样产生锯齿状的边缘。
使用截断 SVD 来减少维度
截断 SVD 是一种矩阵分解技术,将矩阵M分解为三个矩阵U、Σ和V。这与 PCA 非常相似,不同之处在于,SVD 的分解是在数据矩阵上进行的,而 PCA 的分解则是在协方差矩阵上进行的。通常,SVD 在幕后用于找到矩阵的主成分。
准备工作
截断 SVD 不同于常规 SVD,它产生的分解结果列数等于指定的截断数。例如,对于一个n x n的矩阵,SVD 将产生n列的矩阵,而截断 SVD 将产生指定列数的矩阵。通过这种方式,维度得以减少。这里我们将再次使用鸢尾花数据集,供你与 PCA 结果进行比较:
from sklearn.datasets import load_iris
iris = load_iris()
iris_X = iris.data
y = iris.target
如何操作...
这个对象与我们使用的其他对象形式相同。
首先,我们将导入所需的对象,然后拟合模型并检查结果:
from sklearn.decomposition import TruncatedSVD
svd = TruncatedSVD(2)
iris_transformed = svd.fit_transform(iris_X)
然后,展示结果:

结果看起来相当不错。与 PCA 一样,有explained_variance_ratio_的解释方差:
svd.explained_variance_ratio_
array([ 0.53028106, 0.44685765])
它是如何工作的...
现在我们已经了解了在 scikit-learn 中如何执行,让我们看看如何只使用 SciPy,并在这个过程中学到一些东西。
首先,我们需要使用 SciPy 的linalg执行 SVD:
from scipy.linalg import svd
import numpy as np
D = np.array([[1, 2], [1, 3], [1, 4]])
D
array([[1, 2],
[1, 3],
[1, 4]])
U, S, V = svd(D, full_matrices=False)
U.shape, S.shape, V.shape
((3L, 2L), (2L,), (2L, 2L))
我们可以重构原始矩阵D,以确认U、S和V作为分解:
np.dot(U.dot(np.diag(S)), V)
array([[1, 2],
[1, 3],
[1, 4]])
实际上,由截断 SVD 返回的矩阵是U和S矩阵的点积。如果我们想模拟截断,我们将丢弃最小的奇异值及其对应的U列向量。因此,如果我们想要一个单一的组件,我们将执行以下操作:
new_S = S[0]
new_U = U[:, 0]
new_U.dot(new_S)
array([-2.20719466, -3.16170819, -4.11622173])
一般来说,如果我们想要截断到某个维度,例如t,我们会丢弃N - t个奇异值。
还有更多内容...
截断 SVD 有一些杂项内容值得注意,特别是在方法方面。
符号翻转
截断 SVD 有个陷阱。根据随机数生成器的状态,连续应用截断 SVD 可能会翻转输出的符号。为了避免这种情况,建议只进行一次截断 SVD 拟合,然后从那时起使用变换。这是管道方法的另一个好理由!
为了实现这一点,请执行以下操作:
tsvd = TruncatedSVD(2)
tsvd.fit(iris_X)
tsvd.transform(iris_X)
稀疏矩阵
截断 SVD 相对于 PCA 的一个优势是,截断 SVD 可以作用于稀疏矩阵,而 PCA 不能。这是因为 PCA 必须计算协方差矩阵,而这需要在整个矩阵上进行操作。
使用分解方法通过 DictionaryLearning 进行分类
在这个示例中,我们将展示如何使用分解方法进行分类。DictionaryLearning试图将数据集转化为稀疏表示。
准备工作
使用DictionaryLearning,其思路是特征是结果数据集的基础。加载 iris 数据集:
from sklearn.datasets import load_iris
iris = load_iris()
iris_X = iris.data
y = iris.target
此外,通过取iris_X和y的每隔一个元素来创建训练集。剩下的元素用来进行测试:
X_train = iris_X[::2]
X_test = iris_X[1::2]
y_train = y[::2]
y_test = y[1::2]
如何实现...
- 导入
DictionaryLearning:
from sklearn.decomposition import DictionaryLearning
- 使用三个组件来表示三种鸢尾花:
dl = DictionaryLearning(3)
- 变换每隔一个数据点,这样我们就可以在学习器训练后,在结果数据点上测试分类器:
transformed = dl.fit_transform(X_train)
transformed[:5]
array([[ 0\. , 6.34476574, 0\. ],
[ 0\. , 5.83576461, 0\. ],
[ 0\. , 6.32038375, 0\. ],
[ 0\. , 5.89318572, 0\. ],
[ 0\. , 5.45222715, 0\. ]])
- 现在通过简单输入以下命令来测试变换:
test_transform = dl.transform(X_test)
我们可以可视化输出。注意每个值是如何在x、y或z轴上定位的,并且与其他值和零一起显示;这被称为稀疏性:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(14,7))
ax = fig.add_subplot(121, projection='3d')
ax.scatter(transformed[:,0],transformed[:,1],transformed[:,2],c=y_train,marker = '^')
ax.set_title("Training Set")
ax2 = fig.add_subplot(122, projection='3d')
ax2.scatter(test_transform[:,0],test_transform[:,1],test_transform[:,2],c=y_test,marker = '^')
ax2.set_title("Testing Set")

如果你仔细观察,你会发现有一个训练错误。某个类别被误分类了。不过,错误只发生一次并不算大问题。分类中也有错误。如果你记得其他一些可视化,红色和绿色类别经常出现在彼此接近的位置。
它是如何工作的...
DictionaryLearning的背景包括信号处理和神经学。其思路是,在任何给定时刻,只有少数特征是活跃的。因此,DictionaryLearning试图在大多数特征应该为零的约束下,找到数据的合适表示。
使用流形进行维度降维 – t-SNE
准备工作
这是一个简短而实用的示例。
如果你阅读了本章的其余部分,你会发现我们已经在使用 iris 数据集进行很多维度降维。我们继续这种模式进行额外的简便比较。加载 iris 数据集:
from sklearn.datasets import load_iris
iris = load_iris()
iris_X = iris.data
y = iris.target
加载PCA以及manifold模块中的一些类:
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE, MDS, Isomap
#Load visualization library
import matplotlib.pyplot as plt
%matplotlib inline
如何做...
- 对
iris_X应用所有变换。其中一个变换是 t-SNE:
iris_pca = PCA(n_components = 2).fit_transform(iris_X)
iris_tsne = TSNE(learning_rate=200).fit_transform(iris_X)
iris_MDS = MDS(n_components = 2).fit_transform(iris_X)
iris_ISO = Isomap(n_components = 2).fit_transform(iris_X)
- 绘制结果:
plt.figure(figsize=(20, 10))
plt.subplot(221)
plt.title('PCA')
plt.scatter(iris_pca [:, 0], iris_pca [:, 1], c=y)
plt.subplot(222)
plt.scatter(iris_tsne[:, 0], iris_tsne[:, 1], c=y)
plt.title('TSNE')
plt.subplot(223)
plt.scatter(iris_MDS[:, 0], iris_MDS[:, 1], c=y)
plt.title('MDS')
plt.subplot(224)
plt.scatter(iris_ISO[:, 0], iris_ISO[:, 1], c=y)
plt.title('ISO')

t-SNE 算法最近很受欢迎,但它需要大量的计算时间和算力。ISO 生成了一个有趣的图形。
此外,在数据的维度非常高(超过 50 列)的情况下,scikit-learn 文档建议在 t-SNE 之前进行 PCA 或截断 SVD。鸢尾花数据集较小,但我们可以编写语法,在 PCA 之后执行 t-SNE:
iris_pca_then_tsne = TSNE(learning_rate=200).fit_transform(iris_pca)
plt.figure(figsize=(10, 7))
plt.scatter(iris_pca_then_tsne[:, 0], iris_pca_then_tsne[:, 1], c=y)
plt.title("PCA followed by TSNE")

它是如何工作的...
在数学中,流形是一个在每个点局部欧几里得的空间,但它嵌入在更高维的空间中。例如,球体的外表面在三维空间中是一个二维流形。当我们在地球表面行走时,我们倾向于感知地面的二维平面,而不是整个三维空间。我们使用二维地图导航,而不是更高维的地图。
scikit-learn 中的manifold模块对于理解高维空间中的二维或三维空间非常有用。该模块中的算法收集关于某个点周围局部结构的信息,并试图保持这一结构。什么是一个点的邻居?一个点的邻居有多远?
例如,Isomap 算法试图在算法中保持所有点之间的测地距离,从最近邻搜索开始,接着是图搜索,再到部分特征值分解。该算法的目的是保持距离和流形的局部几何结构。多维尺度法(MDS)算法同样尊重距离。
t-SNE 将数据集中点对之间的欧几里得距离转化为概率。在每个点周围都有一个以该点为中心的高斯分布,概率分布表示任何其他点成为邻居的概率。相距很远的点,成为邻居的概率很低。在这里,我们将点的位置转化为距离,再转化为概率。t-SNE 通过利用两点成为邻居的概率,能够很好地保持局部结构。
从非常一般的角度来看,流形方法通过检查每个点的邻居来开始,这些邻居表示流形的局部结构,并试图以不同的方式保持该局部结构。这类似于你在邻里或街区上走动,构建你周围局部结构的二维地图,并专注于二维而不是三维。
使用管道进行降维的测试方法
在这里,我们将看到由降维和支持向量机组成的不同估算器的表现。
准备工作
加载鸢尾花数据集和一些降维库。对于这个特定的步骤来说,这是一个重要的步骤:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.svm import SVC, LinearSVC
from sklearn.decomposition import PCA, NMF, TruncatedSVD
from sklearn.manifold import Isomap
%matplotlib inline
如何实现...
-
实例化一个包含两大部分的管道对象:
-
一个用于降维的对象
-
具有 predict 方法的估算器
-
pipe = Pipeline([
('reduce_dim', PCA()),
('classify', SVC())
])
- 请注意以下代码中,Isomap 来自
manifold模块,并且非负矩阵分解(NMF)算法利用 SVD 将矩阵分解为非负因子,其主要目的是与其他算法进行性能比较,但在自然语言处理(NLP)中非常有用,因为矩阵分解的结果不能为负。现在输入以下参数网格:
param_grid = [
{
'reduce_dim': [PCA(), NMF(),Isomap(),TruncatedSVD()],
'reduce_dim__n_components': [2, 3],
'classify' : [SVC(), LinearSVC()],
'classify__C': [1, 10, 100, 1000]
},
]
这个参数网格将允许 scikit-learn 通过一些降维技术与两种 SVM 类型结合:线性 SVC 和用于分类的 SVC。
- 现在运行一次网格搜索:
grid = GridSearchCV(pipe, cv=3, n_jobs=-1, param_grid=param_grid)
iris = load_iris()
grid.fit(iris.data, iris.target)
- 现在查看最佳参数,以确定最佳模型。使用 PCA 和 SVC 是最佳模型:
grid.best_params_
{'classify': SVC(C=10, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape=None, degree=3, gamma='auto', kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False),
'classify__C': 10,
'reduce_dim': PCA(copy=True, iterated_power='auto', n_components=3, random_state=None,
svd_solver='auto', tol=0.0, whiten=False),
'reduce_dim__n_components': 3}
grid.best_score_
0.97999999999999998
- 如果你想创建一个结果的数据框,使用以下命令:
import pandas as pd
results_df = pd.DataFrame(grid.cv_results_)
- 最后,你可以通过
grid.predict(X_test)方法对未见实例进行预测,X_test是测试集。我们将在后续章节中进行几次网格搜索。
它是如何工作的……
网格搜索进行交叉验证,以确定最佳得分。在这种情况下,所有数据都用于三折交叉验证。对于本书的其余部分,我们将保留一些数据用于测试,以确保模型不会出现异常行为。
关于你刚才看到的管道的最后一点:sklearn.decomposition方法将用于管道中的第一个降维步骤,但并非所有流形方法都设计为适用于管道。
第四章:使用 scikit-learn 进行线性模型
本章包含以下几个步骤:
-
通过数据拟合一条直线
-
使用机器学习通过数据拟合一条直线
-
评估线性回归模型
-
使用岭回归克服线性回归的不足
-
优化岭回归参数
-
使用稀疏性正则化模型
-
采用更基础的方法使用 LARS 进行正则化
介绍
我推测我们天生能很好地感知线性函数。它们非常容易可视化、解释和说明。线性回归非常古老,可能是第一个统计模型。
在本章中,我们将采用机器学习方法进行线性回归。
请注意,这一章节与降维和 PCA 章节类似,涉及使用线性模型选择最佳特征。即使您决定不使用线性模型进行预测回归,也可以选择最有效的特征。
还要注意,线性模型提供了许多机器学习算法使用背后的直觉。例如,RBF 核 SVM 具有平滑边界,从近距离看,它们看起来像一条直线。因此,如果你记住你的线性模型直觉,解释 SVM 就会变得容易。
通过数据拟合一条直线
现在我们将从线性回归的基础建模开始。传统线性回归是第一个,因此可能是最基本的模型——数据的一条直线。
直观地说,对于大多数人来说很熟悉:一个输入变量的变化会按比例改变输出变量。许多人在学校、报纸的数据图表、工作中的演示中都见过它,因此你可以很容易向同事和投资者解释它。
准备工作
波士顿数据集非常适合用于回归分析。波士顿数据集包括波士顿多个地区的房屋中位数价格。它还包括可能影响房价的其他因素,例如犯罪率。首先,导入datasets模块,然后我们可以加载数据集:
from sklearn import datasets boston = datasets.load_boston()
如何做...
实际上,在 scikit-learn 中使用线性回归非常简单。线性回归的 API 基本上与你从前一章已经熟悉的 API 相同。
- 首先,导入
LinearRegression对象并创建一个对象:
from sklearn.linear_model import LinearRegression lr = LinearRegression()
- 现在,只需将独立变量和因变量传递给
LinearRegression的fit方法即可开始:
lr.fit(boston.data, boston.target)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
- 现在,要获得预测结果,请执行以下操作:
predictions = lr.predict(boston.data)
- 你已经获得了线性回归生成的预测结果。现在,进一步探索
LinearRegression类。查看残差,即实际目标集和预测目标集之间的差异:
import numpy as np import pandas as pd import matplotlib.pyplot as plt #within an Ipython notebook:
%matplotlib inline
pd.Series(boston.target - predictions).hist(bins=50)

表达特征系数及其名称的常见模式是zip(boston.feature_names, lr.coef_)。
- 通过键入
lr.coef_找到线性回归的系数:
lr.coef_
array([ -1.07170557e-01, 4.63952195e-02, 2.08602395e-02,
2.68856140e+00, -1.77957587e+01, 3.80475246e+00,
7.51061703e-04, -1.47575880e+00, 3.05655038e-01,
-1.23293463e-02, -9.53463555e-01, 9.39251272e-03,
-5.25466633e-01])
所以,回到数据上,我们可以看到哪些因素与结果呈负相关,哪些因素呈正相关。例如,正如预期的那样,城镇的人均犯罪率的增加与波士顿的房价呈负相关。人均犯罪率是回归分析中的第一个系数。
- 你还可以查看截距,即当所有输入变量为零时目标的预测值:
lr.intercept_
36.491103280361955
- 如果你忘记了系数或截距属性的名称,可以输入
dir(lr):
[... #partial output due to length
'coef_',
'copy_X',
'decision_function',
'fit',
'fit_intercept',
'get_params',
'intercept_',
'n_jobs',
'normalize',
'predict',
'rank_',
'residues_',
'score',
'set_params',
'singular_']
对于许多 scikit-learn 预测器,参数名以单词加一个下划线结尾,如 coef_ 或 intercept_,这些参数特别值得关注。使用 dir 命令是检查 scikit 预测器实现中可用项的好方法。
它是如何工作的...
线性回归的基本思想是找到满足 y = Xv 的 v 系数集合,其中 X 是数据矩阵。对于给定的 X 值,通常不太可能找到一个完全满足方程的系数集合;如果存在不精确的规格或测量误差,误差项将被添加进来。
因此,方程变为 y = Xv + e,其中 e 被假定为正态分布,并且与 X 的值独立。从几何角度来看,这意味着误差项与 X 垂直。虽然这超出了本书的范围,但你可能想亲自证明 E(Xv) = 0。
还有更多内容...
LinearRegression 对象可以自动对输入进行标准化(或缩放):
lr2 = LinearRegression(normalize=True)
lr2.fit(boston.data, boston.target)
LinearRegression(copy_X=True, fit_intercept=True, normalize=True)
predictions2 = lr2.predict(boston.data)
使用机器学习拟合数据线
使用机器学习的线性回归涉及在未见过的数据上测试线性回归算法。在这里,我们将进行 10 折交叉验证:
-
将数据集分成 10 个部分
-
在 9 个部分上进行训练,剩下的部分用来测试
-
重复这个过程 10 次,以便每一部分都能作为测试集一次
准备工作
如前一节所示,加载你想要应用线性回归的数据集,这里是波士顿住房数据集:
from sklearn import datasets
boston = datasets.load_boston()
如何操作...
执行线性回归的步骤如下:
- 导入
LinearRegression对象并创建一个实例:
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
- 将自变量和因变量传递给
LinearRegression的fit方法:
lr.fit(boston.data, boston.target)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
- 现在,为了获得 10 折交叉验证的预测结果,执行以下操作:
from sklearn.model_selection import cross_val_predict
predictions_cv = cross_val_predict(lr, boston.data, boston.target, cv=10)
- 观察残差,即真实数据与预测值之间的差异,它们比前一节中没有交叉验证的线性回归的残差更接近正态分布:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
#within Ipython
%matplotlib inline
pd.Series(boston.target - predictions_cv).hist(bins=50)
- 以下是通过交叉验证获得的新残差。与没有交叉验证时的情况相比,正态分布变得更加对称:

评估线性回归模型
在这个步骤中,我们将查看我们的回归如何拟合基础数据。我们在上一节中拟合了一个回归模型,但没有太关注我们实际的拟合效果。拟合模型后,首先要问的问题显然是:模型拟合得如何?在这个步骤中,我们将深入探讨这个问题。
准备工作
让我们使用 lr 对象和波士顿数据集——回到你在 数据拟合直线 章节中的代码。现在,lr 对象会有很多有用的方法,因为模型已经拟合完毕。
如何操作...
- 从 IPython 开始,导入多个库,包括
numpy、pandas和matplotlib用于可视化:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
- 值得查看 Q-Q 图。我们这里使用
scipy,因为它有内置的概率图:
from scipy.stats import probplot
f = plt.figure(figsize=(7, 5))
ax = f.add_subplot(111)
tuple_out = probplot(boston.target - predictions_cv, plot=ax)
以下屏幕截图展示了概率图:

- 输入
tuple_out[1],你将得到以下结果:
(4.4568597454452306, -2.9208080837569337e-15, 0.94762914118318298)
这是一个形式为 (slope, intercept, r) 的元组,其中 slope 和 intercept 来自最小二乘拟合,而 r 是决定系数的平方根。
- 在这里,我们之前看到的偏斜值变得更清晰了。我们还可以查看一些其他的拟合指标;均方误差 (MSE) 和 均值绝对偏差 (MAD) 是两种常见的指标。让我们在 Python 中定义每个指标并使用它们。
def MSE(target, predictions):
squared_deviation = np.power(target - predictions, 2)
return np.mean(squared_deviation)
MSE(boston.target, predictions)
21.897779217687503
def MAD(target, predictions):
absolute_deviation = np.abs(target - predictions)
return np.mean(absolute_deviation)
MAD(boston.target, predictions)
3.2729446379969205
- 现在你已经看到了使用 NumPy 计算误差的公式,你还可以使用
sklearn.metrics模块快速获取误差:
from sklearn.metrics import mean_absolute_error, mean_squared_error
print 'MAE: ', mean_absolute_error(boston.target, predictions)
print 'MSE: ', mean_squared_error(boston.target, predictions)
'MAE: ', 3.2729446379969205
'MSE: ', 21.897779217687503
它是如何工作的...
MSE 的公式非常简单:

它将每个预测值与实际值之间的偏差平方后再取平均,这实际上是我们优化的目标,用于找到线性回归的最佳系数。高斯-马尔科夫定理实际上保证了线性回归的解是最优的,因为系数具有最小的期望平方误差且是无偏的。在下一节中,我们将探讨当我们允许系数存在偏差时会发生什么。MAD 是绝对误差的期望误差:

在拟合线性回归时,MAD 并没有被使用,但它值得一看。为什么?想一想每个指标的作用,以及在每种情况下哪些错误更重要。例如,在 MSE 中,较大的错误会比其他项受到更多的惩罚,因为平方项的存在。离群值有可能显著地扭曲结果。
还有更多...
有一点被略过了,那就是系数本身是随机变量,因此它们具有分布。让我们使用自助法(bootstrapping)来查看犯罪率系数的分布。自助法是一种常见的技术,用来了解估计的不确定性:
n_bootstraps = 1000
len_boston = len(boston.target)
subsample_size = np.int(0.5*len_boston)
subsample = lambda: np.random.choice(np.arange(0, len_boston),size=subsample_size)
coefs = np.ones(n_bootstraps) #pre-allocate the space for the coefs
for i in range(n_bootstraps):
subsample_idx = subsample()
subsample_X = boston.data[subsample_idx]
subsample_y = boston.target[subsample_idx]
lr.fit(subsample_X, subsample_y)
coefs[i] = lr.coef_[0]
现在,我们可以查看系数的分布:
import matplotlib.pyplot as plt
f = plt.figure(figsize=(7, 5))
ax = f.add_subplot(111)
ax.hist(coefs, bins=50)
ax.set_title("Histogram of the lr.coef_[0].")
以下是生成的直方图:

我们还可能需要查看自助法(bootstrapping)生成的置信区间:
np.percentile(coefs, [2.5, 97.5])
array([-0.18497204, 0.03231267])
这很有趣;事实上,有理由相信犯罪率可能对房价没有影响。注意零值位于置信区间(CI)-0.18 到 0.03 之间,这意味着它可能不起作用。还值得指出的是,自助法可能会带来更好的系数估计,因为自助法的均值收敛速度比使用常规估计方法找出系数要快。
使用岭回归克服线性回归的不足
在本节中,我们将学习岭回归。它与普通的线性回归不同;它引入了一个正则化参数来收缩系数。当数据集包含共线性因素时,这非常有用。
岭回归在共线性存在的情况下非常强大,甚至可以建模多项式特征:向量 x、x²、x³,……这些特征高度共线且相关。
准备开始
让我们加载一个有效秩较低的数据集,并通过系数来比较岭回归和线性回归。如果你不熟悉秩,它是线性独立列和线性独立行中的较小者。线性回归的一个假设是数据矩阵是满秩的。
如何操作...
- 首先,使用
make_regression创建一个包含三个预测变量的简单数据集,但其effective_rank为2。有效秩意味着,尽管矩阵在技术上是满秩的,但许多列之间存在高度的共线性:
from sklearn.datasets import make_regression
reg_data, reg_target = make_regression(n_samples=2000,n_features=3, effective_rank=2, noise=10)
- 首先,让我们回顾一下上一章使用自助法的常规线性回归:
import numpy as np
n_bootstraps = 1000
len_data = len(reg_data)
subsample_size = np.int(0.5*len_data)
subsample = lambda: np.random.choice(np.arange(0, len_data),size=subsample_size)
coefs = np.ones((n_bootstraps, 3))
for i in range(n_bootstraps):
subsample_idx = subsample()
subsample_X = reg_data[subsample_idx]
subsample_y = reg_target[subsample_idx]
lr.fit(subsample_X, subsample_y)
coefs[i][0] = lr.coef_[0]
coefs[i][1] = lr.coef_[1]
coefs[i][2] = lr.coef_[2]
- 可视化系数:

- 使用岭回归执行相同的步骤:
from sklearn.linear_model import Ridge
r = Ridge()
n_bootstraps = 1000
len_data = len(reg_data)
subsample_size = np.int(0.5*len_data)
subsample = lambda: np.random.choice(np.arange(0, len_data),size=subsample_size)
coefs_r = np.ones((n_bootstraps, 3))
for i in range(n_bootstraps):
subsample_idx = subsample()
subsample_X = reg_data[subsample_idx]
subsample_y = reg_target[subsample_idx]
r.fit(subsample_X, subsample_y)
coefs_r[i][0] = r.coef_[0]
coefs_r[i][1] = r.coef_[1]
coefs_r[i][2] = r.coef_[2]
- 可视化结果:
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
ax1 = plt.subplot(311, title ='Coef 0')
ax1.hist(coefs_r[:,0])
ax2 = plt.subplot(312,sharex=ax1, title ='Coef 1')
ax2.hist(coefs_r[:,1])
ax3 = plt.subplot(313,sharex=ax1, title ='Coef 2')
ax3.hist(coefs_r[:,2])
plt.tight_layout()

别被图表的相似宽度欺骗;岭回归的系数要接近零。
- 让我们看一下系数之间的平均差异:
np.var(coefs, axis=0)
array([ 228.91620444, 380.43369673, 297.21196544])
所以,平均而言,线性回归的系数要远高于岭回归的系数。这种差异是系数中的偏差(暂时忽略线性回归系数可能存在的偏差)。那么,岭回归的优势是什么呢?
- 好吧,来看一下我们系数的方差:
np.var(coefs_r, axis=0)
array([ 19.28079241, 15.53491973, 21.54126386])
方差已经显著降低。这就是机器学习中常常讨论的偏差-方差权衡。接下来的内容将介绍如何调整岭回归中的正则化参数,这是这一权衡的核心。
优化岭回归参数
一旦你开始使用岭回归进行预测或了解你正在建模的系统中的关系,你就会开始考虑 alpha 的选择。
例如,使用普通最小二乘 (OLS) 回归可能会显示两个变量之间的关系;然而,当通过 alpha 进行正则化时,这种关系不再显著。这可能是一个是否需要做出决策的问题。
做好准备
通过交叉验证,我们将调整岭回归的 alpha 参数。如果你还记得,在岭回归中,gamma 参数通常在调用 RidgeRegression 时被表示为 alpha,因此,出现的问题是,最优的 alpha 值是什么?创建一个回归数据集,然后我们开始吧:
from sklearn.datasets import make_regression
reg_data, reg_target = make_regression(n_samples=100, n_features=2, effective_rank=1, noise=10)
如何进行...
在 linear_models 模块中,有一个叫做 RidgeCV 的对象,代表岭回归交叉验证。它执行的交叉验证类似于 留一交叉验证 (LOOCV)。
- 在背后,它将为除了一个样本之外的所有样本训练模型。然后它会评估预测这个测试样本的误差:
from sklearn.linear_model import RidgeCV
rcv = RidgeCV(alphas=np.array([.1, .2, .3, .4]))
rcv.fit(reg_data, reg_target)
- 在我们拟合回归之后,alpha 属性将是最佳的 alpha 选择:
rcv.alpha_
0.10000000000000001
- 在之前的例子中,它是第一个选择。我们可能想要集中关注
.1附近的值:
rcv2 = RidgeCV(alphas=np.array([.08, .09, .1, .11, .12]))
rcv2.fit(reg_data, reg_target)
rcv2.alpha_
0.080000000000000002
我们可以继续这个探索,但希望这些机制已经很清晰了。
它是如何工作的...
这些机制可能很清楚,但我们应该再谈谈为什么,并定义最优值的含义。在交叉验证的每个步骤中,模型会对测试样本进行误差评分。默认情况下,本质上是平方误差。
我们可以强制 RidgeCV 对象存储交叉验证值;这将帮助我们可视化它所做的工作:
alphas_to_test = np.linspace(0.01, 1)
rcv3 = RidgeCV(alphas=alphas_to_test, store_cv_values=True)
rcv3.fit(reg_data, reg_target)
如你所见,我们测试了从 0.01 到 1 的一堆点(共 50 个)。由于我们将 store_cv_values 设置为 True,我们可以访问这些值:
rcv3.cv_values_.shape
(100L, 50L)
所以,在初始回归中我们有 100 个值,并测试了 50 个不同的 alpha 值。现在我们可以访问所有 50 个值的误差。因此,我们可以找到最小的均值误差,并选择它作为 alpha:
smallest_idx = rcv3.cv_values_.mean(axis=0).argmin()
alphas_to_test[smallest_idx]
0.030204081632653063
这与 RidgeCV 类的 rcv3 实例找到的最佳值一致:
rcv3.alpha_
0.030204081632653063
也值得可视化正在发生的事情。为此,我们将绘制所有 50 个测试 alpha 的均值:
plt.plot(alphas_to_test, rcv3.cv_values_.mean(axis=0))

还有更多...
如果我们想使用自己的评分函数,也可以这么做。由于我们之前查找了 MAD,我们就用它来评分差异。首先,我们需要定义我们的损失函数。我们将从 sklearn.metrics 导入它:
from sklearn.metrics import mean_absolute_error
在定义损失函数后,我们可以使用 sklearn 中的 make_scorer 函数。这将确保我们的函数被标准化,从而让 scikit 的对象知道如何使用它。此外,因为这是一个损失函数而不是评分函数,所以越低越好,因此需要让 sklearn 翻转符号,将其从最大化问题转变为最小化问题:
from sklearn.metrics import make_scorer
MAD_scorer = make_scorer(mean_absolute_error, greater_is_better=False)
按照之前的方式继续寻找最小的负 MAD 分数:
rcv4 = RidgeCV(alphas=alphas_to_test, store_cv_values=True, scoring=MAD_scorer)
rcv4.fit(reg_data, reg_target)
smallest_idx = rcv4.cv_values_.mean(axis=0).argmin()
查看最低的得分:
rcv4.cv_values_.mean(axis=0)[smallest_idx]
-0.021805192650070034
它发生在 alpha 为 0.01 时:
alphas_to_test[smallest_idx]
0.01
贝叶斯岭回归
此外,scikit-learn 还包含贝叶斯岭回归,它允许轻松估计置信区间。(注意,获取以下贝叶斯岭回归置信区间特别需要 scikit-learn 0.19.0 或更高版本。)
创建一条斜率为3且没有截距的直线,简化起见:
X = np.linspace(0, 5)
y_truth = 3 * X
y_noise = np.random.normal(0, 0.5, len(y_truth)) #normally distributed noise with mean 0 and spread 0.1
y_noisy = (y_truth + y_noise)
导入、实例化并拟合贝叶斯岭回归模型。请注意,一维的X和y变量必须进行重塑:
from sklearn.linear_model import BayesianRidge
br_inst = BayesianRidge().fit(X.reshape(-1, 1), y_noisy)
编写以下代码以获取正则化线性回归的误差估计:
y_pred, y_err = br_inst.predict(X.reshape(-1, 1), return_std=True)
绘制结果。噪声数据是蓝色的点,绿色的线条大致拟合它:
plt.figure(figsize=(7, 5))
plt.scatter(X, y_noisy)
plt.title("Bayesian Ridge Line With Error Bars")
plt.errorbar(X, y_pred, y_err, color='green')

最后,关于贝叶斯岭回归的补充说明,你可以通过交叉验证网格搜索对参数alpha_1、alpha_2、lambda_1和lambda_2进行超参数优化。
使用稀疏性来正则化模型
最小绝对收缩与选择算子(LASSO)方法与岭回归非常相似,也与最小角回归(LARS)类似。它与岭回归的相似之处在于我们通过一定的惩罚量来惩罚回归,而与 LARS 的相似之处在于它可以作为参数选择,通常会导致一个稀疏的系数向量。LASSO 和 LARS 都可以去除数据集中的许多特征,这取决于数据集的特点以及如何应用这些方法,这可能是你想要的,也可能不是。(而岭回归则保留所有特征,这使得你可以用来建模多项式函数或包含相关特征的复杂函数。)
正在准备
为了明确,LASSO 回归并非万能。使用 LASSO 回归可能会带来计算上的后果。正如我们在本配方中看到的,我们将使用一个不可微的损失函数,因此需要特定的、更重要的是、影响性能的解决方法。
如何操作...
执行 LASSO 回归的步骤如下:
- 让我们回到可靠的
make_regression函数,并使用相同的参数创建一个数据集:
import numpy as np
from sklearn.datasets import make_regression
reg_data, reg_target = make_regression(n_samples=200, n_features=500, n_informative=5, noise=5)
- 接下来,我们需要导入
Lasso对象:
from sklearn.linear_model import Lasso
lasso = Lasso()
- LASSO 包含许多参数,但最有趣的参数是
alpha。它用于调整Lasso方法的惩罚项。目前,保持它为 1。顺便说一下,就像岭回归一样,如果这个项为零,LASSO 就等同于线性回归:
lasso.fit(reg_data, reg_target)
- 再次查看有多少系数保持非零:
np.sum(lasso.coef_ != 0)
7
lasso_0 = Lasso(0)
lasso_0.fit(reg_data, reg_target)
np.sum(lasso_0.coef_ != 0)
500
我们的系数没有一个变为零,这正是我们预期的。实际上,如果你运行这个,你可能会收到来自 scikit-learn 的警告,建议你选择LinearRegression。
它是如何工作的...
LASSO 交叉验证 – LASSOCV
选择最合适的 lambda 是一个关键问题。我们可以自己指定 lambda,或者使用交叉验证根据现有数据找到最佳选择:
from sklearn.linear_model import LassoCV
lassocv = LassoCV()
lassocv.fit(reg_data, reg_target)
LASSOCV 将具有作为属性的最合适的 lambda。scikit-learn 在其符号中大多使用 alpha,但文献中使用 lambda:
lassocv.alpha_
0.75182924196508782
系数的数量可以通过常规方式访问:
lassocv.coef_[:5]
array([-0., -0., 0., 0., -0.])
让 LASSOCV 选择最合适的最佳拟合,我们得到15个非零系数:
np.sum(lassocv.coef_ != 0)
15
LASSO 用于特征选择
LASSO 通常可以用于其他方法的特征选择。例如,您可以运行 LASSO 回归来获取合适数量的特征,然后在其他算法中使用这些特征。
为了获取我们想要的特征,创建一个基于非零列的掩码数组,然后过滤掉非零列,保留我们需要的特征:
mask = lassocv.coef_ != 0
new_reg_data = reg_data[:, mask]
new_reg_data.shape
(200L, 15L)
采用更基本的方法进行 LARS 的正则化
借用 Gilbert Strang 对高斯消元法的评估,LARS 是一个你可能最终会考虑的想法,除非它已经在 Efron、Hastie、Johnstone 和 Tibshirani 的工作中被发现[1]。
准备工作
LARS 是一种回归技术,非常适合高维问题,即p >> n,其中p表示列或特征,n是样本的数量。
如何操作...
- 首先,导入必要的对象。我们使用的数据将包含 200 个数据点和 500 个特征。我们还将选择低噪声和少量的信息性特征:
from sklearn.datasets import make_regression
reg_data, reg_target = make_regression(n_samples=200, n_features=500, n_informative=10, noise=2)
- 由于我们使用了 10 个信息性特征,让我们也指定希望 LARS 中有 10 个非零系数。我们可能事先无法确切知道信息性特征的数量,但这对学习来说是有帮助的:
from sklearn.linear_model import Lars
lars = Lars(n_nonzero_coefs=10)
lars.fit(reg_data, reg_target)
- 然后我们可以验证 LARS 返回正确数量的非零系数:
np.sum(lars.coef_ != 0)
10
- 问题是为什么使用更少的特征更有用。为了说明这一点,让我们保留一半的数据并训练两个 LARS 模型,一个具有 12 个非零系数,另一个没有预定数量。我们在这里使用 12 是因为我们可能对重要特征的数量有所了解,但我们可能不确定确切的数量:
train_n = 100
lars_12 = Lars(n_nonzero_coefs=12)
lars_12.fit(reg_data[:train_n], reg_target[:train_n])
lars_500 = Lars() # it's 500 by default
lars_500.fit(reg_data[:train_n], reg_target[:train_n]);
np.mean(np.power(reg_target[train_n:] - lars_12.predict(reg_data[train_n:]), 2))
- 现在,为了查看每个特征如何拟合未知数据,请执行以下操作:
87.115080975821513
np.mean(np.power(reg_target[train_n:] - lars_500.predict(reg_data[train_n:]), 2))
2.1212501492030518e+41
如果你错过了,再看看;测试集上的误差显然非常高。这就是高维数据集的问题所在;给定大量特征,通常不难在训练样本上得到一个拟合良好的模型,但过拟合成为了一个巨大的问题。
它是如何工作的...
LARS 通过反复选择与残差相关的特征来工作。在几何上,相关性实际上是特征和残差之间的最小角度;这也是 LARS 得名的原因。
选择第一个特征后,LARS 将继续沿最小角度方向移动,直到另一个特征与残差的相关性达到相同的程度。然后,LARS 将开始沿这两个特征的组合方向移动。为了直观地理解这一点,考虑以下图表:

所以,我们沿着x1移动,直到x1受到y的拉力与x2受到y的拉力相等。发生这种情况时,我们沿着一条路径移动,这条路径的角度等于x1和x2之间的角度除以二。
还有更多…
就像我们使用交叉验证来调整岭回归一样,我们也可以对 LARS 做同样的操作:
from sklearn.linear_model import LarsCV
lcv = LarsCV()
lcv.fit(reg_data, reg_target)
使用交叉验证可以帮助我们确定使用的非零系数的最佳数量。这里,结果如下:
np.sum(lcv.coef_ != 0)
23
参考文献
- Bradley Efron, Trevor Hastie, Iain Johnstone, 和 Robert Tibshirani, 最小角回归, 《统计年鉴》32(2) 2004: 第 407–499 页, doi:10.1214/009053604000000067, MR2060166。
第五章:线性模型 – 逻辑回归
在本章中,我们将涵盖以下内容:
-
从 UCI 库加载数据
-
使用 pandas 查看 Pima 印第安人糖尿病数据集
-
查看 UCI Pima 印第安人数据集网页
-
使用逻辑回归进行机器学习
-
使用混淆矩阵检查逻辑回归错误
-
改变逻辑回归中的分类阈值
-
接收器操作特征 – ROC 分析
-
绘制没有上下文的 ROC 曲线
-
整合所有内容 – UCI 乳腺癌数据集
介绍
线性回归是一种非常古老的方法,是传统统计学的一部分。机器学习线性回归涉及训练集和测试集。通过这种方式,它可以与其他模型和算法通过交叉验证进行比较。传统线性回归则在整个数据集上进行训练和测试。这仍然是一种常见的做法,可能是因为线性回归往往是欠拟合,而非过拟合。
使用线性方法进行分类 – 逻辑回归
如第一章所示,高效机器学习 – NumPy,逻辑回归是一种分类方法。在某些情况下,它也可以作为回归器使用,因为它计算一个类别的实数概率,然后再进行分类预测。考虑到这一点,我们来探索加利福尼亚大学欧文分校(UCI)提供的 Pima 印第安人糖尿病数据集。
从 UCI 库加载数据
我们将加载的第一个数据集是 Pima 印第安人糖尿病数据集。这需要访问互联网。数据集由 Sigillito V.(1990)提供,存储在 UCI 机器学习库中(archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data),约翰·霍普金斯大学应用物理实验室,马里兰州劳雷尔。
如果你是开源老手,首先想到的可能是,这个数据库的许可证/授权是什么?这是一个非常重要的问题。UCI 库有一个使用政策,要求我们在使用数据库时引用它。我们可以使用它,但必须给予他们应有的赞扬并提供引用。
如何操作...
- 打开 IPython 并导入
pandas:
import pandas as pd
- 将 Pima 印第安人糖尿病数据集的网页地址作为字符串输入,如下所示:
data_web_address = "https://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data"
- 将数据的列名输入为列表:
column_names = ['pregnancy_x',
'plasma_con',
'blood_pressure',
'skin_mm',
'insulin',
'bmi',
'pedigree_func',
'age',
'target']
- 将特征名称存储为一个列表。排除
target列,这是column_names中的最后一列,因为它不是特征:
feature_names = column_names[:-1]
- 创建一个 pandas 数据框来存储输入数据:
all_data = pd.read_csv(data_web_address , names=column_names)
使用 pandas 查看 Pima 印第安人糖尿病数据集
如何操作...
- 你可以通过多种方式查看数据。查看数据框的顶部:
all_data.head()

- 这里似乎没有什么问题,除了可能有一个胰岛素水平为零的情况。这可能吗?那
skin_mm变量呢?它可以是零吗?在你的 IPython 中做个注释:
#Is an insulin level of 0 possible? Is a skin_mm of 0 possible?
- 使用
describe()方法获取数据框的粗略概览:
all_data.describe()

- 再次在笔记本中做个注释,记录其他零值的情况:
#The features plasma_con, blood_pressure, skin_mm, insulin, bmi have 0s as values. These values could be physically impossible.
- 绘制
pregnancy_x变量的直方图。将hist()方法中的 bins 参数设置为 50,以获得更多的区间和更高分辨率的图像;否则,图像会难以阅读:
#If within a notebook, include this line to visualize within the notebook.
%matplotlib inline
#The default is bins=10 which is hard to read in the visualization.
all_data.pregnancy_x.hist(bins=50)

- 为数据框中的所有列绘制直方图。在方法中将
figsize设置为元组(15,9),并再次将 bins 设置为50;否则,图像会很难读取:
all_data.hist(figsize=(15,9),bins=50)

blood_pressure和bmi看起来呈正态分布,除了异常的零值。
pedigree_func和plasma_con变量呈偏态正态分布(可能是对数正态分布)。age和pregnancy_x变量在某种程度上呈衰减状态。胰岛素和skin_mm变量看起来除了零值很多外,可能呈正态分布。
- 最后,注意
target变量中的类别不平衡。使用value_counts()pandas 系列方法重新检查这种不平衡:
all_data.target.value_counts()
0 500
1 268
Name: target, dtype: int64
在许多情况下,人的描述是类别零而不是类别一。
查看 UCI Pima Indians 数据集的网页
我们进行了初步的探索性分析,以大致了解数据。现在我们将阅读 UCI Pima Indians 数据集的文档。
怎么做...
查看引用政策
-
这里是关于 UCI Pima Indians 糖尿病数据集的所有信息。首先,滚动到页面底部,查看他们的引用政策。糖尿病数据集的通用 UCI 引用政策可以在以下链接找到:
archive.ics.uci.edu/ml/citation_policy.html。 -
通用政策表示,若要发布使用数据集的材料,请引用 UCI 存储库。
阅读关于缺失值和上下文的说明
- 页面顶部有重要链接和数据集摘要。摘要提到数据集中存在缺失值:

- 在摘要下方,有一个属性描述(这就是我最初为列命名的方式):

- 那么这些类别变量到底意味着什么呢?目标变量中的零和一分别代表什么?为了弄清楚这一点,点击摘要上方的“数据集描述”链接。滚动到页面的第九点,那里会给出所需的信息:

这意味着 1 代表糖尿病的阳性结果。这是重要信息,并且在数据分析中提供了上下文。
- 最后,有一个免责声明指出:正如一个仓库用户所指出的那样,这不可能是正确的:在某些地方有零值,而这些地方在生物学上是不可能的,例如血压属性。很可能零值表示缺失数据。
因此,我们在数据探索阶段怀疑某些不可能出现的零值时是正确的。许多数据集都有损坏或缺失的值。
使用逻辑回归进行机器学习
你熟悉训练和测试分类器的步骤。使用逻辑回归时,我们将执行以下操作:
-
将数据加载到特征和目标数组
X和y中 -
将数据拆分为训练集和测试集
-
在训练集上训练逻辑回归分类器
-
在测试集上测试分类器的性能
准备中
定义 X、y——特征和目标数组
让我们开始使用 scikit-learn 的逻辑回归进行预测。首先执行必要的导入并设置输入变量 X 和目标变量 y:
import numpy as np
import pandas as pd
X = all_data[feature_names]
y = all_data['target']
如何操作...
提供训练集和测试集
- 导入
train_test_split以为X和y创建测试集和训练集:输入和目标。注意stratify=y,它会将分类变量y进行分层。这意味着y_train和y_test中的零和一的比例是相同的:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7,stratify=y)
训练逻辑回归模型
- 现在导入
LogisticRegression并将其拟合到训练数据上:
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(X_train,y_train)
- 在测试集上进行预测并将其存储为
y_pred:
y_pred = lr.predict(X_test)
对逻辑回归进行评分
- 使用
accuracy_score检查预测的准确性,这是分类正确的百分比:
from sklearn.metrics import accuracy_score
accuracy_score(y_test,y_pred)
0.74675324675324672
所以,我们得到了一个分数,但这个分数是我们在这些情况下可以使用的最佳度量吗?我们能做得更好吗?也许可以。请看一下下面的结果混淆矩阵。
使用混淆矩阵检查逻辑回归的错误
准备中
导入并查看我们构建的逻辑回归的混淆矩阵:
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_pred,labels = [1,0])
array([[27, 27],
[12, 88]])
我向混淆矩阵传递了三个参数:
-
y_test:测试目标集 -
y_pred:我们的逻辑回归预测 -
labels:指代阳性类别
labels = [1,0] 表示阳性类别为 1,阴性类别为 0。在医学背景下,我们在探索 Pima 印第安糖尿病数据集时发现,类别 1 测试为糖尿病阳性。
这是混淆矩阵,再次以 pandas 数据框形式呈现:

如何操作...
阅读混淆矩阵
这个小的数字数组具有以下含义:

混淆矩阵不仅告诉我们分类过程中发生了什么,还能提供准确率评分。矩阵从左上到右下的对角线元素表示正确的分类。共有 27 + 88 = 115 个正确分类。对角线之外,共犯了 27 + 12 = 39 个错误分类。注意,115 / (115 + 39)再次得到分类器的准确度,大约为 0.75。
让我们再次关注错误。在混淆矩阵中,27 个人被错误地标记为没有糖尿病,尽管他们确实患有糖尿病。在实际情况下,这是比那些被认为患有糖尿病但实际上没有的人更严重的错误。第一类可能会回家后忘记,而第二类则可能会重新测试。
上下文中的一般混淆矩阵
一般的混淆矩阵,其中正类表示识别一种病症(此处为糖尿病),因此具有医学诊断背景:

改变逻辑回归中的分类阈值
准备工作
我们将利用以下事实:在逻辑回归分类中,存在回归过程,用于最小化那些本应被诊断为糖尿病的人却被送回家的次数。通过调用估算器的predict_proba()方法来实现:
y_pred_proba = lr.predict_proba(X_test)
这将得到一个概率数组。查看该数组:
y_pred_proba
array([[ 0.87110309, 0.12889691],
[ 0.83996356, 0.16003644],
[ 0.81821721, 0.18178279],
[ 0.73973464, 0.26026536],
[ 0.80392034, 0.19607966], ...
在第一行,类别0的概率约为 0.87,类别1的概率为 0.13。注意,作为概率,这些数字加起来为 1。由于只有两类,这个结果可以看作是一个回归器,一个关于类别为1(或0)概率的实数。通过直方图可视化类别为1的概率。
取数组的第二列,将其转换为 pandas 序列并绘制直方图:
pd.Series(y_pred_proba[:,1]).hist()

在概率直方图中,相比选择 1 的高概率,低概率的选择较多。例如,选择 1 的概率通常在 0.1 到 0.2 之间。在逻辑回归中,算法默认只有在概率大于 0.5(即一半)时才会选择 1。现在,将其与开始时的目标直方图进行对比:
all_data['target'].hist()

在接下来的步骤中,我们将:
-
调用类方法
y_pred_proba() -
使用具有特定阈值的
binarize函数 -
查看由阈值生成的混淆矩阵
如何操作...
- 要基于阈值选择分类类,请使用
preprocessing模块中的binarize。首先导入它:
from sklearn.preprocessing import binarize
- 查看
y_pred_proba的前两列:
array([[ 0.87110309, 0.12889691],
[ 0.83996356, 0.16003644]
- 然后尝试对
y_pred_proba使用binarize函数,设置阈值为0.5。查看结果:
y_pred_default = binarize(y_pred_proba, threshold=0.5)
y_pred_default
array([[ 1., 0.],
[ 1., 0.],
[ 1., 0.],
[ 1., 0.],
[ 1., 0.],
[ 1., 0.]
binarize函数如果y_pred_proba中的值大于 0.5,则用1填充数组;否则填入0。在第一行,0.87 大于 0.5,而 0.13 不大于。因此,为了二值化,将 0.87 替换为1,将 0.13 替换为0。现在,查看y_pred_default的第一列:
y_pred_default[:,1]
array([ 0., 0., 0., 0., 0., 0 ...
这恢复了默认逻辑回归分类器在阈值0.5下所做的决策。
- 在 NumPy 数组上尝试混淆矩阵函数会得到我们遇到的第一个混淆矩阵(请注意,标签再次选择为
[1,0]):
confusion_matrix(y_test, y_pred_default[:,1],labels = [1,0])
array([[27, 27],
[12, 88]])
- 尝试不同的阈值,使得类别
1有更高的选择概率。查看其混淆矩阵:
y_pred_low = binarize(y_pred_proba, threshold=0.2)
confusion_matrix(y_test, y_pred_low[:,1],labels=[1,0]) #positive class is 1 again
array([[50, 4],
[48, 52]])
通过更改阈值,我们增加了预测类别1的概率——增加了混淆矩阵第一列中数字的大小。现在,第一列的总和为 50 + 48 = 98。之前,第一列是 27 + 12 = 39,数字要小得多。现在只有四个人被错误分类为没有糖尿病,尽管他们实际上有。请注意,在某些情况下,这是一件好事。
当算法预测为零时,它很可能是正确的。当它预测为一时,它通常不准确。假设你经营一家医院。你可能喜欢这个测试,因为你很少会送走一个看似没有糖尿病的人,尽管他实际上有糖尿病。每当你送走那些确实有糖尿病的人时,他们就无法尽早治疗,导致医院、保险公司和他们自己都面临更高的费用。
你可以衡量测试在预测为零时的准确性。观察混淆矩阵的第二列,[4, 52]。在这种情况下,准确率为 52 / (52 + 4),大约是 0.93。这叫做负预测值(NPV)。你可以编写一个函数来根据阈值计算 NPV:
from __future__ import division #In Python 2.x
import matplotlib.pyplot as plt
def npv_func(th):
y_pred_low = binarize(y_pred_proba, threshold=th)
second_column = confusion_matrix(y_test, y_pred_low[:,1],labels=[1,0])[:,1]
npv = second_column[1]/second_column.sum()
return npv
npv_func(0.2)
0.9285714285714286
然后绘制它:
ths = np.arange(0,1,0.05)
npvs = []
for th in np.arange(0,1.00,0.05):
npvs.append(npv_func(th))
plt.plot(ths,npvs)

接收者操作特征 – ROC 分析
沿着检查 NPV 的思路,还有一些标准的度量方法用于检查混淆矩阵中的单元格。
准备就绪
敏感性
敏感性,像上一节中的 NPV 一样,是混淆矩阵单元格的数学函数。敏感性是接受测试且实际患有疾病的人群中,正确标记为有疾病的人群的比例,在这个例子中是糖尿病:

从数学角度看,它是正确标记为有疾病(TP)的患者与实际有疾病的所有人(TP + FN)之比。首先,回顾混淆矩阵的单元格。重点关注真实行,对应于所有患有糖尿病的人:

因此:

敏感性另一个名字是真正阳性率(TPR)。
从视觉角度来看
从另一个角度来看,混淆矩阵非常直观。让我们用直方图(下一张图中的左列)来可视化有糖尿病的阳性类别和没有糖尿病的阴性类别。每个类别大致呈正态分布。借助 SciPy,我可以找到最佳拟合的正态分布。右下角注明了阈值被设置为 0.5,这是逻辑回归中的默认设置。观察阈值是如何让我们选择假阴性(FN)和假阳性(FP)的,尽管这种选择并不完美。

如何操作...
在 scikit-learn 中计算 TPR
- scikit-learn 提供了方便的函数,用于根据阳性类别的概率向量
y_pred_proba[:,1]来计算逻辑回归的敏感度或 TPR:
from sklearn.metrics import roc_curve
fpr, tpr, ths = roc_curve(y_test, y_pred_proba[:,1])
这里,给定阳性类别向量,scikit-learn 中的roc_curve函数返回了一个包含三个数组的元组:
-
TPR 数组(记作
tpr) -
FPR 数组(记作
fpr) -
用于计算 TPR 和 FPR 的自定义阈值集(记作
ths)
详细说明假阳性率(FPR),它描述了假警报的发生率。它是错误地认为没有糖尿病的人却被判定为有糖尿病的比例:

这是一个没有糖尿病的人的陈述。从数学角度来看,混淆矩阵的单元格如下:

绘制敏感度图
- 在y轴绘制敏感度,在x轴绘制阈值:
plt.plot(ths,tpr)

- 因此,阈值越低,敏感度越高。查看阈值为
0.1时的混淆矩阵:
y_pred_th= binarize(y_pred_proba, threshold=0.1)
confusion_matrix(y_test, y_pred_th[:,1],labels=[1,0])
array([[54, 0],
[81, 19]])
- 在这种情况下,没有人会误以为自己得了糖尿病。然而,就像我们在计算 NPV 时的情况一样,当测试预测某人患有糖尿病时,它的准确性非常低。这种情况下的最佳情形是当阈值为
0.146时:
y_pred_th = binarize(y_pred_proba, threshold=0.146)
confusion_matrix(y_test, y_pred_th[:,1],labels=[1,0])
array([[54, 0],
[67, 33]])
即便如此,当预测某人患有糖尿病时,测试结果也不起作用。它的有效性为 33 / (33 + 121) = 0.21,或者说 21%的准确率。
还有更多...
非医疗背景下的混淆矩阵
假设你是一位银行工作人员,想要确定一个客户是否值得获得购买房屋的抵押贷款。接下来是一个可能的混淆矩阵,展示了根据银行可用的客户数据是否应该给某人抵押贷款。
任务是对人群进行分类,并决定他们是否应该获得抵押贷款。在这种情况下,可以为每种情况分配一个数字。当混淆矩阵中的每个单元格都有明确的成本时,找到最佳分类器就更容易了:

绘制没有上下文的 ROC 曲线
如何操作...
ROC 曲线是任何分类器的诊断工具,没有任何上下文。没有上下文意味着我们尚不知道哪个错误类型(FP 或 FN)是更不可取的。让我们立即使用概率向量 y_pred_proba[:,1] 绘制它:
from sklearn.metrics import roc_curve
fpr, tpr, ths = roc_curve(y_test, y_pred_proba[:,1])
plt.plot(fpr,tpr)

ROC 曲线是 FPR(假警报)在 x 轴和 TPR(找出所有有病且确实有病的人)在 y 轴上的图示。没有上下文时,它是一个衡量分类器性能的工具。
完美分类器
完美的分类器无论如何都会有 TPR 为 1,假警报率(FAR):

在前面的图表中,FN 非常小;因此 TPR,TP / (TP + FN),接近 1. 其 ROC 曲线呈 L 形:

不完美的分类器
在以下图像中,分布重叠,类别之间无法区分:

在不完美的分类器中,FN 和 TN 几乎相等,FP 和 TP 也几乎相等。因此,通过替代,TPR TP/ (TP + FP) 几乎等于假阴性率(FNR)FP/ (FP + TN)。即使您改变阈值,这也是成立的。因此,我们得到的 ROC 曲线是斜率大约为 1 的直线:

AUC – ROC 曲线下面积
L 形完美分类器的面积是 1 x 1 = 1. 坏分类器的面积是 0.5. 为了衡量分类器的性能,scikit-learn 提供了一个便捷的 ROC 曲线下面积(AUC)计算函数:
from sklearn.metrics import auc
auc(fpr,tpr)
0.825185185185
将所有内容整合在一起 – UCI 乳腺癌数据集
如何做到...
数据集感谢 Street, N (1990),UCI 机器学习库提供 (archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data),威斯康星大学,麦迪逊,WI:计算机科学系:
- 阅读引用/许可证信息后,从 UCI 加载数据集:
import numpy as np
import pandas as pd
data_web_address = data_web_address = "https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data"
column_names = ['radius',
'texture',
'perimeter',
'area',
'smoothness'
,'compactness',
'concavity',
'concave points',
'symmetry',
'malignant']
feature_names = column_names[:-1]
all_data = pd.read_csv(data_web_address , names=column_names)
- 查看数据类型:
all_data.dtypes
radius int64
texture int64
perimeter int64
area int64
smoothness int64
compactness object
concavity int64
concave points int64
symmetry int64
malignant int64
dtype: object
结果表明,特征紧凑度具有像 ? 这样的特征。目前,我们不使用这个特征。
- 现在,阅读文档,目标变量设置为
2(没有癌症)和4(有癌症)。将变量更改为0代表没有癌症,1代表有癌症:
#changing the state of having cancer to 1, not having cancer to 0
all_data['malignant'] = all_data['malignant'].astype(np.int)
all_data['malignant'] = np.where(all_data['malignant'] == 4, 1,0) #4, and now 1 means malignant
all_data['malignant'].value_counts()
0 458
1 241
Name: malignant, dtype: int64
- 定义
X和y:
X = all_data[[col for col in feature_names if col != 'compactness']]
y = all_data.malignant
- 将
X和y分割成训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7,stratify=y)
#Train and test the Logistic Regression. Use the method #predict_proba().
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(X_train,y_train)
y_pred_proba = lr.predict_proba(X_test)
- 绘制 ROC 曲线并计算 AUC 分数:
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, auc, roc_auc_score
fpr, tpr, ths = roc_curve(y_test, y_pred_proba[:,1])
auc_score = auc(fpr,tpr)
plt.plot(fpr,tpr,label="AUC Score:" + str(auc_score))
plt.xlabel('fpr',fontsize='15')
plt.ylabel('tpr',fontsize='15')
plt.legend(loc='best')

这个分类器表现得相当不错。
未来项目的概要
总体而言,对于未来的分类项目,您可以执行以下操作:
-
加载您能找到的最佳数据,以解决特定问题。
-
确定是否有分类上下文:是 FP 更好还是 FN 更好?
-
使用 ROC-AUC 分数对数据进行无上下文的训练和测试。如果几个算法在此步骤中表现不佳,您可能需要回到第 1 步。
-
如果上下文很重要,可以使用混淆矩阵进行探索。
逻辑回归特别适合执行这些步骤,尽管任何具有 predict_proba() 方法的算法也能非常相似地工作。作为一个练习,如果您有雄心,可以将此过程推广到其他算法甚至是一般回归。这里的主要观点是,并非所有的错误都是相同的,这一点在健康数据集中尤其重要,因为治疗所有患有某种病症的患者非常关键。
关于乳腺癌数据集的最后一点:观察数据由细胞测量值组成。您可以通过自动化计算机查看图片并使用传统程序或机器学习找到这些测量值来收集这些数据。
第六章:使用距离度量构建模型
本章将介绍以下配方:
-
使用 k-means 对数据进行聚类
-
优化质心数量
-
评估聚类的正确性
-
使用 MiniBatch k-means 处理更多数据
-
使用 k-means 聚类对图像进行量化
-
在特征空间中找到最接近的对象
-
使用高斯混合模型进行概率聚类
-
使用 k-means 进行异常值检测
-
使用 KNN 进行回归
介绍
本章我们将讨论聚类。聚类通常与无监督技术一起使用。这些技术假设我们不知道结果变量。这会导致实践中的结果和目标出现模糊,但尽管如此,聚类仍然很有用。正如我们将看到的那样,我们可以在监督环境中使用聚类来定位我们的估计。这或许是聚类如此有效的原因;它可以处理各种情况,且结果通常是,在没有更好术语的情况下,可以说是合理的。
本章我们将涵盖各种应用,从图像处理到回归和异常值检测。聚类与类别的分类相关。你有一个有限的簇或类别集。与分类不同,你事先并不知道这些类别。此外,聚类通常可以通过连续的、概率性的或优化的视角来看待。
不同的解释会导致不同的权衡。我们将讨论如何在这里拟合模型,这样当你遇到聚类问题时,你就有工具可以尝试多种模型。
使用 k-means 对数据进行聚类
在数据集中,我们观察到一些点聚集在一起。使用 k-means,我们将把所有点分到不同的组或簇中。
准备工作
首先,让我们演示一些简单的聚类操作,然后我们将讨论 k-means 如何工作:
import numpy as np
import pandas as pd
from sklearn.datasets import make_blobs
blobs, classes = make_blobs(500, centers=3)
此外,由于我们将进行一些绘图操作,按如下方式导入 matplotlib:
import matplotlib.pyplot as plt
%matplotlib inline #Within an ipython notebook
如何进行……
我们将通过一个简单的示例,演示如何对虚拟数据的簇进行聚类。然后,我们将简要介绍 k-means 如何工作,以寻找最佳的簇数:
- 观察我们的簇,我们可以看到有三个明显的聚类:
f, ax = plt.subplots(figsize=(7.5, 7.5))
rgb = np.array(['r', 'g', 'b'])
ax.scatter(blobs[:, 0], blobs[:, 1], color=rgb[classes])
ax.set_title("Blobs")

现在我们可以使用 k-means 找到这些簇的中心。
- 在第一个示例中,我们假设我们知道有三个中心:
from sklearn.cluster import KMeans
kmean = KMeans(n_clusters=3)
kmean.fit(blobs)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=3, n_init=10, n_jobs=1, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
kmean.cluster_centers_
array([[ 3.48939154, -0.92786786],
[-2.05114953, 1.58697731],
[ 1.58182736, -6.80678064]])
f, ax = plt.subplots(figsize=(7.5, 7.5))
ax.scatter(blobs[:, 0], blobs[:, 1], color=rgb[classes])
ax.scatter(kmean.cluster_centers_[:, 0],kmean.cluster_centers_[:, 1], marker='*', s=250,color='black', label='Centers')
ax.set_title("Blobs")
ax.legend(loc='best')
- 以下截图显示了输出结果:

- 其他属性也很有用。例如,
labels_属性将为每个点生成预期的标签:
kmean.labels_[:5]
array([2, 0, 1, 1, 0])
我们可以检查 kmean.labels_ 是否与类别相同,但由于 k-means 在开始时并不知道类别,它无法将样本索引值分配给两个类别:
classes[:5]
array([2, 0, 1, 1, 0])
随时交换类别中的 1 和 0,检查它们是否与 labels_ 匹配。变换函数非常有用,因为它会输出每个点与质心之间的距离:
kmean.transform(blobs)[:5]
array([[ 6.75214231, 9.29599311, 0.71314755], [ 3.50482136, 6.7010513 , 9.68538042], [ 6.07460324, 1.91279125, 7.74069472], [ 6.29191797, 0.90698131, 8.68432547], [ 2.84654338, 6.07653639, 3.64221613]])
它是如何工作的……
k-means 实际上是一个非常简单的算法,旨在最小化群内距离均值的平方和。我们将再次最小化平方和!
它首先设置一个预设的簇数 K,然后在以下步骤之间交替进行:
-
将每个观察值分配给最近的簇
-
通过计算分配给该簇的每个观察值的均值来更新每个质心
这一过程会一直持续,直到满足某个特定的标准。质心很难解释,而且确定我们是否有正确数量的质心也非常困难。理解数据是否标记过是很重要的,因为这将直接影响你可以使用的评估方法。
优化质心数量
在进行 k-means 聚类时,我们实际上无法事先知道正确的簇数,因此找出这一点是一个重要步骤。一旦我们知道(或估计)了质心的数量,问题就开始更像一个分类问题,因为我们可以使用的知识量大大增加了。
准备开始
对无监督技术的模型性能进行评估是一项挑战。因此,sklearn提供了几种在已知真实标签的情况下评估聚类的方法,而在未知真实标签的情况下,方法则非常有限。
我们将从单一簇模型开始并评估其相似度。这更多是为了操作上的需要,因为测量单一簇数量的相似度显然对于找到真实的簇数并没有什么用处。
如何做...
- 为了开始,我们将创建几个簇状数据,用于模拟数据的聚类:
from sklearn.datasets import make_blobs
import numpy as np
blobs, classes = make_blobs(500, centers=3)
from sklearn.cluster import KMeans
kmean = KMeans(n_clusters=3)
kmean.fit(blobs)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=3, n_init=10, n_jobs=1, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
- 首先,我们来看轮廓距离。轮廓距离是群内不相似度与最接近的群外不相似度之间的差值与这两个值的最大值之比。可以将其视为衡量簇与簇之间分离度的标准。让我们看看从点到簇中心的距离分布;这对于理解轮廓距离很有帮助:
from sklearn import metrics
silhouette_samples = metrics.silhouette_samples(blobs, kmean.labels_)
np.column_stack((classes[:5], silhouette_samples[:5]))
array([[ 0\. , 0.69568017],
[ 0\. , 0.76789931],
[ 0\. , 0.62470466],
[ 0\. , 0.6266658 ],
[ 2\. , 0.63975981]])
- 以下是部分输出:

- 注意,通常情况下,接近 1 的系数越多(这很好),得分越高。
它是如何工作的...
轮廓系数的平均值通常用于描述整个模型的拟合度:
silhouette_samples.mean()
0.5633513643546264
这是非常常见的,实际上,metrics 模块暴露了一个函数,可以得到我们刚刚得到的值:
metrics.silhouette_score(blobs, kmean.labels_)
0.5633513643546264
import matplotlib.pyplot as plt
%matplotlib inline
blobs, classes = make_blobs(500, centers=10)
silhouette_avgs = []
for k in range(2, 60):
kmean = KMeans(n_clusters=k).fit(blobs)
silhouette_avgs.append(metrics.silhouette_score(blobs, kmean.labels_))
f, ax = plt.subplots(figsize=(7, 5))
ax.plot(silhouette_avgs)
以下是输出:

这张图显示了随着质心数量的增加,轮廓系数的平均值变化。我们可以看到,根据数据生成过程,最佳的簇数是 3;但在这里,看起来它大约是 7 或 8。这就是聚类的现实;通常,我们无法获得正确的簇数。我们只能希望对簇数进行某种程度的估算。
评估簇的正确性
我们稍微谈了一下当真实标签未知时如何评估聚类。然而,我们还没有讨论如何在已知簇的情况下评估 k-means。在很多情况下,这个是不可知的;然而,如果有外部标注,我们将知道真实标签或至少知道某种代理标签。
准备好了
所以,让我们假设一个世界,假设有一个外部代理为我们提供真实标签。
我们将创建一个简单的数据集,以几种方式评估与真实标签的正确性度量,然后进行讨论:
from sklearn import datasets
from sklearn import cluster
blobs, ground_truth = datasets.make_blobs(1000, centers=3,cluster_std=1.75)
如何操作...
- 在讲解度量标准之前,我们先来看一下数据集:
%matplotlib inline
import matplotlib.pyplot as plt
f, ax = plt.subplots(figsize=(7, 5))
colors = ['r', 'g', 'b']
for i in range(3):
p = blobs[ground_truth == i]
ax.scatter(p[:,0], p[:,1], c=colors[i],
label="Cluster {}".format(i))
ax.set_title("Cluster With Ground Truth")
ax.legend()

- 为了拟合一个 k-means 模型,我们将从聚类模块创建一个
KMeans对象:
kmeans = cluster.KMeans(n_clusters=3)
kmeans.fit(blobs)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=3, n_init=10, n_jobs=1, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
kmeans.cluster_centers_
array([[ 3.61594791, -6.6125572 ],
[-0.76071938, -2.73916602],
[-3.64641767, -6.23305142]])
- 现在我们已经拟合了模型,让我们看看聚类中心:
f, ax = plt.subplots(figsize=(7, 5))
colors = ['r', 'g', 'b']
for i in range(3):
p = blobs[ground_truth == i]
ax.scatter(p[:,0], p[:,1], c=colors[i], label="Cluster {}".format(i))
以下是输出结果:

- 现在我们可以将聚类性能视为分类练习,其相关的度量标准在这里也同样适用:
for i in range(3):
print (kmeans.labels_ == ground_truth)[ground_truth == i].astype(int).mean()
0.946107784431
0.135135135135
0.0750750750751
显然,我们有一些反向的簇。所以,让我们先解决这个问题,然后再看看准确性:
new_ground_truth = ground_truth.copy()
new_ground_truth[ground_truth == 1] = 2
new_ground_truth[ground_truth == 2] = 1
0.946107784431
0.852852852853
0.891891891892
所以,我们大约有 90% 的时间是正确的。我们将看的第二个相似性度量是互信息得分:
from sklearn import metrics
metrics.normalized_mutual_info_score(ground_truth, kmeans.labels_)
0.66467613668253844
当得分趋向 0 时,标签分配可能不是通过相似的过程生成的;然而,接近 1 的得分意味着两个标签之间有很大的相似性。
例如,让我们看看当互信息得分本身时会发生什么:
metrics.normalized_mutual_info_score(ground_truth, ground_truth)
1.0
从名称上看,我们可以推测这可能是一个未归一化的 mutual_info_score:
metrics.mutual_info_score(ground_truth, kmeans.labels_)
0.72971342940406325
这些非常接近;然而,归一化互信息是互信息除以每个集合真实标签和分配标签的熵乘积的平方根。
还有更多内容...
我们还没有讨论过的一个聚类度量,并且这个度量不依赖于真实标签的是惯性。它目前作为度量并没有得到很好的文档化。然而,它是 k-means 最小化的一个度量。
惯性是每个点与其分配的簇之间的平方差之和。我们可以通过一点 NumPy 来确定这一点:
kmeans.inertia_
4849.9842988128385
使用 MiniBatch k-means 处理更多数据
K-means 是一种不错的方法;然而,它对于大量数据并不理想。这是由于 k-means 的复杂性。话虽如此,我们可以通过 MiniBatch k-means 以更好的算法复杂度获得近似解。
准备好了
MiniBatch k-means 是 k-means 的更快实现。K-means 在计算上非常昂贵;该问题是 NP 难问题。
然而,使用 MiniBatch k-means,我们可以通过数量级加速 k-means。这是通过使用许多叫做 MiniBatches 的子样本来实现的。考虑到子抽样的收敛性,提供了良好的初始条件时,可以实现对常规 k-means 的接近近似。
如何操作...
- 让我们对 MiniBatch 聚类进行一些非常高层次的分析。首先,我们将看一下整体速度差异,然后再看一下估计误差:
import numpy as np
from sklearn.datasets import make_blobs
blobs, labels = make_blobs(int(1e6), 3)
from sklearn.cluster import KMeans, MiniBatchKMeans
kmeans = KMeans(n_clusters=3)
minibatch = MiniBatchKMeans(n_clusters=3)
理解这些指标的目的是为了揭示问题。因此,在确保基准的最高准确性方面非常小心。关于这个主题有很多信息可供参考;如果你真的想深入了解为什么 MiniBatch k-means 在扩展性方面更好,建议查看现有的资料。
- 现在设置完成了,我们可以测量时间差异:
%time kmeans.fit(blobs) #IPython Magic
Wall time: 7.88 s
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=3, n_init=10, n_jobs=1, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
%time minibatch.fit(blobs)
Wall time: 2.66 s
MiniBatchKMeans(batch_size=100, compute_labels=True, init='k-means++',
init_size=None, max_iter=100, max_no_improvement=10, n_clusters=3,
n_init=3, random_state=None, reassignment_ratio=0.01, tol=0.0,
verbose=0)
- CPU 时间上有很大的差异。聚类性能的差异如下所示:
kmeans.cluster_centers_
array([[-3.74304286, -0.4289715 , -8.69684375],
[-5.73689621, -6.39166391, 6.18598804],
[ 0.63866644, -9.93289824, 3.24425045]])
minibatch.cluster_centers_
array([[-3.72580548, -0.46135647, -8.63339789],
[-5.67140979, -6.33603949, 6.21512625],
[ 0.64819477, -9.87197712, 3.26697532]])
- 查看这两个数组;找到的中心点是按照相同顺序排列的。这是随机的—聚类不必按相同顺序排列。查看第一个聚类中心之间的距离:
from sklearn.metrics import pairwise
pairwise.pairwise_distances(kmeans.cluster_centers_[0].reshape(1, -1), minibatch.cluster_centers_[0].reshape(1, -1))
array([[ 0.07328909]])
- 这似乎非常接近。对角线将包含聚类中心的差异:
np.diag(pairwise.pairwise_distances(kmeans.cluster_centers_, minibatch.cluster_centers_))
array([ 0.07328909, 0.09072807, 0.06571599])
工作原理...
这里的批次很关键。通过批次迭代以找到批次均值;对于下一次迭代,相对于当前迭代更新前一批次均值。有几个选项决定了一般 k-means 的行为和参数,这些参数决定了 MiniBatch k-means 的更新方式。
batch_size参数决定了批处理的大小。仅供娱乐,让我们来运行 MiniBatch;但是这次我们将批处理大小设置为与数据集大小相同:
minibatch = MiniBatchKMeans(batch_size=len(blobs))
%time minibatch.fit(blobs)
Wall time: 1min
MiniBatchKMeans(batch_size=1000000, compute_labels=True, init='k-means++',
init_size=None, max_iter=100, max_no_improvement=10, n_clusters=8,
n_init=3, random_state=None, reassignment_ratio=0.01, tol=0.0,
verbose=0)
显然,这违背了问题的本意,但它确实说明了一个重要的观点。选择不良的初始条件可能会影响模型的收敛效果,特别是聚类模型。对于 MiniBatch k-means,不能保证会达到全局最优解。
MiniBatch k-means 中有许多有力的教训。它利用了许多随机样本的力量,类似于自助法。在创建大数据的算法时,您可以在许多机器上并行处理许多随机样本。
使用 k-means 聚类量化图像
图像处理是一个重要的话题,聚类在其中有一些应用。值得指出的是,Python 中有几个非常好的图像处理库。scikit-image是 scikit-learn 的姊妹项目。如果您想要做一些复杂的事情,不妨看看它。
本章的重点之一是图像也是数据,聚类可以用来尝试猜测图像中某些物体的位置。聚类可以是图像处理流程的一部分。
准备工作
在这个示例中我们将会有一些乐趣。目标是使用一个聚类方法来对图像进行模糊处理。首先,我们将使用 SciPy 来读取图像。图像被转换为一个三维数组;x和y坐标描述了高度和宽度,第三维表示每个像素的 RGB 值。
首先,下载或将一个 .jpg 图像移动到你的 IPython 笔记本所在的文件夹。你可以使用你自己的照片。我使用的是一张名为 headshot.jpg 的自己的照片。
怎么做……
- 现在,让我们用 Python 读取图像:
%matplotlib inline
import matplotlib.pyplot as plt
from scipy import ndimage
img = ndimage.imread("headshot.jpg")
plt.figure(figsize = (10,7))
plt.imshow(img)
看到以下图片:

- 就是我!现在我们有了图像,让我们检查一下它的维度:
img.shape
(379L, 337L, 3L)
要实际对图像进行量化,我们需要将其转换为二维数组,长度为 379 x 337,宽度为 RGB 值。更好的理解方式是将其看作是三维空间中的一组数据点,接着对这些点进行聚类,以减少图像中远离的颜色——这是一种简单的量化方法。
- 首先,让我们重新整理一下我们的数组;它是一个 NumPy 数组,因此操作起来很简单:
x, y, z = img.shape
long_img = img.reshape(x*y, z)
long_img.shape
(127723L, 3L)
- 现在我们可以开始聚类过程。首先,让我们导入聚类模块并创建一个 k-means 对象。我们将传递
n_clusters=5,这样我们就有五个聚类,或者说,五种不同的颜色。这将是一个很好的练习,帮助我们使用轮廓距离,这个在优化质心数量的练习中已经讲解过:
from sklearn import cluster
k_means = cluster.KMeans(n_clusters=5)
k_means.fit(long_img)
centers = k_means.cluster_centers_
centers
array([[ 169.01964615, 123.08399844, 99.6097561 ],
[ 45.79271071, 94.56844879, 120.00911162],
[ 218.74043562, 202.152748 , 184.14355039],
[ 67.51082485, 151.50671141, 201.9408963 ],
[ 169.69235986, 189.63274724, 143.75511521]])
它是如何工作的……
现在我们有了中心点,接下来我们需要的是标签。这将告诉我们哪些点应该与哪些聚类相关联:
labels = k_means.labels_
labels
array([4, 4, 4, ..., 3, 3, 3])
此时,我们只需进行最简单的 NumPy 数组操作,然后稍作调整,就可以得到新的图像:
plt.figure(figsize = (10,7))
plt.imshow(centers[labels].reshape(x, y, z))
以下是结果图像:

聚类将图像分成了几个区域。
在特征空间中寻找最接近的对象
有时,最简单的方法是找出两个对象之间的距离。我们只需要找出一个距离度量,计算成对距离,并将结果与预期的进行比较。
准备工作
scikit-learn 中的一个底层工具是 sklearn.metrics.pairwise。它包含了用于计算矩阵 X 中向量之间或 X 和 Y 中向量之间距离的服务器函数。这对于信息检索很有帮助。例如,给定一组具有属性 X 的客户,我们可能想选择一个参考客户,找到与该客户最相似的客户。
事实上,我们可能想根据相似度来对客户进行排名,而这种相似度是通过距离函数来衡量的。相似度的质量取决于特征空间的选择以及我们对空间进行的任何变换。我们将通过几种不同的距离衡量场景来进行讲解。
如何做……
我们将使用 pairwise_distances 函数来确定对象之间的相似性。请记住,相似性本质上就是通过我们定义的距离函数来衡量的:
- 首先,让我们从 metrics 模块中导入成对距离函数,并创建一个数据集进行操作:
import numpy as np
from sklearn.metrics import pairwise
from sklearn.datasets import make_blobs
points, labels = make_blobs()
- 检查距离的最简单方法是
pairwise_distances:
distances = pairwise.pairwise_distances(points)
distances是一个 N x N 矩阵,对角线上的值为 0。在最简单的情况下,让我们查看每个点与第一个点之间的距离:
np.diag(distances) [:5]
distances[0][:5]
array([ 0\. , 4.24926332, 8.8630893 , 5.01378992, 10.05620093])
- 按照接近程度对点进行排名非常简单,使用 np.argsort:
ranks = np.argsort(distances[0])
ranks[:5]
array([ 0, 63, 6, 21, 17], dtype=int64)
argsort的一个优点是,现在我们可以对points矩阵进行排序,从而获得实际的点:
points[ranks][:5]
array([[-0.15728042, -5.76309092],
[-0.20720885, -5.52734277],
[-0.08686778, -6.42054076],
[ 0.33493582, -6.29824601],
[-0.89842683, -5.78335127]])
sp_points = points[ranks][:5]
- 看到最接近的点是什么样子是很有用的,如下所示。选定的点[0]被涂成绿色。最接近的点被涂成红色(除了选定的点)。
请注意,除了某些保证之外,这个过程按预期工作:
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=(10,7))
plt.scatter(points[:,0], points[:,1], label = 'All Points')
plt.scatter(sp_points[:,0],sp_points[:,1],color='red', label='Closest Points')
plt.scatter(points[0,0],points[0,1],color='green', label = 'Chosen Point')
plt.legend()

它是如何工作的...
给定某个距离函数,每个点通过成对的函数进行测量。考虑两个点,表示为 N 维空间中的向量,具有分量p[i]和q[i];默认情况下是欧几里得距离,其公式如下:

口头上,这个过程是计算两个向量每个分量之间的差值,平方这些差异,求和所有差异,然后求平方根。这个过程看起来非常熟悉,因为我们在研究均方误差时用过类似的东西。如果我们取平方根,我们就得到了相同的结果。事实上,常用的一个度量是均方根偏差(RMSE),它就是应用的距离函数。
在 Python 中,这看起来如下所示:
def euclid_distances(x, y):
return np.power(np.power(x - y, 2).sum(), .5)
euclid_distances(points[0], points[1])
4.249263322917467
在 scikit-learn 中还有其他一些函数,但 scikit-learn 还将使用 SciPy 的距离函数。在写这本书时,scikit-learn 的距离函数支持稀疏矩阵。有关距离函数的更多信息,请查阅 SciPy 文档:
-
cityblock -
cosine -
euclidean -
l1 -
l2 -
manhattan
我们现在可以解决问题了。例如,如果我们站在网格的原点上,而这些线是街道,我们需要走多远才能到达点(5, 5)?
pairwise.pairwise_distances([[0, 0], [5, 5]], metric='cityblock')[0]
array([ 0., 10.])
还有更多...
使用成对距离,我们可以找到比特向量之间的相似度。对于 N 维向量p和q,问题就变成了计算汉明距离,其定义如下:

使用以下命令:
X = np.random.binomial(1, .5, size=(2, 4)).astype(np.bool)
X
array([[False, False, False, False],
[False, True, True, True]], dtype=bool)
pairwise.pairwise_distances(X, metric='hamming')
array([[ 0\. , 0.75],
[ 0.75, 0\. ]])
请注意,scikit-learn 的hamming度量返回的是汉明距离除以向量的长度,在此情况下为4。
使用高斯混合模型进行概率聚类
在 k-means 中,我们假设簇的方差是相等的。这导致了一种空间的划分,决定了簇的分配方式;但是,如果方差不相等,并且每个簇点与其有某种概率关联,应该如何处理呢?
准备工作
有一种更具概率性的方式来看待 k-means 聚类。硬性 k-means 聚类等同于应用一个具有协方差矩阵 S 的高斯混合模型(GMM),其中协方差矩阵 S 可以分解为误差和单位矩阵的乘积。这对于每个聚类来说都是相同的协方差结构,导致聚类呈球形。然而,如果我们允许 S 变化,则可以估计 GMM 并用于预测。我们将首先在一维空间中看看这种方法如何运作,然后扩展到更多维度。
如何操作...
- 首先,我们需要创建一些数据。例如,我们可以模拟女性和男性的身高。在整个例子中,我们将使用这个例子。它是一个简单的示例,但希望它能够展示我们在 N 维空间中试图实现的目标,这样稍微更容易进行可视化:
import numpy as np
N = 1000
in_m = 72
in_w = 66
s_m = 2
s_w = s_m
m = np.random.normal(in_m, s_m, N)
w = np.random.normal(in_w, s_w, N)
from matplotlib import pyplot as plt
%matplotlib inline
f, ax = plt.subplots(figsize=(7, 5))
ax.set_title("Histogram of Heights")
ax.hist(m, alpha=.5, label="Men");
ax.hist(w, alpha=.5, label="Women");
ax.legend()
这是输出结果:

- 接下来,我们可能有兴趣对子集进行抽样,拟合分布,然后预测剩余的组:
random_sample = np.random.choice([True, False], size=m.size)
m_test = m[random_sample]
m_train = m[~random_sample]
w_test = w[random_sample]
w_train = w[~random_sample]
- 现在,我们需要根据训练集获取男性和女性身高的经验分布:
from scipy import stats
m_pdf = stats.norm(m_train.mean(), m_train.std())
w_pdf = stats.norm(w_train.mean(), w_train.std())
对于测试集,我们将根据数据点来自某个分布的似然度进行计算,最有可能的分布将被分配到相应的标签。
- 我们当然会查看我们的准确度:
m_pdf.pdf(m[0])
0.19762291119664221
w_pdf.pdf(m[0])
0.00085042279862613103
- 注意似然度的差异。假设我们在男性概率较高时进行猜测,但如果女性的概率更高时,我们会覆盖这些猜测:
guesses_m = np.ones_like(m_test)
guesses_m[m_pdf.pdf(m_test) < w_pdf.pdf(m_test)] = 0
- 显然,问题是我们有多准确。由于如果我们猜对了,
guesses_m将是 1,如果猜错了,则为 0,我们可以取向量的均值来计算准确度:
guesses_m.mean()
0.94176706827309242
- 还不错!现在,为了查看我们在女性组上的表现,我们使用以下命令:
guesses_w = np.ones_like(w_test)
guesses_w[m_pdf.pdf(w_test) > w_pdf.pdf(w_test)] = 0
guesses_w.mean()
0.93775100401606426
- 让我们允许不同组之间的方差有所不同。首先,创建一些新的数据:
s_m = 1
s_w = 4
m = np.random.normal(in_m, s_m, N)
w = np.random.normal(in_w, s_w, N)
- 然后,创建一个训练集:
m_test = m[random_sample]
m_train = m[~random_sample]
w_test = w[random_sample]
w_train = w[~random_sample]
f, ax = plt.subplots(figsize=(7, 5))
ax.set_title("Histogram of Heights")
ax.hist(m_train, alpha=.5, label="Men");
ax.hist(w_train, alpha=.5, label="Women");
ax.legend()

- 现在我们可以创建相同的概率密度函数(PDFs):
m_pdf = stats.norm(m_train.mean(), m_train.std())
w_pdf = stats.norm(w_train.mean(), w_train.std())
x = np.linspace(50,80,300)
plt.figure(figsize=(8,5))
plt.title('PDF of Heights')
plt.plot(x, m_pdf.pdf(x), 'k', linewidth=2, color='blue', label='Men')
plt.plot(x, w_pdf.pdf(x), 'k', linewidth=2, color='green',label='Women')
- 以下是输出结果:

你可以在一个多维空间中想象这个过程:
class_A = np.random.normal(0, 1, size=(100, 2))
class_B = np.random.normal(4, 1.5, size=(100, 2))
f, ax = plt.subplots(figsize=(8, 5))
plt.title('Random 2D Normal Draws')
ax.scatter(class_A[:,0], class_A[:,1], label='A', c='r')
ax.scatter(class_B[:,0], class_B[:,1], label='B')

它是如何工作的...
好的,现在我们已经看过如何根据分布来分类数据点,接下来我们看看如何在 scikit-learn 中实现这一点:
from sklearn.mixture import GaussianMixture
gmm = GaussianMixture(n_components=2)
X = np.row_stack((class_A, class_B))
y = np.hstack((np.ones(100), np.zeros(100)))
由于我们是认真的数据科学家,我们将创建一个训练集:
train = np.random.choice([True, False], 200)
gmm.fit(X[train])
GaussianMixture(covariance_type='full', init_params='kmeans', max_iter=100,
means_init=None, n_components=2, n_init=1, precisions_init=None,
random_state=None, reg_covar=1e-06, tol=0.001, verbose=0,
verbose_interval=10, warm_start=False, weights_init=None)
拟合和预测的方式与在 scikit-learn 中拟合其他对象时相同:
gmm.fit(X[train])
gmm.predict(X[train])[:5]
array([0, 0, 0, 0, 0], dtype=int64)
在模型拟合之后,还有其他方法值得关注。例如,使用 score_samples,我们可以获得每个标签的每个样本的似然度。
使用 k-means 进行异常值检测
在这个例子中,我们将讨论 k-means 在异常值检测中的辩论和机制。它在隔离某些类型的错误时可能有用,但使用时需要小心。
准备工作
我们将使用 k-means 对一组点进行异常值检测。值得注意的是,在异常值和异常值检测方面有许多不同的观点。一方面,我们可能通过移除异常值去除了一些由数据生成过程产生的点;另一方面,异常值可能是由于测量误差或其他外部因素导致的。
这是我们对这个辩论给出的最大信任。接下来的部分将关注识别异常值;我们将假设移除异常值的选择是合理的。异常值检测的过程是找到簇的质心,然后通过计算与质心的距离来识别潜在的异常值点。
如何操作……
- 首先,我们将生成一个包含 100 个点的单一簇,然后识别出距离质心最远的五个点。这些点可能是异常值:
from sklearn.datasets import make_blobs
X, labels = make_blobs(100, centers=1)
import numpy as np
- k-means 聚类必须具有一个单一的质心,这是很重要的。这个概念类似于用于异常值检测的单类 SVM:
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=1)
kmeans.fit(X)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=1, n_init=10, n_jobs=1, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
- 现在,让我们看看这个图。正在家里一起操作的朋友们,试着猜猜哪些点会被识别为五个异常值之一:
import matplotlib.pyplot as plt
%matplotlib inline
f, ax = plt.subplots(figsize=(8, 5))
ax.set_title("Blob")
ax.scatter(X[:, 0], X[:, 1], label='Points')
ax.scatter(kmeans.cluster_centers_[:, 0],kmeans.cluster_centers_[:, 1], label='Centroid',color='r')
ax.legend()
以下是输出结果:

- 现在,让我们识别出最接近的五个点:
distances = kmeans.transform(X)
# argsort returns an array of indexes which will sort the array in ascending order
# so we reverse it via [::-1] and take the top five with [:5]
sorted_idx = np.argsort(distances.ravel())[::-1][:5]
- 让我们看看哪些点离得最远:
f, ax = plt.subplots(figsize=(7, 5))
ax.set_title("Single Cluster")
ax.scatter(X[:, 0], X[:, 1], label='Points')
ax.scatter(kmeans.cluster_centers_[:, 0],kmeans.cluster_centers_[:, 1],label='Centroid', color='r')
ax.scatter(X[sorted_idx][:, 0], X[sorted_idx][:, 1],label='Extreme Value', edgecolors='g',facecolors='none', s=100)
ax.legend(loc='best')
以下是输出结果:

- 如果我们愿意,去除这些点是很简单的:
new_X = np.delete(X, sorted_idx, axis=0)
此外,随着这些点的移除,质心明显发生了变化:
new_kmeans = KMeans(n_clusters=1)
new_kmeans.fit(new_X)
- 让我们可视化一下旧质心和新质心之间的差异:
f, ax = plt.subplots(figsize=(7, 5))
ax.set_title("Extreme Values Removed")
ax.scatter(new_X[:, 0], new_X[:, 1], label='Pruned Points')
ax.scatter(kmeans.cluster_centers_[:, 0],kmeans.cluster_centers_[:, 1], label='Old Centroid',color='r',s=80, alpha=.5)
ax.scatter(new_kmeans.cluster_centers_[:, 0],new_kmeans.cluster_centers_[:, 1], label='New Centroid',color='m', s=80, alpha=.5)
ax.legend(loc='best')
以下是输出结果:

很明显,质心几乎没有移动,这在移除五个最极端的值时是可以预期的。这个过程可以重复进行,直到我们满意数据能代表这个过程。
它是如何工作的……
正如我们已经看到的,高斯分布和 k-means 聚类之间有着根本的联系。让我们基于质心和样本协方差矩阵创建一个经验高斯分布,并查看每个点的概率——理论上,这就是我们移除的那五个点。这实际上表明我们已经移除了最不可能出现的值。距离和可能性之间的这种关系非常重要,在你的机器学习训练中会经常遇到。使用以下命令创建经验高斯分布:
from scipy import stats
emp_dist = stats.multivariate_normal(kmeans.cluster_centers_.ravel())
lowest_prob_idx = np.argsort(emp_dist.pdf(X))[:5]
np.all(X[sorted_idx] == X[lowest_prob_idx])
True
使用 KNN 进行回归
回归在本书的其他部分已经涉及,但我们也可能想对特征空间中的小范围进行回归。我们可以认为我们的数据集受到多个数据处理过程的影响。如果这是真的,只有在相似的数据点上进行训练才是明智的选择。
准备工作
我们的老朋友——回归,可以在聚类的背景下使用。回归显然是一种监督学习技术,因此我们将使用K-最近邻(KNN)聚类,而不是 K 均值聚类。对于 KNN 回归,我们将使用特征空间中离测试点最近的 K 个点来构建回归模型,而不是像常规回归那样使用整个特征空间。
如何实现…
对于这个示例,我们将使用iris数据集。如果我们想预测每朵花的花瓣宽度,通过鸢尾花种类进行聚类可能会为我们带来更好的结果。KNN 回归不会根据种类进行聚类,但我们会假设相同种类的样本在特征空间中会较为接近,在这个例子中,即花瓣长度:
- 我们将使用
iris数据集进行这个示例:
import numpy as np
from sklearn import datasets
iris = datasets.load_iris()
iris.feature_names
- 我们将尝试基于萼片的长度和宽度预测花瓣长度。我们还将拟合一个常规的线性回归,看看 KNN 回归与它相比表现如何:
X = iris.data[:,:2]
y = iris.data[:,2]
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(X, y)
print "The MSE is: {:.2}".format(np.power(y - lr.predict(X),2).mean())
The MSE is: 0.41
- 现在,对于 KNN 回归,使用以下代码:
from sklearn.neighbors import KNeighborsRegressor
knnr = KNeighborsRegressor(n_neighbors=10)
knnr.fit(X, y)
print "The MSE is: {:.2}".format(np.power(y - knnr.predict(X),2).mean())
The MSE is: 0.17
- 让我们看看当我们让 KNN 回归使用离测试点最近的 10 个点时,结果会是怎样:
f, ax = plt.subplots(nrows=2, figsize=(7, 10))
ax[0].set_title("Predictions")
ax[0].scatter(X[:, 0], X[:, 1], s=lr.predict(X)*80, label='LR Predictions', color='c', edgecolors='black')
ax[1].scatter(X[:, 0], X[:, 1], s=knnr.predict(X)*80, label='k-NN Predictions', color='m', edgecolors='black')
ax[0].legend()
ax[1].legend()

- 预测结果大部分是接近的,这可能是显而易见的,但让我们看看 Setosa 种类的预测结果与实际值的对比:
setosa_idx = np.where(iris.target_names=='setosa')
setosa_mask = iris.target == setosa_idx[0]
y[setosa_mask][:5]
array([ 1.4, 1.4, 1.3, 1.5, 1.4])
knnr.predict(X)[setosa_mask][:5]
array([ 1.46, 1.47, 1.51, 1.42, 1.48])
lr.predict(X)[setosa_mask][:5]
array([ 1.83762646, 2.1510849 , 1.52707371, 1.48291658, 1.52562087])
- 再次查看图表,我们看到 Setosa 种类(左上角的聚类)在常规线性回归中被大大高估,而 KNN 则非常接近实际值。
它是如何工作的..
KNN 回归非常简单,通过取离测试点最近的K个点的平均值来计算。让我们手动预测一个点:
example_point = X[0]
现在,我们需要找到离我们的example_point最近的 10 个点:
from sklearn.metrics import pairwise
distances_to_example = pairwise.pairwise_distances(X)[0]
ten_closest_points = X[np.argsort(distances_to_example)][:10]
ten_closest_y = y[np.argsort(distances_to_example)][:10]
ten_closest_y.mean()
1.46
我们可以看到,结果非常接近预期。
第七章:交叉验证和后模型工作流
在本章中,我们将涵盖以下内容:
-
使用交叉验证选择模型
-
K 折交叉验证
-
平衡交叉验证
-
使用 ShuffleSplit 的交叉验证
-
时间序列交叉验证
-
使用 scikit-learn 进行网格搜索
-
使用 scikit-learn 进行随机搜索
-
分类度量
-
回归度量
-
聚类度量
-
使用虚拟估计器比较结果
-
特征选择
-
L1 范数上的特征选择
-
使用 joblib 或 pickle 持久化模型
介绍
这也许是最重要的章节。本章所探讨的基本问题如下:
- 我们如何选择一个预测良好的模型?
这就是交叉验证的目的,不管模型是什么。这与传统统计略有不同,传统统计更关心我们如何更好地理解现象。(为什么要限制我对理解的追求?好吧,因为有越来越多的数据,我们不一定能够全部查看、反思并创建理论模型。)
机器学习关注预测以及机器学习算法如何处理新的未见数据并得出预测。即使它看起来不像传统统计,你可以使用解释和领域理解来创建新列(特征)并做出更好的预测。你可以使用传统统计来创建新列。
书的早期,我们从训练/测试拆分开始。交叉验证是许多关键训练和测试拆分的迭代,以最大化预测性能。
本章探讨以下内容:
-
交叉验证方案
-
网格搜索——在估计器中找到最佳参数是什么?
-
评估指标比较
y_test与y_pred——真实目标集与预测目标集
下面一行包含交叉验证方案 cv = 10,用于 neg_log_lost 评分机制,该机制由 log_loss 指标构建:
cross_val_score(SVC(), X_train, y_train, cv = 10, scoring='neg_log_loss')
Scikit-learn 的一部分力量在于在一行代码中包含如此多的信息。此外,我们还将看到一个虚拟估计器,查看特征选择,并保存训练好的模型。这些方法真正使得机器学习成为它所是的东西。
使用交叉验证选择模型
我们看到了自动交叉验证,在 第一章,高性能机器学习 – NumPy 中的 cross_val_score 函数。这将非常相似,除了我们将使用鸢尾花数据集的最后两列作为数据。本节的目的是选择我们可以选择的最佳模型。
在开始之前,我们将定义最佳模型为得分最高的模型。如果出现并列,我们将选择波动最小的模型。
准备就绪
在这个配方中,我们将执行以下操作:
-
加载鸢尾花数据集的最后两个特征(列)
-
将数据拆分为训练数据和测试数据
-
实例化两个k 近邻(KNN)算法,分别设置为三个和五个邻居。
-
对两个算法进行评分
-
选择得分最好的模型
从加载数据集开始:
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data[:,2:]
y = iris.target
将数据拆分为训练集和测试集。样本是分层抽样的,书中默认使用这种方法。分层抽样意味着目标变量在训练集和测试集中的比例相同(此外,random_state被设置为7):
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify = y,random_state = 7)
如何操作……
- 首先,实例化两个最近邻算法:
from sklearn.neighbors import KNeighborsClassifier
kn_3 = KNeighborsClassifier(n_neighbors = 3)
kn_5 = KNeighborsClassifier(n_neighbors = 5)
- 现在,使用
cross_val_score对两个算法进行评分。查看kn_3_scores,这是得分的列表:
from sklearn.model_selection import cross_val_score
kn_3_scores = cross_val_score(kn_3, X_train, y_train, cv=4)
kn_5_scores = cross_val_score(kn_5, X_train, y_train, cv=4)
kn_3_scores
array([ 0.9 , 0.92857143, 0.92592593, 1\. ])
- 查看
kn_5_scores,这是另一个得分列表:
kn_5_scores
array([ 0.96666667, 0.96428571, 0.88888889, 1\. ])
- 查看两个列表的基本统计信息。查看均值:
print "Mean of kn_3: ",kn_3_scores.mean()
print "Mean of kn_5: ",kn_5_scores.mean()
Mean of kn_3: 0.938624338624
Mean of kn_5: 0.95496031746
- 查看分布,查看标准差:
print "Std of kn_3: ",kn_3_scores.std()
print "Std of kn_5: ",kn_5_scores.std()
Std of kn_3: 0.037152126551
Std of kn_5: 0.0406755710299
总体来说,当算法设置为五个邻居时,kn_5的表现比三个邻居稍好,但它的稳定性较差(它的得分有点分散)。
- 现在我们进行最后一步:选择得分最高的模型。我们选择
kn_5,因为它的得分最高。(该模型在交叉验证下得分最高。请注意,涉及的得分是最近邻的默认准确率得分:正确分类的比例除以所有尝试分类的数量。)
它是如何工作的……
这是一个 4 折交叉验证的示例,因为在cross_val_score函数中,cv = 4。我们将训练数据,或交叉验证集(X_train),拆分为四个部分,或称折叠。我们通过轮流将每个折叠作为测试集来迭代。首先,折叠 1 是测试集,而折叠 2、3 和 4 一起构成训练集。接下来,折叠 2 是测试集,而折叠 1、3 和 4 是训练集。我们还对折叠 3 和折叠 4 进行类似的操作:

一旦我们将数据集拆分为折叠,我们就对算法进行四次评分:
-
我们在折叠 2、3 和 4 上训练其中一个最近邻算法。
-
然后我们对折叠 1 进行预测,即测试折。
-
我们衡量分类准确性:将测试折与该折的预测结果进行比较。这是列表中第一个分类分数。
该过程执行了四次。最终输出是一个包含四个分数的列表。
总体来说,我们进行了整个过程两次,一次用于kn_3,一次用于kn_5,并生成了两个列表以选择最佳模型。我们从中导入的模块叫做model_selection,因为它帮助我们选择最佳模型。
K 折交叉验证
在寻找最佳模型的过程中,你可以查看交叉验证折叠的索引,看看每个折叠中有哪些数据。
准备工作
创建一个非常小的玩具数据集:
import numpy as np
X = np.array([[1, 2], [3, 4], [5, 6], [7, 8],[1, 2], [3, 4], [5, 6], [7, 8]])
y = np.array([1, 2, 1, 2, 1, 2, 1, 2])
如何操作……
- 导入
KFold并选择拆分的数量:
from sklearn.model_selection import KFold
kf= KFold(n_splits = 4)
- 你可以遍历生成器并打印出索引:
cc = 1
for train_index, test_index in kf.split(X):
print "Round : ",cc,": ",
print "Training indices :", train_index,
print "Testing indices :", test_index
cc += 1
Round 1 : Training indices : [2 3 4 5 6 7] Testing indices : [0 1]
Round 2 : Training indices : [0 1 4 5 6 7] Testing indices : [2 3]
Round 3 : Training indices : [0 1 2 3 6 7] Testing indices : [4 5]
Round 4 : Training indices : [0 1 2 3 4 5] Testing indices : [6 7]
你可以看到,例如,在第一轮中有两个测试索引,0和1。[0 1]构成了第一个折叠。[2 3 4 5 6 7]是折叠 2、3 和 4 的组合。
- 你还可以查看拆分的次数:
kf.get_n_splits()
4
分割数为 4,这是我们实例化 KFold 类时设置的。
还有更多...
如果愿意,可以查看折叠数据本身。将生成器存储为列表:
indices_list = list(kf.split(X))
现在,indices_list 是一个元组的列表。查看第四个折叠的信息:
indices_list[3] #the list is indexed from 0 to 3
(array([0, 1, 2, 3, 4, 5], dtype=int64), array([6, 7], dtype=int64))
此信息与前面的打印输出信息相匹配,但它以两个 NumPy 数组的元组形式给出。查看第四个折叠的实际数据。查看第四个折叠的训练数据:
train_indices, test_indices = indices_list[3]
X[train_indices]
array([[1, 2],
[3, 4],
[5, 6],
[7, 8],
[1, 2],
[3, 4]])
y[train_indices]
array([1, 2, 1, 2, 1, 2])
查看测试数据:
X[test_indices]
array([[5, 6],
[7, 8]])
y[test_indices]
array([1, 2])
平衡交叉验证
在将不同折叠中的不同数据集分割时,您可能会想知道:k 折交叉验证中每个折叠中的不同集合可能会非常不同吗?每个折叠中的分布可能会非常不同,这些差异可能导致得分的波动。
对此有一个解决方案,使用分层交叉验证。数据集的子集看起来像整个数据集的较小版本(至少在目标变量方面)。
准备工作
创建一个玩具数据集如下:
import numpy as np
X = np.array([[1, 2], [3, 4], [5, 6], [7, 8],[1, 2], [3, 4], [5, 6], [7, 8]])
y = np.array([1, 1, 1, 1, 2, 2, 2, 2])
如何做...
- 如果我们在这个小型玩具数据集上执行 4 折交叉验证,每个测试折叠将只有一个目标值。可以使用
StratifiedKFold来解决这个问题:
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits = 4)
- 打印出折叠的索引:
cc = 1
for train_index, test_index in skf.split(X,y):
print "Round",cc,":",
print "Training indices :", train_index,
print "Testing indices :", test_index
cc += 1
Round 1 : Training indices : [1 2 3 5 6 7] Testing indices : [0 4]
Round 2 : Training indices : [0 2 3 4 6 7] Testing indices : [1 5]
Round 3 : Training indices : [0 1 3 4 5 7] Testing indices : [2 6]
Round 4 : Training indices : [0 1 2 4 5 6] Testing indices : [3 7]
观察 skf 类的 split 方法,即分层 k 折叠分割,具有两个参数 X 和 y。它试图在每个折叠集中以相同的分布分配目标 y。在这种情况下,每个子集都有 50% 的 1 和 50% 的 2,就像整个目标集 y 一样。
还有更多...
您可以使用 StratifiedShuffleSplit 重新洗牌分层折叠。请注意,这并不会尝试制作具有互斥测试集的四个折叠:
from sklearn.model_selection import StratifiedShuffleSplit
sss = StratifiedShuffleSplit(n_splits = 5,test_size=0.25)
cc = 1
for train_index, test_index in sss.split(X,y):
print "Round",cc,":",
print "Training indices :", train_index,
print "Testing indices :", test_index
cc += 1
Round 1 : Training indices : [1 6 5 7 0 2] Testing indices : [4 3]
Round 2 : Training indices : [3 2 6 7 5 0] Testing indices : [1 4]
Round 3 : Training indices : [2 1 4 7 0 6] Testing indices : [3 5]
Round 4 : Training indices : [4 2 7 6 0 1] Testing indices : [5 3]
Round 5 : Training indices : [1 2 0 5 4 7] Testing indices : [6 3]
Round 6 : Training indices : [0 6 5 1 7 3] Testing indices : [2 4]
Round 7 : Training indices : [1 7 3 6 2 5] Testing indices : [0 4]
这些分割不是数据集的分割,而是随机过程的迭代,每个迭代的训练集大小为整个数据集的 75%,测试集大小为 25%。所有迭代都是分层的。
使用 ShuffleSplit 的交叉验证
ShuffleSplit 是最简单的交叉验证技术之一。使用这种交叉验证技术只需取数据的样本,指定的迭代次数。
准备工作
ShuffleSplit 是一种简单的验证技术。我们将指定数据集中的总元素数量,其余由它来处理。我们将通过估计单变量数据集的均值来示例化。这类似于重新采样,但它将说明为什么我们要在展示交叉验证时使用交叉验证。
如何做...
- 首先,我们需要创建数据集。我们将使用 NumPy 创建一个数据集,其中我们知道底层均值。我们将对数据集的一半进行采样以估计均值,并查看它与底层均值的接近程度。生成一个均值为 1000,标准差为 10 的正态分布随机样本:
%matplotlib inline
import numpy as np
true_mean = 1000
true_std = 10
N = 1000
dataset = np.random.normal(loc= true_mean, scale = true_std, size=N)
import matplotlib.pyplot as plt
f, ax = plt.subplots(figsize=(10, 7))
ax.hist(dataset, color='k', alpha=.65, histtype='stepfilled',bins=50)
ax.set_title("Histogram of dataset")

- 估计数据集的一半的平均值:
holdout_set = dataset[:500]
fitting_set = dataset[500:]
estimate = fitting_set[:N/2].mean()
estimate
999.69789261486721
- 你也可以获取整个数据集的均值:
data_mean = dataset.mean()
data_mean
999.55177343767843
- 它不是 1,000,因为随机选择了点来创建数据集。为了观察
ShuffleSplit的行为,写出以下代码并绘制图表:
from sklearn.model_selection import ShuffleSplit
shuffle_split = ShuffleSplit(n_splits=100, test_size=.5, random_state=0)
mean_p = []
estimate_closeness = []
for train_index, not_used_index in shuffle_split.split(fitting_set):
mean_p.append(fitting_set[train_index].mean())
shuf_estimate = np.mean(mean_p)
estimate_closeness.append(np.abs(shuf_estimate - dataset.mean()))
plt.figure(figsize=(10,5))
plt.plot(estimate_closeness)

估计的均值不断接近数据的均值 999.55177343767843,并在距离数据均值 0.1 时停滞。它比仅用一半数据估算出的均值更接近数据的均值。
时间序列交叉验证
scikit-learn 可以对时间序列数据(例如股市数据)进行交叉验证。我们将使用时间序列拆分,因为我们希望模型能够预测未来,而不是从未来泄漏信息。
准备工作
我们将为时间序列拆分创建索引。首先创建一个小的玩具数据集:
from sklearn.model_selection import TimeSeriesSplit
import numpy as np
X = np.array([[1, 2], [3, 4], [1, 2], [3, 4],[1, 2], [3, 4], [1, 2], [3, 4]])
y = np.array([1, 2, 3, 4, 1, 2, 3, 4])
如何做...
- 现在创建一个时间序列拆分对象:
tscv = TimeSeriesSplit(n_splits=7)
- 遍历它:
for train_index, test_index in tscv.split(X):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
print "Training indices:", train_index, "Testing indices:", test_index
Training indices: [0] Testing indices: [1]
Training indices: [0 1] Testing indices: [2]
Training indices: [0 1 2] Testing indices: [3]
Training indices: [0 1 2 3] Testing indices: [4]
Training indices: [0 1 2 3 4] Testing indices: [5]
Training indices: [0 1 2 3 4 5] Testing indices: [6]
Training indices: [0 1 2 3 4 5 6] Testing indices: [7]
- 你也可以通过创建一个包含元组的列表来保存索引:
tscv_list = list(tscv.split(X))
还有更多...
你也可以使用 NumPy 或 pandas 创建滚动窗口。时间序列交叉验证的主要要求是测试集必须出现在训练集之后;否则,你就会从未来预测过去。
时间序列交叉验证很有趣,因为根据数据集的不同,时间的影响是不同的。有时,你不需要将数据行按时间顺序排列,但你永远不能假设你知道过去的未来。
使用 scikit-learn 进行网格搜索
在模型选择和交叉验证章节的开头,我们尝试为鸢尾花数据集的最后两个特征选择最佳的最近邻模型。现在,我们将使用 GridSearchCV 在 scikit-learn 中重新聚焦这一点。
准备工作
首先,加载鸢尾花数据集的最后两个特征。将数据拆分为训练集和测试集:
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data[:,2:]
y = iris.target
from sklearn.model_selection import train_test_split, cross_val_score
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify = y,random_state = 7)
如何做...
- 实例化一个最近邻分类器:
from sklearn.neighbors import KNeighborsClassifier
knn_clf = KNeighborsClassifier()
- 准备一个参数网格,这是网格搜索所必需的。参数网格是一个字典,包含你希望尝试的参数设置:
param_grid = {'n_neighbors': list(range(3,9,1))}
-
实例化一个网格搜索,传入以下参数:
-
估计器
-
参数网格
-
一种交叉验证方法,
cv=10
-
from sklearn.model_selection import GridSearchCV
gs = GridSearchCV(knn_clf,param_grid,cv=10)
- 拟合网格搜索估计器:
gs.fit(X_train, y_train)
- 查看结果:
gs.best_params_
{'n_neighbors': 3}
gs.cv_results_['mean_test_score']
zip(gs.cv_results_['params'],gs.cv_results_['mean_test_score'])
[({'n_neighbors': 3}, 0.9553571428571429),
({'n_neighbors': 4}, 0.9375),
({'n_neighbors': 5}, 0.9553571428571429),
({'n_neighbors': 6}, 0.9553571428571429),
({'n_neighbors': 7}, 0.9553571428571429),
({'n_neighbors': 8}, 0.9553571428571429)]
它是如何工作的...
在第一章中,我们尝试了蛮力法,即使用 Python 扫描最佳得分:
all_scores = []
for n_neighbors in range(3,9,1):
knn_clf = KNeighborsClassifier(n_neighbors = n_neighbors)
all_scores.append((n_neighbors, cross_val_score(knn_clf, X_train, y_train, cv=10).mean()))
sorted(all_scores, key = lambda x:x[1], reverse = True)
[(3, 0.95666666666666667),
(5, 0.95666666666666667),
(6, 0.95666666666666667),
(7, 0.95666666666666667),
(8, 0.95666666666666667),
(4, 0.94000000000000006)]
这种方法的问题是,它更加耗时且容易出错,尤其是当涉及更多参数或额外的转换(如使用管道时)时。
请注意,网格搜索和蛮力法方法都会扫描所有可能的参数值。
使用 scikit-learn 进行随机搜索
从实际角度来看,RandomizedSearchCV 比常规网格搜索更为重要。因为对于中等大小的数据,或者涉及少量参数的模型,进行完整网格搜索的所有参数组合计算开销太大。
计算资源最好用于非常好地分层采样,或者改进随机化过程。
准备就绪
如前所述,加载鸢尾花数据集的最后两个特征。将数据拆分为训练集和测试集:
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data[:,2:]
y = iris.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify = y,random_state = 7)
如何操作...
- 实例化一个最近邻分类器:
from sklearn.neighbors import KNeighborsClassifier
knn_clf = KNeighborsClassifier()
- 准备一个参数分布,这是进行随机网格搜索时必需的。参数分布是一个字典,包含你希望随机尝试的参数设置:
param_dist = {'n_neighbors': list(range(3,9,1))}
-
实例化一个随机网格搜索并传入以下参数:
-
估算器
-
参数分布
-
一种交叉验证类型,
cv=10 -
运行此过程的次数,
n_iter
-
from sklearn.model_selection import RandomizedSearchCV
rs = RandomizedSearchCV(knn_clf,param_dist,cv=10,n_iter=6)
- 拟合随机网格搜索估算器:
rs.fit(X_train, y_train)
- 查看结果:
rs.best_params_
{'n_neighbors': 3}
zip(rs.cv_results_['params'],rs.cv_results_['mean_test_score'])
[({'n_neighbors': 3}, 0.9553571428571429),
({'n_neighbors': 4}, 0.9375),
({'n_neighbors': 5}, 0.9553571428571429),
({'n_neighbors': 6}, 0.9553571428571429),
({'n_neighbors': 7}, 0.9553571428571429),
({'n_neighbors': 8}, 0.9553571428571429)]
- 在这种情况下,我们实际上对所有六个参数进行了网格搜索。你本可以扫描更大的参数空间:
param_dist = {'n_neighbors': list(range(3,50,1))}
rs = RandomizedSearchCV(knn_clf,param_dist,cv=10,n_iter=15)
rs.fit(X_train,y_train)
rs.best_params_
{'n_neighbors': 16}
- 尝试使用 IPython 计时此过程:
%timeit rs.fit(X_train,y_train)
1 loop, best of 3: 1.06 s per loop
- 计时网格搜索过程:
from sklearn.model_selection import GridSearchCV
param_grid = {'n_neighbors': list(range(3,50,1))}
gs = GridSearchCV(knn_clf,param_grid,cv=10)
%timeit gs.fit(X_train,y_train)
1 loop, best of 3: 3.24 s per loop
- 查看网格搜索的最佳参数:
gs.best_params_
{'n_neighbors': 3}
- 结果表明,3-最近邻的得分与 16-最近邻相同:
zip(gs.cv_results_['params'],gs.cv_results_['mean_test_score'])
[({'n_neighbors': 3}, 0.9553571428571429),
({'n_neighbors': 4}, 0.9375),
...
({'n_neighbors': 14}, 0.9553571428571429),
({'n_neighbors': 15}, 0.9553571428571429),
({'n_neighbors': 16}, 0.9553571428571429),
({'n_neighbors': 17}, 0.9464285714285714),
...
因此,我们在三分之一的时间内得到了相同的分数。
是否使用随机搜索,这是你需要根据具体情况做出的决定。你应该使用随机搜索来尝试了解某个算法的表现。可能无论参数如何,算法的表现都很差,这时你可以换一个算法。如果算法表现非常好,可以使用完整的网格搜索来寻找最佳参数。
此外,除了专注于穷举搜索,你还可以通过集成、堆叠或混合一组合理表现良好的算法来进行尝试。
分类指标
在本章早些时候,我们探讨了基于邻居数量 n_neighbors 参数选择几个最近邻实例的最佳方法。这是最近邻分类中的主要参数:基于 KNN 的标签对一个点进行分类。所以,对于 3-最近邻,根据三个最近点的标签对一个点进行分类。对这三个最近点进行多数投票。
该分类指标在此案例中是内部指标 accuracy_score,定义为正确分类的数量除以分类总数。还有其他指标,我们将在这里进行探讨。
准备就绪
- 首先,从 UCI 数据库加载 Pima 糖尿病数据集:
import pandas as pd
data_web_address = "https://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data"
column_names = ['pregnancy_x',
'plasma_con',
'blood_pressure',
'skin_mm',
'insulin',
'bmi',
'pedigree_func',
'age',
'target']
feature_names = column_names[:-1]
all_data = pd.read_csv(data_web_address , names=column_names)
- 将数据拆分为训练集和测试集:
import numpy as np
import pandas as pd
X = all_data[feature_names]
y = all_data['target']
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7,stratify=y)
- 回顾上一部分,使用 KNN 算法运行随机搜索:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import RandomizedSearchCV
knn_clf = KNeighborsClassifier()
param_dist = {'n_neighbors': list(range(3,20,1))}
rs = RandomizedSearchCV(knn_clf,param_dist,cv=10,n_iter=17)
rs.fit(X_train, y_train)
- 然后显示最佳准确率得分:
rs.best_score_
0.75407166123778502
- 此外,查看测试集上的混淆矩阵:
y_pred = rs.predict(X_test)
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_pred)
array([[84, 16],
[27, 27]])
混淆矩阵提供了更具体的关于模型表现的信息。有 27 次模型预测某人没有糖尿病,尽管他们实际上有。这比 16 个被认为有糖尿病的人实际上没有糖尿病的错误更为严重。
在这种情况下,我们希望最大化灵敏度或召回率。在检查线性模型时,我们查看了召回率或灵敏度的定义:

因此,在这种情况下,灵敏度得分为 27/ (27 + 27) = 0.5。使用 scikit-learn,我们可以方便地按如下方式计算此值。
如何操作...
- 从 metrics 模块导入
recall_score。使用y_test和y_pred测量集合的灵敏度:
from sklearn.metrics import recall_score
recall_score(y_test, y_pred)
0.5
我们恢复了之前手动计算的召回得分。在随机搜索中,我们本可以使用 recall_score 来找到具有最高召回率的最近邻实例。
- 导入
make_scorer并使用带有两个参数的函数recall_score和greater_is_better:
from sklearn.metrics import make_scorer
recall_scorer = make_scorer(recall_score, greater_is_better=True)
- 现在执行随机网格搜索:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import RandomizedSearchCV
knn_clf = KNeighborsClassifier()
param_dist = {'n_neighbors': list(range(3,20,1))}
rs = RandomizedSearchCV(knn_clf,param_dist,cv=10,n_iter=17,scoring=recall_scorer)
rs.fit(X_train, y_train)
- 现在查看最高得分:
rs.best_score_
0.5649632669176643
- 查看召回得分:
y_pred = rs.predict(X_test)
recall_score(y_test,y_pred)
0.5
- 结果与之前相同。在随机搜索中,你本可以尝试
roc_auc_score,即曲线下面积(ROC AUC):
from sklearn.metrics import roc_auc_score
rs = RandomizedSearchCV(knn_clf,param_dist,cv=10,n_iter=17,scoring=make_scorer(roc_auc_score,greater_is_better=True))
rs.fit(X_train, y_train)
rs.best_score_
0.7100264217324479
还有更多...
你可以为分类设计自己的评分器。假设你是一个保险公司,并且你为混淆矩阵中的每个单元格分配了成本。相对成本如下:

我们正在查看的混淆矩阵的成本可以按如下方式计算:
costs_array = confusion_matrix(y_test, y_pred) * np.array([[1,2],
[100,20]])
costs_array
array([[ 84, 32],
[2700, 540]])
现在加总总成本:
costs_array.sum()
3356
现在将其放入评分器中并运行网格搜索。评分器中的参数 greater_is_better 设置为 False,因为成本应尽可能低:
def costs_total(y_test, y_pred):
return (confusion_matrix(y_test, y_pred) * np.array([[1,2],
[100,20]])).sum()
costs_scorer = make_scorer(costs_total, greater_is_better=False)
rs = RandomizedSearchCV(knn_clf,param_dist,cv=10,n_iter=17,scoring=costs_scorer)
rs.fit(X_train, y_train)
rs.best_score_
-1217.5879478827362
得分为负,因为当 make_scorer 函数中的 greater_is_better 参数为 false 时,得分会乘以 -1。网格搜索试图最大化该得分,从而最小化得分的绝对值。
测试集的成本如下:
costs_total(y_test,rs.predict(X_test))
3356
在查看这个数字时,别忘了查看测试集中涉及的个体数量,共有 154 人。每个人的平均成本约为 21.8 美元。
回归指标
使用回归指标的交叉验证在 scikit-learn 中非常简单。可以从 sklearn.metrics 导入评分函数并将其放入 make_scorer 函数中,或者你可以为特定的数据科学问题创建自定义评分器。
准备就绪
加载一个使用回归指标的数据集。我们将加载波士顿房价数据集并将其拆分为训练集和测试集:
from sklearn.datasets import load_boston
boston = load_boston()
X = boston.data
y = boston.target
from sklearn.model_selection import train_test_split, cross_val_score
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)
我们对数据集了解不多。我们可以尝试使用高方差算法进行快速网格搜索:
from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import RandomizedSearchCV
knn_reg = KNeighborsRegressor()
param_dist = {'n_neighbors': list(range(3,20,1))}
rs = RandomizedSearchCV(knn_reg,param_dist,cv=10,n_iter=17)
rs.fit(X_train, y_train)
rs.best_score_
0.46455839325055914
尝试一个不同的模型,这次是一个线性模型:
from sklearn.linear_model import Ridge
cross_val_score(Ridge(),X_train,y_train,cv=10).mean()
0.7439511908709866
默认情况下,两个回归器都衡量 r2_score,即 R 平方,因此线性模型更好。尝试一个不同的复杂模型,一个树的集成:
from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor
cross_val_score(GradientBoostingRegressor(max_depth=7),X_train,y_train,cv=10).mean()
0.83082671732165492
集成模型的表现更好。你也可以尝试随机森林:
cross_val_score(RandomForestRegressor(),X_train,y_train,cv=10).mean()
0.82474734196711685
现在我们可以通过最大化内部 R-squared 梯度提升评分器,专注于利用当前评分机制来改进梯度提升。尝试进行一两次随机搜索。这是第二次搜索:
param_dist = {'n_estimators': [4000], 'learning_rate': [0.01], 'max_depth':[1,2,3,5,7]}
rs_inst_a = RandomizedSearchCV(GradientBoostingRegressor(), param_dist, n_iter = 5, n_jobs=-1)
rs_inst_a.fit(X_train, y_train)
为 R-squared 优化返回了以下结果:
rs_inst_a.best_params_
{'learning_rate': 0.01, 'max_depth': 3, 'n_estimators': 4000}
rs_inst_a.best_score_
0.88548410382780185
梯度提升中的树的深度为三。
如何做到...
现在我们将执行以下操作:
-
创建一个评分函数。
-
使用该函数创建一个 scikit-scorer。
-
运行网格搜索以找到最佳的梯度提升参数,最小化误差函数。
让我们开始:
- 使用 Numba 即时编译(JIT)编译器创建平均百分比误差评分函数。原始的 NumPy 函数如下:
def mape_score(y_test, y_pred):
return (np.abs(y_test - y_pred)/y_test).mean()
- 让我们使用 Numba JIT 编译器重写这个函数,稍微加速一些。你可以用类似 C 的代码,通过 Numba 按位置索引数组:
from numba import autojit
@autojit
def mape_score(y_test, y_pred):
sum_total = 0
y_vec_length = len(y_test)
for index in range(y_vec_length):
sum_total += (1 - (y_pred[index]/y_test[index]))
return sum_total/y_vec_length
- 现在创建一个评分器。得分越低越好,不像 R-squared,那里的得分越高越好:
from sklearn.metrics import make_scorer
mape_scorer = make_scorer(mape_score, greater_is_better=False)
- 现在进行网格搜索:
param_dist = {'n_estimators': [4000], 'learning_rate': [0.01], 'max_depth':[1,2,3,4,5]}
rs_inst_b = RandomizedSearchCV(GradientBoostingRegressor(), param_dist, n_iter = 3, n_jobs=-1,scoring = mape_scorer)
rs_inst_b.fit(X_train, y_train)
rs_inst_b.best_score_
0.021086502313661441
rs_inst_b.best_params_
{'learning_rate': 0.01, 'max_depth': 1, 'n_estimators': 4000}
使用此度量,最佳得分对应于深度为 1 的梯度提升树。
聚类度量
衡量聚类算法的性能比分类或回归要复杂一些,因为聚类是无监督机器学习。幸运的是,scikit-learn 已经非常直接地为我们提供了帮助。
准备开始
为了衡量聚类性能,首先加载鸢尾花数据集。我们将鸢尾花重新标记为两种类型:当目标是 0 时为类型 0,当目标是 1 或 2 时为类型 1:
from sklearn.datasets import load_iris
import numpy as np
iris = load_iris()
X = iris.data
y = np.where(iris.target == 0,0,1)
如何做到...
- 实例化一个 k-means 算法并训练它。由于该算法是聚类算法,因此在训练时不要使用目标值:
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2,random_state=0)
kmeans.fit(X)
- 现在导入所有必要的库,通过交叉验证对 k-means 进行评分。我们将使用
adjusted_rand_score聚类性能指标:
from sklearn.metrics.cluster import adjusted_rand_score
from sklearn.metrics import make_scorer
from sklearn.model_selection import cross_val_score
cross_val_score(kmeans,X,y,cv=10,scoring=make_scorer(adjusted_rand_score)).mean()
0.8733695652173914
评估聚类算法与评估分类算法非常相似。
使用虚拟估算器进行结果比较
本食谱是关于创建虚拟估算器的;这不是很华丽或令人兴奋的部分,但它为你最终构建的模型提供了一个参考点,值得一做。
准备开始
在这个食谱中,我们将执行以下任务:
-
创建一些随机数据。
-
拟合各种虚拟估算器。
我们将对回归数据和分类数据执行这两步操作。
如何做到...
- 首先,我们将创建随机数据:
from sklearn.datasets import make_regression, make_classification
X, y = make_regression()
from sklearn import dummy
dumdum = dummy.DummyRegressor()
dumdum.fit(X, y)
DummyRegressor(constant=None, quantile=None, strategy='mean')
- 默认情况下,估算器将通过取值的均值并多次输出它来进行预测:
dumdum.predict(X)[:5]
>array([-25.0450033, -25.0450033, -25.0450033, -25.0450033, -25.0450033])
还有两种其他策略可以尝试。我们可以预测一个提供的常量(参考此食谱中的第一条命令块中的 constant=None)。我们还可以预测中位数值。仅当策略为常量时才会考虑提供常量。
- 我们来看看:
predictors = [("mean", None),
("median", None),
("constant", 10)]
for strategy, constant in predictors:
dumdum = dummy.DummyRegressor(strategy=strategy,
constant=constant)
dumdum.fit(X, y)
print "strategy: {}".format(strategy), ",".join(map(str, dumdum.predict(X)[:5]))
strategy: mean -25.0450032962,-25.0450032962,-25.0450032962,-25.0450032962,-25.0450032962
strategy: median -37.734448002,-37.734448002,-37.734448002,-37.734448002,-37.734448002
strategy: constant 10.0,10.0,10.0,10.0,10.0
- 我们实际上有四种分类器的选择。这些策略与连续情况类似,只不过更加倾向于分类问题:
predictors = [("constant", 0),("stratified", None),("uniform", None),("most_frequent", None)]
#We'll also need to create some classification data:
X, y = make_classification()
for strategy, constant in predictors:
dumdum = dummy.DummyClassifier(strategy=strategy,
constant=constant)
dumdum.fit(X, y)
print "strategy: {}".format(strategy), ",".join(map(str,dumdum.predict(X)[:5]))
strategy: constant 0,0,0,0,0
strategy: stratified 1,0,1,1,1
strategy: uniform 1,0,1,0,1
strategy: most_frequent 0,0,0,0,0
它是如何工作的...
测试你的模型是否能够比最简单的模型表现更好总是个不错的做法,这正是虚拟估计器所能提供的。例如,假设你有一个欺诈检测模型。在这个模型中,数据集中只有 5%是欺诈行为。因此,我们很可能通过仅仅不猜测数据是欺诈的,就能拟合出一个相当不错的模型。
我们可以通过使用分层策略并执行以下命令来创建此模型。我们还可以得到一个很好的例子,说明类别不平衡是如何导致问题的:
X, y = make_classification(20000, weights=[.95, .05])
dumdum = dummy.DummyClassifier(strategy='most_frequent')
dumdum.fit(X, y)
DummyClassifier(constant=None, random_state=None, strategy='most_frequent')
from sklearn.metrics import accuracy_score
print accuracy_score(y, dumdum.predict(X))
0.94615
我们实际上经常是正确的,但这并不是重点。重点是这是我们的基准。如果我们不能创建一个比这个更准确的欺诈检测模型,那么就不值得花费时间。
特征选择
本食谱以及接下来的两个将围绕自动特征选择展开。我喜欢将其视为参数调优的特征类比。就像我们通过交叉验证来寻找合适的参数一样,我们也可以找到一个合适的特征子集。这将涉及几种不同的方法。
最简单的想法是单变量选择。其他方法则涉及特征的组合使用。
特征选择的一个附加好处是,它可以减轻数据收集的负担。假设你已经基于一个非常小的数据子集建立了一个模型。如果一切顺利,你可能想扩大规模,在整个数据子集上预测模型。如果是这种情况,你可以在这个规模上减轻数据收集的工程负担。
准备工作
使用单变量特征选择时,评分函数将再次成为焦点。这一次,它们将定义我们可以用来消除特征的可比度量。
在本食谱中,我们将拟合一个包含大约 10,000 个特征的回归模型,但只有 1,000 个数据点。我们将逐步了解各种单变量特征选择方法:
from sklearn import datasets
X, y = datasets.make_regression(1000, 10000)
现在我们已经有了数据,我们将比较通过各种方法包含的特征。这实际上是你在处理文本分析或某些生物信息学领域时非常常见的情况。
如何操作...
- 首先,我们需要导入
feature_selection模块:
from sklearn import feature_selection
f, p = feature_selection.f_regression(X, y)
- 这里,
f是与每个线性模型拟合相关的f分数,该模型仅使用一个特征。然后我们可以比较这些特征,并根据这种比较来剔除特征。p是与f值相关的p值。在统计学中,p值是指在给定的检验统计量值下,出现比当前值更极端的结果的概率。在这里,f值是检验统计量:
f[:5]
array([ 1.23494617, 0.70831694, 0.09219176, 0.14583189, 0.78776466])
p[:5]
array([ 0.26671492, 0.40020473, 0.76147235, 0.7026321 , 0.37499074])
- 正如我们所见,许多
p值相当大。我们希望p值尽可能小。因此,我们可以从工具箱中取出 NumPy,选择所有小于.05的p值。这些将是我们用于分析的特征:
import numpy as np
idx = np.arange(0, X.shape[1])
features_to_keep = idx[p < .05]
len(features_to_keep)
496
如你所见,我们实际上保留了相对较多的特征。根据模型的上下文,我们可以缩小这个p值。这将减少保留的特征数量。
另一个选择是使用VarianceThreshold对象。我们已经了解了一些它的内容,但需要明白的是,我们拟合模型的能力在很大程度上依赖于特征所产生的方差。如果没有方差,那么我们的特征就无法描述因变量的变化。根据文档的说法,它的一个优点是由于它不使用结果变量,因此可以用于无监督的情况。
- 我们需要设定一个阈值,以决定去除哪些特征。为此,我们只需要取特征方差的中位数并提供它:
var_threshold = feature_selection.VarianceThreshold(np.median(np.var(X, axis=1)))
var_threshold.fit_transform(X).shape
(1000L, 4888L)
如我们所见,我们已经去除了大约一半的特征,这也大致符合我们的预期。
它是如何工作的...
一般来说,这些方法都是通过拟合一个只有单一特征的基本模型来工作的。根据我们是分类问题还是回归问题,我们可以使用相应的评分函数。
让我们看看一个较小的问题,并可视化特征选择如何去除某些特征。我们将使用第一个示例中的相同评分函数,但只使用 20 个特征:
X, y = datasets.make_regression(10000, 20)
f, p = feature_selection.f_regression(X, y)
现在让我们绘制特征的 p 值。我们可以看到哪些特征将被去除,哪些特征将被保留:
%matplotlib inline
from matplotlib import pyplot as plt
f, ax = plt.subplots(figsize=(7, 5))
ax.bar(np.arange(20), p, color='k')
ax.set_title("Feature p values")

如我们所见,许多特征将不会被保留,但有一些特征会被保留。
基于 L1 范数的特征选择
我们将使用一些与 LASSO 回归配方中看到的类似的思想。在那个配方中,我们查看了具有零系数的特征数量。现在我们将更进一步,利用与 L1 范数相关的稀疏性来预处理特征。
准备工作
我们将使用糖尿病数据集来进行回归拟合。首先,我们将使用 ShuffleSplit 交叉验证拟合一个基本的线性回归模型。完成后,我们将使用 LASSO 回归来找到系数为零的特征,这些特征在使用 L1 惩罚时会被去除。这有助于我们避免过拟合(即模型过于专门化,无法适应它未训练过的数据)。换句话说,如果模型过拟合,它对外部数据的泛化能力较差。
我们将执行以下步骤:
-
加载数据集。
-
拟合一个基本的线性回归模型。
-
使用特征选择去除不具信息量的特征。
-
重新拟合线性回归模型,并检查它与完全特征模型相比的拟合效果。
如何操作...
- 首先,让我们获取数据集:
import sklearn.datasets as ds
diabetes = ds.load_diabetes()
X = diabetes.data
y = diabetes.target
- 让我们创建
LinearRegression对象:
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
- 让我们从 metrics 模块导入
mean_squared_error函数和make_scorer包装器。从model_selection模块,导入ShuffleSplit交叉验证方案和cross_val_score交叉验证评分器。接下来,使用mean_squared_error度量来评分该函数:
from sklearn.metrics import make_scorer, mean_squared_error
from sklearn.model_selection import cross_val_score,ShuffleSplit
shuff = ShuffleSplit(n_splits=10, test_size=0.25, random_state=0)
score_before = cross_val_score(lr,X,y,cv=shuff,scoring=make_scorer(mean_squared_error,greater_is_better=False)).mean()
score_before
-3053.393446308266
- 现在我们已经有了常规拟合,让我们在去除系数为零的特征后检查一下。让我们拟合 LASSO 回归:
from sklearn.linear_model import LassoCV
lasso_cv = LassoCV()
lasso_cv.fit(X,y)
lasso_cv.coef_
array([ -0\. , -226.2375274 , 526.85738059, 314.44026013,
-196.92164002, 1.48742026, -151.78054083, 106.52846989,
530.58541123, 64.50588257])
- 我们将删除第一个特征。我将使用 NumPy 数组来表示要包含在模型中的列:
import numpy as np
columns = np.arange(X.shape[1])[lasso_cv.coef_ != 0]
columns
- 好的,现在我们将使用特定的特征来拟合模型(请参见以下代码块中的列):
score_afterwards = cross_val_score(lr,X[:,columns],y,cv=shuff, scoring=make_scorer(mean_squared_error,greater_is_better=False)).mean()
score_afterwards
-3033.5012859289677
之后的得分并没有比之前好多少,尽管我们已经消除了一个无信息特征。在还有更多内容...部分,我们将看到一个额外的示例。
还有更多内容...
- 首先,我们将创建一个具有许多无信息特征的回归数据集:
X, y = ds.make_regression(noise=5)
- 创建一个
ShuffleSplit实例,进行 10 次迭代,n_splits=10。测量普通线性回归的交叉验证得分:
shuff = ShuffleSplit(n_splits=10, test_size=0.25, random_state=0)
score_before = cross_val_score(lr,X,y,cv=shuff, scoring=make_scorer(mean_squared_error,greater_is_better=False)).mean()
- 实例化
LassoCV来消除无信息的列:
lasso_cv = LassoCV()
lasso_cv.fit(X,y)
- 消除无信息的列。查看最终得分:
columns = np.arange(X.shape[1])[lasso_cv.coef_ != 0]
score_afterwards = cross_val_score(lr,X[:,columns],y,cv=shuff, scoring=make_scorer(mean_squared_error,greater_is_better=False)).mean()
print "Score before:",score_before
print "Score after: ",score_afterwards
Score before: -8891.35368845
Score after: -22.3488585347
在我们移除无信息特征后,最后的拟合效果要好得多。
使用 joblib 或 pickle 持久化模型
在这个教程中,我们将展示如何将模型保留以供以后使用。例如,你可能希望使用一个模型来预测结果并自动做出决策。
做好准备
创建数据集并训练分类器:
from sklearn.datasets import make_classification
from sklearn.tree import DecisionTreeClassifier
X, y = make_classification()
dt = DecisionTreeClassifier()
dt.fit(X, y)
如何做...
- 使用 joblib 保存分类器所做的训练工作:
from sklearn.externals import joblib
joblib.dump(dt, "dtree.clf")
['dtree.clf']
打开已保存的模型
- 使用 joblib 加载模型。使用一组输入进行预测:
from sklearn.externals import joblib
pulled_model = joblib.load("dtree.clf")
y_pred = pulled_model.predict(X)
我们不需要重新训练模型,并且节省了大量训练时间。我们只是使用 joblib 重新加载了模型并进行了预测。
还有更多内容...
你也可以在 Python 2.x 中使用cPickle模块,或者在 Python 3.x 中使用pickle模块。就个人而言,我使用这个模块处理几种类型的 Python 类和对象:
- 首先导入
pickle:
import cPickle as pickle #Python 2.x
# import pickle # Python 3.x
- 使用
dump()模块方法。它有三个参数:要保存的数据、保存目标文件和 pickle 协议。以下代码将训练好的树保存到dtree.save文件:
f = open("dtree.save", 'wb')
pickle.dump(dt,f, protocol = pickle.HIGHEST_PROTOCOL)
f.close()
- 按如下方式打开
dtree.save文件:
f = open("dtree.save", 'rb')
return_tree = pickle.load(f)
f.close()
- 查看树:
return_tree
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
max_features=None, max_leaf_nodes=None,
min_impurity_split=1e-07, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
presort=False, random_state=None, splitter='best'
第八章:支持向量机
在本章中,我们将涵盖以下内容:
-
使用线性 SVM 进行数据分类
-
优化 SVM
-
使用 SVM 进行多类分类
-
支持向量回归
介绍
在本章中,我们将首先使用支持向量机(SVM)与线性核,以大致了解 SVM 的工作原理。它们创建一个超平面,或在多个维度中的线性面,最佳地分隔数据。
在二维空间中,这很容易看出:超平面是分隔数据的直线。我们将看到 SVM 的系数和截距数组。它们一起唯一地描述了一个scikit-learn线性 SVC 预测器。
在本章的其余部分,SVM 使用径向基函数(RBF)核。它们是非线性的,但具有平滑的分隔面。在实际应用中,SVM 在许多数据集上表现良好,因此是scikit-learn库的一个重要组成部分。
使用线性 SVM 进行数据分类
在第一章中,我们看到了一些使用 SVM 进行分类的示例。我们重点讨论了 SVM 在分类性能上略优于逻辑回归,但大部分时间我们并未深入探讨 SVM。
在这里,我们将更仔细地关注它们。虽然 SVM 没有容易的概率解释,但它们有一个直观的几何解释。线性 SVM 的主要思想是通过最佳的平面分隔两个类。
让我们使用 SVM 对两个类进行线性分隔。
准备工作
让我们从加载并可视化scikit-learn中提供的鸢尾花数据集开始:
加载数据
加载部分鸢尾花数据集。这将使我们能够与第一章进行轻松的比较:
#load the libraries we have been using
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt #Library for visualization
from sklearn import datasets
iris = datasets.load_iris()
X_w = iris.data[:, :2] #load the first two features of the iris data
y_w = iris.target #load the target of the iris data
现在,我们将使用 NumPy 掩码来关注前两个类:
#select only the first two classes for both the feature set and target set
#the first two classes of the iris dataset: Setosa (0), Versicolour (1)
X = X_w[y_w < 2]
y = y_w[y_w < 2]
可视化这两个类
使用 matplotlib 绘制0和1类。请记住,X_0[:,0]表示 NumPy 数组的第一列。
在以下代码中,X_0表示与目标y为0相对应的输入子集,而X_1是目标值为1的匹配子集:
X_0 = X[y == 0]
X_1 = X[y == 1]
#to visualize within IPython:
%matplotlib inline
plt.figure(figsize=(10,7)) #change figure-size for easier viewing
plt.scatter(X_0[:,0],X_0[:,1], color = 'red')
plt.scatter(X_1[:,0],X_1[:,1], color = 'blue')

从图表中可以清楚地看出,我们可以找到一条直线来分隔这两个类。
如何操作...
找到 SVM 直线的过程很简单。这与任何scikit-learn的监督学习估计器的过程相同:
-
创建训练集和测试集。
-
创建 SVM 模型实例。
-
将 SVM 模型拟合到加载的数据。
-
使用 SVM 模型进行预测,并在准备好对未见数据进行预测之前,衡量模型的性能。
让我们开始吧:
- 将前两个类的前两个特征的数据集进行划分。对目标集进行分层:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7,stratify=y)
- 创建 SVM 模型实例。将核设置为线性,因为我们希望有一条线来分隔这个例子中涉及的两个类:
from sklearn.svm import SVC
svm_inst = SVC(kernel='linear')
- 拟合模型(训练模型):
svm_inst.fit(X_train,y_train)
- 使用测试集进行预测:
y_pred = svm_inst.predict(X_test)
- 测量 SVM 在测试集上的表现:
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)
1.0
它在测试集上表现得非常好。这并不奇怪,因为当我们可视化每个类别时,它们很容易被视觉上分开。
- 通过在二维网格上使用估算器,来可视化决策边界,即分隔类别的直线:
from itertools import product
#Minima and maxima of both features
xmin, xmax = np.percentile(X[:, 0], [0, 100])
ymin, ymax = np.percentile(X[:, 1], [0, 100])
#Grid/Cartesian product with itertools.product
test_points = np.array([[xx, yy] for xx, yy in product(np.linspace(xmin, xmax), np.linspace(ymin, ymax))])
#Predictions on the grid
test_preds = svm_inst.predict(test_points)
- 通过为预测着色来绘制网格。请注意,我们已经修改了之前的可视化图像,加入了 SVM 的预测:
X_0 = X[y == 0]
X_1 = X[y == 1]
%matplotlib inline
plt.figure(figsize=(10,7)) #change figure-size for easier viewing
plt.scatter(X_0[:,0],X_0[:,1], color = 'red')
plt.scatter(X_1[:,0],X_1[:,1], color = 'blue')
colors = np.array(['r', 'b'])
plt.scatter(test_points[:, 0], test_points[:, 1], color=colors[test_preds], alpha=0.25)
plt.scatter(X[:, 0], X[:, 1], color=colors[y])
plt.title("Linearly-separated classes")

我们通过在二维网格上进行预测,详细描述了 SVM 线性决策边界。
它是如何工作的……
有时,在整个网格上进行预测计算可能非常昂贵,尤其是当 SVM 预测许多类别且维度较高时。在这种情况下,您将需要访问 SVM 决策边界的几何信息。
线性决策边界,一个超平面,是由一个垂直于超平面的向量和一个截距唯一确定的。法向量包含在 SVM 实例的coef_ data属性中。截距包含在 SVM 实例的intercept_ data属性中。查看这两个属性:
svm_inst.coef_
array([[ 2.22246001, -2.2213921 ]])
svm_inst.intercept_
array([-5.00384439])
您可能会很快发现,coef_[0] 向量垂直于我们绘制的分隔两个鸢尾花类别的直线。
每次,这两个 NumPy 数组 svm_inst.coef_ 和 svm_inst.intercept_ 的行数是相同的。每一行对应一个分隔相关类别的平面。在这个例子中,两个类别通过一个超平面线性分开。特定的 SVM 类型,SVC 在这种情况下实现了一个一对一分类器:它会绘制一个唯一的平面来分隔每一对类别。
如果我们尝试分离三个类别,那么有三种可能的组合,3 x 2/2 = 3。对于 n 个类别,SVC 提供的平面数如下:

coef_ data 属性中的列数是数据中特征的数量,在本例中是两个。
要找到关于空间中某一点的决策,求解以下方程为零:

如果您只关心平面的唯一性,可以存储元组(coef_, intercept_)。
还有更多……
此外,您还可以查看实例的参数以了解更多信息:
svm_inst
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape=None, degree=3, gamma='auto', kernel='linear',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
传统上,SVC 预测性能通过以下参数进行优化:C、gamma 和核的形状。C 描述了 SVM 的边距,默认设置为 1。边距是超平面两侧没有类别示例的空白区域。如果数据集有许多噪声观察值,可以尝试使用交叉验证来提高 C 的值。C 与边距上的错误成正比,随着 C 值的增大,SVM 将尝试使边距更小。
关于 SVM 的最后一点是,我们可以重新缩放数据,并通过交叉验证测试该缩放效果。方便的是,鸢尾花数据集中的所有输入单位都是厘米,所以不需要重新缩放,但对于任意数据集,你应该考虑这个问题。
优化 SVM
在本示例中,我们将继续使用鸢尾花数据集,但使用两种难以区分的品种——变色鸢尾和维吉尼卡鸢尾。
在本节中,我们将重点关注以下内容:
-
设置 scikit-learn 管道:一系列变换,最后是一个预测模型
-
网格搜索:对多个版本的支持向量机(SVM)进行性能扫描,并改变其参数
准备工作
加载鸢尾花数据集中的两个类别和两个特征:
#load the libraries we have been using
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import datasets
iris = datasets.load_iris()
X_w = iris.data[:, :2] #load the first two features of the iris data
y_w = iris.target #load the target of the iris data
X = X_w[y_w != 0]
y = y_w[y_w != 0]
X_1 = X[y == 1]
X_2 = X[y == 2]
如何操作...
- 首先将数据分为训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7,stratify=y)
构建一个管道
- 然后构建一个包含两个步骤的管道:一个缩放步骤和一个 SVM 步骤。在将数据传递给 SVM 之前,最好先进行缩放:
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
svm_est = Pipeline([('scaler',StandardScaler()),('svc',SVC())])
请注意,在管道中,缩放步骤的名称是scaler,SVM 的名称是svc。这些名称在接下来的步骤中将非常关键。还要注意,默认的 SVM 是 RBF SVM,它是非线性的。
为管道构建参数网格
- 以对数方式改变相关的 RBF 参数 C 和 gamma,每次改变一个数量级:
Cs = [0.001, 0.01, 0.1, 1, 10]
gammas = [0.001, 0.01, 0.1, 1, 10]
- 最后,将参数网格构建成字典。SVM 参数字典的键名以
svc__开头,取管道 SVM 的名称并加上两个下划线。然后是 SVM 估计器内的参数名称,C和gamma:
param_grid = dict(svc__gamma=gammas, svc__C=Cs)
提供交叉验证方案
- 以下是一个分层且经过洗牌的拆分。
n_splits参数指的是数据集将被拆分成的折叠数或尝试次数。test_size参数则指每个折叠中留出来用于测试的数据量。估计器将在每个折叠中使用测试集评分:
from sklearn.model_selection import StratifiedShuffleSplit
cv = StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=7)
分层洗牌的最重要元素是,每个折叠都保持每个类别样本的比例。
- 对于普通的交叉验证方案,将
cv设置为一个整数,表示折叠的数量:
cv = 10
执行网格搜索
网格搜索需要三个必需的元素:
-
估计器
-
参数网格
-
一个交叉验证方案
- 我们有这三项元素。设置网格搜索,并在训练集上运行:
from sklearn.model_selection import GridSearchCV
grid_cv = GridSearchCV(svm_est, param_grid=param_grid, cv=cv)
grid_cv.fit(X_train, y_train)
- 查找通过网格搜索找到的最佳参数:
grid_cv.best_params_
{'svc__C': 10, 'svc__gamma': 0.1}
- 查找与最佳估计器相关的最佳得分:
grid_cv.best_score_
0.71250000000000002
还有更多内容...
让我们从其他角度看一下 SVM 分类。
随机网格搜索替代方案
scikit-learn 的GridSearchCV会执行一个完整的扫描,以寻找估计器的最佳参数集。在此情况下,它会搜索由param_grid参数指定的 5 x 5 = 25(C,gamma)对。
另一种选择是使用RandomizedSearchCV,通过使用以下这一行代替GridSearchCV所用的那一行:
from sklearn.model_selection import RandomizedSearchCV
rand_grid = RandomizedSearchCV(svm_est, param_distributions=param_grid, cv=cv,n_iter=10)
rand_grid.fit(X_train, y_train)
它得到了相同的C和gamma:
rand_grid.best_params_
{'svc__C': 10, 'svc__gamma': 0.001}
可视化非线性 RBF 决策边界
使用类似于之前配方的代码可视化 RBF 决策边界。首先,创建一个网格并预测网格上每个点对应的类别:
from itertools import product
#Minima and maxima of both features
xmin, xmax = np.percentile(X[:, 0], [0, 100])
ymin, ymax = np.percentile(X[:, 1], [0, 100])
#Grid/Cartesian product with itertools.product
test_points = np.array([[xx, yy] for xx, yy in product(np.linspace(xmin, xmax), np.linspace(ymin, ymax))])
#Predictions on the grid
test_preds = grid_cv.predict(test_points)
现在可视化网格:
X_1 = X[y == 1]
X_2 = X[y == 2]
%matplotlib inline
plt.figure(figsize=(10,7)) #change figure-size for easier viewing
plt.scatter(X_2[:,0],X_2[:,1], color = 'red')
plt.scatter(X_1[:,0],X_1[:,1], color = 'blue')
colors = np.array(['r', 'b'])
plt.scatter(test_points[:, 0], test_points[:, 1], color=colors[test_preds-1], alpha=0.25)
plt.scatter(X[:, 0], X[:, 1], color=colors[y-1])
plt.title("RBF-separated classes")
请注意,在结果图中,RBF 曲线看起来相当直,但实际上它对应的是一条轻微的曲线。这是一个 gamma = 0.1 和 C = 0.001 的 SVM:

C 和 gamma 的更多含义
更直观地说,gamma 参数决定了单个样本对每单位距离的影响程度。如果 gamma 较低,则样本在较长距离处具有影响。如果 gamma 较高,则它们的影响仅限于较短的距离。SVM 在其实现中选择支持向量,gamma 与这些向量的影响半径成反比。
关于 C,较低的 C 会使决策面更加平滑,而较高的 C 会使 SVM 尝试正确分类所有样本,导致不太平滑的决策面。
使用 SVM 进行多类别分类
我们将扩展前面的配方,通过两个特征对所有鸢尾花类型进行分类。这不是二分类问题,而是多分类问题。这些步骤是在之前配方的基础上扩展的。
准备就绪
对于多分类问题,SVC 分类器(scikit 的 SVC)可以稍作修改。为此,我们将使用鸢尾数据集中的所有三个类别。
为每个类别加载两个特征:
#load the libraries we have been using
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data[:, :2] #load the first two features of the iris data
y = iris.target #load the target of the iris data
X_0 = X[y == 0]
X_1 = X[y == 1]
X_2 = X[y == 2]
将数据拆分为训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7,stratify=y)
如何操作...
OneVsRestClassifier
- 在管道中加载
OneVsRestClassifier:
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.multiclass import OneVsRestClassifier
svm_est = Pipeline([('scaler',StandardScaler()),('svc',OneVsRestClassifier(SVC()))])
- 设置参数网格:
Cs = [0.001, 0.01, 0.1, 1, 10]
gammas = [0.001, 0.01, 0.1, 1, 10]
- 构建参数网格。注意,表示
OneVsRestClassifierSVC 的语法非常特殊。当在管道中命名为svc时,字典中的参数键名以svc__estimator__开头:
param_grid = dict(svc__estimator__gamma=gammas, svc__estimator__C=Cs)
- 加载一个随机化的超参数搜索。拟合它:
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import StratifiedShuffleSplit
cv = StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=7)
rand_grid = RandomizedSearchCV(svm_est, param_distributions=param_grid, cv=cv,n_iter=10)
rand_grid.fit(X_train, y_train)
- 查找最佳参数:
rand_grid.best_params_
{'svc__estimator__C': 10, 'svc__estimator__gamma': 0.1}
可视化它
我们将通过调用训练好的 SVM 来预测二维网格中每个点的类别:
%matplotlib inline
from itertools import product
#Minima and maxima of both features
xmin, xmax = np.percentile(X[:, 0], [0, 100])
ymin, ymax = np.percentile(X[:, 1], [0, 100])
#Grid/Cartesian product with itertools.product
test_points = np.array([[xx, yy] for xx, yy in product(np.linspace(xmin, xmax,100), np.linspace(ymin, ymax,100))])
#Predictions on the grid
test_preds = rand_grid.predict(test_points)
plt.figure(figsize=(15,9)) #change figure-size for easier viewing
plt.scatter(X_0[:,0],X_0[:,1], color = 'green')
plt.scatter(X_1[:,0],X_1[:,1], color = 'blue')
plt.scatter(X_2[:,0],X_2[:,1], color = 'red')
colors = np.array(['g', 'b', 'r'])
plt.tight_layout()
plt.scatter(test_points[:, 0], test_points[:, 1], color=colors[test_preds], alpha=0.25)
plt.scatter(X[:, 0], X[:, 1], color=colors[y])

SVM 生成的边界通常是平滑曲线,这与我们将在接下来的章节中看到的基于树的边界非常不同。
如何操作...
OneVsRestClassifier 创建许多二元 SVC 分类器:每个类别与其他类别进行对比。在这种情况下,将计算三个决策边界,因为有三个类别。这种类型的分类器很容易理解,因为决策边界和面较少。
如果有 10 个类别,使用默认的 OneVsOneClassifier(SVC)会有 10 x 9/2 = 45 个边界。另一方面,使用 OneVsAllClassifier 会有 10 个边界。
支持向量回归
我们将利用 SVM 分类的配方,在 scikit-learn 的糖尿病数据集上执行支持向量回归。
准备就绪
加载糖尿病数据集:
#load the libraries we have been using
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import datasets
diabetes = datasets.load_diabetes()
X = diabetes.data
y = diabetes.target
将数据划分为训练集和测试集。此情况下回归问题没有分层:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)
如何操作...
- 在管道中创建一个
OneVsRestClassifier,并从sklearn.svm导入支持向量回归(SVR):
from sklearn.svm import SVR
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.multiclass import OneVsRestClassifier
svm_est = Pipeline([('scaler',StandardScaler()),('svc',OneVsRestClassifier(SVR()))])
- 创建一个参数网格:
Cs = [0.001, 0.01, 0.1, 1]
gammas = [0.001, 0.01, 0.1]
param_grid = dict(svc__estimator__gamma=gammas, svc__estimator__C=Cs)
- 执行随机搜索以寻找最佳超参数,C 和 gamma:
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import StratifiedShuffleSplit
rand_grid = RandomizedSearchCV(svm_est, param_distributions=param_grid, cv=5,n_iter=5,scoring='neg_mean_absolute_error')
rand_grid.fit(X_train, y_train)
- 查看最佳参数:
rand_grid.best_params_
{'svc__estimator__C': 10, 'svc__estimator__gamma': 0.1}
- 查看最佳分数:
rand_grid.best_score_
-58.059490084985839
分数似乎不是很好。尝试不同的算法和不同的评分设置,看看哪个表现最好。
第九章:树算法与集成方法
本章将涵盖以下内容:
-
使用决策树进行基本分类
-
使用 pydot 可视化决策树
-
调整决策树
-
使用决策树进行回归
-
使用交叉验证减少过拟合
-
实现随机森林回归
-
使用最近邻法进行包外回归
-
调整梯度提升树
-
调整 AdaBoost 回归器
-
使用 scikit-learn 编写堆叠聚合器
介绍
本章重点讨论决策树和集成算法。决策算法容易解释和可视化,因为它们是我们熟悉的决策过程的概述。集成方法可以部分解释和可视化,但它们包含许多部分(基础估计器),因此我们不能总是轻松地读取它们。
集成学习的目标是多个估计器比单个估计器表现更好。scikit-learn 中实现了两种集成方法:平均方法和提升方法。平均方法(如随机森林、包外法、额外树)通过平均多个估计器的预测来减少方差。提升方法(如梯度提升和 AdaBoost)通过依次构建基础估计器来减少偏差,从而减少整个集成方法的偏差。
许多集成方法的共同特点是使用随机性来构建预测器。例如,随机森林使用随机性(正如其名字所示),我们也将通过许多模型参数的搜索来利用随机性。本章中的随机性思路可以帮助你在工作中降低计算成本,并生成更高分的算法。
本章最后介绍了一个堆叠聚合器,它是一个可能非常不同模型的集成方法。堆叠中的数据分析部分是将多个机器学习算法的预测作为输入。
很多数据科学任务计算量很大。如果可能的话,使用多核计算机。在整个过程中,有一个名为 n_jobs 的参数设置为 -1,它会利用计算机的所有核心。
使用决策树进行基本分类
在这里,我们使用决策树进行基本分类。决策树用于分类时,是一系列决策,用于确定分类结果或类别结果。此外,决策树可以通过 SQL 由同一公司内的其他人员查看数据来进行检查。
准备工作
重新加载鸢尾花数据集并将数据分为训练集和测试集:
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data
y = iris.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y)
如何实现...
- 导入决策树分类器并在训练集上训练它:
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier() #Instantiate tree class
dtc.fit(X_train, y_train)
- 然后在测试集上测量准确度:
from sklearn.metrics import accuracy_score
y_pred = dtc.predict(X_test)
accuracy_score(y_test, y_pred)
0.91111111111111109
决策树看起来很准确。让我们进一步检查它。
使用 pydot 可视化决策树
如果您想生成图表,请安装 pydot 库。不幸的是,对于 Windows 来说,这个安装可能会比较复杂。如果您在安装 pydot 时遇到困难,请专注于查看图表,而不是重现它们。
如何操作...
- 在 IPython Notebook 中,执行多个导入并键入以下脚本:
import numpy as np
from sklearn import tree
from sklearn.externals.six import StringIO
import pydot
from IPython.display import Image
dot_iris = StringIO()
tree.export_graphviz(dtc, out_file = dot_iris, feature_names = iris.feature_names)
graph = pydot.graph_from_dot_data(dot_iris.getvalue())
Image(graph.create_png())

它是如何工作的...
这是通过训练生成的决策树;通过在 X_train 和 y_train 上调用 fit 方法来得到的。仔细查看它,从树的顶部开始。您在训练集中有 105 个样本。训练集被分成三组,每组 35 个:value = [35, 35, 35]。具体来说,这些是 35 个 Setosa、35 个 Versicolor 和 35 个 Virginica 花:

第一个决策是花瓣长度是否小于或等于 2.45。如果答案为真,或者是,花朵将被分类为第一类,value = [35, 0, 0]。该花被分类为 Setosa 类别。在鸢尾花数据集分类的多个示例中,这是最容易分类的一个。
否则,如果花瓣长度大于 2.45,第一个决策将导致一个较小的决策树。这个较小的决策树仅包含最后两类花:Versicolour 和 Virginica,value = [0, 35, 35]。
算法继续生成一个四层的完整树,深度为 4(注意,最上层节点不算在层数之内)。用正式语言来说,图中表现决策的三个节点被称为分裂。
还有更多...
你可能会想知道在决策树的可视化中,gini 参考是什么。Gini 指的是 gini 函数,它衡量分裂的质量,三个节点表示一个决策。当算法运行时,考虑了优化 gini 函数的几个分裂。选择产生最佳 gini 不纯度度量的分裂。
另一个选择是测量熵来确定如何分割树。您可以尝试这两种选择,并通过交叉验证确定哪种效果最好。按如下方式更改决策树中的标准:
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier(criterion='entropy')
dtc.fit(X_train, y_train)
这将生成如下决策树图:

您可以使用 GridSearchCV 检查此标准在交叉验证下的表现,并在参数网格中更改标准参数。我们将在下一节中进行此操作。
调整决策树
我们将继续深入探索鸢尾花数据集,重点关注前两个特征(花萼长度和花萼宽度),优化决策树并创建一些可视化图表。
准备工作
- 加载鸢尾花数据集,专注于前两个特征。并将数据分为训练集和测试集:
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data[:,:2]
y = iris.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y)
- 使用 pandas 查看数据:
import pandas as pd
pd.DataFrame(X,columns=iris.feature_names[:2])

- 在优化决策树之前,我们先试试一个默认参数的单一决策树。实例化并训练一个决策树:
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier() #Instantiate tree with default parameters
dtc.fit(X_train, y_train)
- 测量准确度分数:
from sklearn.metrics import accuracy_score
y_pred = dtc.predict(X_test)
accuracy_score(y_test, y_pred)
0.66666666666666663
使用graphviz可视化树,揭示了一棵非常复杂的树,包含许多节点和层级(这张图片仅供参考:如果你看不懂也没关系!它是一棵非常深的树,存在很多过拟合!):

这是过拟合的一个例子。决策树非常复杂。整个鸢尾数据集由 150 个样本组成,而一个非常复杂的树是不可取的。回想一下,在之前的章节中,我们使用了线性 SVM,它通过几条直线简单地划分空间。
在继续之前,使用 matplotlib 可视化训练数据点:
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=((12,6)))
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])
plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train)

如何实现...
- 为了优化决策树的性能,使用
GridSearchCV。首先实例化一个决策树:
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
- 然后,实例化并训练
GridSearchCV:
from sklearn.model_selection import GridSearchCV, cross_val_score
param_grid = {'criterion':['gini','entropy'], 'max_depth' : [3,5,7,20]}
gs_inst = GridSearchCV(dtc,param_grid=param_grid,cv=5)
gs_inst.fit(X_train, y_train)
请注意,在参数网格param_grid中,我们将分割评分标准在gini和entropy之间变化,并调整树的max_depth。
- 现在尝试在测试集上评分准确度:
from sklearn.metrics import accuracy_score
y_pred_gs = gs_inst.predict(X_test)
accuracy_score(y_test, y_pred_gs)
0.68888888888888888
准确度略有提高。让我们更详细地看看GridSearchCV。
- 查看网格搜索中尝试的所有决策树的分数:
gs_inst.grid_scores_
[mean: 0.78095, std: 0.09331, params: {'criterion': 'gini', 'max_depth': 3},
mean: 0.68571, std: 0.08832, params: {'criterion': 'gini', 'max_depth': 5},
mean: 0.70476, std: 0.08193, params: {'criterion': 'gini', 'max_depth': 7},
mean: 0.66667, std: 0.09035, params: {'criterion': 'gini', 'max_depth': 20},
mean: 0.78095, std: 0.09331, params: {'criterion': 'entropy', 'max_depth': 3},
mean: 0.69524, std: 0.11508, params: {'criterion': 'entropy', 'max_depth': 5},
mean: 0.72381, std: 0.09712, params: {'criterion': 'entropy', 'max_depth': 7},
mean: 0.67619, std: 0.09712, params: {'criterion': 'entropy', 'max_depth': 20}]
请注意,这种方法将在未来版本的 scikit-learn 中不可用。你可以使用zip(gs_inst.cv_results_['mean_test_score'],gs_inst.cv_results_['params'])来产生类似的结果。
从这个分数列表中,你可以看到较深的树表现得比浅层的树差。详细来说,训练集中的数据被分为五部分,训练发生在四部分中,而测试发生在五部分中的一部分。非常深的树会过拟合:它们在训练集上表现很好,但在交叉验证的五个测试集上表现很差。
- 使用
best_estimator_属性选择表现最好的树:
gs_inst.best_estimator_
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
max_features=None, max_leaf_nodes=None,
min_impurity_split=1e-07, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
presort=False, random_state=None, splitter='best')
- 使用
graphviz可视化树:
import numpy as np
from sklearn import tree
from sklearn.externals.six import StringIO
import pydot
from IPython.display import Image
dot_iris = StringIO()
tree.export_graphviz(gs_inst.best_estimator_, out_file = dot_iris, feature_names = iris.feature_names[:2])
graph = pydot.graph_from_dot_data(dot_iris.getvalue())
Image(graph.create_png())

还有更多...
- 为了获得更多的洞察,我们将创建一个额外的可视化。首先按如下方式创建一个 NumPy 网格:
grid_interval = 0.02
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
xmin, xmax = np.percentile(X[:, 0], [0, 100])
ymin, ymax = np.percentile(X[:, 1], [0, 100])
xmin_plot, xmax_plot = xmin - .5, xmax + .5
ymin_plot, ymax_plot = ymin - .5, ymax + .5
xx, yy = np.meshgrid(np.arange(xmin_plot, xmax_plot, grid_interval),
np.arange(ymin_plot, ymax_plot, grid_interval))
- 使用网格搜索中的
best_estimator_属性,预测刚刚创建的 NumPy 网格上的场景:
test_preds = gs_inst.best_estimator_.predict(np.array(zip(xx.ravel(), yy.ravel())))
- 看一下可视化结果:
import matplotlib.pyplot as plt
%matplotlib inline
X_0 = X[y == 0]
X_1 = X[y == 1]
X_2 = X[y == 2]
plt.figure(figsize=(15,8)) #change figure-size for easier viewing
plt.scatter(X_0[:,0],X_0[:,1], color = 'red')
plt.scatter(X_1[:,0],X_1[:,1], color = 'blue')
plt.scatter(X_2[:,0],X_2[:,1], color = 'green')
colors = np.array(['r', 'b','g'])
plt.scatter(xx.ravel(), yy.ravel(), color=colors[test_preds], alpha=0.15)
plt.scatter(X[:, 0], X[:, 1], color=colors[y])
plt.title("Decision Tree Visualization")
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])

- 使用这种类型的可视化,你可以看到决策树尝试构建矩形来分类鸢尾花的种类。每个分割都会创建一条与某个特征垂直的线。在下面的图中,有一条垂直线表示第一个决策,是否花萼长度大于(线的右边)或小于(线的左边)5.45。键入
plt.axvline(x = 5.45, color='black')与前面的代码一起,会得到如下结果:

- 可视化前三行:
plt.axvline(x = 5.45, color='black')
plt.axvline(x = 6.2, color='black')
plt.plot((xmin_plot, 5.45), (2.8, 2.8), color='black')

水平线 sepal_width = 2.8 比较短,并且结束于 x = 5.45,因为它不适用于 sepal_length >= 5.45 的情况。最终,多个矩形区域被创建。
- 下图展示了应用于过拟合的非常大的决策树的相同类型的可视化。决策树分类器试图将一个矩形框住鸢尾花数据集中许多特定样本,这显示了它在面对新样本时的泛化能力差:

- 最后,你还可以绘制最大深度如何影响交叉验证得分。编写一个网格搜索脚本,设置最大深度范围从 2 到 51:
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
from sklearn.model_selection import GridSearchCV, cross_val_score
max_depths = range(2,51)
param_grid = {'max_depth' : max_depths}
gs_inst = GridSearchCV(dtc, param_grid=param_grid,cv=5)
gs_inst.fit(X_train, y_train)
plt.plot(max_depths,gs_inst.cv_results_['mean_test_score'])
plt.xlabel('Max Depth')
plt.ylabel("Cross-validation Score")

该图从另一个角度展示了,较高的最大深度倾向于降低交叉验证得分。
使用决策树进行回归
回归树和分类树非常相似。开发回归模型的过程包括四个部分:
-
加载数据集
-
将数据集拆分为训练集/测试集
-
实例化一个决策树回归器并训练它
-
在测试子集上评分模型
准备工作
对于这个例子,加载 scikit-learn 的糖尿病数据集:
#Use within an Jupyter notebook
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
X = diabetes.data
y = diabetes.target
X_feature_names = ['age', 'gender', 'body mass index', 'average blood pressure','bl_0','bl_1','bl_2','bl_3','bl_4','bl_5']
现在我们已经加载了数据集,必须将数据分为训练集和测试集。在此之前,使用 pandas 可视化目标变量:
pd.Series(y).hist(bins=50)

这是一个回归示例,我们在拆分数据集时不能使用 stratify=y。相反,我们将对目标变量进行分箱:我们将记录目标变量是否小于 50,或在 50 到 100 之间,等等。
创建宽度为 50 的区间:
bins = 50*np.arange(8)
bins
array([ 0, 50, 100, 150, 200, 250, 300, 350])
使用 np.digitize 对目标变量进行分箱:
binned_y = np.digitize(y, bins)
使用 pandas 可视化 binned_y 变量:
pd.Series(binned_y).hist(bins=50)

NumPy 数组 binned_y 记录了每个 y 元素所属的区间。现在,将数据集拆分为训练集和测试集,并对 binned_y 数组进行分层:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,stratify=binned_y)
如何操作...
- 为了创建一个决策树回归器,实例化决策树并训练它:
from sklearn.tree import DecisionTreeRegressor
dtr = DecisionTreeRegressor()
dtr.fit(X_train, y_train)
- 为了衡量模型的准确性,使用测试集对目标变量进行预测:
y_pred = dtr.predict(X_test)
- 使用误差度量比较
y_test(真实值)和y_pred(模型预测)。这里使用mean_absolute_error,它是y_test和y_pred之间差异的绝对值的平均值:
from sklearn.metrics import mean_absolute_error
mean_absolute_error(y_test, y_pred)
58.49438202247191
- 作为替代方案,衡量平均绝对百分比误差,它是绝对值差异的平均值,差异被除以真实值元素的大小。这衡量了相对于真实值元素大小的误差幅度:
(np.abs(y_test - y_pred)/(y_test)).mean()
0.4665997687095611
因此,我们已经建立了关于糖尿病数据集的性能基准。模型的任何变化都可能影响误差度量。
还有更多...
- 使用 pandas,你可以快速可视化误差的分布。将真实值
y_test和预测值y_pred之间的差异转换为直方图:
pd.Series((y_test - y_pred)).hist(bins=50)

- 你也可以对百分比误差进行同样的操作:
pd.Series((y_test - y_pred)/(y_test)).hist(bins=50)

- 最后,使用前面部分的代码,查看决策树本身。注意,我们并未对最大深度进行优化:

这棵树非常复杂,很可能会发生过拟合。
使用交叉验证减少过拟合
在这里,我们将使用前面食谱中的糖尿病数据集进行交叉验证,以提高性能。首先加载数据集,如前面的食谱中所示:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
X = diabetes.data
y = diabetes.target
X_feature_names = ['age', 'gender', 'body mass index', 'average blood pressure','bl_0','bl_1','bl_2','bl_3','bl_4','bl_5']
bins = 50*np.arange(8)
binned_y = np.digitize(y, bins)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,stratify=binned_y)
如何操作...
- 使用网格搜索来减少过拟合。导入决策树并实例化它:
from sklearn.tree import DecisionTreeRegressor
dtr = DecisionTreeRegressor()
- 然后,导入
GridSearchCV并实例化该类:
from sklearn.model_selection import GridSearchCV
gs_inst = GridSearchCV(dtr, param_grid = {'max_depth': [3,5,7,9,20]},cv=10)
gs_inst.fit(X_train, y_train)
- 查看使用
best_estimator_属性的最佳估计器:
gs_inst.best_estimator_
DecisionTreeRegressor(criterion='mse', max_depth=3, max_features=None,
max_leaf_nodes=None, min_impurity_split=1e-07,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False, random_state=None,
splitter='best')
- 最佳估计器的
max_depth为3。现在检查误差指标:
y_pred = gs_inst.predict(X_test)
from sklearn.metrics import mean_absolute_error
mean_absolute_error(y_test, y_pred)
54.299263338774338
- 检查平均百分比误差:
(np.abs(y_test - y_pred)/(y_test)).mean()
0.4672742120960478
还有更多...
最后,使用graphviz可视化最佳回归树:
import numpy as np
from sklearn import tree
from sklearn.externals.six import StringIO
import pydot
from IPython.display import Image
dot_diabetes = StringIO()
tree.export_graphviz(gs_inst.best_estimator_, out_file = dot_diabetes, feature_names = X_feature_names)
graph = pydot.graph_from_dot_data(dot_diabetes.getvalue())
Image(graph.create_png())

该树具有更好的准确度指标,并且已经通过交叉验证以最小化过拟合。
实现随机森林回归
随机森林是一种集成算法。集成算法将多种算法结合使用以提高预测准确性。Scikit-learn 有几种集成算法,大多数都使用树来预测。让我们从扩展决策树回归开始,使用多棵决策树在随机森林中共同工作。
随机森林是由多个决策树组成的混合体,每棵树都为最终预测提供一个投票。最终的随机森林通过平均所有树的结果来计算最终输出。
准备就绪
像我们之前使用决策树一样加载糖尿病回归数据集。将所有数据分成训练集和测试集:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
X = diabetes.data
y = diabetes.target
X_feature_names = ['age', 'gender', 'body mass index', 'average blood pressure','bl_0','bl_1','bl_2','bl_3','bl_4','bl_5']
#bin target variable for better sampling
bins = 50*np.arange(8)
binned_y = np.digitize(y, bins)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,stratify=binned_y)
如何操作...
- 让我们深入了解并导入并实例化一个随机森林。训练这个随机森林:
from sklearn.ensemble import RandomForestRegressor
rft = RandomForestRegressor()
rft.fit(X_train, y_train)
- 测量预测误差。尝试在测试集上运行随机森林:
y_pred = rft.predict(X_test)
from sklearn.metrics import mean_absolute_error
mean_absolute_error(y_test, y_pred)
48.539325842696627
(np.abs(y_test - y_pred)/(y_test)).mean()
0.42821508503434541
与单棵决策树相比,误差略有下降。
- 要访问构成随机森林的任何一棵树,请使用
estimators_属性:
rft.estimators_
[DecisionTreeRegressor(criterion='mse', max_depth=None, max_features='auto',
max_leaf_nodes=None, min_impurity_split=1e-07,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False,
random_state=492413116, splitter='best')
...
- 要在
graphviz中查看列表中的第一棵树,请参考列表中的第一个元素rft.estimators_[0]:
import numpy as np
from sklearn import tree
from sklearn.externals.six import StringIO
import pydot
from IPython.display import Image
dot_diabetes = StringIO()
tree.export_graphviz(rft.estimators_[0], out_file = dot_diabetes, feature_names = X_feature_names)
graph = pydot.graph_from_dot_data(dot_diabetes.getvalue())
Image(graph.create_png())

-
要查看第二棵树,请使用
best_rft.estimators_[1]。要查看最后一棵树,请使用best_rft.estimators_[9],因为默认情况下,有 10 棵树,索引从 0 到 9,这些树构成了随机森林。 -
随机森林的一个附加特性是通过
feature_importances_属性来确定特征的重要性:
rft.feature_importances_
array([ 0.06103037, 0.00969354, 0.34865274, 0.09091215, 0.04331388,
0.04376602, 0.04827391, 0.02430837, 0.23251334, 0.09753567])
- 你也可以可视化特征的重要性:
fig, ax = plt.subplots(figsize=(10,5))
bar_rects = ax.bar(np.arange(10), rft.feature_importances_,color='r',align='center')
ax.xaxis.set_ticks(np.arange(10))
ax.set_xticklabels(X_feature_names, rotation='vertical')

最具影响力的特征是体重指数(BMI),其次是bl_4(六项血清测量中的第四项),然后是平均血压。
最近邻的 Bagging 回归
Bagging 是一种附加的集成方法,有趣的是,它不一定涉及决策树。它在第一个训练集的随机子集上构建多个基础估计器实例。在本节中,我们尝试将k-最近邻(KNN)作为基础估计器。
实际上,bagging 估计器对于减少复杂基础估计器(例如,具有多个层次的决策树)的方差非常有效。另一方面,boosting 通过减少弱模型的偏差(例如,层次较少的决策树或线性模型)来提升性能。
为了尝试 bagging,我们将使用 scikit-learn 的随机网格搜索来寻找最佳参数,即超参数搜索。像之前一样,我们将进行以下流程:
-
确定在算法中需要优化的参数(这些是研究人员在文献中认为最值得优化的参数)。
-
创建一个参数分布,其中最重要的参数会发生变化。
-
执行随机网格搜索。如果你使用的是集成方法,开始时保持较低的估计器数量。
-
使用上一阶段的最佳参数和多个估计器。
准备工作
再次加载上一节中使用的糖尿病数据集:
import numpy as np
import pandas as pd
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
X = diabetes.data
y = diabetes.target
X_feature_names = ['age', 'gender', 'body mass index', 'average blood pressure','bl_0','bl_1','bl_2','bl_3','bl_4','bl_5']
#bin target variable for better sampling
bins = 50*np.arange(8)
binned_y = np.digitize(y, bins)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,stratify=binned_y)
如何操作...
- 首先,导入
BaggingRegressor和KNeighborsRegressor。另外,还需要导入RandomizedSearchCV:
from sklearn.ensemble import BaggingRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import RandomizedSearchCV
-
然后,为网格搜索设置一个参数分布。对于 bagging 元估计器,一些要变化的参数包括
max_samples、max_features、oob_score和估计器数量n_estimators。估计器的数量初始设置为较低的 100,以便在尝试大量估计器之前优化其他参数。 -
此外,还有一个 KNN 算法的参数列表。它被命名为
base_estimator__n_neighbors,其中n_neighbors是 KNN 类中的内部名称。base_estimator是BaggingRegressor类中基础估计器的名称。base_estimator__n_neighbors列表包含数字3和5,它们表示最近邻算法中的邻居数量:
param_dist = {
'max_samples': [0.5,1.0],
'max_features' : [0.5,1.0],
'oob_score' : [True, False],
'base_estimator__n_neighbors': [3,5],
'n_estimators': [100]
}
- 实例化
KNeighboursRegressor类,并将其作为BaggingRegressor中的base_estimator:
single_estimator = KNeighborsRegressor()
ensemble_estimator = BaggingRegressor(base_estimator = single_estimator)
- 最后,实例化并运行一个随机搜索。进行几次迭代,
n_iter = 5,因为这可能会耗时较长:
pre_gs_inst_bag = RandomizedSearchCV(ensemble_estimator,
param_distributions = param_dist,
cv=3,
n_iter = 5,
n_jobs=-1)
pre_gs_inst_bag.fit(X_train, y_train)
- 查看随机搜索运行中的最佳参数:
pre_gs_inst_bag.best_params_
{'base_estimator__n_neighbors': 5,
'max_features': 1.0,
'max_samples': 0.5,
'n_estimators': 100,
'oob_score': True}
- 使用最佳参数训练
BaggingRegressor,除了n_estimators,你可以增加它。在这种情况下,我们将估计器的数量增加到 1,000:
rs_bag = BaggingRegressor(**{'max_features': 1.0,
'max_samples': 0.5,
'n_estimators': 1000,
'oob_score': True,
'base_estimator': KNeighborsRegressor(n_neighbors=5)})
rs_bag.fit(X_train, y_train)
- 最后,在测试集上评估算法的表现。虽然该算法的表现不如其他算法,但我们可以在后续的堆叠聚合器中使用它:
y_pred = rs_bag.predict(X_test)
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_test, y_pred)
print "MAE : ",mean_absolute_error(y_test, y_pred)
print "MAPE : ",(np.abs(y_test - y_pred)/y_test).mean()
R-squared 0.498096653258
MAE : 44.3642741573
MAPE : 0.419361955306
如果仔细观察,你会发现,在上一节中,袋装回归的表现稍微优于随机森林,因为无论是平均绝对误差还是平均绝对百分误差都更好。始终记住,你不必将集成学习仅限于树形结构——在这里,你可以使用 KNN 算法构建一个集成回归器。
调整梯度提升树
我们将使用梯度提升树来分析加利福尼亚住房数据集。我们整体的方法与之前相同:
-
关注梯度提升算法中的重要参数:
-
max_features -
max_depth -
min_samples_leaf -
learning_rate -
loss
-
-
创建一个参数分布,其中最重要的参数会有所变化。
-
执行随机网格搜索。如果使用集成方法,开始时保持估计器的数量较低。
-
使用上一阶段的最佳参数和更多估计器进行训练。
准备就绪
加载加利福尼亚住房数据集,并将加载的数据集分为训练集和测试集:
%matplotlib inline
from __future__ import division #Load within Python 2.7 for regular division
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
cali_housing = fetch_california_housing()
X = cali_housing.data
y = cali_housing.target
#bin output variable to split training and testing sets into two similar sets
bins = np.arange(6)
binned_y = np.digitize(y, bins)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,stratify=binned_y)
如何操作...
- 加载梯度提升算法和随机网格搜索:
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import RandomizedSearchCV
- 为梯度提升树创建一个参数分布:
param_dist = {'max_features' : ['log2',1.0],
'max_depth' : [3, 5, 7, 10],
'min_samples_leaf' : [2, 3, 5, 10],
'n_estimators': [50, 100],
'learning_rate' : [0.0001,0.001,0.01,0.05,0.1,0.3],
'loss' : ['ls','huber']
}
- 运行网格搜索以找到最佳参数。进行 30 次迭代的随机搜索:
pre_gs_inst = RandomizedSearchCV(GradientBoostingRegressor(warm_start=True),
param_distributions = param_dist,
cv=3,
n_iter = 30, n_jobs=-1)
pre_gs_inst.fit(X_train, y_train)
- 现在以数据框形式查看报告。查看报告的函数已经封装,可以多次使用:
import numpy as np
import pandas as pd
def get_grid_df(fitted_gs_estimator):
res_dict = fitted_gs_estimator.cv_results_
results_df = pd.DataFrame()
for key in res_dict.keys():
results_df[key] = res_dict[key]
return results_df
def group_report(results_df):
param_cols = [x for x in results_df.columns if 'param' in x and x is not 'params']
focus_cols = param_cols + ['mean_test_score']
print "Grid CV Report \n"
output_df = pd.DataFrame(columns = ['param_type','param_set',
'mean_score','mean_std'])
cc = 0
for param in param_cols:
for key,group in results_df.groupby(param):
output_df.loc[cc] = (param, key, group['mean_test_score'].mean(), group['mean_test_score'].std())
cc += 1
return output_df
- 查看展示梯度提升树在不同参数设置下表现的数据框:
results_df = get_grid_df(pre_gs_inst)
group_report(results_df)

从这个数据框来看;ls作为损失函数明显优于huber,3是最佳的min_samples_leaf(但4也可能表现不错),3是最佳的max_depth(尽管1或2也能有效),0.3作为学习率效果很好(0.2或0.4也可以),max_features为1.0效果良好,但也可以是其他数字(如特征的一半:0.5)。
- 有了这些信息,尝试另一次随机搜索:
param_dist = {'max_features' : ['sqrt',0.5,1.0],
'max_depth' : [2,3,4],
'min_samples_leaf' : [3, 4],
'n_estimators': [50, 100],
'learning_rate' : [0.2,0.25, 0.3, 0.4],
'loss' : ['ls','huber']
}
pre_gs_inst = RandomizedSearchCV(GradientBoostingRegressor(warm_start=True),
param_distributions = param_dist,
cv=3,
n_iter = 30, n_jobs=-1)
pre_gs_inst.fit(X_train, y_train)
- 查看生成的新报告:
results_df = get_grid_df(pre_gs_inst)
group_report(results_df)

- 有了这些信息,你可以使用以下参数分布再进行一次随机搜索:
param_dist = {'max_features' : [0.4, 0.5, 0.6],
'max_depth' : [5,6],
'min_samples_leaf' : [4,5],
'n_estimators': [300],
'learning_rate' : [0.3],
'loss' : ['ls','huber']
}
- 将结果存储在
rs_gbt中,并使用 4,000 个估计器最后一次进行训练:
rs_gbt = GradientBoostingRegressor(warm_start=True,
max_features = 0.5,
min_samples_leaf = 4,
learning_rate=0.3,
max_depth = 6,
n_estimators = 4000,loss = 'huber')
rs_gbt.fit(X_train, y_train)
- 使用 scikit-learn 的
metrics模块描述测试集上的误差:
y_pred = rs_gbt.predict(X_test)
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_test, y_pred)
print "MAE : ",mean_absolute_error(y_test, y_pred)
print "MAPE : ",(np.abs(y_test - y_pred)/y_test).mean()
R-squared 0.84490423214
MAE : 0.302125381378
MAPE : 0.169831775387
如果你还记得,随机森林的 R 平方值略低,为 0.8252。这种算法稍微好一些。对于两者,我们都进行了随机化搜索。请注意,如果你频繁地进行树的超参数优化,可以自动化多个随机化参数搜索。
还有更多内容...
现在,我们将优化一个梯度提升分类器,而不是回归器。过程非常相似。
寻找梯度提升分类器的最佳参数
使用梯度提升树进行分类与我们之前做的回归非常相似。我们将再次执行以下操作:
-
查找梯度提升分类器的最佳参数。这些参数与梯度提升回归器相同,不同之处在于损失函数选项有所不同。参数名称相同,具体如下:
-
max_features -
max_depth -
min_samples_leaf -
learning_rate -
loss
-
-
使用最佳参数运行估计器,但在估计器中使用更多的树。在下面的代码中,请注意损失函数(称为 deviance)的变化。为了进行分类,我们将使用一个二元变量。回顾目标集
y的可视化:
pd.Series(y).hist(bins=50)

在最右侧,似乎存在异常:分布中的许多值等于五。也许我们想将该数据集分开并单独分析。作为这个过程的一部分,我们可能希望能够预先确定一个点是否应该属于异常集。我们将构建一个分类器,将 y 大于或等于五的点分离出来:
- 首先,将数据集分割为训练集和测试集。对分箱变量
binned_y进行分层:
bins = np.arange(6)
binned_y = np.digitize(y, bins)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,stratify=binned_y)
- 创建一个二元变量,当目标变量
y大于或等于 5 时其值为1,小于 5 时为0。注意,如果二元变量为1,则表示它属于异常集:
y_binary = np.where(y >= 5, 1,0)
- 现在,使用
X_train的形状将二元变量分割为y_train_binned和y_test_binned:
train_shape = X_train.shape[0]
y_train_binned = y_binary[:train_shape]
y_test_binned = y_binary[train_shape:]
- 执行随机网格搜索:
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import RandomizedSearchCV
param_dist = {'max_features' : ['log2',0.5,1.0],
'max_depth' : [2,3,6],
'min_samples_leaf' : [1,2,3,10],
'n_estimators': [100],
'learning_rate' : [0.1,0.2,0.3,1],
'loss' : ['deviance']
}
pre_gs_inst = RandomizedSearchCV(GradientBoostingClassifier(warm_start=True),
param_distributions = param_dist,
cv=3,
n_iter = 10, n_jobs=-1)
pre_gs_inst.fit(X_train, y_train_binned)
- 查看最佳参数:
pre_gs_inst.best_params_
{'learning_rate': 0.2,
'loss': 'deviance',
'max_depth': 2,
'max_features': 1.0,
'min_samples_leaf': 2,
'n_estimators': 50}
- 增加估计器数量并训练最终的估计器:
gbc = GradientBoostingClassifier(**{'learning_rate': 0.2,
'loss': 'deviance',
'max_depth': 2,
'max_features': 1.0,
'min_samples_leaf': 2,
'n_estimators': 1000, 'warm_start':True}).fit(X_train, y_train_binned)
- 查看算法的性能:
y_pred = gbc.predict(X_test)
from sklearn.metrics import accuracy_score
accuracy_score(y_test_binned, y_pred)
0.93580426356589153
该算法是一个二分类器,准确率大约为 94%,能够判断房屋是否属于异常集。梯度提升分类器的超参数优化非常相似,具有与梯度提升回归相同的重要参数。
调整 AdaBoost 回归器
在 AdaBoost 回归器中,重要的调节参数是 learning_rate 和 loss。与之前的算法一样,我们将执行随机参数搜索,以找到该算法能达到的最佳分数。
如何操作...
- 导入算法和随机网格搜索。尝试一个随机的参数分布:
from sklearn.ensemble import AdaBoostRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {
'n_estimators': [50, 100],
'learning_rate' : [0.01,0.05,0.1,0.3,1],
'loss' : ['linear', 'square', 'exponential']
}
pre_gs_inst = RandomizedSearchCV(AdaBoostRegressor(),
param_distributions = param_dist,
cv=3,
n_iter = 10,
n_jobs=-1)
pre_gs_inst.fit(X_train, y_train)
- 查看最佳参数:
pre_gs_inst.best_params_
{'learning_rate': 0.05, 'loss': 'linear', 'n_estimators': 100}
- 这些表明进行另一次带有参数分布的随机搜索:
param_dist = {
'n_estimators': [100],
'learning_rate' : [0.04,0.045,0.05,0.055,0.06],
'loss' : ['linear']
}
- 复制包含最佳参数的字典。将副本中的估计器数量增加到 3,000:
import copy
ada_best = copy.deepcopy(pre_gs_inst.best_params_)
ada_best['n_estimators'] = 3000
- 训练最终的 AdaBoost 模型:
rs_ada = AdaBoostRegressor(**ada_best)
rs_ada.fit(X_train, y_train)
- 在测试集上衡量模型性能:
y_pred = rs_ada.predict(X_test)
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_test, y_pred)
print "MAE : ",mean_absolute_error(y_test, y_pred)
print "MAPE : ",(np.abs(y_test - y_pred)/y_test).mean()
R-squared 0.485619387823
MAE : 0.708716094846
MAPE : 0.524923208329
不幸的是,这个模型明显不如其他树模型表现得好。我们将暂时搁置它,不再进一步优化,因为这样做会增加更多的训练时间和 Python 开发时间。
还有更多...
我们已经找到了几个算法的最佳参数。下面是一个表格,总结了每个算法在交叉验证下需要优化的参数。建议您从优化这些参数开始:

使用 scikit-learn 编写堆叠聚合器
在本节中,我们将使用 scikit-learn 编写一个堆叠聚合器。堆叠聚合器将可能非常不同类型的模型进行混合。我们看到的许多集成算法混合的是同类型的模型,通常是决策树。
堆叠聚合器中的基本过程是,我们使用几个机器学习算法的预测作为训练另一个机器学习算法的输入。
更详细地说,我们使用一对 X 和 y 集合(X_1,y_1)训练两个或多个机器学习算法。然后我们在第二个 X 集(X_stack)上做出预测,得到 y_pred_1、y_pred_2 等。
这些预测,y_pred_1 和 y_pred_2,成为一个机器学习算法的输入,训练输出为 y_stack。最后,可以在第三个输入集 X_3 和真实标签集 y_3 上衡量错误。
在一个示例中会更容易理解。
如何操作...
- 再次从加利福尼亚住房数据集加载数据。观察我们如何再次创建分箱,以便对一个连续变量进行分层:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
cali_housing = fetch_california_housing()
X = cali_housing.data
y = cali_housing.target
bins = np.arange(6)
from __future__ import division
from sklearn.model_selection import train_test_split
binned_y = np.digitize(y, bins)
from sklearn.ensemble import RandomForestRegressor, AdaBoostRegressor, ExtraTreesRegressor, GradientBoostingRegressor
from sklearn.model_selection import GridSearchCV
- 现在通过使用
train_test_split两次,将一对X和y拆分为三个X和y对,输入和输出。注意我们在每个阶段如何对连续变量进行分层:
X_train_prin, X_test_prin, y_train_prin, y_test_prin = train_test_split(X, y,
test_size=0.2,
stratify=binned_y)
binned_y_train_prin = np.digitize(y_train_prin, bins)
X_1, X_stack, y_1, y_stack = train_test_split(X_train_prin,
y_train_prin,
test_size=0.33,
stratify=binned_y_train_prin )
- 使用
RandomizedSearchCV查找堆叠聚合器中第一个算法的最佳参数,在此例中是多个最近邻模型的袋装算法:
from sklearn.ensemble import BaggingRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {
'max_samples': [0.5,1.0],
'max_features' : [0.5,1.0],
'oob_score' : [True, False],
'base_estimator__n_neighbors': [3,5],
'n_estimators': [100]
}
single_estimator = KNeighborsRegressor()
ensemble_estimator = BaggingRegressor(base_estimator = single_estimator)
pre_gs_inst_bag = RandomizedSearchCV(ensemble_estimator,
param_distributions = param_dist,
cv=3,
n_iter = 5,
n_jobs=-1)
pre_gs_inst_bag.fit(X_1, y_1)
- 使用最佳参数,训练一个使用多个估算器的袋装回归器,在此例中为 3,000 个估算器:
rs_bag = BaggingRegressor(**{'max_features': 0.5,
'max_samples': 0.5,
'n_estimators': 3000,
'oob_score': False,
'base_estimator': KNeighborsRegressor(n_neighbors=3)})
rs_bag.fit(X_1, y_1)
- 对
X_1和y_1集合进行梯度提升算法的相同处理:
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {'max_features' : ['log2',0.4,0.5,0.6,1.0],
'max_depth' : [2,3, 4, 5,6, 7, 10],
'min_samples_leaf' : [1,2, 3, 4, 5, 10],
'n_estimators': [50, 100],
'learning_rate' : [0.01,0.05,0.1,0.25,0.275,0.3,0.325],
'loss' : ['ls','huber']
}
pre_gs_inst = RandomizedSearchCV(GradientBoostingRegressor(warm_start=True),
param_distributions = param_dist,
cv=3,
n_iter = 30, n_jobs=-1)
pre_gs_inst.fit(X_1, y_1)
- 使用更多估算器训练最佳参数集:
gbt_inst = GradientBoostingRegressor(**{'learning_rate': 0.05,
'loss': 'huber',
'max_depth': 10,
'max_features': 0.4,
'min_samples_leaf': 5,
'n_estimators': 3000,
'warm_start': True}).fit(X_1, y_1)
- 使用
X_stack和两个算法预测目标:
y_pred_bag = rs_bag.predict(X_stack)
y_pred_gbt = gbt_inst.predict(X_stack)
- 查看每个算法产生的指标(错误率)。查看袋装回归器的指标:
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_stack, y_pred_bag)
print "MAE : ",mean_absolute_error(y_stack, y_pred_bag)
print "MAPE : ",(np.abs(y_stack- y_pred_bag)/y_stack).mean()
R-squared 0.527045729567
MAE : 0.605868386902
MAPE : 0.397345752723
- 查看梯度提升的指标:
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_stack, y_pred_gbt)
print "MAE : ",mean_absolute_error(y_stack, y_pred_gbt)
print "MAPE : ",(np.abs(y_stack - y_pred_gbt)/y_stack).mean()
R-squared 0.841011059404
MAE : 0.297099247278
MAPE : 0.163956322255
- 创建一个包含两个算法预测的 DataFrame。或者,你也可以创建一个 NumPy 数组来存储数据:
y_pred_bag = rs_bag.predict(X_stack)
y_pred_gbt = gbt_inst.predict(X_stack)
preds_df = pd.DataFrame(columns = ['bag', 'gbt'])
preds_df['bag'] = y_pred_bag
preds_df['gbt'] = y_pred_gbt
- 查看新的预测数据框:
preds_df
- 查看预测列之间的相关性。这些列是相关的,但不是完全相关。理想的情况是算法之间没有完全相关,并且两个算法都表现良好。在这种情况下,袋装回归器的表现远不如梯度提升:
preds_df.corr()

- 现在使用第三个算法进行随机搜索。这个算法将前两个算法的预测作为输入。我们将使用额外的树回归器对其他两个算法的预测进行预测:
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {'max_features' : ['sqrt','log2',1.0],
'min_samples_leaf' : [1, 2, 3, 7, 11],
'n_estimators': [50, 100],
'oob_score': [True, False]}
pre_gs_inst = RandomizedSearchCV(ExtraTreesRegressor(warm_start=True,bootstrap=True),
param_distributions = param_dist,
cv=3,
n_iter = 15)
pre_gs_inst.fit(preds_df.values, y_stack)
- 复制参数字典,并在复制的字典中增加估算器的数量。如果你想查看最终的字典,可以查看:
import copy
param_dict = copy.deepcopy(pre_gs_inst.best_params_)
param_dict['n_estimators'] = 2000
param_dict['warm_start'] = True
param_dict['bootstrap'] = True
param_dict['n_jobs'] = -1
param_dict
{'bootstrap': True,
'max_features': 1.0,
'min_samples_leaf': 11,
'n_estimators': 2000,
'n_jobs': -1,
'oob_score': False,
'warm_start': True}
- 在预测数据框上训练额外的树回归器,使用
y_stack作为目标:
final_etr = ExtraTreesRegressor(**param_dict)
final_etr.fit(preds_df.values, y_stack)
- 为了检查堆叠聚合器的整体性能,你需要一个函数,该函数以
X集合为输入,通过袋装回归器和梯度提升创建一个数据框,并最终在这些预测上进行预测:
def handle_X_set(X_train_set):
y_pred_bag = rs_bag.predict(X_train_set)
y_pred_gbt = gbt_inst.predict(X_train_set)
preds_df = pd.DataFrame(columns = ['bag', 'gbt'])
preds_df['bag'] = y_pred_bag
preds_df['gbt'] = y_pred_gbt
return preds_df.values
def predict_from_X_set(X_train_set):
return final_etr.predict(handle_X_set(X_train_set))
- 使用
X_test_prin进行预测,这是我们刚刚创建的有用的predict_from_X_set函数,在留出的X集合上进行预测:
y_pred = predict_from_X_set(X_test_prin)
- 测量模型的性能:
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_test_prin, y_pred)
print "MAE : ",mean_absolute_error(y_test_prin, y_pred)
print "MAPE : ",(np.abs(y_test_prin- y_pred)/y_test_prin).mean()
R-squared 0.844114615094
MAE : 0.298422222752
MAPE : 0.173901911714
接下来怎么办?R 平方指标略有改善,我们为这一小小的提升付出了很多努力。接下来我们可以编写更健壮、更像生产环境的代码,为堆叠器添加许多不相关的估计器,使其更易于操作。
此外,我们可以进行特征工程——通过数学和/或加利福尼亚房地产行业的领域知识来改进数据列。你还可以尝试对不同的输入使用不同的算法。两个列:纬度和经度,非常适合随机森林,而其他输入可以通过线性算法来很好地建模。
第三,我们可以在数据集上探索不同的算法。对于这个数据集,我们专注于复杂的、高方差的算法。我们也可以尝试一些简单的高偏差算法。这些替代算法可能有助于我们最终使用的堆叠聚合器。
最后,关于堆叠器,你可以通过交叉验证旋转X_stacker集合,以最大化训练集的利用率。
第十章:使用 scikit-learn 进行文本和多类分类
本章将涵盖以下食谱:
-
使用 LDA 进行分类
-
使用 QDA(非线性 LDA)进行工作
-
使用 SGD 进行分类
-
使用朴素贝叶斯分类文档
-
半监督学习中的标签传播
使用 LDA 进行分类
线性判别分析(LDA)试图通过特征的线性组合来预测结果变量。LDA 通常用作预处理步骤。我们将在本例中演示这两种方法。
做好准备
在这个食谱中,我们将执行以下操作:
-
从 Google 获取股票数据。
-
将其重新排列成我们习惯的形式。
-
创建 LDA 对象以拟合和预测类别标签。
-
给出一个如何使用 LDA 进行降维的例子。
在开始第 1 步并从 Google 获取股票数据之前,安装一个支持最新股票读取器的 pandas 版本。可以在 Anaconda 命令行中输入以下命令:
conda install -c anaconda pandas-datareader
请注意,你的 pandas 版本将会更新。如果这成为问题,可以为当前的 pandas 版本创建一个新的环境。现在打开一个 notebook,检查pandas-datareader是否能够正确导入:
from `pandas-datareader` import data
如果导入正确,将不会显示任何错误。
如何操作...
在这个例子中,我们将执行类似于 Altman Z 分数的分析。在他的论文中,Altman 根据几个财务指标分析了一家公司在两年内违约的可能性。以下内容摘自 Altman Z 分数的维基百科页面:
| Z 分数公式 | 描述 |
|---|---|
| T1 = 营运资本 / 总资产 | 该指标衡量公司的流动资产与公司规模的关系。 |
| T2 = 留存收益 / 总资产 | 该指标衡量盈利能力,反映了公司的年龄和盈利能力。 |
| T3 = 息税前利润 / 总资产 | 该指标衡量税收和杠杆因素之外的运营效率。它认为运营盈利对公司长期生存至关重要。 |
| T4 = 股本市值 / 总负债账面价值 | 这增加了市场维度,可以揭示证券价格波动作为潜在的预警信号。 |
| T5 = 销售额 / 总资产 | 这是衡量总资产周转率的标准指标(不同产业之间差异很大)。 |
参考 Altman, Edward I.(1968 年 9 月)发表的文章《财务比率、判别分析与公司破产预测》,《金融学杂志》:189–209。
在本分析中,我们将通过 pandas 查看来自 Google 的财务数据。我们将尝试预测基于当前股票属性,六个月后的股价是否会更高。显然,这远不如 Altman 的 Z 分数那样精确。
- 首先进行几个导入,并存储你将使用的股票代码、数据的开始日期和结束日期:
%matplotlib inline
from pandas_datareader import data
import pandas as pd
tickers = ["F", "TM", "GM", "TSLA"]
first_date = '2009-01-01'
last_date = '2016-12-31'
- 现在,让我们获取股票数据:
stock_panel = data.DataReader(tickers, 'google', first_date, last_date)
- 这个数据结构是 pandas 中的一个面板。它类似于 在线分析处理 (OLAP) 立方体或 3D 数据框。让我们来看一下数据,更加熟悉一下收盘价,因为在比较时,我们关心的就是这些:
stock_df = stock_panel.Close.dropna()
stock_df.plot(figsize=(12, 5))
以下是输出结果:

好的,那么现在我们需要将每只股票的价格与它六个月后的价格进行比较。如果更高,我们将其编码为 1,否则为 0。
- 为此,我们将数据框向后移动 180 天并进行比较:
#this dataframe indicates if the stock was higher in 180 days
classes = (stock_df.shift(-180) > stock_df).astype(int)
- 接下来,我们需要做的是将数据集展开:
X = stock_panel.to_frame()
classes = classes.unstack()
classes = classes.swaplevel(0, 1).sort_index()
classes = classes.to_frame()
classes.index.names = ['Date', 'minor']
data = X.join(classes).dropna()
data.rename(columns={0: 'is_higher'}, inplace=True)
data.head()
以下是输出结果:

- 好的,现在我们需要在 NumPy 中创建矩阵。为此,我们将使用
patsy库。这是一个很棒的库,可以用来创建类似于 R 中的设计矩阵:
import patsy
X = patsy.dmatrix("Open + High + Low + Close + Volume + is_higher - 1", data.reset_index(),return_type='dataframe')
X.head()
以下是输出结果:

patsy 是一个非常强大的包;例如,假设我们想应用预处理。在 patsy 中,像 R 一样,我们可以修改公式,来对应设计矩阵中的修改。这里不会做,但如果我们想将值缩放到均值为 0,标准差为 1,函数将是 scale(open) + scale(high)。
- 现在我们已经有了数据集,让我们来拟合 LDA 对象:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
lda = LDA()
lda.fit(X.iloc[:, :-1], X.iloc[:, -1]);
- 我们可以看到,在预测数据集时表现得还不错。当然,我们还想通过其他参数来改进这个模型并进行测试:
from sklearn.metrics import classification_report
print classification_report(X.iloc[:, -1].values,
lda.predict(X.iloc[:, :-1]))
precision recall f1-score support
0.0 0.64 0.81 0.72 3432
1.0 0.64 0.42 0.51 2727
avg / total 0.64 0.64 0.62 6159
这些度量描述了模型以不同方式拟合数据的效果。
precision 和 recall 参数非常相似。从某些方面来看,正如以下列表所示,它们可以被看作是条件比例:
-
precision:给定模型预测的正值,实际正确的比例是多少?这也是为什么 precision 的另一个名称是 正预测值 (PPV) 的原因。 -
recall:在已知某一类别为真时,我们选择的比例是多少?我说选择是因为recall是搜索问题中常见的度量指标。例如,可能有一组与搜索词相关的网页——返回的比例。在第五章,线性模型 - 逻辑回归,你见过一个另一名称的 recall,叫做敏感度。
f1-score 参数试图总结 recall 和 precision 之间的关系。
它是如何工作的...
LDA 实际上与我们之前做的聚类非常相似。我们从数据中拟合了一个基本模型。然后,一旦我们拥有模型,就尝试预测并比较在每个类别中给定数据的似然性。我们选择那个更可能的选项。
LDA 实际上是二次判别分析(QDA)的一种简化,我们将在下一个实例中讨论它。这里我们假设每个类别的协方差是相同的,但在 QDA 中,这个假设被放宽。想一想 KNN 与高斯混合模型(GMM)之间的联系,以及这里和那里之间的关系。
使用 QDA —— 非线性 LDA
QDA 是一个常见技术的推广,如二次回归。它只是一个模型的推广,允许更多复杂的模型拟合,但像所有事物一样,当允许复杂性渗入时,我们也使自己的生活变得更加困难。
准备工作
我们将在上一个实例的基础上扩展,并通过 QDA 对象来看 QDA。
我们说过我们对模型的协方差做了假设。这里我们将放宽这个假设。
如何操作...
- QDA 恰好是
qda模块的成员。使用以下命令来使用 QDA:
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis as QDA
qda = QDA()
qda.fit(X.iloc[:, :-1], X.iloc[:, -1])
predictions = qda.predict(X.iloc[:, :-1])
predictions.sum()
2686.0
from sklearn.metrics import classification_report
print classification_report(X.iloc[:, -1].values, predictions)
precision recall f1-score support
0.0 0.65 0.66 0.65 3432
1.0 0.56 0.55 0.56 2727
avg / total 0.61 0.61 0.61 6159
正如你所见,整体上差不多。如果回顾使用 LDA 进行分类的实例,我们可以看到与 QDA 对象在类别零上的大变化,以及类别一上的细微差别。
正如我们在上一个实例中所讨论的,我们基本上是在这里比较似然。但我们如何比较似然呢?我们不妨使用手头的价格来尝试分类is_higher。
它是如何工作的...
我们假设收盘价服从对数正态分布。为了计算每个类别的似然,我们需要创建收盘价的子集,并为每个类别准备训练集和测试集。我们将使用内置的交叉验证方法:
from sklearn.model_selection import ShuffleSplit
import scipy.stats as sp
shuffle_split_inst = ShuffleSplit()
for test, train in shuffle_split_inst.split(X):
train_set = X.iloc[train]
train_close = train_set.Close
train_0 = train_close[~train_set.is_higher.astype(bool)]
train_1 = train_close[train_set.is_higher.astype(bool)]
test_set = X.iloc[test]
test_close = test_set.Close.values
ll_0 = sp.norm.pdf(test_close, train_0.mean())
ll_1 = sp.norm.pdf(test_close, train_1.mean())
现在我们已经有了两个类别的似然,可以进行比较并分配类别:
(ll_0 > ll_1).mean()
0.14486740032473389
使用 SGD 进行分类
随机梯度下降(SGD)是用于拟合回归模型的基本技术。SGD 在分类或回归中的应用有自然的联系。
准备工作
在回归分析中,我们最小化了一个惩罚错误选择的代价函数,它是在一个连续尺度上进行的,但对于分类问题,我们将最小化一个惩罚两个(或更多)情况的代价函数。
如何操作...
- 首先,让我们创建一些非常基本的数据:
from sklearn import datasets
X, y = datasets.make_classification(n_samples = 500)
- 将数据分割成训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y,stratify=y)
- 实例化并训练分类器:
from sklearn import linear_model
sgd_clf = linear_model.SGDClassifier()
#As usual, we'll fit the model:
sgd_clf.fit(X_train, y_train)
- 测量测试集上的性能:
from sklearn.metrics import accuracy_score
accuracy_score(y_test,sgd_clf.predict(X_test))
0.80000000000000004
还有更多...
我们可以设置class_weight参数来考虑数据集中不同程度的不平衡。
合页损失函数定义如下:

这里,t是真实分类,表示+1表示一个类别,-1表示另一个类别。系数向量由从模型中拟合的y表示,x是感兴趣的值。还有一个截距用于辅助计算。换句话说:


使用朴素贝叶斯分类文档
朴素贝叶斯是一个非常有趣的模型。它有点类似于 KNN,因为它做了一些假设,这些假设可能会过于简化现实,但在许多情况下仍然表现良好。
准备好
在这个实例中,我们将使用朴素贝叶斯进行文档分类,使用的是sklearn。我个人的一个例子是使用会计中的账户描述词汇,例如应付账款,来判断它属于损益表、现金流量表还是资产负债表。
基本思想是使用标注测试语料库中的单词频率来学习文档的分类。然后,我们可以将其应用于训练集,并尝试预测标签。
我们将使用sklearn中的newgroups数据集来玩转朴素贝叶斯模型。这是一个非平凡量的数据集,所以我们会直接获取它,而不是加载它。我们还将限制类别为rec.autos和rec.motorcycles:
import numpy as np
from sklearn.datasets import fetch_20newsgroups
categories = ["rec.autos", "rec.motorcycles"]
newgroups = fetch_20newsgroups(categories=categories)
#take a look
print "\n".join(newgroups.data[:1])
From: gregl@zimmer.CSUFresno.EDU (Greg Lewis)
Subject: Re: WARNING.....(please read)...
Keywords: BRICK, TRUCK, DANGER
Nntp-Posting-Host: zimmer.csufresno.edu
Organization: CSU Fresno
Lines: 33
...
newgroups.target_names
['rec.autos', 'rec.motorcycles']
现在我们有了新组,我们需要将每个文档表示为词袋模型。这种表示法也正是朴素贝叶斯名称的由来。该模型是“朴素”的,因为文档分类时并不考虑文档内部的词汇协方差。这可能被认为是一个缺陷,但事实证明,朴素贝叶斯在许多情况下都能合理有效地工作。
我们需要将数据预处理成词袋矩阵。这是一个稀疏矩阵,当文档中存在某个单词时,就会有对应的条目。这个矩阵可能会变得非常大,如下所示:
from sklearn.feature_extraction.text import CountVectorizer
count_vec = CountVectorizer()
bow = count_vec.fit_transform(newgroups.data)
这个矩阵是一个稀疏矩阵,它的维度是文档数量和每个单词的数量。矩阵中的文档和单词值是特定术语的频率:
bow
<1192x19177 sparse matrix of type '<type 'numpy.int64'>'
with 164296 stored elements in Compressed Sparse Row format>
实际上,我们需要将矩阵转换为稠密数组,以便用于朴素贝叶斯对象。所以,我们需要将其转换回来:
bow = np.array(bow.todense())
显然,大多数条目都是零,但我们可能想要重新构建文档计数以进行合理性检查:
words = np.array(count_vec.get_feature_names())
words[bow[0] > 0][:5]
array([u'10pm', u'1qh336innfl5', u'33', u'93740',
u'___________________________________________________________________'],
dtype='<U79')
现在,这些是第一个文档中的示例吗?我们可以使用以下命令来检查:
'10pm' in newgroups.data[0].lower()
True
'1qh336innfl5' in newgroups.data[0].lower()
True
如何操作...
好吧,准备数据比平常多花了一些时间,但我们处理的是文本数据,这些数据不像我们平常处理的矩阵数据那样可以迅速表示。
- 然而,现在我们准备好了,就可以启动分类器并拟合我们的模型:
from sklearn import naive_bayes
clf = naive_bayes.GaussianNB().fit(X_train, y_train)
- 将
bow和newgroups.target分别重命名为X和y。在拟合模型之前,我们先将数据集拆分为训练集和测试集:
X = bow
y = newgroups.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.5,stratify=y)
- 现在我们在测试集上拟合了一个模型,并试图预测训练集,以确定哪些类别与哪些文章相对应,让我们来看看大致的准确性:
from sklearn.metrics import accuracy_score
accuracy_score(y_test,clf.predict(X_test) )
0.94630872483221473
它是如何工作的...
朴素贝叶斯的基本思想是,我们可以根据特征向量估算某个数据点属于某个类别的概率。
通过贝叶斯公式可以重新排列,得到最大后验(MAP)估计特征向量。这个 MAP 估计选择了特征向量的概率最大化的类别。
还有更多...
我们也可以将朴素贝叶斯扩展到多类工作。我们不再假设高斯似然,而是使用多项式似然。
首先,让我们获取第三类数据:
from sklearn.datasets import fetch_20newsgroups
mn_categories = ["rec.autos", "rec.motorcycles", "talk.politics.guns"]
mn_newgroups = fetch_20newsgroups(categories=mn_categories)
我们需要像处理分类问题一样对其进行向量化:
mn_bow = count_vec.fit_transform(mn_newgroups.data)
mn_bow = np.array(mn_bow.todense())
将mn_bow和mn_newgroups.target分别重命名为X和y。让我们创建一个训练集和一个测试集,并使用训练数据训练一个多项式贝叶斯模型:
X = mn_bow
y = mn_newgroups.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.5,stratify=y)
from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB().fit(X_train, y_train)
测量模型准确度:
from sklearn.metrics import accuracy_score
accuracy_score(y_test,clf.predict(X_test) )
0.96317606444188719
这并不完全令人惊讶,我们表现得相当不错。我们在双类情况下表现得相当好,而且由于有人猜测talk.politics.guns类别与其他两个类别相对独立,因此我们应该表现得相当好。
半监督学习中的标签传播
标签传播是一种半监督技术,利用标记数据和未标记数据来学习未标记数据。通常,受益于分类算法的数据很难标记。例如,标记数据可能非常昂贵,因此只有一部分数据进行人工标记才是具有成本效益的。尽管如此,似乎有慢慢增长的趋势支持公司雇佣分类学家。
准备工作
另一个问题领域是审查数据。你可以想象一个情况,时间的前沿将影响你收集标记数据的能力。例如,假设你对患者进行了测量并给他们服用了实验药物。在某些情况下,如果药物反应足够迅速,你可以测量药物的效果,但你可能还想预测反应较慢的药物的效果。药物可能对某些患者引起致命反应,可能需要采取挽救生命的措施。
怎么做...
- 为了表示半监督或审查数据,我们需要进行一些数据预处理。首先,我们将通过一个简单的例子进行演示,然后再处理一些更复杂的情况:
from sklearn import datasets
d = datasets.load_iris()
- 由于我们将对数据进行修改,所以让我们创建副本并在目标名称的副本中添加一个未标记成员。这样以后更容易识别数据:
X = d.data.copy()
y = d.target.copy()
names = d.target_names.copy()
names = np.append(names, ['unlabeled'])
names
array(['setosa', 'versicolor', 'virginica', 'unlabeled'],
dtype='|S10')
- 现在,让我们用
-1更新y。这表示未标记的情况。这也是我们在名称末尾添加未标记的原因:
y[np.random.choice([True, False], len(y))] = -1
- 我们的数据现在有一堆
-1与实际数据交错在一起:
y[:10]
array([ 0, -1, -1, 0, 0, 0, 0, -1, 0, -1])
names[y[:10]]
array(['setosa', 'unlabeled', 'unlabeled', 'setosa', 'setosa', 'setosa',
'setosa', 'unlabeled', 'setosa', 'unlabeled'],
dtype='|S10')
- 我们显然有很多未标记的数据,现在的目标是使用
LabelPropagation方法来预测标签:
from sklearn import semi_supervised
lp = semi_supervised.LabelPropagation()
lp.fit(X, y)
LabelPropagation(alpha=1, gamma=20, kernel='rbf', max_iter=30, n_jobs=1,
n_neighbors=7, tol=0.001)
- 测量准确度评分:
preds = lp.predict(X)
(preds == d.target).mean()
0.97333333333333338
还不错,尽管我们使用了所有数据,这有点像作弊。另外,鸢尾花数据集是一个相对分离的数据集。
使用整个数据集让人联想到更传统的统计方法。选择不在测试集上进行测量减少了我们对预测的关注,鼓励我们更多地理解和解释整个数据集。如前所述,理解与黑箱预测的区别在于传统统计学和机器学习。
顺便说一下,让我们来看一下 LabelSpreading,它是 LabelPropagation 的姊妹类。我们将在本节 如何工作... 中对 LabelPropagation 和 LabelSpreading 做出技术性区分,但它们非常相似:
ls = semi_supervised.LabelSpreading()
LabelSpreading 比 LabelPropagation 更加鲁棒和抗噪声,正如它的工作方式所观察到的那样:
ls.fit(X, y)
LabelSpreading(alpha=0.2, gamma=20, kernel='rbf', max_iter=30, n_jobs=1,
n_neighbors=7, tol=0.001)
测量准确率得分:
(ls.predict(X) == d.target).mean()
0.96666666666666667
不要将标签传播算法遗漏的一个案例看作是它表现较差的标志。关键是我们可能赋予它一定的能力,使其在训练集上进行良好的预测,并能适用于更广泛的情况。
它是如何工作的……
标签传播通过创建一个数据点的图来工作,边缘上根据以下公式设置权重:

该算法通过标记的数据点将其标签传播到未标记的数据点。这个传播过程在一定程度上由边缘权重决定。
边缘权重可以放置在一个转移概率矩阵中。我们可以通过迭代的方式来确定实际标签的良好估计值。
第十一章:神经网络
本章我们将涵盖以下配方:
-
感知机分类器
-
神经网络 – 多层感知机
-
与神经网络堆叠
介绍
最近,神经网络和深度学习非常流行,因为它们解决了很多难题,并且可能已经成为人工智能公众面貌的重要组成部分。让我们探索 scikit-learn 中可用的前馈神经网络。
感知机分类器
使用 scikit-learn,你可以探索感知机分类器,并将其与 scikit-learn 中的其他分类方法进行比较。此外,感知机是神经网络的构建模块,神经网络是机器学习中的一个重要部分,特别是在计算机视觉领域。
准备开始
让我们开始吧。过程如下:
-
加载 UCI 糖尿病分类数据集。
-
将数据集划分为训练集和测试集。
-
导入感知机。
-
实例化感知机。
-
然后训练感知机。
-
尝试在测试集上使用感知机,或者更好地计算
cross_val_score。
加载 UCI 糖尿病数据集:
import numpy as np
import pandas as pd
data_web_address = "https://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data"
column_names = ['pregnancy_x',
'plasma_con',
'blood_pressure',
'skin_mm',
'insulin',
'bmi',
'pedigree_func',
'age',
'target']
feature_names = column_names[:-1]
all_data = pd.read_csv(data_web_address , names=column_names)
X = all_data[feature_names]
y = all_data['target']
你已经加载了X,输入特征集,以及y,我们希望预测的变量。将X和y划分为测试集和训练集。通过分层目标集来完成这一步,确保在训练集和测试集中类的比例平衡:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,stratify=y)
如何实现……
- 对特征集进行缩放。仅在训练集上执行缩放操作,然后继续进行测试集:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
- 实例化并在训练集上训练感知机:
from sklearn.linear_model import Perceptron
pr = Perceptron()
pr.fit(X_train_scaled, y_train)
Perceptron(alpha=0.0001, class_weight=None, eta0=1.0, fit_intercept=True,
n_iter=5, n_jobs=1, penalty=None, random_state=0, shuffle=True,
verbose=0, warm_start=False)
- 测量交叉验证得分。将
roc_auc作为交叉验证评分机制。此外,通过设置cv=skf,使用分层 K 折交叉验证:
from sklearn.model_selection import cross_val_score, StratifiedKFold
skf = StratifiedKFold(n_splits=3)
cross_val_score(pr, X_train_scaled, y_train, cv=skf,scoring='roc_auc').mean()
0.76832628835771022
- 在测试集上评估性能。导入
sklearn.metrics模块中的两个指标,accuracy_score和roc_auc_score:
from sklearn.metrics import accuracy_score, roc_auc_score
print "Classification accuracy : ", accuracy_score(y_test, pr.predict(X_test_scaled))
print "ROC-AUC Score : ",roc_auc_score(y_test, pr.predict(X_test_scaled))
Classification accuracy : 0.681818181818
ROC-AUC Score : 0.682592592593
测试很快就完成了。结果表现还行,比逻辑回归稍差,逻辑回归的准确率为 75%(这是一个估计值;我们不能将本章的逻辑回归与任何之前章节中的逻辑回归做比较,因为训练集和测试集的划分不同)。
它是如何工作的……
感知机是大脑中神经元的简化模型。在下面的图示中,感知机从左边接收输入 x[1] 和 x[2]。计算偏置项 w[0] 和权重 w[1]、w[2]。x[i] 和 w[i] 组成一个线性函数。然后将这个线性函数传递给激活函数。
在以下激活函数中,如果权重与输入向量的点积之和小于零,则将单独的行分类为 0;否则分类为 1:

这发生在单次迭代或遍历感知机时。该过程会在多个迭代中重复,每次都会重新调整权重,从而最小化损失函数。
关于感知器和当前神经网络的状态,它们表现良好,因为研究人员已经尝试了许多方法。实际上,基于目前的计算能力,它们的表现非常好。
随着计算能力的不断提升,神经网络和感知器能够解决越来越复杂的问题,且训练时间持续缩短。
还有更多内容...
尝试通过调整感知器的超参数来运行网格搜索。几个显著的参数包括正则化参数penalty和alpha、class_weight以及max_iter。class_weight参数通过赋予代表性不足的类别更多的权重,能够很好地处理类别不平衡问题。max_iter参数表示感知器的最大迭代次数。一般来说,其值越大越好,因此我们将其设置为 50。(请注意,这是针对 scikit-learn 0.19.0 的代码。在 scikit-learn 0.18.1 版本中,请使用n_iter参数代替max_iter参数。)
尝试以下网格搜索:
from sklearn.model_selection import GridSearchCV
param_dist = {'alpha': [0.1,0.01,0.001,0.0001],
'penalty': [None, 'l2','l1','elasticnet'],
'random_state': [7],
'class_weight':['balanced',None],'eta0': [0.25,0.5,0.75,1.0],
'warm_start':[True,False], 'max_iter':[50], 'tol':[1e-3]}
gs_perceptron = GridSearchCV(pr, param_dist, scoring='roc_auc',cv=skf).fit(X_train_scaled, y_train)
查看最佳参数和最佳得分:
gs_perceptron.best_params_
{'alpha': 0.001,
'class_weight': None,
'eta0': 0.5,
'max_iter': 50,
'penalty': 'l2',
'random_state': 7,
'tol': 0.001,
'warm_start': True}
gs_perceptron.best_score_
0.79221656570311072
使用交叉验证调整超参数已改善结果。现在尝试使用一组感知器进行集成学习,如下所示。首先注意并选择网格搜索中表现最好的感知器:
best_perceptron = gs_perceptron.best_estimator_
执行网格搜索:
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import BaggingClassifier
param_dist = {
'max_samples': [0.5,1.0],
'max_features' : [0.5,1.0],
'oob_score' : [True, False],
'n_estimators': [100],
'n_jobs':[-1],
'base_estimator__alpha': [0.001,0.002],
'base_estimator__penalty': [None, 'l2','l1','elasticnet'], }
ensemble_estimator = BaggingClassifier(base_estimator = best_perceptron)
bag_perceptrons = GridSearchCV(ensemble_estimator, param_dist,scoring='roc_auc',cv=skf,n_jobs=-1).fit(X_train_scaled, y_train)
查看新的交叉验证得分和最佳参数:
bag_perceptrons.best_score_
0.83299842529587864
bag_perceptrons.best_params_
{'base_estimator__alpha': 0.001,
'base_estimator__penalty': 'l1',
'max_features': 1.0,
'max_samples': 1.0,
'n_estimators': 100,
'n_jobs': -1,
'oob_score': True}
因此,对于这个数据集,一组感知器的表现优于单一感知器。
神经网络 – 多层感知器
在 scikit-learn 中使用神经网络非常简单,步骤如下:
-
加载数据。
-
使用标准缩放器对数据进行缩放。
-
进行超参数搜索。首先调整 alpha 参数。
准备就绪
加载我们在第九章中使用的中等规模的加州住房数据集,决策树算法与集成方法:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
cali_housing = fetch_california_housing()
X = cali_housing.data
y = cali_housing.target
将目标变量进行分箱,使得目标训练集和目标测试集更为相似。然后使用分层的训练/测试拆分:
bins = np.arange(6)
binned_y = np.digitize(y, bins)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,stratify=binned_y)
如何做...
- 首先对输入变量进行缩放。仅在训练数据上训练缩放器:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
- 然后,在测试集上执行缩放:
X_test_scaled = scaler.transform(X_test)
- 最后,执行随机搜索(或如果你喜欢,也可以进行网格搜索)来找到
alpha的合理值,确保其得分较高:
from sklearn.model_selection import RandomizedSearchCV
from sklearn.neural_network import MLPRegressor
param_grid = {'alpha': [10,1,0.1,0.01],
'hidden_layer_sizes' : [(50,50,50),(50,50,50,50,50)],
'activation': ['relu','logistic'],
'solver' : ['adam']
}
pre_gs_inst = RandomizedSearchCV(MLPRegressor(random_state=7),
param_distributions = param_grid,
cv=3,
n_iter=15,
random_state=7)
pre_gs_inst.fit(X_train_scaled, y_train)
pre_gs_inst.best_score_
0.7729679848718175
pre_gs_inst.best_params_
{'activation': 'relu',
'alpha': 0.01,
'hidden_layer_sizes': (50, 50, 50),
'solver': 'adam'}
它是如何工作的...
在神经网络的背景下,单一感知器的结构如下所示:

输出是权重和输入的点积之和的函数。该函数f是激活函数,可以是 sigmoid 曲线。例如,在神经网络中,超参数激活指的就是这个函数。在 scikit-learn 中,有 identity、logistic、tanh 和 relu 的选项,其中 logistic 即为 sigmoid 曲线。
整个网络是这样的(以下是来自 scikit 文档的图示,链接:scikit-learn.org/stable/modules/neural_networks_supervised.html):

使用我们熟悉的数据集——加利福尼亚住房数据集来训练神经网络是很有教育意义的。加利福尼亚住房数据集似乎更适合非线性算法,特别是树算法和树的集成。树算法在这个数据集上表现得很好,并为算法在该数据集上的表现建立了基准。
最终,神经网络表现还不错,但远不如梯度提升机好。此外,它们在计算上非常昂贵。
关于神经网络的哲学思考
神经网络是数学上通用的函数逼近器,可以学习任何函数。此外,隐藏层通常被解释为网络学习过程的中间步骤,而无需人工编写这些中间步骤。这可以来自计算机视觉中的卷积神经网络,在那里很容易看到神经网络如何推断出每一层。
这些事实构成了有趣的心智图像,并且可以应用于其他估计器。许多人往往不会把随机森林看作是树在逐步推理的过程,或者说是树与树之间的推理(也许是因为它们的结构不如有组织,而随机森林不会让人联想到生物大脑的可视化)。在更实际的细节上,如果你想组织随机森林,你可以限制它们的深度,或者使用梯度提升机。
无论神经网络是否真正智能这一事实如何,随着领域的进展和机器变得越来越聪明,携带这样的心智图像是有帮助的。携带这个想法,但要专注于结果;这就是现在机器学习的意义。
使用神经网络进行堆叠
两种最常见的元学习方法是袋装法和提升法。堆叠法使用得较少;然而,它非常强大,因为可以将不同类型的模型结合起来。这三种方法都通过一组较弱的估计器创建了一个更强的估计器。我们在第九章,树算法与集成方法中尝试了堆叠过程。在这里,我们尝试将神经网络与其他模型结合。
堆叠的过程如下:
-
将数据集划分为训练集和测试集。
-
将训练集划分为两部分。
-
在训练集的第一部分上训练基础学习器。
-
使用基础学习器对训练集第二部分进行预测。存储这些预测向量。
-
将存储的预测向量作为输入,目标变量作为输出。训练一个更高层次的学习器(注意我们仍然处于训练集的第二部分)。
之后,你可以查看在测试集上整体过程的结果(注意,不能通过查看测试集上的结果来选择模型)。
准备就绪
导入加利福尼亚州住房数据集以及我们一直使用的库:numpy、pandas和matplotlib。这是一个中等大小的数据集,但相对于其他 scikit-learn 数据集来说,它还是比较大的:
from __future__ import division
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
#From within an ipython notebook
%matplotlib inline
cali_housing = fetch_california_housing()
X = cali_housing.data
y = cali_housing.target
将目标变量进行分箱,以提高数据集在目标变量上的平衡性:
bins = np.arange(6)
binned_y = np.digitize(y, bins)
将数据集X和y划分为三个集合。X_1和X_stack分别表示第一个和第二个训练集的输入变量,y_1和y_stack分别表示第一个和第二个训练集的输出目标变量。测试集由X_test_prin和y_test_prin组成:
from sklearn.model_selection import train_test_split
X_train_prin, X_test_prin, y_train_prin, y_test_prin = train_test_split(X, y,test_size=0.2,stratify=binned_y,random_state=7)
binned_y_train_prin = np.digitize(y_train_prin, bins)
X_1, X_stack, y_1, y_stack = train_test_split(X_train_prin,y_train_prin,test_size=0.33,stratify=binned_y_train_prin,random_state=7 )
另一个选择是使用来自 scikit-learn 的model_selection模块中的StratifiedShuffleSplit。
如何实现...
我们将使用三个基础回归器:一个神经网络,一个单一的梯度提升机,和一个梯度提升机的袋装集成。
第一个基础模型 - 神经网络
- 通过对第一个训练集进行交叉验证的网格搜索,添加一个神经网络:
X_1为输入,y_1为目标集合。这将找到该数据集的最佳神经网络参数。在此示例中,我们仅调整alpha参数。不要忘记对输入进行标准化,否则网络将无法很好地运行:
from sklearn.model_selection import GridSearchCV
from sklearn.neural_network import MLPRegressor
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
mlp_pipe = Pipeline(steps=[('scale', StandardScaler()), ('neural_net', MLPRegressor())])
param_grid = {'neural_net__alpha': [0.02,0.01,0.005],
'neural_net__hidden_layer_sizes' : [(50,50,50)],
'neural_net__activation': ['relu'],
'neural_net__solver' : ['adam']
}
neural_net_gs = GridSearchCV(mlp_pipe, param_grid = param_grid,cv=3, n_jobs=-1)
neural_net_gs.fit(X_1, y_1)
- 查看网格搜索的最佳参数和最佳得分:
neural_net_gs.best_params_
{'neural_net__activation': 'relu',
'neural_net__alpha': 0.01,
'neural_net__hidden_layer_sizes': (50, 50, 50),
'neural_net__solver': 'adam'}
neural_net_gs.best_score_
0.77763106799320014
- 持久化在网格搜索中表现最好的神经网络。这将保存我们已经完成的训练,以免我们不得不反复进行训练:
nn_best = neural_net_gs.best_estimator_
import pickle
f = open('nn_best.save', 'wb')
pickle.dump(nn_best, f, protocol = pickle.HIGHEST_PROTOCOL)
f.close()
第二个基础模型 - 梯度提升集成
- 对梯度增强树进行随机网格搜索:
from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import GradientBoostingRegressor
param_grid = {'learning_rate': [0.1,0.05,0.03,0.01],
'loss': ['huber'],
'max_depth': [5,7,10],
'max_features': [0.4,0.6,0.8,1.0],
'min_samples_leaf': [2,3,5],
'n_estimators': [100],
'warm_start': [True], 'random_state':[7]
}
boost_gs = RandomizedSearchCV(GradientBoostingRegressor(), param_distributions = param_grid,cv=3, n_jobs=-1,n_iter=25)
boost_gs.fit(X_1, y_1)
- 查看最佳得分和参数:
boost_gs.best_score_
0.82767651150013244
boost_gs.best_params_
{'learning_rate': 0.1, 'loss': 'huber', 'max_depth': 10, 'max_features': 0.4, 'min_samples_leaf': 5, 'n_estimators': 100, 'random_state': 7, 'warm_start': True}
- 增加估算器的数量并训练:
gbt_inst = GradientBoostingRegressor(**{'learning_rate': 0.1,
'loss': 'huber',
'max_depth': 10,
'max_features': 0.4,
'min_samples_leaf': 5,
'n_estimators': 4000,
'warm_start': True, 'random_state':7}).fit(X_1, y_1)
- 对估算器进行持久化。为了方便和可重用性,持久化的代码被封装成一个函数:
def pickle_func(filename, saved_object):
import pickle
f = open(filename, 'wb')
pickle.dump(saved_object, f, protocol = pickle.HIGHEST_PROTOCOL)
f.close()
return None
pickle_func('grad_boost.save', gbt_inst)
第三个基础模型 - 梯度提升集成的袋装回归器
- 现在,进行一个小规模的网格搜索,尝试一组梯度增强树的袋装。理论上很难判断这种集成方法是否会表现良好。对于堆叠而言,只要它与其他基础估算器的相关性不太高,它就足够好:
from sklearn.ensemble import BaggingRegressor,GradientBoostingRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {
'max_samples': [0.5,1.0],
'max_features' : [0.5,1.0],
'oob_score' : [True, False],
'base_estimator__min_samples_leaf': [4,5],
'n_estimators': [20]
}
single_estimator = GradientBoostingRegressor(**{'learning_rate': 0.1,
'loss': 'huber',
'max_depth': 10,
'max_features': 0.4,
'n_estimators': 20,
'warm_start': True, 'random_state':7})
ensemble_estimator = BaggingRegressor(base_estimator = single_estimator)
pre_gs_inst_bag = RandomizedSearchCV(ensemble_estimator,
param_distributions = param_dist,
cv=3,
n_iter = 5,
n_jobs=-1)
pre_gs_inst_bag.fit(X_1, y_1)
- 查看最佳参数和评分:
pre_gs_inst_bag.best_score_
0.78087218305611195
pre_gs_inst_bag.best_params_
{'base_estimator__min_samples_leaf': 5,
'max_features': 1.0,
'max_samples': 1.0,
'n_estimators': 20,
'oob_score': True}
- 持久化最佳估算器:
pickle_func('bag_gbm.save', pre_gs_inst_bag.best_estimator_)
堆叠器的一些函数
- 使用类似于第九章的函数,树算法与集成方法。
handle_X_set函数在X_stack集合上创建预测向量的数据框。概念上,它指的是对训练集第二部分进行预测的第四步:
def handle_X_set(X_train_set_in):
X_train_set = X_train_set_in.copy()
y_pred_nn = neural_net.predict(X_train_set)
y_pred_gbt = gbt.predict(X_train_set)
y_pred_bag = bag_gbm.predict(X_train_set)
preds_df = pd.DataFrame(columns = ['nn', 'gbt','bag'])
preds_df['nn'] = y_pred_nn
preds_df['gbt'] = y_pred_gbt
preds_df['bag'] = y_pred_bag
return preds_df
def predict_from_X_set(X_train_set_in):
X_train_set = X_train_set_in.copy()
return final_etr.predict(handle_X_set(X_train_set))
- 如果你之前已经持久化了文件,并希望从这一步开始,解持久化文件。以下文件使用正确的文件名和变量名加载,以执行
handle_X_set函数:
def pickle_load_func(filename):
f = open(filename, 'rb')
to_return = pickle.load(f)
f.close()
return to_return
neural_net = pickle_load_func('nn_best.save')
gbt = pickle_load_func('grad_boost.save')
bag_gbm = pickle_load_func('bag_gbm.save')
- 使用
handle_X_set函数创建预测数据框。打印预测向量之间的皮尔逊相关性:
preds_df = handle_X_set(X_stack)
print (preds_df.corr())
nn gbt bag
nn 1.000000 0.867669 0.888655
gbt 0.867669 1.000000 0.981368
bag 0.888655 0.981368 1.000000
元学习器 – 额外树回归器
- 类似于第九章,树算法与集成方法,在预测数据框上训练一个额外树回归器。使用
y_stack作为目标向量:
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {'max_features' : ['sqrt','log2',1.0],
'min_samples_leaf' : [1, 2, 3, 7, 11],
'n_estimators': [50, 100],
'oob_score': [True, False]}
pre_gs_inst = RandomizedSearchCV(ExtraTreesRegressor(warm_start=True,bootstrap=True,random_state=7),
param_distributions = param_dist,
cv=3,
n_iter = 15,random_state=7)
pre_gs_inst.fit(preds_df.values, y_stack)
- 查看最佳参数:
pre_gs_inst.best_params_
{'max_features': 1.0,
'min_samples_leaf': 11,
'n_estimators': 100,
'oob_score': False}
- 训练额外树回归器,但增加估计器的数量:
final_etr = ExtraTreesRegressor(**{'max_features': 1.0,
'min_samples_leaf': 11,
'n_estimators': 3000,
'oob_score': False, 'random_state':7}).fit(preds_df.values, y_stack)
- 查看
final_etr估计器的交叉验证性能:
from sklearn.model_selection import cross_val_score
cross_val_score(final_etr, preds_df.values, y_stack, cv=3).mean()
0.82212054913537747
- 查看测试集上的性能:
y_pred = predict_from_X_set(X_test_prin)
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_test_prin, y_pred)
print "MAE : ",mean_absolute_error(y_test_prin, y_pred)
print "MAPE : ",(np.abs(y_test_prin- y_pred)/y_test_prin).mean()
R-squared 0.839538887753
MAE : 0.303109168851
MAPE : 0.168643891048
- 或许我们可以进一步提高结果。将训练列与预测向量并排放置。首先修改我们一直在使用的函数:
def handle_X_set_sp(X_train_set_in):
X_train_set = X_train_set_in.copy()
y_pred_nn = neural_net.predict(X_train_set)
y_pred_gbt = gbt.predict(X_train_set)
y_pred_bag = bag_gbm.predict(X_train_set)
#only change in function: include input vectors in training dataframe
preds_df = pd.DataFrame(X_train_set, columns = cali_housing.feature_names)
preds_df['nn'] = y_pred_nn
preds_df['gbt'] = y_pred_gbt
preds_df['bag'] = y_pred_bag
return preds_df
def predict_from_X_set_sp(X_train_set_in):
X_train_set = X_train_set_in.copy()
#change final estimator's name to final_etr_sp and use handle_X_set_sp within this function
return final_etr_sp.predict(handle_X_set_sp(X_train_set))
- 按照之前的方式继续训练额外树回归器:
preds_df_sp = handle_X_set_sp(X_stack)
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.model_selection import RandomizedSearchCV
param_dist = {'max_features' : ['sqrt','log2',1.0],
'min_samples_leaf' : [1, 2, 3, 7, 11],
'n_estimators': [50, 100],
'oob_score': [True, False]}
pre_gs_inst_2 = RandomizedSearchCV(ExtraTreesRegressor(warm_start=True,bootstrap=True,random_state=7),
param_distributions = param_dist,
cv=3,
n_iter = 15,random_state=7)
pre_gs_inst_2.fit(preds_df_sp.values, y_stack)
- 我们继续按之前的方式进行。查看最佳参数,并使用更多的估计器训练模型:
{'max_features': 'log2',
'min_samples_leaf': 2,
'n_estimators': 100,
'oob_score': False}
final_etr_sp = ExtraTreesRegressor(**{'max_features': 'log2',
'min_samples_leaf': 2,
'n_estimators': 3000,
'oob_score': False,'random_state':7}).fit(preds_df_sp.values, y_stack)
- 查看交叉验证性能:
from sklearn.model_selection import cross_val_score
cross_val_score(final_etr_sp, preds_df_sp.values, y_stack, cv=3).mean()
0.82978653642597144
- 我们在堆叠器的高级学习器训练中包含了原始输入列。交叉验证性能从 0.8221 提高到 0.8297。因此,我们得出结论,包含输入列的模型是最优模型。现在,在我们选择这个模型作为最终最佳模型后,我们查看估计器在测试集上的表现:
y_pred = predict_from_X_set_sp(X_test_prin)
from sklearn.metrics import r2_score, mean_absolute_error
print "R-squared",r2_score(y_test_prin, y_pred)
print "MAE : ",mean_absolute_error(y_test_prin, y_pred)
print "MAPE : ",(np.abs(y_test_prin- y_pred)/y_test_prin).mean()
R-squared 0.846455829258
MAE : 0.295381654368
MAPE : 0.163374936923
还有更多...
在尝试过 scikit-learn 中的神经网络后,你可以尝试像skflow这样的包,它借用了 scikit-learn 的语法,但利用了谷歌强大的开源 TensorFlow。
关于堆叠,你可以尝试在整个训练集X_train_prin上进行交叉验证性能评估和预测,而不是将其分割成两部分X_1和X_stack。
数据科学中的许多包都大量借鉴了 scikit-learn 或 R 的语法。
第十二章:创建一个简单的估计器
在本章中,我们将涵盖以下几种方法:
- 创建一个简单的估计器
介绍
我们将使用 scikit-learn 创建一个自定义估计器。我们将传统的统计数学和编程转化为机器学习。你可以通过使用 scikit-learn 强大的交叉验证功能,将任何统计学方法转变为机器学习。
创建一个简单的估计器
我们将进行一些工作,构建我们自己的 scikit-learn 估计器。自定义的 scikit-learn 估计器至少包括三个方法:
-
一个
__init__初始化方法:该方法接受估计器的参数作为输入 -
一个
fit方法:该方法用于训练估计器 -
一个
predict方法:该方法对未见过的数据进行预测
从图示来看,类大致如下:
#Inherit from the classes BaseEstimator, ClassifierMixin
class RidgeClassifier(BaseEstimator, ClassifierMixin):
def __init__(self,param1,param2):
self.param1 = param1
self.param2 = param2
def fit(self, X, y = None):
#do as much work as possible in this method
return self
def predict(self, X_test):
#do some work here and return the predictions, y_pred
return y_pred
准备工作
从 scikit-learn 中加载乳腺癌数据集:
import numpy as np
import pandas as pd
from sklearn.datasets import load_breast_cancer
bc = load_breast_cancer()
new_feature_names = ['_'.join(ele.split()) for ele in bc.feature_names]
X = pd.DataFrame(bc.data,columns = new_feature_names)
y = bc.target
将数据划分为训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=7, stratify = y)
如何实现...
一个 scikit 估计器应该有一个 fit 方法,该方法返回类本身,并且有一个 predict 方法,该方法返回预测结果:
- 以下是我们称之为
RidgeClassifier的分类器。导入BaseEstimator和ClassifierMixin,并将它们作为参数传递给你的新分类器:
from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.linear_model import Ridge
class RidgeClassifier(BaseEstimator, ClassifierMixin):
"""A Classifier made from Ridge Regression"""
def __init__(self,alpha=0):
self.alpha = alpha
def fit(self, X, y = None):
#pass along the alpha parameter to the internal ridge estimator and perform a fit using it
self.ridge_regressor = Ridge(alpha = self.alpha)
self.ridge_regressor.fit(X, y)
#save the seen class labels
self.class_labels = np.unique(y)
return self
def predict(self, X_test):
#store the results of the internal ridge regressor estimator
results = self.ridge_regressor.predict(X_test)
#find the nearest class label
return np.array([self.class_labels[np.abs(self.class_labels - x).argmin()] for x in results])
让我们重点关注 __init__ 方法。在这里,我们输入一个单一参数;它对应于底层岭回归器中的正则化参数。
在 fit 方法中,我们执行所有的工作。工作内容包括使用内部的岭回归器,并将类标签存储在数据中。如果类别超过两个,我们可能希望抛出一个错误,因为多个类别通常不能很好地映射到一组实数。在这个示例中,有两个可能的目标:恶性癌症或良性癌症。它们映射到实数,表示恶性程度,可以视为与良性相对立的度量。在鸢尾花数据集中,有 Setosa、Versicolor 和 Virginica 三种花。Setosaness 属性没有一个明确的对立面,除非以一对多的方式查看分类器。
在 predict 方法中,你会找到与岭回归器预测最接近的类标签。
- 现在编写几行代码应用你的新岭回归分类器:
r_classifier = RidgeClassifier(1.5)
r_classifier.fit(X_train, y_train)
r_classifier.score(X_test, y_test)
0.95744680851063835
- 它在测试集上的表现相当不错。你也可以在其上执行网格搜索:
from sklearn.model_selection import GridSearchCV
param_grid = {'alpha': [0,0.5,1.0,1.5,2.0]}
gs_rc = GridSearchCV(RidgeClassifier(), param_grid, cv = 3).fit(X_train, y_train)
gs_rc.grid_scores_
[mean: 0.94751, std: 0.00399, params: {'alpha': 0},
mean: 0.95801, std: 0.01010, params: {'alpha': 0.5},
mean: 0.96063, std: 0.01140, params: {'alpha': 1.0},
mean: 0.96063, std: 0.01140, params: {'alpha': 1.5},
mean: 0.96063, std: 0.01140, params: {'alpha': 2.0}]
它是如何工作的...
创建你自己的估计器的目的是让估计器继承 scikit-learn 基础估计器和分类器类的属性。在以下代码中:
r_classifier.score(X_test, y_test)
你的分类器查看了所有 scikit-learn 分类器的默认准确度评分。方便的是,你不需要去查找或实现它。此外,使用你的分类器时,过程与使用任何 scikit 分类器非常相似。
在以下示例中,我们使用逻辑回归分类器:
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(X_train,y_train)
lr.score(X_test,y_test)
0.9521276595744681
你的新分类器比逻辑回归稍微好了一些。
还有更多...
有时,像 Python 的statsmodels或rpy(Python 中的 R 接口)这样的统计包包含非常有趣的统计方法,你可能希望将它们通过 scikit 的交叉验证来验证。或者,你可以自己编写方法并希望对其进行交叉验证。
以下是一个使用statsmodels的广义估计方程(GEE)构建的自定义估计器,该方法可以在www.statsmodels.org/dev/gee.html找到。
GEE 使用的是一般线性模型(借鉴了 R),我们可以选择一个类似分组的变量,其中观察值在一个聚类内部可能相关,但跨聚类之间无关——根据文档的说法。因此,我们可以根据某个变量进行分组或聚类,并查看组内相关性。
在这里,我们根据 R 风格公式创建一个基于乳腺癌数据的模型:
'y ~ mean_radius + mean_texture + mean_perimeter + mean_area + mean_smoothness + mean_compactness + mean_concavity + mean_concave_points + mean_symmetry + mean_fractal_dimension + radius_error + texture_error + perimeter_error + area_error + smoothness_error + compactness_error + concavity_error + concave_points_error + symmetry_error + fractal_dimension_error + worst_radius + worst_texture + worst_perimeter + worst_area + worst_smoothness + worst_compactness + worst_concavity + worst_concave_points + worst_symmetry + worst_fractal_dimension'
我们根据特征mean_concavity进行聚类(mean_concavity变量未包含在 R 风格公式中)。首先导入statsmodels模块的库。示例如下:
import statsmodels.api as sm
import statsmodels.formula.api as smf
from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.linear_model import Ridge
class GEEClassifier(BaseEstimator, ClassifierMixin):
"""A Classifier made from statsmodels' Generalized Estimating Equations documentation available at: http://www.statsmodels.org/dev/gee.html
"""
def __init__(self,group_by_feature):
self.group_by_feature = group_by_feature
def fit(self, X, y = None):
#Same settings as the documentation's example:
self.fam = sm.families.Poisson()
self.ind = sm.cov_struct.Exchangeable()
#Auxiliary function: only used in this method within the class
def expand_X(X, y, desired_group):
X_plus = X.copy()
X_plus['y'] = y
#roughly make ten groups
X_plus[desired_group + '_group'] = (X_plus[desired_group] * 10)//10
return X_plus
#save the seen class labels
self.class_labels = np.unique(y)
dataframe_feature_names = X.columns
not_group_by_features = [x for x in dataframe_feature_names if x != self.group_by_feature]
formula_in = 'y ~ ' + ' + '.join(not_group_by_features)
data = expand_X(X,y,self.group_by_feature)
self.mod = smf.gee(formula_in,
self.group_by_feature + "_group",
data,
cov_struct=self.ind,
family=self.fam)
self.res = self.mod.fit()
return self
def predict(self, X_test):
#store the results of the internal GEE regressor estimator
results = self.res.predict(X_test)
#find the nearest class label
return np.array([self.class_labels[np.abs(self.class_labels - x).argmin()] for x in results])
def print_fit_summary(self):
print res.summary()
return self
fit方法中的代码与 GEE 文档中的代码类似。你可以根据自己的具体情况或统计方法来调整代码。predict方法中的代码与创建的岭回归分类器类似。
如果你像运行岭回归估计器那样运行代码:
gee_classifier = GEEClassifier('mean_concavity')
gee_classifier.fit(X_train, y_train)
gee_classifier.score(X_test, y_test)
0.94680851063829785
关键在于,你将一种传统的统计方法转变为机器学习方法,利用了 scikit-learn 的交叉验证。
尝试在皮马糖尿病数据集上使用新的 GEE 分类器
尝试在皮马糖尿病数据集上使用 GEE 分类器。加载数据集:
import pandas as pd
data_web_address = "https://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data"
column_names = ['pregnancy_x',
'plasma_con',
'blood_pressure',
'skin_mm',
'insulin',
'bmi',
'pedigree_func',
'age',
'target']
feature_names = column_names[:-1]
all_data = pd.read_csv(data_web_address , names=column_names)
import numpy as np
import pandas as pd
X = all_data[feature_names]
y = all_data['target']
将数据集分成训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7,stratify=y)
使用 GEE 分类器进行预测。我们将使用blood_pressure列作为分组依据:
gee_classifier = GEEClassifier('blood_pressure')
gee_classifier.fit(X_train, y_train)
gee_classifier.score(X_test, y_test)
0.80519480519480524
你也可以尝试岭回归分类器:
r_classifier = RidgeClassifier()
r_classifier.fit(X_train, y_train)
r_classifier.score(X_test, y_test)
0.76623376623376627
你可以将这些方法——岭回归分类器和 GEE 分类器——与第五章中的逻辑回归进行比较,线性模型 – 逻辑回归。
保存你训练好的估计器
保存你的自定义估计器与保存任何 scikit-learn 估计器相同。按照以下方式将训练好的岭回归分类器保存到文件rc_inst.save中:
import pickle
f = open('rc_inst.save','wb')
pickle.dump(r_classifier, f, protocol = pickle.HIGHEST_PROTOCOL)
f.close()
要检索训练好的分类器并使用它,请执行以下操作:
import pickle
f = open('rc_inst.save','rb')
r_classifier = pickle.load(f)
f.close()
在 scikit-learn 中保存一个训练好的自定义估计器非常简单。



浙公网安备 33010602011771号