類目特徵使用

不管是XGBoost仍是其餘的Boosting Tree,使用的Tree都是cart迴歸樹,這也就意味着該類提高樹算法只接受數值特徵輸入,不直接支持類別特徵,默認狀況下,xgboost會把類別型的特徵當成數值型。事實上,對於類別特徵的處理,參考XGBoost PPT以下:node

xgboost 樹模型實際上是不建議使用one-hot編碼,在xgboost上面的 issue 也提到過,相關的說明以下python

I do not know what you mean by vector. xgboost treat every input feature as numerical, with support for missing values and sparsity. The decision is at the user
So if you want ordered variables, you can transform the variables into numerical levels(say age). Or if you prefer treat it as categorical variable, do one hot encoding.

在另外一個issues上也提到過(tqchen commented on 8 May 2015):算法

One-hot encoding could be helpful when the number of categories are small( in level of 10 to 100). In such case one-hot encoding can discover interesting interactions like (gender=male) AND (job = teacher).
While ordering them makes it harder to be discovered(need two split on job). However, indeed there is not a unified way handling categorical features in trees, and usually what tree was really good at was ordered continuous features anyway..

總結起來的結論,大至兩條:編程

  • 對於類別有序的類別型變量,好比 age 等,當成數值型變量處理能夠的。對於非類別有序的類別型變量,推薦 one-hot。可是 one-hot 會增長內存開銷以及訓練時間開銷。
  • 類別型變量在範圍較小時(tqchen 給出的是[10,100]範圍內)推薦使用

Label encoding與 One-Hot encoding

xgboost是不支持category特徵的,在訓練模型以前,須要咱們進行預處理,能夠根據特徵的具體形式來選擇:數組

  • 無序特徵:one-hot encoding,好比城市
  • 有序特徵:label encoding,好比版本號

Label encoding

Label encoding是使用字典的方式,將每一個類別標籤與不斷增長的整數相關聯,即生成一個名爲class_的實例數組的索引。瀏覽器

from sklearn.preprocessing import LabelEncoder
 
le = LabelEncoder()
 
city_list = ["paris", "paris", "tokyo", "amsterdam"]
 
le.fit(city_list)
print(le.classes_)  # 輸出爲:['amsterdam' 'paris' 'tokyo']
 
city_list_le = le.transform(city_list)  # 進行Encode
print(city_list_le)  # 輸出爲:[1 1 2 0]
 
city_list_new = le.inverse_transform(city_list_le)  # 進行decode
print(city_list_new) # 輸出爲:['paris' 'paris' 'tokyo' 'amsterdam']

若是是多列數據如何進行方便的編碼。網絡

方案一:session

from sklearn.preprocessing import LabelEncoder
from collections import defaultdict
import pandas as pd
 
d = defaultdict(LabelEncoder)
 
df = pd.DataFrame({
    'pets': ['cat', 'dog', 'cat', 'monkey', 'dog', 'dog'],
    'owner': ['Champ', 'Ron', 'Brick', 'Champ', 'Veronica', 'Ron'],
    'location': ['San_Diego', 'New_York', 'New_York', 'San_Diego', 'San_Diego',
                 'New_York']
})
 
 
# Encoding the variable
fit = df.apply(lambda x: d[x.name].fit_transform(x))
 
# Inverse the encoded
fit.apply(lambda x: d[x.name].inverse_transform(x))
 
# Using the dictionary to label future data
df.apply(lambda x: d[x.name].transform(x))

方案2:app

import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.pipeline import Pipeline
 
# Create some toy data in a Pandas dataframe
fruit_data = pd.DataFrame({
    'fruit':  ['apple','orange','pear','orange'],
    'color':  ['red','orange','green','green'],
    'weight': [5,6,3,4]
})
 
class MultiColumnLabelEncoder:
    def __init__(self,columns = None):
        self.columns = columns # array of column names to encode
 
    def fit(self,X,y=None):
        return self # not relevant here
 
    def transform(self,X):
        '''
        Transforms columns of X specified in self.columns using
        LabelEncoder(). If no columns specified, transforms all
        columns in X.
        '''
        output = X.copy()
        if self.columns is not None:
            for col in self.columns:
                output[col] = LabelEncoder().fit_transform(output[col])
        else:
            for colname,col in output.iteritems():
                output[colname] = LabelEncoder().fit_transform(col)
        return output
 
    def fit_transform(self,X,y=None):
        return self.fit(X,y).transform(X)

