SparkMllib特征提取、转换、选择

SparkMllib特征提取、转换、选择

SparkMllib特征提取

TF-IDF的理解

TF(Term frequence):某个词在一个文档中出现的频率。
T F = 某 个 词 在 一 个 文 档 中 出 现 的 次 数 这 个 文 档 中 词 的 总 数 TF=\frac{某个词在一个文档中出现的次数}{这个文档中词的总数} TF=
有些停用词几乎在所有的文章中都有出现,故只使用词频来进行计算是不合适的,这个时候就引入了IDF。

IDF(Inverse document frequency):就是说在一个语料库中,有很多个文档,那么文档库中出现某个词的文档数越大,说明这个词汇在更多的文档中出现,所以其重要性相对较低。底为自然对数e
I D F = l o g 语 料 库 中 文 档 的 总 数 ( 语 料 库 中 出 现 某 个 词 的 文 档 的 总 数 + 1 ) IDF=log\frac{语料库中文档的总数}{(语料库中出现某个词的文档的总数+1)} IDF=log(+1)
IDF跟TF相乘,就得到了TF-IDF算法的核心公式。
T F − I D F = ( T F ) 词 频 ∗ ( I D F ) 逆 文 档 频 率 TF-IDF=(TF)词频*(IDF)逆文档频率 TFIDF=(TF)(IDF)

  • 该算法的优点:

  • 简单快速,容易理解。

  • 缺点

    • 使用词频来衡量一个文章中某个词的重要程度不够严谨
    • 这种计算无法体现位置信息,无法体现词在上下文中的重要性,word2vec算法可以解决这个问题。
    import org.apache.spark.ml.feature.{HashingTF, IDF, IDFModel, Tokenizer}
    import org.apache.spark.sql.{DataFrame, SparkSession}
    
    /**
      * tf-idf=tf*idf
      * tf词频-----一个词在文章中出现的次数/文章中的总词数
      * idf逆文档频率------log(文章总数/(包含该词的文档数+1))
      *
      */
    object tf_idf_count2 {
      def main(args: Array[String]): Unit = {
        // 1. 构建SparkSession的环境
        val session: SparkSession = SparkSession.builder().appName("tf_idf_count2").master("local[*]").getOrCreate()
    
        // 2. 构建语料库,文章ID,和文章中的语句
        val seqdata = Seq(
          (0, "Hi I hear about spark"),
          (1, "I wish Java could be case class"),
          (2, "Logistic Regression model are near")
        )
        // 3. 转换成DataFrame格式的数据
        val input: DataFrame = session.createDataFrame(seqdata).toDF("docId","sentence")
    //    input.show(false)
    //    +-----+----------------------------------+
    //    |docId|sentence                          |
    //    +-----+----------------------------------+
    //    |0    |Hi I hear about spark             |
    //    |1    |I wish Java could be case class   |
    //    |2    |Logistic Regression model are near|
    //    +-----+----------------------------------+
        // 4-1. 构建分词器,一般继承transform方法的类实例化的对象都会有setInputCol,setOutputCol方法
        val tokenizer: Tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
        // 4-2.通过分词器对句子进行分词
        val wordDf: DataFrame = tokenizer.transform(input)
        wordDf.show(false)
    //    +-----+----------------------------------+----------------------------------------+
    //    |docId|sentence                          |words                                |
    //    +-----+----------------------------------+----------------------------------------+
    //    |0    |Hi I hear about spark             |[hi, i, hear, about, spark]             |
    //    |1    |I wish Java could be case class   |[i, wish, java, could, be, case, class] |
    //    |2    |Logistic Regression model are near|[logistic, regression, model, are, near]|
    //    +-----+----------------------------------+----------------------------------------+
        // 4-3 构建HashingTF,词频统计器
        val hashingTf: HashingTF = new HashingTF().setInputCol("words").setOutputCol("tf")
        // 4-4 通过词频统计器来统计词频
        val hashingTfResult: DataFrame = hashingTf.transform(wordDf)
        hashingTfResult.show(false)
    //    +-----+----------------------------------+----------------------------------------+-------------------------------------------------------------------------------------+
    //    |docId|sentence                          |words                                |tf                                                                                   |
    //    +-----+----------------------------------+----------------------------------------+-------------------------------------------------------------------------------------+
    //    |0    |Hi I hear about spark             |[hi, i, hear, about, spark]             |(262144,[24417,49304,91137,111370,234657],[1.0,1.0,1.0,1.0,1.0])                     |
    //    |1    |I wish Java could be case class   |[i, wish, java, could, be, case, class] |(262144,[19862,20719,24417,55551,147765,167152,192310],[1.0,1.0,1.0,1.0,1.0,1.0,1.0])|
    //    |2    |Logistic Regression model are near|[logistic, regression, model, are, near]|(262144,[13671,92225,150278,167122,190884],[1.0,1.0,1.0,1.0,1.0])                    |
    //    +-----+----------------------------------+----------------------------------------+-------------------------------------------------------------------------------------+
        // 4-5 配置IDF模型的参数
        val idf: IDF = new IDF().setInputCol("tf").setOutputCol("features")
        // 4-6 训练IDF模型
        val idfModel: IDFModel = idf.fit(hashingTfResult)
        // 4-7 调用transform方法,得到每一个单词对应的TFIDF
        val tfidf: DataFrame = idfModel.transform(hashingTfResult)
        tfidf.show(false)
        // 可以看到第一句和第二句都出现了i,对应的hash值为24417,这个i对应的tfidf的值就比较小,是0.28768207245178085
    //    ----------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    //    words                                   |features                                                                                                                                                                                       |
    //    ----------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    //    [hi, i, hear, about, spark]             |(262144,[24417,49304,91137,111370,234657],[0.28768207245178085,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453])                                                   |
    //    [i, wish, java, could, be, case, class] |(262144,[19862,20719,24417,55551,147765,167152,192310],[0.6931471805599453,0.6931471805599453,0.28768207245178085,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453])|
    //    [logistic, regression, model, are, near]|(262144,[13671,92225,150278,167122,190884],[0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453])                                                   |
    //    ----------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    
      }
    }
    

Word2Vec

  • word2vec,字面意思,将word转化为vector,word是顺序有意义的实体,比如文档中单词、用户依次点击的商品。
  • word2vec是NLP领域的重要算法,它的功能是将word用K维的dense vector来表达,训练集是语料库,不含标点,以空格断句。因此可以看作是种特征处理方法。
  • word2vec两种实现方式:
    • Skip-gram:用一个词语作为输入,来预测它周围的上下文。同义词p(word1|word2)
    • CBOW :用一个词语的上下文作为输入,来预测这个词语本身。完形填空p(word1|word2,word3)

CountVectorizer

CountVectorizer并CountVectorizerModel旨在帮助将一组文本文档转换为标签计数的向量。当apriori字典不可用时,CountVectorizer可以用作Estimator提取词汇表,并生成一个CountVectorizerModel。该模型为词汇表上的文档生成稀疏表示,然后可以将其传递给其他算法,如LDA。

在拟合过程中,CountVectorizer将选择vocabSize按语料库中的术语频率排序的顶部单词。可选参数minDF还通过指定术语必须出现在文档中的最小数量(或<1.0)来影响拟合过程。另一个可选的二进制切换参数控制输出向量。如果设置为true,则所有非零计数都设置为1.这对于模拟二进制而非整数计数的离散概率模型特别有用。

SparkMllib特征转换

Binarizer

二值化,数值特征的值通过跟阈值比较转换成二进制(0/1)的过程,需要设定一个阈值,>阈值得到的特征值被二进制化为1.0,<=阈值得到的特征值被二进制化为0.0,支持Vector,Double类型的inputCol

object BinarizerTest {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
    val context: SparkContext = spark.sparkContext
    //1.data
    val data = spark.createDataFrame(Array((0, 0.1), (1, 0.8), (2, 0.5))).toDF("label", "features")
    //2.二值化的操作---estimator or  transform
    // 继承Transformer类型,不需要fit,直接transform就好,estimator,需要fit得到模型对象,然后通过模型对象进行transform
    val binarizer: Binarizer = new Binarizer().setInputCol("features").setOutputCol("binarizer").setThreshold(0.5)
    val result: DataFrame = binarizer.transform(data)
    result.show(false)
  }
}

PCA

PCA是Principal Component Analysis的缩写,汉语称为主成分分析,主要使用的场景是多维度特征降低维度到低纬度:寻找方差最大化的操作。

import org.apache.spark.SparkContext
import org.apache.spark.ml.feature.{PCA, PCAModel}
import org.apache.spark.ml.linalg
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SparkSession}

/**
  *主成分分析法,方差越大说明特征越重要。
  */
object PCATest1 {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
    val sc: SparkContext = spark.sparkContext

    val vectors: Array[linalg.Vector] = Array(
      Vectors.sparse(5, Seq((1, 30.0), (2, 40.0), (4, 100.0))),
      Vectors.dense(10, 234, 44, 33, 55),
      Vectors.dense(1, 34, 4, 3, 5)
    )
    val rdd: RDD[linalg.Vector] = sc.parallelize(vectors)
    // 将Array中的成员转化成一个个的一元元组
    val df: DataFrame = spark.createDataFrame(rdd.map(Tuple1.apply)).toDF("features")

    val pca: PCA = new PCA().setInputCol("features").setOutputCol("pca_features").setK(3)
    val pcaModel: PCAModel = pca.fit(df)
    val pcaDf: DataFrame = pcaModel.transform(df)
    pcaDf.show(false)

//    +-----------------------------+-----------------------------------------------------------+
//    |features                     |pca_features                                               |
//    +-----------------------------+-----------------------------------------------------------+
//    |(5,[1,2,4],[30.0,40.0,100.0])|[-35.060835042756494,-106.13187833133475,0.455442077573025]|
//      |[10.0,234.0,44.0,33.0,55.0]  |[-240.5674422053953,-55.014030054036596,0.4554420775730357]|
//    |[1.0,34.0,4.0,3.0,5.0]       |[-34.36209692083503,-4.414073768726447,0.4554420775730281] |
//    +-----------------------------+-----------------------------------------------------------+
  }
}

StringIndexer,IndexToString

  • StringIndexer:将标签列的字符串类型编码为标签索引列,索引从0开始,到标签列中标签种类的个数减去1,标签列中标签出现的越频繁,编码后的索引越小,因此最频繁出现的标签的索引为0。

  • 当我们使用StringIndexer将标签列转换成标签索引列,使用监督算法,训练好了一个模型,进行了预测,想要把预测后的标签列转换成原来标签对应的字符串,这个时候就引入IndexToString了。

    import org.apache.spark.ml.feature.{IndexToString, StringIndexer, StringIndexerModel}
    import org.apache.spark.sql.{DataFrame, SparkSession}
    
    object StringToIndexerToString {
      def main(args: Array[String]): Unit = {
        // 1. 构建环境
        val sparkSession: SparkSession = SparkSession.builder().master("local[*]").appName("StringToIndexerToString").getOrCreate()
    
        // 2. 构建数据集
        val data: Seq[(Int, String)] = Seq((1,"x"),(2,"y"),(3,"z"),(4,"z"),(5,"x"),(6,"z"))
    
        // 3. 转化成DataFrame结构的数据
        val df: DataFrame = sparkSession.createDataFrame(data).toDF("id","StringLabel")
    
        // 4. 创建StringIndexer对象
        val stringIndexer: StringIndexer = new StringIndexer().setInputCol("StringLabel").setOutputCol("IndexLabel")
    
        // 5. 训练StringIndexerModel模型
        val stringIndexerModel: StringIndexerModel = stringIndexer.fit(df)
        // 6. 通过模型转换原始数据中的标签列的字符串值编码成数字类型的值。
        val indexerDf: DataFrame = stringIndexerModel.transform(df)
        indexerDf.show(false)
    //    +---+-----------+----------+
    //    |id |StringLabel|IndexLabel|
    //    +---+-----------+----------+
    //    |1  |x          |1.0       |
    //    |2  |y          |2.0       |
    //    |3  |z          |0.0       |
    //    |4  |z          |0.0       |
    //    |5  |x          |1.0       |
    //    |6  |z          |0.0       |
    //    +---+-----------+----------+
        //
        // 当我们通过上面的转换好的数据,使用监督算法,训练好了一个模型,进行了预测,想要把预测后的标签列转换成原来标签的字符串
        // 这个时候就引入了IndexToString了
        // 7. 构建IndexToString对象,指定好输入列和输出列
        val indexToString: IndexToString = new IndexToString().setInputCol("IndexLabel").setOutputCol("IndexLabelToString")
        // 8. 进行transform转换上面已经存在"IndexLabel"列的DataFrame类型的数据
        val indexToStringDf: DataFrame = indexToString.transform(indexerDf)
        indexToStringDf.show(false)
      }
    }
    
    

OneHotEncoder

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

  • 优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。

  • 缺点:当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用PCA来减少维度。而且one hot encoding+PCA这种组合在实际中也非常有用。

import org.apache.spark.ml.feature.{OneHotEncoder, StringIndexer, StringIndexerModel}
import org.apache.spark.sql.{DataFrame, SparkSession}

/**
  * 类别1  类别2  类别3
  * 0       0     1
  * 0       1     0
  * 1       0     0
  */
object OneHotEncoderTest1 {
  def main(args: Array[String]): Unit = {
    // 1. 构建SparkSession环境
    val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
    // 2. 构建data测试数据集
    val data = Seq((0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "c"))
    val df: DataFrame = spark.createDataFrame(data).toDF("id", "categories")
    // A one-hot encoder that maps a column of category indices to a column of binary vectors
    // 注意在使用OneHotEncoder()时,输入的列需要是标签索引的类型,所以标签需要进行索引编码,使用StringIndexer。
    // 3-1. 构建StringIndexer对象
    val stringIndexer: StringIndexer = new StringIndexer().setInputCol("categories").setOutputCol("indies")
    // 3-2. 训练StringIndexerModel
    val model: StringIndexerModel = stringIndexer.fit(df)
    // 3-3. 对标签列编码成索引标签列
    val stringLabel: DataFrame = model.transform(df)
    // 4-1. 创建OneHotEncoder对象
    // Spark源码: The last category is not included by default 最后一个种类默认不包含
    // 和python scikit-learn's OneHotEncoder不同,scikit-learn's OneHotEncoder包含所有
    val ohe: OneHotEncoder = new OneHotEncoder().setInputCol("indies").setOutputCol("ohe")
    //    Whether to drop the last category in the encoded vector (default: true)
      // todo 注意这个地方默认是true,会将最后一个类别丢弃掉,如果不想丢弃掉,需要设置成false.
      .setDropLast(false)
    // 4-2. 对索引标签进行独热编码的转换

    val oheDf: DataFrame = ohe.transform(stringLabel)
    oheDf.show(false)
//    |id |categories|indies|ohe          |
//    +---+----------+------+-------------+
//    |0  |a         |0.0   |(3,[0],[1.0])|
//    |1  |b         |2.0   |(3,[2],[1.0])|
//    |2  |c         |1.0   |(3,[1],[1.0])|
//    |3  |a         |0.0   |(3,[0],[1.0])|
//    |4  |a         |0.0   |(3,[0],[1.0])|
//    |5  |c         |1.0   |(3,[1],[1.0])|
//    +---+----------+------+-------------+
  }
}

VectorIndexer

  • VectorIndexer将向量转化为indexer索引,在决策树方面能够帮助构建模型

  • 多个向量组合成一个表结构,VectorIndexer转化的是以列为转化的角度。

    import org.apache.spark.ml.feature.{VectorIndexer, VectorIndexerModel}
    import org.apache.spark.ml.linalg.Vectors
    import org.apache.spark.sql.{DataFrame, SparkSession}
    
    /**
      * VectorIndexer将向量转化为indexer索引,在决策树方面能够帮助构建模型
      * 多个向量组合成一个表结构,VectorIndexer转化的是以列为转化的角度。
      * 比如下面的最后一列的值为10,11,12,100,6,那么对应的索引为:1.0,2.0,3.0,4.0,0.0
      * 真实值跟索引是升序的关系,即真实值最小的那个值的索引为0
      *  setMaxCategories,设置最多转化多少列为indexer索引。
      */
    object VectorIndexerTest1 {
      def main(args: Array[String]): Unit = {
        val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
        val data=Seq(
          Vectors.dense(-1,1,1,8,56,10),
          Vectors.dense(-1,3,-1,-9,88,11),
          Vectors.dense(0,5,1,10,96,12),
          Vectors.dense(0,5,1,11,589,100),
          Vectors.dense(0,5,1,11,688,6)
        )
        val df: DataFrame = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")
        //1-定义操作
        val indexr: VectorIndexer = new VectorIndexer().setInputCol("features").setOutputCol("features_indexer").setMaxCategories(5)
        //2-训练estemator
        val model: VectorIndexerModel = indexr.fit(df)
        val set1: Set[Int] = model.categoryMaps.keys.toSet
        println(set1.mkString(","))//0,5,1,2,3,4
        //3-展示和转化
        model.transform(df).show(false)
    //    +------------------------------+-------------------------+
    //    |features                      |features_indexer         |
    //    +------------------------------+-------------------------+
    //    |[-1.0,1.0,1.0,8.0,56.0,10.0]  |[1.0,0.0,1.0,1.0,0.0,1.0]|
    //    |[-1.0,3.0,-1.0,-9.0,88.0,11.0]|[1.0,1.0,0.0,0.0,1.0,2.0]|
    //    |[0.0,5.0,1.0,10.0,96.0,12.0]  |[0.0,2.0,1.0,2.0,2.0,3.0]|
    //    |[0.0,5.0,1.0,11.0,589.0,100.0]|[0.0,2.0,1.0,3.0,3.0,4.0]|
    //    |[0.0,5.0,1.0,11.0,688.0,6.0]  |[0.0,2.0,1.0,3.0,4.0,0.0]|
    //    +------------------------------+-------------------------+
      }
    }
    
    

    Normalizer

    import org.apache.spark.ml.feature.Normalizer
    import org.apache.spark.ml.linalg.Vectors
    import org.apache.spark.sql.{DataFrame, SparkSession}
    
    /**
      *
      * 正则罚项是L1和L2正则罚项
      * 范数的含义是距离的函数 --- 闵可夫斯基距离
      * L1范数  L1正则罚项------稀疏化特征
      * L2范数  L2正则罚项------凸函数
      */
    object NormL1L2Test1 {
      def main(args: Array[String]): Unit = {
        val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
        val dataframe = spark.createDataFrame(Seq(
          (0, Vectors.dense(1.0, 1.5, -1.0)),
          (1, Vectors.dense(2.0, 1.0, 1.0)),
          (2, Vectors.dense(4.0, 10.0, 2.0))
        )).toDF("id", "features")
        //使用L1正则罚项
        val norm: Normalizer = new Normalizer().setInputCol("features").setOutputCol("normfeatures").setP(1.0)
        val l1Norm: DataFrame = norm.transform(dataframe)
        l1Norm.show(truncate = false)
        // todo L1正则罚项如何计算的呢?比如[1.0,1.5,-1.0]怎么得到的=>[0.2857142857142857,0.42857142857142855,-0.2857142857142857]
        // todo 累加向量中每一项的解绝对值,每一项在除以上一步的累加和,|1.0|+|1.5|+|-1.0|=3.5=>[1/3.5,1.5/3.5,-1.0/3.5]=>[0.2857142857142857,0.42857142857142855,-0.2857142857142857]
        //    |id |features      |normfeatures                                                |
        //    +---+--------------+------------------------------------------------------------+
        //    |0  |[1.0,1.5,-1.0]|[0.2857142857142857,0.42857142857142855,-0.2857142857142857]|
        //    |1  |[2.0,1.0,1.0] |[0.5,0.25,0.25]                                             |
        //    |2  |[4.0,10.0,2.0]|[0.25,0.625,0.125]                                          |
        //    +---+--------------+------------------------------------------------------------+
    
        //使用L2正则罚项
        val l2Norm: DataFrame = norm.transform(dataframe, norm.p -> 2)
        l2Norm.show(truncate = false)
        // todo L2正则罚项如何求解的?[1.0,1.5,-1.0]|[0.48507125007266594,0.7276068751089989,-0.48507125007266594]
        // todo 向量中每一项平方后求和,然后开方 sqrt{1.0*1.0+1.5*1.5+(-1.0)*(-1.0)}=2.061552812808830274910704927987
        // todo 向量中每一项除以上一项的开方后的结果[1.0/2.0615528128,1.5/2.0615528128,(-1.0)/2.0615528128]
        //    +---+--------------+-------------------------------------------------------------+
        //    |id |features      |normfeatures                                                 |
        //    +---+--------------+-------------------------------------------------------------+
        //    |0  |[1.0,1.5,-1.0]|[0.48507125007266594,0.7276068751089989,-0.48507125007266594]|
        //    |1  |[2.0,1.0,1.0] |[0.8164965809277261,0.4082482904638631,0.4082482904638631]   |
        //    |2  |[4.0,10.0,2.0]|[0.3651483716701107,0.9128709291752769,0.18257418583505536]  |
      }
    }
    

数值型数据处理的三种方法

StanderScaler
  • 标准化的处理数据—数值型数据
    StandardScaler,有两个参数比较重要,一个是setWithMean,每一列的每一个元素是否需要减去当前列的平均值,默认是false.
    setWithStd,每一列中的每一个数据是否需要除以当前列的样本标准差,(注意是样本标准差而不是标准差,主要是分母除以n-1哦),默认是true.
MaxMinScaler
  • 最大值最小值处理的处理0-1,当前列的每一个数据 - 当前列的最小值 / (当前列的最大值 - 最小值)
MaxAbsScaler
  • 最大值绝对值的处理: -1到1之间 当前列中每一个数据 / 当前列中数据的绝对值的最大值
import org.apache.spark.ml.feature._
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.sql.SparkSession

/**
  * Standscaler:标准化操作--通过减去均值除以方差来得到转化后的数据--转化为0均值1方差的数据,按列进行计算的。
  * minmax:数据-最小值/最大值-最小值---将数据缩放到【0,1】,按列进行计算的。
  * maxabs:将数据缩放到[-1,1]之间,按列进行计算的。
  */
object StandScaler_MinMax_MaxAbsTest1 {
  def main(args: Array[String]): Unit = {


    val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
    spark.sparkContext.setLogLevel("WARN")
    val rawdata = 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 path1 = "E:\\ml\\workspace\\SparkMllibBase\\sparkmllib_part1\\sparkmllib_base\\data\\sample_libsvm_data.txt"
//    val rawdata: DataFrame = spark.read.format("libsvm").load(path1)
    //标准化的处理数据---数值型数据
    // StandardScaler,有两个参数比较重要,一个是setWithMean,每一列的每一个元素是否需要减去当前列的平均值,默认是false.
    // setWithStd,每一列中的每一个数据是否需要除以当前列的样本标准差,(注意是样本标准差而不是标准差,主要是分母除以n-1哦),默认是true.
    val stanscaler: StandardScaler = new StandardScaler().setInputCol("features").setOutputCol("StandardScaler").setWithMean(true).setWithStd(true)
    val model_sta: StandardScalerModel = stanscaler.fit(rawdata)
    model_sta.transform(rawdata).select("StandardScaler").show(false)
    // 最大值最小值处理的处理0-1,当前列的每一个数据 - 当前列的最小值 / (当前列的最大值 - 最小值)
    val minmax: MinMaxScaler =new MinMaxScaler().setInputCol("features").setOutputCol("MinMaxScaler")
    val model_sta1: MinMaxScalerModel = minmax.fit(rawdata)
    model_sta1.transform(rawdata).select("MinMaxScaler").show(false)
    //最大值绝对值的处理: -1到1之间   当前列中每一个数据 / 当前列中数据的绝对值的最大值
    val maxabs: MaxAbsScaler = new MaxAbsScaler().setInputCol("features").setOutputCol("MaxAbsScaler")
    val model_sta2: MaxAbsScalerModel = maxabs.fit(rawdata)
    model_sta2.transform(rawdata).select("MaxAbsScaler").show(false)
  }
}

Bucketizer

Bucketizer其实是Binarizer的升级版本,Binarizer是将连续值离散化成两个值(1.0/0.0),升级成离散化成多个值。

import org.apache.spark.ml.feature.Bucketizer
import org.apache.spark.sql.{DataFrame, SparkSession}

/**
  * Bucketizer---分箱或分桶的操作---将连续值属性离散化
  */
object BuckizerTest1 {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
    //准备分箱的区间
    val splits = Array(Double.NegativeInfinity, -0.5, 0.0, 0.5, Double.PositiveInfinity)
    //准备数据
    val dataframe = spark.createDataFrame(Array(-10, -0.5, -0.3, 0.0, 0.2).map(Tuple1.apply)).toDF("features")
    //分箱操作
    val buckizer: Bucketizer = new Bucketizer().setInputCol("features").setOutputCol("bucketfeatures").setSplits(splits)
    val backetResultData: DataFrame = buckizer.transform(dataframe)
    backetResultData.show()
    //    +--------+--------------+
    //    |features|bucketfeatures|
    //    +--------+--------------+
    //    |   -10.0|           0.0|
    //      |    -0.5|           1.0|
    //      |    -0.3|           1.0|
    //      |     0.0|           2.0|
    //      |     0.2|           2.0|
  }
}

VectorAssembler

将多个分散数据整合成一个数据,分散的数据需要是数值类型的数据-整合成的一个数据可以用户训练模型。

import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.sql.{DataFrame, SparkSession}

/**
  * VectorAssemble将若干分散的数据整合为一个完成数据--可以用于训练模型
  *  id | hour | mobile | userFeatures     | clicked | features
  * ----|------|--------|------------------|---------|--------------------------
  * 0  | 110   | 1.0    | [0.0, 10.0, 0.5] | 1.0     | [18.0, 1.0, 0.0, 10.0, 0.5]
  */
object VectorAssembleTest1 {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
    val data=Seq(
      (0,110,1.0,Vectors.dense(0.0,10.0,0.5),1.0)
    )
    val df: DataFrame = spark.createDataFrame(data).toDF("id","hour","mobile","userfeatures","click")
    //1-定义操作
    val resultdata=new VectorAssembler().setInputCols(Array("hour","mobile","userfeatures")).setOutputCol("features").transform(df)
    resultdata.show(false)
//    +---+----+------+--------------+-----+-----------------------+
//    |id |hour|mobile|userfeatures  |click|features               |
//    +---+----+------+--------------+-----+-----------------------+
//    |0  |110  |1.0   |[0.0,10.0,0.5]|1.0  |[110.0,1.0,0.0,10.0,0.5]|
//    +---+----+------+--------------+-----+-----------------------+
    println(resultdata.select("features","click").first())//| [18.0, 1.0, 0.0, 10.0, 0.5]
  }
}

QuantileDiscretizer

QuantileDiscretizer采用具有连续特征的列,并输出具有分箱分类特征的列。箱数由numBuckets参数设定。使用近似算法选择bin范围(有关详细说明,请参阅aboutQuantile的文档 )。

import org.apache.spark.ml.feature.QuantileDiscretizer
import org.apache.spark.sql.{DataFrame, SparkSession}

/**
  * QuantileDiscretizer将连续值的属性离散化的操作
  * 将整个连续值的通过最大值-最小值平均分成几份。最小的那一组对应的结果为0.0
  * 划分的范围是左闭右开的,下面的例子中连续值区间是0-18
  * 分位数的概念进行离散化
  * +---+----+------+
  * | id|hour|result|
  * +---+----+------+
  * |  0|17.0|   2.0|
  * |  1|18.0|   2.0|
  * |  2| 8.0|   1.0|
  * |  3| 5.0|   1.0|
  * |  4| 2.2|   0.0|
  * +---+----+------+
  */
object QuantileTest1 {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
    val data=Array((0,17.0),(1,18.0),(2,8.0),(3,5.0),(4,2.2))
    val df: DataFrame = spark.createDataFrame(data).toDF("id","hour")
    //连续值属性离散化的操作
    val discriter: QuantileDiscretizer = new QuantileDiscretizer().setInputCol("hour").setOutputCol("result").setNumBuckets(3)
    val result: DataFrame = discriter.fit(df).transform(df)
    result.show()
  }
}

SparkMllib特征选择

VectorSlicer

通过特征列的索引和特征列的名字来选择特征

import java.util
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.types.StructType
import org.apache.spark.sql.{DataFrame, Row, SparkSession}

/**
  * VectorSlicer---根据下标或者特征列的名字选择特征
  * userFeatures     | features
  * ------------------|-----------------------------
  * [0.0, 10.0, 0.5] | [10.0, 0.5]
  * ["f1", "f2", "f3"] | ["f2", "f3"]
  */
object VectorSlicerTest2 {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder().master("local[*]").getOrCreate()
    spark.sparkContext.setLogLevel("WARN")
    // 1. 构造一个Vector
    val data = util.Arrays.asList(Row(Vectors.dense(-2.0, 2.3, 0.0)))
    // 2. 构建一个Array
    val attrs: Array[NumericAttribute] = Array("f1", "f2", "f3").map(NumericAttribute.defaultAttr.withName)
    for (elem <- attrs) {println(elem)}
//    {"type":"numeric","name":"f1"}
//    {"type":"numeric","name":"f2"}
//    {"type":"numeric","name":"f3"}
    // 3. 构建一个组,一个组中还分成3个属性值,属性的个数个向量的维数相同
    val attGroup: AttributeGroup = new AttributeGroup("userFeatures", attrs.asInstanceOf[Array[Attribute]])
    println(attGroup)
//    {"ml_attr":{"attrs":{"numeric":[{"idx":0,"name":"f1"},{"idx":1,"name":"f2"},{"idx":2,"name":"f3"}]},"num_attrs":3}}
    // 4-构造df,一个df中的一个列有一个列名,这个列名下还可以分成各个属性值
    val df = spark.createDataFrame(data, StructType(Array(attGroup.toStructField())))

    //5-1-构造分割器
    val slicer: VectorSlicer = new VectorSlicer().setInputCol("userFeatures").setOutputCol("features")
    //5-2设定如何选择特征列
    slicer.setIndices(Array(0)).setNames(Array("f2"))
    //5-3直接得到结果
    val result: DataFrame = slicer.transform(df)
    result.show(false)
    //    +--------------+---------+
    //    |  userFeatures| features|
    //    +--------------+---------+
    //    |[-2.0,2.3,0.0]|[2.3,0.0]|
    //    +--------------+---------+

  }
}

