使用SPARK进行特征工程

news2024/11/26 16:42:08

文章目录

  • 特征工程
    • 预处理
    • 特征选择
    • 归一化
    • 离散化
    • Embedding
    • 向量计算
    • 效果对比

特征工程

在机器学习领域,有一条尽人皆知的“潜规则”:Garbage in,garbage out。它的意思是说,当我们喂给模型的数据是“垃圾”的时候,模型“吐出”的预测结果也是“垃圾”。垃圾是一句玩笑话,实际上,它指的是不完善的特征工程。特征工程不完善的成因有很多,比如数据质量参差不齐、特征字段区分度不高,还有特征选择不到位、不合理,等等。作为初学者,我们必须要牢记一点:特征工程制约着模型效果,它决定了模型效果的上限,也就是“天花板”。而模型调优,仅仅是在不停地逼近这个“天花板”而已。因此,提升模型效果的第一步,就是要做好特征工程。
特征工程是一个很大的概念,为了便于理解,我将特征工程拆分成了6大部分,如下图所示:
在这里插入图片描述
通常来说,对于原始数据中的字段,我们会把它们分为数值型(Numeric)和非数值型(Categorical)。之所以要这样区分,原因在于字段类型不同,处理方法也不同。在上图中,从左到右,依次是:预处理特征选择归一化离散化Embedding向量计算除此之外,Spark MLlib 还提供了一些用于自然语言处理(NLP,Natural Language Processing)的初级函数,如图中左上角的虚线框所示。我会从每个分类里各挑选一个最具代表性的函数(上图中字体加粗的函数)讲解。

预处理

由于绝大多数模型(包括线性回归模型)都不能直接“消费”非数值型数据,因此,咱们的第一步,就是把房屋属性中的非数值字段,转换为数值字段。在特征工程中,对于这类基础的数据转换操作,我们统一把它称为预处理。
我们可以利用 Spark MLlib 提供的 StringIndexer 完成预处理。顾名思义,StringIndexer 的作用是,以数据列为单位,把字段中的字符串转换为数值索引。例如,使用 StringIndexer,我们可以把“车库类型”属性 GarageType 中的字符串转换为数字,如下图所示。
在这里插入图片描述
StringIndexer 的用法比较简单,可以分为三个步骤:
第一步,实例化 StringIndexer 对象;
第二步,通过 setInputCol 和 setOutputCol 来指定输入列和输出列;
第三步,调用 fit 和 transform 函数,完成数据转换。
接下来,我们就结合上一讲的“房价预测”项目,使用 StringIndexer 对所有的非数值字段进行转换,从而演示并学习它的用法。首先,我们读取房屋源数据并创建 DataFrame。


import org.apache.spark.sql.DataFrame
 
// 这里的下划线"_"是占位符,代表数据文件的根目录
val rootPath: String = _
val filePath: String = s"${rootPath}/train.csv"
 
val sourceDataDF: DataFrame = spark.read.format("csv").option("header", true).load(filePath)

然后,我们挑选出所有的非数值字段,并使用 StringIndexer 对其进行转换。


// 导入StringIndexer
import org.apache.spark.ml.feature.StringIndexer
 
// 所有非数值型字段,也即StringIndexer所需的“输入列”
val categoricalFields: Array[String] = Array("MSSubClass", "MSZoning", "Street", "Alley", "LotShape", "LandContour", "Utilities", "LotConfig", "LandSlope", "Neighborhood", "Condition1", "Condition2", "BldgType", "HouseStyle", "OverallQual", "OverallCond", "YearBuilt", "YearRemodAdd", "RoofStyle", "RoofMatl", "Exterior1st", "Exterior2nd", "MasVnrType", "ExterQual", "ExterCond", "Foundation", "BsmtQual", "BsmtCond", "BsmtExposure", "BsmtFinType1", "BsmtFinType2", "Heating", "HeatingQC", "CentralAir", "Electrical", "KitchenQual", "Functional", "FireplaceQu", "GarageType", "GarageYrBlt", "GarageFinish", "GarageQual", "GarageCond", "PavedDrive", "PoolQC", "Fence", "MiscFeature", "MiscVal", "MoSold", "YrSold", "SaleType", "SaleCondition")
 
