當前位置:
首頁 > 最新 > 用一篇文章來了解數據編碼

用一篇文章來了解數據編碼

【前言】在很多業務場景建模中,都會遇到大規模特徵,也就是ID特徵的一種形式。比如,電商平台有1億用戶,圍繞著用戶本身會有很多靜態和動態特徵,組合起來就看到所有用戶對應所有維度的結果(如0和1),這個也是一個大稀疏矩陣。同樣,對於商品也可以這樣處理,而這一切的特徵信息(離散和連續)在進入模型前,都可以預先進行數據編碼,比如One-Hot Encoding、Dummy Encoding與Effect Encoding等,通過這樣的編碼處理後,總體的特徵維度會變得特別大(高維建模的一種形式),間接性也會提升模型效果。因此,本文將會針對數據預處理中的數據編碼進行擴展與實踐,而針對稀疏矩陣處理、正則化處理、業務場景應用與優化,將不屬於本文的介紹範圍。

說明:此文首發於數據重構未來知識星球。為了便於大家理解後去實踐,這裡直接貼出原文(附原碼),同時核心代碼見於星球里!

一、關於數據編碼介紹

這裡會依次對上述提到的數據編碼(One-Hot Encoding、Dummy Encoding與Effect Encoding)形式進行介紹,具體如下所示:① One-Hot編碼即獨熱編碼,又稱一位有效編碼,其方法是使用N位狀態寄存器來對N個狀態進行編碼,每個狀態都有它獨立的寄存器位,並且在任意時候,其中只有一位有效。② Dummy編碼它與One-Hot編碼類似,但是相比之下會缺少一個狀態位,更簡潔一些,因為如果一個研究樣本存在4個狀態位,那隻需要知道其中3個就可以推測出最後一個狀態位的值了。③ Effect編碼從直觀上的結果來看,它與Dummy編碼有些類似,但是在變數賦值上存在差異性,相對來說,它在回歸係數的解釋方面會更有優勢。案例說明:在互聯網金融行業,借款用戶的基本信息中都會涉及學歷情況,如小學、初中、高中、本科及以上,共計4個狀態。在以往的模型中,對於這類離散特徵的數據,很多時候就直接標識(0、1、2、3),這種形式存在的不足先不過多討論,假如我們採用上述3種數據編碼形式進行處理,會分別得到以下結果。

[One-Hot]小學 -> [1,0,0,0]初中 -> [0,1,0,0]高中 -> [0,0,1,0]本科及以上 -> [0,0,0,1][Dummy]小學 -> [1,0,0]初中 -> [0,1,0]高中 -> [0,0,1]本科及以上 -> [0,0,0][Effect]小學 -> [1,0,0]初中 -> [0,1,0]高中 -> [0,0,1]本科及以上 -> [1,1,1]

通過上面的例子,基本上可以看出它們之間編碼的差異性,不過在模型實踐(R、Python、Spark)使用和推廣中,傾向性會選擇One-Hot編碼,雖然會存在信息冗餘,但畢竟特徵屬性的每個值都需要在建模中考慮,而且間接性也能擴展總維度。

二、關於使用One-Hot的合理性

對於它的優勢,想必通過上面的案例也能看出來,比如不需要數據歸一化處理,能夠處理離散型數據特徵(對於連續性數據也可以按業務需求劃分區間,看作離散特徵),擴充模型特徵總數。而對於這種編碼形式存在的合理性,可以結合歐式空間的距離計算進行理解。在大家所接觸的各類場景(回歸、聚類、分類等)的模型中,都會涉及相似度的計算,相應的計算公式也有很多,但不管採取哪種方法,最關鍵的都是樣本與樣本之間,點與點的距離分析。因此,就像One-Hot編碼就可以將特徵值映射到歐式空間,每種狀態位對應一個點,從非數值特徵量化的角度來看,並不強調屬性值之間會存在相似度的差異性,所以可以結合上面的案例,實際來看看。常規方式,對於小學、初中、高中、本科及以上,量化處理以後為0、1、2、3,結果發現不同學歷之間的距離不一致(3與1、0與1),這肯定不是模型輸入想看到的。但如果採取One-Hot編碼,通過歐式距離的計算,顯然可以看得出來每個學歷之間的距離都是sqrt(2),更為合理性。