RFormula

通过回归方程来选择特征

RFormula选择由R模型公式指定的列。目前,我们支持R运算符的有限子集,包括’〜’,’。’,’:’,’+‘和’ - '。基本的运营商是:

  • ~ 单独的目标和条款

  • + concat术语,“+ 0”表示删除拦截

  • - 删除一个术语,“ - 1”表示删除拦截

  • : 交互(数值乘法或二进制分类值)

  • . 除目标之外的所有列

假设a并且b是双列,我们使用以下简单示例来说明以下效果RFormula:

  • y ~ a + b意味着模型y ~ w0 + w1 * a + w2 * b在哪里w0是截距并且w1, w2是系数。
  • y ~ a + b + a:b - 1表示模型y ~ w1 * a + w2 * b + w3 * a * b在哪里w1, w2, w3是系数。

RFormula生成一个特征向量列和一个标签的双列或字符串列。就像在R中使用公式进行线性回归一样,字符串输入列将是**独热编码**的,而数字列将被转换为双精度。如果label列的类型为string,则首先将其转换为double StringIndexer。如果DataFrame中不存在标签列,则将从公式中的指定响应变量创建输出标签列。

package sparkmllib_base.features_select_05

import org.apache.spark.ml.feature.RFormula
import org.apache.spark.sql.{DataFrame, SparkSession}

/**
  * R公式---y=kx+b结构选额特征
  * 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
  */
object RFormulaTest1 {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder()
      .appName("SparkMlilb")
      .master("local[1]")
      .getOrCreate()
    spark.sparkContext.setLogLevel("WARN")
    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: RFormula = new RFormula().setFormula("clicked ~ country + hour")
      .setFeaturesCol("features").setLabelCol("clicked")

    val result: DataFrame = formula.fit(dataset).transform(dataset)
    result.select("features", "clicked").show()
    //    +--------------+-------+
    //    |      features|clicked|
    //    +--------------+-------+
    //    |[0.0,0.0,18.0]|    1.0|
    //      |[1.0,0.0,12.0]|    0.0|
    //      |[0.0,1.0,15.0]|    0.0|
    //      +--------------+-------+
  }
}

卡方验证

根据假设检验的方法来选择特征-选择相关性较高的特征

  • 卡方检验选择特征的原因
    • 建模的目标尽量简单的模型实现尽量好的效果,减少一些价值贡献小的特征。
    • 那怎么判断特征的价值小呢?那些对因变量不产生影响或影响较小的变量。
    • 那怎么判断特征对因变量的影响程度呢?卡方检验对特征与因变量进行独立性检验,如果独立性高,说明特征与标签没有太大关系,特征可以舍弃,相反,特征应该被选择。
import org.apache.spark.ml.feature.ChiSqSelector
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.sql.{DataFrame, SparkSession}

/**
  * 通过假设检验的方法--卡方验证的原理
  * 假设其中一列数据和类别标签列的数据相关的,通过卡方验证得到的结果结果是比较大的,说明偏差较大,拒绝原假设
  * 通过卡方验证选择和类别标签列相关的TopN的特征
  * id | features              | clicked | selectedFeatures
  * ---|----------------------|--------|------------------
  * 7 | [0.0, 0.0, 18.0, 1.0] | 1.0     | [1.0]
  * 8 | [0.0, 1.0, 12.0, 0.0] | 0.0     | [0.0]
  * 9 | [1.0, 0.0, 15.0, 0.1] | 0.0     | [0.1]
  */
object ChiAquareTest {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder()
      .appName("SparkMlilb")
      .master("local[2]")
      .getOrCreate()
    spark.sparkContext.setLogLevel("WARN")
    import spark.implicits._
    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.1), 0.0)
    )

    val df = spark.createDataset(data).toDF("id", "features", "clicked")
    df.show()

    val selector: ChiSqSelector = new ChiSqSelector().setNumTopFeatures(2).setFeaturesCol("features").setLabelCol("clicked").setOutputCol("selectFeatures")
    val result: DataFrame = selector.fit(df).transform(df)
    result.show()
//    +---+------------------+-------+--------------+
//    | id|          features|clicked|selectFeatures|
//    +---+------------------+-------+--------------+
//    |  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.1]|    0.0|    [15.0,0.1]|
//    +---+------------------+-------+--------------+
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值