很多时候,在我们拿到的数据集里,特征不都是连续的值,而是由某些离散化取值的数据组成。例如,性别特征可以具有如下取值:["male", "female"],天气特征有如下取值:["rainy", "sunny", "snowy"...]。
1. 数字化编码LabelEncoder
这样的特征是无法直接被模型识别的,因此需要将这些特征转换为数学模型能动的语言,一个很容易想到的方法就是,对这些特征进行数字化,也就是编号,比如,["male", "female"]可以用[0, 1]表示,["rainy", "sunny", "snowy"...]可以用[0,1, 2, ...]表示。总之,对于一个有N个类别的特征,总是可以用[0, N-1]之间的连续整数进行编号。
# 用sklearn实现
from sklearn.preprocessing import LabelEncoder
Le = LabelEncoder() # 构造编码器
Le.fit(['sunny','rainy','snowy']) # 训练编码器
Le.classes_ # 等于array(['rainy', 'snowy', 'sunny'])
Le.transform(['sunny']) # 等于array([2])
2. 独热编码OneHotEncoder
上述的编码方式,归根结底,只是把离散型数据换了个表达方式,本质上还是不连续的。而且如果直接用的话,容易被当成是连续的数值型数据进行处理,造成错误,除非编码后得到的数据连续性是有实际意义的。
对此,我们可以选择用独热编码来解决。通俗地讲,对于一个有N个类别的特征,总是可以构造N个新特征来唯一表示,比如,对于性别特征可以构造两个新特征,两个新特征的取值分别为:"male"→[1, 0],"female"→[0, 1]。总之,这N个新特征,在任何情况下,均只有1个特征取值为1,其他特征取值为0,从而保证了唯一性。
# 用sklearn实现
from sklearn.preprocessing import OneHotEncoder
Ohe = OneHotEncoder() # 构造编码器
Ohe.fit([[1],[2],[3],[1],[3]]) # 训练编码器,输入是LabelEncoder得到的整数型数据
Ohe.active_features_ # 等于array([1, 2, 3])
Ohe.transform([[2]]).toarray() # 等于array([[ 0., 1., 0.]])
3. 为什么要用独热编码
可能你会感觉奇怪,特征取值为[0, 1],还是离散的呀,何以解决数据的离散问题?那么我们要回到本质上来看,为什么我们不喜欢离散型数据?归根结底,是因为没办法对此类特征进行任何计算。(那你算帮我算算“李雷”和“韩梅梅”的距离有多远?)
而我们将用OneHotEncoder编码后的特征当成是向量,相当于将特征扩展到了欧式空间,那么某个取值就对应着空间里的某个点。在机器学习算法中,特征之间往往要进行相似性的计算,或者距离的计算,这些计算都是在欧式空间里完成的。实际上,大部分机器学习算法是基于向量空间中的度量来进行计算的。所以,OneHotEncoder编码满足了我们对于计算的要求。(为了通俗一点就这么解释啦)
4. 独热编码的优缺点
- 优点
解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。 - 缺点
当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用PCA来减少维度。而且OneHotEncoder + PCA这种组合在实际中也非常有用。
5. 注意事项
上面也讲到了,OneHotEncoder编码的目的是扩展到欧式空间,便于计算距离。但是对于某些算法,不需要计算特征的距离,特征是离散的也OK,那么我们就没必要多此一举。比如决策树算法,以及以决策树为基学习器的各类算法。