第 1 章 监督学习

第 1 章 监督学习

在这一章,我们将介绍以下主题:

  • 数据预处理技术

  • 标记编码方法

  • 创建线性回归器(linear regressor)

  • 计算回归准确性

  • 保存模型数据

  • 创建岭回归器(ridge regressor)

  • 创建多项式回归器(polynomial regressor)

  • 估算房屋价格

  • 计算特征的相对重要性

  • 评估共享单车的需求分布

1.1 简介

如果你熟悉机器学习的基础知识,那么肯定知道什么是监督学习。监督学习是指在有标记的样本(labeled samples)上建立机器学习的模型。例如,如果用尺寸、位置等不同参数建立一套模型来评估一栋房子的价格,那么首先需要创建一个数据库,然后为参数打上标记。我们需要告诉算法,什么样的参数(尺寸、位置)对应什么样的价格。有了这些带标记的数据,算法就可以学会如何根据输入的参数计算房价了。

无监督学习与刚才说的恰好相反,它面对的是没有标记的数据。假设需要把一些数据分成不同的组别,但是对分组的条件毫不知情,于是,无监督学习算法就会以最合理的方式将数据集分成确定数量的组别。我们将在后面章节介绍无监督学习。

建立书中的各种模型时,将使用许多Python程序包,像NumPy、SciPy、scikit-learn、matplotlib等。如果你使用Windows系统,推荐安装兼容SciPy关联程序包的Python发行版,网址为http://www.scipy.org/install.html,这些Python发行版里已经集成了常用的程序包。如果你使用Mac OS X或者Ubuntu系统,安装这些程序包就相当简单了。下面列出来程序包安装和使用文档的链接:

现在,请确保你的计算机已经安装了所有程序包。

1.2 数据预处理技术

在真实世界中,经常需要处理大量的原始数据,这些原始数据是机器学习算法无法理解的。为了让机器学习算法理解原始数据,需要对数据进行预处理。

1.2.1 准备工作

来看看Python是如何对数据进行预处理的。首先,用你最喜欢的文本编辑器打开一个扩展名为.py的文件,例如preprocessor.py。然后在文件里加入下面两行代码:

import numpy as np
from sklearn import preprocessing

我们只是加入了两个必要的程序包。接下来创建一些样本数据。向文件中添加下面这行代码:

data = np.array([[3,  -1.5,    2,  -5.4], [0,    4,    -0.3,  2.1], [1,    3.3,
-1.9, -4.3]])

现在就可以对数据进行预处理了。

1.2.2 详细步骤

