基于spark ml的特征提取、转换和选择

引言:在实际的机器学习项目中,我们获取的数据往往是不规范、不一致的,有很多缺失数据,甚至不少错误数据,
这些数据有时称为脏数据或者噪声,在模型训练前,务必对这些脏数据进行处理,否则再好的模型也只能 garbage in,garbage out。

数据处理主要包括三部分,特征提取、特征转换和特征选择。


一、特征提取
特征提取一般指从原始数据中抽取特征。

1.计数向量器(Countvectorizer)
定义及用途:计数向量器(Countvectorizer)和计数向量器模型(Countvectorizermodel)
           将所有的文本词语进行编号,并统计该词语在文档中的词频作为特征向量。

应用场景:在文本挖掘中广泛使用的特征向量化方法。

注意点:
     (1)在拟合过程中,CountVectorizer将选择通过语料库按术语频率排序的top前几vocabSize词。 
    (2)可选参数minDF还通过指定术语必须出现以包含在词汇表中的文档的最小数量(或小于1.0)来影响拟合过程。
    (3)setMinDF代表过滤词出现在多少个文档中。
在spark ml中中示例:   
import org.apache.spark.ml.feature.{CountVectorizer, CountVectorizerModel}
val df = spark.createDataFrame(Seq(
      (0, Array("苹果","官网","苹果","宣布","香蕉")),
      (1, Array("苹果","梨","香蕉","番茄","梨","宣布"))
)).toDF("id", "words")    
var cvModel = new CountVectorizer().setInputCol("words").setOutputCol("features").setVocabSize(5).setMinDF(2).fit(df)
val cv1 = cvModel.transform(df)
cv1.show(false)
+---+------------------------+-------------------------+
|id |words                   |features                 |
+---+------------------------+-------------------------+
|0  |[苹果, 官网, 苹果, 宣布]|(5,[0,1,2],[2.0,1.0,1.0])|
|1  |[苹果, 梨, 香蕉]        |(5,[0,3,4],[1.0,1.0,1.0])|
+---+------------------------+-------------------------+

2.词频-逆向文件频率(TF-IDF)
    定义及用途:"词频-逆向文件频率"(TF-IDF)就是衡量词语能提供多少信息以区分文档。
        如果仅使用词频 TF 来评估的单词的重要性,很容易过分强调一些经常出现而并没有包含太多与文档有关信息的单词。
        如果一个单词在整个语料库中出现的非常频繁,这意味着它并没有携带特定文档的某些特殊信息。
 
        IDF=log((|D|+1)/DF(t,D)+1)
        所以最后对TF-IDF定义为TF和IDF的乘积
        TFIDF(t,d,D) = TF*IDF
        
        词语由t表示,文档由d表示,语料库由D表示。词频TF(t,d)是词语t在文档d中出现的次数。
        文件频率DF(t,D)是包含词语的文档的个数。
        
        TF(词频Term Frequency):HashingTF与CountVectorizer都可以用于生成词频TF矢量。
            HashingTF是一个转换器(Transformer),它可以将特征词组转换成给定长度的(词频)特征向量组。
            举例说明:[i, heard, about, spark, and, i, love, spark]转化成
            (2000,[240,333,1105,1329,1357,1777],[1.0,1.0,2.0,2.0,1.0,1.0])  
            通过哈希函数映射到低维向量的索引(index),在哈希的同时会统计各个词条的词频。

            
        IDF(逆文档频率): 接收特征向量(由HashingTF产生),然后计算每一个词在文档中出现的频次。
            IDF会减少那些在语料库中出现频率较高的词的权重。
            直观地看,特征词出现的文档越多,权重越低(down-weights colume)。
            
应用场景:在文本挖掘中广泛使用的特征向量化方法。

注意点:当词出现在所有文档中时,它的IDF值变为0。加1是为了避免分母为0的情况。

在spark ml中中示例:
import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer}
val sentenceData = spark.createDataFrame(Seq(
    (0, "I heard about Spark and I love Spark"),
    (0, "I wish Spark could use case classes"),
    (1, "Logistic regression models are neat")
)).toDF("label", "sentence")
// step1 分词
val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
val wordsData = tokenizer.transform(sentenceData)
// step2 TF
val hashingTF = new HashingTF().setInputCol("words").setOutputCol("rawFeatures").setNumFeatures(2000)
val featurizedData = hashingTF.transform(wordsData)
//featurizedData.select("label","words","rawFeatures").show(false)
// step3 IDF
val idf = new IDF().setInputCol("rawFeatures").setOutputCol("IDFfeatures")
val idfModel = idf.fit(featurizedData)
val rescaledData = idfModel.transform(featurizedData)
rescaledData.select("label","IDFfeatures").show(false)
+-----+------------------------------------+---------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|label|sentence                            |words                                        |rawFeatures                                                          |IDFfeatures                                                                                                                                                                     |
+-----+------------------------------------+---------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|0    |I heard about Spark and I love Spark|[i, heard, about, spark, and, i, love, spark]|(2000,[240,333,1105,1329,1357,1777],[1.0,1.0,2.0,2.0,1.0,1.0])       |(2000,[240,333,1105,1329,1357,1777],[0.6931471805599453,0.6931471805599453,0.5753641449035617,0.5753641449035617,0.6931471805599453,0.6931471805599453])                        |
|0    |I wish Spark could use case classes |[i, wish, spark, could, use, case, classes]  |(2000,[213,342,489,495,1105,1329,1809],[1.0,1.0,1.0,1.0,1.0,1.0,1.0])|(2000,[213,342,489,495,1105,1329,1809],[0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.28768207245178085,0.28768207245178085,0.6931471805599453])|
|1    |Logistic regression models are neat |[logistic, regression, models, are, neat]    |(2000,[286,695,1138,1193,1604],[1.0,1.0,1.0,1.0,1.0])                |(2000,[286,695,1138,1193,1604],[0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453])                                                |
+-----+------------------------------------+---------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

3.Word2Vec
定义及用途:Word2Vec是一个将单词转换成向量形式的工具。可以把对文本内容的处理简化为向量空间中的运算,
          计算出向量空间上的相似度,来表示文本语义上的相似度。
应用场景:在文本挖掘中广泛使用的特征向量化方法。
在spark ml中中示例:
import org.apache.spark.ml.feature.Word2Vec
val documentDF = spark.createDataFrame(Seq(
      "苹果 官网 苹果 宣布".split(" "),
      "苹果 梨 香蕉".split(" "),
      "苹果 官网 宣布 乔布斯".split(" ")
    ).map(Tuple1.apply)).toDF("text")
val word2Vec = new Word2Vec().setInputCol("text").setOutputCol("result").setVectorSize(3).setMinCount(1)
val model = word2Vec.fit(documentDF)
val result = model.transform(documentDF)
result.show(false)
+------------------------+----------------------------------------------------------------+
|text                    |result                                                          |
+------------------------+----------------------------------------------------------------+
|[苹果, 官网, 苹果, 宣布]|[-0.08192903269082308,-0.07735428772866726,0.07417313754558563] |
|[苹果, 梨, 香蕉]        |[0.014445872977375984,0.039761245250701904,0.02381060807965696] |
|[苹果, 官网, 宣布]      |[-0.10295478564997514,-0.059404375652472176,0.05113388101259867]|
+------------------------+----------------------------------------------------------------+

注意点:

二、特征转换
1.分词器
定义及用途:分词器(Tokenization)将文本划分为独立个体(通常为单词)。
           RegexTokenizer基于正则表达式提供更多的划分选项。默认情况下,参数“pattern”为划分文本的分隔符。
           或者可以指定参数“gaps”来指明正则“patten”表示“tokens”而不是分隔符,这样来为分词结果找到所有可能匹配的情况。
应用场景:适用于文本挖掘场景。
在spark ml中中示例:
import org.apache.spark.ml.feature.{RegexTokenizer, Tokenizer}
val sentenceDataFrame = spark.createDataFrame(Seq(
  (0, "Hi I heard about Spark"),
  (1, "I wish Java could use case classes"),
  (2, "Logistic,regression,models,are,neat")
)).toDF("label", "sentence")

val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
val regexTokenizer = new RegexTokenizer().setInputCol("sentence").setOutputCol("words").setPattern("\\W") 
val tokenized = tokenizer.transform(sentenceDataFrame)
tokenized.show(false)

2.移除停用词
    定义及用途:停用词(StopWordsRemover)是在文档中频繁出现,但未承载太多意义的词语,他们不应该包含在算法输入中。
    应用场景:适用于文本挖掘场景。
    在spark ml中示例:
import org.apache.spark.ml.feature.StopWordsRemover
val remover = new StopWordsRemover().setInputCol("raw").setOutputCol("filtered")
val dataSet = spark.createDataFrame(Seq(
  (0, Seq("I", "saw", "the", "red", "baloon")),
  (1, Seq("Mary", "had", "a", "little", "lamb"))
)).toDF("id", "raw")
remover.transform(dataSet).show(false)
+---+----------------------------+--------------------+
|id |raw                         |filtered            |
+---+----------------------------+--------------------+
|0  |[I, saw, the, red, baloon]  |[saw, red, baloon]  |
|1  |[Mary, had, a, little, lamb]|[Mary, little, lamb]|
+---+----------------------------+--------------------+
    注意点:
    StopWordsRemover的输入为一系列字符串(如分词器输出),输出中删除了所有停用词。
    停用词表有stopWords参数提供。
    一些语言的默认停用词可以通过StopWordsRemover.loadDefaultStopWords(language)调用。
    布尔参数caseSensitive指明是否区分大小写(默认为否)。
    
3.n-gram
    定义及用途:一个n-gram是一个长度为整数n的字符串。NGram输入为一系列字符串(如Tokenizer的输出)。
    应用场景:适用于文本挖掘场景。
    在spark ml中示例:
import org.apache.spark.ml.feature.NGram
val wordDataFrame = spark.createDataFrame(Seq(
  (0, Array("Hi", "I", "heard", "about", "Spark")),
  (1, Array("I", "wish", "Java", "could", "use", "case", "classes")),
  (2, Array("Logistic", "regression", "models", "are", "neat"))
)).toDF("label", "words")
// 这里设置参数n
val ngram = new NGram().setN(3).setInputCol("words").setOutputCol("ngrams")
val ngramDataFrame = ngram.transform(wordDataFrame)
ngramDataFrame.show(false);
+-----+------------------------------------------+--------------------------------------------------------------------------------+
|label|words                                     |ngrams                                                                          |
+-----+------------------------------------------+--------------------------------------------------------------------------------+
|0    |[Hi, I, heard, about, Spark]              |[Hi I heard, I heard about, heard about Spark]                                  |
|1    |[I, wish, Java, could, use, case, classes]|[I wish Java, wish Java could, Java could use, could use case, use case classes]|
|2    |[Logistic, regression, models, are, neat] |[Logistic regression models, regression models are, models are neat]            |
+-----+------------------------------------------+--------------------------------------------------------------------------------+
    注意点:
    (1)参数n决定每个n-gram包含的对象的个数。
    (2)结果包含一系列n-gram,其中每个n-gram代表一个空格分割的n个连续字符。
    (3)如果输入少于n个字符串,将没有输出结果。
    
4.二值化
    定义及用途:二值化(Binarizer)是根据阀值将连续数值特征转换为0-1特征的过程。
    应用场景:通用。
    在spark ml中示例:
import org.apache.spark.ml.feature.Binarizer
val data = Array((0, 0.1), (1, 0.8), (2, 0.2), (3, 0.9), (4,0.5))
val dataFrame = spark.createDataFrame(data).toDF("label", "feature")
//这里设置阈值
val binarizer: Binarizer = new Binarizer().setInputCol("feature").setOutputCol("binarized_feature").setThreshold(0.5)
val binarizedDataFrame = binarizer.transform(dataFrame)
binarizedDataFrame.show(false)
+-----+-------+-----------------+
|label|feature|binarized_feature|
+-----+-------+-----------------+
|    0|    0.1|              0.0|
|    1|    0.8|              1.0|
|    2|    0.2|              0.0|
|    3|    0.9|              1.0|
+-----+-------+-----------------+
    注意点:
    (1)Binarizer参数有输入、输出以及阀值。
    (2)特征值大于阀值将映射为1.0,特征值小于等于阀值将映射为0.0。
    
5.主成分分析
    定义及用途:主成分分析(PCA)是一种统计学方法,本质是在线性空间中进行一个基变换,
        使得变换后的数据投影在一组新的"坐标轴"上的方差最大化。根据变换后
        方差大小确定坐标轴的权重或者重要性,权重高的成为主成分。主要用于降维。
    应用场景:主要用于降维。
    在spark ml中示例:将5维特征向量转换为3维主成分向量。
import org.apache.spark.ml.feature.PCA
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.ml.feature.StandardScaler
val data = Array(
  Vectors.sparse(5, Seq((1, 1.0), (3, 7.0))),
  Vectors.dense(2.0, 0.0, 3.0, 4.0, 5.0), Vectors.dense(4.0, 0.0, 0.0, 6.0, 7.0))
val df = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")
val scaledDataFrame = new StandardScaler().setInputCol("features").setOutputCol("scaledFeatures").fit(df).transform(df)
// 设置K值
val pca = new PCA().setInputCol("features").setOutputCol("pcaFeatures").setK(3).fit(scaledDataFrame)
val pcaDF = pca.transform(scaledDataFrame)
pcaDF.select("features","pcaFeatures").show(false)    
+---------------------+-----------------------------------------------------------------+-----------------------------------------------------------+
|features             |scaledFeatures                                                   |pcaFeatures                                                |
+---------------------+-----------------------------------------------------------------+-----------------------------------------------------------+
|(5,[1,3],[1.0,7.0])  |(5,[1,3],[1.7320508075688774,4.58257569495584])                  |[1.6485728230883807,-4.013282700516296,-5.524543751369388] |
|[2.0,0.0,3.0,4.0,5.0]|[1.0,0.0,1.7320508075688776,2.6186146828319083,1.386750490563073]|[-4.645104331781534,-1.1167972663619026,-5.524543751369387]|
|[4.0,0.0,0.0,6.0,7.0]|[2.0,0.0,0.0,3.9279220242478625,1.941450686788302]               |[-6.428880535676489,-5.337951427775355,-5.524543751369389] |
+---------------------+-----------------------------------------------------------------+-----------------------------------------------------------+

    注意点:
    (1)PCA前一定要对特征向量进行标准化。因为各主成分之间值变化太大,有数量级的差别。
    标准化特征向量后各主成分之间基本在同一个水平,结果更合理。
    (2)如何选择K值?
for(x<-pcaModel.explainedVariance.toArray){
    println(i+"\t"+x+" ")
    i += 1
}
//运行结果发现,随着k的增加,精度趋于平稳。

6.多项式展开
    定义及用途:通过产生n维组合将原始特征扩展到多项式空间。
    应用场景:
    在spark ml中示例:将特征集拓展到3维多项式空间。
import org.apache.spark.ml.feature.PolynomialExpansion
import org.apache.spark.ml.linalg.Vectors

val data = Array(
  Vectors.dense(2.0, 1.0),
  Vectors.dense(0.0, 0.0),
  Vectors.dense(3.0, -1.0)
)
val df = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")
val polynomialExpansion = new PolynomialExpansion().setInputCol("features").setOutputCol("polyFeatures").setDegree(3)
val polyDF = polynomialExpansion.transform(df)
polyDF.show(false);

    注意点:
    
    
7.离散余弦变换
    定义及用途:离散余弦变换是与傅里叶变换相关的一种变换,它类似于离散傅立叶变换但是只使用实数。
        离散余弦变换相当于一个长度大概是它两倍的离散傅里叶变换,这个离散傅里叶变换是对一个实偶函数进行的(因为一个实偶函数的傅里叶变换仍然是一个实偶函数)
    应用场景:用于对信号和图像(包括静止图像和运动图像)进行有损数据压缩。
    在spark ml中示例:
import org.apache.spark.ml.feature.DCT
import org.apache.spark.ml.linalg.Vectors

val data = Seq(
  Vectors.dense(0.0, 1.0, -2.0, 3.0),
  Vectors.dense(-1.0, 2.0, 4.0, -7.0),
  Vectors.dense(14.0, -2.0, -5.0, 1.0))

val df = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")
val dct = new DCT().setInputCol("features").setOutputCol("featuresDCT").setInverse(false)
val dctDf = dct.transform(df)
dctDf.show(false)
+--------------------+----------------------------------------------------------------+
|features            |featuresDCT                                                     |
+--------------------+----------------------------------------------------------------+
|[0.0,1.0,-2.0,3.0]  |[1.0,-1.1480502970952693,2.0000000000000004,-2.7716385975338604]|
|[-1.0,2.0,4.0,-7.0] |[-1.0,3.378492794482933,-7.000000000000001,2.9301512653149677]  |
|[14.0,-2.0,-5.0,1.0]|[4.0,9.304453421915744,11.000000000000002,1.5579302036357163]   |
+--------------------+----------------------------------------------------------------+

    注意点:
    
8.字符串-索引变换
    定义及用途:字符串-索引变换(StringIndexer)是将字符串列编码为标签索引列。索引从0开始,按照标签频率排序。
        最常见的标签索引0即代表频率最高的标签。
    应用场景:Spark的机器学习处理过程中,经常需要把标签数据(一般是字符串)转化成整数索引,而在计算结束又
        需要把整数索引还原为标签。
    在spark ml中示例:
import org.apache.spark.ml.feature.StringIndexer
val df = spark.createDataFrame(
  Seq((0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "c"))
).toDF("id", "category")

val indexer = new StringIndexer().setInputCol("category").setOutputCol("categoryIndex")
val indexed = indexer.fit(df).transform(df)
indexed.show(false)
+---+--------+-------------+
|id |category|categoryIndex|
+---+--------+-------------+
|0  |a       |0.0          |
|1  |b       |2.0          |
|2  |c       |1.0          |
|3  |a       |0.0          |
|4  |a       |0.0          |
|5  |c       |1.0          |
+---+--------+-------------+
    注意点:
        有的时候我们通过一个数据集构建了一个StringIndexer,然后准备把它应用到另一个数据集上的时候,
    会遇到新数据集中有一些没有在前一个数据集中出现的标签,这时候一般有两种策略来处理:
    第一种是抛出一个异常(默认情况下),第二种是通过掉用 setHandleInvalid(“skip”)来彻底忽略包含这类标签的行。

9.索引-字符串变换
    定义及用途:对称的,IndexToString的作用是把标签索引的一列重新映射回原有的字符型标签。一般都是和StringIndexer配合,
        先用StringIndexer转化成标签索引,进行模型训练,然后在预测标签的时候再把标签索引转化成原有的字符标签。
        这个主要从列的元数据中推断。
    应用场景:
    在spark ml中示例:
import org.apache.spark.ml.feature.{IndexToString, StringIndexer}
val df = spark.createDataFrame(Seq(
  (0, "a"),(1, "b"),(2, "c"),(3, "a"),(4, "a"),(5, "c")
)).toDF("id", "category")
val indexer = new StringIndexer().setInputCol("category").setOutputCol("categoryIndex").fit(df)
val indexed = indexer.transform(df)
val converter = new IndexToString().setInputCol("categoryIndex").setOutputCol("originalCategory")
val converted = converter.transform(indexed)
converted.show(false)
+---+--------+-------------+----------------+
|id |category|categoryIndex|originalCategory|
+---+--------+-------------+----------------+
|0  |a       |0.0          |a               |
|1  |b       |2.0          |b               |
|2  |c       |1.0          |c               |
|3  |a       |0.0          |a               |
|4  |a       |0.0          |a               |
|5  |c       |1.0          |c               |
+---+--------+-------------+----------------+

    注意点:
    
10.独热编码
    定义及用途:独热编码(OneHotEncoder)将标签指标映射为二值变量。
    应用场景:这种编码可以使期望连续特征的算法使用分类特征,如逻辑回归等。
    在spark ml中示例:
import org.apache.spark.ml.feature.{OneHotEncoder, StringIndexer}
val df = spark.createDataFrame(Seq(
  (0, "a"),(1, "b"),(2, "c"),(3, "a"),(4, "a"),(5, "c"),(6, "d")
)).toDF("id", "category")
val indexer = new StringIndexer().setInputCol("category").setOutputCol("categoryIndex").fit(df)
val indexed = indexer.transform(df)
val encoder = new OneHotEncoder().setInputCol("categoryIndex").setOutputCol("categoryVec")
val encoded = encoder.transform(indexed)
encoded.show(false)
+---+--------+-------------+-------------+
|id |category|categoryIndex|categoryVec  |
+---+--------+-------------+-------------+
|0  |a       |0.0          |(2,[0],[1.0])|
|1  |b       |2.0          |(2,[],[])    |
|2  |c       |1.0          |(2,[1],[1.0])|
|3  |a       |0.0          |(2,[0],[1.0])|
|4  |a       |0.0          |(2,[0],[1.0])|
|5  |c       |1.0          |(2,[1],[1.0])|
+---+--------+-------------+-------------+

    注意点:
    (1)OneHotEncoder缺省状态下将删除最后一个分类或把最后一个分类作为0。
        上例中最后一个分类b变为向量后已被删除。
    (2)如果想不删除最后一个分类,可以添加setDropLast(false)。

11.向量-索引变换
    定义及用途:向量-索引变换(VectorIndexer)能提高决策树或者随机森林等ML方法的分类效果,
        是对数据集特征向量中的类别(离散值)特征进行编号。它能够自动判断哪些特征是
        离散值类型特征,并对他们进行编号。
    应用场景:
    在spark ml中示例:下面例子中读入一个数据集,然后使用VectorIndexer来决定哪些特征需要被作为非数值类型处理,
        将非数值型特征转换为他们的索引。
import org.apache.spark.ml.feature.VectorIndexer
import org.apache.spark.ml.linalg.Vectors
val data=Seq(
      Vectors.dense(-1,1,1,8,56),Vectors.dense(-1,3,-1,-9,88),
      Vectors.dense(0,5,1,10,96), Vectors.dense(0,5,1,11,589))
val df=spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")    
val indexer = new VectorIndexer().setInputCol("features").setOutputCol("indexed").setMaxCategories(3)
val indexerModel = indexer.fit(df)
val indexedData = indexerModel.transform(df)
indexedData.show(false)
+-------------------------+-----------------------+
|features                 |indexed                |
+-------------------------+-----------------------+
|[-1.0,1.0,1.0,8.0,56.0]  |[1.0,0.0,1.0,1.0,56.0] |
|[-1.0,3.0,-1.0,-9.0,88.0]|[1.0,1.0,0.0,0.0,88.0] |
|[0.0,5.0,1.0,10.0,96.0]  |[0.0,2.0,1.0,2.0,96.0] |
|[0.0,5.0,1.0,11.0,589.0] |[0.0,2.0,1.0,3.0,589.0]|
|[0.0,5.0,1.0,11.0,688.0] |[0.0,2.0,1.0,3.0,688.0]|
+-------------------------+-----------------------+
    注意点:
        设置maxCategories为2,即只有种类小于2的特征才被认为是类别型特征,否则被认为是连续型特征。

12.交互式
    定义及用途:
    应用场景:
    在spark ml中示例:
    注意点:
    交互(Interaction)是一个变换器,
    输入是向量或者双值列,输出是双值列的所有组合的乘积。

13.正则化
    定义及用途:Normalizer的作用范围是每一行,使每一个行向量的范数变换为一个单位范数。
        参数为p(默认值:2)来指定正则化中使用的p-norm。正则化操作可以使输入数据标准化并提高后期学习算法的效果。
    ||x||p = (|x1|p + |x2|p)1/p    
    ...........................
    应用场景:
    在spark ml中示例:下面的例子展示如何读入一个libsvm格式的数据,然后将每一行转换为\[{L^2}\]以及\[{L^\infty }\]形式。
import org.apache.spark.ml.feature.Normalizer
val data=Seq(Vectors.dense(-1,1,1,8,56),Vectors.dense(-1,3,-1,-9,88),
      Vectors.dense(0,5,1,10,96), Vectors.dense(0,5,1,11,589),
      Vectors.dense(0,5,1,11,688))
val df=spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")    
val normalizer = new Normalizer().setInputCol("features").setOutputCol("normFeatures").setP(1.0)
val l1NormData = normalizer.transform(df)
l1NormData.show(false)
+-------------------------+--------------------------------------------------------------------------------------------------------+
|features                 |normFeatures                                                                                            |
+-------------------------+--------------------------------------------------------------------------------------------------------+
|[-1.0,1.0,1.0,8.0,56.0]  |[-0.014925373134328358,0.014925373134328358,0.014925373134328358,0.11940298507462686,0.835820895522388] |
|[-1.0,3.0,-1.0,-9.0,88.0]|[-0.00980392156862745,0.029411764705882353,-0.00980392156862745,-0.08823529411764706,0.8627450980392157]|
|[0.0,5.0,1.0,10.0,96.0]  |[0.0,0.044642857142857144,0.008928571428571428,0.08928571428571429,0.8571428571428571]                  |
|[0.0,5.0,1.0,11.0,589.0] |[0.0,0.00825082508250825,0.0016501650165016502,0.018151815181518153,0.971947194719472]                  |
|[0.0,5.0,1.0,11.0,688.0] |[0.0,0.0070921985815602835,0.0014184397163120568,0.015602836879432624,0.975886524822695]                |
+-------------------------+--------------------------------------------------------------------------------------------------------+
    注意点:


14.规范化
    定义及用途:对于同一个特征,不同的样本中的取值可能会相差非常大,一些异常小或异常大的数据会误导模型的正确训练;
    另外,如果数据的分布很分散也会影响训练结果。以上两种方式都体现在方差会非常大。此时,我们可以将特征中的值进行
    标准差标准化,即转换为均值为0,方差为1的正态分布。规范化(StandardScaler)处理的对象是每一列,也就是每一维特征,
    标准化每个特征使得其有统一的标准差或者均值为零,方差为1。
    x* = (x-u)/标准差
    应用场景:
    在spark ml中示例:将每一列的标准差缩放到1
import org.apache.spark.ml.feature.StandardScaler
import org.apache.spark.ml.linalg.Vectors
val dataFrame = spark.createDataFrame(Seq(
(0, Vectors.dense(1.0, 0.5, -1.0)),(1, Vectors.dense(2.0, 1.0, 1.0)),
(2, Vectors.dense(4.0, 10.0, 2.0)))).toDF("id", "features")
val scaler = new StandardScaler().setInputCol("features").setOutputCol("scaledFeatures").setWithStd(true).setWithMean(false)
val scalerModel = scaler.fit(dataFrame)
val scaledData = scalerModel.transform(dataFrame)
scaledData.show(false)
+---+--------------+------------------------------+
|id |features      |scaledFeatures                |
+---+--------------+------------------------------+
|0  |[1.0,-1.0,2.0]|[0.0,-1.0,1.0910894511799618] |
|1  |[2.0,0.0,0.0] |[1.0,0.0,-0.21821789023599236]|
|2  |[0.0,1.0,-1.0]|[-1.0,1.0,-0.8728715609439694]|
+---+--------------+------------------------------+

    注意点:
    如果特征的标准差为零,则该特征在向量中返回的默认值为0.0。
    
    
15.最大值-最小值缩放
    定义及用途:最大值-最小值缩放(MinMaxScaler)通过重新调节大小将Vector形式的行转换到制定的范围内,通常为[0,1]
        该模型可以将独立的特征的值转换到指定的范围内。
        x*=(x-min)/(max-min)
    应用场景:
    在spark ml中示例:将数据调整其特征值到[0,1]之间
import org.apache.spark.ml.feature.MinMaxScaler
import org.apache.spark.ml.linalg.Vectors
val dataFrame = spark.createDataFrame(Seq(
(0, Vectors.dense(1.0, 0.5, -1.0, 0)),
(1, Vectors.dense(2.0, 1.0, 1.0, 0)),
(2, Vectors.dense(4.0, 10.0, 2.0, 0))
)).toDF("id", "features")
val scaler = new MinMaxScaler().setInputCol("features").setOutputCol("scaledFeatures")
// Compute summary statistics and generate MinMaxScalerModel
val scalerModel = scaler.fit(dataFrame)
// rescale each feature to range [min, max].
val scaledData = scalerModel.transform(dataFrame)
scaledData.show(false)
+---+------------------+---------------------------------------------------------------+
|id |features          |scaledFeatures                                                 |
+---+------------------+---------------------------------------------------------------+
|0  |[1.0,0.5,-1.0,0.0]|[0.0,0.0,0.0,0.5]                                              |
|1  |[2.0,1.0,1.0,0.0] |[0.3333333333333333,0.05263157894736842,0.6666666666666666,0.5]|
|2  |[4.0,10.0,2.0,0.0]|[1.0,1.0,1.0,0.5]                                              |
+---+------------------+---------------------------------------------------------------+
    注意点:注意因为零值转换后可能变为非零值,所以即便为稀疏输入,输出也可能为稠密向量。
    
16.最大值-绝对值缩放
    定义及用途:最大值-绝对值缩放(MaxAbsScaler)转换Vector行的数据,通过划分每个特征的最大绝对值,将每个特征重新缩放到[-1,1]中。
    应用场景:
    在spark ml中示例:
import org.apache.spark.ml.feature.MaxAbsScaler
import org.apache.spark.ml.linalg.Vectors
val dataFrame = spark.createDataFrame(Seq(
(0, Vectors.dense(1.0, 0.5, -1.0)),
(1, Vectors.dense(2.0, 1.0, 1.0)),
(2, Vectors.dense(4.0, 10.0, 2.0))
)).toDF("id", "features")
val scaler = new MaxAbsScaler().setInputCol("features").setOutputCol("scaledFeatures")
// Compute summary statistics and generate MaxAbsScalerModel
val scalerModel = scaler.fit(dataFrame)
// rescale each feature to range [-1, 1]
val scaledData = scalerModel.transform(dataFrame)
scaledData.show(false)
+---+--------------+----------------+
|id |features      |scaledFeatures  |
+---+--------------+----------------+
|0  |[1.0,0.5,-1.0]|[0.25,0.05,-0.5]|
|1  |[2.0,1.0,1.0] |[0.5,0.1,0.5]   |
|2  |[4.0,10.0,2.0]|[1.0,1.0,1.0]   |
+---+--------------+----------------+
    注意点:
    
    
17.离散化重组
    定义:离散化重组(Bucketizer)是将一列连续特征转换为一列特征桶,其中的桶由用户指定。
    应用场景:
    在spark ml中示例:特征划分在4个分段内
import org.apache.spark.ml.feature.Bucketizer
val splits = Array(Double.NegativeInfinity, -0.5, 0.0, 0.5, Double.PositiveInfinity)
val data = Array(-8.0, -0.5, -0.3, 0.0, 0.2, 9.0)
val dataFrame = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")
val bucketizer = new Bucketizer().setInputCol("features").setOutputCol("bucketedFeatures").setSplits(splits)
// Transform original data into its bucket index.
val bucketedData = bucketizer.transform(dataFrame)
bucketedData.show(false)    
+--------+----------------+
|features|bucketedFeatures|
+--------+----------------+
|-8.0    |0.0             |
|-0.5    |1.0             |
|-0.3    |1.0             |
|0.0     |2.0             |
|0.2     |2.0             |
|9.0     |3.0             |
+--------+----------------+
    注意点:
    (1)每个分段是左闭右开[a,b)方式。
    (2)当不确定分裂的上下边界时,应当添加Double.NegativeInfinity和Double.PositiveInfinity以免越界。
    
18.元素乘积
    定义及用途:ElementwiseProduct按提供的“weight”向量,返回与输入向量元素级别的乘积。
        即是说,按提供的权重分别对输入数据进行缩放,得到输入向量v以及权重向量w的Hadamard积。
    应用场景:
    在spark ml中示例:
    注意点:
    
    
19.SQL转换器
    定义及用途:SQL转换器(SQLTransformer)可以实现由SQL语句的定义的转换。
        支持spark sql的任何select字句,还可以使用spark sql内置函数和udf操作这些选定的列。
    应用场景:
    在spark ml中示例:
import org.apache.spark.ml.feature.SQLTransformer
val df = spark.createDataFrame(
  Seq((0, 1.0, 3.0), (2, 2.0, 5.0))).toDF("id", "v1", "v2")
val sqlTrans = new SQLTransformer().setStatement(
  "SELECT *, (v1 + v2) AS v3, (v1 * v2) AS v4 FROM __THIS__")
sqlTrans.transform(df).show()
+---+---+---+---+----+
| id| v1| v2| v3|  v4|
+---+---+---+---+----+
|  0|1.0|3.0|4.0| 3.0|
|  2|2.0|5.0|7.0|10.0|
+---+---+---+---+----+

    注意点:
    
    
20.向量汇编
    定义及用途:VectorAssembler是一个转换器,它将给定的若干列合并为单列向量。它可以将原始特征和一系列
        通过其他转换器变换得到的特征合并为单一的特征向量,来训练如逻辑回归和决策树等机器学习算法。
        VectorAssembler可接受的输入列类型:数值型、布尔型、向量型。输入列的值将按指定顺序依次添加到一个新向量中。
        可以把它理解为一个特征拼接的过程。
    应用场景:
    在spark ml中示例:
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.linalg.Vectors
val dataset = spark.createDataFrame(
  Seq((0, 18, 1.0, Vectors.dense(0.0, 10.0, 0.5), 1.0))
).toDF("id", "hour", "mobile", "userFeatures", "clicked")
val assembler = new VectorAssembler().setInputCols(Array("hour", "mobile", "userFeatures")).setOutputCol("features")
val output = assembler.transform(dataset)
output.show(false)
    注意点:
   
    
21.分位数离散化
    定义:分位数离散化(QuantileDiscretizer)是将具有连续功能的列转换为带有分级分类功能的列。
        分级数量有参数numBuckets设置。
    应用场景:
    在spark ml中示例:
import org.apache.spark.ml.feature.QuantileDiscretizer
val data = Array((0, 18.0), (1, 19.0), (2, 8.0), (3, 5.0), (4, 2.2))
var df = spark.createDataFrame(data).toDF("id", "hour")
val discretizer = new QuantileDiscretizer().setInputCol("hour").setOutputCol("result").setNumBuckets(3)
val result = discretizer.fit(df).transform(df)
result.show()
    注意:对于NaN值的处理。在QuantileDiscretizer拟合过程中,可以设置选择保留或者删除NaN值。

三、特征选择
    特征选择是从特征向量中选择那些更有效的特征,组成新的、更简单有效的特征向量的过程。
    适用于在高维数据分析中,可以剔除冗余或影响不大的特征,提升模型的性能。
1.向量机 VectorSlicer
    定义及用途:VectorSlicer用于从原来的特征向量中切割一部分,形成新的特征向量,比如,原来的特征向量长度为10,
        我们希望切割其中的5~10作为新的特征向量,使用VectorSlicer可以快速实现。
        这个转换器可以支持用户自定义选择列,可以基于下标索引,也可以基于列名。
        这个选择器的实际作用就是选择某些列的特征,在dataframe出现之后可以直接选择。当时出现的原因可能是基于RDD的考虑。
    应用场景:这个选择器适合那种有很多特征,并且明确知道自己想要哪个特征的情况。比如你有一个很全的用户画像系统,每个人有成百上千个特征,
        但是你指向抽取用户对电影感兴趣相关的特征,因此只要手动选择一下就可以了。
    在spark ml中示例:
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
 
val data = Arrays.asList(Row(Vectors.dense(-2.0, 2.3, 0.0)))
val defaultAttr = NumericAttribute.defaultAttr
val attrs = Array("f1", "f2", "f3").map(defaultAttr.withName)
val attrGroup = new AttributeGroup("userFeatures", attrs.asInstanceOf[Array[Attribute]])
val dataset = spark.createDataFrame(data, StructType(Array(attrGroup.toStructField())))
val slicer = new VectorSlicer().setInputCol("userFeatures").setOutputCol("features")
slicer.setIndices(Array(1)).setNames(Array("f3"))
val output = slicer.transform(dataset)
output.show(false)
+--------------+---------+
|userFeatures  |features |
+--------------+---------+
|[-2.0,2.3,0.0]|[2.3,0.0]|
+--------------+---------+

    注意点:
    (1)可以同时使用setInices和setName;
    (2)如果下标和索引重复,会报重复的错;
    (3)如果下标不存在会报错;
    (4)如果名称不存在也会报错;
    (5)经过特征选择后,特征的顺序与索引和名称的顺序相同。
    
2.R公式
    定义及用途:RFormula产生一个特征向量和一个double或者字符串标签列(label)。就如R中使用formulas一样,字符型的输入将转换成one-hot编码,
            数字输入转换成双精度。如果类别列是字符串类型,它将通过StringIndexer转换为double类型索引。
            如果标签列不存在,则formulas输出中将通过特定的响应变量创造一个标签列。 
            如果不用这个,可能要反复进行StringIndex...OneHotEncoder...。
    应用场景:这个选择器适合在需要做OneHotEncoder的时候,可以一个简单的代码把所有的离散特征转化成数值化表示。
    在spark ml中示例:使用RFormula公式clicked ~ country + hour,则表明我们希望基于country 和hour预测clicked
import org.apache.spark.ml.feature.RFormula
val dataset = spark.createDataFrame(Seq(
  (7, "US", 18, 1.0),(8, "CA", 12, 0.0),(9, "NZ", 15, 0.0)
)).toDF("id", "country", "hour", "clicked")
val formula = new RFormula().setFormula("clicked ~ country + hour").setFeaturesCol("features").setLabelCol("label")
val output = formula.fit(dataset).transform(dataset)
output.show(false)
+---+-------+----+-------+--------------+-----+
|id |country|hour|clicked|features      |label|
+---+-------+----+-------+--------------+-----+
|7  |US     |18  |1.0    |[1.0,0.0,18.0]|1.0  |
|8  |CA     |12  |0.0    |[0.0,0.0,12.0]|0.0  |
|9  |NZ     |15  |0.0    |[0.0,1.0,15.0]|0.0  |
+---+-------+----+-------+--------------+-----+

    注意点:
    
    
3.卡方特征选择
    定义及用途:卡方特征选择(ChiSqSelector)代表卡方特征选择。它适用于带有类别特征的标签数据。
    ChiSqSelector根据分类的卡方独立性检验来对特征排序,选取类别标签主要依赖的特征。
    它类似于选取最有预测能力的特征。
    ML目前支持三种选择方法,numTopFeatures、percentile和fpr。
    
    numTopFeatures:根据卡方测试选择固定数量的排名靠前的几个特征。这类似于产生具有最大预测能力的特征。
    percentile:类似于numTopFeatures,但是按照百分比选择特征,而不是固定数量。
    fpr:选择p值低于阈值的所有特征,从而控制选择的假阳性率。
    应用场景:卡方检验选择器适合在你有比较多的特征,但是不知道这些特征哪个有用,哪个没用,
        想要通过某种方式帮助你快速筛选特征,那么这个方法很适合。
    在spark ml中示例:
    
import org.apache.spark.ml.feature.ChiSqSelector
import org.apache.spark.ml.linalg.Vectors
val data = Seq(
  (7, Vectors.dense(0.0, 0.0, 18.0, 1.0), 1.0), 
  (8, Vectors.dense(0.0, 1.0, 12.0, 0.0), 0.0),
  (9, Vectors.dense(1.0, 0.0, 15.0, 0.2), 0.0))
val df = spark.createDataset(data).toDF("id", "features", "clicked")
val selector = new ChiSqSelector().setNumTopFeatures(2).setFeaturesCol("features").setLabelCol("clicked").setOutputCol("selectedFeatures")
val result = selector.fit(df).transform(df)
result.show(false)
+---+------------------+-------+----------------+
|id |features          |clicked|selectedFeatures|
+---+------------------+-------+----------------+
|7  |[0.0,0.0,18.0,1.0]|1.0    |[18.0,1.0]      |
|8  |[0.0,1.0,12.0,0.0]|0.0    |[12.0,0.0]      |
|9  |[1.0,0.0,15.0,0.2]|0.0    |[15.0,0.2]      |
+---+------------------+-------+----------------+

你可能感兴趣的:(机器学习)