1. 介绍
特征选择(Feature Selection)指的是在特征向量中选择出那些“优秀”的特征,组成新的、更“精简”的特征向量的过程。
特征选择在高维数据分析中十分常用,可以剔除掉“冗余”和“无关”的特征,提升学习器的性能。特征选择方法和分类方法一样,也主要分为有监督(Supervised)和无监督(Unsupervised)两种
1. 向量机
1.1 算法说明
类别:transformer【转换器】
向量机(VectorSlicer) 是一个转换器,输入特征向量, 输出原始特征向量子集。
VectorSlicer接收带有特定索引的向量列, 通过对这些索引的值进行筛选得到新的向量集。 可接收如下两种索引:
- 整数索引,setIndices()
- 字符串索引代表向量中特征的名字, 此类要求向量列有AttributeGroup, 因为该工具根据Attribute匹配名字字段。
指定整数或者字符串类型都是可以的。 另外,同时使用整数索引和字符串名字也是可以的。
- 不允许使用重复的特征, 所以所选的索引或者名字必须是独一无二的。 注意如果使用名字特征, 当遇到空值的时候将会报错。
输出将会按照所选的数字索引排序(按输入顺序排序), 其次按名字排序(按输入顺序)。
注意:
使用下标选择特征时下标数不能大于特征数量并且不能重复选择特征否则会报错
使用列名选择特征时不能输入不存在的特征名称并且不能重复选择特征否则会报错
假设我们有一个DataFrame 含有userFeatures 列:
userFeatures |
---|
[0.0,10.0,0.5] |
userFeatures是一个向量包含3个用户特征。 假设userFeatures的第一列全为0, 我们希望删除它,并且只选择后两项。 我们可以通过索引setIndices(1,2) 来选择后两项并产生一个新的features 列:
userFeatures | features |
---|---|
[0.0,10.0,0.5] | [10.0,0.5] |
假设我们还有如同 [“f1″,”f2″,”f3”] 的属性, 那么可以通过名字 setNames(“f2″,”f3”) 的形式来选择
userFeatures | features |
---|---|
[0.0,10.0,0.5] | [10.0,0.5] |
[“f1″,”f2″,”f3”] | [“f2″,”f3”] |
1.2 代码示例
package hnbian.spark.ml.feature.selecting
import hnbian.spark.utils.SparkUtils
import java.util.Arrays
import org.apache.spark.ml.attribute.{Attribute, AttributeGroup, NumericAttribute}
import org.apache.spark.ml.feature.VectorSlicer
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.StructType
/**
* @author hnbian
* @ Description 特征选择 向量机代码示例 【转换器】
* @ Date 2018/12/29 9:52
**/
object VectorSlicer extends App {
val spark = SparkUtils.getSparkSession("VectorSlicer", 4)
//定义数据集
val data = Arrays.asList(Row(Vectors.dense(0.0, 10.0, 0.5)))
val defaultAttr = NumericAttribute.defaultAttr
val attrs = Array("f1", "f2", "f3").map(defaultAttr.withName)
println(attrs.toBuffer)
//ArrayBuffer({"type":"numeric","name":"f1"}, {"type":"numeric","name":"f2"}, {"type":"numeric","name":"f3"})
val attrGroup = new AttributeGroup("userFeatures", attrs.asInstanceOf[Array[Attribute]])
val dataDF = spark.createDataFrame(data, StructType(Array(attrGroup.toStructField())))
dataDF.show(false)
/**
* +--------------+
* |userFeatures |
* +--------------+
* |[0.0,10.0,0.5]|
* +--------------+
*/
//定义通过下表选择特征的转换器设置输入输出与选择列的下标
val slicer = new VectorSlicer()
.setInputCol("userFeatures")
.setOutputCol("features")
.setIndices(Array(1, 2)) //使用索引选择特征 or slicer.setIndices(Array(1, 2))
//使用转换器对数据集进行转换并展示结果
val output = slicer.transform(dataDF).show()
/**
* +--------------+----------+
* |userFeatures |features |
* +--------------+----------+
* |[0.0,10.0,0.5]|[10.0,0.5]|
* +--------------+----------+
*/
//定义通过列名选择特征的转换器,并设置输入输出与选择列名
val slicer2 = new VectorSlicer()
.setInputCol("userFeatures")
.setOutputCol("features")
.setNames(Array("f1", "f2"))
//使用名称选择特征 or slicer.setNames(Array("f1", "f2"))
//使用转换器对数据进行转换并展示结果
val output2 = slicer2.transform(dataDF)
output2.show(false)
/**
* +--------------+----------+
* |userFeatures |features |
* +--------------+----------+
* |[0.0,10.0,0.5]|[0.0,10.0]|
* +--------------+----------+
*/
}
2. R公式
2.1 算法说明
类别:estimator【评估器】
R公式(Rformula) 通过R模型公式来选择类。 支持R操作中的部分操作,包括
操作符 | 描述 |
---|---|
~ | 分割标签与特征 |
. | 合并对象(两个特征相加),“+0” 意味着删除空格 |
: | 除了目标列的全部列 |
+ | 将多个数值特征相乘变成一个特征 |
– | 减去一个特征 |
假设a 和b 为两列:
\1. y ~ a + b 表示建立这样的线性模型 y ~ w0 + w1 * a + w2 * b 其中w0位截距,w1 和w2为相关系数。
\2. y ~ a + b + a : b – 1 表示线性模型 y ~ w1 * a + w2 * b + w3 * a * b , 其中 w1,w2,w3是相关系数。
(-1 表示去掉截距,所以模型中没有w0了,a : b表示将ab两个特征相乘生成新的特征)
也就是说,我们可以通过这些简单的符号去表示线性模型。
RFormula可以生成多组列向量来表示特征,和一组double或string类型的列来标签。 如果类别列式字符串类型, 它将通过StringIndexer 转换为double类型。就像在R中使用公式来建立线性模型一样,字符串类型的特征会被One-hot编码,数值类型的特征会被转换成double类型。如果标签列是字符串类型,会先将它转换成双精度的字符串索引。
如果在dataframe中不存在标签列,将会根据公式中的自变量去生成标签应变量作为输出。
假设我们有一个DataFrame 含有id,country,hour,clicked 四列:
iD | country | hour | clicked |
---|---|---|---|
7 | US | 18 | 1.0 |
8 | CA | 12 | 0.0 |
9 | NZ | 15 | 0.0 |
如果我们使用RFormula 公式 clicked ~country + hour , 则表明我们希望基于country 和hour 预测clicked, 通过转换我们可以得到如下的DataFrame:
iD | country | hour | clicked | features | label |
---|---|---|---|---|---|
7 | US | 18 | 1.0 | [0.0,0.0,18.0] | 1.0 |
8 | CA | 12 | 0.0 | [0.0,1.0,12.0] | 0.0 |
9 | NZ | 15 | 0.0 | [1.0,0.0,15.0] | 0.0 |
features列为转换后的特征,因为country是字符串类型的类编变量,故进行one-hot编码变成了两列, hour是数值型的,故转换成double类型。
label列是应变量click列,双精度的类型保持不变。
2.2 代码示例
package hnbian.spark.ml.feature.selecting
import hnbian.spark.utils.SparkUtils
import org.apache.spark.ml.feature.RFormula
/**
* @author hnbian
* @ Description R公式
* @ Date 2018/12/29 11:12
**/
object RFormula extends App {
val spark = SparkUtils.getSparkSession("RFormula", 4)
// 定义一个dataFrame
val dataDF = spark.createDataFrame(Seq(
(7, "US", 18, 1.0),
(8, "CA", 12, 0.0),
(9, "NZ", 15, 0.0)
)).toDF("id", "country", "hour", "clicked")
//展示数据
dataDF.show()
/**
* +---+-------+----+-------+
* | id|country|hour|clicked|
* +---+-------+----+-------+
* | 7| US| 18| 1.0|
* | 8| CA| 12| 0.0|
* | 9| NZ| 15| 0.0|
* +---+-------+----+-------+
*/
//定义评估器 并设置输入输出与计算公式
val formula = new RFormula()
.setFormula("clicked ~ country + hour")
.setFeaturesCol("features")
.setLabelCol("label")
//使用评估器训练模型
val outputModel = formula.fit(dataDF)
//使用转换器 对测试数据集进行转换 并查看转换后的数据
outputModel.transform(dataDF).show
/**
* +---+-------+----+-------+--------------+-----+
* | id|country|hour|clicked| features|label|
* +---+-------+----+-------+--------------+-----+
* | 7| US| 18| 1.0|[0.0,0.0,18.0]| 1.0|
* | 8| CA| 12| 0.0|[1.0,0.0,12.0]| 0.0|
* | 9| NZ| 15| 0.0|[0.0,1.0,15.0]| 0.0|
* +---+-------+----+-------+--------------+-----+
*/
}
3. 卡方选择
3.1 算法说明
类别:estimator【评估器】
卡方选择(ChiSqSelector)则是统计学上常用的一种有监督特征选择方法,它通过对特征和真实标签之间进行卡方检验,来判断该特征和真实标签的关联程度,进而确定是否对其进行选择。
和ML库中的大多数学习方法一样,ML中的卡方选择也是以 estimator + transformer 的形式出现的,其主要由ChiSqSelector和ChiSqSelectorModel两个类来实现。
参数:NumTopFeatures : 选取与标签关联性最强的几个特征 (默认选取全部特征)
3.2 代码示例
package hnbian.spark.ml.feature.selecting
import hnbian.spark.utils.SparkUtils
import org.apache.spark.ml.feature.{ChiSqSelector, ChiSqSelectorModel}
import org.apache.spark.ml.linalg.Vectors
/**
* @author hnbian
* @Description 卡方选择
* @Date 2018/12/29 11:26
**/
object ChiSqSelector extends App {
val spark = SparkUtils.getSparkSession("ChiSqSelector", 4)
//定义数据集 有三个样本,四个特征维度的数据集,标签有1,0两种
val df = spark.createDataFrame(Seq(
(1, Vectors.dense(0.0, 0.0, 18.0, 1.0,0.0), 1),
(2, Vectors.dense(0.0, 1.0, 12.0, 0.0,0.0), 0),
(3, Vectors.dense(1.0, 0.0, 15.0, 0.1,0.0), 0)
)).toDF("id", "features", "label")
//展示数据集
df.show()
/**
* +---+------------------+-----+
* | id| features|label|
* +---+------------------+-----+
* | 1|[0.0,0.0,18.0,1.0]| 1|
* | 2|[0.0,1.0,12.0,0.0]| 0|
* | 3|[1.0,0.0,15.0,0.1]| 0|
* +---+------------------+-----+
*/
/**
* 卡方选择进行特征选择器的训练,
* 为了观察地更明显,我们设置只选择和标签关联性最强的一个特征
* (可以通过setNumTopFeatures()方法进行设置)
*/
val selector = new ChiSqSelector()
.setNumTopFeatures(2) // 选取与标签关联性最强的几个特征 默认全选
.setFeaturesCol("features")
.setLabelCol("label")
.setOutputCol("selectedFeature")
//训练模型
val selectorModel = selector.fit(df)
//使用模型对数据集进行转换并展示数据
selectorModel.transform(df).show(false)
/**
* .setNumTopFeatures(1)
* +---+------------------+-----+---------------+
* | id| features|label|selectedFeature|
* +---+------------------+-----+---------------+
* | 1|[0.0,0.0,18.0,1.0]| 1| [18.0]|
* | 2|[0.0,1.0,12.0,0.0]| 0| [12.0]|
* | 3|[1.0,0.0,15.0,0.1]| 0| [15.0]|
* +---+------------------+-----+---------------+
*/
/**
* .setNumTopFeatures(2)
* +---+----------------------+-----+---------------+
* |id |features |label|selectedFeature|
* +---+----------------------+-----+---------------+
* |1 |[0.0,0.0,18.0,1.0,0.0]|1 |[18.0,1.0] |
* |2 |[0.0,1.0,12.0,0.0,0.0]|0 |[12.0,0.0] |
* |3 |[1.0,0.0,15.0,0.1,0.0]|0 |[15.0,0.1] |
* +---+----------------------+-----+---------------+
*/
}