创建一个完整的机器学习工程(四)- 模型建立与评估

这几篇博客将通过对加州房价模型的建立,介绍如何搭建一个完整的机器学习工程。
本文将介绍如何通过训练处理后的数据得到模型,以及如何利用测试集数据检验模型的表现。
过程中如有任何错误,请各位指正与包涵。

文章的内容源自’Hands-On Machine Learning with Scikit-Learn and TensorFlow’一书第二章

  • 数据(housing.cvs)来源:https://github.com/ageron/handson-ml/tree/master/datasets/housing
  • 上一篇内容介绍了数据划分及预处理的方法,详情请见创建一个完整的机器学习工程(三)处理
    本文内容基于python进行开发,将用到的package包括sklearn, pandas, numpy, joblib,请先搭配好程序运行环境
  • 本文不会对相关模型的原理进行展开,仅利用scikit-learn中相关库的方法进行实现,针对模型的理论知识,将会在之后的文章中再详细讨论
    若希望得到跟本文及书中一样的运行效果,需要在此部分及之前的代码中设置random_state=42

一、模型训练

在前几篇文章中,我们讨论了如何划分数据,如何利用数据概览找出问题,并针对性进行处理。如此我们得到了训练集和测试集,可以进行模型训练与评估了。
在训练模型之前,先利用上一篇文章封装后的方法pipeline(),得到经过处理后的训练集与测试集。

import numpy as np
from Chapter2.process2 import pipeline
# data acquisition
X_train, X_test, y_train, y_test = pipeline()

1. 线性模型

作为最常用、最简洁也最容易理解的模型,让我们先利用得到的结果建立一个线性模型出来。

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

# ------------------------------linear model------------------------------ #
lin_reg = LinearRegression()
lin_reg.fit(X_train, y_train)
# performance
lin_predict = lin_reg.predict(X_train)
lin_mse = mean_squared_error(y_train, lin_predict)
print('train set rmse: %.3f' % np.sqrt(lin_mse))
lin_predict = lin_reg.predict(X_test)
lin_mse = mean_squared_error(y_test, lin_predict)
print('test set rmse: %.3f' % np.sqrt(lin_mse))
  • 利用fit()方法对模型进行训练,进而查看模型对训练集数据的预期;
    利用mean_square_error()方法计算预测值与实际值的均方误差
  • 最后,该模型训练集的均方根误差为68628.198, 测试集均方根误差为66911.981
  • 特别说明的是,若运行代码时未得到与本文或书中同样的结果,请检查代码。例如是否在数据抽样时设置随机种子random_state=42,或是在数据处理时,是否排除了house_median_value

该模型测试集与训练集的均方根误差相差很小,说明产生的该线性模型基本不存在过拟合。
只是目前的预测值与真实值相比,还有不小的差距。

2. 决策树模型

利用sklearn.tree模块中的DecisionTreeRegressor,可以生成决策树模型。

from sklearn.tree import DecisionTreeRegressor

# -----------------------------decision tree------------------------------ #
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(X_train, y_train)
# performance
tree_predict = tree_reg.predict(X_train)
tree_mse = mean_squared_error(y_train, tree_predict)
print('train set rmse: %.3f' % np.sqrt(tree_mse))
tree_predict = tree_reg.predict(X_test)
tree_mse = mean_squared_error(y_test, tree_predict)
print('test set rmse: %.3f ' % np.sqrt(tree_mse))
  • 与线性模型不同,决策树模型有其随机性,所以需要设置随机种子random_state=42,以便得到与书中相同的结果
  • 该模型训练集的均方根误差为0.000, 测试集均方根误差为70388.942

该模型的均方根误差为0说明,通过该模型预测训练集的数据,可以得到与真实值一致的结果。
但是这并不表示该模型很好,因为其在测试集上的表现甚至不如线性模型。这说明,该模型存在严重的过度拟合。

3. 随机森林模型

利用sklearn.ensemble模块中的RandomForestRegressor,可以生成随机森林模型。

from sklearn.ensemble import RandomForestRegressor

# -----------------------------random forest------------------------------ #
forest_reg = RandomForestRegressor(n_estimators=100, random_state=42)
forest_reg.fit(X_train, y_train)
# performance
forest_predict = forest_reg.predict(X_train)
forest_mse = mean_squared_error(y_train, forest_predict)
print('train set rmse: %.3f' % np.sqrt(forest_mse))
forest_predict = forest_reg.predict(X_test)
forest_mse = mean_squared_error(y_test, forest_predict)
print('test set rmse: %.3f ' % np.sqrt(forest_mse))
  • 这里,随机森林比决策树模型需要多设置一个参数,子树的数量n_estimators=100,以便得到与书中相同的结果。
  • 该模型训练集的均方根误差为18603.515, 测试集均方根误差为48306.376

测试集比其训练集的均方根误差仍要大很多,说明该模型仍然存在过度拟合,只是情况比之前的决策树模型要稍好一些。
但总的来说,即使存在过拟合的现象,该模型的整体表现还是比之前的线性模型要好很多。

