计算机系统应用教程网站

网站首页 > 技术文章 正文

一篇文章理解机器学习中的离散特征处理One-Hot Encoding

btikc 2024-09-25 15:14:41 技术文章 19 ℃ 0 评论

机器学习学习过程中,了解了一些算法模型后,在使用过程中,首先遇到的就是如何对特征数据进行处理,对离散型的分类型的数据进行处理,网上的多数文章都是一带而过,而在实际使用过程中则会遇到很多疑问,本文则对One-Hot Encoding的概念,使用方式以及遇到的问题进行讲解。

一、什么是One-Hot Encoding

定义:独热码,在英文文献中称做 one-hot code, 直观来说就是有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制

举个栗子,在我们用户的特征信息中,我们计算出了给用户偏爱的车型级别标签,假设用户车型级别有以下几个分类:微型车,小型车,中型车,SUV等四种类型(示例略去其他类型)

我们都知道,在机器学习的分类算法中,特征值只会处理数值类型,在认识热点编码之前我们可能会想的按照如下最简单的方式进行编码:

0 微型车,

1 小型车,

2 中型车,

3 SUV

假设我们通过用户车型级别等特征来预测用户购车预算(所选特征仅作示例使用),我们假设满足线性关系,函数f(x) = a + b1X1 + b2X2 + … + bkXk,其中a为常数,b为特征系数,x为特征值,比如x1为用户偏爱的车型级别特征,x2,x3..等为其他特征。在模型训练中,suv的值比小型车的值大是没有任何意义的,不同于房价,1万和2万有着明确的特征意义。这一个特征值的值大小会导致样本中该特征的权重发生变化。

二、编码过程

我们按照独热码的定义,将车型级别这一个特征进行编码,这个特征有4种取值,我们用四个位来表示。第一位代表是否是微型车,第二位代表是否是小型车,第三位代表是否是中型车,第四位代表是否是suv;那经过这种方式编码后如下:

0 微型车(1000)

1 小型车 (0100)

2 中型车(0010)

3 SUV (0001)

通过这种方式编码后,把每一位变成独立的一个特征值,像举例中的车型级别特征变成了4 个特征值。

三、代码示例

在spark的MLLib库中,提供了特征处理相关的方法,其中org.apache.spark.ml.feature.OneHotEncoder就是实现了上述的编码过程。那我们编写代码试试:

如上代码中,我们首先使用StringIndexer对分类对应一个id,输出到categoryIndex中,然后只需将categoryIndex输入到OneHotEncoder中进行编码。

运行以上代码返回结果如下

对于初学者,第一次看到这个结果可能与预期略有不同,因为预期的结果如编码过程中所讲的应该是一串0中带着1的数组,

那么向量会有哪几种表示方式呢?一般来说,对于一个向量(1.0,0,0,2.0)有以下两种表示方法:第一种则为[1.0,0,0,2.0],这种方式和一般的数组一样。而对于稀疏矩阵,由于数值为0的元素数目远远多于非0元素的数目,比如一个8*8的矩阵,如果里面只有第一行第一列为非零元素,如用二维数组则需要8*8的空间,而如果只记录非零元素所在行、列和其值,便可以只用更小空间即可记录。

在Spark MLlib中,也提供了对于密集矩阵和稀疏矩阵的不同表示方法,

1、密集矩阵:Vectors.dense(values: Array[Double])

如最开始的例子:val v1=Vectors.dense(1.0,0,0,2.0)

2、稀疏矩阵:

支持三种表示方式,对于向量(1.0,0,0,2.0)其表示方式按照顺序表示方式如下:

val v2 =Vectors.sparse(3,(0,2),(1.0,2.0)) #长度为3,第0,2个位置的值为1.0和2.0

val v3 = Vectors.sparse(3, Array(0, 1.0), Array(2, 2.0)) #结果同上

val v4=Vectors.sparse(3,Seq((0,1.0),(2,2.0))) #结果同上

这样对于程序运行的结果就很清晰的明白什么含义了,其表示的含义为:向量大小,序号数组,值数组,序号从0开始。

然而对于运行结果,大家还会发现另外一个问题:明明我输入的特征是有4个取值,向量大小应该为4,为何最终出来的结果中向量长度为3,且对于id为3表示成向量后为(3,[],[])即全部为0的向量。

查看对于官方文档中对于OneHotEncoder的描述中,大家会发现这样一句话:

The last category is not included by default (configurable via OneHotEncoder!.dropLast because it makes the vector entries sum up to one, and hence linearly dependent.

(https://spark.apache.org/docs/1.5.2/api/java/org/apache/spark/ml/feature/OneHotEncoder.html)

即最后一个分类是默认不被包含进去的(可通过.setDropLast方法设置包含),其原因为会造成列向量相加为1,从而造成共线性。这种情况又成为虚拟变量陷阱,如何理解这句话呢,还是以上面的特征为例:

f(x) = a + b1x1 + b2x2 + b3x3 + b4x4

x1为是否为微型车,x2为是否为小型车,x3为是否为中型车,x4为是否为suv,其中b1,b2,b3,b4为系数,a为常数项(可以看为1*a)

假设不采用n-1个特征表示,那其样本矩阵就可以如下示例表示(第一列为常数项,看为样本值固定为1,a为系数)

1 0 0 1 0

1 0 1 0 1

1 1 0 0 0

1 0 0 0 1

可以发现,这个矩阵不是满秩的,列向量线性相关,第2,3,4,5列的和等于第一列。这样会导致无法估计参数,那如何解决这个问题呢?有两个办法,第一就是采用n-1个特征。这样这一组的列向量和就不会相加为1了。样本矩阵如下:

1 0 0 1 0

1 0 1 0 1

1 1 0 0 0

1 0 0 0 0

在一个办法就是去掉常数项(截距),也可以解决上述问题。去掉常数项后,样本矩阵示例如下表示:

0 0 1 0

0 1 0 1

1 0 0 0

0 1 0 0

通过这两种方法,矩阵变为满秩,这样就可以正常进行参数估计了。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表