数据可以通过许多技术进行预处理,接下来将介绍一些最常用的预处理技术。

  1. 均值移除(Mean removal)

    通常我们会把每个特征的平均值移除,以保证特征均值为0(即标准化处理)。这样做可以消除特征彼此间的偏差(bias)。将下面几行代码加入之前打开的Python文件中:

    data_standardized = preprocessing.scale(data)
    print "\nMean =", data_standardized.mean(axis=0)
    print "Std deviation =", data_standardized.std(axis=0)
    
    

    现在来运行代码。打开命令行工具,然后输入以下命令:

    $ python preprocessor.py
    
    

    命令行工具中将显示以下结果:

    Mean = [  5.55111512e-17  -1.11022302e-16  -7.40148683e-17  -7.40148683e-17]
    Std deviation = [ 1.  1.  1.  1.]
    
    

    你会发现特征均值几乎是0,而且标准差为1

  2. 范围缩放(Scaling)

    数据点中每个特征的数值范围可能变化很大,因此,有时将特征的数值范围缩放到合理的大小是非常重要的。在Python文件中加入下面几行代码,然后运行程序:

    data_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
    data_scaled = data_scaler.fit_transform(data)
    print "\nMin max scaled data =", data_scaled
    
    

    范围缩放之后,所有数据点的特征数值都位于指定的数值范围内。输出结果如下所示:

    Min max scaled data:
    [[ 1.            0.            1.            0.        ]
     [ 0.            1.            0.41025641    1.        ]
     [ 0.33333333    0.87272727    0.            0.14666667]]
    
  3. 归一化(Normalization)

    数据归一化用于需要对特征向量的值进行调整时,以保证每个特征向量的值都缩放到相同的数值范围。机器学习中最常用的归一化形式就是将特征向量调整为L1范数,使特征向量的数值之和为1。增加下面两行代码到前面的Python文件中:

    data_normalized = preprocessing.normalize(data, norm='l1')
    print "\nL1 normalized data =", data_normalized
    
    

    执行Python文件,就可以看到下面的结果:

    L1    normalized    data:
    [[    0.25210084    -0.12605042    0.16806723    -0.45378151]
     [    0.             0.625        -0.046875       0.328125  ]
     [    0.0952381      0.31428571   -0.18095238    -0.40952381]]
    
    

    这个方法经常用于确保数据点没有因为特征的基本性质而产生较大差异,即确保数据处于同一数量级,提高不同特征数据的可比性。

  4. 二值化(Binarization)

    二值化用于将数值特征向量转换为布尔类型向量。增加下面两行代码到前面的Python文件中:

    data_binarized = preprocessing.Binarizer(threshold=1.4).transform(data)
    print "\nBinarized data =", data_binarized
    
    

    再次执行Python文件,就可以看到下面的结果:

    Binarized data:
    [[    1.    0.    1.    0.]
     [    0.    1.    0.    1.]
     [    0.    1.    0.    0.]]
    
    

    如果事先已经对数据有了一定的了解,就会发现使用这个技术的好处了。

  5. 独热编码

    通常,需要处理的数值都是稀疏地、散乱地分布在空间中,然而,我们并不需要存储这些大数值,这时就需要使用独热编码(One-Hot Encoding)。可以把独热编码看作是一种收紧(tighten)特征向量的工具。它把特征向量的每个特征与特征的非重复总数相对应,通过one-of-k 的形式对每个值进行编码。特征向量的每个特征值都按照这种方式编码,这样可以更加有效地表示空间。例如,我们需要处理4维向量空间,当给一个特性向量的第n 个特征进行编码时,编码器会遍历每个特征向量的第n 个特征,然后进行非重复计数。如果非重复计数的值是K,那么就把这个特征转换为只有一个值是1其他值都是0的K 维向量。增加下面几行代码到前面的Python文件中:

    encoder = preprocessing.OneHotEncoder()
    encoder.fit([[0, 2, 1, 12], [1, 3, 5, 3], [2, 3, 2, 12], [1, 2, 4, 3]])
    encoded_vector = encoder.transform([[2, 3, 5, 3]]).toarray()
    print "\nEncoded vector =", encoded_vector
    
    

    结果如下所示:

    Encoded vector:
    [[ 0.  0.  1.  0.  1.  0.  0.  0.  1.  1.  0.]]
    
    

    在上面的示例中,观察一下每个特征向量的第三个特征,分别是1524这4个不重复的值,也就是说独热编码向量的长度是4。如果你需要对5进行编码,那么向量就是[0, 1, 0, 0]。向量中只有一个值是1。第二个元素是1,对应的值是5

1.3 标记编码方法

在监督学习中,经常需要处理各种各样的标记。这些标记可能是数字,也可能是单词。如果标记是数字,那么算法可以直接使用它们,但是,许多情况下,标记都需要以人们可理解的形式存在,因此,人们通常会用单词标记训练数据集。标记编码就是要把单词标记转换成数值形式,让算法懂得如何操作标记。接下来看看如何标记编码。

详细步骤

(1) 新建一个Python文件,然后导入preprocessing程序包:

from sklearn import preprocessing

(2) 这个程序包包含许多数据预处理需要的函数。定义一个标记编码器(label encoder),代码如下所示:

label_encoder = preprocessing.LabelEncoder()

(3) label_encoder对象知道如何理解单词标记。接下来创建一些标记:

input_classes = ['audi', 'ford', 'audi', 'toyota', 'ford', 'bmw']

(4) 现在就可以为这些标记编码了:

label_encoder.fit(input_classes)
print "\nClass mapping:"
for i, item in enumerate(label_encoder.classes_):
    print item, '-->', i

(5) 运行代码,命令行工具中显示下面的结果:

Class mapping:
audi --> 0
bmw --> 1
ford --> 2
toyota --> 3

(6) 就像前面结果显示的那样,单词被转换成从0开始的索引值。现在,如果你遇到一组标记,就可以非常轻松地转换它们了,如下所示:

labels = ['toyota', 'ford', 'audi']
encoded_labels = label_encoder.transform(labels)
print "\nLabels =", labels
print "Encoded labels =", list(encoded_labels)

命令行工具中将显示下面的结果:

Labels = ['toyota', 'ford', 'audi']
Encoded labels = [3, 2, 0]

(7) 这种方式比纯手工进行单词与数字的编码要简单许多。还可以通过数字反转回单词的功能检查结果的正确性:

encoded_labels = [2, 1, 0, 3, 1]
decoded_labels = label_encoder.inverse_transform(encoded_labels)
print "\nEncoded labels =", encoded_labels
print "Decoded labels =", list(decoded_labels)

结果如下所示:

Encoded labels = [2, 1, 0, 3, 1]
Decoded labels = ['ford', 'bmw', 'audi', 'toyota', 'bmw']

可以看到,映射结果是完全正确的。

1.4 创建线性回归器

回归是估计输入数据与连续值输出数据之间关系的过程。数据通常是实数形式的,我们的目标是估计满足输入到输出映射关系的基本函数。让我们从一个简单的示例开始。考虑下面的输入与输出映射关系:

1 → 2

3 → 6

4.3 → 8.6

7.1 → 14.2

如果要你估计输入与输出的关联关系,你可以通过模式匹配轻松地找到结果。我们发现输出结果一直是输入数据的两倍,因此输入与输出的转换公式就是这样:

f(x) = 2x

这是体现输入值与输出值关联关系的一个简单函数。但是,在真实世界中通常都不会这么简单,输入与输出的映射关系函数并不是一眼就可以看出来的。

1.4.1 准备工作

线性回归用输入变量的线性组合来估计基本函数。前面的示例就是一种单输入单输出变量的线性回归。

现在考虑如图1-1所示的情况。

{%}

图 1-1

线性回归的目标是提取输入变量与输出变量的关联线性模型,这就要求实际输出与线性方程预测的输出的残差平方和(sum of squares of differences)最小化。这种方法被称为普通最小二乘法(Ordinary Least Squares,OLS)。

你可能觉得用一条曲线对这些点进行拟合效果会更好,但是线性回归不允许这样做。线性回归的主要优点就是方程简单。如果你想用非线性回归,可能会得到更准确的模型,但是拟合速度会慢很多。线性回归模型就像前面那张图里显示的,用一条直线近似数据点的趋势。接下来看看如何用Python建立线性回归模型。

1.4.2 详细步骤

假设你已经创建了数据文件data_singlevar.txt,文件里用逗号分隔符分割字段,第一个字段是输入值,第二个字段是与逗号前面的输入值相对应的输出值。你可以用这个文件作为输入参数。

(1) 创建一个Python文件regressor.py,然后在里面增加下面几行代码:

import sys
import numpy as np
filename = sys.argv[1]
X = []
y = []
with open(filename, 'r') as f:
    for line in f.readlines():
        xt, yt = [float(i) for i in line.split(',')]
        X.append(xt)
        y.append(yt)

把输入数据加载到变量Xy,其中X是数据,y是标记。在代码的for循环体中,我们解析每行数据,用逗号分割字段。然后,把字段转化为浮点数,并分别保存到变量Xy中。

(2) 建立机器学习模型时,需要用一种方法来验证模型,检查模型是否达到一定的满意度(satisfactory level)。为了实现这个方法,把数据分成两组:训练数据集(training dataset)与测试数据集(testing dataset)。训练数据集用来建立模型,测试数据集用来验证模型对未知数据的学习效果。因此,先把数据分成训练数据集与测试数据集:

num_training = int(0.8 * len(X))
num_test = len(X) - num_training

# 训练数据
X_train = np.array(X[:num_training]).reshape((num_training,1))
y_train = np.array(y[:num_training])

# 测试数据
X_test = np.array(X[num_training:]).reshape((num_test,1))
y_test = np.array(y[num_training:])

这里用80%的数据作为训练数据集,其余20%的数据作为测试数据集。

(3) 现在已经准备好训练模型。接下来创建一个回归器对象,代码如下所示:

from sklearn import linear_model

# 创建线性回归对象
linear_regressor = linear_model.LinearRegression()

# 用训练数据集训练模型
linear_regressor.fit(X_train, y_train)