// 非数值字段对应的目标索引字段,也即StringIndexer所需的“输出列”
val indexFields: Array[String] = categoricalFields.map(_ + "Index").toArray
 
// 将engineeringDF定义为var变量,后续所有的特征工程都作用在这个DataFrame之上
var engineeringDF: DataFrame = sourceDataDF
 
// 核心代码:循环遍历所有非数值字段,依次定义StringIndexer,完成字符串到数值索引的转换
for ((field, indexField) <- categoricalFields.zip(indexFields)) {
 
// 定义StringIndexer,指定输入列名、输出列名
val indexer = new StringIndexer()
.setInputCol(field)
.setOutputCol(indexField)
 
// 使用StringIndexer对原始数据做转换
engineeringDF = indexer.fit(engineeringDF).transform(engineeringDF)
 
// 删除掉原始的非数值字段列
engineeringDF = engineeringDF.drop(field)
}

尽管代码看上去很多,但我们只需关注与 StringIndexer 有关的部分即可。我们刚刚介绍了 StringIndexer 用法的三个步骤,咱们不妨把这些步骤和上面的代码对应起来,这样可以更加直观地了解 StringIndexer 的具体用法。
在这里插入图片描述
以“车库类型”GarageType 字段为例,我们先初始化一个 StringIndexer 实例。然后,把 GarageType 传入给它的 setInputCol 函数。接着,把 GarageTypeIndex 传入给它的 setOutputCol 函数。最后,我们在 StringIndexer 之上,依次调用 fit 和 transform 函数来生成输出列,这两个函数的参数都是待转换的 DataFrame。转换完成之后,你会发现 engineeringDF 中多了一个新的数据列,也就是 GarageTypeIndex 这个字段。而这一列包含的数据内容,就是与 GarageType 数据列对应的数值索引,如下所示。


engineeringDF.select("GarageType", "GarageTypeIndex").show(5)
 
/** 结果打印
+----------+---------------+
|GarageType|GarageTypeIndex|
+----------+---------------+
| Attchd| 0.0|
| Attchd| 0.0|
| Attchd| 0.0|
| Detchd| 1.0|
| Attchd| 0.0|
+----------+---------------+
only showing top 5 rows
*/

特征选择

特征选择顾名思义,就是遴选出关键特征,然后进行建模。实际上,面对数量众多的候选特征,业务经验往往是特征选择的重要出发点之一。与此同时,我们还会使用一些统计方法,去计算候选特征与预测标的之间的关联性,从而以量化的方式,衡量不同特征对于预测标的重要性。统计方法在验证专家经验有效性的同时,还能够与之形成互补,因此,在日常做特征工程的时候,我们往往将两者结合去做特征选择。业务经验因场景而异,无法概述,因此,咱们重点来说一说可以量化的统计方法。
在这里插入图片描述
统计方法的原理并不复杂,本质上都是基于不同的算法(如 Pearson 系数、卡方分布),来计算候选特征与预测标的之间的关联性。
关于特征选择方法的详细介绍可见
Spark MLlib 框架为我们提供了多种特征选择器(Selectors),这些 Selectors 封装了不同的统计方法。接下来,咱们还是以“房价预测”的项目为例,说一说 ChiSqSelector 的用法与注意事项。
既然是量化方法,这就意味着 Spark MLlib 的 Selectors 只能用于数值型字段。要使用 ChiSqSelector 来选择数值型字段,我们需要完成两步走:第一步,使用 VectorAssembler 创建特征向量;第二步,基于特征向量,使用 ChiSqSelector 完成特征选择。VectorAssembler 原本属于特征工程中向量计算的范畴,不过,在 Spark MLlib 框架内,很多特征处理函数的输入参数都是特性向量(Feature Vector),比如现在要讲的 ChiSqSelector。因此,这里我们先要对 VectorAssembler 做一个简单的介绍。
VectorAssembler 的作用是,把多个数值列捏合为一个特征向量。以房屋数据的三个数值列“LotFrontage”、“BedroomAbvGr”、“KitchenAbvGr”为例,VectorAssembler 可以把它们捏合为一个新的向量字段,如下图所示。
在这里插入图片描述
VectorAssembler 的用法很简单,初始化 VectorAssembler 实例之后,调用 setInputCols 传入待转换的数值字段列表(如上图中的 3 个字段),使用 setOutputCol 函数来指定待生成的特性向量字段,如上图中的“features”字段。接下来,我们结合代码,来演示 VectorAssembler 的具体用法。


