前言:在我的上一份文章中,介绍到了数据清洗的常用方法及其Python实现。这篇文章主要是在数据清洗后的特征工程的实现,特征工程包含三个方面,分别为特征提取、特征创造、特征选择。
特征提取 | 特征创造 | 特征选择 |
---|---|---|
从文字、图像、声音等其他非结构化数据中提取新信息作为特征。比如说,从地址中提取国家、省、市等信息。 | 将现有的特征进行组合,或者进行某种计算得到新的特征。例如我们的多元多次线性回归就可以借助已有的特征创造新的特征。 | 从所有的特征中,选择出有意义的,对模型有帮助的特征,避免特征量过多,训练时间长。 |
作为入门篇,我们主要的重点放在特征的选择上,其余两种会略有提及。
一:数据预处理
我们对数据的处理除了上一份文章提及到的消除缺失值、异常值、重复值的影响之外,还包括我们的数据预处理。这一部分都不改变数据原有的结构。
1.1 无量纲化
1.1.1 为什么要无量纲化
让我们思考这样一个问题,当我们使用梯度下降法进行近似求解的时候,如果各个特征量的量纲不统一会如何?可以参考下方的gif动图。实现代码如下:
import numpy as np
from ipywidgets import FloatSlider,VBox
from bqplot import pyplot as plt
# 创建数据集
X = np.random.rand(100, 1)
y = (4 + 3 * X) + np.random.randn(100, 1)
X_b = np.c_[np.ones((100, 1)), X]
# 创建超参数
learning_rate = 0.001
n_interation = 1000
# 初始化θ
thetas = np.empty((n_interation,2))
theta = np.random.randn(2, 1)
# 判断是否收敛,一般不会设定阈值,而是直接采用设置相对大的迭代次数保证可以收敛
for iteration in range(n_interation):
# 求梯度
gradient = X_b.T.dot(X_b.dot(theta) - y)
# 应用梯度下降法调整θ值
theta = theta - learning_rate * gradient
thetas[iteration] = theta.reshape(1,2)
title_tmpl = 'θ的10的{}次方量级下的图形'
line_fig = plt.figure(title=title_tmpl.format(0))
line = plt.plot(np.array(range(n_interation)),thetas[0],'m',stroke_width=3)
line_2 = plt.plot(np.array(range(n_interation)),thetas[1],colors=['green'])
i_slider = FloatSlider(description='i',value=0,min=0,max=5,step=1)
def update_y(change):
new_i = i_slider.value
# 创建数据集
X = np.random.rand(100, 1)*(10**new_i)
y = (4 + 3 * X)/(10**new_i) + np.random.randn(100, 1)
X_b = np.c_[np.ones((100, 1)), X]
# 创建超参数
learning_rate = 0.001
n_interation = 1000
# 初始化θ
thetas = np.empty((n_interation,2))
theta = np.random.randn(2, 1)
# 判断是否收敛,一般不会设定阈值,而是直接采用设置相对大的迭代次数保证可以收敛
for iteration in range(n_interation):
# 求梯度
gradient = X_b.T.dot(X_b.dot(theta) - y)
# 应用梯度下降法调整θ值
theta = theta - learning_rate * gradient
thetas[iteration] = theta.reshape(1,2)
line.y = thetas[0]
line_2.y = thetas[1]
line_fig.title = title_tmpl.format(new_i)
i_slider.observe(update_y,'value')
finale_layout = VBox([line_fig,i_slider])
finale_layout
不同量级的θ比较
通过Gif动图,我们可以发现,θ0的量级几乎不变,而θ1的量级快速升高(可查看代码比较)。而这也就是我们之所以要进行无量纲化的原因。目的就是为了统一量纲,不至于发生gif图中的情况。
-
最大最小值无量纲化
最大最小值无量纲化的实现
如此,我们的数据就统一到了0-1之间,当然如果你要指定量纲到m到n的话,可以使用如下公式:
等效到我们的sklearn中,则是我们的preprocessing模块中的MinMaxScaler方法,传入数据直接就能帮助我们实现最大最小值无量纲化。
当然,如果你喜欢的话,使用我们的numpy可以手动实现该无量纲话过程。不过,有现成的工具当然是直接用现成的工具了啊。另外,如果想要改变MinMaxScaler的数据区间,可以使用它的feature_range属性指定区间。其余的步骤都是一样的,实现如下:
指定区间标准化数据
注意:对于最大最小值标准化来说,存在一个问题,那就是当我们存在极大异常值时,数据将会受到极大异常值的的影响。除了极大值外,其余的数据标准化后都接近0,因此给建模造成极大影响。为了解决这个问题,我们提出了数据标准化。 -
数据标准化
数据标准化
另外一种无量纲化的方式是标准归一化,主要借助了我们的均值μ和我们的σ。使用这两个数据进行归一化的公式如下:
而其在sklearn中实现的方式为StandardScaler,使用方式和MinMaxScaler相同,值得注意的就是它的两个属性,一个是.mean_(均值),一个是.var_(方差)。
查看均值和方差 -
其他标准化
其余标准化
更多标准化可以参考sklearn官方网站的preprocessing模块
注意:并不是说所有的数据都要进行预处理,是否预处理数据取决于你的业务逻辑需求,而不是建模精度,比如银行的评分卡模型,关于用户的月收入就不需要进行预处理,哪怕量纲相对来说很大。但是为了业务人员更好的理解月收入和评分之间的关系,因此不做预处理。
1.2 数据编码
1.2.1 概念
目前为止我们展示的数据都是数值型数据,那么当我们遇到了文字型数据应该怎么处理呢?
这就需要用到我们的数据编码,而数据的编码需要结合我们的数据类型来选用不同的方式。理由也很简单,比如我们预测结果属于yes和No的二分类,进行0、1转换就行了。而对于有次序关系但没有计算关系的数据,如营长、排长、班长。你还可以说营长职位最高,但不能说几个班长加起来等于营长,这种就需要one-hot编码。再比如既有次序,又有计算能力的数据,比如75kg、85kg、95kg。可以说75kg + 10kg = 85kg,因为其本身就可以计算。
数据类型及定义
数据名称 | 数据类型 | 定义 | 举例 |
---|---|---|---|
名义 | 离散的、定性的数据 | 名义变量就是用来告诉我们数据的名称、用以区分数据类型的 | 性别、姓名、颜色等 |
有序 | 离散的、定性的数据 | 有序变量提供数据的相对大小的信息,告诉我们数据之间的次序信息,不提供计算数据的能力 | 等级、职位、分位数等 |
有距 | 连续的、定量的数据 | 有距变量之间的间隔有固定的意义,可以进行加减 | 日期等 |
比率 | 连续的、定量的数据 | 比率变量之间的间隔和比例都有意义,可以加减乘除 | 计数、价格、质量等 |
1.2.2 sklearn的实现
sklearn实现数据编码主要通过如下三个方法:
编码变量sklearn的实现1.LabelEncoder
LabelEncoder针对名义变量,一般用于标签的数字编码。
import pandas as pd
from sklearn.preprocessing import OneHotEncoder,LabelEncoder,OrdinalEncoder
names = ['卯月','竹秋','花朝','殷正']
names = pd.Series(names)
encodr = LabelEncoder()
new_names = encodr.fit_transform(names)
display(new_names)
encodr.inverse_transform(new_names)
encodr.classes_
2. OrdinalEncoder
OrdinalEncoder针对有序变量,用于将有序但无距的变量离散出来,例如我们前文提到的等级、学历等。
position = ['军长','师长','旅长','团长','营长','排长']
position = pd.DataFrame(position)
ori_encoder = OrdinalEncoder()
new_position = ori_encoder.fit_transform(position)
new_position
ori_encoder.categories_
3. OneHotEncoder
OneHotEncoder也是为了数字编码名义变量的,还记得我们说的有距变量和比率变量的特征吧?当我们使用LabelEncoder和OrdinalEncoder编码的时候,无法避免的给特征加上了距离,并且距离可以计算。为了解决这样的问题,我们使用了OneHotEncoder独热编码,将特征转化为该特征下多个不同的分类的稀疏矩阵。
prices = ['29元','28元','27元','30元','31元']
one_encoder = OneHotEncoder()
new_prices = one_encoder.fit_transform(prices)
new_prices.toarray()
one_encoder.categories_
数据编码
二:特征工程
2.1 过滤法
当我们对数据进行了预处理后,意味着我们的数据都能够被sklearn进行处理,我们得到了处理后的数据。现在,让我们来思考一个问题:如果我有几十上百万个特征让你进行建模,你要怎么做?
当然,实际的生活中肯定不可能一下子就让你接触这么多的特征。但我们依旧可以这么思考。要处理这个问题的核心就在于,我们的每一个特征是不是有意义。所以,我们的问题就转变成了:如何衡量信息是否有意义?
那么,我们如何衡量信息是否有意义呢?还记得我们的数学中如何衡量距离的远近么?
没错,最简单的方法,可以使用方差作为衡量指标。当然,我们也可以使用卡方检验捕捉相关性,还可以使用我们的F检验捕捉线性相关性,互信息法捕捉相关性。也可以使用熵作为信息量大小的判定(要求能得到概率才可以使用),使用余弦距离衡量句子的相关性(自然语言处理)。
总结起来如下表: sklearn的过滤方法2.1.1 方差过滤
方差过滤基于特征量本身的信息大小来进行筛选。当一个特征量的方差很小时,我们可以得出特征量基本一致的结论,尤其是当方差为0时,说明该特征量的各个值之间极其接近,就可以直接将该特征量去除。当然,也可以指定阈值(threshold),将方差小于阈值(threshold)的特征量去除。 方差过滤2.1.2 卡方过滤
卡方过滤针对离散化的数据标签。计算每个非负特征和标签之间的卡方统计量(真实标签和假设标签之间的关系是独立还是有关系的?)
卡方检验的本质是推测两组数据之间的差异,其检验的原理是假设两组数据相互独立。返回卡方值和P值两个统计量,卡方值难以界定范围,因此我们使用P值作为显著性水平,追求P值小于0.05或0.01。
P值和假设值之间的关系卡方过滤寻找分类数据的P值
2.1.3 F检验
F检验,用来辅助捕捉每个特征与标签之间的线性关系的过滤方法,既可以用作回归过滤也可以用作分类过滤。所以 ,可以使用f_classif和f_regression两种方法。
F检验的原假设是数据不存在显著的线性关系。它返回F值和P值两个统计量。和卡方过滤一样,我们希望选取P值小于0.05或0.01的特征以反驳原假设。
F检验分类数据F回归和F分类的方式是一样的,因此不再做演示
2.1.4 互信息法
我们的F检验验证的是特征和结果之间的线性关系,但是日常生活的数据大部分都是非线性的。
因此互信息法就能派上用场了,它衡量的是任何关系,无论是线性关系还是非线性关系都能够衡量。它和F检验一样也有分类和回归两种方式可以使用。不过,不同的是互信息法并不返回P值或F值这样类似的统计量,它返回的是一个“每个特征与目标之间的互信息量的估计”,在[0,1]之间,为0表示完全独立,为1表示完全相关。
互信息法分类2.2 嵌入法
我们前面提到的过滤法还是要基于我们的经验,需要我们选定阈值,那么有没有一种方法可以自己从算法中选择特征呢?
当然,既然提出了这样的问题,肯定是有解决方法的,这样的方法就是嵌入法和包装法,我们先介绍嵌入法。
嵌入法是一种让算法自己决定使用那些特征的方法。我们先使用某些算法进行训练,得到各个特征的权值系数,再根据权值系数的大小进行特征选择。
权值系数代表了特征对模型的重要程度,因此无关的特征和无区分度的特征都会因为对模型的构建不重要而被舍弃。
嵌入法在sklearn中通过SelectFromModel实现,有如下重要参数: 使用参数及说明在这里我们对它有一个概念就行了,所以接下来只做演示,不对代码进行说明。具体的建模算法和原因之后分析算法的时候会说到:
注意:使用嵌入法完成的只是特征选择这一步,得到的是一个包含选择后的新的数据,使用该数据再进行建模才能有结果,而不是直接出结果。
2.3 包装法
包装法与嵌入法十分相似,依赖特征自身的选择,比如coef_属性或feature_importances_属性完成特征选择。
不过,我们使用目标函数作为一个黑盒参与特征选择过程,不需要我们再指定阈值或评估指标。另外,包装法要使用特种子集多次训练,因此需要的计算成本是以上的方法中最高的。
包装法在sklearn中通过RFE实现,有如下重要的参数和属性: RFE重要属性和参数说明 依旧是目前了解概念即可,后面的算法分析会有详细解释: RFE包装法演示2.4 其他特征工程相关
除了上文介绍到的特征选择以外,在特征工程里面还包括贴特征创造,特征提取,不过这个属于数据分析经验相关的高级方法了,因此作为入门不做介绍,但是可以有一个概念了解。 特征工程三大类三:数据处理
当我们的数据经过清洗、特征经过特征工程后,就数据而言已经满足了建模的要求了。然而,我们的数据依旧可能存在准确率偏低的情况,这又是为什么呢?
当我们在训练数据的时候,非常容易出现过拟合的想象,究其原因无非是特征量少、训练集大、算法选择不正确。因此,我们就需要针对建模过程中进行数据处理,防止模型过拟合或者欠拟合。
过拟合可理解为数据过于拟合训练集,以至于训练集的噪声也学会了,从而在测试集上表现不佳。而欠拟合则表现为训练集测试不佳、测试集也表现不佳。(可参考下方正则化过程进行理解)
3.1 正则化
在数据建模时,我们通常将数据分为训练集、验证集、测试集(也有训练集、测试集的分法,看需求选择)。建模后,数据在训练集上表现很高,预测准确率很高,但是在测试集上表现非常糟糕,这意味着我们的数据建模过拟合,过拟合的意思是数据非常符合训练集的规律,因此导致在其他的同类型数据集上表现不好。正则化就是为了防止训练集出现过拟合而出现的方法。
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from ipywidgets import FloatSlider,VBox
from bqplot import pyplot as plt
# 创建一条带有噪声的正弦曲线
x = np.sort(5*np.random.rand(80,1),axis=1)
y = np.sin(x).ravel()
y[::5] += 3*(0.5-np.random.rand(16))
# 实例化回归树并训练数据
regr_1 = DecisionTreeRegressor(max_depth=2)
regr_1.fit(x,y)
# 测试集导入模型,预测结果
x_test = np.arange(0.0,5.0,.01)[:,np.newaxis]
y_1 = regr_1.predict(x_test)
title_tmpl = '{}层回归树的拟合效果'
line_fig = plt.figure(title=title_tmpl.format(2))
line = plt.plot(x_test,y_1,'m',stroke_width=3)
plt.scatter(x.ravel(),y.ravel())
depth_slider = FloatSlider(description='depth',value=2,min=2,max=10,step=1)
def update_y(change):
new_depth = depth_slider.value
print(new_depth)
regr = DecisionTreeRegressor(max_depth=new_depth)
regr = regr.fit(x,y)
# 更新y轴的值和标题
line.y = regr.predict(x_test)
line_fig.title = title_tmpl.format(new_depth)
depth_slider.observe(update_y,'value')
final_layout = VBox([line_fig,depth_slider])
final_layout
过拟合演示
从上方回归树的拟合我们可以发现,随着树的层级的加深,我们整个模型对于训练集的拟合效果越来越好。整个图形越来越光滑,并且学习了测试集的大部分特征(包括噪声)。因此,针对不同的算法,我们有不同的过拟合调参方式。不过,大体可总结为针对线性模型的Lasso(L1正则)、Ridge(L2正则),针对树模型的剪枝,集成算法的剪枝等。具体的详情在后续的算法介绍中会给出针对具体算法的防止过拟合策略。这里有一个概念就行了。
3.2 升维
升维针对分类和聚类问题,是为了找到一个决策边界用于区分不同类型数据。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
x = np.random.rand(1000,2)
x_1 = x[:,0].tolist()
y_1 = x[:,1].tolist()
z_1 = np.zeros(1)
z_2 = np.zeros(499)+0.3
z_3 = np.ones(500)
z = np.concatenate((z_1,z_2,z_3),axis=0).tolist()
# 二维图
plt.scatter(x_1,y_1)
plt.show()
# 三维图
ax = plt.axes(projection='3d')
ax.set
ax.scatter3D(x_1, y_1, z, c=z, cmap='Greens')
plt.show()
升维
当我们没有进行升维的时候,二维平面图就是一团乱糟糟的散点图,根本区分不了谁是谁。然而,当我们对数据进行了升维之后,从二维数据变为了三维数据后,整个分类就清晰了,因为升维后,数据之间就有了间隔,可以清晰的找到它的决策边界,将数据分类或者聚类,这就是升维的力量。升维是为了线性可分!
说明
- 本文由我本人原创,发布于简书卯月七账号、知乎卯月七账号、CSDN卯月七账号。
- 本文允许转载、学习,转载请注明出处,谢谢。
- 作者邮箱mcj6989@163.com,有问题可以联系。
- 本文为我整理的数据清洗的入门文章,更多知识可以购买专业书籍学习。
- 创作不易,如果对你有帮助,希望能给我一些反馈,包括不限于点赞,评论,转发,非常感谢!!
网友评论