Spark MLlib 特征工程(下)

news2025/1/11 21:01:35

Spark MLlib 特征工程(下)

前面我们提到,典型的特征工程包含如下几个环节,即预处理、特征选择、归一化、离散化、Embedding 和向量计算,如下图所示。

image-20240802155356825

在上一讲,我们着重讲解了其中的前 3 个环节,也就是预处理、特征选择和归一化,今天这一讲,咱们继续来说说剩下的离散化、Embedding 与向量计算。

特征工程

离散化:Bucketizer

与归一化一样,离散化也是用来处理数值型字段的。离散化可以把原本连续的数值打散,从而降低原始数据的多样性(Cardinality)。举例来说,“BedroomAbvGr”字段的含义是居室数量,在 train.csv 这份数据样本中,“BedroomAbvGr”包含从 1 到 8 的连续整数。

现在,我们根据居室数量,把房屋粗略地划分为小户型、中户型和大户型

image-20240802160109736

不难发现,“BedroomAbvGr”离散化之后,数据多样性由原来的 8 降低为现在的 3。那么问题来了,原始的连续数据好好的,为什么要对它做离散化呢?离散化的动机,主要在于提升特征数据的区分度与内聚性,从而与预测标的产生更强的关联

就拿“BedroomAbvGr”来说,我们认为一居室和两居室对于房价的影响差别不大,同样,三居室和四居室之间对于房价的影响,也是微乎其微。但是,小户型与中户型之间,以及中户型与大户型之间,房价往往会出现跃迁的现象。换句话说,相比居室数量,户型的差异对于房价的影响更大、区分度更高。因此,把“BedroomAbvGr”做离散化处理,目的在于提升它与预测标的之间的关联性。

那么,在 Spark MLlib 的框架下,离散化具体该怎么做呢?与其他环节一样,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 做离散化。

离散化前后的数据对比,如下图所示。

image-20240802160951449

Embedding

实际上,Embedding 是一个非常大的话题,随着机器学习与人工智能的发展,Embedding 的方法也是日新月异、层出不穷。从最基本的热独编码到 PCA 降维,从 Word2Vec 到 Item2Vec,从矩阵分解到基于深度学习的协同过滤,可谓百花齐放、百家争鸣。更有学者提出:“万物皆可 Embedding”。那么问题来了,什么是 Embedding 呢?

Embedding 是个英文术语,如果非要找一个中文翻译对照的话,我觉得“向量化”(Vectorize)最合适。Embedding 的过程,就是把数据集合映射到向量空间,进而把数据进行向量化的过程。这句话听上去有些玄乎,我换个更好懂的说法,Embedding 的目标,就是找到一组合适的向量,来刻画现有的数据集合。

以 GarageType 字段为例,它有 6 个取值,也就是说我们总共有 6 种车库类型。那么对于这 6 个字符串来说,我们该如何用数字化的方式来表示它们呢?毕竟,模型只能消费数值,不能直接消费字符串。

image-20240802161235080

种方法是采用预处理环节的 StringIndexer,把字符串转换为连续的整数,然后让模型去消费这些整数。在理论上,这么做没有任何问题。但从模型的效果出发,整数的表达方式并不合理。为什么这么说呢?

我们知道,连续整数之间,是存在比较关系的,比如 1 < 3,6 > 5,等等。但是原始的字符串之间,比如,“Attchd”与“Detchd”并不存在大小关系,如果强行用 0 表示“Attchd”、用 1 表示“Detchd”,逻辑上就会出现“Attchd”<“Detchd”的悖论。

因此,预处理环节的 StringIndexer,仅仅是把字符串转换为数字,转换得到的数值是不能直接喂给模型做训练。我们需要把这些数字进一步向量化,才能交给模型去消费。那么问题来了,对于 StringIndexer 输出的数值,我们该怎么对他们进行向量化呢?这就要用到 Embedding 了

咱们不妨从最简单的热独编码(One Hot Encoding)开始,去认识 Embedding 并掌握它的基本用法。我们先来说说,热独编码,是怎么一回事。

image-20240802161337575

首先,通过 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.fit(engineeringData).transform(engineeringData)
}

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

其实我们也可以OneHotEncoder输入输出接受数组,也就是说我们可以不用使用循环,看起来代码更简洁

val oheEncoder = new OneHotEncoder()
oheEncoder.setInputCols(indexFields).setOutputCols(oheFields)
engineeringData= oheEncoder.fit(engineeringData).transform(engineeringData)
向量计算