(4) 我们利用训练数据集训练了线性回归器。向fit方法提供输入数据即可训练模型。用下面的代码看看它如何拟合:

import matplotlib.pyplot as plt

y_train_pred = linear_regressor.predict(X_train)
plt.figure()
plt.scatter(X_train, y_train, color='green')
plt.plot(X_train, y_train_pred, color='black', linewidth=4)
plt.title('Training data')
plt.show()

(5) 在命令行工具中执行如下命令:

$ python regressor.py data_singlevar.txt

就会看到如图1-2所示的线性回归。

图 1-2

(6) 在前面的代码中,我们用训练的模型预测了训练数据的输出结果,但这并不能说明模型对未知的数据也适用,因为我们只是在训练数据上运行模型。这只能体现模型对训练数据的拟合效果。从图1-2中可以看到,模型训练的效果很好。

(7) 接下来用模型对测试数据集进行预测,然后画出来看看,代码如下所示:

y_test_pred = linear_regressor.predict(X_test)

plt.scatter(X_test, y_test, color='green')
plt.plot(X_test, y_test_pred, color='black', linewidth=4)
plt.title('Test data')
plt.show()

运行代码,可以看到如图1-3所示的线性回归。

图 1-3

1.5 计算回归准确性

现在已经建立了回归器,接下来最重要的就是如何评价回归器的拟合效果。在模型评价的相关内容中,用误差(error)表示实际值与模型预测值之间的差值。

1.5.1 准备工作

下面快速了解几个衡量回归器拟合效果的重要指标(metric)。回归器可以用许多不同的指标进行衡量,部分指标如下所示。

  • 平均绝对误差(mean absolute error):这是给定数据集的所有数据点的绝对误差平均值。

  • 均方误差(mean squared error):这是给定数据集的所有数据点的误差的平方的平均值。这是最流行的指标之一。

  • 中位数绝对误差(median absolute error):这是给定数据集的所有数据点的误差的中位数。这个指标的主要优点是可以消除异常值(outlier)的干扰。测试数据集中的单个坏点不会影响整个误差指标,均值误差指标会受到异常点的影响。

  • 解释方差分(explained variance score):这个分数用于衡量我们的模型对数据集波动的解释能力。如果得分1.0分,那么表明我们的模型是完美的。

  • R方得分(R2 score):这个指标读作“R方”,是指确定性相关系数,用于衡量模型对未知样本预测的效果。最好的得分是1.0,值也可以是负数。

1.5.2 详细步骤

scikit-learn里面有一个模块,提供了计算所有指标的功能。重新打开一个Python文件,然后输入以下代码:

import sklearn.metrics as sm

print "Mean absolute error =", round(sm.mean_absolute_error(y_test, y_test_pred), 2)
print "Mean squared error =", round(sm.mean_squared_error(y_test, y_ test_pred), 2)
print "Median absolute error =", round(sm.median_absolute_error(y_ test, y_test_pred), 2)
print "Explained variance score =", round(sm.explained_variance_ score(y_test, y_test_pred), 2)
print "R2 score =", round(sm.r2_score(y_test, y_test_pred), 2)

每个指标都描述得面面俱到是非常乏味的,因此只选择一两个指标来评估我们的模型。通常的做法是尽量保证均方误差最低,而且解释方差分最高。

1.6 保存模型数据

模型训练结束之后,如果能够把模型保存成文件,那么下次再使用的时候,只要简单地加载就可以了。

详细步骤

用程序保存模型的具体操作步骤如下。

(1) 在Python文件regressor.py中加入以下代码:

import cPickle as pickle

output_model_file = 'saved_model.pkl'
with open(output_model_file, 'w') as f:
    pickle.dump(linear_regressor, f)

(2) 回归模型会保存在saved_model.pkl文件中。下面看看如何加载并使用它,代码如下所示:

with open(output_model_file, 'r') as f:
    model_linregr = pickle.load(f)

y_test_pred_new = model_linregr.predict(X_test)
print "\nNew mean absolute error =", round(sm.mean_absolute_ error(y_test, y_test_pred_new), 2)

(3) 这里只是把回归模型从Pickle文件加载到model_linregr变量中。你可以将打印结果与前面的结果进行对比,确认模型与之前的一样。

