特征处理实践

数值型特征

1、onehotencoder

现实情况下公司存在很多的类别特征,无论是地域类别、商品类别都是利用ID与类别映射的关系,如可能在地域类别特征中(1->’北京’,2->’上海’)。由于机器学习中的很多模型都是对数字敏感的,这些特征的显示意义只是单纯的作为类别标识,并不具有数学上的大小关系,而将上面描述的地域特征(1->’北京’,2->’上海’)模型可能会觉得‘上海’比‘北京’要大,这是我们不想看到的。onehotencoder是针对特征中存在的类别特征进行处理,使之转换为哑变量的一种特征处理方式。
举个例子:
针对类别特征【‘北京’,‘上海’,‘广州’】:
北京->(1,0,0)
上海->(0,1,0)
广州->(0,0,1)
三种类别对应的哑变量在同一相同列都只有一个位置为1。

#房价数据:地区1的房价均价为5W一平米,地区2的房价均价为1W一平米,地区3的房价均价为2W一平米,数据纯属捏造
#每列的字段含义:(区域标识,房屋面积,房价)
import numpy as np
data = np.array([
    [1,100,500],
    [1,80,420],
    [1,140,680],
    [2,100,100],
    [2,80,90],
    [2,140,130],
    [3,100,200],
    [3,80,170],
    [3,140,270]
])

#线性回归模型
from sklearn.linear_model import LinearRegression
reg = LinearRegression()

#没有进行onehotencoder的特征
X = data[:,:-1]#(区域标识,房屋面积)
y = data[:,-1]#(房价)
#训练
reg.fit(X,y)
#预测区域2,100平米的房屋价格
reg.predict([2,100])
#结果:array([ 269.52380952])
#MSE
mse = np.sqrt(np.sum(np.square(reg.predict(X)-y)))
#MSE:394.67889298951445

#进行onehotencoder特征处理
area = np.c_[data[:,0]]
#引入onehotencoder模型
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
#编码转换
areaEnc = encoder.fit_transform(area)
Xnew = np.c_[(areaEnc,data[:,1])]
reg.fit(Xnew,y)
#预测区域2,100平米的房屋价格
reg.predict([2,100])
#结果:array([ 91.74603175])
#MSE
mse = np.sqrt(np.sum(np.square(reg.predict(Xnew)-y)))
#MSE:116.40105819624816

onehotencoder的好处:
- 解决了分类器不好处理属性数据的问题
- 针对不同的类别训练了“不同的模型“。(我自己这么认为的,内部的训练更像是将不同类别的数据单拎出来训练出该类别的模型一样)

2、特征离散化

有些特征虽然是数值型的,但是该特征的取值相加相减是没有实际太大的意义,那么该数值型特征也要看成离散特征,采用离散化的技术。
例如:
很多电商系统的价格筛选条件一般都是一个区间段,分别对应相应的消费能力,高消费与低消费人群的消费的主要决定因素不同,假设同一场景下两款相似的商品的差价是2000元,对于低消费的人群来说可能更多的选择价格低的商品,而对于高消费人群差价没有低消费人群的选择决定性并不会那么高。

#捏造测试数据,这里假想年龄与体能的关系,data中对应的字段分别为[年龄,体力值]
import numpy as np
data = np.array(
[
    [1,1],
    [5,10],
    [10,30],
    [15,60],
    [20,90],
    [25,100],
    [30,95],
    [35,90],
    [40,90],
    [45,80],
    [50,75],
    [55,70],
    [60,65],
    [65,60],
    [70,55]
]
)

#绘制散点图
import matplotlib.pyplot as plt
plt.scatter(data[:,0],data[:,1],c='b',label='data')

#定义线性回归模型
from sklearn.linear_model import LinearRegression
reg = LinearRegression()

#对特征不作任何处理简单的拟合
X = np.c_[data[:,0]]
y = data[:,1]
reg.fit(X,y)
#绘制拟合曲线
xline = np.arange(1,70,1)
ypredict = reg.predict(np.c_[xline])
plt.plot(x,ypredict,color='y',label='feature without handle')

#对特征进行离散化
import pandas as pd
#将年龄离散化到不同的桶中
Xcut = pd.cut(data[:,0],bins=[0,10,20,30,40,50,60,70],labels=[1,2,3,4,5,6,7])
#onehotencoder
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
Xcutenc = encoder.fit_transform(np.c_[Xcut]).toarray()
reg.fit(Xcutenc,y)
xlinecut = pd.cut(xline,bins=[0,10,20,30,40,50,60,70],labels=[1,2,3,4,5,6,7])
xlinecutenc = encoder.transform(np.c_[xlinecut])
ycutpredict = reg.predict(xlinecutenc)
plt.plot(x,ypredict,color='r',label='feature with cut')

