深入理解XGBoost:高效机器学习算法与进阶
上QQ阅读APP看书,第一时间看更新

2.1 搭建Python机器学习环境

Python是一种解释型、面向对象、动态数据类型的高级程序设计语言,可以运行在Windows、Mac和Linux/UNIX系统上。这里强烈推荐Anaconda,它是一个开源的Python发行版本,集成了科学计算、数学和工程所需的几乎所有常用的Python工具包,用户无须再一一安装,使用十分方便,安装方法可参考Anaconda安装指南参见https://docs.anaconda.com/anaconda/install/。

除了Anaconda之外,读者也可以通过Python的包管理工具pip安装第三方包。如果已经安装了Python(本书版本2.7.12)和pip(本书版本10.0.1),则可通过如下命令安装Python包:

pip install SomePackage

本书主要用到的Python工具包及版本号如下。

·Jupyter Notebook(版本4.2.0):一个交互式笔记本,支持实时代码、数学方程和可视化。

·NumPy(版本1.11.3):一个用Python实现的科学计算包,适用于向量、矩阵等复杂科学计算。

·Pandas(版本0.18.1):基于NumPy,用于数据快速处理和分析。

·Matplotlib(版本1.5.3):一个Python的2D绘图库。

·scikit-learn(版本0.19.1):基于NumPy和Scipy的一个常用机器学习算法库,包含大量经典机器学习模型。

为保证本书中的代码可正确运行,请确认已安装的软件包版本号大于或等于上述版本。读者可通过下列命令安装上述指定版本的Python包:

pip install jupyter==4.2.0
pip install numpy==1.11.3
pip install pandas==0.18.1
pip install matplotlib==1.5.3
pip install sklearn==0.19.1

2.1.1 Jupyter Notebook

Jupyter Notebook是基于浏览器的图形界面,支持IPython Shell,具有丰富的显示功能,除了可以执行Python语句之外,还支持格式化文本、静态和动态可视化、数学方程等。另外,Jupyter文档也允许其他人打开,在自己的系统上执行代码并保存。

虽然Jupyter Notebook是通过浏览器访问的,但在访问之前需要先启动Jupyter Notebook,启动命令如下:

jupyter notebook

启动后,会看到类似下面的日志信息:

https://jupyter.readthedocs.io/en/latest/running.html#running
   $ jupyter notebook
   [I 16:53:17.122 NotebookApp] Serving notebooks from local directory: /Users/xgb
   [I 16:53:17.122 NotebookApp] 0 active kernels
   [I 16:53:17.122 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/
   [I 16:53:17.122 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).

其中,http://localhost:8888/为Jupyter Notebook应用访问的URL。打开该URL便可以看到Jupyter Notebook面板,如图2-1所示。

图2-1 Jupyter-Notebook面板

可以看到,该面板显示了当前启动目录包含的文件及子目录,通常当前启动目录即为Jupyter Notebook程序的主目录。

如果想新建一个Notebook,只需要单击New下拉按钮,选择希望启动的Notebook类型即可,如图2-2所示。

图2-2 新建Notebook

也可以单击列表中Notebook的名称,打开现有Notebook。每个Notebook由多个单元格组成,用户可以在单元格内执行代码,如图2-3所示。

图2-3 要执行的代码

图2-4 执行结果

按Shift+Enter组合键之后,即可执行代码,执行结果如图2-4所示。

此外,Jupyter Notebook还有许多其他非常实用的特性,可参考Jupyter官方文档。参见https://jupyter.readthedocs.io/en/latest/index.html。

2.1.2 NumPy

NumPy是Python用于科学计算的开源工具包,提供了高效的接口来存储和操作密集数据缓冲区。在某种程度上,NumPy数组就像Python的内置列表,但NumPy数组提供了更为高效的存储和数据操作。NumPy提供了强大的N维数组对象Array、较为成熟的(广播)函数库及线性代数、傅里叶变换和随机数等功能。下面对NumPy的基本操作进行介绍。

1.NumPy数组

NumPy数组包含相同类型的元素,和标准Python类库中的array.array不同,后者只提供一维数组及少量功能,而NumPy数组包含很多重要属性。首先定义一个NumPy数组,代码如下:

In [1]: 
import numpy as np
a = np.arange(10).reshape(2, 5)
print a
Out [1]:
[[0 1 2 3 4]
[5 6 7 8 9]]

NumPy数组创建完成后,可以通过ndim、shape和size查看数组维度、每一维的大小及数组总大小,代码如下:

In [2]: 
a.ndim
Out [2]:
2

其中,通过shape输出的结果是一个整型元组,表示每一维的大小。例如,对于一个具有n行m列的矩阵,其shape输出为(n,m)。Numpy数组另外一个比较实用的属性是dtype,用于显示数组元素的数据类型,示例如下:

In [3]: 
a.dtype
Out [3]: 
dtype('int64')

NumPy数组也可以是多维数组,其中维度称为轴(axis),以下面数组为例:

[[1,2],

[2,3],

[3,4]]

其总共包含两维,第一维长度为3,第二维长度为2。

2.基本操作

(1)矩阵运算

可以将数学运算符直接应用于NumPy数组,从而产生一个新的数组。例如,对两个数组进行加、减、乘、除运算,即对应位置的元素进行相加、相减、相乘、相除,得到一个新的数组。代码如下:

In [4]: 
# 加法
a = np.array([1,2,3,4,5])
b = a + 2
print b
Out [4]: 
[3 4 5 6 7]
In [5]: 
# b数组求立方
b ** 3
Out [5]: 
array([ 27,  64, 125, 216, 343])
In [6]: 
# 矩阵加法
a = np.array([[1,2],
           [3,4]])
b = np.array([[1,2],
               [2,1]])
a + b
Out [6]: 
array([[2, 4],
        [5, 5]])
In [7]: 
# 矩阵减法
a - b
Out [7]: 
array([[0, 0],
        [1, 3]])

值得注意的是,在NumPy中,乘积符号“*”代表矩阵中对应元素相乘,而非矩阵乘积。矩阵乘积可以用dot()函数来实现,代码如下:

In [8]: 
# 矩阵对应元素相乘
a * b
Out [8]: 
array([[1, 4],
        [6, 4]])
In [9]: 
# 矩阵乘法
np.dot(a, b)
Out [9]: 
array([[ 5,  4]
        [11, 10]])

(2)数组索引与切片

NumPy支持强大的数组索引和切片功能,代码如下:

In [10]: 
# 随机生成一维数组
# low、high分别表示随机整数的下界和上界,size表示数组大小
a = np.random.randint(low=1, high=20, size=5)
a
Out [10]: 
array([12, 10, 14,  6,  9])
In [11]: 
# 获取一维数组a中索引为2~3的元素
a[2:4]
Out [11]: 
array([14,  6])
In [12]: 
# 随机生成二维数组
a = np.random.randint(low=1, high=20, size=(5,5)) 
a 
Out [12]: 
array([[16,  2, 16,  8,  6],
       [11, 18,  5,  4, 19],
       [ 7, 19,  2, 10, 16],
       [14,  7, 17, 18,  3],
       [ 6,  4, 18, 18,  3]])
In [13]: 
# 第2~4行中的第2列元素(此处行列均为索引)
a[2:5, 2] 
Out [13]: 
array([ 2, 17, 18])
In [14]: 
# a中的1~2行,输出所有列 
a[1:3, ]
Out [14]: 
array([[11, 18,  5,  4, 19],
       [ 7, 19,  2, 10, 16]])
In [15]: 
# 对a中1~3行、2~3列进行切片并赋值给b
b = a[1:4, 2:4]
b
Out [15]: 
array([[ 5,  4],
      [ 2, 10],
      [17, 18]])

另外,还可以通过reshape()函数改变数组形状。

In [16]: 
# 改变数组形状
a = np.random.randint(low=1, high=20, size=9)
a
Out [16]: 
array([19,  8,  4, 17,  6, 16, 15,  2, 15])
In [17]: 
a.reshape(3,3)
a
Out [17]: 
array([[19,  8,  4],
       [17,  6, 16],
       [15,  2, 15]])

(3)矩阵拼接

通过NumPy可以方便地实现多个矩阵之间的拼接,代码如下:

In [18]: 
a = np.random.randint(low=1, high=20, size=(3,3))
a
Out [18]: 
array([[11,  1,  1],
       [10,  4,  8],
       [ 6,  4,  5]])
In [19]: 
b = np.random.randint(low=1, high=20, size=(3,3))
b
Out [19]: 
array([[19, 10, 19],
       [16,  6,  6],
       [14, 18, 15]])
In [20]: 
# 垂直拼接
np.vstack((a,b))
Out [20]: 
array([[11,  1,  1],
       [10,  4,  8],
       [ 6,  4,  5],
       [19, 10, 19],
       [16,  6,  6],
       [14, 18, 15]])
In [21]: 
# 水平拼接
np.hstack((a,b))
Out [21]: 
array([[11,  1,  1, 19, 10, 19],
        [10,  4,  8, 16,  6,  6],
        [ 6,  4,  5, 14, 18, 15]])

(4)统计运算

对于数组元素的统计运算,NumPy提供了十分便利的方法,既可以通过a.min()直接统计数组a中最小元素,也可以通过np.mean(a,axis=0)来对某一维度进行统计。下面介绍NumPy中常用的几种统计运算。

In [22]: 
a = np.random.randint(low=1, high=20, size=(2,3))
a
Out [22]: 
array([[ 1  9  3],
      [11  7  8]])
In [23]: 
# 最小值
a.min()
Out [23]: 
1
In [24]: 
# 最大值
a.max()
Out [24]: 
11
In [25]: 
# 求和
a.sum()
Out [25]: 
39
In [26]: 
# 平均值
a.mean()
Out [26]: 
6.5
In [27]: 
# 标准差
a.std()
Out [27]: 
3.4520525295346629
In [28]: 
# 每列最大值
np.amax(a, axis = 0)
Out [28]: 
array([11,  9,  8])
In [29]: 
# 每行最小值
np.amin(a, axis = 1)
Out [29]: 
array([1, 7])
In [30]: 
# 每行标准差
np.std(a, axis = 1)
Out [30]: 
array([ 3.39934634,  1.69967317])
In [31]: 
# 每列方差
np.var(a, axis = 0)
Out [31]: 
array([ 25.  ,   1.  ,   6.25])

(5)数组排序

NumPy可以按任意维度对数组进行排序,支持3种不同的排序方法:快速排序(quicksort)、归并排序(mergesort)和堆排序(heapsort),可以通过参数指定不同的排序方法,若不指定,则默认使用快速排序。此外,NumPy还支持按照某种属性进行排序。

In [32]: 
# 生成矩阵
a = np.random.randint(low=1, high=20, size=(3,3))
a
Out [32]: 
array([[10, 18,  1],
     [10,  5,  5],
     [ 2, 18,  2]])
In [33]: 
#  默认按行进行排序
np.sort(a)
Out [33]: 
array([[ 1, 10, 18],
       [ 5,  5, 10],
       [ 2,  2, 18]])
In [34]: 
# 按列排序
np.sort(a, axis=0)
Out [34]: 
array([[ 2,  5,  1],
       [10, 18,  2],
       [10, 18,  5]])
In [35]: 
# 现有3列属性,分别为id、salary和age
schema = [('id', int), ('salary', int), ('age', int)]
# 3条记录
records = [(1, 3000, 21), (2, 5000, 30),
(3, 8000, 38)]
# 创建NumPy数组
a = np.array(records, dtype=schema)
# 将数组按照salary进行排序
np.sort(a, order='salary')
Out [35]: 
array([(1, 3000, 21), (2, 5000, 30), (3, 8000, 38)],
      dtype=[('id', '<i8'), ('salary', '<i8'), ('age', '<i8')])

本节主要介绍了NumPy的常用基础功能,使读者对NumPy有初步的了解。若想学习更多NumPy的使用技巧,可参考NumPy官方文档参见https://www.numpy.org/devdocs/user/index.html。

2.1.3 Pandas

Pandas是一个基于NumPy的开源软件包,可以方便快捷地进行数据处理和数据分析。Pandas功能十分强大,它集成了大量的库和数据模型,提供了很多数据操作的方法,基本可以满足绝大多数实际应用中的数据处理和分析需求。

Pandas主要实现了两个数据结构:Series和DataFrame。Series是一维数组,类似于NumPy中的一维数组,能够保存任何数据类型(如整型、字符串、浮点数和Python对象等)。Series中的数据通过索引进行标记,从而可以方便、高效地通过索引访问数据。DataFrame是一个二维数据结构,不同的列可以存放不同类型的数据,有点类似数据库中的表。此外,Pandas还提供了数据库用户都比较熟悉的数据操作方法。通过如下代码可引入Pandas包:

import pandas as pd

1.Series和DataFrame基本操作

Series由一组数据及其相应的索引组成,可以通过如下方式创建:

In [36]: 
import pandas as pd
s = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])
s
Out [36]: 
a    1
b    2
c    3
d    4
e    5
dtype: int64

