数据产品

特征工程

2020-07-21  本文已影响0人  堂堂正正的大号

1、数据分桶(分桶后需要编码)

连续值经常离散化或者分离成“箱子”进行分析, 为什么要做数据分桶呢?

当然还有很多原因,LightGBM 在改进 XGBoost 时就增加了数据分桶,增强了模型的泛化性。现在介绍数据分桶的方式:

最好将数据分桶的特征作为新一列的特征,不要把原来的数据给替换掉。这里放一个数据分桶的其他例子(迁移之用)

# 连续值经常离散化或者分离成“箱子”进行分析。
# 假设某项研究中一组人群的数据,想将他们进行分组,放入离散的年龄框中
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
# 如果按年龄分成18-25, 26-35, 36-60, 61以上的若干组,可以使用pandas中的cut
bins = [18, 25, 35, 60, 100]         # 定义箱子的边
cats = pd.cut(ages, bins)
print(cats)   # 这是个categories对象    通过bin分成了四个区间, 然后返回每个年龄属于哪个区间
# codes属性
print(cats.codes)    #  这里返回一个数组,指明每一个年龄属于哪个区间
print(cats.categories)
print(pd.value_counts(cats))   # 返回结果是每个区间年龄的个数

# 与区间的数学符号一致, 小括号表示开放,中括号表示封闭, 可以通过right参数改变
print(pd.cut(ages, bins, right=False))

# 可以通过labels自定义箱名或者区间名
group_names = ['Youth', 'YonngAdult', 'MiddleAged', 'Senior']
data = pd.cut(ages, bins, labels=group_names)
print(data)
print(pd.value_counts(data))

# 如果将箱子的边替代为箱子的个数,pandas将根据数据中的最小值和最大值计算出等长的箱子
data2 = np.random.rand(20)
print(pd.cut(data2, 4, precision=2))   # precision=2 将十进制精度限制在2位

# qcut是另一个分箱相关的函数, 基于样本分位数进行分箱。取决于数据的分布,使用cut不会使每个箱子具有相同数据数量的数据点,而qcut,使用
# 样本的分位数,可以获得等长的箱
data3 = np.random.randn(1000)   # 正太分布
cats = pd.qcut(data3, 4)
print(pd.value_counts(cats))

结果如下:


2、数据转换

数据转换的方式有:

2.1 标准化/归一化

标准化/归一化的好处:
1. 提升模型精度
在机器学习算法的目标函数(例如SVM的RBF内核或线性模型的l1和l2正则化),许多学习算法中目标函数的基础都是假设所有的特征都是零均值并且具有同一阶数上的方差。如果某个特征的方差比其他特征大几个数量级,那么它就会在学习算法中占据主导位置,导致学习器并不能像我们说期望的那样,从其他特征中学习。

举一个简单的例子,在KNN中,我们需要计算待分类点与所有实例点的距离。假设每个实例点(instance)由n个features构成。如果我们选用的距离度量为欧式距离,如果数据预先没有经过归一化,那么那些绝对值大的features在欧式距离计算的时候起了决定性作用。

从经验上说,归一化是让不同维度之间的特征在数值上有一定比较性,可以大大提高分类器的准确性。

2. 提升收敛速度
对于线性模型来说,数据归一化后,最优解的寻优过程明显会变得平缓,更容易正确的收敛到最优解。


比较这两个图,前者是没有经过归一化的,在梯度下降的过程中,走的路径更加的曲折,而第二个图明显路径更加平缓,收敛速度更快。 对于神经网络模型,避免饱和是一个需要考虑的因素,通常参数的选择决定于input数据的大小范围。

归一化与标准化的区别:

归一化: \frac{x_{i}-\min \left(x_{i}\right)}{\max \left(x_{i}\right)-\min \left(x_{i}\right)}

标准化:\frac{x_{i}-\bar{x}}{s d(x)}

首先明确,在机器学习中,标准化是更常用的手段,归一化的应用场景是有限的。我总结原因有两点:

哪些模型必须归一化/标准化:SVM、KNN、神经网络、PCA等

2.2 各编码之间的区别:

独热编码 哑变量编码 标签编码

独热编码缺点就是,当类别的数量很多时,特征空间会变得非常大。

中学和硕士的平均值是大学,所以label encoding最直观的缺点就是赋值难以解释,适用场景更窄。

独热编码与哑变量编码使用场景区分在于加不加bias项

2.3 修复偏斜特征:

BOX-COX转换
我们测得一些数据,要对数据进行分析的时候,会发现数据有一些问题使得我们不能满足我们以前分析方法的一些要求(正态分布、平稳性)为了满足经典线性模型的正态性假设,常常需要使用指数变换或者对数转化,使其转换后的数据接近正态

BOX-COX变换的目标有两个:
1、变换后,可以一定程度上减小不可观测的误差和预测变量的相关性。主要操作是对因变量转换,使得变换后的因变量于回归自变量具有线性相依关系,误差也服从正态分布,误差各分量是等方差且相互独立。
2、用这个变换来使得因变量获得一些性质,比如在时间序列分析中的平稳性,或者使得因变量分布为正态分布。

关于box-cox转换,一般是用于连续的变量不满足正态的时候,在做线性回归的过程中,一般线性模型假定:
Y=X \beta+\varepsilon其中ε假设满足正态分布,正态分布的假设让回归曲线对于极端值更加敏感:因为在正态假设下,极大的误差看起来是很不可能的,所以当极大的误差出现时,回归曲线往往会偏向极端点。

这时正态假设在某些情况下可能会限制线性回归的表现

因此,很多时候利用实际数据建立回归模型时,个别变量的系数通不过。例如往往不可观测的误差 ε 可能是和预测变量相关的,不服从正态分布,于是给线性回归的最小二乘估计系数的结果带来误差,为了使模型满足线性性、独立性、方差齐性以及正态性,需改变数据形式,故应用BOX-COX转换。具体详情这里不做过多介绍,当然还有很多转换非正态数据分布的方式:

在一些情况下(P值<0.003)上述方法很难实现正态化处理,所以优先使用BOX-COX转换,但是当P值>0.003时两种方法均可,优先考虑普通的平方变换。(注:统计学上一般P值大于0.05我们可认为该组数据是符合正态分布。)

BOX-COX的变换公式:y^{(\lambda)}=\left\{\begin{array}{l}\frac{(y+c)^{\lambda}-1}{\lambda}, \text { if } \lambda \neq 0 \\ \log (y+c), \text { if } \lambda=0\end{array}\right.

具体实现:

from scipy.stats import boxcox
boxcox_transformed_data = boxcox(original_data)

3、特征构造

在特征构造的时候,需要借助一些背景知识,遵循的一般原则就是需要发挥想象力,尽可能多的创造特征,不用先考虑哪些特征可能好,可能不好,先弥补这个广度。特征构造的时候需要考虑时间特征,数值特征,类别特征。

从这个比赛开始,看看这几种类型的特征如何进行构造新特征出来。

根据上面的描述,下面就可以着手进行构造特征了,由于在数据清洗的时候就根据字段的类型不同把数据进行了划分,现在可以直接对特征进行构造。

时间特征的构造(time_{data})根据上面的分析,可以构造的时间特征如下:

汽车的使用时间特征
createDate-regDate, 反应汽车使用时间,一般来说与价格成反比。但要注意的问题就是时间格式, regDateFalse这个字段有些是0月,如果忽略错误计算的话,使用时间有一些会是空值, 当然可以考虑删除这些空值,但是因为训练集和测试集合并了,那么就不轻易删除了。

本文采取的办法是把错误字段都给他加1个月,然后计算出天数之后在加上30天(这个有不同的处理方式, 但是一般不喜欢删除或者置为空,因为删除和空值都有潜在的副作用)

# 这里是为了标记一下哪些字段有错误
def regDateFalse(x):
    if str(x)[4:6] == '00':
        return 1
    else: 
        return 0
time_data['regDateFalse'] = time_data['regDate'].apply(lambda x: regDateFalse(x))
# 这里是改正错误字段
def changeFalse(x):
    x = str(x)
    if x[4:6] == '00':
        x = x[0:4] + '01' + x[6:]
        x = int(x)
    return x
time_data['regDate'] = time_data['regDate'].apply(lambda x: changeFalse(x))
# 使用时间:data['creatDate'] - data['regDate'],反应汽车使用时间,一般来说价格与使用时间成反比
# 不过要注意,数据里有时间出错的格式,所以我们需要 errors='coerce'
time_data['used_time'] = (pd.to_datetime(time_data['creatDate'], format='%Y%m%d') - 
                            pd.to_datetime(time_data['regDate'], format='%Y%m%d')).dt.days
# 修改错误
# 但是需要加上那一个月
time_data.loc[time_data.regDateFalse==1, 'used_time'] += 30
# 删除标记列
del time_data['regDateFalse']

这样,一个特征构造完毕, used_time字段,表示汽车的使用时间。全部构造完毕之后,我们再看一下结果。

汽车是不是符合报废
时间特征还可以继续提取,我们假设用了10年的车作为报废车的话, 那么我们可以根据使用天数计算出年数, 然后根据年数构造出一个特征是不是报废

# 使用时间换成年来表示
time_data['used_time'] = time_data['used_time'] / 365.0
time_data['Is_scrap'] = time_data['used_time'].apply(lambda x: 1 if x>=10 else 0)

我们还可以对used_time进行分箱,这个是根据背景估价的方法可以发现, 汽车的使用时间3年,3-7年,10年以上估价会有不同,所以分一下箱

bins = [0, 3, 7, 10, 20, 30]
time_data['estivalue'] = pd.cut(time_data['used_time'], bins, labels=False)

这样就又构造了两个时间特征。Is_scrap表示是否报废, estivalue表示使用时间的分箱。

是不是淡旺季
这个是根据汽车的上线售卖时间看, 每年的2, 3月份及6,7,8月份是整个汽车行业的低谷, 年初和年末及9月份是二手车销售的黄金时期, 所以根据上线时间选出淡旺季。

# 选出淡旺季
low_seasons = ['3', '6', '7', '8']
time_data['is_low_seasons'] = time_data['creatDate'].apply(lambda x: 1 if str(x)[5] in low_seasons else 0)

# 独热一下
time_data = pd.get_dummies(time_data, columns=['is_low_seasons'])

# 这样时间特征构造完毕,删除日期了
del time_data['regDate']
del time_data['creatDate']

看一下最后的构造结果, 报废特征没有构造,因为发现了一个特点就是这里的数据10年以上的车会偏斜,所以感觉这个用10年作为分界线不太合适,只是提供一种思路。


根据汽车的使用时间或者淡旺季分桶进行统计特征的构造
# 构造统计特征的话需要在训练集上先计算
train_data_timestats = train_data.copy()   # 不要动train_data

train_data_timestats['estivalue'] = time_data['estivalue'][:train_data.shape[0]]
train_data_timestats['price'] = train_target

train_gt = train_data_timestats.groupby('estivalue')
all_info = {}
for kind, kind_data in train_gt:
    info = {}
    kind_data = kind_data[kind_data['price'] > 0]
    info['estivalue_count'] = len(kind_data)
    info['estivalue_price_max'] = kind_data.price.max()
    info['estivalue_price_median'] = kind_data.price.median()
    info['estivalue_price_min'] = kind_data.price.min()
    info['estivalue_price_sum'] = kind_data.price.sum()
    info['estivalueprice_std'] = kind_data.price.std()
    info['estivalue_price_average'] = round(kind_data.price.sum() / (len(kind_data) + 1), 2)
    all_info[kind] = info

estivalue_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={"index": "estivalue"})
time_data = time_data.merge(estivalue_fe, how='left', on='estivalue')

这样, 时间特征就基本构造完毕,最后的结果如下:

这样,时间特征这块就构造了10个特征出来, 当然还可以更多, 由于篇幅原因,其他的可以自行尝试。

类别特征的构造(cat_{data})经过上面的分析,可以构造的类别特征如下:

注意,OneHot不要太早,否则有些特征就没法提取潜在信息了。

邮编特征
从邮编中提取城市信息, 因为是德国的数据,所以参考德国的邮编,加入先验知识。

cat_data['city'] = cat_data['regionCode'].apply(lambda x: str(x)[0])

私用车和商务车分开(bodyType)

com_car = [2.0, 3.0, 6.0]  # 商用车
GL_car = [0.0, 4.0, 5.0]   # 豪华系列  
self_car = [1.0, 7.0]

def class_bodyType(x):
    if x in GL_car:
        return 0
    elif x in com_car:
        return 1
    else:
        return 2

cat_data['car_class'] = cat_data['bodyType'].apply(lambda x : class_bodyType(x))

新能源车和燃油车分开(fuelType)

# 是否是新能源
is_fuel = [0.0, 1.0, 2.0, 3.0]
cat_data['is_fuel'] = cat_data['fuelType'].apply(lambda x: 1 if x in is_fuel else 0)

构造统计特征
这一块依然是可以构造很多统计特征,可以根据brand, 燃油类型, gearbox类型,车型等,都可以,这里只拿一个举例,其他的类似,可以封装成一个函数处理。

以gearbox构建统计特征:

train_data_gearbox = train_data.copy()   # 不要动train_data
train_data_gearbox['gearbox'] = cat_data['gearbox'][:train_data.shape[0]]
train_data_gearbox['price'] = train_target

train_gb = train_data_gearbox.groupby('gearbox')
all_info = {}
for kind, kind_data in train_gb:
    info = {}
    kind_data = kind_data[kind_data['price'] > 0]
    info['gearbox_count'] = len(kind_data)
    info['gearbox_price_max'] = kind_data.price.max()
    info['gearbox_price_median'] = kind_data.price.median()
    info['gearbox_price_min'] = kind_data.price.min()
    info['gearbox_price_sum'] = kind_data.price.sum()
    info['gearbox_std'] = kind_data.price.std()
    info['gearbox_price_average'] = round(kind_data.price.sum() / (len(kind_data) + 1), 2)
    all_info[kind] = info

gearbox_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={"index": "gearbox"})

cat_data = cat_data.merge(gearbox_fe, how='left', on='gearbox')

当然下面就可以把bodyType和fuelType删除,因为该提取的信息也提取完了,该独热的独热。

# 删掉bodyType和fuelType,然后把gearbox, car_class is_fuel独热一下, 这个不能太早,构造晚了统计特征之后再独热
del cat_data['bodyType']
del cat_data['fuelType']

cat_data = pd.get_dummies(cat_data, columns=['gearbox', 'car_class', 'is_fuel', 'notRepairedDamage'])

这样,把类别特征构造完毕。最终结果如下:

本文构造了41个特征,当然可以更多, 也是自己尝试。

数值特征的构造(num_{data})数值特征这块,由于大部分都是匿名特征,处理起来不是太好处理,只能尝试一些加减组合和统计特征

对里程进行一个分箱操作
一部车有效寿命30万公里,将其分为5段,每段6万公里,每段价值依序为新车价的5/15、4/15、3/15、2/15、1/15。假设新车价12万元,已行驶7.5万公里(5年左右),那么该车估值为12万元×(3+3+2+1)÷15=7.2万元。

# 分成三段
bins = [0, 5, 10, 15]
num_data['kil_bin'] = pd.cut(num_data['kilometer'], bins, labels=False)

V系列特征的统计特征
平均值, 总和和标准差

v_features = ['v_' + str(i) for i in range(15)]
num_data['v_sum'] = num_data[v_features].apply(lambda x: x.sum(), axis=1)
num_data['v_mean'] = num_data[v_features].apply(lambda x: x.mean(), axis=1)
num_data['v_std'] = num_data[v_features].apply(lambda x: x.std(), axis=1)

这样,数值特征构造完毕,最终构造的结果特征如下:

通过上面的步骤,特征工程就完成了。简单的梳理一下, 基于二手车的背景信息进行特征构造的,首先是构造的时间特征, 构造了使用时间,是否报废,使用时间分箱, 是否淡旺季等特征;然后是类别特征,构造了邮编特征,是否私用,是否新能源,是否微型车,并且还提取了大量统计特征;最后是数值特征,给kilometer分箱, 对V特征进行了统计等。