1.7 创建岭回归器

线性回归的主要问题是对异常值敏感。在真实世界的数据收集过程中,经常会遇到错误的度量结果。而线性回归使用的普通最小二乘法,其目标是使平方误差最小化。这时,由于异常值误差的绝对值很大,因此会引起问题,从而破坏整个模型。

1.7.1 准备工作

先看图1-4。

{%}

图 1-4

右下角的两个数据点明显是异常值,但是这个模型需要拟合所有的数据点,因此导致整个模型都错了。仅凭直觉观察,我们就会觉得如图1-5的拟合结果更好。

{%}

图 1-5

普通最小二乘法在建模时会考虑每个数据点的影响,因此,最终模型就会像图1-4显示的直线那样。显然,我们发现这个模型不是最优的。为了避免这个问题,我们引入正则化项的系数作为阈值来消除异常值的影响。这个方法被称为岭回归

1.7.2 详细步骤

接下来看看如何用Python建立岭回归器。

(1) 你可以从data_multi_variable.txt文件中加载数据。这个文件的每一行都包含多个数值。除了最后一个数值外,前面的所有数值构成输入特征向量。

(2) 把下面的代码加入regressor.py文件中。我们用一些参数初始化岭回归器:

ridge_regressor = linear_model.Ridge(alpha=0.01, fit_ intercept=True, max_iter=10000)

(3) alpha参数控制回归器的复杂程度。当alpha趋于0时,岭回归器就是用普通最小二乘法的线性回归器。因此,如果你希望模型对异常值不那么敏感,就需要设置一个较大的alpha值。这里把alpha值设置为0.01

(4) 下面让我们来训练岭回归器。

ridge_regressor.fit(X_train, y_train)
y_test_pred_ridge = ridge_regressor.predict(X_test)
print "Mean absolute error =", round(sm.mean_absolute_error
    (y_ test, y_test_pred_ridge), 2)
print "Mean squared error =", round(sm.mean_squared_error
    (y_test, y_test_pred_ridge), 2)
print "Median absolute error =", round(sm.median_absolute_error
    (y_ test, y_test_pred_ridge), 2)
print "Explain variance score =", round(sm.explained_variance_ score
    (y_test, y_test_pred_ridge), 2)
print "R2 score =", round(sm.r2_score(y_test, y_test_pred_ridge), 2)

运行代码检查误差指标。可以用同样的数据建立一个线性回归器,并与岭回归器的结果进行比较,看看把正则化引入回归模型之后的效果如何。

1.8 创建多项式回归器

线性回归模型有一个主要的局限性,那就是它只能把输入数据拟合成直线,而多项式回归模型通过拟合多项式方程来克服这类问题,从而提高模型的准确性。

1.8.1 准备工作

先看图1-6。

{%}

图 1-6

从图1-6中可以看到,数据点本身的模式中带有自然的曲线,而线性模型是不能捕捉到这一点的。再来看看多项式模型的效果,如图1-7所示。

{%}

图 1-7

图1-7中的虚线表示线性回归模型,实线表示多项式回归模型。这个模型的曲率是由多项式的次数决定的。随着模型曲率的增加,模型变得更准确。但是,增加曲率的同时也增加了模型的复杂性,因此拟合速度会变慢。当我们对模型的准确性的理想追求与计算能力限制的残酷现实发生冲突时,就需要综合考虑了。

1.8.2 详细步骤

(1) 将下面的代码加入Python文件regressor.py中:

from sklearn.preprocessing import PolynomialFeatures

polynomial = PolynomialFeatures(degree=3)

(2) 上一行将曲线的多项式的次数的初始值设置为3。下面用数据点来计算多项式的参数:

X_train_transformed = polynomial.fit_transform(X_train)

其中,X_train_transformed表示多项式形式的输入,与线性回归模型是一样大的。

(3) 接下来用文件中的第一个数据点来检查多项式模型是否能够准确预测:

datapoint = [0.39,2.78,7.11]
poly_datapoint = polynomial.fit_transform(datapoint)

poly_linear_model = linear_model.LinearRegression()
poly_linear_model.fit(X_train_transformed, y_train)
print "\nLinear regression:", linear_regressor.predict(datapoint) [0]
print "\nPolynomial regression:", poly_linear_model.predict(poly_datapoint)[0]

