选择并训练模型

可到这一步了!你在前面限定了问题、获得了数据、探索了数据、采样了一个测试集、写了自动化的转换流水线来清理和为算法准备数据。现在,你已经准备好选择并训练一个机器学习模型了。

在训练集上训练和评估

好消息是基于前面的工作,接下来要做的比你想的要简单许多。像前一章那样,我们先来训练一个线性回归模型:

  1. from sklearn.linear_model import LinearRegression
  2. lin_reg = LinearRegression()
  3. lin_reg.fit(housing_prepared, housing_labels)

完毕!你现在就有了一个可用的线性回归模型。用一些训练集中的实例做下验证:

  1. >>> some_data = housing.iloc[:5]
  2. >>> some_labels = housing_labels.iloc[:5]
  3. >>> some_data_prepared = full_pipeline.transform(some_data)
  4. >>> print("Predictions:\t", lin_reg.predict(some_data_prepared))
  5. Predictions: [ 303104. 44800. 308928. 294208. 368704.]
  6. >>> print("Labels:\t\t", list(some_labels))
  7. Labels: [359400.0, 69700.0, 302100.0, 301300.0, 351900.0]

行的通,尽管预测并不怎么准确(比如,第二个预测偏离了 50%!)。让我们使用 Scikit-Learn 的mean_squared_error函数,用全部训练集来计算下这个回归模型的 RMSE:

  1. >>> from sklearn.metrics import mean_squared_error
  2. >>> housing_predictions = lin_reg.predict(housing_prepared)
  3. >>> lin_mse = mean_squared_error(housing_labels, housing_predictions)
  4. >>> lin_rmse = np.sqrt(lin_mse)
  5. >>> lin_rmse
  6. 68628.413493824875

OK,有总比没有强,但显然结果并不好:大多数街区的median_housing_values位于 120000 到 265000 美元之间,因此预测误差 68628 美元不能让人满意。这是一个模型欠拟合训练数据的例子。当这种情况发生时,意味着特征没有提供足够多的信息来做出一个好的预测,或者模型并不强大。就像前一章看到的,修复欠拟合的主要方法是选择一个更强大的模型,给训练算法提供更好的特征,或去掉模型上的限制。这个模型还没有正则化,所以排除了最后一个选项。你可以尝试添加更多特征(比如,人口的对数值),但是首先让我们尝试一个更为复杂的模型,看看效果。

来训练一个DecisionTreeRegressor。这是一个强大的模型,可以发现数据中复杂的非线性关系(决策树会在第 6 章详细讲解)。代码看起来很熟悉:

  1. from sklearn.tree import DecisionTreeRegressor
  2. tree_reg = DecisionTreeRegressor()
  3. tree_reg.fit(housing_prepared, housing_labels)

现在模型就训练好了,用训练集评估下:

  1. >>> housing_predictions = tree_reg.predict(housing_prepared)
  2. >>> tree_mse = mean_squared_error(housing_labels, housing_predictions)
  3. >>> tree_rmse = np.sqrt(tree_mse)
  4. >>> tree_rmse
  5. 0.0

等一下,发生了什么?没有误差?这个模型可能是绝对完美的吗?当然,更大可能性是这个模型严重过拟合数据。如何确定呢?如前所述,直到你准备运行一个具备足够信心的模型,都不要碰测试集,因此你需要使用训练集的部分数据来做训练,用一部分来做模型验证。

使用交叉验证做更佳的评估

评估决策树模型的一种方法是用函数train_test_split来分割训练集,得到一个更小的训练集和一个验证集,然后用更小的训练集来训练模型,用验证集来评估。这需要一定工作量,并不难而且也可行。

另一种更好的方法是使用 Scikit-Learn 的交叉验证功能。下面的代码采用了 K 折交叉验证(K-fold cross-validation):它随机地将训练集分成十个不同的子集,成为“折”,然后训练评估决策树模型 10 次,每次选一个不用的折来做评估,用其它 9 个来做训练。结果是一个包含 10 个评分的数组:

  1. from sklearn.model_selection import cross_val_score
  2. scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
  3. scoring="neg_mean_squared_error", cv=10)
  4. rmse_scores = np.sqrt(-scores)

警告:Scikit-Learn 交叉验证功能期望的是效用函数(越大越好)而不是损失函数(越低越好),因此得分函数实际上与 MSE 相反(即负值),这就是为什么前面的代码在计算平方根之前先计算-scores

来看下结果:

  1. >>> def display_scores(scores):
  2. ... print("Scores:", scores)
  3. ... print("Mean:", scores.mean())
  4. ... print("Standard deviation:", scores.std())
  5. ...
  6. >>> display_scores(tree_rmse_scores)
  7. Scores: [ 74678.4916885 64766.2398337 69632.86942005 69166.67693232
  8. 71486.76507766 73321.65695983 71860.04741226 71086.32691692
  9. 76934.2726093 69060.93319262]
  10. Mean: 71199.4280043
  11. Standard deviation: 3202.70522793

现在决策树就不像前面看起来那么好了。实际上,它看起来比线性回归模型还糟!注意到交叉验证不仅可以让你得到模型性能的评估,还能测量评估的准确性(即,它的标准差)。决策树的评分大约是 71200,通常波动有 ±3200。如果只有一个验证集,就得不到这些信息。但是交叉验证的代价是训练了模型多次,不可能总是这样。

让我们计算下线性回归模型的的相同分数,以做确保:

  1. >>> lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,
  2. ... scoring="neg_mean_squared_error", cv=10)
  3. ...
  4. >>> lin_rmse_scores = np.sqrt(-lin_scores)
  5. >>> display_scores(lin_rmse_scores)
  6. Scores: [ 70423.5893262 65804.84913139 66620.84314068 72510.11362141
  7. 66414.74423281 71958.89083606 67624.90198297 67825.36117664
  8. 72512.36533141 68028.11688067]
  9. Mean: 68972.377566
  10. Standard deviation: 2493.98819069

判断没错:决策树模型过拟合很严重,它的性能比线性回归模型还差。

现在再尝试最后一个模型:RandomForestRegressor。第7章我们会看到,随机森林是通过用特征的随机子集训练许多决策树。在其它多个模型之上建立模型称为集成学习(Ensemble Learning),它是推进 ML 算法的一种好方法。我们会跳过大部分的代码,因为代码本质上和其它模型一样:

  1. >>> from sklearn.ensemble import RandomForestRegressor
  2. >>> forest_reg = RandomForestRegressor()
  3. >>> forest_reg.fit(housing_prepared, housing_labels)
  4. >>> [...]
  5. >>> forest_rmse
  6. 22542.396440343684
  7. >>> display_scores(forest_rmse_scores)
  8. Scores: [ 53789.2879722 50256.19806622 52521.55342602 53237.44937943
  9. 52428.82176158 55854.61222549 52158.02291609 50093.66125649
  10. 53240.80406125 52761.50852822]
  11. Mean: 52634.1919593
  12. Standard deviation: 1576.20472269

现在好多了:随机森林看起来很有希望。但是,训练集的评分仍然比验证集的评分低很多。解决过拟合可以通过简化模型,给模型加限制(即,规整化),或用更多的训练数据。在深入随机森林之前,你应该尝试下机器学习算法的其它类型模型(不同核心的支持向量机,神经网络,等等),不要在调节超参数上花费太多时间。目标是列出一个可能模型的列表(两到五个)。

提示:你要保存每个试验过的模型,以便后续可以再用。要确保有超参数和训练参数,以及交叉验证评分,和实际的预测值。这可以让你比较不同类型模型的评分,还可以比较误差种类。你可以用 Python 的模块pickle,非常方便地保存 Scikit-Learn 模型,或使用sklearn.externals.joblib,后者序列化大 NumPy 数组更有效率:

  1. from sklearn.externals import joblib
  2. joblib.dump(my_model, "my_model.pkl")
  3. # 然后
  4. my_model_loaded = joblib.load("my_model.pkl")