也可通过Python中的dict来创建,如下:

In [37]: 
records = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
s = pd.Series(records)
s
Out [37]: 
a    1
b    2
c    3
d    4
e    5
dtype: int64

Series也可以像NumPy数组一样支持强大的索引和切片功能,代码如下:

In [38]: 
s[1:3]
Out [38]: 
b    2
c    3
dtype: int64
In [39]: 
s[(s > 1) & (s < 5)]
Out [39]: 
b    2
c    3
d    4
dtype: int64
In [40]: 
s[s > s.median()]
Out [40]: 
d    4
e    5
dtype: int64
In [41]: 
s['c']
Out [41]: 
3

DataFrame是Pandas中最常用的数据结构,它可以包含多个列,每一列可以是不同的数据类型,可将其看作电子表格、SQL表或Series对象的字典。通过如下方式创建DataFrame:

默认行索引是从0开始的正整数,也可以指定行索引:

然后即可通过行索引对数据进行选取:

In [44]: 
df1.loc[12]
Out [44]: 
a     2
b     6
c    10
Name: 12, dtype: int64
In [45]: 
df1.iloc[2]
Out [45]: 
a     3
b     7
c    11
Name: 13, dtype: int64

也可以采用类似dict的方式按列选取、设置和删除数据:

上述按列选取也可以通过df1.c来实现,结果是一样的。另外,还可以通过布尔值选取,例如:

2.算术运算和数据对齐

Pandas中的DataFrame支持丰富的算术运算,代码如下:

另外,NumPy中的一些函数也可直接应用于DataFrame,如exp、log等,代码如下:

除了单个DataFrame的运算之外,Pandas也支持多个DataFrame之间的算术运算:

此外,Pandas可以根据索引实现数据自动对齐,索引不重合的部分被置为NaN,代码如下:

3.统计与汇总数据

Pandas中的对象包含许多统计和汇总数据的方法,大多是聚合函数,如sum()、mean()等,如下所示:

也可以直接通过describe()函数计算各种统计信息:

4.数据排序

Pandas支持多种方式的排序,如按索引排序、按值排序等。通过sort_index()方法可实现按索引级别对Pandas对象(如Series、DataFrame等)进行排序。

通过sort_values()方法可实现Pandas对象按值排序。Series通过sort_values()方法对对象内的值进行排序,DataFrame通过该方法按列或行对DataFrame进行排序。

5.函数应用

Pandas支持通过apply()方法将自定义函数应用到DataFrame的行和列上:

也可以采用下面形式:

In [74]: 
def func1(df, a, b=1):
    return (df.max() - df.min() + a) * b
df.apply(func1, args=(2,), b=2)
Out [74]: 
b    6
c    12
a    16
d    6
dtype: int64

6.缺省值处理

在数据分析中,数据缺省的情况经常出现,在Pandas中以NaN表示数据缺省。Pandas提供了多种缺省值处理函数,可以通过isnull()、notnull()来判断数据是否缺省,这两个函数的返回值均为一个包含布尔值的对象,布尔值表示该元素是否为缺省。isnull()函数的返回值True表示缺省值,False表示非缺省值,notnull()函数则相反。