多项式回归模型计算变量数据点的值恰好就是输入数据文件中的第一行数据值。再用线性回归模型测试一下,唯一的差别就是展示数据的形式。运行代码,可以看到下面的结果:

Linear regression: -11.0587294983
Polynomial regression: -10.9480782122

可以发现,多项式回归模型的预测值更接近实际的输出值。如果想要数据更接近实际输出值,就需要增加多项式的次数。

(4) 将多项式的次数加到10看看结果:

polynomial = PolynomialFeatures(degree=10)

可以看到下面的结果:

Polynomial regression: -8.20472183853

现在,你可以发现预测值与实际的输出值非常地接近。

1.9 估算房屋价格

是时候用所学的知识来解决真实世界的问题了。让我们用这些原理来估算房屋价格。房屋估价是理解回归分析最经典的案例之一,通常是一个不错的切入点。它符合人们的直觉,而且与人们的生活息息相关,因此在用机器学习处理复杂事情之前,通过房屋估价可以更轻松地理解相关概念。我们将使用带AdaBoost算法的决策树回归器(decision tree regressor)来解决这个问题。

1.9.1 准备工作

决策树是一个树状模型,每个节点都做出一个决策,从而影响最终结果。叶子节点表示输出数值,分支表示根据输入特征做出的中间决策。AdaBoost算法是指自适应增强(adaptive boosting)算法,这是一种利用其他系统增强模型准确性的技术。这种技术是将不同版本的算法结果进行组合,用加权汇总的方式获得最终结果,被称为弱学习器(weak learners)。AdaBoost算法在每个阶段获取的信息都会反馈到模型中,这样学习器就可以在后一阶段重点训练难以分类的样本。这种学习方式可以增强系统的准确性。

首先使用AdaBoost算法对数据集进行回归拟合,再计算误差,然后根据误差评估结果,用同样的数据集重新拟合。可以把这些看作是回归器的调优过程,直到达到预期的准确性。假设你拥有一个包含影响房价的各种参数的数据集,我们的目标就是估计这些参数与房价的关系,这样就可以根据未知参数估计房价了。

1.9.2 详细步骤

(1) 创建一个新的Python文件housing.py,然后加入下面的代码:

import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor
from sklearn import datasets
from sklearn.metrics import mean_squared_error, explained_variance_score
from sklearn.utils import shuffle
import matplotlib.pyplot as plt

(2) 网上有一个标准房屋价格数据库,人们经常用它来研究机器学习。你可以在https://archive.ics.uci.edu/ml/datasets/Housing下载数据。不过scikit-learn提供了数据接口,可以直接通过下面的代码加载数据:

housing_data = datasets.load_boston()

每个数据点由影响房价的13个输入参数构成。你可以用housing_data.data获取输入的数据,用housing_data.target获取对应的房屋价格。

(3) 接下来把输入数据与输出结果分成不同的变量。我们可以通过shuffle函数把数据的顺序打乱:

X, y = shuffle(housing_data.data, housing_data.target, random_ state=7)

(4) 参数random_state用来控制如何打乱数据,让我们可以重新生成结果。接下来把数据分成训练数据集和测试数据集,其中80%的数据用于训练,剩余20%的数据用于测试:

num_training = int(0.8 * len(X))
X_train, y_train = X[:num_training], y[:num_training]
X_test, y_test = X[num_training:], y[num_training:]

(5) 现在已经可以拟合一个决策树回归模型了。选一个最大深度为4的决策树,这样可以限制决策树不变成任意深度:

dt_regressor = DecisionTreeRegressor(max_depth=4)
dt_regressor.fit(X_train, y_train)

(6) 再用带AdaBoost算法的决策树回归模型进行拟合:

ab_regressor =AdaBoostRegressor(DecisionTreeRegressor(max_depth=4), n_estimators=400, random_state=7)
ab_regressor.fit(X_train, y_train)

这样可以帮助我们对比训练效果,看看AdaBoost算法对决策树回归器的训练效果有多大改善。

(7) 接下来评价决策树回归器的训练效果:

y_pred_dt = dt_regressor.predict(X_test)
mse = mean_squared_error(y_test, y_pred_dt)
evs = explained_variance_score(y_test, y_pred_dt)
print "\n#### Decision Tree performance ####"
print "Mean squared error =", round(mse, 2)
print "Explained variance score =", round(evs, 2)

