[机器学习]xgboost处理类别特征

6,204 阅读7分钟

0/参考网址

blog.csdn.net/m0_37870649…

one hot encode  独热编码,可以把特征映射到多维度的空间,
方便计算距离和相似度。

0/前言

无论是XGBoost,还是其他的Boosting Tree,使用的决策树都是cart回归树,
这也就意味着该类提升树算法只接受连续型特征和离散特征中的数值型特征,不直接支持离散特征中的类别型特征(比如好, 较好, 交差, 差)。
默认情况下,xgboost会把类别型的特征当成数值型。

总结起来的结论,大至两条:
- 对于有序的类别型特征,比如 age 等,当成数值型变量处理可以的,用label encode
  对于无序的类别型特征,推荐one-hot。但是one-hot会增加内存开销以及训练时间开销(因为1列变成多列)
- 类别型变量在范围较小时(tqchen 给出的是[10,100]范围内)推荐使用。

xgboost是不支持category特征的,在训练模型之前,需要我们进行预处理,可以根据特征的具体形式来选择:
- 有序特征:label encoding,比如版本号
- 无序特征:one-hot encoding,比如城市

1/用Label encoding(序号编码)处理有序类别特征

Label encoding是用来处理有序的类别特征,不会额外的增加空间。
# 针对单列进行序号编码
# 使用的是sklearn扩展包中的工具
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']


# 针对多列,同时进行序号 label encoding编码
# 方案1
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from collections import defaultdict

d = defaultdict(LabelEncoder)

df = pd.DataFrame({undefined
    '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
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({undefined
    '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)

2/用one-hot encode(独热编码)处理无序类别特征

 在实际的机器学习的应用任务中,特征有时候并不总是有序的,有可能是一些分类值,
 如性别可分为“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]

<1>为什么能使用One-Hot Encoding?

(1)使用one-hot编码,将类别特征的取值扩展到了欧式空间,类别特征的某个取值就对应欧式空间的某个点。
   在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,也是基于的欧式空间。
   
(2)将类别型特征使用one-hot编码,可以会让特征之间的距离计算更加合理。
   比如,有一个离散型特征,代表工作类型,该类别型特征,共有三个取值。
   不使用one-hot编码,计算出来的特征的距离是不合理。
   那如果使用one-hot编码,显得更合理。

<2>独热编码优缺点

-   优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。
-   缺点:当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用[PCA(主成分分析)](https://www.biaodianfu.com/pca.html)来减少维度。而且One-Hot Encoding+PCA这种组合在实际中也非常有用。

<3>One-Hot Encoding的使用场景

-   独热编码用来解决类别型数据的离散值问题。将离散型特征进行one-hot编码的作用,是为了让距离计算更合理,但如果特征是离散的,并且不用one-hot编码就可以很合理的计算出距离,那么就没必要进行one-hot编码,比如,该离散特征共有1000个取值,我们分成两组,分别是400600,两个小组之间的距离有合适的定义,组内的距离也有合适的定义,那就没必要用one-hot 编码。
-   基于树的方法是不需要进行特征的归一化,例如随机森林,bagging 和 boosting等。对于决策树来说,one-hot的本质是增加树的深度,决策树是没有特征大小的概念的,只有特征处于他分布的哪一部分的概念。

<4>one-hot encode demo

# 第一种方式
# 基于sklearn 的one hot encode
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)


# 基于pandas的one hot encode
# 第二种方式
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中进行流程化地机器学习过程。