估算房屋价格
是时候用所学的知识来解决真实世界的问题了。让我们用这些原理来估算房屋价格。房屋估价是理解回归分析最经典的案例之一,通常是一个不错的切入点。它符合人们的直觉,而且与人们的生活息息相关,因此在用机器学习处理复杂事情之前,通过房屋估价可以更轻松地理解相关概念。我们将使用带AdaBoost算法的决策树回归器(decision tree regressor)来解决这个问题。
准备工作
决策树是一个树状模型,每个节点都做出一个决策,从而影响最终结果。叶子节点表示输出数值,分支表示根据输入特征做出的中间决策。AdaBoost算法是指自适应增强(adaptive boosting)算法,这是一种利用其他系统增强模型准确性的技术。这种技术是将不同版本的算法结果进行组合,用加权汇总的方式获得最终结果,被称为弱学习器(weak learners)。AdaBoost算法在每个阶段获取的信息都会反馈到模型中,这样学习器就可以在后一阶段重点训练难以分类的样本。这种学习方式可以增强系统的准确性。
首先使用AdaBoost算法对数据集进行回归拟合,再计算误差,然后根据误差评估结果,用同样的数据集重新拟合。可以把这些看作是回归器的调优过程,直到达到预期的准确性。假设你拥有一个包含影响房价的各种参数的数据集,我们的目标就是估计这些参数与房价的关系,这样就可以根据未知参数估计房价了。
详细步骤
(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。
计算特征的相对重要性
所有特征都同等重要吗?在这个案例中,我们用了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,看看能从决策树回归器中获得什么。
(4) 从图1-8可以发现,不带AdaBoost算法的决策树回归器显示的最重要特征是RM。再看看带AdaBoost算法的决策树回归器的特征重要性排序条形图,如图1-9所示。
加入AdaBoost算法之后,房屋估价模型的最重要特征是LSTAT。在现实生活中,如果对这个数据集建立不同的回归器,就会发现最重要的特征是LSTAT,这足以体现AdaBoost算法对决策树回归器训练效果的改善。
评估共享单车的需求分布
用一种新的回归方法解决共享单车的需求分布问题。我们采用随机森林回归器(random forest regressor)估计输出结果。随机森林是一个决策树集合,它基本上就是用一组由数据集的若干子集构建的决策树构成,再用决策树平均值改善整体学习效果。
准备工作
我们将使用bike_day.csv文件中的数据集,它可以在 https://archive.ics.uci.edu/ml/datasets/Bike+Sharing+Dataset 获取。这份数据集一共16列,前两列是序列号与日期,分析的时候可以不用;最后三列数据是不同类型的输出结果;最后一列是第十四列与第十五列的和,因此建立模型时可以不考虑第十四列与第十五列。
详细步骤
接下来看看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所示的图形。
看来温度(temp)是影响自行车租赁的最重要因素。
更多内容
把第十四列与第十五列数据加入数据集,看看结果有什么区别。在新的特征重要性条形图中,除了这两个特征外,其他特征都变成了0。这是由于输出结果可以通过简单地对第十四列与第十五列数据求和得出,因此算法不需要其他特征计算结果。在数据集加载函数load_dataset中,我们需要对for循环内的取值范围稍作调整:
X.append(row[2:15])
现在再画出特征重要性条形图,可以看到如图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中显示,最重要的特征是一天中的不同时点(hr),这也完全符合人们的直觉;其次重要的是温度,与我们之前分析的结果一致。