(8) 现在评价一下AdaBoost算法改善的效果:

y_pred_ab = ab_regressor.predict(X_test)
mse = mean_squared_error(y_test, y_pred_ab)
evs = explained_variance_score(y_test, y_pred_ab)
print "\n#### AdaBoost performance ####"
print "Mean squared error =", round(mse, 2)
print "Explained variance score =", round(evs, 2)

(9) 命令行工具显示的输出结果如下所示:

#### 决策树学习效果 ####
Mean squared error = 14.79
Explained variance score = 0.82

#### AdaBoost算法改善效果 ####
Mean squared error = 7.54
Explained variance score = 0.91

前面的结果表明,AdaBoost算法可以让误差更小,且解释方差分更接近1。

1.10 计算特征的相对重要性

所有特征都同等重要吗?在这个案例中,我们用了13个特征,它们对模型都有贡献。但是,有一个重要的问题出现了:如何判断哪个特征更加重要?显然,所有的特征对结果的贡献是不一样的。如果需要忽略一些特征,就需要知道哪些特征不太重要。scikit-learn里面有这样的功能。

详细步骤

(1) 画出特征的相对重要性,在housing.py文件中加入下面几行代码:

plot_feature_importances(dt_regressor.feature_importances_,
        'Decision Tree regressor', housing_data.feature_names)
plot_feature_importances(ab_regressor.feature_importances_,
        'AdaBoost regressor', housing_data.feature_names)

回归器对象有一个feature_importances_方法会告诉我们每个特征的相对重要性。

(2) 接下来需要定义plot_feature_importances来画出条形图:

def plot_feature_importances(feature_importances, title, feature_names):
    # 将重要性值标准化
    feature_importances = 100.0 * (feature_importances / max(feature_importances))

    # 将得分从高到低排序
    index_sorted = np.flipud(np.argsort(feature_importances))

    # 让X坐标轴上的标签居中显示
    pos = np.arange(index_sorted.shape[0]) + 0.5

    # 画条形图
    plt.figure()
    plt.bar(pos, feature_importances[index_sorted], align='center')
    plt.xticks(pos, feature_names[index_sorted])
    plt.ylabel('Relative Importance')
    plt.title(title)
    plt.show()

(3) 我们从feature_importances_方法里取值,然后把数值放大到0~100的范围内。运行前面的代码,可以看到两张图(不带AdaBoost算法与带AdaBoost算法两种模型)。仔细观察图1-8和图1-9,看看能从决策树回归器中获得什么。

{%}

图 1-8

(4) 从图1-8可以发现,不带AdaBoost算法的决策树回归器显示的最重要特征是RM。再看看带AdaBoost算法的决策树回归器的特征重要性排序条形图,如图1-9所示。

{%}

图 1-9

加入AdaBoost算法之后,房屋估价模型的最重要特征是LSTAT。在现实生活中,如果对这个数据集建立不同的回归器,就会发现最重要的特征是LSTAT,这足以体现AdaBoost算法对决策树回归器训练效果的改善。

1.11 评估共享单车的需求分布

本节将用一种新的回归方法解决共享单车的需求分布问题。我们采用随机森林回归器(random forest regressor)估计输出结果。随机森林是一个决策树集合,它基本上就是用一组由数据集的若干子集构建的决策树构成,再用决策树平均值改善整体学习效果。

1.11.1 准备工作

我们将使用bike_day.csv文件中的数据集,它可以在 https://archive.ics.uci.edu/ml/datasets/Bike+Sharing+Dataset 获取。这份数据集一共16列,前两列是序列号与日期,分析的时候可以不用;最后三列数据是不同类型的输出结果;最后一列是第十四列与第十五列的和,因此建立模型时可以不考虑第十四列与第十五列。

1.11.2 详细步骤

接下来看看Python如何解决这个问题。如果你下载了本书源代码,就可以看到bike_sharing.py文件里已经包含了完整代码。这里将介绍若干重要的部分。

(1) 首先导入一些新的程序包,如下:

import csv
from sklearn.ensemble import RandomForestRegressor
from housing import plot_feature_importances

