机器学习纳米学位

模型评估与验证

项目: 预测波士顿房价

欢迎来到机器学习纳米学位的第一个实战项目!在这个 notebook 中,我们已经为你提供了一些代码模板,你将需要补充函数来顺利完成项目。除非有明确要求,你无须修改任何已给出的代码。以 “实现” 为开头的部分意味着你需要在之后的大括号中提供额外的函数。每一部分都会有详细的指导,需要实现的部分也会以 “TODO” 标出。请仔细阅读所有的提示!

除了实现代码外,你还必须回答一些与项目和实现有关的问题。每一个需要你回答的问题都会以 “问题 X” 为标题。请仔细阅读每个问题,并且在以 “回答:” 开头的文字框中写出完整的答案。你的项目将会根据你的回答和实现来进行评分。

请注意: Code 和 Markdown 区域可通过 Shift + Enter 快捷键运行。此外,Markdown可以通过双击进入编辑模式。

开始项目

在这个项目中,你将利用马萨诸塞州波士顿郊区的房屋信息数据来训练和测试一个模型,并对模型的性能和预测能力进行评估。使用该数据训练出适配的模型可用于对房屋进行特定预测,尤其是预测房屋的价值。对于房地产经纪等人的日常工作来说,这样的预测模型非常有价值。

此项目的数据集来自 UCI 机器学习代码库。波士顿房屋数据于1978年开始统计,共506个数据点,涵盖了波士顿郊区房屋的14种特征信息。本项目对原始数据集做了以下处理:

  • 有16个数据点的 'MEDV' 值为 50.0。由于这些数据点包含遗失或被删去的值,因此已被移除。
  • 有1个数据点的 RM 值为 8.78。这是一个异常值,因此已被移除。
  • 本项目必须的特征为 'RM''LSTAT''PTRATIO''MEDV'。其余不相关的特征已被移除。
  • 特征 'MEDV' 已根据 35 年来的市场通货膨胀状况进行了加倍

运行下面的代码以加载波士顿房屋数据集,以及一些此项目所需的 Python 库。如果成功返回数据集的大小,表示数据集已载入成功。


In [ ]:
# Import libraries necessary for this project
import numpy as np
import pandas as pd
from sklearn.cross_validation import ShuffleSplit

# Import supplementary visualizations code visuals.py
import visuals as vs

# Pretty display for notebooks
%matplotlib inline

# Load the Boston housing dataset
data = pd.read_csv('housing.csv')
prices = data['MEDV']
features = data.drop('MEDV', axis = 1)
    
# Success
print("Boston housing dataset has {} data points with {} variables each.".format(*data.shape))

数据分析

在该项目的第一部分,你将对波士顿的房地产数据进行初步观察和分析。你可以先探索和熟悉数据,这样可以更好地理解和解释你的结果。

由于本项目的最终目标是建立一个预测房屋价值的模型,因此我们需要将数据集分为特征目标变量特征,即'RM''LSTAT''PTRATIO' 为我们提供了每个数据点的数量信息。目标变量,即 'MEDV' 是我们准备预测的变量。它们被分别存储在 featuresprices 中。

实现:计算统计数据

你的第一个编程实现是计算有关波士顿房价的描述统计数据。我们已为你导入了 numpy,你需要使用这个库来执行必要的计算。这些统计数据对于分析模型的预测结果十分重要。

在下面的代码中,你将需要实现以下内容:

  • 计算存储在 prices 中的 'MEDV' 的最小值、最大值、均值、中值和标准差。
  • 将运算结果存储在相应的变量中。

In [ ]:
# TODO: Minimum price of the data
minimum_price = None

# TODO: Maximum price of the data
maximum_price = None

# TODO: Mean price of the data
mean_price = None

# TODO: Median price of the data
median_price = None

# TODO: Standard deviation of prices of the data
std_price = None