參考連接: https://stackoverflow.com/questions/24458645/label-encoding-across-multiple-columns-in-scikit-learn框架

LabelBinarizer

這種方法很簡單,在許多狀況下效果很好,但他有一個缺點:全部的標籤都變成了數字,而後算法模型直接將根據其距離來考慮類似的數字,而不考慮標籤的具體含義。所以,一般優選獨熱編碼(one-hot encoding)將數據二進制化。

from sklearn.preprocessing import LabelBinarizer
 
lb = LabelBinarizer()
 
city_list = ["paris", "paris", "tokyo", "amsterdam"]
 
lb.fit(city_list)
print(lb.classes_)  # 輸出爲:['amsterdam' 'paris' 'tokyo']
 
city_list_le = lb.transform(city_list)  # 進行Encode
print(city_list_le)  # 輸出爲:
# [[0 1 0]
#  [0 1 0]
#  [0 0 1]
#  [1 0 0]]
 
city_list_new = lb.inverse_transform(city_list_le)  # 進行decode
print(city_list_new)  # 輸出爲:['paris' 'paris' 'tokyo' 'amsterdam']

DictVectorizer

當類別的特徵被構形成相似於字典的列表時,列表中的值僅僅須要時幾個特徵的值而不須要很密集,此時可採用另外一種分類方法。DictVectorizer能夠用於將各列使用標準的Python dict對象表示的特徵數組,轉換成sklearn中的estimators可用的NumPy/SciPy表示的對象。Python的dict的優勢是,很方便使用,稀疏,能夠存儲feature名和值。DictVectorizer實現了一個稱爲one-of-K或者」one-hot」編碼的類別特徵。類別特徵是「屬性-值」對,它的值嚴格對應於一列無序的離散機率(好比:topic id, 對象類型,tags, names…)

下例中,」city」是類別的屬性,而」temperature」是一個傳統的數值型feature:

from sklearn.feature_extraction import DictVectorizer
 
measurements = [
    {'city': 'Dubai', 'temperature': 33.},
    {'city': 'London', 'temperature': 12.},
    {'city': 'San Fransisco', 'temperature': 18.},
]
 
vec = DictVectorizer()
measurements_vec = vec.fit_transform(measurements)
print(measurements_vec)
  # 輸出內容:
  # (0, 0)	1.0
  # (0, 3)	33.0
  # (1, 1)	1.0
  # (1, 3)	12.0
  # (2, 2)	1.0
  # (2, 3)	18.0
print(measurements_vec.toarray())
# 輸出內容
# [[ 1.  0.  0. 33.]
#  [ 0.  1.  0. 12.]
#  [ 0.  0.  1. 18.]]
 
feature_names = vec.get_feature_names()
print(feature_names)  # 輸出:['city=Dubai', 'city=London', 'city=San Fransisco', 'temperature']

FeatureHasher

通常的vectorizer是爲訓練過程當中遇到的特徵構建一個hash table,而FeatureHasher類則直接對特徵應用一個hash函數來決定特徵在樣本矩陣中的列索引。這樣的作法使得計算速度提高而且節省了內存,the hasher沒法記住輸入特徵的樣子,並且不遜在你想變換操做:inverse_transform。

由於哈希函數可能會致使原本不相關的特徵之間發生衝突,因此使用了有符號的hash函數。對一個特徵,其hash值的符號決定了被存儲到輸出矩陣中的值的符號。經過這種方式就可以消除特徵hash映射時發生的衝突而不是累計衝突。並且任意輸出的值的指望均值是0。sklearn中的FeatureHasher使用了MurmurHash 3做爲其Hash算法。

FeatureHasher的輸出一般是CSR格式的scipy.sparse matrix。Feature hashing 可被用於文檔分類中去,可是與text.CountVectorizer不一樣,FeatureHasher不作單詞切分或其餘的預處理操做,除了Unicode-to-UTF-8編碼之外。

one-hot encoding

什麼是one-hot encoding

在實際的機器學習的應用任務中,特徵有時候並不老是連續值,有多是一些分類值,如性別可分爲「male」和「female」。在機器學習任務中,對於這樣的特徵,一般咱們須要對其進行特徵數字化,好比有以下三個特徵屬性:

  • 性別:[「male」,」female」]
  • 地區:[「Europe」,」US」,」Asia」]
  • 瀏覽器:[「Firefox」,」Chrome」,」Safari」,」Internet Explorer」]

對於某一個樣本,如[「male」,」US」,」Internet Explorer」],咱們須要將這個分類值的特徵數字化,最直接的方法,咱們能夠採用序列化的方式:[0,1,3]。可是,即便轉化爲數字表示後,上述數據也不能直接用在咱們的分類器中。由於,分類器每每默認數據是連續的,而且是有序的。按照上述的表示,數字並非有序的,而是隨機分配的。這樣的特徵處理並不能直接放入機器學習算法中。

爲了解決上述問題,其中一種可能的解決方法是採用獨熱編碼(One-Hot Encoding)。獨熱編碼,又稱爲一位有效編碼。其方法是使用N位狀態寄存器來對N個狀態進行編碼,每一個狀態都由他獨立的寄存器位,而且在任意時候,其中只有一位有效。能夠這樣理解,對於每個特徵,若是它有m個可能值,那麼通過獨熱編碼後,就變成了m個二元特徵。而且,這些特徵互斥,每次只有一個激活。所以,數據會變成稀疏的。

對於上述的問題,性別的屬性是二維的,同理,地區是三維的,瀏覽器則是四維的,這樣,咱們能夠採用One-Hot編碼的方式對上述的樣本「[「male」,」US」,」Internet Explorer」]」編碼,「male」則對應着[1,0],同理「US」對應着[0,1,0],「Internet Explorer」對應着[0,0,0,1]。則完整的特徵數字化的結果爲:[1,0,0,1,0,0,0,0,1]。

爲何能使用One-Hot Encoding?

  • 使用one-hot編碼,將離散特徵的取值擴展到了歐式空間,離散特徵的某個取值就對應歐式空間的某個點。在迴歸,分類,聚類等機器學習算法中,特徵之間距離的計算或類似度的計算是很是重要的,而咱們經常使用的距離或類似度的計算都是在歐式空間的類似度計算,計算餘弦類似性,也是基於的歐式空間。
  • 將離散型特徵使用one-hot編碼,能夠會讓特徵之間的距離計算更加合理。好比,有一個離散型特徵,表明工做類型,該離散型特徵,共有三個取值,不使用one-hot編碼,計算出來的特徵的距離是不合理。那若是使用one-hot編碼,顯得更合理。

獨熱編碼優缺點

  • 優勢:獨熱編碼解決了分類器很差處理屬性數據的問題,在必定程度上也起到了擴充特徵的做用。它的值只有0和1,不一樣的類型存儲在垂直的空間。
  • 缺點:當類別的數量不少時,特徵空間會變得很是大。在這種狀況下,通常能夠用PCA(主成分分析)來減小維度。並且One-Hot Encoding+PCA這種組合在實際中也很是有用。

One-Hot Encoding的使用場景

  • 獨熱編碼用來解決類別型數據的離散值問題。將離散型特徵進行one-hot編碼的做用,是爲了讓距離計算更合理,但若是特徵是離散的,而且不用one-hot編碼就能夠很合理的計算出距離,那麼就不必進行one-hot編碼,好比,該離散特徵共有1000個取值,咱們分紅兩組,分別是400和600,兩個小組之間的距離有合適的定義,組內的距離也有合適的定義,那就不必用one-hot 編碼。
  • 基於樹的方法是不須要進行特徵的歸一化,例如隨機森林,bagging 和 boosting等。對於決策樹來講,one-hot的本質是增長樹的深度,決策樹是沒有特徵大小的概念的,只有特徵處於他分佈的哪一部分的概念。

One-Hot使用示例

一、基於sklearn 的one hot encoding:

import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
 
df = pd.DataFrame([
    ['green', 'Chevrolet', 2017],
    ['blue', 'BMW', 2015],
    ['yellow', 'Lexus', 2018],
])
df.columns = ['color', 'make', 'year']
 
le_color = LabelEncoder()
le_make = LabelEncoder()
df['color_encoded'] = le_color.fit_transform(df.color)
df['make_encoded'] = le_make.fit_transform(df.make)
 
color_ohe = OneHotEncoder()
make_ohe = OneHotEncoder()
X = color_ohe.fit_transform(df.color_encoded.values.reshape(-1, 1)).toarray()
Xm = make_ohe.fit_transform(df.make_encoded.values.reshape(-1, 1)).toarray()
 
dfOneHot = pd.DataFrame(X, columns=["Color_" + str(int(i)) for i in range(X.shape[1])])
df = pd.concat([df, dfOneHot], axis=1)
 
dfOneHot = pd.DataFrame(Xm, columns=["Make" + str(int(i)) for i in range(X.shape[1])])
df = pd.concat([df, dfOneHot], axis=1)

參考連接: http://www.insightsbot.com/blog/McTKK/python-one-hot-encoding-with-scikit-learn

二、基於pandas的one hot encoding:

其實若是咱們跳出 scikit-learn, 在 pandas 中能夠很好地解決這個問題,用 pandas 自帶的get_dummies函數便可

import pandas as pd
 
df = pd.DataFrame([
    ['green', 'Chevrolet', 2017],
    ['blue', 'BMW', 2015],
    ['yellow', 'Lexus', 2018],
])
df.columns = ['color', 'make', 'year']
 
 
df_processed = pd.get_dummies(df, prefix_sep="_", columns=df.columns[:-1])
print(df_processed)

get_dummies的優點在於:

  • 自己就是 pandas 的模塊,因此對 DataFrame 類型兼容很好
  • 無論你列是數值型仍是字符串型,均可以進行二值化編碼
  • 可以根據指令,自動生成二值化編碼後的變量名

get_dummies雖然有這麼多優勢,但畢竟不是 sklearn 裏的transformer類型,因此獲得的結果得手動輸入到 sklearn 裏的相應模塊,也沒法像 sklearn 的transformer同樣能夠輸入到pipeline中進行流程化地機器學習過程。

參考連接: https://blog.cambridgespark.com/robust-one-hot-encoding-in-python-3e29bfcec77e

利用神經網絡的Embedding層處理類別特徵

Embedding簡介

Embedding的起源和火爆都是在NLP中的,經典的word2vec都是在作word embedding這件事情,而真正首先在結構數據探索embedding的是在kaggle上的《Rossmann Store Sales》中的rank 3的解決方案,做者在比賽完後爲此方法整理一篇論文放在了arXiv,文章名:《 Entity Embeddings of Categorical Variables 》。

Embedding也被稱爲嵌套,是將大型稀疏矢量映射到一個保留語義關係的低維空間。在此模塊的隨後幾個部分中,咱們將從直觀角度、概念角度和編程角度來詳細探討嵌套。

要解決稀疏輸入數據的核心問題,您能夠將高維度數據映射到低維度空間。即使是小型多維空間,也能自由地將語義上類似的項歸到一塊兒,並將相異項分開。矢量空間中的位置(距離和方向)可對良好的嵌套中的語義進行編碼。例如,下面的真實嵌套可視化圖所展現的幾何關係圖捕獲了國家與其首都之間的語義關係。

嵌套充當查詢表

嵌套是一個矩陣,每列表示您詞彙中的一項所對應的矢量。要得到某個詞彙項的密集矢量,您能夠檢索該項所對應的列。可是,如何轉換字詞矢量的稀疏包呢?要得到表示多個詞彙項(例如,一句或一段中的全部字詞)的稀疏矢量的密集矢量,您能夠檢索各項的嵌套,而後將它們相加。若是稀疏矢量包含詞彙項的計數,則您能夠將每項嵌套與其對應項的計數相乘,而後再求和。這些運算可能看起來很眼熟吧。