(2) 我们需要处理CSV文件,因此加入了csv程序包来读取CSV文件。由于这是一个全新的数据集,因此需要自己定义一个数据集加载函数:

def load_dataset(filename):
    file_reader = csv.reader(open(filename, 'rb'), delimiter=',')
    X, y = [], []
    for row in file_reader:
        X.append(row[2:13])
        y.append(row[-1])

    # 提取特征名称
    feature_names = np.array(X[0])

    # 将第一行特征名称移除,仅保留数值
    return np.array(X[1:]).astype(np.float32), np.array(y[1:]).astype(np.float32), feature_names

在这个函数中,我们从CSV文件读取了所有数据。把数据显示在图形中时,特征名称非常有用。把特征名称数据从输入数值中分离出来,并作为函数返回值。

(3) 读取数据,并打乱数据顺序,让新数据与原来文件中数据排列的顺序没有关联性:

X, y, feature_names = load_dataset(sys.argv[1])
X, y = shuffle(X, y, random_state=7)

(4) 和之前的做法一样,需要将数据分成训练数据和测试数据。这一次,我们将90%的数据用于训练,剩余10%的数据用于测试:

num_training = int(0.9 * len(X))
X_train, y_train = X[:num_training], y[:num_training]
X_test, y_test = X[num_training:], y[num_training:]

(5) 下面开始训练回归器:

rf_regressor = RandomForestRegressor(n_estimators=1000, max_depth=10, min_samples_split=1)
rf_regressor.fit(X_train, y_train)

其中,参数n_estimators是指评估器(estimator)的数量,表示随机森林需要使用的决策树数量;参数max_depth是指每个决策树的最大深度;参数min_samples_split是指决策树分裂一个节点需要用到的最小数据样本量。

(6) 评价随机森林回归器的训练效果:

y_pred = rf_regressor.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
evs = explained_variance_score(y_test, y_pred)
print "\n#### Random Forest regressor performance ####"
print "Mean squared error =", round(mse, 2)
print "Explained variance score =", round(evs, 2)

(7) 由于已经有画出特征重要性条形图的函数plot_feature_importances了,接下来直接调用它:

plot_feature_importances(rf_regressor.feature_importances_, 'Random Forest regressor', feature_names)

执行代码,可以看到如图1-10所示的图形。

{%}

图 1-10

看来温度(temp)是影响自行车租赁的最重要因素。

1.11.3 更多内容

把第十四列与第十五列数据加入数据集,看看结果有什么区别。在新的特征重要性条形图中,除了这两个特征外,其他特征都变成了0。这是由于输出结果可以通过简单地对第十四列与第十五列数据求和得出,因此算法不需要其他特征计算结果。在数据集加载函数load_dataset中,我们需要对for循环内的取值范围稍作调整:

X.append(row[2:15])

现在再画出特征重要性条形图,可以看到如图1-11所示的柱形图。

{%}

图 1-11

与预想的一样,从图中可以看出,只有这两个特征是重要的,这确实也符合常理,因为最终结果仅仅是这两个特征相加得到的。因此,这两个变量与输出结果有直接的关系,回归器也就认为它不需要其他特征来预测结果了。在消除数据集冗余变量方面,这是非常有用的工具。

还有一份按小时统计的自行车共享数据bike_hour.csv。我们需要用到第3~14列,因此先对数据集加载函数load_dataset做一点调整:

X.append(row[2:14])

运行代码,可以看到回归器的训练结果如下:

#### 随机森林学习效果 ####
Mean squared error = 2619.87
Explained variance score = 0.92

特征重要性条形图如图1-12所示。

{%}

图 1-12

图1-12中显示,最重要的特征是一天中的不同时点(hr),这也完全符合人们的直觉;其次重要的是温度,与我们之前分析的结果一致。

目录

  • 版权声明
  • 译者序
  • 前言
  • 第 1 章 监督学习
  • 第 2 章 创建分类器
  • 第 3 章 预测建模
  • 第 4 章 无监督学习——聚类
  • 第 5 章 构建推荐引擎
  • 第 6 章 分析文本数据
  • 第 7 章 语音识别
  • 第 8 章 解剖时间序列和时序数据
  • 第 9 章 图像内容分析
  • 第 10 章 人脸识别
  • 第 11 章 深度神经网络
  • 第 12 章 可视化数据