机器学习实战:基于Scikit-Learn、Keras和TensorFlow(原书第3版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.5 为机器学习算法准备数据

是时候为你的机器学习算法准备数据了。这里你应该为这个目的编写函数,而不是手动执行此操作,原因如下:

· 你可以在任何数据集上轻松地重现这些转换(例如,下次你获得新的数据集时)。

· 你可以逐步构建一个可在未来项目中重复使用的转换函数库。

· 你可以在实时系统中使用这些函数来转换新数据,再将其提供给你的算法。

· 你可以轻松尝试各种转换并查看哪种转换组合效果最好。

但首先,要恢复到干净的训练集(通过再次复制strat_train_set)。你还应该将预测变量和标签分开,因为你不一定要对预测变量和目标值应用相同的转换(请注意drop()创建数据的副本并且不影响strat_train_set)

2.5.1 清洗数据

大多数机器学习算法无法处理缺失的特征,因此你需要处理这些问题。例如,你之前注意到total_bedrooms属性有一些缺失值。你可以通过三个选项来解决此问题:

1.去掉相应的地区。

2.去掉整个属性。

3.将缺失值设置为某个值(零、均值、中位数等)。这称为归责。

你可以使用Pandas DataFrame的dropna()drop()fillna()方法轻松完成这些操作:

你决定选择选项3,因为它破坏性最小,但你将使用一个方便的Scikit-Learn类:SimpleImputer,而不是前面的代码。它的好处是将存储每个特征的中位数值:这不仅可以在训练集上估算缺失值,而且可以在验证集、测试集和任何提供给模型的新数据上估算缺失值。要使用它,首先你需要创建一个SimpleImputer实例,指定你要用该属性的中位数替换每个属性的缺失值:

由于只能根据数值属性来计算中位数,因此你需要创建仅包含数值属性的数据副本(这将排除文本属性ocean_proximity):

现在你可以使用fit()方法将imputer实例拟合到训练数据中:

imputer简单地计算了每个属性的中位数并将结果存储在它的statistics_实例变量中。只有total_bedrooms属性有缺失值,但你不能确定系统上线后新数据中不会有任何缺失值,所以对所有数值属性应用imputer更安全:

现在你可以使用这个“训练有素”的imputer来通过用学习到的中位数替换缺失值来转换训练集:

缺失值也可以替换为平均值(strategy="mean"),或替换为最频繁的值(strategy="most_frequent"),或替换为常数值(strategy="constant",fill_value=...)。最后两种策略支持非数值数据。

sklearn.impute包中还有更强大的imputers(两者都仅用于数值特征):

· KNNImputer将每个缺失值替换为该特征的k近邻的平均值。该距离基于所有可用的特征。

· IterativeImputer为每个特征训练一个回归模型,以根据所有其他可用特征预测缺失值。然后,它会根据更新后的数据再次训练模型,并多次重复该过程,在每次迭代中改进模型和替换值。

Scikit-Learn的设计

Scikit-Learn的API设计得非常好。这些是主要设计原则(https://homl.info/11[9]

一致性

  所有对象共享一个一致且简单的接口。

估计器

  任何可以根据数据集估计某些参数的对象都称为估计器(例如,SimpleImputer是估计器)。估计本身由fit()方法执行,它将一个数据集作为参数(或两个数据集用于监督学习算法,第二个数据集包含标签)。指导估计过程所需的任何其他参数都被视为超参数(例如SimpleImputerstrategy),并且必须将其设置为实例变量(通常通过构造函数参数)。

转换器

  一些估计器(例如SimpleImputer)也可以转换数据集,这些被称为转换器。同样,API很简单:转换由transform()方法执行,将要转换的数据集作为参数。它返回转换后的数据集。这种转换通常依赖于学习到的参数,就像SimpleImputer的情况一样。所有的转换器还有一个名为fit_transform()的便捷方法,相当于先调用fit()再调用transform()(但有时fit_transform()会经过优化,运行速度更快)。

预测器

  最后,一些估计器在给定数据集的情况下能够进行预测,这些被称为预测器。例如,第1章中的LinearRegression模型是一个预测器,给定一个国家的人均GDP,它预测生活满意度。预测器有一个predict()方法,它获取新实例的数据集并返回相应预测的数据集。它还有一个score()方法,可以在给定测试集(以及,在监督学习算法中对应的标签)的情况下测量预测的质量[10]

检查

  所有估计器的超参数都可以通过公开实例变量(例如,imputer.strategy)直接访问,并且所有估计器的学习参数可以通过带有下划线后缀的公共实例变量访问(例如,imputer.statistics_)

防止类扩散

  数据集表示为NumPy数组或SciPy稀疏矩阵,而不是自定义类。超参数只是常规的Python字符串或数字。

构成

  尽可能重用现有的构建块。例如,正如你看到的,很容易从任意序列的转换器来创建一个Pipeline估计器,然后是最终估计器。

合理的默认值

  Scikit-Learn为大多数参数提供了合理的默认值,可以轻松快速地创建基本工作系统。

Scikit-Learn转换器输出NumPy数组(或有时是SciPy稀疏矩阵),即使将Pandas DataFrame作为输入[11]。因此,imputer.transform(housing_num)的输出是一个 NumPy数组:X既没有列名也没有索引。幸运的是,将X包装在DataFrame中并从housing_num中恢复列名和索引并不难:

2.5.2 处理文本和类别属性

到目前为止,我们只处理了数字属性,但你的数据也可能包含文本属性。在这个数据集中,只有一个:ocean_proximity属性。让我们看看它在前几个实例中的值:

它不是任意文本:可能的值数量有限,每个值代表一个类别。所以这个属性是一个类别属性。大多数机器学习算法更喜欢处理数字,所以让我们将这些类别从文本转换为数字。为此,我们可以使用Scikit-Learn的OrdinalEncoder类:

这是housing_cat_encoded中前几个编码值的样子:

你可以使用categories_实例变量来获取类别列表。它是一个包含每个分类属性的一维类别数组的列表(在本例中,列表包含一个数组,因为只有一个分类属性):

这种表示的一个问题是ML算法会假设两个距离较近的值比两个距离较远的值更相似。这在某些情况下可能没问题(例如,对于有序类别,如“坏”“平均”“好”和“优秀”),但ocean_proximity列显然不是情况(例如,类别0和4显然比类别0和1更相似)。要解决此问题,一种常见的解决方案是为每个类别创建一个二进制属性:当类别为"<1H OCEAN"时,这个属性等于1(否则为0),当类别为"INLAND"时,另一个属性等于1 (否则为0),以此类推。这称为独热编码,因为只有一个属性会等于1(热),而其他属性将为0(冷)。新属性有时称为虚拟属性。Scikit-Learn提供了一个OneHotEncoder类来将类别值转换为独热向量:

默认情况下,OneHotEncoder的输出是SciPy稀疏矩阵,而不是NumPy数组:

对于主要包含零的矩阵,稀疏矩阵是一种非常有效的表示。实际上,它在内部仅存储非零值及其位置。当一个类别属性有成百上千个类别时,独热编码会产生一个非常大的矩阵,除了每行一个1之外其他元素全是0。在这种情况下,稀疏矩阵正是你所需要的:它将节省大量内存并加快计算速度。你可以像普通二维数组一样使用稀疏矩阵[12],但如果你想将其转换为(密集)NumPy数组,只需调用toarray()方法:

或者,你可以在创建OneHotEncoder时设置sparse=False,在这种情况下,transform()方法将直接返回常规的(密集)NumPy数组。

OrdinalEncoder一样,你可以使用编码器的categories_实例变量获取类别列表:

Pandas有一个名为get_dummies()的函数,它将每个分类特征转换为独热表示,每个类别有一个二元特征:

它看起来既漂亮又简单,那么为什么不使用它来代替OneHotEncoder呢?好吧,OneHotEncoder的优势在于它会记住经过了哪些类别的训练。这非常重要,因为一旦你的模型投入生产,它应该被提供与训练期间完全相同的特征:不多也不少。看看经过训练的cat_encoder在我们转换相同的df_test时会输出什么[使用transform(),而不是fit_transform()]:

看到不同了吗?get_dummies()只看到两个类别,因此它输出两列,而OneHotEncoder以正确的顺序为每个学习到的类别输出一列。此外,如果你向get_dummies()提供一个包含未知类别(例如,"<2H OCEAN")的DataFrame,它会为其生成一列:

OneHotEncoder更聪明:它会检测未知类别并引发异常。如果你愿意,可以将handle_unknown超参数设置为“忽略”,在这种情况下,它将只用零表示未知类别:

如果分类属性有大量可能的类别(例如,国家代码、职业、物种),则独热编码将导致大量输入特征。这可能会减慢训练速度并降低性能。如果发生这种情况,你可能希望用与类别相关的有用数值特征来替换分类输入。例如,你可以将ocean_proximity特征替换为到海洋的距离(类似地,国家代码可以替换为国家的人口数量和人均GDP)。你也可以使用GitHub(https://github.com/scikit-learn-contrib/category_encoders)上的category_encoders包提供的编码器。或者,在处理神经网络时,你可以用称为嵌入的可学习的低维向量替换每个类别。这是表示的一个示例(更多细节参见第13章和第17章)。

当你使用DataFrame拟合任何Scikit-Learn估计器时,估计器会将列名称存储在feature_names_in_属性中。然后,Scikit-Learn确保之后馈送到该估计器的任何DataFrame [例如,到transform()predict()]具有相同的列名。Transformer还提供了一个get_feature_names_out()方法,你可以使用该方法围绕Transformer的输出构建DataFrame:

2.5.3 特征缩放和转换

你需要应用于数据的最重要的转换之一是特征缩放。除了少数例外,机器学习算法在输入数值属性具有非常不同的尺度时表现不佳。房屋数据就是这种情况:房间总数大约在6~39 320之间,而收入中位数仅在0~15之间。如果不进行任何缩放,大多数模型将偏向于忽略收入中位数并更多地关注于房间的数量。

有两种常用的方法可以使所有属性具有相同的尺度:最小-最大缩放和标准化。

与所有估计器一样,重要的是仅把缩放器拟合到训练数据:永远不要对训练集以外的任何其他对象使用fit()fit_transform()。一旦你有了一个训练好的缩放器,你就可以用它来transform()任何其他集合,包括验证集、测试集和新数据。请注意,虽然训练集值将始终缩放到指定范围,如果新数据包含异常值,这些值可能最终会缩放到范围之外。如果你想避免这种情况,只需将clip超参数设置为True

最小-最大缩放(很多人称之为归一化)是最简单的:对于每个属性,值被移动和重新缩放,这样它们最终值在0~1之间。这是通过减去最小值并除以最小值和最大值之间的差值来执行的。Scikit-Learn为此提供了一个名为MinMaxScaler的转换器。它有一个feature_range超参数,如果出于某种原因你不想要0~1,则允许你更改范围(例如,神经网络在零均值输入下效果最好,因此-1~1的范围更可取)。它很容易使用:

标准化是不同的:首先它减去平均值(因此标准化值的均值为零),然后将结果除以标准差(因此标准化值的标准差等于1)。与最小-最大缩放不同,标准化不会将值限制在特定范围内。但是,标准化受异常值的影响要小得多。例如,假设一个地区的收入中位数等于100(错误数据),而不是通常的0~15。最小-最大缩放到0~1范围会将此异常值映射到1,并将所有其他值压缩到0~0.15,而标准化不会受到太大影响。Scikit-Learn提供了一个名为StandardScaler的转换器用于标准化:

如果你想缩放稀疏矩阵而不先将其转换为密集矩阵,则可以使用StandardScaler,并将其with_mean超参数设置为False:它只会将数据除以标准差,而不减去均值(因为这会破坏稀疏性)。

当一个特征的分布有一个重尾(heavy tail)时(即当远离平均值的值不是指数级稀有时),最小-最大缩放和标准化都会将大多数值压缩到一个小范围内。机器学习模型通常不喜欢这样,正如你将在第4章中看到的那样。因此,在缩放特征之前,你应该首先对其进行变换来缩小重尾,并尽可能使分布大致对称。例如,对于右侧有重尾的正特征,一种常见的方法是用它的平方根来替换特征(或将特征提升到0~1之间的幂)。如果该特征有一个很长很重的尾巴,例如幂律(power law)分布,那么用对数替换该特征可能会有帮助。例如,population特征大致遵循幂律:拥有10 000名居民的地区出现频率仅比拥有1000名居民的地区低10倍,而不是呈指数级降低。图2-17展示了当你计算它的对数时这个特征看起来有多好:它非常接近高斯分布(即钟形)。

图2-17:变换特征使其更接近高斯分布

处理重尾特征的另一种方法是对特征进行分桶(bucketizing)。这意味着将其分布分成大致相等大小的桶,并用它所属的桶的索引替换每个特征值,就像我们创建income_cat特征时所做的一样(尽管我们只将其用于分层采样)。例如,你可以将每个值替换为其百分位数。使用大小相等的桶进行分桶会产生几乎均匀分布的特征,因此无须进一步缩放,或者你可以仅除以桶的数量来强制值在0~1范围内。

当一个特征具有多峰分布(即具有两个或更多清晰的峰,称为模式)时,例如housing_median_age特征,将其分桶也很有帮助,但这次将桶的ID视为类别,而不是数值。这意味着必须对桶索引进行编码,例如使用OneHotEncoder(所以你通常不想使用太多的桶)。这种方法将使回归模型更容易学习针对该特征值的不同范围的不同规则。例如,大约35年前建造的房屋可能具有一种过时的奇特风格,因此它们的价格比单凭其年龄所表明的要便宜。

另一种转换多峰分布的方法是为每个模式(至少是主要模式)添加一个特征,表示房屋年龄中位数与该特定模式之间的相似性。相似性度量通常使用径向基函数(Radial Basis Function,RBF)计算——任何一个仅取决于输入值和固定点之间距离的函数。最常用的RBF是高斯RBF,其输出值随着输入值远离固定点而呈指数衰减。例如,房屋年龄x和35之间的高斯RBF相似性由方程exp(-γx-35)2)给出。超参数γ(gamma)决定了当x远离35时相似性度量衰减的速度。使用Scikit-Learn的rbf_kernel()函数,可以创建一个新的高斯RBF特征来测量房屋年龄中位数与35之间的相似性:

图2-18展示了这个作为房屋年龄中位数(实线)的函数的新特征。它还展示了如果你使用较小的gamma值,该函数会是什么样子。如图2-18所示,新的年龄相似性特征在35处达到峰值,正好在房屋年龄中位数分布的峰值附近:如果这个特定年龄组与较低的价格密切相关,那么这个新特征很有可能会有所帮助。

图2-18:高斯RBF特征测量房屋年龄中位数与35之间的相似性

到目前为止,我们只查看了输入特征,但目标值可能还需要转换。例如,如果目标分布有一条重尾,你可以选择用其对数替换目标。但如果这样做,现在回归模型将预测房价中位数的对数,而不是房价中位数本身。如果你想要已预测的房屋中位数,则需要计算模型预测的指数。

幸运的是,大多数Scikit-Learn的转换器都有一个inverse_transform()方法,这使得计算它们的逆转换变得容易。例如,下面的代码展示了如何使用StandardScaler来缩放标签(就像我们对输入所做的那样),然后在生成的缩放标签上训练一个简单的线性回归模型,并使用它对一些新数据进行预测,我们使用经过训练的缩放器的inverse_transform()方法转换回原始尺度。请注意,我们将标签从Pandas Series转换为DataFrame,因为StandardScaler需要2D输入。此外,在此示例中,为简单起见,我们仅在单个原始输入特征(收入中位数)上训练模型:

这很好用,但更简单的选择是使用TransformedTargetRegressor。我们只需要构造它,给它回归模型和标签转换器,然后使用原始的未缩放标签,将它拟合到训练集上。它将自动使用转换器来缩放标签并在缩放后的标签上训练回归模型,就像我们之前所做的那样。然后,当我们想要进行预测时,它会调用回归模型的predict()方法并使用缩放器的inverse_transform()方法来产生预测:

2.5.4 定制转换器

尽管Scikit-Learn提供了许多有用的转换器,但你需要编写自己的转换器来执行自定义转换、清洗操作或组合一些特定的属性等任务。

对于不需要任何训练的转换,你只需编写一个函数,将NumPy数组作为输入并输出转换后的数组。例如,如上一节所述,通过将重尾分布的特征替换为它们的对数(假设特征为正且尾部在右侧)来转换具有重尾分布的特征通常是个好主意。让我们创建一个对数转换器并将其应用于population特征:

inverse_func参数是可选的。它允许你指定一个逆变换函数,例如,如果你计划在TransformedTargetRegressor中使用你的转换器。

你的转换函数可以将超参数作为附加参数。例如,下面是如何创建一个转换器来计算与之前相同的高斯RBF相似性度量:

请注意,RBF内核没有反函数,因为在距离固定点的给定距离处总是有两个值(距离0除外)。另请注意,rbf_kernel()不会单独处理这些特征。如果你向它传递一个具有两个特征的数组,它会测量2D距离(欧几里得)来测量相似性。例如,以下是如何添加一个特征来测量每个地区与旧金山之间的地理相似性:

自定义转换器也可用于组合特征。例如,这里有一个计算输入特征0和1之间比率的FunctionTransformer

FunctionTransformer非常方便,但是如果你希望你的转换器是可训练的,可以在fit()方法中学习一些参数并稍后在transform()方法中使用它们,你该怎么办?为此,你需要编写一个自定义类。Scikit-Learn依赖鸭子类型,因此此类不必继承自任何特定的基类。它只需要三个方法:fit()(必须返回self)transform()fit_transform()

你只需将TransformerMixin添加为基类即可以得到fit_transform():默认实现将只调用fit(),然后调用transform()。如果将BaseEstimator添加为基类(避免在构造函数中使用*args**kwargs),你还将获得两个额外的方法:get_params()set_params()。这些对于自动超参数调整很有用。

例如,这里有一个与StandardScaler非常相似的自定义转换器:

这里有几点需要注意:

· sklearn.utils.validation包包含几个我们可以用来验证输入的函数。为简单起见,我们将在本书的其余部分跳过此类测试,但生产环境代码应该有它们。

· Scikit-Learn流水线要求fit()方法有两个参数Xy,这就是为什么我们需要y=None参数,即使我们不使用y

· 所有Scikit-Learn估计器都在fit()方法中设置n_features_in_,它们确保传递给transform()predict()的数据具有这个数量的特征。

· fit()方法必须返回self

· 此实现并没有100%完成:所有估计器在传递DataFrame时都应在fit()方法中设置feature_names_in_。此外,所有的转换器都应该提供一个get_feature_names_out()方法,以及一个inverse_transform()方法,当它们的转换可以被逆转时。有关详细信息,请参阅本章末尾的最后一个练习。

自定义转换器可以(并且经常)在其实现中使用其他估计器。例如,以下代码演示了使用KMeans的自定义转换器在fit()方法中识别出训练数据中的主要集群,然后在transform()方法中使用rbf_kernel()来测量每个样本与每个集群中心的相似程度:

你可以通过将实例传递给sklearn.utils.estimator_checks包中的check_estimator()来检查你的自定义估算器是否遵循Scikit-Learn的API。有关完整的API,请查看https://scikit-learn.org/stable/developers

正如你将在第9章中看到的,k均值是一种聚类算法,用于在数据中定位集群。它的搜索数量是由n_clusters超参数控制的。训练后,集群中心可通过cluster_centers_属性获得。KMeansfit()方法支持可选参数sample_weight,它允许用户指定样本的相对权重。k均值是一种随机算法,意味着它依赖于随机性来定位集群,所以如果你想要可重现的结果,你必须设置random_state参数。如你所见,尽管任务很复杂,但代码相当简单。现在让我们使用这个自定义转换器:

此代码创建一个ClusterSimilarity转换器,将集群数设置为10。然后用训练集中每个地区的经纬度调用fit_transform(),用每个地区的房价中位数加权。Transformer使用k均值来定位集群,然后测量每个地区与所有10个集群中心之间的高斯RBF相似性。结果是一个矩阵,每个地区一行,每个集群一列。让我们看一下前三行,四舍五入到小数点后两位:

图2-19展示了k均值找到的10个集群中心。这些地区根据其与其最近的集群中心的地理相似性进行着色。如你所见,大多数集群位于人口稠密和昂贵的地区。

图2-19:高斯RBF与最近的集群中心的相似性

2.5.5 转换流水线

如你所见,我们需要以正确的顺序执行许多数据转换步骤。幸运的是,Scikit-Learn提供了Pipeline类来帮助处理此类转换序列。以下是一个用于数值属性的小流水线,它首先估算然后缩放输入特征:

Pipeline构造函数采用定义一系列步骤的名称/估计器对(二元组)列表。名称可以是你喜欢的任何名称,只要它们是唯一的并且不包含双下划线(__)。稍后,当我们讨论超参数调整时,它们会很有用。估计器必须都是转换器[即它们必须有一个fit_transform()方法],除了最后一个,它可以是任何东西:转换器、预测器或任何其他类型的估计器。

在Jupyter notebook中,如果你导入sklearn并运行sklearn .set_config(display="diagram"),所有Scikit-Learn估计器都将呈现为交互式图表。这对于可视化流水线特别有用。要可视化num_pipeline,运行一个以num_pipeline作为最后一行的单元格。单击估计器会显示更多细节。

如果不想给转换器命名,则可以使用make_pipeline()函数来代替。它以转换器作为位置参数,并使用转换器类的名称创建流水线,小写且不带下划线(例如"simpleimputer")

如果多个转换器具有相同的名称,则在它们的名称后附加一个索引(例如,"foo-1""foo-2"等)。

当你调用流水线的fit()方法时,它会在所有转换器上依次调用fit_transform(),将每次调用的输出作为参数传递给下一次调用,直到它到达最终的估计器,它只调用fit()方法。

流水线公开了与最终估计器相同的方法。在这个示例中,最后一个估计器是一个StandardScaler,它是一个转换器,所以流水线也像一个转换器。如果你调用流水线的transform()方法,它将顺序地将所有转换应用于数据。如果最后一个估计器是预测器而不是转换器,那么流水线有一个predict()方法而不是transform()方法。调用它会按顺序将所有转换应用于数据并将结果传递给预测器的predict()方法。

让我们调用流水线的fit_transform()方法并查看输出的前两行,四舍五入到小数点后两位:

正如你之前看到的,如果你想恢复一个好的DataFrame,则你可以使用流水线的get_feature_names_out()方法:

流水线还支持索引;例如,pipeline[1]返回流水线中的第二个估计器,而pipeline[:-1]返回一个Pipeline对象,其中包含除最后一个估计器之外的所有估计器。你还可以通过steps属性(名称/估计器对列表)或通过named_steps字典属性(将名称映射到估计器)访问估计器。例如,num_pipeline["simpleimputer"]返回名为"simpleimputer"的估计器。

到目前为止,我们已经分别处理了类别列和数值列。拥有一个能够处理所有列的转换器,对每一列应用适当的转换会更方便。为此,你可以使用ColumnTransformer。例如,以下的ColumnTransformer会将num_pipeline(我们刚刚定义的)应用于数值属性,将cat_pipeline应用于分类属性:

首先我们导入ColumnTransformer类,然后我们定义数字和类别列名称的列表,并为分类属性构建一个简单的流水线。最后,我们构造一个ColumnTransformer。它的构造函数需要一个三元组的列表,每个包含一个名称(必须是唯一的并且不包含双下划线)、一个转换器,以及一个转换器应该应用到的列的名称(或索引)列表。

如果你希望删除列,则可以指定字符串"drop"而不是使用转换器,或者如果你希望列保持不变,则可以指定"passthrough"。默认情况下,剩余的列(即未列出的列)将被删除,但如果你希望以不同方式处理这些列,则可以将remainder超参数设置为任何转换器(或"passthrough")

由于列出所有列名不是很方便,Scikit-Learn提供了一个make_column_selector()函数,它返回一个选择器函数,你可以使用它来自动选择给定类型的所有特征,例如数值或类别。你可以将此选择器函数传递给ColumnTransformer,而不是列名或索引。此外,如果你不关心转换器的命名,你可以使用make_column_transformer(),它会为你选择名称,就像make_pipeline()所做的那样。例如,以下代码创建与之前相同的ColumnTransformer,只是转换器被自动命名为"pipeline-1""pipeline-2",而不是"num""cat"

现在我们已准备好将此ColumnTransformer应用于房屋数据:

很好!我们有一个预处理流水线,它获取整个训练数据集并将每个转换器应用于适当的列,然后水平连接转换后的列(转换器绝不能更改行数)。这再一次返回一个NumPy数组,但你可以使用preprocessing.get_feature_names_out()来获取列名,并将数据包装在一个漂亮的DataFrame中,就像我们之前所做的那样。

OneHotEncoder返回一个稀疏矩阵,而num_pipeline返回一个密集矩阵。当稀疏矩阵和密集矩阵混合存在时,ColumnTransformer会估计最终矩阵的密度(即非零单元的比率),如果密度低于给定阈值(默认情况下,sparse_threshold=0.3)。在此示例中,它返回一个密集矩阵。

你的项目进展很顺利,你几乎可以训练一些模型了!你现在想要创建一个单一的流水线来执行到目前为止实验过的所有转换。让我们回顾一下流水线将做什么以及为什么做:

· 数字特征中的缺失值将通过用中位数替换它们来估算,因为大多数ML算法不期望缺失值。在分类特征中,缺失值将被最常见的类别替换。

· 类别特征将被独热编码,因为大多数ML算法只接受数字输入。

· 计算并添加一些比率特征:bedrooms_ratiorooms_per_housepeople_per_house。希望这些能更好地与房价中位数相关联,从而帮助ML模型。

· 还添加了一些集群相似性特征。这些可能比纬度和经度对模型更有用。

· 长尾特征被它们的对数取代,因为大多数模型更喜欢具有大致均匀分布或高斯分布的特征。

· 所有数值特征都将被标准化,因为大多数ML算法更喜欢所有特征具有大致相同的尺度。

构建流水线来执行所有这些操作的代码现在看起来应该很熟悉:

如果你运行此ColumnTransformer,它会执行所有转换并输出具有24个特征的NumPy数组: