机器学习之特征工程

一、哑变量

关于哑变量,这篇博文写的很好,相关概念可以参阅:《机器学习总结之——Dummy Coding(哑变量)》
下面用一个小小的例子来演示一下哑变量的实现。

import pandas as pd
from IPython.display import display

#手工输入一个数据表
fruits = pd.DataFrame({'数值特征':[5, 6, 7, 8, 9],
                       '类型特征':['西瓜', '香蕉', '橘子', '苹果', '葡萄']})
#展示fruits数据表
print('fruits数据表为:')
display(fruits)

#转化数据表中的字符串为数值
fruits_dum = pd.get_dummies(fruits)
#展示转化后的数据表
print('\n转化后的数据表为:')
display(fruits_dum)

#让程序把数值也看作字符串
fruits['数值特征'] = fruits['数值特征'].astype(str)
#再用 get_dummies 转化字符串
fruits_dum1 = pd.get_dummies(fruits, columns=['数值特征'])
print('\n数值特征也转化后的数据表为:')
display(fruits_dum1)

执行结果如下所示:

fruits数据表为:
   数值特征 类型特征
0     5   西瓜
1     6   香蕉
2     7   橘子
3     8   苹果
4     9   葡萄

转化后的数据表为:
   数值特征  类型特征_橘子  类型特征_苹果  类型特征_葡萄  类型特征_西瓜  类型特征_香蕉
0     5        0        0        0        1        0
1     6        0        0        0        0        1
2     7        1        0        0        0        0
3     8        0        1        0        0        0
4     9        0        0        1        0        0

数值特征也转化后的数据表为:
  类型特征  数值特征_5  数值特征_6  数值特征_7  数值特征_8  数值特征_9
0   西瓜       1       0       0       0       0
1   香蕉       0       1       0       0       0
2   橘子       0       0       1       0       0
3   苹果       0       0       0       1       0
4   葡萄       0       0       0       0       1

1. 通过 get_dummmies 的转换,之前的类型全部变成了只有0和1的数值变量,是一个稀疏矩阵,但默认情况下,get_dummmies 不会对数值特征进行转换。
2. 如果希望对数值特征也进行转换,那么可以通过设置 column 参数来实现。

二、数据装箱

关于数据装箱,参考《机器学习(十六)特征工程之数据分箱》
采用KNN 和 MLP 两种算法分别对同一个数据集在装箱前后分别进行拟合,算法参数均采用默认值,即MLP有一个隐藏层,节点数为100,KNN 的n_neighbors =5。需要对预测的数据也进行相同的装箱操作,才能得到正确的结果。

注:

函数原型
normal(self, loc=0.0, scale=1.0, size=None)
作用:生成高斯分布的概率密度随机数
参数解释
loc:float
此概率分布的均值(对应着整个分布的中心centre)
scale:float
此概率分布的标准差(对应于分布的宽度,scale越大越矮胖,scale越小,越瘦高)
size:int or tuple of ints
输出的shape,默认为None,只输出一个值

#数据装箱,生成随机数,并分别用 MLP 和 KNN 算法对数据集进行回归分析
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neural_network import MLPRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import OneHotEncoder

#生成随机数列
rnd = np.random.RandomState(38)
x  = rnd.uniform(-5, 5, size=50)
#向数据中添加噪声
y_no_noise = (np.cos(6 * x) + x)
X = x.reshape(-1, 1)
y = (y_no_noise + rnd.normal(size=len(x)))/ 2

#生成一个等差数列
line = np.linspace(-5, 5, 1000, endpoint=False).reshape(-1, 1)

#分别用两种算法拟合数据

mlpr = MLPRegressor().fit(X, y)
knr = KNeighborsRegressor().fit(X, y)

#绘制图形
plt.plot(line, mlpr.predict(line), label='MLP')
plt.plot(line, knr.predict(line), label='KNN')
plt.plot(X, y, 'o', c='r')
plt.title("before binning")
plt.show()

#设置箱体数为11
bins = np.linspace(-1, 5, 11)
#将数据进行装箱操作
target_bin = np.digitize(X, bins=bins)
#打印装箱数据范围
print('装箱数据范围:\n{}'.format(bins))

onehot = OneHotEncoder(sparse=False)
onehot.fit(target_bin)

#使用独热编码转化数据
X_in_bin = onehot.transform(target_bin)

print('装箱后的数据形态:{}'.format(X_in_bin.shape))
print('\n装箱后的前十个数据点:\n{}'.format(X_in_bin[:10]))

#使用独热编码进行数据表达
new_line = onehot.transform(np.digitize(line, bins=bins))
#使用新的数据训练模型
new_mlpr = MLPRegressor().fit(X_in_bin, y)
new_knr = KNeighborsRegressor().fit(X_in_bin, y)

#绘制图形
plt.plot(line, new_mlpr.predict(new_line), label='MLP')
plt.plot(line, new_knr.predict(new_line), label='KNN')
plt.plot(X, y, 'o', c='r')
plt.legend(loc='best')
plt.title("after binning")
plt.show()

执行结果如下所示:

装箱数据范围:
[-1.  -0.4  0.2  0.8  1.4  2.   2.6  3.2  3.8  4.4  5. ]

前十个数据的特征值:
[[-1.1522688 ]
 [ 3.59707847]
 [ 4.44199636]
 [ 2.02824894]
 [ 1.33634097]
 [ 1.05961282]
 [-2.99873157]
 [-1.12612112]
 [-2.41016836]
 [-4.25392719]]

前十个数据所在的箱子:
[[ 0]
 [ 8]
 [10]
 [ 6]
 [ 4]
 [ 4]
 [ 0]
 [ 0]
 [ 0]
 [ 0]]
装箱后的数据形态:(50, 11)

装箱后的前十个数据点:
[[ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.]
 [ 0.  0.  0.  0.  0.  0.  1.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.]
 [ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]]
10.2before_binning.png

10.2after_binning.png

1. 在装箱之前,MLP 产生的回归线非常接近线模型的结果,而KNN则相对更复杂一些,它试图覆盖更多的点。
2. 装箱处理也称为离散化处理,在生成容器的时候指定范围和数据点生成的范围一致,生成11个元素的等差数列,这样每两个数值之间就生成了一个"箱子"(或者"容器")。
3. 使用独热编码 OneHotEncoder 来表达已经装箱的数据。OneHotEncoder 目前只能用于整型数值的变量类型。拓展一下子:几个比较绕的概念可以参考我的另一篇 :《label_binarize、LabelBinarizer和LabelEncoder》
装箱后样本数不变,但是特征数变成了10个,是因为生成的箱子是10个。
4. 经过数据装箱,两个模型变的更相近了,对比发现,MLP的回归模型变的更复杂,而KNN的回归模型变的更简单,所以对数据进行装箱的一个好处就是,它可以纠正模型过拟合或者欠拟合。尤其是针对大规模高纬度的数据集使用线性模型的时候,装箱处理可以大幅提高典型模型的预测准确率。
注意
这种对于样本进行装箱的操作对基于决策树的算法,包括随机森林等没有太大的作用,因为这类算法本身就是不停的在拆分样本的数据特征,所以不需要再使用装箱操作了。

三、数据“升维”

实际应用中,常遇到数据集的特征值不足的情况,常用两种方法:交互式特征和多项式特征。

交互式特征

就是在原始数据特征中添加交互项,使特征数增加。

例子使用numpy的hastack函数来实现,关于hastack的使用详解可以参考:《Python numpy函数hstack() vstack() stack() dstack() vsplit() concatenate()》

#数据升维
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neural_network import MLPRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import OneHotEncoder

#生成随机数列
rnd = np.random.RandomState(38)
x  = rnd.uniform(-5, 5, size=50)
#向数据中添加噪声
y_no_noise = (np.cos(6 * x) + x)
X = x.reshape(-1, 1)
y = (y_no_noise + rnd.normal(size=len(x)))/ 2

#生成一个等差数列
line = np.linspace(-5, 5, 1000, endpoint=False).reshape(-1, 1)

#设置箱体数为11
bins = np.linspace(-1, 5, 11)
#将数据进行装箱操作
target_bin = np.digitize(X, bins=bins)

onehot = OneHotEncoder(sparse=False)
onehot.fit(target_bin)

#使用独热编码转化数据
X_in_bin = onehot.transform(target_bin)

#使用独热编码进行数据表达
new_line = onehot.transform(np.digitize(line, bins=bins))

#1.向数据集添加交互式特征
#将原始数据和装箱后的数据进行堆叠
X_stack = np.hstack([X, X_in_bin])

print(X_stack.shape)
#将数据进行堆叠
line_stack = np.hstack([line, new_line])
#训练模型
mlpr_interact = MLPRegressor().fit(X_stack, y)

#绘制图形
plt.plot(line, mlpr_interact.predict(line_stack), label='MLP for interaction')
plt.ylim(-4, 4)
for vline in bins:
    plt.plot([vline, vline], [-5, 5], ':', c='k')
plt.plot(X, y, 'o', c='r')
plt.legend(loc='lower right')

#使用新的堆叠方式
X_multi = np.hstack([X_in_bin, X*X_in_bin])
print('\n')
print(X_multi.shape)
print(X_multi[0])
#重新训练模型
mlpr_multi = MLPRegressor().fit(X_multi, y)
line_multi = np.hstack([new_line, line*new_line])
#绘制图形
plt.plot(line, mlpr_multi.predict(line_multi), label='MLP for new interaction')
for vline in bins:
    plt.plot([vline, vline], [-5, 5], ':', c='gray')
plt.plot(X, y, 'o', c='r')
plt.legend(loc='lower right')
plt.title('MLP for interaction')


#2.向数据集添加多项式特征

plt.show()

执行结果如下所示:

数据堆叠后:
(50, 12)

新方式数据堆叠后:
(50, 22)
[ 1.         0.         0.         0.         0.         0.         0.         0.
  0.         0.         0.        -1.1522688 -0.        -0.        -0.        -0.
 -0.        -0.        -0.        -0.        -0.        -0.       ]
10.3mlp_for_interaction.png

1. 对比装箱后的模型,添加交互式特征后的模型是倾斜的,即添加交互式特征后,在每个数据所在的箱体中,MLP模型增加了斜率。模型复杂度有所提高。
2. 为了每个箱体都有自己的截距和斜率,更换新的数据处理方式,X_multi 变成了每个样本有20个特征值的形态。每个箱子中模型的 截距和斜率都不一样了,这样做的目的就是为了让比较容易出现欠拟合现象的模型能有更好的表现。
3. 线性模型在高维数据集中有良好的性能,但是在低维数据集中却表现一般,因此需要用上面的方法来进行特征扩充,以便给数据集升维,从而提高线性模型的准确率。并用线性模型进行验证

多项式特征

在机器学习中,常用的扩展样本特征的方式就是将特征X进行乘方。用scikit-learn 中内置的 PolynoialFeatures.

注:

函数原型
sklearn.preprocessing.PolynomialFeatures(degree=2, *, interaction_only=False, include_bias=True, order='C')
作用:这个类可以进行特征的构造,构造的方式就是特征与特征相乘(自己与自己,自己与其他人),这种方式叫做使用多项式的方式
参数解释
degree:控制多项式的次数
interaction_only:默认为 False,如果指定为 True,那么就不会有特征自己和自己结合的项,组合的特征中没有 a2 和 b2;
include_bias:默认为 True 。如果为 True 的话,那么结果中就会有 0 次幂项,即全为 1 这一列。

#数据升维
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LinearRegression #导入线性回归模型


#生成随机数列
rnd = np.random.RandomState(38)
x  = rnd.uniform(-5, 5, size=50)
#向数据中添加噪声
y_no_noise = (np.cos(6 * x) + x)
X = x.reshape(-1, 1)
y = (y_no_noise + rnd.normal(size=len(x)))/ 2

#生成一个等差数列
line = np.linspace(-5, 5, 1000, endpoint=False).reshape(-1, 1)

#2.向数据集添加多项式特征

#向数据集添加多项式特征
poly = PolynomialFeatures(degree=20, include_bias=False)
X_poly = poly.fit_transform(X)
print('\n添加多项式特征后:')
print(X_poly.shape)
print('原始数据集中的第一个样本特征:\n{}'.format(X[0]))
print('\n处理后的数据集中第一个样本特征:\n{}'.format(X_poly[0]))

#打印多项式特征处理的方式
print('PolynomialFeatures对原始数据的处理:\n{}'.format(poly.get_feature_names()))

#使用处理后的数据训练线性回归模型
LNR_poly = LinearRegression().fit(X_poly, y)

line_poly = poly.transform(line)
#绘制图形
plt.plot(line, LNR_poly.predict(line_poly), label='Linear Regress')
plt.xlim(np.min(X) - 0.5, np.max(X) + 0.5)
plt.ylim(np.min(y) - 0.5, np.max(y) + 0.5)
plt.plot(X, y, 'o', c='r')
plt.legend(loc='lower right')
plt.title('Linear Regress')
plt.show()

执行结果如下:

添加多项式特征后:
(50, 20)
原始数据集中的第一个样本特征:
[-1.1522688]

处理后的数据集中第一个样本特征:
[ -1.1522688    1.3277234   -1.52989425   1.76284942  -2.0312764
   2.34057643  -2.6969732    3.10763809  -3.58083443   4.1260838
  -4.75435765   5.47829801  -6.3124719    7.27366446  -8.38121665
   9.65741449 -11.12793745  12.82237519 -14.77482293  17.02456756]
PolynomialFeatures对原始数据的处理:
['x0', 'x0^2', 'x0^3', 'x0^4', 'x0^5', 'x0^6', 'x0^7', 'x0^8', 'x0^9', 'x0^10', 'x0^11', 'x0^12', 'x0^13', 'x0^14', 'x0^15', 'x0^16', 'x0^17', 'x0^18', 'x0^19', 'x0^20']
10.3Linear_Regress.png

1. 可以看出,经过多项式特征添加后,样本特征变成了20,处理后的第一个特征就是原始数据样本特征,而第二个特征是原始特征的2次方,第三个是3次方,以此类推
2. 对于低维数据集,线性模型常常会出现欠拟合的问题,而将数据集进行多项式扩展后,可以在一定程度上解决线性模型欠拟合的问题。
3. 除了以上方法之外,还可以用类似正弦函数sin(),对数函数log(),或者是指数函数exp()等来进行相似的操作。

题外话:
对于样本分布不均衡的问题,可以参考《样本类别分布不均衡方法》

四、自动特征选择

4.1 使用单一变量法进行特征选择

详细介绍可以参考: 《[机器学习] 特征选择笔记2-单变量特征选择》

在scikit-learn 中最简单的有2种:

SelectPercentile

SelectPercentile根据最高分数的百分位数选择特征

#函数原型 
sklearn.feature_selection.SelectPercentile(score_func=, *, percentile=10)
#参数解释 
Parameters
----------
score_func:callable
            函数接受两个数组X和y,并返回一对数组(分数,
            pvalue)或带分数的单个数组。默认值为f_classif。
            默认功能仅适用于分类任务。

percentile:int, optional, default=10
            要保留的特征百分比。

Attributes
----------
scores_:array-like of shape (n_features,)
         Scores of features.

pvalues_:array-like of shape (n_features,)
          p-values of feature scores, 
          None if score_func returned only scores.

#方法
'fit(self, X, y)'   
    在(X,y)上训练过滤器并获得适当的功能。

'fit_transform(self, X[, y])'
    训练过滤器,然后对X进行转换。

'get_params(self[, deep])'
    获取此估计量的参数。

'get_support(self[, indices])'
    获取所选特征的掩码或整数索引
    Get a mask, or integer index, of the features selected

'inverse_transform(self, X)'
    反向转换操作

'set_params(self, \*\*params)'
    设置此估算器的参数。

'transform(self, X)'
    将X缩小为选定的特征。

注意:分数相等的要素之间的关系将以不确定的方式断开。
转自《特征选择 - SelectPercentile》

SelectKBest

根据给定的选择器,选择出前k个与标签最相关的特征。

#函数原型 
sklearn.feature_selection.SelectKBest(score_func=, *, k=10)

#参数解释  
Parameters
----------
score_func: 可调用的
            函数输入两个数组X和y,并返回一对数组(分数,p-value)或带分数的
            单个数组。默认值为f_classif(请参见下文“另请参见”)。默认功能仅
            适用于分类任务。

k:int or “all”, optional, 默认=10
   要选择的主要特征数(保留前几个最佳特征)。
   “ all”选项绕过选择,用于参数搜索。

Attributes
----------
scores_:array-like of shape (n_features,)
         特征分数。

pvalues_:array-like of shape (n_features,)
          特征分数的p值,如果score_func仅返回分数,则为None 。

#方法
'fit(self, X, y)'
    在(X,y)上运行得分功能并获得适当的功能。
    相当于训练模型吧。
    Parameters
        X:array-like of shape (n_samples, n_features)
           输入训练样本。
        y:array-like of shape (n_samples,)
           目标值(分类中的类标签,回归中的实数)。
    Returns
        self:object

'fit_transform(self, X[, y])'
    用X训练模型,然后对其进行转换。
    Parameters
        X:{array-like, sparse matrix, dataframe} of shape (n_samples, n_features)
        y:ndarray of shape (n_samples,), default=None
           目标值。
        **fit_paramsdict:其他拟合参数。

    Returns
        X_new:ndarray array of shape (n_samples, n_features_new)
               转换后的数组。

'get_params(self[, deep])'
    获取此估计量的参数。
    Parameters
        deep: bool, default=True
              如果为True,则将返回此估算器和作为估算器的包含子对象的参数。

    Returns
        params: mapping of string to any
                参数名称映射到其值。

'get_support(self[, indices])'
    获取所选特征的掩码或整数索引
    Get a mask, or integer index, of the features selected

'inverse_transform(self, X)'
    反向转换操作
    Parameters
        X: array of shape [n_samples, n_selected_features]
           输入样本。

    Returns
        X_r: array of shape [n_samples, n_original_features]
             在X中被transform删除的特征位置加入零列。

'set_params(self, \*\*params)'
    设置此估算器的参数。
    Parameters
        **params: dict
                  估算器参数.

    Returns
        self: object
              估算器实例。
              
'transform(self, X)'
    将X缩小为指定的k个特征。
    Parameters
        X: array of shape [n_samples, n_features]
           输入样本。

    Returns
        X_new: array of shape [n_samples, n_selected_features]
             仅保留k个最优特征的输入样本。

转自《特征选择 - SelectKBest》

4.2 基于模型的特征选择

基于模型的特征选择原理是,先使用一个有监督学习的模型对数据特征的重要性进行判断,然后把最重要的特征进行保留。

#函数原型 
sklearn.feature_selection.SelectFromModel(estimator, 
        *, threshold=None, prefit=False, norm_order=1, 
        max_features=None)[source]

#参数解释 
Parameters
----------
estimator: object
           用来构建变压器的基本估算器。
           既可以是拟合的(如果prefit设置为True),也可以是不拟合的估计量。
           拟合后,估算器必须具有 feature_importances_或coef_属性。

threshold: str, float, optional default None
           用于特征选择的阈值。
           保留重要性更高或相等的要素,而其他要素则被丢弃。
           如果为“中位数”(分别为“平均值”),
             则该threshold值为要素重要性的中位数(分别为平均值)。
             也可以使用缩放因子(例如,“ 1.25 *平均值”)。
           如果为None且估计器的参数惩罚显式或隐式设置为l1(例如Lasso),
             则使用的阈值为1e-5。
           否则,默认使用“均值”。

prefit: bool, default False
        预设模型是否期望直接传递给构造函数。
        如果为True,transform必须直接调用和
        SelectFromModel不能使用cross_val_score, GridSearchCV而且克隆估计类似的实用程序。
        否则,使用训练模型fit,然后transform进行特征选择。

norm_order: 非零 int, inf, -inf, default 1
            在估算器threshold的coef_属性为维度2 的情况下,
            用于过滤以下系数矢量的范数的顺序 。

max_features:int or None, optional
              要选择的最大功能数。
              若要仅基于选择max_features,请设置threshold=-np.inf。

Attributes
----------
estimator_:一个估算器
            用来建立变压器的基本估计器。
            只有当一个不适合的估计器传递给SelectFromModel时,
            才会存储这个值,即当prefit为False时。

threshold_:float
            用于特征选择的阈值。

#方法
'fit(self, X[, y])'
    训练SelectFromModel元变压器。

'fit_transform(self, X[, y])'
    训练元变压器,然后对X进行转换。

'get_params(self[, deep])'
    获取此估计量的参数。
    
'get_support(self[, indices])'
    获取所选特征的掩码或整数索引

'inverse_transform(self, X)'
    反向转换操作
    
'partial_fit(self, X[, y])'
    仅将SelectFromModel元变压器训练一次。

'set_params(self, \*\*params)'
    设置此估算器的参数。

'transform(self, X)'
    将X缩小为选定的特征。

注:包括随机森林在内的基于决策树的算法都会内置一个称为 feature_importances_的属性,可以让SelectFromModel直接从这个属性中抽取特征的重要性。也可以用L1正则化的线性模型。

4.3 迭代式特征选择

#函数原型 
RFE(BaseEstimator, MetaEstimatorMixin, SelectorMixin)

#参数解释 
 Parameters
    ----------
    estimator : object
       估计函数,底层的回归模型。一个监督学习的估计函数,有fit方法,fit方法,通过coef_ 属性或者 feature_importances_ 属性来提供feature重要性的信息.

    n_features_to_select : int or None (default=None)
        选择(最优)feature的数量,超出的部分按照关联性排序。如果选择None, 就选择一半的feature

    step : int or float, 可选(default=1)
        如果大于等于1,step对应于迭代过程中每次移除的属性的数量(integer)。如果是(0.0,1.0),就对应于每次移除的特征的比例,四舍五入。

    verbose : int, default=0
        Controls verbosity of output.

    Attributes
    ----------
    n_features_ : int
         int所选特征的数量.

    support_ : array of shape [n_features]
        [n_features]大小的array,所选特征的一种模糊的表示,可以看出来,打印结果就是true和false,最优的是true,别的是false.

    ranking_ : array of shape [n_features]
        [n_features]大小的array,特征的排序,比如 ranking_[i]表示的就是第i个特征的排名位置。估计最佳的属性被排为1..

    estimator_ : object
        object外部估计函数的相关信息.

其他方法参考:《机器学习之特征选择方法》

你可能感兴趣的:(机器学习之特征工程)