# Show the calculated statistics
print("Statistics for Boston housing dataset:\n")
print("Minimum price: ${}".format(minimum_price)) 
print("Maximum price: ${}".format(maximum_price))
print("Mean price: ${}".format(mean_price))
print("Median price ${}".format(median_price))
print("Standard deviation of prices: ${}".format(std_price))

问题 1 - 特征观察

在前面我们提到过,波士顿房屋信息的数据组中,我们用到三个特征,即 'RM''LSTAT''PTRATIO'。对于每个数据点(街区):

  • 'RM' 指该街区房屋的平均房间数量。
  • 'LSTAT' 指该街区屋主为”低收入阶层“(收入微薄)的比例。
  • 'PTRATIO' 指该街区的小学和中学中学生与老师的比例。

根据你的直觉,对于上面的三个特征而言,你认为增大某特征的数值, 'MEDV' 值是会增大还是减小?请给出理由。

提示: 该问题可以使用下面的例子来解释:

  • 你认为 'RM' 值(房间数量)为 6 的房子与 'RM' 为 7 的房子相比,哪一个价值更高?
  • 你认为 'LSTAT' 值(低收入阶级比例)为 15 的街区与 'LSTAT' 值为 20 的街区相比,哪一个房价更高?
  • 你认为 'PTRATIO' 值(学生和教师的比例)为 10 的街区与 'PTRATIO' 值为15的街区相比,哪一个房价更高?

回答:


开发模型

在本项目的第二部分,你将会了解必要的工具和技术来让模型进行预测。通过使用这些工具和技术,你可以精确评估每个模型的性能,来更好地加强预测结果的准确性。

实现:定义性能的衡量标准

如果不能对模型的训练和测试的表现进行量化评估,我们很难衡量模型的好坏。通常我们会定义一些衡量标准,这些标准可以通过对某些误差或者拟合程度的计算来得到。在这个项目中,你将通过运算决定系数 R2 来量化模型的表现。模型的决定系数是回归分析中常用的统计信息,经常被当作衡量模型预测能力好坏的标准。

R2 的数值位于 0 和 1 之间,它表示目标变量的预测值和实际值相关程度平方的百分比。当一个模型的 R2 值为 0 时,其预测效果还不如使用目标变量的均值进行预测,而 R2 值为 1 的模型能完美预测目标变量。当数值位于 0 至 1 之间时,则表示该模型中目标变量能以多大百分比用该特征表示。模型中的 R2 也有可能出现负值,在这种情况下,模型做出的预测会比直接预测均值差很多

在下方代码块的 performance_metric 函数中,你需要实现:

  • 使用 sklearn.metrics 中的 r2_score 来进行 y_true 和 `y_predict 之间的性能计算。
  • 将性能分数存储至 score 变量中。

In [ ]:
# TODO: Import 'r2_score'

def performance_metric(y_true, y_predict):
    """ Calculates and returns the performance score between 
        true and predicted values based on the metric chosen. """
    
    # TODO: Calculate the performance score between 'y_true' and 'y_predict'
    score = None
    
    # Return the score
    return score

问题 2 - 拟合程度

假设一个数据集有五个数据,且一个模型对目标变量做出以下预测:

True Value Prediction
3.0 2.5
-0.5 0.0
2.0 2.1
7.0 7.8
4.2 5.3

请运行下方的代码,使用 performance_metric 函数计算该模型的决定系数。


In [ ]:
# Calculate the performance of this model
score = performance_metric([3, -0.5, 2, 7, 4.2], [2.5, 0.0, 2.1, 7.8, 5.3])
print("Model has a coefficient of determination, R^2, of {:.3f}.".format(score))
  • 你认为这个模型成功描述了目标变量的变化吗?
  • 为什么?

提示:R2 分数是从自变量中预测的因变量的方差比例。换句话说:

  • R2 分数为 0 意味着该因变量无法根据自变量进行预测。
  • R2 分数为 1 意味着该因变量可以根据自变量进行预测。
  • R2 分数在 0 和 1 之间意味着该因变量可被预测的程度。
  • 当 R2 分数为 0.4 时,Y 中 40% 的方差可以根据 X 进行预测。

回答:

实现:数据分割与重新排列

在这个实现中,你需要把波士顿房屋数据集分成训练和测试两个子集。通常在这个过程中,数据也会被重排列,以消除数据集中由于顺序而产生的偏差。

在下面的代码中,你需要实现以下内容:

  • 使用 sklearn.cross_validation 中的 train_test_split 来将 featuresprices 分割成训练集和测试集,并重新排列。
    • 分割比例为:80% 的数据用于训练,20% 的数据用于测试。
    • 选择一个数值来为 train_test_split 设置 random_state。这将保证结果的一致性。
  • 将数据集和测试集分割为 X_trainX_testy_trainy_test

In [ ]:
# TODO: Import 'train_test_split'

# TODO: Shuffle and split the data into training and testing subsets
X_train, X_test, y_train, y_test = (None, None, None, None)

# Success
print("Training and testing split was successful.")

问题 3 - 训练与测试

  • 将数据集按一定比例分割为测试集和训练集对学习算法有什么好处?

提示: 思考一下数据分割会如何造成过拟合和欠拟合。

回答:


分析模型性能

在本项目的第三部分,你将学习不同训练数据的子集中,几个模型的学习和测试性能。此外,你还会关注一个特定的算法,该算法的 'max_depth' 参数在整个训练集中呈上升趋势,通过这个算法,你将观察到模型的复杂度对性能的影响。根据不同标准绘制模型的性能曲线可以帮助我们进行分析,比如能让我们看到一些单从结果无法判断的行为。

学习曲线

下方区域内的代码会输出四幅图像,它们是一个决策树模型在不同最大深度下的表现。每一张图显示了随着训练集的增加,训练和测试模型的学习曲线的变化情况。请注意,学习曲线中的阴影部分代表了该曲线的不确定性(标准差)。该模型在训练集和测试集中的性能使用决定系数 R2 进行评分。

运行下面的代码,并根据这些图像回答问题。


In [ ]:
# Produce learning curves for varying training set sizes and maximum depths
vs.ModelLearning(features, prices)

问题 4 - 学习数据

  • 选择一张上方的图表,说明该模型的最大深度。
  • 当训练点增加时,训练曲线的分数会如何变化?
  • 训练点增加对模型有什么好处?

提示:学习曲线最终会汇集到一个特定的分数吗?一般来说,数据越多越好。但如果你的训练和测试曲线在一个特定分数汇集,并且超过了你的基准,这是否必要?

根据训练和测试曲线是否汇聚,思考增加训练点的优缺点。

答案:

复杂度曲线

下列代码将输出一幅图像,它表示一个使用训练数据进行过训练和验证的决策树模型在不同最大深度下的表现。该图形将包含两条复杂度曲线,一个是训练集的变化,一个是测试集的变化。跟学习曲线相似,阴影区域代表该曲线的不确定性,模型训练和测试部分的评分都使用 performance_metric 函数。

运行下方代码,并根据此图表回答下面的两个问题(Q5 和 Q6)。


In [ ]:
vs.ModelComplexity(X_train, y_train)

问题 5 - 偏差与方差的权衡

  • 当进行训练的模型最大深度为 1 时,该模型是出现高方差还是高偏差?
  • 当进行训练的模型最大深度为 10 时情形又如何?上方的图像是否可以支撑你的结论?

提示: 高偏差是欠拟合的表示(模型不够复杂,无法察觉数据中的微妙变化),而高方差是过拟合的标志(模型中数据过于庞大,无法一般化)。思考两个模型(深度为 1 和 10)中哪一个与偏差或方差一致。

回答:

问题 6 - 最佳猜测模型

  • 你认为最大深度是多少的模型能够最好地预测不可见数据?
  • 你得出这个答案的依据是什么?

提示:结合问题 5 中的图像,查看模型在不同深度下的验证分数。当深度更高时,分数也更高吗?我们什么时候能在避免使模型过度复杂化的情况下获得最佳分数?请记住,Occams Razor 曾说过“在多个假说中,我们应该选择假设最少的哪一个。”

回答:


评估模型性能

在本项目的最后一部分,你将构建一个模型,并使用 fit_model 中的优化模型来预测客户的特征集。

问题 7 - 网格搜索

  • 什么是网格搜索技术?
  • 如何用它来优化学习算法?

提示:在解释网格搜索技术时,请说明为何使用这一技术,“网格”代表什么,该方法的目标又是什么?你还可以举例说明如何使用这个方法来优化模型中的参数。

答案:

问题 8 - 交叉验证

  • 什么是 K 折交叉验证训练技术?

  • 在优化模型时,该技术会给网格搜索带来哪些益处?

提示:在解释 K 折交叉验证技术时,请说明“k”代表什么?如何根据“k”值将数据集分成训练集和测试集,以及确定运行次数。

在说明 k 折交叉验证给网格搜索带来的好处时,请思考网格搜索的主要劣势,它取决于使用特定的数据集子集来进行训练或测试,以及 k 折交叉验证如何改善这一点。你可以参考这个文件

答案:

实现:拟合模型

在最终的实现中,你需要整合学习的内容,并使用决策树算法来训练一个模型。为了保证得到最优模型,你需要使用网格搜索方法来训练模型,并为这个决策树优化 'max_depth' 参数。你可以将 'max_depth' 参数视作在做出预测前,该决策树被允许针对数据提出的问题。决策树是监督学习算法的一部分。

此外,你还会发现你的实现使用 ShuffleSplit() 作为交叉验证的另一种形式(查看 'cv_sets' 变量)。尽管这并不是你在 问题 8 中描述的 k 折交叉验证技术,这种交叉验证方法同样十分有用!下方的 ShuffleSplit() 实现将创建 10('n_splits') 个重新排列的数据集,针对每个数据集,20%('test_size')的数据将作为“验证集”。在完成你的实现时,请思考该方法与 k 折验证技术的不同点和相似之处。

请注意,在 scikit-learn 的 0.17 和 0.18 版本中,ShuffleSplit 的参数也有所不同。 对于下方代码块中的 fit_model 函数,你需要实现以下内容:

  • 使用 sklearn.tree 中的 DecisionTreeRegressor 创建一个决策树回归量对象。
    • 将该对象指定为 'regressor' 变量。
  • 'max_depth' 创建字典,其深度为 1 到 10,并指定为 'params' 变量。
  • 使用 sklearn.metrics 中的 make_scorer 创建一个计分函数对象。
    • 将该 performance_metric 函数作为参数转到对象。
    • 将该计分函数指定为'scoring_fnc' 变量。
  • 使用 sklearn.grid_search 中的 GridSearchCV 创建网格搜索对象。
    • 'regressor''params''scoring_fnc''cv_sets' 作为参数转到对象。
    • GridSearchCV 对象指定为 'grid' 变量。

In [ ]:
# TODO: Import 'make_scorer', 'DecisionTreeRegressor', and 'GridSearchCV'

def fit_model(X, y):
    """ Performs grid search over the 'max_depth' parameter for a 
        decision tree regressor trained on the input data [X, y]. """
    
    # Create cross-validation sets from the training data
    # sklearn version 0.18: ShuffleSplit(n_splits=10, test_size=0.1, train_size=None, random_state=None)
    # sklearn versiin 0.17: ShuffleSplit(n, n_iter=10, test_size=0.1, train_size=None, random_state=None)
    cv_sets = ShuffleSplit(X.shape[0], n_iter = 10, test_size = 0.20, random_state = 0)

    # TODO: Create a decision tree regressor object
    regressor = None

    # TODO: Create a dictionary for the parameter 'max_depth' with a range from 1 to 10
    params = {}

    # TODO: Transform 'performance_metric' into a scoring function using 'make_scorer' 
    scoring_fnc = None

    # TODO: Create the grid search cv object --> GridSearchCV()
    # Make sure to include the right parameters in the object:
    # (estimator, param_grid, scoring, cv) which have values 'regressor', 'params', 'scoring_fnc', and 'cv_sets' respectively.
    grid = None

    # Fit the grid search object to the data to compute the optimal model
    grid = grid.fit(X, y)

    # Return the optimal model after fitting the data
    return grid.best_estimator_

做出预测

当根据一组数据训练模型后,该模型可根据新输入的数据组进行预测。在决策树回归函数中,模型已经学会如何针对输入的数据进行提问,并返回目标变量的预测值。你可以根据这个预测来获取有关未知目标变量值的信息,比如不包含在训练数据内的数据。

问题 9 - 最佳模型

  • 最优模型的最大深度是多少?此答案与你在问题 6 中的猜测是否相同?

运行下方区域内的代码,将决策树回归函数与训练数据进行拟合,以得到最优化的模型。


In [ ]:
# Fit the training data to the model using grid search
reg = fit_model(X_train, y_train)

# Produce the value for 'max_depth'
print("Parameter 'max_depth' is {} for the optimal model.".format(reg.get_params()['max_depth']))

提示: 该答案来自上方代码的输出。

回答:

问题 10 - 预测销售价格

假设你是波士顿地区的房屋经纪人,并希望使用此模型帮助你的客户评估他们想出售的房屋。你已经从三个客户那里收集到以下的信息:

特征 客户 1 客户 2 客户 3
房屋内的房间总数 5 间 4 间 8 间
社区贫困指数(%) 17% 32% 3%
附近学校的学生-教师比例 15-to-1 22-to-1 12-to-1
  • 你会建议每位客户以什么价位出售房屋?
  • 从房屋特征的数值判断,这样的价格合理吗?

提示: 使用你在数据分析部分计算的数据,来验证你的回答。对于这三个客户,客户 3 的房屋最大,附近有最优秀的公立学校,同时贫困指数最低。而客户 2 的房屋最小,所在街区贫困指数相对较高,附近的公立学校也十分一般。

请运行下面的代码,使用你的优化模型来预测每位客户的房屋价值。


In [ ]:
# Produce a matrix for client data
client_data = [[5, 17, 15], # Client 1
               [4, 32, 22], # Client 2
               [8, 3, 12]]  # Client 3

# Show predictions
for i, price in enumerate(reg.predict(client_data)):
    print("Predicted selling price for Client {}'s home: ${:,.2f}".format(i+1, price))

回答:

敏感性

有时最优模型并不一定是一个稳健模型。有时模型会过于复杂或过于简单,因而难以泛化新增添的数据;有时模型采用的学习算法并不适用于特定的数据结构;有时样本本身可能有太多噪点或样本数量过少,使得模型无法准确地预测目标变量。此时,该模型欠拟合。

使用不同训练数据及测试数据,将下方的 fit_model 函数运行十次,以测试模型针对不同客户的数据做出的预测有何不同。


In [ ]:
vs.PredictTrials(features, prices, fit_model, client_data)

问题 11 - 适用性

  • 简要探讨构建的模型是否可应用于现实世界。

提示: 根据上方代码计算出的价格范围,回答以下问题:

  • 今天的数据与 1978 年收集的数据有何相关性?通货膨胀是否重要?
  • 数据中的特征是否足以描述一间房屋?诸如房屋电器质量、小区面积、是否有泳池等因素是否也应计入特征?
  • 该模型是否能够做出一致的预测?
  • 在波士顿这样的大都市收集的数据是否也可以应用于乡村城市?
  • 根据整个街区的状况来判断房屋价值是否公平?

答案:

请注意:在完成了上方所有的代码实现和问题之后,你将这个 iPython Notebook 导出为 HTML 文件来完成这个项目。你可以通过上方的菜单导出文件,或转到 File -> Download as -> HTML (.html)。请将完成的文件和这个 notebook 一起提交审阅。