转自:https://www.cnblogs.com/pinard/p/9061549.html

特征选择中,讲到了特征选择的一些要点。本篇继续讨论特征工程,重点关注于特征表达部分,即如果对某一个特征的具体表现形式做处理。主要包括缺失值处理,特殊的特征处理比如时间和地理位置处理,离散特征的连续化和离散化处理,连续特征的离散化处理几个方面。

1. 缺失值处理

          特征有缺失值是非常常见的,大部分机器学习模型在拟合前需要所有的特征都有值,不能是空或者NULL。那么如果有缺失值我们需要怎么处理呢?

     首先我们会看是该特征是连续值还是离散值。如果是连续值,那么一般有两种选择,一是选择所有有该特征值的样本,然后取平均值,来填充缺失值,另一种是取中位数来填充缺失值。如果是离散值,则一般会选择所有有该特征值的样本中最频繁出现的类别值,来填充缺失值。在sklearn中,可以使用preprocessing.Imputer来选择这三种不同的处理逻辑做预处理。

2. 特殊的特征处理

   有些特征的默认取值比较特殊,一般需要做了处理后才能用于算法。比如日期时间,比如显示20180519,这样的值一般没办法直接使用。那么一般需要如何变换呢?

        对于时间原始特征,处理方法有很多,这里只举例几种有代表性的方法。 第一种是使用连续的时间差值法,即计算出所有样本的时间到某一个未来时间之间的数值差距,这样这个差距是UTC的时间差,从而将时间特征转化为连续值。第二种方法是根据时间所在的年,月,日,星期几,小时数,将一个时间特征转化为若干个离散特征,这种方法在分析具有明显时间趋势的问题比较好用。第三种是权重法,即根据时间的新旧得到一个权重值。比如对于商品,三个月前购买的设置一个较低的权重,最近三天购买的设置一个中等的权重,在三个月内但是三天前的设置一个较大的权重。当然,还有其他的设置权重的方法,这个要根据要解决的问题来灵活确定。

       对地理特征,比如“广州市天河区XX街道XX号”,这样的特征我们应该如何使用呢?处理成离散值和连续值都是可以的。如果是处理成离散值,则需要转化为多个离散特征,比如城市名特征,区县特征,街道特征等。但是如果我们需要判断用户分布区域,则一般处理成连续值会比较好,这时可以将地址处理成经度和纬度的连续特征。

3. 离散特征的连续化处理

    有很多机器学习算法只能处理连续值特征,不能处理离散值特征,比如线性回归,逻辑回归等。那么想使用逻辑回归,线性回归时这些值只能丢弃吗?当然不是。我们可以将离散特征连续化处理。

   最常见的离散特征连续化的处理方法是独热编码one-hot encoding。处理方法其实比较简单,比如某特征的取值是高,中和低,那么我们就可以创建三个取值为0或者1的特征,将高编码为1,0,0这样三个特征,中编码为0,1,0这样三个特征,低编码为0,0,1这样三个特征。也就是说,之前的一个特征被我们转化为了三个特征。sklearn的OneHotEncoder可以帮我们做这个处理。

   第二个方法是特征嵌入embedding。这个一般用于深度学习中。比如对于用户的ID这个特征,如果要使用独热编码,则维度会爆炸,如果使用特征嵌入就维度低很多了。对于每个要嵌入的特征,我们会有一个特征嵌入矩阵,这个矩阵的行很大,对应我们该特征的数目。比如用户ID,如果有100万个,那么嵌入的特征矩阵的行就是100万。但是列一般比较小,比如可以取20。这样每个用户ID就转化为了一个20维的特征向量。进而参与深度学习模型。在tensorflow中,我们可以先随机初始化一个特征嵌入矩阵,对于每个用户,可以用tf.nn.embedding_lookup找到该用户的特征嵌入向量。特征嵌入矩阵会在反向传播的迭代中优化。

   此外,在自然语言处理中,我们也可以用word2vec将词转化为词向量,进而可以进行一些连续值的后继处理。

4. 离散特征的离散化处理

   离散特征有时间也不能直接使用,需要先进行转化。比如最常见的,如果特征的取值是高,中和低,那么就算你需要的是离散值,也是没法直接使用的。

   对于原始的离散值特征,最常用的方法也是独热编码,方法在第三节已经讲到。

   第二种方法是虚拟编码dummy coding,它和独热编码类似,但是它的特点是,如果我们的特征有N个取值,它只需要N-1个新的0,1特征来代替,而独热编码会用N个新特征代替。比如一个特征的取值是高,中和低,那么我们只需要两位编码,比如只编码中和低,如果是1,0则是中,0,1则是低。0,0则是高了。目前虚拟编码使用的没有独热编码广,因此一般有需要的话还是使用独热编码比较好。

   此外,有时候我们可以对特征进行研究后做一个更好的处理。比如,我们研究商品的销量对应的特征。里面有一个原始特征是季节春夏秋冬。我们可以将其转化为淡季和旺季这样的二值特征,方便建模。当然有时候转化为三值特征或者四值特征也是可以的。

   对于分类问题的特征输出,我们一般需要用sklearn的LabelEncoder将其转化为0,1,2,...这样的类别标签值。

5. 连续特征的离散化处理

   对于连续特征,有时候我们也可以将其做离散化处理。这样特征变得高维稀疏,方便一些算法的处理。

   对常用的方法是根据阈值进行分组,比如我们根据连续值特征的分位数,将该特征分为高,中和低三个特征。将分位数从0-0.3的设置为高,0.3-0.7的设置为中,0.7-1的设置为高。

   当然还有高级一些的方法。比如使用GBDT。在LR+GBDT的经典模型中,就是使用GDBT来先将连续值转化为离散值。那么如何转化呢?比如我们用训练集的所有连续值和标签输出来训练GBDT,最后得到的GBDT模型有两颗决策树,第一颗决策树有三个叶子节点,第二颗决策树有4个叶子节点。如果某一个样本在第一颗决策树会落在第二个叶子节点,在第二颗决策树落在第4颗叶子节点,那么它的编码就是0,1,0,0,0,0,1,一共七个离散特征,其中会有两个取值为1的位置,分别对应每颗决策树中样本落点的位置。在sklearn中,我们可以用GradientBoostingClassifier的 apply方法很方便的得到样本离散化后的特征,然后使用独热编码即可。 

         具体的一个示例代码如下:

复制代码
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.preprocessing import OneHotEncoder
X, y = make_classification(n_samples=10)  
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5)
gbc = GradientBoostingClassifier(n_estimators=2)
one_hot = OneHotEncoder()
gbc.fit(X_train, y_train)
X_train_new = one_hot.fit_transform(gbc.apply(X_train)[:, :, 0])
print (X_train_new.todense())
复制代码

       输出是:

[[0. 1. 1. 0.]
 [1. 0. 0. 1.]
 [1. 0. 0. 1.]
 [1. 0. 0. 1.]
 [0. 1. 1. 0.]]

6. 小结

       本文总结了特征表达的一些具体方法, 但是特征表达的方法不止于上文中的方法,毕竟这是工程实践。