// 所有数值型字段,共有27个
val numericFields: Array[String] = Array("LotFrontage", "LotArea", "MasVnrArea", "BsmtFinSF1", "BsmtFinSF2", "BsmtUnfSF", "TotalBsmtSF", "1stFlrSF", "2ndFlrSF", "LowQualFinSF", "GrLivArea", "BsmtFullBath", "BsmtHalfBath", "FullBath", "HalfBath", "BedroomAbvGr", "KitchenAbvGr", "TotRmsAbvGrd", "Fireplaces", "GarageCars", "GarageArea", "WoodDeckSF", "OpenPorchSF", "EnclosedPorch", "3SsnPorch", "ScreenPorch", "PoolArea")
 
// 预测标的字段
val labelFields: Array[String] = Array("SalePrice")
 
import org.apache.spark.sql.types.IntegerType
 
// 将所有数值型字段,转换为整型Int
for (field <- (numericFields ++ labelFields)) {
engineeringDF = engineeringDF.withColumn(s"${field}Int",col(field).cast(IntegerType)).drop(field)
}
 
import org.apache.spark.ml.feature.VectorAssembler
 
// 所有类型为Int的数值型字段
val numericFeatures: Array[String] = numericFields.map(_ + "Int").toArray
 
// 定义并初始化VectorAssembler
val assembler = new VectorAssembler()
.setInputCols(numericFeatures)
.setOutputCol("features")
 
// 在DataFrame应用VectorAssembler,生成特征向量字段"features"
engineeringDF = assembler.transform(engineeringDF)

代码内容较多,我们把目光集中到最下面的两行。首先,我们定义并初始化 VectorAssembler 实例,将包含有全部数值字段的数组 numericFeatures 传入给 setInputCols 函数,并使用 setOutputCol 函数指定输出列名为“features”。然后,通过调用 VectorAssembler 的 transform 函数,完成对 engineeringDF 的转换。转换完成之后,engineeringDF 就包含了一个字段名为“features”的数据列,它的数据内容,就是拼接了所有数值特征的特征向量。好啦,特征向量准备完毕之后,我们就可以基于它来做特征选择了。还是先上代码。


import org.apache.spark.ml.feature.ChiSqSelector
import org.apache.spark.ml.feature.ChiSqSelectorModel
 
// 定义并初始化ChiSqSelector
val selector = new ChiSqSelector()
.setFeaturesCol("features")
.setLabelCol("SalePriceInt")
.setNumTopFeatures(20)
 
// 调用fit函数,在DataFrame之上完成卡方检验
val chiSquareModel = selector.fit(engineeringDF)
 
// 获取ChiSqSelector选取出来的入选特征集合(索引)
val indexs: Array[Int] = chiSquareModel.selectedFeatures
 
import scala.collection.mutable.ArrayBuffer
 
val selectedFeatures: ArrayBuffer[String] = ArrayBuffer[String]()
 
// 根据特征索引值,查找数据列的原始字段名
for (index <- indexs) {
selectedFeatures += numericFields(index)
}

首先,我们定义并初始化 ChiSqSelector 实例,分别通过 setFeaturesCol 和 setLabelCol 来指定特征向量和预测标的。毕竟,ChiSqSelector 所封装的卡方检验,需要将特征与预测标的进行关联,才能量化每一个特征的重要性。接下来,对于全部的 27 个数值特征,我们需要告诉 ChiSqSelector 要从中选出多少个进行建模。这里我们传递给 setNumTopFeatures 的参数是 20,也就是说,ChiSqSelector 需要帮我们从 27 个特征中,挑选出对房价影响最重要的前 20 个特征。ChiSqSelector 实例创建完成之后,我们通过调用 fit 函数,对 engineeringDF 进行卡方检验,得到卡方检验模型 chiSquareModel。访问 chiSquareModel 的 selectedFeatures 变量,即可获得入选特征的索引值,再结合原始的数值字段数组,我们就可以得到入选的原始数据列。听到这里,你可能已经有点懵了,不要紧,结合下面的示意图,你可以更加直观地熟悉 ChiSqSelector 的工作流程。这里我们还是以“LotFrontage”、“BedroomAbvGr”、“KitchenAbvGr”这 3 个字段为例,来进行演示。
在这里插入图片描述
可以看到,对房价来说,ChiSqSelector 认为前两个字段比较重要,而厨房个数没那么重要。因此,在 selectedFeatures 这个数组中,ChiSqSelector 记录了 0 和 1 这两个索引,分别对应着原始的“LotFrontage”和“BedroomAbvGr”这两个字段。

