大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
个人主页-Sonhhxg_柒的博客_CSDN博客
欢迎各位→点赞 + 收藏⭐️ + 留言
系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟
文章目录
编码混合数据
加载数据中
清除空值
One-hot encoding
结合 one-hot 编码矩阵和数值列
自定义 scikit-learn 转换器
定制transformers
自定义混合空值输入器
One-hot 编码混合数据
预处理管道
完成 XGBoost 模型
第一个 XGBoost 模型
微调 XGBoost 超参数
测试模型
构建机器学习管道
概括
在关于 XGBoost 的最后一章中,您将把所有东西放在一起并开发新技术来构建一个强大的工业机器学习模型。为行业部署模型与为研究和竞赛构建模型略有不同。在工业中,自动化很重要,因为新数据频繁出现。更多的重点放在程序上,而较少强调通过调整机器学习模型来获得微小的百分点。
具体来说,在本章中,您将获得有关one-hot 编码和稀疏矩阵的重要经验。此外,您将实施和自定义 scikit-learn 转换器,以自动化机器学习管道,以对混合了分类和数值列的数据进行预测。在本章结束时,您的机器学习管道将为任何传入数据做好准备。
在本章中,我们将介绍以下主题:
编码混合数据
自定义 scikit-learn 转换器
完成 XGBoost 模型
构建机器学习管道
想象一下,您正在为一家教育科技公司工作你的工作是预测学生的成绩,以针对旨在弥合技术技能差距的服务。您的第一步是将包含学生成绩的数据加载到pandas中。
学生表现数据集,提供您的公司可以通过加载已为您导入的student-por.csv文件来访问。
首先导入pandas并消除警告。然后,下载数据集并查看前五行:
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
df = pd.read_csv('student-por.csv')
df.head()
这是预期的输出:
图 10.1 – 学生表现数据集原样
欢迎来到工业世界,数据并不总是如预期的那样出现。
推荐的选项是查看 CSV 文件。这可以在 Jupyter Notebooks 中通过找到本章的文件夹并单击student-por.csv文件来完成。
图 10.2 – 学生成绩 CSV 文件
如上图所示,数据用分号隔开。CSV 展台对于逗号分隔值,而不是分号分隔值。幸运的是,pandas带有一个sep参数,代表分隔符,可以设置为分号,(;),如下:
df = pd.read_csv('student-por.csv', sep=';')
df.head()
这是预期的输出:
图 10.3 – 学生表现数据集
现在 DataFrame 看起来像预期的那样,混合对于分类值和数值,我们必须清理空值。
可以查看空值的所有列通过调用 df.insull() 上的.sum( )方法。以下是结果的摘录:
df.isnull().sum()
school 0
sex 1
age 1
address 0
…
health 0
absences 0
G1 0
G2 0
G3 0
dtype: int64
您可以使用条件表示法查看这些列的行,方法是将df.isna().any(axis=1)放在带有df的括号内:
df[df.isna().any(axis=1)]
图 10.4 – 学生表现空数据
最好在中间看到空列,Jupyter 默认将其删除,因为列。这很容易通过将最大列设置为None来纠正,如下所示:
pd.options.display.max_columns = None
现在,再次运行代码会显示所有列:
df[df.isna().any(axis=1)]
图 10.5 – 学生表现数据集所有行的空数据
如您所见,现在显示所有列,包括“监护人”下的隐藏空值。
数值空值可以设置为 -999.0 或其他值,XGBoost 将使用第 5 章XGBoost Unveiled中介绍的缺失超参数为您找到最佳替代品。
这是用-999.0填充“年龄”列的代码:
df['age'].fillna(-999.0)
接下来,可以按模式填充分类列。众数是列中最常见的情况。但是,仅当空值的数量很大时,使用众数填充分类列可能会扭曲结果分布。只有两个空值存在,所以我们的分布不会受到影响。另一种选择包括用“未知”字符串替换分类空值,这可能在一次性编码后成为自己的列。请注意,XGBoost 需要数字输入,因此截至 2020 年,缺失的超参数无法直接应用于分类列。
以下代码将'sex'和'guardian'分类列转换为mode:
df['sex'] = df['sex'].fillna(df['sex'].mode())
df['guardian'] = df['guardian'].fillna(df['guardian'].mode())
因为我们的空值在前两个行,我们可以使用df.head()显示它们已被更改:
df.head()
图 10.6 – 删除空值的学生表现数据集(仅前五行)
空值已按预期全部清除。
接下来,我们将使用 one-hot 编码将所有分类列转换为数值列。
之前,我们使用pd.get_dummies进行改造所有分类变量为数值0和1的值,0表示不存在,1表示存在。虽然可以接受,但这种方法有一些缺点。
第一个缺点是pd.get_dummies的计算量可能很大,正如您在前几章中等待代码运行时可能已经发现的那样。第二个缺点是pd.get_dummies不能很好地转换为 scikit-learn 的管道,我们将在下一节中探讨这个概念。
pd.get_dummies的一个不错的替代品是 scikit-learn 的OneHotEncoder。与pd.get_dummies一样,one-hot 编码将所有分类值转换为0和1,其中0表示不存在,1表示存在,但与pd.get_dummies不同的是,它的计算成本并不高。OneHotEncoder使用稀疏矩阵而不是密集矩阵来节省空间和时间。
稀疏矩阵通过仅存储值不包括 0 的数据来节省空间。通过使用更少的位来保存相同数量的信息。
此外,OneHotEncoder是一个 scikit-learn 转换器,这意味着它专门设计用于机器学习管道。
在过去的 scikit-learn 版本中,OneHotEncoder只接受数字输入。在这种情况下,使用LabelEncoder采取中间步骤,首先将所有分类列转换为数值列。
要在特定列上使用OneHotEncoder,您可以使用以下步骤:
将dtype对象的所有分类列转换为列表:
categorical_columns = df.columns[df.dtypes==object].tolist()
导入并初始化OneHotEncoder:
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder()
在列上使用fit_transform方法:
hot = ohe.fit_transform(df[categorical_columns])
可选:将 one-hot 编码的稀疏矩阵转换为标准数组并转换为 DataFrame 以供查看:
hot_df = pd.DataFrame(hot.toarray())
hot_df.head()
图 10.7 – one-hot 编码矩阵的 DataFrame
这看起来符合预期,所有值都是0或1。
如果你想看看热稀疏矩阵实际上是什么样子的,你可以打印出来如下:
print(hot)
以下是结果的摘录:
(0, 0) 1.0
(0, 2) 1.0
(0, 5) 1.0
(0, 6) 1.0
(0, 8) 1.0
…
(648, 33) 1.0
(648, 35) 1.0
(648, 38) 1.0
(648, 40) 1.0
(648, 41) 1.0
如您所见,仅跳过了0的值。例如,由 ( 0, 1 ) 表示的第 0 行和第 1 列在密集矩阵中的值为0.0,但在 one-hot 矩阵中被跳过。
如果您想了解更多关于稀疏矩阵,只需输入以下变量:
hot
结果如下:
<649x43 sparse matrix of type ''
with 11033 stored elements in Compressed Sparse Row format>
这告诉我们矩阵是649 x 43,但只存储了11033个值,从而节省了大量空间。请注意,对于具有许多零的文本数据,稀疏矩阵非常常见。
现在我们有一个one-hot 编码稀疏矩阵,我们必须将它与原始 DataFrame 的数值列结合起来。
首先,让我们隔离数字列。这可以通过exclude=["object"]参数作为df.select_dtypes的输入来完成,该参数选择某些类型的列,如下所示:
cold_df = df.select_dtypes(exclude=["object"])
cold_df.head()
图 10.8 – 学生表现数据集的数值列
这些是我们正在寻找的列。
对于这种大小的数据,我们有一个的选择将稀疏矩阵转换为常规 DataFame,如前面的屏幕截图所示,或者将此 DataFrame 转换为稀疏矩阵。考虑到工业中的 DataFrame 可以变得巨大并且节省空间可能是有利的,让我们追求后者:
要将Cold_df DataFrame 转换为压缩的稀疏矩阵,请从scipy.sparse导入csr_matrix并将 DataFrame 放入其中,如下所示:
from scipy.sparse import csr_matrix
cold = csr_matrix(cold_df)
最后,通过导入和使用hstack来堆叠热矩阵和冷矩阵,它水平组合稀疏矩阵:
from scipy.sparse import hstack
final_sparse_matrix = hstack((hot, cold))
通过将稀疏矩阵转换为密集矩阵并像往常一样显示 DataFrame 来验证final_sparse_matrix是否按预期工作:
final_df = pd.DataFrame(final_sparse_matrix.toarray())
final_df.head()
图 10.9 – 最终稀疏矩阵的 DataFrame
输出向右移动到一起显示 one-hot 编码和数字列。
现在数据是准备好机器学习,让我们使用转换器和管道来自动化这个过程。
现在我们有一个将 DataFrame 转换为机器学习就绪的稀疏矩阵的过程,使用转换器概括该过程将是有利的,以便可以轻松地重复新数据的输入。
Scikit-learn 转换器通过使用fit方法(查找模型参数)和transform方法(将这些参数应用于数据)与机器学习算法一起工作。这些方法可以组合成一个fit_transform方法,在一行代码中拟合和转换数据。
当一起使用时,包括机器学习算法在内的各种转换器可以在同一管道中一起工作,以方便使用。然后将数据放置在适合并转换以实现所需输出的管道中。
Scikit-learn 带有许多出色的转换器,例如分别用于标准化和规范化数据的StandardScaler和Normalizer ,以及用于转换空值的SimpleImputer 。但是,当数据包含分类列和数字列的混合时,您必须小心,就像这里的情况一样。在某些情况下,scikit-learn 选项可能不是自动化的最佳选择。在这种情况下,值得创建自己的转换器来完全按照您的意愿行事。
创建自己的关键transformers 是使用 scikit-learn 的TransformerMixin作为你的超类。
这是在 scikit-learn 中创建自定义转换器的一般代码大纲:
class YourClass(TransformerMixin):
def __init__(self):
None
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
# insert code to transform X
return X
如您所见,您不必初始化任何东西,并且fit总是可以返回self。简而言之,您可以将所有用于转换数据的代码放在transform方法下。
现在您已经了解了自定义的一般工作原理,让我们创建一个自定义转换器来处理不同类型的空值。
让我们通过创建一个定制的混合空值输入器。在这里,定制的原因是为了处理不同类型的列,使用不同的方法来纠正空值。
以下是步骤:
导入TransformerMixin并定义一个以TransformerMixin为超类的新类:
from sklearn.base import TransformerMixin
class NullValueImputer(TransformerMixin):
使用self作为输入初始化类。如果这什么都不做也没关系:
def __init__(self):
None
创建一个fit方法,将self和X作为输入,使用y=None并返回self:
def fit(self, X, y=None):
return self
创建一个将self和X作为输入的转换方法,使用y=None,并通过返回一个新的X来转换数据,如下所示:
def transform(self, X, y=None):
我们需要根据列分别处理空值。
以下是转换 null 的步骤模式或-999.0的值,具体取决于列类型:
a) 通过将列转换为列表来循环它们:
for column in X.columns.tolist():
b) 在循环中,通过检查哪些列属于对象dtype 来访问属于字符串的列:
if column in X.columns[X.dtypes==object].tolist():
c) 将字符串(对象)列的空值转换为模式:
X[column] = X[column].fillna(X[column].mode())
d) 否则,用-999.0填充列:
else:
X[column]=X[column].fillna(-999.0)
return X
在前面的代码中,您可能想知道为什么使用y=None。原因是在管道中包含机器学习算法时需要y作为输入。通过将y设置为None,只会按预期对预测列进行更改。
现在已经定义了自定义的 imputer,可以通过对数据调用fit_transform方法来使用它。
让我们通过从 CSV 文件中建立一个新的 DataFrame 并在一行代码中转换空值来重置数据使用定制的NullValueImputer:
df = pd.read_csv('student-por.csv', sep=';')
nvi = NullValueImputer().fit_transform(df)
nvi.head()
图 10.10 – NullValueImputer() 之后的 Student Performance DataFrame
如您所见,所有空值已被清除。
接下来,让我们像以前一样将数据转换为 one-hot 编码的稀疏矩阵。
我们将在这里应用类似的步骤通过创建一个定制的转换器来对分类列进行一次热编码,然后将它们与数值列连接为稀疏矩阵(对于这种大小的数据集,密集矩阵也可以),与上一节中的那些相比:
定义一个以TransformerMixin作为超类的新类:
class SparseMatrix(TransformerMixin):
使用self作为输入初始化类。如果这什么都不做也没关系:
def __init__(self):
None
创建一个以self和X作为输入并返回self的fit方法:
def fit(self, X, y=None):
return self
创建一个将self和X作为输入的转换方法,转换数据,并返回一个新的X:
def transform(self, X, y=None):
以下是步骤完成改造;首先仅访问属于对象类型的分类列,如下所示:
a)将分类列放在列表中:
categorical_columns= X.columns[X.dtypes==object].tolist()
b) 初始化OneHotEncoder:
ohe = OneHotEncoder()
c) 使用OneHotEncoder转换分类列:
hot = ohe.fit_transform(X[categorical_columns])
d) 仅通过排除字符串来创建数值列的 DataFrame:
cold_df = X.select_dtypes(exclude=["object"])
e) 将数值 DataFrame 转换为稀疏矩阵:
cold = csr_matrix(cold_df)
f) 将两个稀疏矩阵合并为一个:
final_sparse_matrix = hstack((hot, cold))
g) 将其转换为压缩稀疏行( CSR )矩阵以限制错误。请注意,XGBoost 需要 CSR 矩阵,并且此转换可能会根据您的 XGBoost 版本自动发生:
final_csr_matrix = final_sparse_matrix.tocsr()
return final_csr_matrix
现在我们可以使用 SparseMatrix 上强大的 fit_transform 方法来转换没有空值的nvi数据:
sm = SparseMatrix().fit_transform(nvi)
print(sm)
此处给出的预期输出被截断节省空间:
(0, 0) 1.0
(0, 2) 1.0
(0, 5) 1.0
(0, 6) 1.0
(0, 8) 1.0
(0, 10) 1.0
: :
(648, 53) 4.0
(648, 54) 5.0
(648, 55) 4.0
(648, 56) 10.0
(648, 57) 11.0
(648, 58) 11.0
您可以通过将稀疏矩阵转换回密集矩阵来验证数据是否符合预期,如下所示:
sm_df = pd.DataFrame(sm.toarray())
sm_df.head()
图 10.11 – 稀疏矩阵转换为密集矩阵
这似乎是正确的。该图显示了一个第 27 列的值为 0.0,第28列的值为1.0。前面的 one-hot 编码输出排除 ( 0 , 27 ) 并显示( 0 , 28 )的值1.0,与密集输出匹配。
现在数据已经转换,让我们将两个预处理步骤组合到一个管道中。
在构建机器学习时模型,首先将数据分成X和y是标准的。在考虑 pipeline时,转换预测列X而不是目标列y是有意义的。此外,为以后保留一个测试集也很重要。
在将数据放入机器学习管道之前,让我们将数据拆分为训练集和测试集,并留下测试集。我们从顶部开始如下:
首先,将 CSV 文件作为 DataFrame 读取:
df = pd.read_csv('student-por.csv', sep=';')
在为学生表现数据集选择X和y时,请务必注意最后三列都包括学生成绩。两项潜在的研究在这里很有价值:
a) 包括以前的成绩作为预测列
b) 不包括以前的成绩作为预测列
假设您的 EdTech 公司希望根据社会经济变量而不是以前获得的成绩进行预测,因此请忽略索引为 - 2和 - 3的前两个成绩列。
选择最后一列作为y,以及所有列除了最后三个为X:
y = df.iloc[:, -1]
X = df.iloc[:, :-3]
现在导入train_test_split并将X和y拆分为训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=2)
现在让我们使用以下步骤构建管道:
首先从sklearn.pipeline导入管道:
from sklearn.pipeline import Pipeline
接下来,使用语法(name,transformer)按顺序分配元组作为Pipeline的参数:
data_pipeline = Pipeline([('null_imputer', NullValueImputer()),
('sparse', SparseMatrix())])
最后,通过将X_train放在data_pipeline的fit_transform方法中来转换我们的预测列X_train:
X_train_transformed = data_pipeline.fit_transform(X_train)
现在您有了一个没有空值的数字稀疏矩阵,可以用作机器学习的预测列。
此外,您有一个可用于在一行代码中转换任何传入数据的管道!现在让我们最终确定一个 XGBoost 模型来进行预测。
是时候构建一个强大的 XGBoost 模型来添加了管道。继续导入XGBRegressor、numpy、GridSearchCV、cross_val_score、KFold和mean_squared_error,如下所示:
import numpy as np
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score, KFold
from sklearn.metrics import mean_squared_error as MSE
from xgboost import XGBRegressor
现在让我们构建模型。
这个学生表现数据集有预测变量列y_train的一个有趣的值范围,如下所示:
y_train.value_counts()
结果是这样的:
11 82
10 75
13 58
12 53
14 42
15 36
9 29
16 27
8 26
17 24
18 14
0 10
7 7
19 1
6 1
5 1
如您所见,值范围为5 - 19,其中包括0。
由于目标列是序数,这意味着值是按数字顺序排列的,即使输出有限,回归也比分类更可取。在通过回归训练模型后,可能会对最终结果进行四舍五入以给出最终预测。
以下是使用此数据集对XGBRegressor进行评分的步骤:
首先使用KFold设置交叉验证:
kfold = KFold(n_splits=5, shuffle=True, random_state=2)
现在定义一个交叉验证函数使用cross_val_score返回均方根误差:
def cross_val(model):
scores = cross_val_score(model, X_train_transformed,
y_train, scoring='neg_root_mean_squared_error', cv=kfold)
rmse = (-scores.mean())
return rmse
通过使用XGBRegressor作为输入并使用missing=-999.0调用cross_val来建立基本分数,以便 XGBoost 可以找到最佳替换:
cross_val(XGBRegressor(missing=-999.0))
分数是这样的:
2.9702248207546296
这是一个可观的开始得分。一个根19 种可能性中2.97的均方误差表明等级在几个准确度范围内。这几乎是 15%,使用美国 ABCDF 系统在一个字母等级内是准确的。在工业中,您甚至可以使用统计数据来包含置信区间来提供预测区间,这是本书范围之外的推荐策略。
现在您有了基线分数,让我们微调超参数以改进模型。
让我们从早期检查n_estimators开始停止。回想一下,要使用提前停止,我们可能会检查一个测试折叠。创建测试折叠需要进一步拆分X_train和y_train:
这是第二个train_test_split可用于创建测试集以进行验证,确保隐藏真实的测试集以供以后使用:
X_train_2, X_test_2, y_train_2, y_test_2 = train_test_split(X_train_transformed, y_train, random_state=2)
现在定义一个函数,该函数使用提前停止来返回回归器的最佳估计数(参见第 6 章,XGBoost 超参数):
def n_estimators(model):
eval_set = [(X_test_2, y_test_2)]
eval_metric="rmse"
model.fit(X_train_2, y_train_2, eval_metric=eval_metric, eval_set=eval_set, early_stopping_rounds=100)
y_pred = model.predict(X_test_2)
rmse = MSE(y_test_2, y_pred)**0.5
return rmse
现在运行n_estimators函数,设置最大为5000:
n_estimators(XGBRegressor(n_estimators=5000, missing=-999.0))
以下是输出的最后五行:
[128] validation_0-rmse:3.10450
[129] validation_0-rmse:3.10450
[130] validation_0-rmse:3.10450
[131] validation_0-rmse:3.10450
Stopping. Best iteration:
[31] validation_0-rmse:3.09336
分数如下:
3.0933612343143153
使用我们的默认模型,目前有 31 个估计器给出了最好的估计。这将是我们的起点。
接下来,这里是我们多次使用的grid_search函数,它搜索超参数网格并显示最佳参数和最佳分数:
def grid_search(params, reg=XGBRegressor(missing=-999.0)):
grid_reg = GridSearchCV(reg, params, scoring='neg_mean_squared_error', cv=kfold)
grid_reg.fit(X_train_transformed, y_train)
best_params = grid_reg.best_params_
print("Best params:", best_params)
best_score = np.sqrt(-grid_reg.best_score_)
print("Best score:", best_score)
这里有几个微调模型的推荐步骤:
从max_depth开始,范围从1到8,同时将n_estimators设置为31:
grid_search(params={'max_depth':[1, 2, 3, 4, 6, 7, 8],
'n_estimators':[31]})
结果是这样的:
Best params: {'max_depth': 1, 'n_estimators': 31}
Best score: 2.6634430373079425
将 max_depth从1缩小到3,同时将min_child_weight从1缩小到5并将n_esimtators保持在31:
grid_search(params={'max_depth':[1, 2, 3],
'min_child_weight':[1,2,3,4,5],
'n_estimators':[31]})
结果是这样的:
Best params: {'max_depth': 1, 'min_child_weight': 1, 'n_estimators': 31}
Best score: 2.6634430373079425
没有任何改善。
您可以通过强制min_child_weight取值2或3来保证一些更改,同时包括从0.5到0.9的子样本范围。此外,增加n_estimators可能有助于为模型提供更多学习时间:
grid_search(params={'max_depth':[2],
'min_child_weight':[2,3],
'subsample':[0.5, 0.6, 0.7, 0.8, 0.9],
'n_estimators':[31, 50]})
Best params: {'max_depth': 1, 'min_child_weight': 2, 'n_estimators': 50, 'subsample': 0.9}
Best score: 2.665209161229433
分数是几乎相同,但稍差一些。
在colsample_bytree使用0.5到0.9的范围时缩小min_child_weight和subsample:
grid_search(params={'max_depth':[1],
'min_child_weight':[1, 2, 3],
'subsample':[0.6, 0.7, 0.8],
'colsample_bytree':[0.5, 0.6, 0.7, 0.8, 0.9, 1],
'n_estimators':[50]})
结果是这样的:
Best params: {'colsample_bytree': 0.9, 'max_depth': 1, 'min_child_weight': 3, 'n_estimators': 50, 'subsample': 0.8}
Best score: 2.659649642579931
这是迄今为止最好的成绩。
保持最佳当前值,使用colsample_bynode和colsample_bylevel尝试从0.6到1.0的范围:
grid_search(params={'max_depth':[1],
'min_child_weight':[3],
'subsample':[.8],
'colsample_bytree':[0.9],
'colsample_bylevel':[0.6, 0.7, 0.8, 0.9, 1],
'colsample_bynode':[0.6, 0.7, 0.8, 0.9, 1],
'n_estimators':[50]})
结果是在这里给出:
Best params: {'colsample_bylevel': 0.9, 'colsample_bynode': 0.8, 'colsample_bytree': 0.9, 'max_depth': 1, 'min_child_weight': 3, 'n_estimators': 50, 'subsample': 0.8}
Best score: 2.64172735526102
分数又提高了。
用基础学习器对dart和gamma进行进一步的实验并没有新的收获。
根据项目的时间和范围,可能值得进一步调整超参数,甚至在RandomizedSearch中一起尝试它们。在工业中,您很有可能可以访问云计算,其中廉价、可抢占的虚拟机( VM ) 将允许更多的超参数搜索以找到更好的结果。请注意,scikit-learn 目前不提供在代码完成之前停止耗时搜索以保存最佳参数的方法。
现在我们有了一个健壮的模型,我们可以继续前进并测试模型。
现在你有一个潜在的最终模型,根据测试集对其进行测试很重要。
回想一下,测试集没有在我们的管道中转换。幸运的是,此时只需一行代码即可对其进行转换:
X_test_transformed = data_pipeline.fit_transform(X_test)
现在我们可以使用上一节中选择的最佳调整超参数初始化一个模型,将其拟合到训练集上,并针对保留的测试集对其进行测试:
model = XGBRegressor(max_depth=2, min_child_weight=3, subsample=0.9, colsample_bytree=0.8, gamma=2, missing=-999.0)
model.fit(X_train_transformed, y_train)
y_pred = model.predict(X_test_transformed)
rmse = MSE(y_pred, y_test)**0.5
rmse
分数如下:
2.7908972630881435
分数要高一些,尽管这可能是由于折叠。
如果不是,我们的模型与验证集的拟合过于紧密,这可能在微调超参数并密切调整它们以改进验证集时发生。该模型泛化得相当好,但它可以更好地泛化。
对于接下来的步骤,在考虑是否可以提高分数时,可以使用以下选项:
回到超参数微调。
保持模型不变。
根据超参数知识进行快速调整。
快速调整超参数是可行的,因为模型可能过度拟合。例如,增加min_child_weight和降低子样本应该有助于模型更好地泛化。
让我们做最后的调整最终模型:
model = XGBRegressor(max_depth=1,
min_child_weight=5,
subsample=0.6,
colsample_bytree=0.9,
colsample_bylevel=0.9,
colsample_bynode=0.8,
n_estimators=50,
missing=-999.0)
model.fit(X_train_transformed, y_train)
y_pred = model.predict(X_test_transformed)
rmse = MSE(y_pred, y_test)**0.5
rmse
结果如下:
2.730601403138633
请注意,分数有所提高。
此外,您绝对不应该来回尝试提高保留测试分数。制作一个是可以接受的然而,在收到考试成绩后很少调整;否则,您将永远无法改进第一个结果。
现在剩下的就是完成管道。
完成机器学习管道需要将机器学习模型添加到之前的管道中。在NullValueImputer和SparseMatrix之后需要一个机器学习元组,如下所示:
full_pipeline = Pipeline([('null_imputer', NullValueImputer()),
('sparse', SparseMatrix()),
('xgb', XGBRegressor(max_depth=1, min_child_weight=5, subsample=0.6,
colsample_bytree=0.9, colsample_bylevel=0.9, colsample_bynode=0.8, missing=-999.0))])
这个管道现在已经完成了一个机器学习模型,它可以适应任何X,y组合,如下所示:
full_pipeline.fit(X, y)
现在您可以对目标列未知的任何数据进行预测:
new_data = X_test
full_pipeline.predict(new_data)
以下是预期输出的前几行:
array([13.55908 , 8.314051 , 11.078157 , 14.114085 , 12.2938385, 11.374797 , 13.9611025, 12.025812 , 10.80344 , 13.479145 , 13.02319 , 9.428679 , 12.57761 , 12.405045 , 14.284043 , 8.549758 , 10.158956 , 9.972576 , 15.502667 , 10.280028 , ...
为了得到现实的预测,数据可能会被四舍五入如下:
np.round(full_pipeline.predict(new_data))
这里给出了预期的输出:
array([14., 8., 11., 14., 12., 11., 14., 12., 11., 13., 13., 9.,
13., 12., 14., 9., 10., 10., 16., 10., 13., 13., 7., 12., 7.,
8., 10., 13., 14., 12., 11., 12., 15., 9., 11., 13., 12., 11., 8.,
...
11., 13., 12., 13., 9., 13., 10., 14., 12., 15., 15., 11., 14.,
10., 14., 9., 9., 12., 13., 9., 11., 14., 13., 11., 13., 13.,
13., 13., 11., 13., 14., 15., 13., 9., 10., 13., 8., 8., 12.,
15., 14., 13., 10., 12., 13., 9.], dtype=float32)
最后,如果有新数据出现,它可以与以前的数据连接并通过相同的管道放置一个更强大的模型,因为新模型可能适合更多数据,如下所示:
new_df = pd.read_csv('student-por.csv')
new_X = df.iloc[:, :-3]
new_y = df.iloc[:, -1]
new_model = full_pipeline.fit(new_X, new_y)
现在,该模型可用于对新数据进行预测,如下代码所示:
more_new_data = X_test[:25]
np.round(new_model.predict(more_new_data))
预期输出如下:
array([14., 8., 11., 14., 12., 11., 14., 12., 11., 13., 13., 9.,
13., 12., 14., 9., 10., 10., 16., 10., 13., 13., 7., 12., 7.], dtype=float32)
有一个小问题。
如果您只想对一行数据进行预测怎么办?如果您通过管道运行单行,则生成的稀疏矩阵将没有正确的列数,因为它只会对单行中存在的类别进行一次热编码。这将导致数据中的不匹配错误,因为机器学习模型已经适合需要更多数据行的稀疏矩阵。
一个简单的解决方案是将新的数据行与足够的数据行连接起来,以保证存在完整的稀疏矩阵,并转换所有可能的分类列。我们已经看到这适用于X_test的 25 行,因为没有错误。在这种特殊情况下,使用X_test中的20 行或更少行将导致不匹配错误。
所以,如果你想用单行数据,将单行与X_test的前25行连接起来,做如下预测:
single_row = X_test[:1]
single_row_plus = pd.concat([single_row, X_test[:25]])
print(np.round(new_model.predict(single_row_plus))[:1])
结果是这样的:
[14.]
您现在知道如何将机器学习模型包含在管道中以对新数据进行转换和预测。
恭喜你读完本书!这是一段非凡的旅程,从基本的机器学习和pandas开始,到构建您自己的自定义转换器、管道和函数,以在具有稀疏矩阵的行业场景中部署稳健、微调的 XGBoost 模型以对新数据进行预测。
一路走来,您已经了解了 XGBoost 的故事,从第一棵决策树到随机森林和梯度提升,然后发现使 XGBoost 如此特别的数学细节和复杂性。您一次又一次地看到 XGBoost 优于其他机器学习算法,并且在调整 XGBoost 的广泛超参数(包括n_estimators、max_depth、gamma、colsample_bylevel、missing和scale_pos_weight )方面获得了必要的实践。
您了解了物理学家和天文学家如何在具有历史意义的案例研究中获得有关我们宇宙的知识,并通过不平衡的数据集和替代基础学习器的应用了解了 XGBoost 的广泛范围。你甚至通过高级特征工程、非相关集成和堆叠从 Kaggle 比赛中学到了交易技巧。最后,您学习了先进的工业自动化流程。
此时,您对 XGBoost 的了解已达到高级水平。您现在可以高效、快速、强大地使用 XGBoost 来解决您遇到的机器学习问题。当然,XGBoost 并不完美。如果您正在处理图像或文本等非结构化数据,神经网络可能会更好地为您服务。对于大多数机器学习任务,尤其是那些具有表格数据的任务,XGBoost 通常会给您带来优势。
如果你有兴趣通过 XGBoost 继续深造,我个人的建议是参加 Kaggle 比赛。原因是 Kaggle 比赛由经验丰富的机器学习从业者组成,与他们竞争会让你变得更好。此外,Kaggle 竞赛提供了一个结构化的机器学习环境,由许多解决同一问题的从业者组成,这导致共享笔记本和论坛讨论可以进一步促进教育过程。这也是 XGBoost 第一次在希格斯玻色子竞赛中获得非凡声誉的地方,正如本书所述。
现在,您可以使用 XGBoost 自信地进入大数据世界,推进研究、参加比赛并构建机器学习模型以供生产使用。