写在前面:文章中主要给出了各特征处理所使用的方法和代码例子,并没有详细的解释每个方法的含义是什么、参数是什么、方法背后对应的处理和实现逻辑又是什么。
如果要从更底层的原理上了解这些方法,首先还是建议读你是用的Scala版本的官方文档,如我近期放在手头上会随时查阅的Spark2.3.0 ScalaDoc
或者找其他博主的一些相对详细的文章看看。虽然我还是建议你看官方文档,但前提是你要有代码的基础,要懂一些对象、抽象类、抽象方法类的知识,要有一些设计模式的架构设计知识。
它们会告诉你一些代码里看不到的细节,如StringIndexer将String类型的标签列按照出现频次建立索引:出现次数最多的标签索引值为0。(若输入列为数字类型,先将其转换为String类型再建索引);
再比如,使用向量的时候不使用名称Vector,是因为默认情况下,Scala会导入scala.collection.immutable.Vector。我们要引入org.apache.spark.ml.linalg.Vector的工厂方法对象(实际为一个object)才可以。
等等跟这些方法的实现、源码相关的细节。
1、为后面的代码示例准备下数据
val modelPath = "/user/gaoToby/model_saved"
val dataPath = "/user/gaoToby/ml_data"
//1-1 load data
// in Scala
val sales = spark.read.format("csv")
.option("header", "true")
.option("inferSchema", "true")
.load(dataPath + "/retail-data/by-day/*.csv")
.coalesce(5)
.where("Description IS NOT NULL")
val fakeIntDF = spark.read.parquet(dataPath + "/data/simple-ml-integers")
var simpleDF = spark.read.json(dataPath + "/data/simple-ml")
val scaleDF = spark.read.parquet(dataPath + "/data/simple-ml-scaling")
sales.cache()
sales.show()
fakeIntDF.cache()
fakeIntDF.show()
scaleDF.cache()
scaleDF.show()
scaleDF.printSchema()
simpleDF.cache()
simpleDF.show()
simpleDF.printSchema()
2、标准化
//2 - StandardScaler
import org.apache.spark.ml.feature.StandardScaler
val ss = new StandardScaler().setInputCol("features")
ss.fit(scaleDF).transform(scaleDF).show()
//10 - MaxAbsScaler
import org.apache.spark.ml.feature.MinMaxScaler
val minMax = new MinMaxScaler()
.setMin(5) //自定义最小值
.setMax(10) //自定义最大值
.setInputCol("features")
.setOutputCol("minMax_features")
val fittedminMax = minMax.fit(scaleDF)
fittedminMax.transform(scaleDF).show()
//11 - MaxAbsScaler
import org.apache.spark.ml.feature.MaxAbsScaler
val maScaler = new MaxAbsScaler()
.setInputCol("features")
.setOutputCol("maxAbs_features")
val fittedmaScaler = maScaler.fit(scaleDF)
fittedmaScaler.transform(scaleDF).show()
//12 - ElementwiseProduct 点积
import org.apache.spark.ml.feature.ElementwiseProduct
import org.apache.spark.ml.linalg.Vectors
val scaleUpVec = Vectors.dense(10.0, 15.0, 20.0)
val scalingUp = new ElementwiseProduct()
.setScalingVec(scaleUpVec) //用来对列进行自定义放缩,每列乘不同的倍数放缩。向量和矩阵的点积
.setInputCol("features")
.setOutputCol("elementWiseProduct_features")
scalingUp.transform(scaleDF).show()
// 13 - 范数规范化
import org.apache.spark.ml.feature.Normalizer
val manhattanDistance = new Normalizer()
.setP(1) //L范数
.setInputCol("features")
.setOutputCol("normed_features")
manhattanDistance.transform(scaleDF).show()
3、离散化
//7 - Bucketizer
import org.apache.spark.ml.feature.Bucketizer
val bucketBorders = Array(-1.0, 5.0, 10.0, 250.0, 600.0) //如果改成等距、等频的分桶呢?
val bucketerBin = new Bucketizer()
.setSplits(bucketBorders) //设置分桶方式
.setInputCol("id") //设置分桶列
.setOutputCol("binId") //设置输出列名
bucketerBin.transform(contDF).show()
//8 - QuantileDiscretizer
import org.apache.spark.ml.feature.QuantileDiscretizer
val bucketerQuant = new QuantileDiscretizer()
.setNumBuckets(5) //等频分桶
.setInputCol("id")
.setOutputCol("quantId")
val fittedBucketer = bucketerQuant.fit(contDF)
fittedBucketer.transform(contDF).show()
4、特征组装器
//5 VectorAssembler
val va = new VectorAssembler()
.setInputCols(Array("int1", "int2", "int3"))
va.transform(fakeIntDF).show()
//3 - RFormula
import org.apache.spark.ml.feature.RFormula
val supervised = new RFormula()
.setFormula("lab ~ . + color:value1 + color:value2")
supervised.fit(simpleDF).transform(simpleDF).show()
//.setFormula("y ~ . - w - gmv + x1_times:w + x2_times:w + x3_times:w + x4_times:w") 注意RFormula中符号的含义: . - + :
//自己使用map进行特征向量的组装。
//引入org.apache.spark.ml.linalg.Vector的工厂方法对象(实际为一个object)
//不使用名称Vector,是因为默认情况下,Scala会导入scala.collection.immutable.Vector, 为 了和这个类的伴生对象更好的区分
import org.apache.spark.ml.linalg.Vectors
//稠密型向量
Vectors.dense(Array(1.2, 2, 3)) // 推荐写法
Vectors.dense(1.2, 2, 3) // 写法2
//稀疏型向量
Vectors.sparse(10, Array(0, 3), Array(1, 3.1)) // 推荐写法
Vectors.sparse(10, Seq(0->1.0, 3->3.1)) // 写法2
注意:
5、编码
// 14 -1 StringIndexer 目标分类列编码 - 整数编码映射 string to index
import org.apache.spark.ml.feature.StringIndexer
val lblIndxr0 = new StringIndexer()
.setInputCol("lab")
.setOutputCol("labelInd")
.setHandleInvalid("skip") //可选项:skip \ keep ,含义是如果在测试的时候发现了一个未曾见过的分类,选择是skip忽略这条数据,还是将其分类统一归为N+1
val idxRes = lblIndxr0.fit(simpleDF).transform(simpleDF)
idxRes.show()
// 14 -2 StringIndexer 特征值列编码
val valIndexer = new StringIndexer()
.setInputCol("value1")
.setOutputCol("valueInd")
valIndexer.fit(simpleDF).setHandleInvalid("keep").transform(simpleDF).show()
// 16 - VectorIndexer 对特征向量进行编码。
import org.apache.spark.ml.feature.VectorIndexer
import org.apache.spark.ml.linalg.Vectors
val idxIn = spark.createDataFrame(Seq(
(Vectors.dense(1, 2, 3),1),
(Vectors.dense(2, 5, 6),2),
(Vectors.dense(1, 8, 9),3)
)).toDF("features", "label")
val indxr = new VectorIndexer()
.setInputCol("features")
.setOutputCol("idxed")
.setMaxCategories(3) //出现大于N个不同值的数值列看作连续型的特征列
indxr.fit(idxIn).transform(idxIn).show()
// 17 - OneHotEncoder ,并比较其与 StringIndexer的异同
import org.apache.spark.ml.feature.{OneHotEncoder, StringIndexer}
val lblIndxr = new StringIndexer().setInputCol("color").setOutputCol("colorInd")
val colorLab = lblIndxr.fit(simpleDF).transform(simpleDF.select("color"))
val ohe = new OneHotEncoder().setInputCol("colorInd")
ohe.transform(colorLab).show()
6、反编码
// 15 - reverse index to string 反编码
import org.apache.spark.ml.feature.IndexToString
val labelReverse = new IndexToString().setInputCol("labelInd")
labelReverse.transform(idxRes).show() //注意这里的DF是有经过编码的
7、特征扩展
// 27 - PolynomialExpansion
import org.apache.spark.ml.feature.PolynomialExpansion
val pe = new PolynomialExpansion()
.setInputCol("features")
.setDegree(2)
pe.transform(scaleDF).show(false)
8、特征选择
// 26 - PCA
import org.apache.spark.ml.feature.PCA
val pca = new PCA()
.setInputCol("features")
.setK(2)
pca.fit(scaleDF).transform(scaleDF).show(false)
// 28 - ChiSqSelector 特征选择 - 卡方
import org.apache.spark.ml.feature.{ChiSqSelector, Tokenizer}
val tkn2 = new Tokenizer().setInputCol("Description").setOutputCol("DescOut")
val tokenized3 = tkn2
.transform(sales.select("Description", "CustomerId"))
.where("CustomerId IS NOT NULL")
val prechi = fittedCV.transform(tokenized3)
val chisq = new ChiSqSelector()
.setFeaturesCol("countVec")
.setLabelCol("CustomerId")
.setNumTopFeatures(2) //保留特征量
chisq.fit(prechi).transform(prechi)
.drop("customerId", "Description", "DescOut").show()
9、保存
// 29 - save
val fittedPCA = pca.fit(scaleDF)
fittedPCA.write.overwrite().save(modelPath + "/tmp/fittedPCA")
10、加载
// 30 - load
import org.apache.spark.ml.feature.PCAModel
val loadedPCA = PCAModel.load(modelPath + "/tmp/fittedPCA")
loadedPCA.transform(scaleDF).show()