归一化

归一化的作用,是把一组数值,统一映射到同一个值域,而这个值域通常是[0, 1]。当原始数据之间的量纲差异较大时,在模型训练的过程中,梯度下降不稳定、抖动较大,模型不容易收敛,从而导致训练效率较差。相反,当所有特征数据都被约束到同一个值域时,模型训练的效率会得到大幅提升。
Spark MLlib 支持多种多样的归一化函数,如 StandardScaler、MinMaxScaler,等等。尽管这些函数的算法各有不同,但效果都是一样的。我们以 MinMaxScaler 为例,MinMaxScaler 会把所有的数值都映射到[0, 1]这个范围。接下来,我们结合代码,来演示 MinMaxScaler 的具体用法。
与很多特征处理函数(如刚刚讲过的 ChiSqSelector)一样,MinMaxScaler 的输入参数也是特征向量,因此,MinMaxScaler 的用法,也分为两步走:第一步,使用 VectorAssembler 创建特征向量;第二步,基于特征向量,使用 MinMaxScaler 完成归一化。


// 所有类型为Int的数值型字段
// val numericFeatures: Array[String] = numericFields.map(_ + "Int").toArray
 
// 遍历每一个数值型字段
for (field <- numericFeatures) {
 
// 定义并初始化VectorAssembler
val assembler = new VectorAssembler()
.setInputCols(Array(field))
.setOutputCol(s"${field}Vector")
 
// 调用transform把每个字段由Int转换为Vector类型
engineeringData = assembler.transform(engineeringData)
}

在第一步,我们使用 for 循环遍历所有数值型字段,依次初始化 VectorAssembler 实例,把字段由 Int 类型转为 Vector 向量类型。接下来,在第二步,我们就可以把所有向量传递给 MinMaxScaler 去做归一化了。可以看到,MinMaxScaler 的用法,与 StringIndexer 的用法很相似。


import org.apache.spark.ml.feature.MinMaxScaler
 
// 锁定所有Vector数据列
val vectorFields: Array[String] = numericFeatures.map(_ + "Vector").toArray
 
// 归一化后的数据列
val scaledFields: Array[String] = vectorFields.map(_ + "Scaled").toArray
 
// 循环遍历所有Vector数据列
for (vector <- vectorFields) {
 
// 定义并初始化MinMaxScaler
val minMaxScaler = new MinMaxScaler()
.setInputCol(vector)
.setOutputCol(s"${vector}Scaled")
// 使用MinMaxScaler,完成Vector数据列的归一化
engineeringData = minMaxScaler.fit(engineeringData).transform(engineeringData)
}

首先,我们创建一个 MinMaxScaler 实例,然后分别把原始 Vector 数据列和归一化之后的数据列,传递给函数 setInputCol 和 setOutputCol。接下来,依次调用 fit 与 transform 函数,完成对目标字段的归一化。这段代码执行完毕之后,engineeringData(DataFrame)就包含了多个后缀为“Scaled”的数据列,这些数据列的内容,就是对应原始字段的归一化数据,如下所示。
在这里插入图片描述

离散化

离散化:Bucketizer与归一化一样,离散化也是用来处理数值型字段的。离散化可以把原本连续的数值打散,从而降低原始数据的多样性(Cardinality)。举例来说,“BedroomAbvGr”字段的含义是居室数量,在 train.csv 这份数据样本中,“BedroomAbvGr”包含从 1 到 8 的连续整数。现在,我们根据居室数量,把房屋粗略地划分为小户型、中户型和大户型。