二、交叉验证

1. 情形分析

经过上述的分析,我们对三种模型的表现有了基本的认识:

  • 线性模型几乎不存在过拟合现象,但其模型误差较大
  • 决策树模型存在严重的过拟合现象,说明其对新数据的预测不会有好的表现
  • 随机森林模型虽然仍有一定的过拟合,但其模型在预测准确性的表现更好

但是,这些都是我们主观且抽象的认识。若希望能将这一认识反映在机器学习的算法中,尤其是希望通过算法改善模型时,我们需要有具体的数值作为衡量标准。
而仅仅利用模型在面对训练集与测试集进行预测时,产生的两个均方根误差并不够,原因是,

  1. 我们不希望让模型过早接触到测试集,因为针对降低测试集预测的误差而生成的模型,只会让该模型再次陷入过拟合的麻烦中;
  2. 我们也无法继续降低其面对训练集时的预测误差,因为目前的模型已经过拟合了。

因此,我们希望有一种方式,能将训练集继续拆散变成多个部分,仅利用其中一部分进行验证,而将剩余的部分用作训练使用,再换另一部分与该部分外剩余的部分用作验证与训练。将这一步骤多次执行、以其表现的平均值作为衡量的标准,将使得结果更加客观。
这一思想,便是交叉验证

2. 算法实现

具体来说,交叉验证是利用训练集,随机拆散成k组( k ≥ 2 k\geq2 k2)。将其中的k-1组用作训练、剩下的1组用作测试,再换另一组与该组外剩余的组用作验证与训练。如此进行下去,直到分成的所有小组均有用作测试集进行检验后,计算每次得到的结果的平均值,进而将该值作为该模型的真实误差表现留作后用。
在了解该算法的原理后,我们可以利用以下代码实现这一过程,并打印出其k-fold交叉验证的平均得分。

from sklearn.model_selection import cross_val_score

def display_scores(scores: np.ndarray):
    print('mean: %.3f' % scores.mean())
    print('std:  %.3f' % scores.std())

# cross validation
forest_scores = cross_val_score(forest_reg, X_train, y_train, scoring='neg_mean_squared_error', cv=10)
forest_rmse_scores = np.sqrt(-forest_scores)
display_scores(forest_rmse_scores)
  • 随机森林模型在之前的表现最好,所以这里我们以该模型为例进行交叉验证,且设置分组数为10组。
  • 注意,这里我们采用的评价标准是neg_mean_squared_error,计算得到的结果是负数。
    这是因为对于cross_val_score的所有评价标准,scikit-learn的开发者希望统一采用效用模型(越大越好)的标准进行设计。这样在进行模型间进行比较时,可以统一评判标准,进而简化他们该部分算法的代码,而无需针对具体情况进行判断。
    因此,即使在考虑均方误差时,虽然理应其值越小表示模型越好,但还是取其相反数而不选择其本身作为评价标准。
  • 这里forest_scores的数据类型是numpy.ndarray,包含模型10次得分。
    通过创建display_scores()方法,可将10次得分的均值及标准差打印出来。
    得到的结果是:mean: 50182.303, std: 2097.081

输出的结果表明,目前随机森林模型的真实误差在50182 ± \pm ± 2097左右。
利用同样的代码还可以得到,线性模型的真实误差在69052 ± \pm ± 2731左右、决策树模型的真实误差在71407 ± \pm ± 2439
所以综合来看,相较于其他两种模型,随机森林模型的表现还是更好一些。其唯一的缺陷是运算时间较长,但为了得到更有效的模型,我们愿意牺牲一定的运算效率。

三、模型优化

1. 网格搜索

之前所用的随机森林模型中,除了随机种子,我们只设置了其子树数量(n_setimator)为100,而其余参数均使用模型默认值。为了优化模型,我们希望通过多次设置参数的值,从中找出更好的更合适的参数用在模型中。
scikit-learn.model_selection模块提供了网格搜索的办法,从设定的多组参数中找出其中最好的一组,具体实现方式如下。

from sklearn.model_selection import GridSearchCV