嵌套查詢充當矩陣乘法

咱們剛剛闡述的查詢、乘法和加法程序等效於矩陣乘法。假設有一個的稀疏表示 S 和一個的嵌套表 E,矩陣乘法能夠得出密集矢量。這個概念用神經網絡圖來表示以下:

但首要問題是,如何獲取 E 呢?咱們將在下一部分介紹如何獲取嵌套。

Network Embedding,即將網絡節點、community投影到低維向量空間,用於node classification、link prediction、community detection、visualization等任務。

核心假設:節點間距離越近,embedding向量越接近,定義LOSS爲:

Network Embedding算法分類

基於矩陣特徵向量計算(譜聚類)

目標是將類似性高的兩個節點,映射到低維空間後依然保持距離相近,其損失函數定義爲:

基於random walk框架計算(Deep Walk & Node2Vec)

基於Deep Learning框架計算 SDNE(Structural Deep Network Embeddings)

主要思想:將節點的類似性向量直接做爲模型的輸入,經過 Auto-encoder 對這個向量進行降維壓縮,獲得其向量化後的結果 $Z_i#。其損失函數定義爲:

  

模型框架爲:

GCN(Graph Convolutional Networks)

主要思想:將節點自己及其鄰居節點的屬性(好比文本信息)或特徵(好比統計信息)編碼進向量中,引入了更多特徵信息,而且在鄰居節點間共享了一些特徵或參數,基於最終的目標(如節點分類)作總體優化。

模型框架示意和計算流程:

更多參考:

Embedding實戰

使用Keras進行Embedding

Keras對Tensorflow又進行了一層封裝,操做簡單,功能強大。

# 構造輸入數據
# 輸入數據是320*6,320個樣本,6個類別特徵,且類別特徵的可能值是0到36之間(37個)。
# 對這6個特徵作one-hot的話,應該爲37*6,
# embedding就是使1個特徵本來應該one-hot的37維變爲3維(手動設定,也能夠是其它),由於有36個類別特徵
# 這樣輸出的結果就應該是3*6
# 參考連接:https://keras.io/zh/layers/embeddings/
# 建議降維的維度爲 math.ceil(category_count ** 0.25)
 
import numpy as np
np.random.seed(42)
input_array = np.random.randint(37, size=(320, 6))
print(input_array)
 
 
import tensorflow as tf
from keras import backend as K
from keras.models import Sequential
from keras.layers.embeddings import Embedding
 
 
with tf.Session() as sess:
    K.set_session(sess)
    model = Sequential()
    model.add(Embedding(37, 3, input_length=6))
    model.compile('rmsprop', 'mse')
    output_array = model.predict(input_array)
    print(output_array)
    # weight = model.get_weights()
    # print(weight)

在上述的代碼中,咱們能夠看到6個類別特徵的值都在0到37,而且咱們沒有對模型進行訓練,而是直接就搭建了一個網絡,就輸出結果了。在真實的應用中,不是這樣。有2點須要改進:

  • 對每個類別特徵構建一個embedding層。對embedding層進行拼接。
  • 訓練網絡,獲得訓練後的embedding層的輸出做爲類別特徵one-hot的替換,這樣的embedding的輸出更精確

爲了解決上述的2個問題,咱們這裏仍是人工構建訓練集,咱們搭建的模型如圖:

從模型中,咱們能夠看到,這是符合現實世界的數據集的:即既有分類特徵,又有連續特徵。咱們先訓練一個網絡,embedding_3和embedding_4層的輸出結果就是用embedding處理類別特徵後的結果。

import numpy as np
import tensorflow as tf
from keras.models import Model
from keras.layers import Input, Dense, Concatenate, Reshape, Dropout
from keras.layers.embeddings import Embedding
from keras import backend as K
from keras.utils import plot_model
 