在这里插入图片描述
不难发现,“BedroomAbvGr”离散化之后,数据多样性由原来的 8 降低为现在的 3。那么问题来了,原始的连续数据好好的,为什么要对它做离散化呢?离散化的动机,主要在于提升特征数据的区分度与内聚性,从而与预测标的产生更强的关联。就拿“BedroomAbvGr”来说,我们认为一居室和两居室对于房价的影响差别不大,同样,三居室和四居室之间对于房价的影响,也是微乎其微。但是,小户型与中户型之间,以及中户型与大户型之间,房价往往会出现跃迁的现象。换句话说,相比居室数量,户型的差异对于房价的影响更大、区分度更高。因此,把“BedroomAbvGr”做离散化处理,目的在于提升它与预测标的之间的关联性。
与其他环节一样,Spark MLlib 提供了多个离散化函数,比如 Binarizer、Bucketizer 和 QuantileDiscretizer。我们不妨以 Bucketizer 为代表,结合居室数量“BedroomAbvGr”这个字段,来演示离散化的具体用法。老规矩,还是先上代码为敬。


// 原始字段
val fieldBedroom: String = "BedroomAbvGrInt"
// 包含离散化数据的目标字段
val fieldBedroomDiscrete: String = "BedroomDiscrete"
// 指定离散区间,分别是[负无穷, 2][3, 4][5, 正无穷]
val splits: Array[Double] = Array(Double.NegativeInfinity, 3, 5, Double.PositiveInfinity)
 
import org.apache.spark.ml.feature.Bucketizer
 
// 定义并初始化Bucketizer
val bucketizer = new Bucketizer()
// 指定原始列
.setInputCol(fieldBedroom)
// 指定目标列
.setOutputCol(fieldBedroomDiscrete)
// 指定离散区间
.setSplits(splits)
 
// 调用transform完成离散化转换
engineeringData = bucketizer.transform(engineeringData)

不难发现,Spark MLlib 提供的特征处理函数,在用法上大同小异。首先,我们创建 Bucketizer 实例,然后将数值型字段 BedroomAbvGrInt 作为参数传入 setInputCol,同时使用 setOutputCol 来指定用于保存离散数据的新字段 BedroomDiscrete。离散化的过程是把连续值打散为离散值,但具体的离散区间如何划分,还需要我们通过在 setSplits 里指定。离散区间由浮点型数组 splits 提供,从负无穷到正无穷划分出了[负无穷, 2]、[3, 4]和[5, 正无穷]这三个区间。最终,我们调用 Bucketizer 的 transform 函数,对 engineeringData 做离散化。离散化前后的数据对比,如下图所示。
在这里插入图片描述

Embedding

Embedding 是一个非常大的话题,随着机器学习与人工智能的发展,Embedding 的方法也是日新月异、层出不穷。从最基本的热独编码到 PCA 降维,从 Word2Vec 到 Item2Vec,从矩阵分解到基于深度学习的协同过滤,可谓百花齐放、百家争鸣。那么问题来了,什么是 Embedding 呢?Embedding 的过程,就是把数据集合映射到向量空间,进而把数据进行向量化的过程。
关于Embedding的详细介绍
在预处理环节,我们使用 StringIndexer,把字符串转换为连续的整数,然后让模型去消费这些整数。在理论上,这么做没有任何问题。但从模型的效果出发,整数的表达方式并不合理。我们知道,连续整数之间,是存在比较关系的,比如 1 < 3,6 > 5,等等。但是原始的字符串之间,比如,“Attchd”与“Detchd”并不存在大小关系,如果强行用 0 表示“Attchd”、用 1 表示“Detchd”,逻辑上就会出现“Attchd”<“Detchd”的悖论。因此,预处理环节的 StringIndexer,仅仅是把字符串转换为数字,转换得到的数值是不能直接喂给模型做训练。我们需要把这些数字进一步向量化,才能交给模型去消费。那么问题来了,对于 StringIndexer 输出的数值,我们该怎么对他们进行向量化呢?这就要用到 Embedding 了。
咱们不妨从最简单的热独编码(One Hot Encoding)开始,去认识 Embedding 并掌握它的基本用法。我们先来说说,热独编码,是怎么一回事。相比照本宣科说概念,咱们不妨以 GarageType 为例,从示例入手,你反而更容易心领神会。
在这里插入图片描述
首先,通过 StringIndexer,我们把 GarageType 的 6 个取值分别映射为 0 到 5 的六个数值。接下来,使用热独编码,我们把每一个数值都转化为一个向量。向量的维度为 6,与原始字段(GarageType)的多样性(Cardinality)保持一致。换句话说,热独编码的向量维度,就是原始字段的取值个数。仔细观察上图的六个向量,只有一个维度取值为 1,其他维度全部为 0。取值为 1 的维度与 StringIndexer 输出的索引相一致。举例来说,字符串“Attchd”被 StringIndexer 映射为 0,对应的热独向量是[1, 0, 0, 0, 0, 0]。向量中索引为 0 的维度取值为 1,其他维度全部取 0。不难发现,热独编码是一种简单直接的 Embedding 方法,甚至可以说是“简单粗暴”。不过,在日常的机器学习开发中,“简单粗暴”的热独编码却颇受欢迎。接下来,我们还是从“房价预测”的项目出发,说一说热独编码的具体用法。在预处理环节,我们已经用 StringIndexer 把非数值字段全部转换为索引字段,接下来,我们再用 OneHotEncoder,把索引字段进一步转换为向量字段。