最后我们合并一下所有的数据

final_data = pd.concat([num_data, cat_data, time_data], axis=1)
final_data.shape  # (200000, 74)
final_data.head()

特征构造后的最终数据:

最终有74个特征, 当然还可以更多,但是太多了也不是个好事,会造成冗余,特征相关等特征,对后面的模型造成一些负担。所以,还得进行特征筛选才可以,毕竟上面的74个特征不可能都是有用的,甚至有些还会造成负面影响。


4、特征选择

特征选择(排序)对于数据科学家、机器学习从业者来说非常重要。好的特征选择能够提升模型的性能,更能帮助我们理解数据的特点、底层结构,这对进一步改善模型、算法都有着重要作用。

但是拿到数据集,一个特征选择方法,往往很难同时完成这两个目的。通常情况下,我们经常不管三七二十一,选择一种自己最熟悉或者最方便的特征选择方法(往往目的是降维,而忽略了对特征和数据理解的目的), 但是真的好使吗?只能说具体问题具体分析,也许会暴力出奇迹呢。

特征选择主要有两个功能:

通常来说,从两个方面考虑来选择特征:

根据特征选择的形式又可以将特征选择方法分为3种:

首先,导入之前处理备份的数据,看一下构造的特征:

过滤式主要思想: 对每一维特征“打分”,即给每一维的特征赋予权重,这样的权重就代表着该特征的重要性,然后依据权重排序。先进行特征选择,然后去训练学习器,所以特征选择的过程与学习器无关。相当于先对特征进行过滤操作,然后用特征子集来训练分类器。

主要方法:

这里,本文为大家提供一些有价值的小tricks:

去掉取值变化小的特征
这应该是最简单的特征选择方法了:假设某特征的特征值只有0和1,并且在所有输入样本中,95%的实例的该特征取值都是1,那就可以认为这个特征作用不大。如果100%都是1,那这个特征就没意义了。

当特征值都是离散型变量的时候这种方法才能用,如果是连续型变量,就需要将连续变量离散化之后才能用,而且实际当中,一般不太会有95%以上都取某个值的特征存在,所以这种方法虽然简单但是不太好用。

可以把它作为特征选择的预处理,先去掉那些取值变化小的特征,然后再从接下来提到的的特征选择方法中选择合适的进行进一步的特征选择。例如,我们前面的seller和offerType特征。

# 对方差的大小排序
select_data.std().sort_values()    
# select_data是final_data去掉了独热的那些特征

根据这个,可以把方差非常小的特征作为备选的删除特征(备选,可别先盲目删除

单变量特征选择
单变量特征选择能够对每一个特征进行测试,衡量该特征和响应变量之间的关系,根据得分扔掉不好的特征。对于分类问题(y离散)可以采用卡方检验等方式对特征进行测试。

这种方法比较简单,易于运行,易于理解,通常对于理解数据有较好的效果(但对特征优化、提高泛化能力来说不一定有效);这种方法有许多改进的版本、变种。

下面重点介绍一下Pearson相关系数(适合y连续),皮尔森相关系数是一种最简单的,比较常用的方式。能帮助理解特征和响应变量之间关系的方法,该方法衡量的是变量之间的线性相关性,结果的取值区间为[-1,1],-1表示完全的负相关(这个变量下降,那个就会上升),+1表示完全的正相关,0表示没有线性相关。

Pearson Correlation速度快、易于计算,经常在拿到数据(经过清洗和特征提取之后的)之后第一时间就执行。Scipy的pearsonr方法能够同时计算相关系数和p-value, 当然pandas的corr也可以计算。

直接根据pearson系数画出图像:

corr = select_data.corr('pearson')    # .corr('spearman')
plt.figure(figsize=(25, 15))
corr['price'].sort_values(ascending=False)[1:].plot(kind='bar')
plt.tight_layout()

结果如下:

当然,这个数据用pearson系数可能不是那么合理,可以使用spearman系数,这个被认为是排列后的变量的pearson的相关系数, 具体的可以看(Pearson)皮尔逊相关系数和spearman相关系数, 这里只整理两者的区别和使用场景, 区别如下:

当然还可以画出热力图来,这个不陌生了吧, 这个的目的是可以看变量之间的关系, 相关性大的,可以考虑保留其中一个:

# 下面看一下互相之间的关系
f, ax = plt.subplots(figsize=(50, 30))
sns.heatmap(corr, annot=True)

结果如下:

从上面两个步骤中,就可以发现一些结论:

当然,依然是备选删除选项和备选保留选项(这些都先别做), 因为我们有时候不能盲目,就比如上面的相关性,明明知道pearson的缺陷是无法捕捉非线性相关,所以得出的这个结论也是片面的结论。这些都是备选,先做个心中有数,后面再用一些别的方式看看再说(如果现在就删除了,后面的方法就不好判断了)

包裹式单变量特征选择方法独立的衡量每个特征与响应变量之间的关系,另一种主流的特征选择方法是基于机器学习模型的方法。有些机器学习方法本身就具有对特征进行打分的机制,或者很容易将其运用到特征选择任务中,例如回归模型,SVM,决策树,随机森林等等。

主要思想:包裹式从初始特征集合中不断的选择特征子集,训练学习器,根据学习器的性能来对子集进行评价,直到选择出最佳的子集。包裹式特征选择直接针对给定学习器进行优化。

主要方法:递归特征消除算法, 基于机器学习模型的特征排序

优缺点:
优点:从最终学习器的性能来看,包裹式比过滤式更好;
缺点:由于特征选择过程中需要多次训练学习器,因此包裹式特征选择的计算开销通常比过滤式特征选择要大得多。

下面,这里整理基于学习模型的特征排序方法,这种方法的思路是直接使用你要用的机器学习算法,针对每个单独的特征和响应变量建立预测模型。其实Pearson相关系数等价于线性回归里的标准化回归系数。

假如某个特征和响应变量之间的关系是非线性的,可以用基于树的方法(决策树、随机森林)、或者扩展的线性模型等。基于树的方法比较易于使用,因为他们对非线性关系的建模比较好,并且不需要太多的调试。但要注意过拟合问题,因此树的深度最好不要太大,再就是运用交叉验证。

我们可以用随机森林来跑一下,看看随机森林比较喜欢特征:

from sklearn.model_selection import cross_val_score, ShuffleSplit
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import KFold

X = select_data.iloc[:, :-1]
Y = select_data['price']
names = select_data.columns

rf = RandomForestRegressor(n_estimators=20, max_depth=4)
kfold = KFold(n_splits=5, shuffle=True, random_state=7)
scores = []
for column in X.columns:
    print(column)
    tempx = X[column].values.reshape(-1, 1)
    score = cross_val_score(rf, tempx, Y, scoring="r2",
                              cv=kfold)
    scores.append((round(np.mean(score), 3), column))
print(sorted(scores, reverse=True))

这里对喜欢的特征排序并打分,结果如下:

这里就可以看出随机森林有用的特征排序,如果我们后面选择随机森林作为模型,就可以根据这个特征重要度选择特征。当然,如果你是xgboost,xgboost里面有个画特征重要性的函数,可以这样做:

# 下面再用xgboost跑一下
from xgboost import XGBRegressor
from xgboost import plot_importance

xgb = XGBRegressor()
xgb.fit(X, Y)

plt.figure(figsize=(20, 10))
plot_importance(xgb)
plt.show()

这样,直接把xgboost感兴趣的特征画出来:

最后,我们把上面的这两种方式封装起来, 还可以画出边际效应:

from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
# sfs = SFS(LinearRegression(), k_features=20, forward=True, floating=False, scoring='r2', cv=0)
sfs = SFS(RandomForestRegressor(n_estimators=10, max_depth=4), k_features=20, forward=True, floating=False, scoring='r2', cv=0)

X = select_data.iloc[:, :-1]
Y = select_data['price']

sfs.fit(X, Y)
sfs.k_feature_names_    # 随机森林放这里跑太慢了,所以中断了

结果如下:

画出边际效应:

from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs

fig1 = plot_sfs(sfs.get_metric_dict(), kind='std_dev')
plt.grid()
plt.show()

这个也是看选出的特征的重要性:

这样,根据我们使用的模型,我们可以对特征进行一个选择,综合上面的这几种方式,我们就可以把保留和删除的特征给选出来了,该删除的可以删除了。

如果真的这样尝试一下,就会发现保留的特征里面, v_std, v_3, used_time, power, kilometer, estivalue等这些特征都在,虽然我们不知道v系列特征的含义,但是汽车使用时间,发动机功率,行驶公里, 汽车使用时间的分箱特征其实对price的影响都是比较大的。

嵌入式在过滤式和包裹式特征选择方法中,特征选择过程与学习器训练过程有明显的分别。而嵌入式特征选择在学习器 训练过程中自动地进行特征选择。嵌入式选择最常用的是L1正则化与L2正则化。在对线性回归模型加入两种正则化方法后,他们分别变成了岭回归与Lasso回归。

主要思想:在模型既定的情况下学习出对提高模型准确性最好的特征。也就是在确定模型的过程中,挑选出那些对模型的训练有重要意义的特征。

主要方法:简单易学的机器学习算法–岭回归(Ridge Regression),就是线性回归过程加入了L2正则项。

L1正则化有助于生成一个稀疏权值矩阵,进而可以用于特征选择

L2正则化在拟合过程中通常都倾向于让权值尽可能小,最后构造一个所有参数都比较小的模型。因为一般认为参 数值小的模型比较简单,能适应不同的数据集,也在一定程度上避免了过拟合现象。可以设想一下对于一个线性 回归方程,若参数很大,那么只要数据偏移一点点,就会对结果造成很大的影响;但如果参数足够小,数据偏移 得多一点也不会对结果造成什么影响,专业一点的说法是『抗扰动能力强』

这里简单介绍一下怎么使用,其实和上面机器学习模型的使用方法一样, 所以有时候这些方法没有必要严格的区分开:

from sklearn.linear_model import LinearRegression, Ridge,Lasso

models = [LinearRegression(), Ridge(), Lasso()]
result = dict()

for model in models:
    model_name = str(model).split('(')[0]
    scores = cross_val_score(model, X=train_X, y=train_y, verbose=0, cv=5, scoring='r2')
    result[model_name] = scores

4、PCA降维技术

通过上面的特征选择部分,可以选出更好的分析特征,但是如果这些特征维度仍然很高怎么办?

如果数据特征维度太高,首先计算很麻烦,其次增加了问题的复杂程度,分析起来也不方便。这时候就会想是不是再去掉一些特征就好了呢?但是这个特征也不是凭自己的意愿去掉的,因为盲目减少数据的特征会损失掉数据包含的关键信息,容易产生错误的结论,对分析不利。

所以想找到一个合理的方式,既可以减少需要分析的指标,而且尽可能多的保持原来数据的信息,PCA就是这个合理的方式之一。 但要注意一点, 特征选择是从已存在的特征中选取携带信息最多的,选完之后的特征依然具有可解释性,而PCA,将已存在的特征压缩,降维完毕后不是原来特征的任何一个,也就是PCA降维之后的特征我们根本不知道什么含义了。

当然,针对这个比赛,也不打算使用PCA降维技术,因为如果做完了特征选择之后,就会发现特征的量不是那么多, 但笔者在这里用了一下,可以看看效果:

from sklearn.decomposition import PCA
# 然后使用
pca = PCA(n_components=10)
X_new = pca.fit_transform(X)

"""查看PCA的一些属性"""
print(X_new.shape)   # (200000, 10)
print(pca.explained_variance_)    # 属性可以查看降维后的每个特征向量上所带的信息量大小(可解释性方差的大小)
print(pca.explained_variance_ratio_)  # 查看降维后的每个新特征的信息量占原始数据总信息量的百分比
print(pca.explained_variance_ratio_.sum())    # 降维后信息保留量

上面假设保留了10个特征,然后运行代码,一下子就成了10维的矩阵, 我们可以看一下X_new

这些数已经只能说尽可能的保留原有的数据信息,但是是什么含义,就不得而知了。


转载:零基础入门数据挖掘:特征工程

上一篇 下一篇

猜你喜欢

热点阅读