机器学习人工智能/模式识别/机器学习精华专题机器学习与数据挖掘

机器学习与人工智能基础:价值估算(第七章-提升系统)

2017-11-10  本文已影响46人  奔IV程序猿

提升系统

Improve Our System

前言叨B叨

大家是不是这会正在忙着抢券,忙着剁手呢? 好吧, 祝你们剁手愉快.
本章包含以下内容:

1. 拟合不足和过度拟合
2. 粗暴的解决方案:网格搜索
3. 特征选择

正文

1. 拟合不足和过度拟合(Overfitting and underfitting)

构建机器学习模型的一个关键挑战是学习如何处理欠拟合过拟合问题。我们来看一个房价的图表,


其中每个房子的价值只根据房子的大小来确定。一个好的模型可以按照平滑曲线预测价格。这条曲线是跟随数据的趋势的。
过度拟合(overfitting)是当你的模型记住你的确切的训练数据,但实际上并没有找出数据中的模式。过度拟合模型可能会像这样预测价格。
换句话说,该模型太贴合训练数据。对任何不在训练数据集中的房屋都会做出不恰当的预测。如果是这样,这个模型根本没有把握住房价的套路。它只是记住这些确切的数据点。
而拟合不足(underfitting)恰恰相反, 也就是说你的模型太简单,并没有完全学习数据模式。下面是一个拟合不足例子。
在这种情况下,对于右侧的值模型是正常的,但是左侧的值却相差很远。因为你所使用的模型不够复杂,无法匹配训练数据,所以可能会发生拟合不足的情况。在这种情况下,我们的模型只是一条直线, 而没有那一条直线能够准确地跟踪这些房价的趋势。

不幸的是,当我们构建真实世界的机器学习模型时,我们不仅仅要关注像房子大小这样的单一功能。我们的模型可能具有数百或数千个特征。因此,我们不能在图表上绘制我们所有的数据,来查看我们的机器学习模型是否合适, 这个试图化看起来太复杂了。因此,我们不直接看图表,而是通过查看训练数据和测试数据的错误率来间接计算出模型的好坏。
如果我们的训练数据的错误率很低,但是我们的测试数据集的错误率非常高,这意味着我们的模型是过度拟合的。

我们知道,因为这个模型完全符合我们的训练数据,但并没有推广到测试数据。太复杂的模型会导致过度拟合。我们需要减少模型的复杂性。通过使用较少的决策树,使得每个决策树更小,或者通过选择简单的决策树而不是复杂决策树,可以使您的梯度增强模型变得更加复杂。但即使这样模型仍然可能拟合不足,原因可能是我们没有足够的训练数据。如果降低模型的复杂度并不能解决问题,则可能是因为你没有足够的训练数据来解决问题。

如果我们的训练数据集和测试数据集的错误率都很高,那意味着我们的模型是拟合不足,它没有很好地捕捉到数据集中的模式,太简单的模型就会这样。
所以您需要使模型更复杂一些。通过使用更多的决策树,或者使每个决策树的层级更深些,可以使梯度提升模型更加复杂。如果我们的训练集和测试集的错误率都很低,这意味着我们的模型运行良好,而且训练数据和测试数据都是准确的。这意味着模型已经学习到了数据背后的真实模式。

通过调整模型的超参数,我们可以修复拟合不足和过度拟合的问题,最终得到一个合适的模型。

2. 粗暴的解决方案:网格搜索( Grid search)

机器学习模型中的两个常见问题是过度拟合和拟合不足。我们通常可以通过调整模型上的超参数来解决这个问题。但问题是机器学习模型有很多超参数需要调整。寻找最佳设置的最好方法通常是通过试验和纠错,但尝试所有可能的组合需要花费大量的工作。

现在让我们打开源码中的train_model.py看下。

# Fit regression model

model = ensemble.GradientBoostingRegressor(
    n_estimators=1000,
    learning_rate=0.1,
    max_depth=6,
    min_samples_leaf=9,
    max_features=0.1,
    loss='huber'
)

当我们创建我们的模型时,我们传入了这些参数。我们在这里有六个不同的参数,我们可以调整,大多数这些参数接受任何数字,所以我们可以尝试无数的组合。

解决这个问题的方法是使用网格搜索(Grid Search)。网格搜索可以列出你想要调整的每个参数,然后都尝试几遍。用这个你可以训练和测试每个参数组合的模型,最后生成最佳预测的参数组合是您应该用于真实模型的一组参数。幸运的是,scikit-learn完全自动化了这个过程。我们来打开grid_search.py​​。这几乎和train_model.py中的代码完全一样。

# Create the model

model = ensemble.GradientBoostingRegressor()

第一个区别是我们声明了我们的模型而不传入任何参数。
相反,我们在下面有一个参数网格。参数网格为每个参数都有一个数组。

# Parameters we want to try

param_grid = {
    'n_estimators': [500, 1000, 3000],
    'max_depth': [4, 6],
    'min_samples_leaf': [3, 5, 9, 17],
    'learning_rate': [0.1, 0.05, 0.02, 0.01],
    'max_features': [1.0, 0.3, 0.1],
    'loss': ['ls', 'lad', 'huber']
}

对于每个设置,我们添加我们想要尝试的值的范围。我们在这里设置的范围是能够很好覆盖大多数问题的。
一个好的策略就是对每个参数尝试一些值,在这个值上增加或减少一个很大的数值,比如1.0到0.3到0.1,就像我们这里所做的那样。尝试非常接近的值如1.0到0.95,没有多大意义,因为结果可能不会有太大的不同。
接下来,使用网格搜索CV函数定义网格搜索。

# Define the grid search we want to run. Run it with four cpus in parallel.

gs_cv = GridSearchCV(model, param_grid, n_jobs=4)

这需要模型对象,参数表和我们想用来运行网格搜索的CPU数量。如果您的计算机具有多个CPU,则可以使用它们全部加速。接下来,我们调用CV的fit函数来运行网格搜索。

# Run the grid search - on only the training data!

gs_cv.fit(X_train, y_train)

这里我们只将训练数据传递给网格搜索CV功能, 这一点是非常重要的。我们不给它访问我们的测试数据集。网格搜索中的CV代表交叉验证(Cross-Validation)。该功能将自动将训练数据切片成较小的子集,并将部分数据用于训练不同模型和不同部分数据以测试这些模型。
这意味着模型配置没有看到我们的测试数据,这么做的目的是确保我们对最终模型进行完全的盲测。
运行网格搜索将需要很长时间,因为它实际上是在对齐网格中为每个可能的参数组合训练一个模型。完成训练后,将打印出效果最好的模型超参数。

# Print the parameters that gave us the best result!

print(gs_cv.best_params_)

当使用最佳参数时,它也会告诉我们两个数据集的平均误差。

# Find the error rate on the training set using the best parameters
mse = mean_absolute_error(y_train, gs_cv.predict(X_train))
print("Training Set Mean Absolute Error: %.4f" % mse)

# Find the error rate on the test set using the best parameters
mse = mean_absolute_error(y_test, gs_cv.predict(X_test))
print("Test Set Mean Absolute Error: %.4f" % mse)

3. 特征选择

我们来打开feature_selection.py。在我们的房价模型中,

# These are the feature labels from our data set
feature_labels = np.array(['year_built', 'stories', 'num_bedrooms', 'full_bathrooms', 'half_bathrooms', 'livable_sqft', 'total_sqft', 'garage_sqft'...'])

如果我们包含18个原始特征,加上使用one-hot编码创建的新特征,我们总共有63个特征。其中一些特征如房屋面积(以平方英尺计算),对确定房屋的价值可能非常重要。其他功能,如房子是否有壁炉,在计算最终价格时可能不太重要,但是有多不重要?也许有一些特征根本就没有必要,我们可以从模型中删除它们。
通过基于树的机器学习算法(如梯度增强),我们可以查看训练模型,并让它告诉我们每个特征在决定最终价格中的使用频率。
首先,我们使用joblib.load来加载模型。

# Load the trained model created with train_model.py

model = joblib.load('trained_house_classifier_model.pkl')

如果你还没有生成train house classifier model.pkl文件,只需打开train_model.py并运行它来生成。现在我们可以从我们训练的模型中获得每个特征的重要性。为此,我们调用以下划线结尾的model.feature重要性。

# Create a numpy array based on the model's feature importances

importance = model.feature_importances_

在scikit-learn中,这步将给我们一个包含每个特征的重要性的数组。所有特征的重要性的总和加起来为1,因此您可以将此视为百分比评定该特征用于确定房屋价值的频率。
为了使特征列表更易于阅读,让我们根据重要性对它们进行排序。我们将使用numpy的argsort函数给出指向数组中每个元素的数组索引列表。然后,我们将使用一个前向循环打印出每个特征的名称和它的重要性。

# Sort the feature labels based on the feature importance rankings from the model
feauture_indexes_by_importance = importance.argsort()

# Print each feature label, from most important to least important (reverse order)
for index in feauture_indexes_by_importance:
    print("{} - {:.2f}%".format(feature_labels[index], (importance[index] * 100.0)))

让我们来运行这个程序。结果如下:

city_Julieberg - 0.00%
city_New Robinton - 0.00%
city_New Michele - 0.00%
city_Martinezfort - 0.00%
city_Davidtown - 0.04%
city_Rickytown - 0.08%
...
stories - 2.06%
half_bathrooms - 2.14%
full_bathrooms - 3.60%
carport_sqft - 3.95%
num_bedrooms - 4.66%
year_built - 13.18%
garage_sqft - 13.44%
livable_sqft - 16.59%
total_sqft - 16.91%

我们可以看到,这些最后几个特征是房子价格中最重要的。确定房屋价格的最重要的因素是规模,建筑年份,车库的大小,卧室的数量和浴室的数量。
如果我们稍微向上滚动,我们可以看到其他因素,比如房子有多少个故事,或者有没有一个游泳池,都会影响价格,但是它们往往不是那么重要。如果你一路走到名单的最前面,我们可以看到,有些因素,如房子是否在新米歇尔市,根本不会影响价格。但总的来说,在我们的例子中,这63个特征中的大部分都被使用了,但是如果你有一个非常大的模型,有成千上万的特征,你可以用这种方法来选择要保留哪些特征,哪些特征在下一次训练的时候可以丢掉。即使你不排除模型中的任何特征,这也是了解你的模型实际在做什么的好方法。
例如,如果你看到模型认为最重要的特征是你认为并不重要的特征,也就意味着你的模型还不能很好地工作。

结语

下一章将会讲解最后一章, 如何使用我们的模型来预测真实的数据, 欢迎关注.

你的 关注-收藏-转发 是我继续分享的动力!

上一篇下一篇

猜你喜欢

热点阅读