import org.apache.spark.ml.feature.OneHotEncoder
 
// 非数值字段对应的目标索引字段,也即StringIndexer所需的“输出列”
// val indexFields: Array[String] = categoricalFields.map(_ + "Index").toArray
 
// 热独编码的目标字段,也即OneHotEncoder所需的“输出列”
val oheFields: Array[String] = categoricalFields.map(_ + "OHE").toArray
 
// 循环遍历所有索引字段,对其进行热独编码
for ((indexField, oheField) <- indexFields.zip(oheFields)) {
val oheEncoder = new OneHotEncoder()
.setInputCol(indexField)
.setOutputCol(oheField)
engineeringData= oheEncoder.transform(engineeringData)
}

可以看到,我们循环遍历所有非数值特征,依次创建 OneHotEncoder 实例。在实例初始化的过程中,我们把索引字段传入给 setInputCol 函数,把热独编码目标字段传递给 setOutputCol 函数。最终通过调用 OneHotEncoder 的 transform,在 engineeringData 之上完成转换。

向量计算

向量计算,作为特征工程的最后一个环节,主要用于构建训练样本中的特征向量(Feature Vectors)。在 Spark MLlib 框架下,训练样本由两部分构成,第一部分是预测标的(Label),在“房价预测”的项目中,Label 是房价。而第二部分,就是特征向量,在形式上,特征向量可以看作是元素类型为 Double 的数组。根据前面的特征工程流程图,我们不难发现,特征向量的构成来源多种多样,比如原始的数值字段、归一化或是离散化之后的数值字段、以及向量化之后的特征字段,等等。Spark MLlib 在向量计算方面提供了丰富的支持,比如前面介绍过的、用于集成特征向量的 VectorAssembler,用于对向量做剪裁的 VectorSlicer,以元素为单位做乘法的 ElementwiseProduct,等等。灵活地运用这些函数,我们可以随意地组装特征向量,从而构建模型所需的训练样本。在前面的几个环节中(预处理、特征选择、归一化、离散化、Embedding),我们尝试对数值和非数值类型特征做各式各样的转换,目的在于探索可能对预测标的影响更大的潜在因素。接下来,我们使用 VectorAssembler 将这些潜在因素全部拼接在一起、构建特征向量,从而为后续的模型训练准备好训练样本。


import org.apache.spark.ml.feature.VectorAssembler
 
/**
入选的数值特征:selectedFeatures
归一化的数值特征:scaledFields
离散化的数值特征:fieldBedroomDiscrete
热独编码的非数值特征:oheFields
*/
 
val assembler = new VectorAssembler()
.setInputCols(selectedFeatures ++ scaledFields ++ fieldBedroomDiscrete ++ oheFields)
.setOutputCol("features")
 
engineeringData = assembler.transform(engineeringData)

转换完成之后,engineeringData 这个 DataFrame 就包含了一列名为“features”的新字段,这个字段的内容,就是每条训练样本的特征向量。接下来,我们就可以像上一讲那样,通过 setFeaturesCol 和 setLabelCol 来指定特征向量与预测标的,定义出线性回归模型。


// 定义线性回归模型
val lr = new LinearRegression()
.setFeaturesCol("features")
.setLabelCol("SalePriceInt")
.setMaxIter(100)
 