向量计算,作为特征工程的最后一个环节,主要用于构建训练样本中的特征向量(Feature Vectors)。在 Spark MLlib 框架下,训练样本由两部分构成

  1. 第一部分是预测标的(Label),在“房价预测”的项目中,Label 是房价。
  2. 第二部分,就是特征向量,在形式上,特征向量可以看作是元素类型为 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.toArray ++ scaledFields ++ Array(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}")

到此为止,我们打通了特征工程所有关卡

你可能会好奇:“这些不同环节的特征处理,真的会对模型效果有帮助吗?毕竟,折腾了半天,我们还是要看模型效果的”。没错,特征工程的最终目的,是调优模型效果。接下来,通过将不同环节输出的训练样本喂给模型,我们来对比不同特征处理方法对应的模型效果。

image-20240802164105354

总结

可以看到,随着特征工程的推进,模型在训练集上的预测误差越来越小,这说明模型的拟合能力越来越强,而这也就意味着,特征工程确实有助于模型性能的提升。

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

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

相关文章

Memecoin的火爆与AMM在Solana上的主导地位

随着Solana区块链的高速发展&#xff0c;尤其是近年来Memecoin市场的崛起&#xff0c;AMM&#xff08;自动做市商&#xff09;逐渐成为Solana去中心化交易所&#xff08;DEX&#xff09;的主导交易模式。尽管Solana以其高效性能能够支持每秒数千笔交易&#xff0c;足以让中心化…

【C语言篇】深入理解指针3(附转移表源码)

文章目录 数组指针什么是数组指针数组指针变量的初始化 二维数组传参的本质函数指针函数指针变量的创建函数指针变量的使用 两端有趣的代码typedef 关键字 函数指针数组转移表写在最后 数组指针 什么是数组指针 在【C语言篇】深入理解指针2我们学习了指针数组&#xff0c;指针…

Qt项目【上位机十字屏开发】

效果图 说明 重写 QWidget 中的 paintEvent() 处理绘图事件&#xff0c;废话不多说&#xff0c;上代码 源码 #ifndef MYWIDGETFORM_H #define MYWIDGETFORM_H#include <QWidget>namespace Ui { class myWidgetForm; }enum MYTYPE{SIZEWIDTH,SIZEHEIGHT,TOPWIDTH,TOPHE…

XMind在软件需求分析中编写测试用例的应用技巧

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言 在软件需求分析中&#xff0c;编写测试用例是确保软件质量的重要环节。之前很多同学都是用Excel&#xff0c;但是XMind作为一款功能强大的思维导图工具&#xff0c;可以在需求分析阶段帮助测试人员系统地设计和组织测试用…

报错解决——苹果电脑mac装windows10,总是提示“启动转换”安装失败:拷贝Windows安装文件时出错

报错原因&#xff1a; 所安装的镜像文件大于4GB。 解决办法一&#xff1a; 使用小于4GB的镜像文件。 参考文章&#xff1a; 安装小于4GB的windows系统镜像 小于4GB的windows10镜像下载&#xff1a; 系统库官网 解决办法二&#xff1a; 参考文章&#xff1a; Mac air装…

如何利用Maven命令使得本地 .jar 文件安装到本地仓库中,以供其他项目通过 Maven 依赖引用

文件夹打包 例如此时我的文件夹example当中有两个class文件 复制文件夹路径 cmd运行命令&#xff1a;jar cvf nation.jar -C 你的文件夹路径 . 以我的举例&#xff1a; 这样就完成了打包 导入仓库 先找到jar文件的位置&#xff0c;复制路径 并且确定自己有安装好maven命…

【概率统计】三扇门游戏(蒙提霍尔问题)

三扇门游戏 两种答案2/3的重选正确率1/2的重选正确率 正确答案 也称为蒙提霍尔问题&#xff08;Monty Hall problem&#xff09;&#xff1a; 有三扇门&#xff0c;其中只有一扇是正确的门&#xff0c;打开后将能获得一辆豪车。另外两扇门是错误选项&#xff0c;门内只有山羊。…

模板——从初级到进阶

目录 前言&#xff1a; 一、非类型模板参数 二、模板的特化 2.1 函数模板特化 2.2 类模板特化 2.2.1 全特化 2.2.2 偏特化 三、模板分离编译 3.1 什么是分离编译 3.2 模板的分离编译 四、模板总结 前言&#xff1a; 我们前面已经对初阶模板有了比较深刻的了解&#xff…