可通过dropna()方法丢弃包含缺省值的行或列,默认丢弃含有缺省值的行,也可通过指定参数只丢弃全为缺省值的行或列:

也可对缺省值进行填充处理:

7.时间序列

实际应用中经常需要对时间进行一系列处理,Pandas包含了许多关于时间序列的工具,使其非常适于处理时间序列。

In [83]: 
time = pd.Series(np.random.randn(8),
                 index =pd.date_range('2018-06-01', periods = 8))
time
Out [83]: 
2018-06-01    1.187882
2018-06-02    0.667788
2018-06-03   -1.098277
2018-06-04   -0.363420
2018-06-05   -0.257057
2018-06-06    0.543445
2018-06-07    2.671669
2018-06-08   -0.101492
Freq: D, dtype: float64

对于时间序列的数据,可灵活地通过时间范围对数据进行切片索引:

In [84]: 
time['2018-06-03']
Out [84]: 
-1.0982767096049197
In [85]: 
time['2018/06/03']
Out [85]: 
-1.0982767096049197
In [86]: 
time['2018-06-03':'2018-06-06']
Out [86]: 
2018-06-03   -1.098277
2018-06-04   -0.363420
2018-06-05   -0.257057
2018-06-06    0.543445
Freq: D, dtype: float64
In [87]: 
time['2018-06']
Out [87]: 
2018-06-01    1.187882
2018-06-02    0.667788
2018-06-03   -1.098277
2018-06-04   -0.363420
2018-06-05   -0.257057
2018-06-06    0.543445
2018-06-07    2.671669
2018-06-08   -0.101492
Freq: D, dtype: float64

对于带有重复索引的时间序列,可以通过groupby()对数据进行聚合:

In [88]: 
dates = pd.DatetimeIndex(['2018-06-06','2018-06-07',
                          '2018-06-07','2018-06-07',
                          '2018-06-08','2018-06-09'])
time = pd.Series(np.arange(6),index = dates)
time
Out [88]: 
2018-06-06    0
2018-06-07    1
2018-06-07    2
2018-06-07    3
2018-06-08    4
2018-06-09    5
dtype: int64
In [89]: 
time.groupby(level=0).sum()
Out [89]: 
2018-06-06    0
2018-06-07    6
2018-06-08    4
2018-06-09    5
dtype: int64

8.数据存取

Pandas支持多种文件形式的数据存储与读取,如csv、json、excel、parquet等。

2.1.4 Matplotlib

Matplotlib是一个强大的Python数据可视化库,可以方便地创建多种类型的图表。Matplotlib十分适用于交互式制图,也可以将其作为制图空间,嵌入GUI应用程序中。

1.导入Matplotlib

和NumPy、Pandas类似,可通过如下语句载入Matplotlib库:

import matplotlib.pyplot as plt

下面是一个简单的折线图绘制示例。

In [94]: 
import numpy as np
import matplotlib.pyplot as plt
x = np.array([1,2,3,4,5,6])
y = np.array([1,5,4,6,10,6])
# plot中参数x、y分别为横、纵坐标数据,b表示折线颜色为蓝色
plt.plot(x,y,'b')
plt.show()

输出结果如图2-5所示。

图2-5 输出结果(折线图)

2.设置坐标轴

创建一个简单的折线图后,下面介绍如何设置坐标轴。

In [95]: 
x = np.array([1,2,3,4,5,6])
y = x*3 +1

plt.plot(x, y)

# 设置x轴、y轴显示的范围
plt.xlim((3, 6)) 
plt.ylim((5, 35))

# 设置x轴、y轴的标签
plt.xlabel('x') 
plt.ylabel('y')

# 设置x轴、y轴的刻度
plt.xticks([3, 3.5, 4, 4.5, 5, 5.5, 6])      
plt.yticks([10, 15, 20, 25, 30, 35])
plt.show()

输出结果如图2-6所示。

图2-6 输出结果设置折线图的坐标轴

如图2-6所示,Matplotlib可通过xlim()和ylim()来设置x轴和y轴的显示范围,通过xlabel()、ylabel()设置x轴和y轴的标签名称,通过xticks()和yticks()设置x轴和y轴的刻度。此外,还可以设置x轴和y轴的位置:

In [96]: 
x = np.linspace(-6, 6, 50)

y = x**2

plt.plot(x, y, color='red') # 线条为红色

# 获取当前的坐标轴
ax = plt.gca()
# 设置右边框和上边框
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# 设置x坐标轴为下边框
ax.xaxis.set_ticks_position('bottom')
# 设置y坐标轴为左边框
ax.yaxis.set_ticks_position('left')
# 设置x轴、y轴在(0,0)的位置
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))
plt.show()

输出结果如图2-7所示。

图2-7 输出结果(设置x轴、y轴)

3.绘制多种类型的数据图

Matplotlib支持绘制多种类型的数据图,如直方图、散点图、饼状图、等高线图等。

(1)直方图

Matplotlib通过plt.hist()方法绘制直方图:

In [97]: 
mu ,sigma = 0, 1
sampleNum = 1024
np.random.seed(0)
s = np.random.normal(mu, sigma, sampleNum)

plt.xlim((-3, 3)) 
plt.ylim((0, 0.5))

plt.hist(s, bins=50, normed=True)
plt.show()

输出结果如图2-8所示。

(2)散点图

Matplotlib通过plt.scatter()方法绘制散点图:

In [98]: 
X = np.random.normal(0, 1, 1024) 
Y = np.random.normal(0, 1, 1024)
# 绘制散点图
plt.scatter(X, Y, s=75) 
plt.show()

图2-8 输出结果(直方图)

输出结果如图2-9所示。

图2-9 输出结果(散点图)

(3)饼状图

Matplotlib通过pie()方法绘制饼状图:

In [99]: 
X = [1,2,3,4]

# 饼状图中每个部分离中心点的距离,其中0.2表示图中远离中心的A部分
explode=(0.2,0,0,0)

plt.pie(X, 
    labels=['A','B','C','D'],
    explode=explode, 
    autopct='percent:%1.1f%%'  # 每个部分的比例标签
    )  

plt.axis('equal')  # 防止饼状图被压缩成椭圆
plt.show()

输出结果如图2-10所示。

图2-10 输出结果(饼状图)

(4)等高线图

Matplotlib通过contour()方法绘制等高线图:

In [100]: 
delta = 0.025
x = np.arange(-2.0, 2.0, delta)
y = np.arange(-1.5, 1.5, delta)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2
C = plt.contour(X, Y, Z)
plt.clabel(C, inline=True, fontsize=10) 
plt.show()

输出结果如图2-11所示。

图2-11 输出结果(等高线图)

(5)Figure对象

在Matplotlib中,Figure(图像)对象是整个绘图区域,可以包含一个或者多个axes,其中每个axes拥有自己的坐标系,是一个单独的绘图区域,axes可以在整个绘图区域任意摆放。用户可以通过subplot()来绘制多个子图,通过subplot()创建的子图只能按网格整齐排列。

In [101]: 
plt.figure()

# 绘制一个子图,其中row=2,col=2,该子图占第1个位置
plt.subplot(2, 2, 1)  
plt.plot([0, 1], [0, 1])

# 绘制一个子图,其中row=2,col=2,该子图占第2个位置
plt.subplot(2, 2, 2)
plt.plot([0, 1], [1, 0])

plt.subplot(2, 2, 3)
plt.plot([1, 2], [2, 1])

plt.subplot(2, 2, 4)
plt.plot([1, 2], [1, 2])

plt.show() 

输出结果如图2-12所示。

图2-12 输出结果(Figure对象)

2.1.5 scikit-learn

scikit-learn是一个包含大量经典机器学习模型的开源工具库,用Python实现,包括数据预处理、分类、回归、降维、模型选择等常用的机器学习算法,可见scikit-learn是一个功能十分强大的机器学习工具包。XGBoost配合scikit-learn使用,可以说是如虎添翼。因scikit-learn中的算法较多,在此不做一一介绍,后续示例用到时再进行针对性的讲解。关于scikit-learn的资料非常多,市面上也有很多介绍scikit-learn的书籍,想要深入了解的读者可以自行选择相关资料进行学习。