// 训练模型
val lrModel = lr.fit(engineeringData)
 
// 获取训练状态
val trainingSummary = lrModel.summary
// 获取训练集之上的预测误差
println(s"Root Mean Squared Error (RMSE) on train data: ${trainingSummary.rootMeanSquaredError}")

好啦,到此为止,我们打通了特征工程所有关卡,恭喜你!尽管不少关卡还有待我们进一步去深入探索,但这并不影响我们从整体上把握特征工程,构建结构化的知识体系。

效果对比

特征工程任一环节的输出,都可以用来构建特征向量,用于模型训练。在介绍特征工程的部分,我们花了大量篇幅介绍不同环节的作用与用法。你可能会好奇:“这些不同环节的特征处理,真的会对模型效果有帮助吗?毕竟,折腾了半天,我们还是要看模型效果的”。没错,特征工程的最终目的,是调优模型效果。接下来,通过将不同环节输出的训练样本喂给模型,我们来对比不同特征处理方法对应的模型效果。
在这里插入图片描述
代码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/42539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1. Vue 3.0介绍

Vue3.0介绍 1.Vue.js 3.0 源码组织方式 Vue2.x与Vue3.0的区别 源码组织方式的变化 Vue3.0的源码全部采用TypeScript重写使用Monorepo方式来组织项目结构&#xff0c;把独立的功能模块都提取到不同的包中。 packages下都是独立发行的包&#xff0c;可以独立使用。 Compositi…

[U3D ShaderGraph] 全面学习ShaderGraph节点 | 第二课 | Input/Geometry

ShaderGraph是可视化的着色器编辑工具。您可以使用此工具以可视方式创建着色器。 本专栏可以让你更了解ShaderGraph中每个节点的功能&#xff0c;更自如的在做出自己想要的效果。 如果你想学习在unity中如何制作一个特效&#xff0c;如何在unity中让模型更炫酷&#xff0c;那就…

Python实现导弹自动追踪

自动追踪算法&#xff0c;在我们制作射击类游戏时经常会用到。这个听起来很高大上的东西&#xff0c;其实并不是军事学的专利&#xff0c;从数学上来说就是解微分方程。 这个没有点数学基础是很难算出来的。但是我们有了计算机就不一样了&#xff0c;依靠计算机极快速的运算速…

【Scala专栏】走进Scala

官方文档: https://www.scala-lang.org/ 一、What is Scala? Scala是一种针对JVM 将面向函数和面向对象技术组合在一起的编程语言。Scala编程语言近来抓住了很多开发者的眼球。它看起来像是一种纯粹的面向对象编程语言&#xff0c;而又无缝地结合了命令式和函数式的编程风格…

服务访问质量(QoS)——流量整形与拥塞管理

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.流量整形和监管配置 1.流量整形配置两种方式 ①流量整形的命…

从零开始上手 MQTT over QUIC:快速体验下一代物联网标准协议

前言 QUIC(RFC9000) 是下一代互联网协议 HTTP/3 的底层传输协议&#xff0c;与 TCP/TLS 协议相比&#xff0c;它在减少连接开销与消息延迟的同时&#xff0c;为现代移动互联网提供了有效灵活的传输层。 EMQX 5.0 是首个将 QUIC 引入 MQTT 的开创性产品。在长期的客户服务和技…

eunomia-bpf项目重磅开源!eBPF 轻量级开发框架来了

近日&#xff0c;在 2022 云栖大会龙蜥峰会 eBPF & Linux 稳定性专场上&#xff0c;来自 eBPF 技术探索 SIG Maintainer 、浙江大学的郑昱笙分享了《eunomia-bpf&#xff1a;eBPF 轻量级开发框架》技术演讲&#xff0c;以下为本次演讲内容&#xff1a; 大家好&#xff01;…

【新知实验室-TRTC开发】实时音视频之web端云监工系统(Vue3+Element plus+TS+Pinia)

在线上线下一体化、虚拟现实加速融合的趋势下&#xff0c;音视频已经演进成一种基本能力&#xff0c;深刻变革了社会的交互方式。未来&#xff0c;音视频作为全真互联时代的重要基石&#xff0c;将持续推动互联网和实体产业的数字化创新与升级。 今天我们将体验腾讯的实时音视…