鸿蒙前端开发——工具安装与项目创建

工具安装 DevEco Studio https://developer.huawei.com/consumer/cn/ 直接下一步。 创建空项目 双击进入 空项目如下&#xff1a; 点击previewer进行预览 备用地址下载

十、OpenCVSharp 中的图像的几何变换

文章目录 简介一、平移1. 平移向量的定义和计算2. 平移操作的矩阵表示二、旋转1. 旋转角度的表示和计算2. 旋转中心的选择3. 旋转矩阵的推导和应用三、缩放1. 缩放因子的确定2. 缩放操作的数学模型3. 缩放过程中的图像插值方法(如最近邻插值、双线性插值、双三次插值)四、仿射…

Qt连接Postgres数据库

数据库相关代码可以看我这篇文章&#xff0c;今天要说的是驱动问题&#xff0c;网上很多说将Postgres/bin目录下的某些.dll文件拷贝到运行目录&#xff0c;实际测试的时候发现&#xff0c;还是加载不了驱动。 后来发现postgres可以直接下载相关的驱动依赖&#xff0c;将流程分…

计算机三级嵌入式笔记(五)——嵌入式系统的开发

目录 考点1 嵌入式系统的开发过程 考点2 嵌入式系统的开发平台与工具 考点3 嵌入式系统的调试 考点4 ADS1.2 工具软件 考点5 RVDS 考点6 GNU 考点7 基于嵌入式 Web 服务器的应用设计 23考纲 考点1 嵌入式系统的开发过程 (1)嵌入式系统的开发过程可以划分为系统需求分析与…

Golang | Leetcode Golang题解之第334题递增的三元子序列

题目&#xff1a; 题解&#xff1a; func increasingTriplet(nums []int) bool {n : len(nums)if n < 3 {return false}first, second : nums[0], math.MaxInt32for i : 1; i < n; i {num : nums[i]if num > second {return true} else if num > first {second n…

Ajax-01.原生方式

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Ajax-原生方式</title> </head> <!-…

Apache Tomcat 信息泄露漏洞排查处理CVE-2024-21733)

一、漏洞描述 Apache Tomcat作为一个流行的开源Web服务器和Java Servlet容器并用于很多中小型项目的开发中。其中,Coyote作为Tomcat的连接器组件,是Tomcat服务器提供的供客户端访问的外部接口,客户端通过Coyote与服务器建立链接、发送请求并且接收响应。 近日发现Apache To…

K8S系列——一、Ubuntu上安装Helm

在使用K8S搭建集群服务时&#xff0c;有时候需要用到Helm&#xff08;一个用于Kubernetes应用管理的工具&#xff09;&#xff0c;下面是在Ubuntu上安装Helm的过程。 1.更新系统软件包列表 sudo apt-get update2.安装必要的依赖项 sudo apt-get install apt-transport-https…

Java | Leetcode Java题解之第337题打家劫舍III

题目&#xff1a; 题解&#xff1a; class Solution {public int rob(TreeNode root) {int[] rootStatus dfs(root);return Math.max(rootStatus[0], rootStatus[1]);}public int[] dfs(TreeNode node) {if (node null) {return new int[]{0, 0};}int[] l dfs(node.left);i…

【es学习】

es学习 1. 倒排索引2. stored fields 用于存储文档信息3. doc values 用于排序和聚合4. segment 具备完整搜索功能的最小单元5. lucene单机文本搜索库6. 从lucene到es&#xff1a;高性能 高扩展性 高可用7. node角色分化8. es写入流程9. es搜索流程10. 倒排索引涉及的数据结构1…

【海奇HC-RTOS平台E100-问题点】

海奇HC-RTOS平台E100-问题点 ■ 屏幕是1280*720, UI是1024*600,是否修改UI■ hc15xx-db-e100-v10-hcdemo.dtb 找不到■ 触摸屏驱动 能否给个实例■ 按键驱动■ __initcall(projector_auto_start)■ source insigt4.0 #ifdef 代码怎么自动灰显示问题■ 补丁是打在运行程序&#…

人工智能在前列腺癌中的研究进展|顶刊速递·24-08-15

小罗碎碎念 今天的推文虽然只有五篇文献&#xff0c;但是内容分布还是很均匀的&#xff0c;影像组学、病理组学和基因组学均有涉及。 第一篇和第四篇是与病理AI相关的&#xff0c;这两篇文献都很有参考价值。第一篇把我们熟知的模型&#xff08;如全监督、弱监督和无监督模型…