决策树独热onehotencoding

http://dblab.xmu.edu.cn/blog/1297-2/



Spark的机器学习处理过程中,经常需要把标签数据(一般是字符串)转化成整数索引,而在计算结束又需要把整数索引还原为标签。这就涉及到几个转换器:StringIndexer、 IndexToString,OneHotEncoder,以及针对类别特征的索引VectorIndexer。

StringIndexer

​ StringIndexer是指把一组字符型标签编码成一组标签索引,索引的范围为0到标签数量,索引构建的顺序为标签的频率,优先编码频率较大的标签,所以出现频率最高的标签为0号。如果输入的是数值型的,我们会把它转化成字符型,然后再对其进行编码。在pipeline组件,比如Estimator和Transformer中,想要用到字符串索引的标签的话,我们一般需要通过setInputCol来设置输入列。另外,有的时候我们通过一个数据集构建了一个StringIndexer,然后准备把它应用到另一个数据集上的时候,会遇到新数据集中有一些没有在前一个数据集中出现的标签,这时候一般有两种策略来处理:第一种是抛出一个异常(默认情况下),第二种是通过掉用 setHandleInvalid("skip")来彻底忽略包含这类标签的行。

   
   
  1. import org.apache.spark.SparkConf
  2. import org.apache.spark.SparkContext
  3. import org.apache.spark.sql.SQLContext
  4. import org.apache.spark.ml.feature.StringIndexer
  5.  
  6. scala> val sqlContext = new SQLContext(sc)
  7. sqlContext: org.apache.spark.sql.SQLContext = org.apache.spark.sql.SQLContext@2869d920
  8.  
  9. scala> import sqlContext.implicits._
  10. import sqlContext.implicits._
  11.  
  12. scala> val df1 = sqlContext.createDataFrame(
  13. | Seq((0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "c"))
  14. | ).toDF("id", "category")
  15. df1: org.apache.spark.sql.DataFrame = [id: int, category: string]
  16.  
  17. scala> val indexer = new StringIndexer().
  18. | setInputCol("category").
  19. | setOutputCol("categoryIndex")
  20. indexer: org.apache.spark.ml.feature.StringIndexer = strIdx_95a0a5afdb8b
  21.  
  22. scala> val indexed1 = indexer.fit(df1).transform(df1)
  23. indexed1: org.apache.spark.sql.DataFrame = [id: int, category: string, categoryIndex: double]
  24.  
  25. scala> indexed1.show()
  26. +---+--------+-------------+
  27. | id|category|categoryIndex|
  28. +---+--------+-------------+
  29. | 0| a| 0.0|
  30. | 1| b| 2.0|
  31. | 2| c| 1.0|
  32. | 3| a| 0.0|
  33. | 4| a| 0.0|
  34. | 5| c| 1.0|
  35. +---+--------+-------------+
  36.  
  37. scala> val df2 = sqlContext.createDataFrame(
  38. | Seq((0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "d"))
  39. | ).toDF("id", "category")
  40. df2: org.apache.spark.sql.DataFrame = [id: int, category: string]
  41.  
  42. scala> val indexed2 = indexer.fit(df1).setHandleInvalid("skip").transform(df2)
  43. indexed2: org.apache.spark.sql.DataFrame = [id: int, category: string, categoryIndex: double]
  44.  
  45. scala> indexed2.show()
  46. +---+--------+-------------+
  47. | id|category|categoryIndex|
  48. +---+--------+-------------+
  49. | 0| a| 0.0|
  50. | 1| b| 2.0|
  51. | 2| c| 1.0|
  52. | 3| a| 0.0|
  53. | 4| a| 0.0|
  54. +---+--------+-------------+
  55.  
  56. scala> val indexed3 = indexer.fit(df1)transform(df2)
  57. indexed3: org.apache.spark.sql.DataFrame = [id: int, category: string, categoryIndex: double]
  58.  
  59. scala> indexed3.show()
  60. org.apache.spark.SparkException: Unseen label: d.