# grid search
forest_reg = RandomForestRegressor(random_state=42)
param_grid = [
    {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
    {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
grid_search = GridSearchCV(forest_reg, param_grid, cv=5, scoring='neg_mean_squared_error', return_train_score=True)
grid_search.fit(X_train, y_train)

# best score
print('best parameters: ', grid_search.best_params_)
print('best score: %.3f' % np.sqrt(-grid_search.best_score_))
  • param_grid是一个包含了两个字典的列表,每个字典对应着之后网格搜索时可选的参数。
    在第一组中,两种参数共包含12种可选的组合方式;第二组中共包含6种可选的组合方式。
    所以程序会从这18种参数的组合中选出一个得分最高的作为返回值存入grid_search中。
  • 与之前类似,通过best_score_访问得到的分数是负数
    打印的结果:best parameters: {‘max_features’: 8, ‘n_estimator’: 30} best score: 49682.253

继续调整选择的参数还可以让模型误差进一步降低,这里只是为了说明,采用网格搜索的方式可以起到优化模型的效果,所以此处也不再继续深入研究具体能够将模型优化到何种程度了。
在现实中,为了能尽可能优化模型,还是有必要根据情况选择继续优化的。

2. 模型检验

得到了经过改进的模型之后,现在我们可以利用测试集对模型进行检验了。

# prediction performance
forest_predict = grid_search.predict(X_test)
forest_mse = mean_squared_error(y_test, forest_predict)
print('test set rmse: %.3f' % np.sqrt(forest_mse))

打印的结果显示,模型面对测试集的均方根误差为47730.227
这与上一步骤中通过训练集得到结果基本一致,说明我们经过交叉验证和参数微调得到的模型,既在性能上有所提升、同时也有效地降低了模型过拟合的现象。

3. 模型保存

至此,我们成功得到了一个面对新数据表现也还不错的模型。但目前还差最后一步需要做,那就是将我们最后得到的模型保存起来。毕竟,我们不希望以后每次有新数据需要进行预测时,都需要重新跑一次这里的程序来返回一个模型。
利用joblib模块可以实现这一过程。

import joblib

# model save
joblib.dump(grid_search.best_estimator_, 'forest_model.pkl')
  • 这里joblib最好直接导入,书中所说通过sklearn.externals导入的方式将在sklearn 0.23(书中版本为0.21)及以后的版本中取消掉。
  • 保存时不一定需要设置其后缀名为.pkl,任意名称或者不加后缀名也是被允许的。
    需要时,可利用my_model = joblib.load('forest_model.pkl')调用该模型。
  • 之前进行的每一步,均可利用这一方式将所得的模型保存下来,以便后续的处理时使用。

四、小结

经过上述的分析,我们对于在得到了经过处理的数据之后,如何利用这些数据进行模型的筛选与比较、以及如何对模型优化有了基本的认识。具体来说,大致包含以下几个步骤。

  1. 初步选取几个待考察的模型,观察其经过训练之后,面对已有数据(训练集)与未知数据(测试集)的表现,从而对几种模型的表现有一个初步的认识;
  2. 利用交叉验证的方式,可以得到几种模型更接近真实水平的表现,从而方便我们从中精选出一个(或几个)最好的模型,进行后续的优化处理;
  3. 可以(但不限于)使用网格搜索的方式,调整模型使用的参数,从而进一步优化模型;
  4. 将最后的结果保存在本地,以便以后进行新的数据时进行调用。

五、总结

在这几篇文章中,我们通过对加州房价数据的分析并建立预测模型的过程,梳理了创建一个完整的机器学习工程的思路与大致过程:

  1. 通过对数据可视化的分析,从而对其基本情况、以及数据存在的问题有了初步的认识;
  2. 采用合适的方式,将数据划分成训练集与测试集;
  3. 针对第一步中发现的问题,采用合适的方式对整个数据(训练集与测试集)进行处理;
  4. 选择合适的模型进行训练、并通过合适的方式优化该模型,最后将得到的结果进行保存。

这其中每一步,将因数据的不同,而需要找到适合该数据的处理方式。同时,针对不同的模型需求(例如本文中的模型是预测模型),也会需要有不同的选择与分析的方式。
所以在面对具体的问题时需要具体的分析。


本文内容源自Hands-On Machine Learning with Scikit-Learn and TensorFlow一书,是其第二章关于模型建立部分的读书笔记。文中加入了少量自己的理解,如有不正之处,望各位指正与包涵。

https://github.com/ageron/handson-ml/
这是原书作者关于此书的链接,若想获取书中的源代码和数据可访此链接获取
本书有中文版《Scikit-Learn与TensorFlow机器学习实用指南》,如有需要可自行搜索查找
本书目前最新版是第二版,相较于第一版,其在代码部分进行了适应性升级,建议选择新版阅读

本文与书中最大的不同是,本中将训练集提前进行了使用,以观察初步建立的模型表现以及其过拟合的程度。书中则是得到了最终的模型后,才使用训练集的数据以观察模型的表现。
我这么做的目的是,希望以直观的方式感受到模型进行预测时的好坏,并通过比较训练集与测试集的预测结果判断其过拟合的程度。而不需等到发现生成的模型在面对训练集时误差为0,才意识到其过拟合的严重性。

书中还对用以优化模型的随机化搜索进行了简要介绍,该算法的实现可在作者github网站链接中找到。
对文中提及的部分模块,书中有提及其他相关接口的调用,这里进行了删减,感兴趣的朋友可在书中进行查看。

https://github.com/ShaoboZhang/ML_Notebook/tree/master/Chapter2
文中涉及的代码完整版可通过此链接获取(model.py),代码将模型比较与网格搜索封装为两个方法,主程序则是对保存的模型进行调用,以查看其面对测试集的预测表现
运行程序前请配置好编译环境及相关包

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anycall201

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值