三、如何實踐One-Hot編碼

其實上面的內容都是在給大家作個簡單歸納,可能有朋友以前都了解過,相對來說還是很好去理解的。唯獨的困難點,我認為有三個方面,具體如下:其一,結合大規模數據的特徵樣本去快速實現One-Hot編碼;其二,One-Hot編碼的業務應用場景實踐;其三,One-Hot編碼的特徵優化策略;本文主要會針對第一點進行介紹和實踐,其餘的兩點,會放在後續來介紹,畢竟我也需要親自結合業務實踐才好下決定!案例說明:

在互聯網金融行業,涉及資金端(理財)會經常做大量營銷活動,也會與很多渠道進行合作從而獲取新客戶,這其中就不可避免會涉及「羊毛黨(關於它的介紹,可以參考我以往的文章)」的監控防範,現在需要線下訓練一個二分類模型,從而初步去識別新用戶是否為「羊毛黨」。現階段有10個訓練樣本(便於本文實踐,數量就限制了),其中「羊毛用戶」與「非羊毛用戶」各佔50%的比例,結合業務調研,初步篩選5個特徵(手機號風險辨別、設備風險辨別、是否為代理IP、是否為高危地區、會員等級),最終的樣本數據如下所示:

IDphoneRiskdeviceRiskis_agentIPis_riskAreagradecategory1 低 低 否 否 1 02 低 中 否 否 0 03 中 中 是 是 2 04 低 低 否 否 3 05 低 高 否 是 4 06 中 低 是 否 0 17 高 低 否 否 1 18 中 低 是 是 0 19 低 中 否 否 2 110 中 中 是 否 1 1

註:以上數據均為模擬,共計10個訓練樣本,其中phoneRisk與deviceRisk取值為低、中、高;is_agentIP與is_riskArea取值均為是、否;grade的取值為0、1、2、3、4;【數據量化】在實踐的模型輸入中,都需要將上述樣本數據集進行量化,保證均為數值型,便於模型的計算,因此,量化後的數據集如下所示:

IDphoneRiskdeviceRiskis_agentIPis_riskAreagradecategory1 0 0 0 0 1 02 0 1 0 0 0 03 1 1 1 1 2 04 0 0 0 0 3 05 0 2 0 1 4 06 1 0 1 0 0 17 2 0 0 0 1 18 1 0 1 1 0 19 0 1 0 0 2 110 1 1 1 0 1 1

接下來,我們會分別採取Python、Spark與自定義方法的形式,去結合One-Hot編碼規則,將上述樣本數據進行預處理,同時也將會對比不同方法之間的差異性和優缺點!【利用Python來實現One-Hot編碼】這裡的核心,主要是利用sklearn庫中的OneHotEncoder方法去實現,具體代碼如下:

from sklearn.preprocessing import OneHotEncoderSample = [[0,0,0,0,1], [0,1,0,0,0], [1,1,1,1,2], [0,0,0,0,3], [0,2,0,1,4], [1,0,1,0,0], [2,0,0,0,1], [1,0,1,1,0], [0,1,0,0,2], [1,1,1,0,1]]Train_enc = OneHotEncoder()Train_enc.fit(Sample)

#每個維度的取數和偏移量print(Train_enc.n_values_)print(Train_enc.feature_indices_)

# 樣本的數據編碼結果

print(Train_enc.transform(Sample).toarray())

最終的結果輸出如下所示:

[3 3 2 2 5][ 0 3 6 8 10 15][[ 1. 0. 0. 1. 0. 0. 1. 0. 1. 0. 0. 1. 0. 0. 0.] [ 1. 0. 0. 0. 1. 0. 1. 0. 1. 0. 1. 0. 0. 0. 0.] [ 0. 1. 0. 0. 1. 0. 0. 1. 0. 1. 0. 0. 1. 0. 0.] [ 1. 0. 0. 1. 0. 0. 1. 0. 1. 0. 0. 0. 0. 1. 0.] [ 1. 0. 0. 0. 0. 1. 1. 0. 0. 1. 0. 0. 0. 0. 1.] [ 0. 1. 0. 1. 0. 0. 0. 1. 1. 0. 1. 0. 0. 0. 0.] [ 0. 0. 1. 1. 0. 0. 1. 0. 1. 0. 0. 1. 0. 0. 0.] [ 0. 1. 0. 1. 0. 0. 0. 1. 0. 1. 1. 0. 0. 0. 0.] [ 1. 0. 0. 0. 1. 0. 1. 0. 1. 0. 0. 0. 1. 0. 0.] [ 0. 1. 0. 0. 1. 0. 0. 1. 1. 0. 0. 1. 0. 0. 0.]]

