Sklearn 秘籍第二版(一)

原文:annas-archive.org/md5/7039549ff2b32d189f96a3420dc66360

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

从安装和设置 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并注册,将文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标悬停在顶部的“支持”标签上。

  3. 点击“代码下载与勘误”。

  4. 在搜索框中输入书名。

  5. 选择您要下载代码文件的书籍。

  6. 从下拉菜单中选择您购买本书的渠道。

  7. 点击“代码下载”。

一旦文件下载完成,请确保使用以下最新版解压或提取文件夹:

  • 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 SetosaIris VersicolorIris Virginica

为了衡量预测的强度,我们将:

  • 保存一些数据以供测试

  • 只使用训练数据构建模型

  • 测量在测试集上的预测能力

预测——三种花卉类型中的一种是分类的。这类问题称为分类问题

非正式地说,分类问的是,*它是苹果还是橙子?*与此对比,机器学习回归问题问的是,有多少个苹果?顺便说一下,回归问题的答案可以是4.5 个苹果

通过其设计的演变,scikit-learn 主要通过四个类别来解决机器学习问题:

  • 分类:

    • 非文本分类,例如鸢尾花的例子

    • 文本分类

  • 回归

  • 聚类

  • 降维

NumPy 基础

数据科学部分处理结构化数据表。scikit-learn库要求输入表格是二维 NumPy 数组。在本节中,你将了解numpy库。

如何操作…

我们将对 NumPy 数组进行一些操作。NumPy 数组的所有元素具有相同的数据类型,并且具有预定义的形状。让我们首先查看它们的形状。

NumPy 数组的形状和维度

  1. 首先导入 NumPy:
import numpy as np
  1. 生成一个包含 10 个数字的 NumPy 数组,类似于 Python 的range(10)方法:
np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  1. 数组看起来像一个只有一对括号的 Python 列表。这意味着它是单维的。存储数组并找出它的形状:
array_1 = np.arange(10)
array_1.shape
(10L,)
  1. 数组具有一个数据属性,shapearray_1.shape的类型是元组(10L,),它的长度为1,在本例中是这样。维度的数量与元组的长度相同——在本例中是1维:
array_1.ndim      #Find number of dimensions of array_1
1
  1. 数组有 10 个元素。通过调用reshape方法重塑数组:
array_1.reshape((5,2))
array([[0, 1],
 [2, 3],
 [4, 5],
 [6, 7],
 [8, 9]])
  1. 这将把数组重塑为 5 x 2 的数据对象,类似于列表的列表(三维 NumPy 数组看起来像是列表的列表的列表)。你没有保存更改。请按以下方式保存重塑后的数组:
array_1 = array_1.reshape((5,2))
  1. 注意,array_1现在是二维的。这是预期的,因为它的形状有两个数字,看起来像是一个 Python 的列表的列表:
array_1.ndim
2

NumPy 广播

  1. 通过广播将1添加到数组的每个元素中。请注意,数组的更改没有保存:
array_1 + 1
array([[ 1,  2],
 [ 3,  4],
 [ 5,  6],
 [ 7,  8],
 [ 9, 10]])

广播这个术语指的是将较小的数组扩展或广播到较大的数组上。在第一个示例中,标量 1 被扩展为一个 5 x 2 的形状,并与 array_1 相加。

  1. 创建一个新的 array_2 数组。观察当你将数组与自身相乘时会发生什么(这不是矩阵乘法;而是数组的逐元素乘法):
array_2 = np.arange(10)
array_2 * array_2
array([ 0,  1,  4,  9, 16, 25, 36, 49, 64, 81])
  1. 每个元素都已被平方。这里发生了逐元素乘法。下面是一个更复杂的例子:
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]])
  1. 也修改 array_1
array_1 = array_1 + 1
array_1
array([[ 1,  2],
 [ 3,  4],
 [ 5,  6],
 [ 7,  8],
 [ 9, 10]])
  1. 现在通过简单地将数组之间加上加号来逐元素地将 array_1array_2 相加:
array_1 + array_2
array([[ 1,  3],
 [ 7, 13],
 [21, 31],
 [43, 57],
 [73, 91]])
  1. 正式的广播规则要求,当你比较两个数组的形状时,从右到左,所有的数字必须匹配或为 1。形状为5 X 25 X 2的两个数组从右到左都匹配。然而,形状为5 X 2 X 15 X 2不匹配,因为从右到左的第二个值,分别是25,不匹配:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/862cc6cc-1c37-433c-870d-70f656a4da57.jpg