session_conf = tf.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)
sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
K.set_session(sess)
 
 
def build_embedding_network():
    """
    以網絡結構embeddding層在前,dense層在後。即訓練集的X必須以分類特徵在前,連續特徵在後。
    """
    inputs = []
    embeddings = []
 
    input_cate_feature_1 = Input(shape=(1,))
    embedding = Embedding(10, 3, input_length=1)(input_cate_feature_1)
    embedding = Reshape(target_shape=(3,))(embedding)  # embedding後是10*1*3,爲了後續計算方便,所以使用Reshape轉爲10*3
    inputs.append(input_cate_feature_1)
    embeddings.append(embedding)
 
    input_cate_feature_2 = Input(shape=(1,))
    embedding = Embedding(4, 2, input_length=1)(input_cate_feature_2)
    embedding = Reshape(target_shape=(2,))(embedding)
    inputs.append(input_cate_feature_2)
    embeddings.append(embedding)
 
    input_numeric = Input(shape=(1,))
    embedding_numeric = Dense(16)(input_numeric)
    inputs.append(input_numeric)
    embeddings.append(embedding_numeric)
 
    x = Concatenate()(embeddings)
    x = Dense(10, activation='relu')(x)
    x = Dropout(.15)(x)
    output = Dense(1, activation='sigmoid')(x)
 
    model = Model(inputs, output)
    model.compile(loss='binary_crossentropy', optimizer='adam')
    return model
 
 
"""
構造訓練數據
輸入數據是320*3,320個樣本,2個類別特徵,1個連續特徵。
對類別特徵作entity embedding,第一個類別特徵10個,第二個類別特徵4個。對這2個特徵作one-hot的話,應該爲10+4,
對第一個類別特徵作embedding使其爲3維,對第二個類別特徵作embedding使其爲2維。3對連續特徵不作處理。這樣理想輸出的結果就應該是3+2+1。
維和2維的設定是根據實驗效果和交叉驗證設定。
"""
sample_num = 320  # 樣本數爲32
cate_feature_num = 2  # 類別特徵爲2
contious_feature_num = 1  # 連續特徵爲1
 
rng = np.random.RandomState(0)  # 保證了訓練集的復現
cate_feature_1 = rng.randint(10, size=(sample_num, 1))
cate_feature_2 = rng.randint(4, size=(sample_num, 1))
contious_feature = rng.rand(sample_num, 1)
 
X = [cate_feature_1, cate_feature_2, contious_feature]
Y = np.random.randint(2, size=(sample_num, 1))  # 二分類
 
cate_embedding_dimension = {'0': 3, '1': 2}  # 記錄類別特徵embedding後的維度。key爲類別特徵索引,value爲embedding後的維度
 
"""
訓練和預測
"""
NN = build_embedding_network()
plot_model(NN, to_file='NN.png')  # 畫出模型,須要GraphViz包。另外須要安裝 pip install pydot
 
NN.fit(X, Y, epochs=3, batch_size=4, verbose=0)
y_preds = NN.predict(X)[:, 0]
 
"""
讀embedding層的輸出結果
"""
model = NN  # 建立原始模型
for i in range(cate_feature_num):
    layer_name = NN.get_config()['layers'][cate_feature_num + i]['name']  # cate_feature_num+i就是全部embedding層
    intermediate_layer_model = Model(inputs=NN.input, outputs=model.get_layer(layer_name).output)
    intermediate_output = intermediate_layer_model.predict(X)
    intermediate_output.resize([sample_num, cate_embedding_dimension[str(i)]])
    if i == 0:
        X_embedding_trans = intermediate_output
    else:
        X_embedding_trans = np.hstack((X_embedding_trans, intermediate_output))  # 水平拼接
 
for i in range(contious_feature_num):
    if i == 0:
        X_contious = X[cate_feature_num + i]
    else:
        X_contious = np.hstack((X_contious, X[cate_feature_num + i]))
 
X_trans = np.hstack((X_embedding_trans, X_contious))  # 在類別特徵作embedding後的基礎上,拼接連續特徵,造成最終矩陣,也就是其它學習器的輸入。
 
print(X_trans[:5])  # 其中,類別特徵維度爲5(前5個),連續特徵維度爲1(最後1個)
 
weight = NN.trainable_weights[0].eval(session=sess) # embedding_1層的參數。
print(weight[:5])

參考連接: https://blog.csdn.net/h4565445654/article/details/78998444

相關文章
相關標籤/搜索