以上就是利用Python去實現數據編碼的過程,看起來還是挺容易,不過在我看來,有幾點值得擔憂,具體如下:① 人工成本參與,這裡所涉及的Sample其實只是樣本數據中的特徵集,不涉及ID和category兩個欄位,需要單獨維護。當然,這並不算缺點,畢竟你可以利用DataFrame讀取文本數據,抽取特徵集數據轉換成Array,再進行數據編碼;② 使用場景限制,如果訓練樣本足夠大,再或者未來投入於真實業務場景中,如果利用DataFrame一次性讀取樣本數據存放於內存中,想必是不現實的;③ 計算效率限制,通過代碼可以看出來,數據編碼前期有一個訓練過程fit,後期會將訓練結果使用於其他數據(包括樣本本身)中,講真的,我覺得過於繁瑣,而且增加了工作量;所以,如果只是日常業務需求的分析建模,應用於事後數據挖掘和事前分析預測,不涉及線上用戶的自動識別和業務決策,倒是可以利用Python去滿足日常需求。

【利用Spark來實現One-Hot編碼】以前的文章就提到過,Spark目前支持4種語言,分別為R、Python、Java與Scala(原生底層語言),為了吸引更多數據科學熱愛者投身於Spark的使用中,自然而然也會在Spark的ml和mllib包中開發相應的數據演算法,包括本文提到的One-Hot編碼。但是畢竟Spark不完全只服務於數據挖掘類的應用,所以有些方法使用起來相對很麻煩,個性化不夠高,就比如One-Hot編碼而言,官方只提供了單一特徵的數據編碼樣式,對於多特徵的還需要另外開發,具體如下所示:

object OneHotEncoder {//請查看知識星球中}

最終針對單特徵的數據編碼(因為category有3種取值,所以對應3個狀態位)結果如下所示:

1.0,0.0,0.00.0,1.0,0.00.0,0.0,0.01.0,0.0,0.0

以上就是利用Spark官方提供的案例去實現單一特徵的數據編碼,但也只是了解而已,如果想要應用於實際模型中,還是需要二次開發,接下來會針對性介紹如何去給多特徵進行數據編碼。

(phoneRiskdeviceRiskis_agentIPis_riskAreagradecategory)0,0,0,0,1,0...1,1,1,0,1,1

以上就是之前的樣本數據(欄位分割形式可多樣,這裡按逗號進行分割),存放於本地目錄中,以onehot.txt命名。在實踐開發過程前,我們需要先介紹一個知識點,也就是RDD -> DataFrame,一方面是Spark2.X系列提倡使用後者,介面更靈活,另一方面,本文也會使用到。SparkSQL提供了兩種方式把RDD轉換為DataFrame。① 第一種通過反射(需知道schema,本文所採用);② 第二種通過提供的介面創建schema。註:對於第一種方法,Case classes 在 Scala 2.10 只支持最多22個特徵,需自定義介面突破這個限制。

object OneHotEncoderUpdate {//請查看評論中}最終的數據查詢結果如下所示:

+---------+----------+----------+-----------+-----+-----------------------------+|phoneRisk|deviceRisk|is_agentIP|is_riskArea|grade|category|+---------+----------+----------+-----------+-----+-----------------------------+| 0| 1| 0| 0| 0| 0|| 1| 0| 1| 0| 0| 1|| 1| 0| 1| 1| 0| 1|+---------+----------+----------+-----------+-----+-----------------------------+

以上就完成了數據從本地目錄(也可以HDFS)中讀取,生成RDD,並最終轉化為DataFrame來進行One-Hot數據編碼了(這裡貼出核心代碼)。

val colTips = Array("phoneRisk","deviceRisk","is_agentIP","is_riskArea","grade") val index_transformers:Array[org.apache.spark.ml.PipelineStage] = colTips.map(record =>{ new StringIndexer().setInputCol(record).setOutputCol(s"$_index") } ) val index_pipeline = new Pipeline().setStages(index_transformers) val index_model = index_pipeline.fit(value) val df_indexed = index_model.transform(value) val indexColumns = df_indexed.columns.filter(x => x contains "index") val one_hot_encoders:Array[org.apache.spark.ml.PipelineStage] = indexColumns.map(record => { new OneHotEncoder().setInputCol(record).setOutputCol(s"$_vec") } ) val pipeline = new Pipeline().setStages(index_transformers ++ one_hot_encoders) val model = pipeline.fit(value) val ouputResult = model.transform(value).select("category","phoneRisk_index_vec","deviceRisk_index_vec","is_agentIP_index_vec","is_riskArea_index_vec","grade_index_vec").map(record =>{ val value = new StringBuilder() val tips = Array("phoneRisk_index_vec","deviceRisk_index_vec","is_agentIP_index_vec","is_riskArea_index_vec","grade_index_vec") value.append(record.apply(0)).append(",") for(tip { value.append(data).append(",") } ) value.append(0.0).append(",") } value.toString().substring(0,value.length-1) } )

最終的數據編碼結果如下所示(可以對照Python的編碼結果,by:樂平汪二):

0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,0.0,0.0,0.00,1.0,0.0,0.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,0.00,0.0,1.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.00,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.00,1.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.01,0.0,1.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,0.01,0.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,0.0,0.0,0.01,0.0,1.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.01,1.0,0.0,0.0,0.0,1.0,0.0,1.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.01,0.0,1.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,1.0,0.0,0.0,0.0,0.0

同樣也能對樣本數據的特徵進行數據編碼,但是不管是Python,還是Spark,即使寫了很多代碼,但是簡單來說也僅僅是為了模型數據的預處理,並沒有過多作用。因此,為了得了這個結果,當數據量很大時,而且特徵數也在一定數量下,除了Spark的這種形式較為合適外,我們是否還有更輕鬆、更高效的方式去針對數據集進行預處理呢?【利用Scala自定義實現One-Hot編碼】相比繁瑣的代碼和處理邏輯,我更願意選擇個性化的方法去滿足樣本數據預處理的需求,而且只需要針對特徵屬性與屬性值進行單獨維護即可,具體如下所示(這裡舉一個簡單的案例):

def OneHot(inputValue:String,featureSet:Array[String]):String={//請查看知識星球中 }

就比如上面的OneHot方法,在確保數據質量可靠(數據清洗和過濾需要把關)的情況下,每一行樣本數據,通過字元分割獲取到相應的欄位值後,我只需要向OneHot方法傳入兩個值(欄位值,該特徵的屬性值數組),即可返回該欄位值的獨熱編碼,還是挺簡單的!

def main(args:Array[String]):Unit={ val value = "碩士" val sets = Array("小學","中學","高中","大學","碩士") println(OneHot(value,sets)) }具體的輸出結果為:0,0,0,0,1因此,如果正式使用於業務場景建模中,假設LR模型有15個特徵,這裡只需要單獨創建一個配置文件,負責維護15個特徵的屬性名+屬性值集,這樣的話就可以在數據預處理中,直接對樣本數據、線上數據進行One-Hot編碼。

四、本文總結

這篇文章開頭用了少量的篇幅介紹了數據編碼的知識點,畢竟結合數據就能夠很快理解這個編碼的結果形式,所以這不是難點。而接下來,在所謂的三個難點之中(參考第3小節),本文的核心是以工程的形式,針對特徵樣本去快速實現One-Hot編碼。這裡分別介紹了Python、Spark以及自定義的形式,各有優缺點,以及應用場景,因此大家在實際業務場景的建模中,需要針對性去評估。的確,本文太多代碼了,很多偏業務的朋友可能消化有些吃力,但是我認為這些代碼都還算好理解,畢竟我這周也是從調研、理解、實踐、開發、總結,這過程來的。而且這個知識點對於模型優化還是很重要,另外在後期內容中,我還會結合業務實踐,去與大家共同分享數據編碼接下來的兩個困難點!

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 樂平汪二 的精彩文章:

「樂平汪二的數據價值探索之路」

TAG:樂平汪二 |