本文为数据茶水间群友原创,经授权在本公众号发表。
关于作者:JunLiang,一个热爱挖掘的数据从业者,勤学好问、动手达人,期待与大家一起交流探讨机器学习相关内容~
数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。由此可见,特征工程在机器学习中占有相当重要的地位。在实际应用当中,可以说特征工程是机器学习成功的关键。
那特征工程是什么?
特征工程是利用数据领域的相关知识来创建能够使机器学习算法达到最佳性能的特征的过程。
特征工程又包含了 Data PreProcessing(数据预处理)、Feature Extraction(特征提取)、Feature Selection(特征选择)和 Feature construction(特征构造)等子问题,本章内容主要讨论特征构造的方法。
创造新的特征是一件十分困难的事情,需要丰富的专业知识和大量的时间。机器学习应用的本质基本上就是特征工程。 ——Andrew Ng
之前文章已经介绍了聚合特征构造和转换特征构造,接下来将介绍怎么进行笛卡尔乘积特征构造以及遗传编程特征构造。
笛卡尔乘积是指在数学中,两个集合X和Y的笛卡尓积( Cartesian product ),又称直积,表示为 X×Y ,第一个对象是X的成员而第二个对象是 Y 的所有可能有序对的其中一个成员。
假设集合 A={a, b} ,集合 B={0, 1, 2} ,则两个集合的笛卡尔积为 {(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}。
通过将单独的特征求笛卡尔乘积的方式来组合2个或更多个特征,从而构造出组合特征。
最终获得的预测能力将远远超过任一特征单独的预测能力。
例如,如果狗狗在下午5点主人下班回来时(快乐地)叫喊,可能表示对主人满意度的正面预测结果。如果狗狗在凌晨3点主人熟睡时(也许痛苦地)哀叫,可能表示对主人满意度的强烈负面预测结果。
这两个特征各自可以离散化为3维和2维的向量,对它们做笛卡尔乘积转化,就可以组合出长度为6的特征,它们分别对应着原始值对 (red, on),(red, off),(green, on),(green, off),(blue, on),(blue, off)。下面的矩阵表达方式更清楚地说明了这种组合。
X | on | off |
---|---|---|
red | ||
green | ||
blue |
对于三个特征的笛卡尔乘积组合,可以表达为立方的形式。更多特征的组合依次类推。这个方法也可以直接用于连续特征与类别特征之间的组合,只要把连续特征看成是一维的类别特征就好了,这时候组合后特征对应的值就不是 0/1 了,而是连续特征的取值。
样本 | color=red& light=on | color=red& light=off | color=green& light=on | color=green& light=off | color=blue& light=on | color=blue& light=off |
---|---|---|---|---|---|---|
1 | 1 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 1 | 0 | 0 | 0 | 0 |
3 | … | … | … | … | … | … |
color = ['on', 'on', 'off', 'off', 'off', ]
light = ['red', 'green', 'blue', 'red', 'green', ]
df = pd.DataFrame({'color': color, 'light': light})
display(df.head())
输出
color | light | |
---|---|---|
0 | on | red |
1 | on | green |
2 | off | blue |
3 | off | red |
4 | off | green |
def cartesian_product_feature_crosses(df, feature1_name, feature2_name):
feature1_df = pd.get_dummies(df[feature1_name], prefix=feature1_name)
feature1_columns = feature1_df.columns
feature2_df = pd.get_dummies(df[feature2_name], prefix=feature2_name)
feature2_columns = feature2_df.columns
combine_df = pd.concat([feature1_df, feature2_df], axis=1)
crosses_feature_columns = []
for feature1 in feature1_columns:
for feature2 in feature2_columns:
crosses_feature = '{}&{}'.format(feature1, feature2)
crosses_feature_columns.append(crosses_feature)
combine_df[crosses_feature] = combine_df[feature1] * combine_df[feature2]
combine_df = combine_df.loc[:, crosses_feature_columns]
return combine_df
combine_df = cartesian_product_feature_crosses(df, 'color', 'light')
display(combine_df.head())
输出
color_off& light_blue | color_off& light_green | color_off& light_red | color_on& light_blue | color_on& light_green | color_on& light_red | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 1 |
1 | 0 | 0 | 0 | 0 | 1 | 0 |
2 | 1 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 1 | 0 | 0 | 0 |
4 | 0 | 1 | 0 | 0 | 0 | 0 |
笛卡尔乘积组合特征方法一般应用于类别特征之间,连续值特征使用笛卡尔乘积组合特征时一般需要先进行离散化(离散化方法可以参考特征工程系列:特征预处理(上)中的《数值型特征特征分箱》相关内容),然后再进行特征组合。
binned_latitude(lat) = [
0 < lat <= 10
10 < lat <= 20
20 < lat <= 30
]
binned_longitude(lon) = [
0 < lon <= 15
15 < lon <= 30
]
样本 | 0<lat<=10& 0<lon<=15 | 0<lat<=10& 15<lon<=30 | 10<lat<=20& 0<lon<=15 | 10<lat<=20& 15<lon<=30 | 20<lat<=30& 0<lon<=15 | 20<lat<=30& 15<lon<=30 |
---|---|---|---|---|---|---|
1 | 1 | 0 | 0 | 0 | 0 | 0 |
2 | … | … | … | … | … | … |
遗传编程或称基因编程,简称GP,是一种从生物演化过程得到灵感的自动化生成和选择计算机程序来完成用户定义的任务的技术。从理论上讲,人类用遗传编程只需要告诉计算机“需要完成什么”,而不用告诉它“如何去完成”,最终可能实现真正意义上的人工智能:自动化的发明机器。
遗传编程是一种特殊的利用进化算法的机器学习技术,它开始于一群由随机生成的千百万个计算机程序组成的“人群”,然后根据一个程序完成给定的任务的能力来确定某个程序的适合度,应用达尔文的自然选择(适者生存)确定胜出的程序,计算机程序间也模拟两性组合,变异,基因复制,基因删除等代代进化,直到达到预先确定的某个终止条件为止。
遗传编程创造新特征是基于 genetic programming 的 symbolic regression(符号回归),具体的原理和实现可参考https://www.researchgate.net/publication/322795740_Symbolic_Regression。符号回归的具体实现方式是遗传算法(genetic algorithm)。一开始,一群天真而未经历选择的公式会被随机生成。此后的每一代中,最「合适」的公式们将被选中。随着迭代次数的增长,它们不断繁殖、变异、进化,从而不断逼近数据分布的真相。
当然我这里不是想尝试符号回归的机器学习方式,而是符号回归的机器学习方式提供了另外一种生成特征的思路。
目前,python 环境下最好用的基因编程库为gplearn。虽然遗传编程 (GP) 可以用来执行非常广泛的任务,但是 gplearn 有目的地约束了符号回归问题。
在手工特征工程中,我们会对两个或者多个特征进行一些加减乘除的操作,来生成一些特征,希望能够生成一些根据领域的先验知识,对金额特征, 日期特征进行比值操作生成一些特征,这些特征经常能够提升验证集和测试集的分数,在模型中也有很高的重要程度。
gplearn 这个库提供了解决的思路:
gplearn 的主要组成部分有两个:SymbolicRegressor 和 SymbolicTransformer 。两者的适应度有所不同。
此处,我们只关注怎样使用符号转换器来构造更多的特征,所以不对怎样使用符号回归器预测目标变量展开讨论。
把已有的特征进行组合转换,组合的方式(一元、二元、多元算子)可以由用户自行定义,也可以使用库中自带的函数(如加减乘除、min、max、三角函数、指数、对数)。组合的目的,是创造出和目标y值最“相关”的新特征。这种相关程度可以用spearman或者pearson的相关系数进行测量。spearman多用于决策树(免疫单特征单调变换),pearson多用于线性回归等其他算法。
# 数据集:波士顿数据集
# 训练Ridge模型
est = Ridge()
est.fit(boston.data[:300, :], boston.target[:300])
print(est.score(boston.data[300:, :], boston.target[300:]))
# 输出:0.759145222183
# 使用超过20代的2000人。选择最好的100个hall_of_fame,然后使用最不相关的10作为我们的新功能。因为我们使用线性模型作为估算器,所以这里使用默认值metric='pearson'。
function_set = ['add', 'sub', 'mul', 'div',
'sqrt', 'log', 'abs', 'neg', 'inv',
'max', 'min']
gp = SymbolicTransformer(generations=20, population_size=2000,
hall_of_fame=100, n_components=10,
function_set=function_set,
parsimony_coefficient=0.0005,
max_samples=0.9, verbose=1,
random_state=0, n_jobs=3)
gp.fit(boston.data[:300, :], boston.target[:300])
# 将新构造的特征拼接到原始数据上
gp_features = gp.transform(boston.data)
new_boston = np.hstack((boston.data, gp_features))
# 使用新的特征重新训练Ridge模型
est = Ridge()
est.fit(new_boston[:300, :], boston.target[:300])
print(est.score(new_boston[300:, :], boston.target[300:]))
# 输出:0.841750404385
以上实验可以知道,使用新构造的特征训练模型效果很显著。
预告:下一篇文章将介绍GBDT特征构造以及聚类特征构造。
[1] https://machinelearning-notes.readthedocs.io/zh_CN/latest/feature/%E7%89%B9%E5%BE%81%E5%B7%A5%E7%A8%8B%E2%80%94%E2%80%94%E6%97%B6%E9%97%B4.html [2] https://www.cnblogs.com/nxf-rabbit75/p/11141944.html#_nav_12 [3] https://gplearn.readthedocs.io/en/stable/examples.html#symbolic-classifier [4] 利用 gplearn 进行特征工程. https://bigquant.com/community/t/topic/120709 [5] Practical Lessons from Predicting Clicks on Ads at Facebook. https://pdfs.semanticscholar.org/daf9/ed5dc6c6bad5367d7fd8561527da30e9b8dd.pdf [6] Feature Tools:可自动构造机器学习特征的Python库. https://www.jiqizhixin.com/articles/2018-06-21-2 [7] 各种聚类算法的系统介绍和比较. https://blog.csdn.net/abc200941410128/article/details/78541273