scala

​ 在上例当中,我们首先构建了1个dataframe,然后设置了StringIndexer的输入列和输出列的名字。通过indexed1.show(),我们可以看到,StringIndexer依次按照出现频率的高低,把字符标签进行了排序,即出现最多的“a”被编号成0,“c”为1,出现最少的“b”为0。接下来,我们构建了一个新的dataframe,这个dataframe中有一个再上一个dataframe中未曾出现的标签“d”,然后我们通过设置setHandleInvalid("skip")来忽略标签“d”的行,结果通过indexed2.show()可以看到,含有标签“d”的行并没有出现。如果,我们没有设置的话,则会抛出异常,报出“Unseen label: d”的错误。

IndexToString

​ 对称的,IndexToString的作用是把标签索引的一列重新映射回原有的字符型标签。一般都是和StringIndexer配合,先用StringIndexer转化成标签索引,进行模型训练,然后在预测标签的时候再把标签索引转化成原有的字符标签。当然,也允许你使用自己提供的标签。

   
   
  1. import org.apache.spark.SparkConf
  2. import org.apache.spark.SparkContext
  3. import org.apache.spark.sql.SQLContext
  4. import org.apache.spark.ml.feature.{StringIndexer, IndexToString}
  5.  
  6. scala> val sqlContext = new SQLContext(sc)
  7. sqlContext: org.apache.spark.sql.SQLContext = org.apache.spark.sql.SQLContext@2869d920
  8.  
  9. scala> import sqlContext.implicits._
  10. import sqlContext.implicits._
  11.  
  12. scala> val df = sqlContext.createDataFrame(Seq(
  13. | (0, "a"),
  14. | (1, "b"),
  15. | (2, "c"),
  16. | (3, "a"),
  17. | (4, "a"),
  18. | (5, "c")
  19. | )).toDF("id", "category")
  20. df: org.apache.spark.sql.DataFrame = [id: int, category: string]
  21.  
  22. scala> val indexer = new StringIndexer().
  23. | setInputCol("category").
  24. | setOutputCol("categoryIndex").
  25. | fit(df)
  26. indexer: org.apache.spark.ml.feature.StringIndexerModel = strIdx_00fde0fe64d0
  27.  
  28. scala> val indexed = indexer.transform(df)
  29. indexed: org.apache.spark.sql.DataFrame = [id: int, category: string, categoryIndex: double]
  30.  
  31. scala> val converter = new IndexToString().
  32. | setInputCol("categoryIndex").
  33. | setOutputCol("originalCategory")
  34. converter: org.apache.spark.ml.feature.IndexToString = idxToStr_b95208a0e7ac
  35.  
  36. scala> val converted = converter.transform(indexed)
  37. converted: org.apache.spark.sql.DataFrame = [id: int, category: string, categoryIndex: double, originalCategory: string]
  38.  
  39. scala> converted.select("id", "originalCategory").show()
  40. +---+----------------+
  41. | id|originalCategory|
  42. +---+----------------+
  43. | 0| a|
  44. | 1| b|
  45. | 2| c|
  46. | 3| a|
  47. | 4| a|
  48. | 5| c|
  49. +---+----------------+
scala

​ 在上例中,我们首先用StringIndexer读取数据集中的“category”列,把字符型标签转化成标签索引,然后输出到“categoryIndex”列上。然后再用IndexToString读取“categoryIndex”上的标签索引,获得原有数据集的字符型标签,然后再输出到“originalCategory”列上。最后,通过输出“originalCategory”列,可以看到数据集中原有的字符标签。

OneHotEncoder