初始化 NumPy 数组和数据类型

除了 np.arange,还有多种方法可以初始化 NumPy 数组:

  1. 使用 np.zeros 初始化一个全为零的数组。np.zeros((5,2)) 命令创建一个 5 x 2 的零数组:
np.zeros((5,2))
array([[ 0.,  0.],
 [ 0.,  0.],
 [ 0.,  0.],
 [ 0.,  0.],
 [ 0.,  0.]])
  1. 使用 np.ones 初始化一个全为 1 的数组。引入 dtype 参数,设置为 np.int,以确保这些 1 为 NumPy 整型。注意,scikit-learn 期望数组中的 dtypenp.float 类型。dtype 指的是 NumPy 数组中每个元素的类型,它在整个数组中保持一致。下面数组的每个元素都是 np.int 类型。
np.ones((5,2), dtype = np.int)
array([[1, 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]])
  1. 使用np.zerosnp.onesnp.empty为 NumPy 数组分配不同初始值的内存。

索引

  1. 使用索引查找二维数组的值:
array_1[0,0]   #Finds value in first row and first column.
1
  1. 查看第一行:
array_1[0,:]
array([1, 2])
  1. 然后查看第一列:
array_1[:,0]
array([1, 3, 5, 7, 9])
  1. 查看沿两个轴的特定值。同时查看第二行到第四行:
array_1[2:5, :]
array([[ 5,  6],
 [ 7,  8],
 [ 9, 10]])
  1. 仅查看第一列的第二行到第四行:
array_1[2:5,0]
array([5, 7, 9])

布尔数组

此外,NumPy 还使用布尔逻辑来处理索引:

  1. 首先生成一个布尔数组:
array_1 > 5
array([[False, False],
 [False, False],
 [False,  True],
 [ True,  True],
 [ True,  True]], dtype=bool)
  1. 将布尔数组加上括号,以便通过布尔数组进行过滤:
array_1[array_1 > 5]
array([ 6,  7,  8,  9, 10])

算术运算

  1. 使用 sum 方法将数组的所有元素相加。回到 array_1
array_1
array([[ 1,  2],
 [ 3,  4],
 [ 5,  6],
 [ 7,  8],
 [ 9, 10]])
array_1.sum()
55
  1. 按行求和:
array_1.sum(axis = 1)
array([ 3,  7, 11, 15, 19])
  1. 按列求和:
array_1.sum(axis = 0)
array([25, 30])
  1. 以类似的方式求每一列的均值。注意,均值数组的dtypenp.float
array_1.mean(axis = 0)
array([ 5.,  6.])

NaN 值

  1. Scikit-learn 不接受 np.nan 值。假设有如下的 array_3
array_3 = np.array([np.nan, 0, 1, 2, np.nan])
  1. 使用 np.isnan 函数创建一个特殊的布尔数组来查找 NaN 值:
np.isnan(array_3)
array([ True, False, False, False,  True], dtype=bool)
  1. 通过否定布尔数组并在表达式周围加上括号来过滤 NaN 值:
array_3[~np.isnan(array_3)]
>array([ 0.,  1.,  2.])
  1. 另一种方法是将 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 中加载 numpypandaspyplot 库:

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 

如何操作…

  1. 从 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 数据集涉及一个监督分类问题。

如何操作…

  1. 要访问观测变量,输入:
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
  1. 让我们检查一下 NumPy 数组:
iris.data.shape

这会返回:

(150L, 4L)

这意味着数据是 150 行 4 列。让我们看看第一行:

iris.data[0]

array([ 5.1,  3.5,  1.4,  0.2])

第一行的 NumPy 数组包含四个数字。

  1. 要确定它们的含义,输入:
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

这会返回一个输出数组:012。只有这三种输出。输入:

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 构建的库:

  1. 创建一个包含观测变量iris.data的数据框,并以columns作为列名:
import pandas as pd
iris_df = pd.DataFrame(iris.data, columns = iris.feature_names)

数据框比 NumPy 数组更易于使用。

  1. 查看数据框中sepal length值的快速直方图:
iris_df['sepal length (cm)'].hist(bins=30)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/bc06d071-510d-464e-8d01-1b6917d17b23.jpg

  1. 你还可以通过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)
  1. 在这里,迭代每种花的目标数字,并为每个花绘制一个彩色直方图。考虑这一行:
np.where(iris.target== class_number)[0]

它找到了每种花类别的 NumPy 索引位置:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/fc479efd-c396-4ea0-8faa-2d699d0f84c5.jpg

观察到直方图有重叠。这鼓励我们将三个直方图建模为三个正态分布。如果我们仅将训练数据建模为三个正态分布而不是整个数据集,这在机器学习中是可行的。然后,我们使用测试集来测试我们刚刚构建的三个正态分布模型。最后,我们在测试集上测试我们预测的准确性。

它是如何工作的…

数据框架数据对象是一个二维的 NumPy 数组,包含列名和行名。在数据科学中,基本的数据对象看起来像一个二维表格,这可能与 SQL 的悠久历史有关。NumPy 还支持三维数组、立方体、四维数组等,这些也经常出现。

使用 NumPy 和 matplotlib 绘图

使用 NumPy 进行可视化的一个简单方法是使用 matplotlib 库。让我们快速创建一些可视化图形。

准备工作

首先导入 numpymatplotlib。你可以使用 %matplotlib inline 命令在 IPython Notebook 中查看可视化图形:

import numpy as np
 import matplotlib.pyplot as plt
 %matplotlib inline

如何操作…

  1. matplotlib 中的主要命令,伪代码如下:
plt.plot(numpy array, numpy array of same length)
  1. 通过放置两个相同长度的 NumPy 数组来绘制一条直线:
plt.plot(np.arange(10), np.arange(10))

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/287e1a2e-a723-403f-912a-dc2ab10cfd2c.png

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

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/df166a79-ba13-48c5-adfa-765233d0aec8.png

  1. 将两个图表并排放置:
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)))

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/490c815b-e265-454c-967b-e7dc4e617bfd.png

子图命令中的前两个数字表示由 plt.figure() 实例化的图形中的网格大小。plt.subplot(221) 中提到的网格大小是 2 x 2,前两个数字表示网格的行列数。最后一个数字表示按阅读顺序遍历网格:从左到右,再从上到下。

  1. 在 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)))

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/b6546b85-8074-4d63-9b2f-66bdadc52305.png

  1. 最后,使用真实数据:
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 目标中的颜色 012

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/975ccab7-6564-4a71-82cd-daee92065546.png

一个简化的机器学习配方 – SVM 分类

机器学习的核心是进行预测。为了进行预测,我们需要:

  • 陈述要解决的问题

  • 选择一个模型来解决问题

  • 训练模型

  • 进行预测

  • 测量模型的表现

准备工作

回到鸢尾花示例,现在我们将观察数据的前两个特征(列)存储为 X,目标存储为 y,这是机器学习社区中的惯例:

X = iris.data[:, :2] 
y = iris.target

如何操作…

  1. 首先,我们陈述问题。我们试图从一组新的观察数据中确定花卉类型类别。这是一个分类任务。可用的数据包括一个目标变量,我们将其命名为 y。这属于监督分类问题。

监督学习的任务涉及使用输入变量和输出变量训练模型,从而预测输出变量的值。

  1. 接下来,我们选择一个模型来解决监督分类问题。目前我们将使用支持向量分类器。由于其简单性和可解释性,它是一个常用的算法(可解释性意味着容易阅读和理解)。

  2. 为了衡量预测性能,我们将数据集分为训练集和测试集。训练集是我们将从中学习的数据。测试集是我们保留的数据,并假装不知道它,以便衡量我们学习过程的性能。因此,导入一个将数据集拆分的函数:

from sklearn.model_selection import train_test_split
  1. 将该函数应用于观察数据和目标数据:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=1)

测试集大小为 0.25,即整个数据集的 25%。一个随机状态值 1 固定了函数的随机种子,使得每次调用该函数时都能得到相同的结果,这对现在保持结果一致性非常重要。

  1. 现在加载一个常用的估算器——支持向量机:
from sklearn.svm import SVC
  1. 你已经从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 在这些数据上的表现。最终,我们得到了一个衡量准确度的数值,或者说衡量模型表现的数值。