plt.legend()
plt.show()

离散化

从上图可以发现,通过特征离散化使得原本的年龄这个单一的特征表现出非线性的关系。
离散化的作用:

  • 发现特征的非线性关系
  • 转换特征使得模型更精确

3、特征融合

如果针对上面例子提到的(年龄,体力值)的例子不运用特征离散化怎么才能更好的拟合曲线呢?答案是特征融合。简单的来说特征融合是针对已有的特征进行特征组合,得到新的特征来进行模型的训练。

#这里仅仅有一个特征字段年龄,我们简单的做一下特征融合取年龄的平方和三次方作为新的特征进行训练
age = data[:,0]
#特征融合,这里特征比较简单,如果特征过多比较复杂可以直接引入#sklearn.processing.PolynomialFeatures进行处理
X = np.c_[(age,age**2,age**3)]
y = data[0:,1]

#定义线性回归模型
from sklearn.linear_model import LinearRegression
reg = LinearRegression()
#训练
reg.fit(X,y)

#绘制结果
plt.scatter(age,y,c='b',label='data')
xline = np.arange(1,70,1)
xlinefeature = np.c_[(xline,xline**2,xline**3)]
ypredict = reg.predict(xlinefeature)
plt.plot(xline,ypredict,color='r',label='feature fusion')

plt.legend()
plt.show()

特征融合

特征融合的作用:

  • 发现特征隐藏的多元线性关系,比如:圆的周长跟半径是线性的关系,圆的面积跟半径的平方才是线性关系
  • 丰富特征,可以从低维度,扩展到无限维

4、特征标准化

一句话概括就是将连续的特征值归一化到(0,1)或者(-1,1)的区间中。一般存在两种归一化的方式:

  • minmax normalization:最大最小标准化 max()min() ,该方法的取值区间是(0,1)
  • std normalization:该方法是将特征映射为期望是0,方差为1的分布中,该方法的取值区间为(-1,1)
# minmax normalization
from sklearn import preprocessing
data = [0,100,500,1000,5000]
minmax = preprocessing.MinMaxScaler()
minmax.fit_transform(data)
#结果:array([ 0.  ,  0.02,  0.1 ,  0.2 ,  1.  ])

#std normalization
std = preprocessing.StandardScaler()
std.fit_transform(data)
#结果:array([-0.70460403, -0.65122493, -0.43770856, -0.1708131 ,  1.96435062])

归一化的作用:

  • 将不同阈值空间的特征转换到统一的基准空间中,防止在模型训练中存在的迂回曲折,比如:梯度下降方法中如果两个特征不是同一个量级的话,收敛的速度会比较慢,而且经常发生“跃变”
  • 模型训练完成之后特征的系数是同一个量级的,这种情况的下的特征系数才有可比性,衡量每个特征的重要性

文本型特征

1、bag of words

“词袋”模型,是利用字典表向量化一份文本的方法。简单的说,所要向量化的文本按照字典表中字典的顺序统计文本中出现的个数。
举个例子:
字典表= {1:”Bob”, 2. “likes”, 3. “to”, 4. “play”, 5. “basketball”, 6. “also”, 7. “football”, 8. “games”, 9. “Jim”, 10. “too”}
文本1 = {Bob likes to play basketball, Jim likes too}
文本2 = {Bob also likes to play football games}
得到的向量分别为:
1:[1, 2, 1, 1, 1, 0, 0, 0, 1, 1]
2:[1, 1, 1, 1 ,0, 1, 1, 1, 0, 0]
每个向量的数字代表该索引处字典表中单词在文本中出现的次数,有点类似直方图的感觉。

#bag of words model
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()

#训练文本
corpus = [
    'This is the first document.',
    'This is the second second document.',
    'And the third one.',
    'Is this the first document?'
]

#训练和转换
X = vectorizer.fit_transform(corpus).toarray()
#结果array([[0, 1, 1, 1, 0, 0, 1, 0, 1],
#       [0, 1, 0, 1, 0, 2, 1, 0, 1],
#       [1, 0, 0, 0, 1, 0, 1, 1, 0],
#       [0, 1, 1, 1, 0, 0, 1, 0, 1]], dtype=int64)

#字典表
vectorizer.vocabulary_
#{'and': 0,
# 'document': 1,
# 'first': 2,
# 'is': 3,
# 'one': 4,
# 'second': 5,
# 'the': 6,
# 'third': 7,
# 'this': 8}

