1. 程式人生 > 其它 >Spark ML中的特徵轉換演算法——OneHotEncoder

Spark ML中的特徵轉換演算法——OneHotEncoder

一、為什麼要用獨熱編碼?

在很多機器學習任務中,特徵並不總是連續值,而有可能是分類值。

離散特徵的編碼分為兩種情況:

1、離散特徵的取值之間沒有大小的意義,比如color:[red,blue],那麼就使用one-hot編碼

2、離散特徵的取值有大小的意義,比如size:[X,XL,XXL],那麼就使用數值的對映{X:1,XL:2,XXL:3}

例如,考慮一下的三個特徵:

["male", "female"]

["from Europe", "from US", "from Asia"]

["uses Firefox", "uses Chrome", "uses Safari", "uses Internet Explorer
"]

如果將上述特徵用數字表示,效率會高很多。例如

["male", "from US", "uses Internet Explorer"] 表示為[0, 1, 3]

["female", "from Asia", "uses Chrome"]表示為[1, 2, 1]

但是,即使轉化為數字表示後,上述資料也不能直接用在我們的分類器中。因為,分類器往往預設資料資料是連續的(可以計算距離?),並且是有序的(而上面這個0並不是說比1要高階)。但是,按照我們上述的表示,數字並不是有序的,而是隨機分配的。

為了解決上述問題,其中一種可能的解決方法是採用獨熱編碼(One-Hot Encoding)。獨熱編碼即 One-Hot 編碼,又稱一位有效編碼,其方法是使用N位狀態暫存器來對N個狀態進行編碼,每個狀態都由他獨立的暫存器位,並且在任意時候,其中只有一位有效。

例如:

自然狀態碼為:000,001,010,011,100,101
獨熱編碼為:000001,000010,000100,001000,010000,100000

可以這樣理解,對於每一個特徵,如果它有m個可能值,那麼經過獨熱編碼後,就變成了m個二元特徵。如上例所示,自然碼有6個狀態,每個狀態都可以由6位的獨熱編碼(0、1)解釋。

並且,這些特徵互斥,每次只有一個啟用。因此,資料會變成稀疏的。

這樣做的好處主要有:

  • 解決了分類器不好處理屬性資料的問題
  • 在一定程度上也起到了擴充特徵的作用

二、獨熱編碼

  獨熱編碼(是因為大部分演算法是基於向量空間中的度量來進行計算的,為了使非偏序關係的變數取值不具有偏序性,並且到原點是等距的。使用one-hot編碼,將離散特徵的取值擴充套件到了歐式空間,離散特徵的某個取值就對應歐式空間的某個點。將離散型特徵使用one-hot編碼,會讓特徵之間的距離計算更加合理。離散特徵進行one-hot編碼後,編碼後的特徵,其實每一維度的特徵都可以看做是連續的特徵。就可以跟對連續型特徵的歸一化方法一樣,對每一維特徵進行歸一化。比如歸一化到[-1,1]或歸一化到均值為0,方差為1。

  將離散特徵通過one-hot編碼對映到歐式空間,是因為,在迴歸,分類,聚類等機器學習演算法中,特徵之間距離的計算或相似度的計算是非常重要的,而我們常用的距離或相似度的計算都是在歐式空間的相似度計算,計算餘弦相似性,基於的就是歐式空間。

  • 優點:獨熱編碼解決了分類器不好處理屬性資料的問題,在一定程度上也起到了擴充特徵的作用。它的值只有0和1,不同的型別儲存在垂直的空間。
  • 缺點:當類別的數量很多時,特徵空間會變得非常大。在這種情況下,一般可以用PCA來減少維度。而且one hot encoding+PCA這種組合在實際中也非常有用。
  • 適合用的場景:獨熱編碼用來解決類別型資料的離散值問題;
  • 不適用的場景:將離散型特徵進行one-hot編碼的作用,是為了讓距離計算更合理,但如果特徵是離散的,並且不用one-hot編碼就可以很合理的計算出距離,那麼就沒必要進行one-hot編碼。有些基於樹的演算法在處理變數時,並不是基於向量空間度量,數值只是個類別符號,即沒有偏序關係,所以不用進行獨熱編碼。 Tree Model不太需要one-hot編碼: 對於決策樹來說,one-hot的本質是增加樹的深度。
  • 需要歸一化:基於引數的模型或基於距離的模型,都是要進行特徵的歸一化;
  • 不用歸一化:基於樹的方法是不需要進行特徵的歸一化,例如隨機森林,bagging 和 boosting等。

三、獨熱編碼使用示例

  One-hot 編碼將表示為標籤索引的分類特徵對映到二進位制向量,該向量最多具有一個單值,表示所有特徵值集中存在特定特徵值。 這種編碼允許期望連續特徵的演算法(例如邏輯迴歸)使用分類特徵。 對於字串型別的輸入資料,通常首先使用 StringIndexer 對分類特徵進行編碼。

  OneHotEncoder 可以轉換多個列,為每個輸入列返回一個單熱編碼的輸出向量列。 通常使用 VectorAssembler 將這些向量合併為單個特徵向量。

  OneHotEncoder 支援 handleInvalid 引數來選擇在轉換資料時如何處理無效輸入。 可用選項包括“keep”(任何無效輸入都分配給額外的分類索引)和“error”(丟擲錯誤)。

%spark
// 特徵轉換 —— —— OneHotEncoder
// 一種單熱編碼器又稱一位有效編碼,其方法是使用N位狀態暫存器來對N個狀態進行編碼,每個狀態都有它獨立的暫存器位,並且在任意時候,其中只有一位有效。
// 可以這樣理解,對於每一個特徵,如果它有m個可能值,那麼經過獨熱編碼後,就變成了m個二元特徵。並且,這些特徵互斥,每次只有一個啟用。因此,資料會變成稀疏的。
// 作用:將離散的分類特徵轉換為數字表示的特徵
// 這樣做的好處:1、解決了分類器不好處理屬性資料的問題;2、在一定程度上也起到了擴充特徵的作用。

import org.apache.spark.ml.feature.OneHotEncoder

val df = spark.createDataFrame(Seq(
  (0.0, 1.0),
  (1.0, 0.0),
  (2.0, 1.0),
  (0.0, 2.0),
  (0.0, 1.0),
  (2.0, 0.0)
)).toDF("categoryIndex1", "categoryIndex2")

val encoder = new OneHotEncoder()
  .setInputCols(Array("categoryIndex1", "categoryIndex2"))
  .setOutputCols(Array("categoryVec1", "categoryVec2"))
//   用於在 transform() 期間如何處理無效資料的引數。 選項是“keep”(無效資料顯示為額外的分類特徵)或“error”(丟擲錯誤)。 請注意,此引數僅在轉換期間使用; 在擬合過程中,無效資料將導致錯誤。 預設是error
//   .setHandleInvalid("keep")
//   是否刪除編碼向量中的最後一個類別(預設值:true) 
  .setDropLast(false)
val model = encoder.fit(df)

val encoded = model.transform(df)
encoded.show()

輸出:(稀疏矩陣)
+--------------+--------------+-------------+-------------+
|categoryIndex1|categoryIndex2| categoryVec1| categoryVec2|
+--------------+--------------+-------------+-------------+
|           0.0|           1.0|(3,[0],[1.0])|(3,[1],[1.0])|
|           1.0|           0.0|(3,[1],[1.0])|(3,[0],[1.0])|
|           2.0|           1.0|(3,[2],[1.0])|(3,[1],[1.0])|
|           0.0|           2.0|(3,[0],[1.0])|(3,[2],[1.0])|
|           0.0|           1.0|(3,[0],[1.0])|(3,[1],[1.0])|
|           2.0|           0.0|(3,[2],[1.0])|(3,[0],[1.0])|
+--------------+--------------+-------------+-------------+