还有更多内容……

总结一下,我们将用不同的算法——逻辑回归——来执行所有步骤:

  1. 首先,导入LogisticRegression
from sklearn.linear_model import LogisticRegression
  1. 然后编写一个包含建模步骤的程序:

    1. 将数据分为训练集和测试集。

    2. 拟合逻辑回归模型。

    3. 使用测试观察数据进行预测。

    4. 使用y_testy_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_2y_train中 25%的目标数据。

正如你可能预料的,你必须使用这些新的拆分来在两个模型之间做选择:SVC 和逻辑回归。通过编写一个预测程序来实现这一点。

如何做到…

  1. 从导入所有库并加载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)
  1. 创建一个 SVC 分类器实例并进行拟合:
svc_clf = SVC(kernel = 'linear',random_state = 7)
svc_clf.fit(X_train_2, y_train_2)
  1. 对逻辑回归做相同操作(逻辑回归的两行代码压缩成一行):
lr_clf = LogisticRegression(random_state = 7).fit(X_train_2, y_train_2)
  1. 现在预测并检查 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
  1. 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 中进行交叉验证。

交叉验证涉及将训练集拆分为多个部分,就像我们之前做的一样。为了与前面的示例相匹配,我们将训练集拆分为四个部分或折叠。我们将通过轮流选取其中一个折叠作为测试集,其他三个折叠作为训练集,来设计一个交叉验证迭代。这与之前的拆分相同,只不过进行了四次轮换,从某种意义上来说,测试集在四个折叠中轮换:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/f1e7c4be-773e-49cb-9291-1b06a0b75050.jpg

使用 scikit-learn,这个操作相对容易实现:

  1. 我们从一个导入开始:
from sklearn.model_selection import cross_val_score
  1. 然后我们在四个折叠上生成准确度评分:
svc_scores = cross_val_score(svc_clf, X_train, y_train, cv=4)
svc_scores 
array([ 0.82758621,  0.85714286,  0.92857143,  0.77777778])
  1. 我们可以找到平均表现的均值和所有得分相对于均值的标准差来衡量得分的分布情况:
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
  1. 同样,对于逻辑回归实例,我们计算出四个得分:
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)。

如何做…

  1. 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被设置为零,可以设置为任何整数。

  1. 通过改变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)
  1. 评分并打印出选择的得分:
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——一个分类器和回归器

  1. datasets模块加载iris
import numpy as np
import pandas as pd
from sklearn import datasets

iris = datasets.load_iris()
  1. 为简便起见,只考虑目标01,分别对应 Setosa 和 Versicolor。使用布尔数组iris.target < 2来过滤目标2。将其放入括号中,作为筛选器在定义观测集X和目标集y时使用:
X = iris.data[iris.target < 2]
y = iris.target[iris.target < 2]
  1. 现在导入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)
  1. 通过导入 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)
  1. 和之前的部分一样,查看评分的平均值:
svc_scores.mean()

0.94795321637426899
  1. 通过从sklearn.svm导入SVR,对支持向量回归执行相同操作,该模块也包含 SVC:
from sklearn.svm import SVR
  1. 然后编写必要的语法来拟合模型。它与 SVC 的语法几乎相同,只需将一些c关键字替换为r
svr_clf = SVR(kernel = 'linear').fit(X_train, y_train)

创建评分器

要创建评分器,你需要:

  • 一个评分函数,它将y_test(真实值)与y_pred(预测值)进行比较

  • 确定高分是好是坏

在将 SVR 回归器传递给交叉验证之前,先通过提供两个元素来创建评分器:

  1. 在实际操作中,首先导入make_scorer函数:
from sklearn.metrics import make_scorer
  1. 使用这个样本评分函数:
#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 函数将预测值四舍五入到最接近的整数,希望它是目标值之一,即 01astype 方法将预测值的类型更改为整数类型,因为原始目标是整数类型,并且在类型上保持一致性是更优的选择。四舍五入之后,评分函数使用你熟悉的旧版 accuracy_score 函数。

  1. 现在,确定较高的分数是否更好。更高的准确度更好,因此在这种情况下,较高的分数更好。在 scikit 代码中:
svr_to_class_scorer = make_scorer(for_scorer, greater_is_better=True) 
  1. 最后,使用一个新的参数——评分参数,运行交叉验证:
svr_scores = cross_val_score(svr_clf, X_train, y_train, cv=4, scoring = svr_to_class_scorer)
  1. 计算均值:
svr_scores.mean()

0.94663742690058483

SVR 回归器基础的分类器与传统的 SVC 分类器在准确率评分上相似。

它是如何工作的…

你可能会问,为什么我们从目标集合中去掉了类 2

其原因在于,为了使用回归模型,我们的目标必须是预测一个实数。类别必须具备实数性质:即它们是有序的(非正式地说,如果我们有三个有序的类别 xyz,并且 x < yy < 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)的数学细节和实践过程,请对自己宽容些,因为机器学习更关注预测性能,而非传统统计学。

一个管道

在编程中,管道是一个按顺序连接的过程集,其中一个过程的输出作为下一个过程的输入:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/dc22816a-cefb-4269-9c4d-04900308b55e.png

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

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/d2a52812-d38e-45ae-b047-3c96e13f0d62.png

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

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/8267f221-917f-41ea-b55a-d9185d3fbb01.png

在接下来的章节中,我们将看到 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模块为dnumpynp

import sklearn.datasets as d
import numpy as np

如何实现…

本节将带你逐步创建几个数据集。除了示例数据集外,这些数据集将贯穿整本书,用于创建具有算法所需特征的数据。

创建回归数据集

  1. 首先是可靠的——回归:
reg_data = d.make_regression()

默认情况下,这将生成一个包含 100 x 100 矩阵的元组——100 个样本和 100 个特征。然而,默认情况下,只有 10 个特征负责目标数据的生成。元组的第二个成员是目标变量。实际上,也可以更深入地参与回归数据的生成。

  1. 例如,为了生成一个 1000 x 10 的矩阵,其中五个特征负责目标的创建,偏置因子为 1.0,并且有两个目标,可以运行以下命令:
complex_reg_data = d.make_regression(1000, 10, 5, 2, 1.0)
complex_reg_data[0].shape

(1000L, 10L)

创建一个不平衡的分类数据集

分类数据集也非常容易创建。创建一个基本的分类数据集很简单,但基本情况在实践中很少见——大多数用户不会转换,大多数交易不是欺诈性的,等等。

  1. 因此,探索不平衡数据集上的分类是非常有用的:
classification_set = d.make_classification(weights=[0.1])
np.bincount(classification_set[1])

array([10, 90], dtype=int64)

创建聚类数据集

聚类也会涉及到。实际上,有多个函数可以创建适用于不同聚类算法的数据集。

  1. 例如,blobs 非常容易创建,并且可以通过 k-means 来建模:
blobs_data, blobs_target = d.make_blobs()
  1. 这将看起来像这样:
import matplotlib.pyplot as plt
%matplotlib inline 
#Within an Ipython notebook
plt.scatter(blobs_data[:,0],blobs_data[:,1],c = blobs_target) 

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/0bf9b285-7654-42fc-a2cb-4f14e9d40e44.png

它是如何工作的…

让我们通过查看源代码(做了一些修改以便清晰)来逐步了解 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)

计算Xground_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

如何实现…

  1. 继续使用波士顿数据集,运行以下命令:
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])
  1. 从一开始就可以学到很多东西。首先,第一个特征的均值最小,但变化范围比第三个特征更大。第二个特征的均值和标准差最大——它需要分布最广的数值:
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)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/0d0ec1e7-2c6e-447a-a34c-c6a548aab0db.png

变换后的样子如下:

pd.Series(preprocessing.scale(X[:, 2])).hist(bins=50)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/1a8a94aa-e223-4952-94f9-2ab4258b4705.png

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类的最小值和最大值从默认的01更改为其他值:


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. 为此,我们需要创建一个均值的阈值。如果值大于均值,返回1;如果小于均值,返回0
from sklearn import preprocessing
new_target = preprocessing.binarize(y,threshold=boston.target.mean())
new_target[:5]

 array([[ 1.],
 [ 0.],
 [ 1.],
 [ 1.],
 [ 1.]])
  1. 这很简单,但让我们检查一下以确保它正常工作:
(y[:5] > y.mean()).astype(int)