​ 独热编码是指把一列标签索引映射成一列二进制数组,且最多的时候只有一位有效。这种编码适合一些期望类别特征为连续特征的算法,比如说逻辑斯蒂回归。

   
   
  1. import org.apache.spark.SparkConf
  2. import org.apache.spark.SparkContext
  3. import org.apache.spark.sql.SQLContext
  4. import org.apache.spark.ml.feature.{OneHotEncoder, StringIndexer}
  5.  
  6. scala> val sqlContext = new SQLContext(sc)
  7. sqlContext: org.apache.spark.sql.SQLContext = org.apache.spark.sql.SQLContext@2869d920
  8.  
  9. scala> import sqlContext.implicits._
  10. import sqlContext.implicits._
  11.  
  12. scala> val df = sqlContext.createDataFrame(Seq(
  13. | (0, "a"),
  14. | (1, "b"),
  15. | (2, "c"),
  16. | (3, "a"),
  17. | (4, "a"),
  18. | (5, "c"),
  19. | (6, "d"),
  20. | (7, "d"),
  21. | (8, "d"),
  22. | (9, "d"),
  23. | (10, "e"),
  24. | (11, "e"),
  25. | (12, "e"),
  26. | (13, "e"),
  27. | (14, "e")
  28. | )).toDF("id", "category")
  29. df: org.apache.spark.sql.DataFrame = [id: int, category: string]
  30.  
  31. scala> val indexer = new StringIndexer().
  32. | setInputCol("category").
  33. | setOutputCol("categoryIndex").
  34. | fit(df)
  35. indexer: org.apache.spark.ml.feature.StringIndexerModel = strIdx_b315cf21d22d
  36.  
  37. scala> val indexed = indexer.transform(df)
  38. indexed: org.apache.spark.sql.DataFrame = [id: int, category: string, categoryIndex: double]
  39.  
  40. scala> val encoder = new OneHotEncoder().
  41. | setInputCol("categoryIndex").
  42. | setOutputCol("categoryVec")
  43. encoder: org.apache.spark.ml.feature.OneHotEncoder = oneHot_bbf16821b33a
  44.  
  45. scala> val encoded = encoder.transform(indexed)
  46. encoded: org.apache.spark.sql.DataFrame = [id: int, category: string, categoryIndex: double, categoryVec: vector]
  47.  
  48. scala> encoded.show()
  49. +---+--------+-------------+-------------+
  50. | id|category|categoryIndex| categoryVec|
  51. +---+--------+-------------+-------------+
  52. | 0| a| 2.0|(4,[2],[1.0])|
  53. | 1| b| 4.0| (4,[],[])|
  54. | 2| c| 3.0|(4,[3],[1.0])|
  55. | 3| a| 2.0|(4,[2],[1.0])|
  56. | 4| a| 2.0|(4,[2],[1.0])|
  57. | 5| c| 3.0|(4,[3],[1.0])|
  58. | 6| d| 1.0|(4,[1],[1.0])|
  59. | 7| d| 1.0|(4,[1],[1.0])|
  60. | 8| d| 1.0|(4,[1],[1.0])|
  61. | 9| d| 1.0|(4,[1],[1.0])|
  62. | 10| e| 0.0|(4,[0],[1.0])|
  63. | 11| e| 0.0|(4,[0],[1.0])|
  64. | 12| e| 0.0|(4,[0],[1.0])|
  65. | 13| e| 0.0|(4,[0],[1.0])|
  66. | 14| e| 0.0|(4,[0],[1.0])|
  67. +---+--------+-------------+-------------+
scala

​ 在上例中,我们构建了一个dataframe,包含“a”,“b”,“c”,“d”,“e” 五个标签,通过调用OneHotEncoder,我们发现出现频率最高的标签“e”被编码成第0位为1,即第0位有效,出现频率第二高的标签“d”被编码成第1位有效,依次类推,“a”和“c”也被相继编码,出现频率最小的标签“b”被编码成全0。

VectorIndexer

        VectorIndexer解决向量数据集中的类别特征索引。它可以自动识别哪些特征是类别型的,并且将原始值转换为类别索引。它的处理流程如下:

​ 1.获得一个向量类型的输入以及maxCategories参数。

​ 2.基于不同特征值的数量来识别哪些特征需要被类别化,其中最多maxCategories个特征需要被类别化。

​ 3.对于每一个类别特征计算0-based(从0开始)类别索引。

​ 4.对类别特征进行索引然后将原始特征值转换为索引。

       索引后的类别特征可以帮助决策树等算法恰当的处理类别型特征,并得到较好结果。

       在下面的例子中,我们读入一个数据集,然后使用VectorIndexer来决定哪些特征需要被作为类别特征,将类别特征转换为他们的索引。

   
   
  1. import org.apache.spark.SparkConf
  2. import org.apache.spark.SparkContext
  3. import org.apache.spark.sql.SQLContext
  4. import org.apache.spark.ml.feature.VectorIndexer
  5. import org.apache.spark.mllib.linalg.{Vector, Vectors}
  6.  
  7. scala> val sqlContext = new SQLContext(sc)
  8. sqlContext: org.apache.spark.sql.SQLContext = org.apache.spark.sql.SQLContext@2869d920
  9.  
  10. scala> import sqlContext.implicits._
  11. import sqlContext.implicits._
  12.  
  13. scala> val data = Seq(Vectors.dense(-1.0, 1.0, 1.0),Vectors.dense(-1.0, 3.0, 1.0), Vectors.dense(0.0, 5.0, 1.0))
  14. data: Seq[org.apache.spark.mllib.linalg.Vector] = List([-1.0,1.0,1.0], [-1.0,3.0,1.0], [0.0,5.0,1.0])
  15.  
  16. scala> val df = sqlContext.createDataFrame(data.map(Tuple1.apply)).toDF("features")
  17. df: org.apache.spark.sql.DataFrame = [features: vector]
  18.  
  19. scala> val indexer = new VectorIndexer().
  20. | setInputCol("features").
  21. | setOutputCol("indexed").
  22. | setMaxCategories(2)
  23. indexer: org.apache.spark.ml.feature.VectorIndexer = vecIdx_abee81bafba8
  24.  
  25. scala> val indexerModel = indexer.fit(df)
  26. indexerModel: org.apache.spark.ml.feature.VectorIndexerModel = vecIdx_abee81bafba8
  27.  
  28. scala> val categoricalFeatures: Set[Int] = indexerModel.categoryMaps.keys.toSet
  29. categoricalFeatures: Set[Int] = Set(0, 2)
  30.  
  31. scala> println(s"Chose ${categoricalFeatures.size} categorical features: " + categoricalFeatures.mkString(", "))
  32. Chose 2 categorical features: 0, 2
  33.  
  34. scala> val indexedData = indexerModel.transform(df)
  35. indexedData: org.apache.spark.sql.DataFrame = [features: vector, indexed: vector]
  36.  
  37. scala> indexedData.foreach { println }
  38. [[-1.0,1.0,1.0],[1.0,1.0,0.0]]
  39. [[-1.0,3.0,1.0],[1.0,3.0,0.0]]
  40. [[0.0,5.0,1.0],[0.0,5.0,0.0]]
scala

​ 从上例可以看到,我们设置maxCategories为2,即只有种类小于2的特征才被认为是类别型特征,否则被认为是连续型特征。其中类别型特征将被进行编号索引,为了索引的稳定性,规定如果这个特征值为0,则一定会被编号成0,这样可以保证向量的稀疏度(未来还会再维持索引的稳定性上做更多的工作,比如如果某个特征类别化后只有一个特征,则会进行警告等等,这里就不过多介绍了)。于是,我们可以看到第0类和第2类的特征由于种类数不超过2,被划分成类别型特征,并进行了索引,且为0的特征值也被编号成了0号。


猜你喜欢

转载自blog.csdn.net/weixin_35780812/article/details/78802369