上面的方法是将单一的单词作为字典表中的单一元素,如果考虑进去单词短语呢?即如果是两个单词组成的短语也作为字典表中的元素,这种方法叫做N-gram,上面介绍的单一单词的长度仅为1的方法理论上叫做1-gram,现在我们考虑2-gram对上述例子进行转换:

#建立一个考虑单个单词和连续两个单词的bag of words model
bigram_vectorizer = CountVectorizer(ngram_range=(1,2))

#训练和转换
feature = bigram_vectorizer.fit_transform(corpus).toarray()
#结果array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0],
#       [0, 0, 1, 0, 0, 1, 1, 0, 0, 2, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0],
#       [1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],
#       [0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1]], dtype=int64)

#词典
bigram_vectorizer.vocabulary_
#{'and': 0,
# 'and the': 1,
# 'document': 2,
# 'first': 3,
# 'first document': 4,
# 'is': 5,
# 'is the': 6,
# 'is this': 7,
# 'one': 8,
# 'second': 9,
# 'second document': 10,
# 'second second': 11,
# 'the': 12,
# 'the first': 13,
# 'the second': 14,
# 'the third': 15,
# 'third': 16,
# 'third one': 17,
# 'this': 18,
# 'this is': 19,
# 'this the': 20}

2、TF-IDF

“词频-逆文档率”:

tfidf(t,d)=tf(t,d)idf(t)

idf(t)=lognd1+df(d,t)+1

其中t代表term单词;d代表document文档;tf(t,d)代表term frequnecy词频,即该单词在文档出现的次数;idf(t)代表该词的逆文档率由上面的公司计算; nd 代表文档的总数目;df(d,t)代表出现单词t的文档数目;

#针对bag of words中的每行我们假设是一个文档,则对输出的向量转换成tf-idf的形式
from sklearn.feature_extraction.text import TfidfTransformer
transformer = TfidfTransformer()

#这里对得到的tf-idf的值运用了l2标准化,缩放到了(0,1)的区间
#标准化的主要原因是因为文本之间篇幅差异比较大,比如1万字的文章出现某单词10次,和1千字的文章出现某单词5次,如果不加标准化前者的tf-idf大于后者,反之标准化之后后者大于前者
transformer.fit_transform(feature).toarray()
#array([[ 0.        ,  0.        ,  0.29752161,  0.36749838,  0.36749838,
#         0.29752161,  0.36749838,  0.        ,  0.        ,  0.        ,
#         0.        ,  0.        ,  0.24324341,  0.36749838,  0.        ,
#         0.        ,  0.        ,  0.        ,  0.29752161,  0.36749838,
#         0.        ],
#       [ 0.        ,  0.        ,  0.20454415,  0.        ,  0.        ,
#         0.20454415,  0.25265271,  0.        ,  0.        ,  0.64091586,
#         0.32045793,  0.32045793,  0.16722824,  0.        ,  0.32045793,
#         0.        ,  0.        ,  0.        ,  0.20454415,  0.25265271,
#         0.        ],
#       [ 0.39928771,  0.39928771,  0.        ,  0.        ,  0.        ,
#         0.        ,  0.        ,  0.        ,  0.39928771,  0.        ,
#         0.        ,  0.        ,  0.20836489,  0.        ,  0.        ,
#         0.39928771,  0.39928771,  0.39928771,  0.        ,  0.        ,
#         0.        ],
#       [ 0.        ,  0.        ,  0.27571531,  0.34056326,  0.34056326,
#         0.27571531,  0.        ,  0.43196131,  0.        ,  0.        ,
#         0.        ,  0.        ,  0.22541533,  0.34056326,  0.        ,
#         0.        ,  0.        ,  0.        ,  0.27571531,  0.        ,
#         0.43196131]])

3、word embedding

“词嵌入”技术是指将一个单词映射到一个特定空间中从而得到该词在空间的向量。把每个单词变成一个向量,目的还是为了方便计算,比如“求单词A的同义词”,就可以通过“求与单词A在cos距离下最相似的向量”来做到。
简单的来说,我们希望达到如下效果:
“厕所”“卫生间”这两个单词的向量的求cos会是趋近到1的,即它们的cos距离趋近于0
“中国-北京≈日本-东京≈首都”单词在空间上加减也是具有一定含义的。
目前google开源的工具包word2vec具有一定的特性,它可以输入分词后的文本进行训练得到你指定维数的特征向量,从而使得你输入一个单词可以得到它的词向量。

其他

GBDT+LR

GBDT(Gradient Boost Decision Tree)是一种常用的非线性模型,它基于集成学习中的boosting思想,每次迭代都在减少残差的梯度方向新建立一颗决策树,迭代多少次就会生成多少颗决策树。GBDT的思想使其具有天然优势可以发现多种有区分性的特征以及特征组合,决策树的路径可以直接作为LR输入特征使用,省去了人工寻找特征、特征组合的步骤。这种通过GBDT生成LR特征的方式(GBDT+LR),业界已有实践(Facebook,Kaggle-2014),且效果不错,是非常值得尝试的思路。

from sklearn import datasets
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, auc
from sklearn import linear_model
from sklearn.ensemble import GradientBoostingClassifier
import numpy as np
#造一些样本点
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000)

#定义LR和GBDT模型
lr =linear_model.LogisticRegression()
gbdt = GradientBoostingClassifier(n_estimators=2)

#先求原始的LR对X,y的分类效果
lr.fit(X,y)
preds = lr.predict_proba(X)[:, 1]
fpr_lr,tpr_lr,threshold = roc_curve(y.ravel(),preds.ravel())
roc_lr = auc(fpr_lr,tpr_lr)

#再求GBDT对X,y的分类效果
gbdt.fit(X,y)
fpr_gbdt,tpr_gbdt,_=roc_curve(y,gbdt.predict_proba(X)[:,1])
roc_gbdt = auc(fpr_gbdt,tpr_gbdt)

#最后求GBDT+LR的分类效果
#GBDT得到的新特征
feature = gbdt.apply(X)
feature = feature.reshape(len(X),-1)
#组合新特征和已有特征
alienfeature = np.c_[feature,X]
lr.fit(alienfeature,y)
probs_gbdtlr = lr.predict_proba(alienfeature)[:,1]
fpr_gbdtlr,tpr_gbdtlr,_ = roc_curve(y,probs_gbdtlr)
roc_gbdtlr = auc(fpr_gbdtlr,tpr_gbdtlr)


#绘制效果图
plt.plot([0, 1], [0, 1], 'k--')
plt.plot(fpr_lr,tpr_lr,c='r',label='LR (area = %0.2f)' % roc_lr)
plt.plot(fpr_gbdt,tpr_gbdt,c='b',label='GBDT (area = %0.2f)' % roc_gbdt)
plt.plot(fpr_gbdtlr,tpr_gbdtlr,c='g',label='GBDT+LR (area = %0.2f)' % roc_gbdtlr)
plt.legend()
plt.show()

GBDT+LR

通过结果可以看出,加入GBDT得到的新特征能有效提高LR的准确率。当然也可以运用RF和DT来形成特征,sklearn中给的例子效果:GBDT+LR>GBDT>RF+LR>RF>DT+LR。所以,如果你们现在的模型还是基于LR的,不妨可以尝试。

PCA降维

PCA的原理就是将原来的样本数据投影到一个新的空间中,相当于我们在矩阵分析里面学习的将一组矩阵映射到另外的坐标系下。通过一个转换坐标,也可以理解成把一组坐标转换到另外一组坐标系下,但是在新的坐标系下,表示原来的原本不需要那么多的变量,只需要原来样本的最大的一个线性无关组的特征值对应的空间的坐标即可。
比如,原来的样本是30*1000000的维数,就是说我们有30个样本,每个样本有1000000个特征点,这个特征点太多了,我们需要对这些样本的特征点进行降维。那么在降维的时候会计算一个原来样本矩阵的协方差矩阵,这里就是1000000*1000000,当然,这个矩阵太大了,计算的时候有其他的方式进行处理,这里只是讲解基本的原理,然后通过这个1000000*1000000的协方差矩阵计算它的特征值和特征向量,最后获得具有最大特征值的特征向量构成转换矩阵。比如我们的前29个特征值已经能够占到所有特征值的99%以上,那么我们只需要提取前29个特征值对应的特征向量即可。这样就构成了一个1000000*29的转换矩阵,然后用原来的样本乘以这个转换矩阵,就可以得到原来的样本数据在新的特征空间的对应的坐标。30*1000000 * 1000000*29 = 30 *29, 这样原来的训练样本每个样本的特征值的个数就降到了29个。
如果在现实运用中,样本的特征数目过多影响模型的训练,可以考虑运用PCA降维来提升模型的训练速度,但是可能会损失一点模型的精度。

总结

文章是对最近学习特征处理的一点总结,写的都是皮毛,但是可以作为切入点去扩展深入理解特征处理中常见的一些特征处理方法。特征处理是机器学习的重要一环,再牛逼的模型没有好的特征也是白搭,好的特征一般都是结合业务场景生成的,所以理解自己业务发现更好的特征对模型的提升比模型的迭代来的要快。文章多有错误之处,还请各位大神批评指正。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值