array([[1],
       [0],
       [1],
       [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 &lt; 0

fit方法

fit方法是针对二值化转换存在的,但它不会对任何东西进行拟合;它只会返回该对象。该对象会存储阈值,并准备好进行transform方法。

处理分类变量

类别变量是一个问题。一方面,它们提供了有价值的信息;另一方面,它们很可能是文本——无论是实际的文本还是与文本对应的整数——例如查找表中的索引。

所以,显然我们需要将文本表示为整数,以便于模型处理,但不能仅仅使用 ID 字段或天真地表示它们。这是因为我们需要避免类似于通过阈值创建二进制特征食谱中出现的问题。如果我们处理的是连续数据,它必须被解释为连续数据。

准备工作

波士顿数据集对于本节不适用。虽然它对于特征二值化很有用,但不足以从类别变量中创建特征。为此,鸢尾花数据集就足够了。

为了使其生效,问题需要彻底转变。设想一个问题,目标是预测花萼宽度;在这种情况下,花卉的物种可能作为一个特征是有用的。

如何做……

  1. 让我们先整理数据:
from sklearn import datasets
import numpy as np
iris = datasets.load_iris()

X = iris.data
y = iris.target
  1. Xy,所有数值数据,放在一起。使用 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()。如果Xy是管道的一部分,管道将分别转换训练集和测试集,这是更可取的做法:

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 函数将每个实数转换为整数 01

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]])

如何做……

  1. 本书中的一个常见主题(由于 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]])
  1. 注意 [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)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/d1ebad85-c139-418a-94ec-06c1d3ce5791.png

给数据添加噪声,并将其标记为 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')

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/f9790eae-a74e-4d0f-93b0-45057e7afff8.png

如何操作…

  1. 导入 LinearRegressionTheilSenRegressor。使用原始线作为测试集 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)
  1. 绘制这些线条。请注意,普通最小二乘法OLS)与真实线 y_truth 相差甚远,而 Theil-Sen 则与真实线重叠:
plt.plot(x_vals, y_truth, label='True line')
plt.legend(loc='upper left')

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/87bf00b0-a0bf-4e0e-8e82-8fcdd3a54a7d.png

  1. 绘制数据集和估计的线条:
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')

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/24418ea6-bb8c-4d1d-90bf-c357f9d6bd18.png

它是如何工作的…

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。你可以想象(我们稍后会做)这个工作流程可能需要在训练数据集和保留集之间拆分;管道将使这更容易,但首先我们需要迈出小小的一步。

  1. 让我们加载所需的库:
from sklearn import pipeline, preprocessing, decomposition
  1. 接下来,创建imputerpca类:
pca = decomposition.PCA()
imputer = preprocessing.Imputer()
  1. 现在我们已经有了需要的类,我们可以将它们加载到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])

如何做到……

  1. 我们有数据,将创建一个 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 以供实际使用。该值表示优化内核时的迭代次数。

  1. 导入所需的内核函数并设置一个灵活的内核:
from sklearn.gaussian_process.kernels import RBF, ConstantKernel as CK

mixed_kernel = kernel = CK(1.0, (1e-4, 1e4)) * RBF(10, (1e-4, 1e4))
  1. 最后,实例化并拟合算法。请注意,alpha对所有值都设置为5。我之所以选择这个数字,是因为它大约是目标值的四分之一:
gpr = GaussianProcessRegressor(alpha=5,
 n_restarts_optimizer=20,
 kernel = mixed_kernel)

gpr.fit(boston_X[train_set],boston_y[train_set])
  1. 将对未见数据的预测存储为test_preds
test_preds = gpr.predict(boston_X[~train_set])
  1. 绘制结果:
>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")

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/1a8840b6-2a13-4f42-9d39-ff0de8bb8cf4.png

使用噪声参数进行交叉验证

你可能会想,这是否是最佳的噪声参数alpha=5?为了弄清楚这一点,尝试一些交叉验证。

  1. 首先,使用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'))
  1. 查看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

注意,最后一次折叠中的得分与其他三次折叠不一样。

  1. 现在使用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
  1. 这个得分看起来更好一些。现在,尝试将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
  1. 这看起来更好,因为均值较高,标准差较低。让我们选择最后一个模型进行最终训练:
gpr7n.fit(boston_X[train_set],boston_y[train_set])
  1. 进行预测:
test_preds = gpr7n.predict(boston_X[~train_set])
  1. 可视化结果:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/a5930ca5-55ac-412c-8b48-5282cc355af9.png

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

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/877acdd9-751d-40ab-a2db-8ab3246271bb.png

数组 alphas 与 cross_val_score 不兼容,因此我无法通过查看最终图形并判断哪一个是最佳模型来选择该模型。所以,我们最终选择的模型是 gpr7n,并且设置了 alpha=7normalize_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));

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/0116515f-41b8-4bfd-9c78-911c85eac557.png

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

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/003714ee-b7a8-4bad-a8d8-81da8b3abe4e.png

对于某些点,不确定性非常高。如你所见,许多给定点的估计值有很大的差异。然而,整体误差并不算太差。

使用 SGD 进行回归

在这个教程中,我们将首次体验随机梯度下降。我们将在回归中使用它。

准备就绪

SGD(随机梯度下降)通常是机器学习中的一个默默无闻的英雄。在许多算法背后,正是 SGD 在默默地执行工作。由于其简单性和速度,SGD 非常受欢迎——这两者在处理大量数据时是非常有用的。SGD 的另一个优点是,尽管它在许多机器学习算法的计算核心中扮演重要角色,但它之所以如此有效,是因为它能够简明地描述这一过程。归根结底,我们对数据应用一些变换,然后用损失函数将数据拟合到模型中。

如何实现……

  1. 如果 SGD 在大数据集上表现良好,我们应该尝试在一个相对较大的数据集上测试它:
from sklearn.datasets import make_regression
X, y = make_regression(int(1e6))  #1,000,000 rows

了解对象的组成和大小可能是值得的。幸运的是,我们处理的是 NumPy 数组,因此可以直接访问 nbytes。Python 内建的访问对象大小的方法对于 NumPy 数组不起作用。

  1. 该输出可能与系统有关,因此你可能无法得到相同的结果:
print "{:,}".format(X.nbytes)

800,000,000
  1. 为了获得一些人类的视角,我们可以将 nbytes 转换为兆字节。大约每兆字节包含 100 万字节:
X.nbytes / 1e6

800
  1. 因此,每个数据点的字节数如下:
X.nbytes / (X.shape[0]*X.shape[1])

8

好吧,对于我们想要实现的目标,这样不显得有点杂乱无章吗?然而,了解如何获取你正在处理的对象的大小是很重要的。

  1. 所以,现在我们有了数据,我们可以简单地拟合一个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。

  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)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/skl-cb-2e/img/e0b40007-829c-455a-8bcc-bcb3866d8f80.png

它是如何工作的…

很明显,我们使用的虚拟数据集还不错,但你可以想象一些具有更大规模的数据集。例如,如果你在华尔街工作,在任何一天,某个市场上的交易量可能达到 20 亿笔。现在,想象一下你有一周或一年的数据。处理大量数据时,内存中的算法并不适用。

之所以通常比较困难,是因为要执行 SGD,我们需要在每一步计算梯度。梯度有任何高等微积分课程中的标准定义。

算法的要点是,在每一步中,我们计算一组新的系数,并用学习率和目标函数的结果更新它。在伪代码中,这可能看起来像这样:

while not converged:
 w = w – learning_rate*gradient(cost(w))

相关变量如下:

  • w:这是系数矩阵。

  • learning_rate:这表示每次迭代时步长的大小。如果收敛效果不好,可能需要调整这个值。

  • gradient:这是二阶导数的矩阵。

  • cost:这是回归的平方误差。稍后我们会看到,这个代价函数可以适应分类任务。这种灵活性是 SGD 如此有用的一个原因。

这不会太难,除了梯度函数很昂贵这一点。随着系数向量的增大,计算梯度变得非常昂贵。对于每一次更新步骤,我们需要为数据中的每一个点计算一个新的权重,然后更新。SGD 的工作方式略有不同;与批量梯度下降的定义不同,我们将每次用新的数据点来更新参数。这个数据点是随机选取的,因此得名随机梯度下降。

关于 SGD 的最后一点是,它是一种元启发式方法,赋予了多种机器学习算法强大的能力。值得查阅一些关于元启发式方法在各种机器学习算法中的应用的论文。前沿解决方案可能就隐藏在这些论文中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值