vue3 antd table表格的增删改查(一)input输入框根据关键字搜索【后台管理系统纯前端filter过滤】

input输入框——关键字模糊搜索引言铺垫场景复现解决方案筛选的实现重置筛选信息优化处理&#xff08;监听的实现&#xff09;功能实现可能要用到的知识&#xff1a;vue3数据变化侦测&&信息筛选过滤.filter() .map() .forEach(). find()&#x1f525;vue3【watch检测/监…

[附源码]Python计算机毕业设计Django4S店汽车售后服务管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

【服务器数据恢复】IBM服务器RAID控制器出错的数据恢复案例

服务器数据恢复环境&#xff1a; 北京某公司IBM X系列某型号服务器&#xff1b; 服务器上共8块硬盘组建raid5磁盘阵列&#xff1b; 服务器上部署有oracle数据库。 服务器故障&分析&#xff1a; 服务器在运行过程中&#xff0c;raid5磁盘阵列中有2块硬盘报警&#xff0c;服务…

CRM(Mapper层)详细代码

Mapper详细代码&#xff1a; DicValueMapper&#xff1a; package com.bjpowernode.crm.settings.mapper;import com.bjpowernode.crm.settings.domain.DicValue;import java.util.List;public interface DicValueMapper {/*** This method was generated by MyBatis Generato…

制作覆盖手绘图的导游地图,非常简单,你也可以

目录 1 前言 2 手绘地图的准备 3 下载软件 4 切图软件基本设置 5 配准设置 6 从平台取得上传切片所需要的3个参数 7 程序切片 8 增加位置点 1 前言 上一篇介绍了制作“简版导游地图”的步骤&#xff0c;真的是特别简单&#xff0c;如果提前准备好了文字材料&#xff0c…

PHP转Go,框架选什么?

文章目录内功心法PHP转Go&#xff0c;优选哪个框架&#xff1f;为什么&#xff1f;为什么不火&#xff1f;GoFrame特点优势&#xff1a;劣势&#xff1a;框架选型谁适合用GoFrame谁不适合用GoFrameGoFrame框架设计思想开发流程从0到1核心步骤总结视频一起学习这是一期会引起广泛…

即时通讯赛道开打信创牌,WorkPlus为何独树一帜?

近期&#xff0c;信创火了。 随着近期国家相关政策文件的推出&#xff0c;未来三年&#xff0c;党政信创、行业信创以及央国企信创的建设&#xff0c;将迎来全面加速。业内人士认为&#xff1a;“大信创”时代或已来临&#xff01; 信创是什么&#xff1f; 信创&#xff0c;…

加载用户数据至用户维度表

目录 1.创建转换 2.配置表输入 3.配置表输入2 4.创建新转换 5.配置映射输入规范 6.配置数据库查询 7.配置数据库查询2 8.配置数据库查询3 9.配置过滤记录 10配置JavaScript代码 11.配置字段选择 12.配置映射输出规范 13.配置映射&#xff08;子转换&#xff09; 1…

JS进阶第一篇:手写call apply bind

文章目录手写call apply bind深入理解 call 方法手写call手写apply手写bind手写call apply bind 深入理解 call 方法 call 理解了&#xff0c;apply和bind就都迎刃而解了&#xff0c;他们都是大同小异。在此对call和apply不做过多的定义性解释&#xff0c;先来看下调用了call…

opencv阈值图像Threshold方法

图像阈值 固定阈值&#xff0c;自适应阈值&#xff0c;Otsu 二值化等 全局阈值和局部阈值 一、图像二值化 定义&#xff1a;图像的二值化&#xff0c;就是将图像上的像素点的灰度值设置为0或255&#xff0c;也就是将整个图像呈现出明显的只有黑和白的视觉效果。 灰度值0&…

热门Java开发工具IDEA入门指南——导出项目到Eclipse

IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能是非常强大的。 上文…

打工人,这里有一份述职技巧,请查收

大家好&#xff0c;马上到年底了&#xff0c;有多少小伙伴正在期待着述职邮件&#xff0c;毕竟收到述职邮件&#xff0c;也就意味着有机会升职加薪。有没有跟糖糖一样&#xff0c;没收到邮件的&#xff1f; 工作要善于总结&#xff0c;也要善于表达&#xff0c;如何在限时内将…