[译] 使用 Python 进行自动化特征工程

Python 中的特征工程自动化

如何自动化地创建机器学习特征

机器学习正在利用诸如 H20TPOTauto-sklearn 等工具越来越多地从手工设计模型向自动化优化管道迁移。以上这些类库,连同如 random search 等方法一起,目的是在不需要人工干预的情况下找到适合于数据集的最佳模型,以此来简化器学习的模型选择和调优部分。然而,特征工程,作为机器学习管道中一个可以说是更有价值的方面,几乎全部是手工活。

特征工程,也称为特征创建,是从已有数据中创建出新特征并且用于训练机器学习模型的过程。这个步骤可能要比实际使用的模型更加重要,因为机器学习算法仅仅从我们提供给他的数据中进行学习,创建出与任务相关的特征是非常关键的(可以参照这篇文章 "A Few Useful Things to Know about Machine Learning" —— 《了解机器学习的一些有用的事》,译者注)。

通常来说,特征工程是一个漫长的手工过程,依赖于某个特定领域的知识、直觉、以及对数据的操作。这个过程可能会非常乏味并且最终获得的特性会被人类的主观性和花在上面的时间所限制。自动特征工程的目标是通过从数据集中创建许多候选特征来帮助数据科学家减轻工作负担,从这些创建了候选特征的数据集中,数据科学家可以选择最佳的特征并且用来训练。

在这篇文章中,我们将剖析一个基于 featuretools Python library 库进行自动特征工程处理的案例。我们将使用一个样例数据集来展示基本信息(请继续关注未来的使用真实数据的文章)。这篇文章最终的代码可以在 GitHub 获取。


特征工程基础

特征工程意味着从分布在多个相关表格中的现有数据集中构建出额外的特征。特征工程需要从数据中提取相关信息,并且将其放入一个单独的表中,然后可以用来训练机器学习模型。

构建特征的过程非常耗时,因为每获取一项新的特征都需要很多步骤才能构建出来,尤其是当需要从多于一张表格中获取信息时。我们可以把特征创建的操作分成两类:转换聚合。让我们通过几个例子的实战来看看这些概念。

一次转换操作仅作用于一张表,该操作能从一个或多个现有列中创建新特征(比如说 Python 中,一张表就如同 Pandas 库中的一个 DataFrame)。如下面的例子所示,假如我们有如下的一张客户(clients)信息表:

我们可以通过从 joined 列中寻找出月份或者对 income 列取自然对数来创建特征。这些都是转换的范畴,因为他们都是使用了单张表中的信息。

另一方面,聚合 则是跨表执行的,其使用了一对多关系进行分组观察,然后再计算统计数据。比如说,如果我们还有另外一张含有客户贷款信息的表格,这张表里可能每个客户都有多种贷款,我们就可以计算出每位客户端诸如贷款平均值、最大值、最小值等统计数据。

这个过程包括了根据客户进行贷款表格分组、计算聚合、然后把计算结果数据合并到客户数据中。如下代码展示了我们如何使用 Python 中的 language of Pandas 库进行计算的过程:

import pandas as pd

# 根据客户 id (client id)进行贷款分组,并计算贷款平均值、最大值、最小值
stats = loans.groupby('client_id')['loan_amount'].agg(['mean', 'max', 'min'])
stats.columns = ['mean_loan_amount', 'max_loan_amount', 'min_loan_amount']

# 和客户的 dataframe 进行合并
stats = clients.merge(stats, left_on = 'client_id', right_index=True, how = 'left')

stats.head(10)
复制代码

这些操作本身并不困难,但是如果我们有数百个变量分布在数十张表中,手工进行操作则是不可行的。理想情况下,我们希望有一种解决方案,可以在多个表格当中进行自动转换和聚合操作,最后将结果数据合并到一张表格中。尽管 Pandas 是一个很优秀的资源库,但利用 Pandas 时我们仍然需要手工操作很多的数据!(更多关于手工特征工程的信息可以查看如下这个杰出的著作 Python Data Science Handbook)。

Featuretools 框架

幸运的是, featuretools 正是我们所寻找的解决方案。这个开源的 Python 库可以自动地从一系列有关联的表格中创建出很多的特征。 Featuretools 是基于一个被称为 "Deep feature synthesis" (深度特征合成)的方法所创建出来的,这个方法听起来要比实际跑起来更加令人印象深刻。(这个名字是来自于多特征的叠加,并不是因为这个方法使用了深度学习!)

深度特征合成叠加了多个转换和聚合操作(在 feautretools 中也被称为 feature primitives (特征基元))来从遍布很多表格中的数据中创建出特征。如同绝大多数机器学习中的想法一样,这是一种建立在简单概念基础上的复杂方法。通过一次学习一个构建模块,我们可以很好地理解这个强大的方法。

首先,让我们看看我们的数据。之前我们已经看到了一些数据集,完整的表集合如下所示:

  • clients : 客户在信用社的的基本信息。每个客户在这个 dataframe 中仅占一行

  • loans: 给客户的贷款。每个贷款在这个 dataframe 中仅占一行,但是客户可能会有多个贷款

  • payments: 贷款偿还。每个付款只有一行,但是每笔贷款可以有多笔付款。

如果我们有一件机器学习任务,例如预测一个客户是否会偿还一个未来的贷款,我们将把所有关于客户的信息合并到一个表格中。这些表格是相互关联的(通过 client_idloan_id 变量),我们可以使用一系列的转换和聚合操作来手工完成这一过程。然而,我们很快就将看到,我们可以使用 featuretools 来自动化这个过程。

实体和实体集

对于 featuretools 来说,最重要的两个概念是实体实体集。一个实体就只是一张表(或者说一个 Pandas 中的 DataFrame) 。一个实体集是一系列表的集合以及这些表格之间的关系。你可以把实体集认为是 Python 中的另外一个数据结构,这个数据结构有自己的方法和参数。

我们可以在 featuretools 中利用下面的代码创建出一个空的实体集:

import featuretools as ft

# 创建新实体集  
es = ft.EntitySet(id = 'clients')
复制代码

现在我们必须添加一些实体。每个实体必须有一个索引,它是一个包含所有唯一元素的列。也就是说,索引中的每个值必须只出现在表中一次。clients dataframe 中的索引是 client_id ,因为每个客户在这个 dataframe 中只有一行。我们使用以下语法向实体集添加一个已经有索引的实体:

# 从客户 dataframe 中创建出一个实体
# 这个 dataframe 已经有一个索引和一个时间索引
es = es.entity_from_dataframe(entity_id = 'clients', dataframe = clients, 
                              index = 'client_id', time_index = 'joined')
复制代码

loans datafram 同样有一个唯一的索引,loan_id 以及向实体集添加 loan_id 的语法和 clients 一样。然而,对于 payments dataframe 来说,并不存在唯一的索引。当我们向实体集添加实体时,我们需要把参数 make_index 设置为 True( make_index = True ),同时为索引指定好名称。此外,虽然 featuretools 会自动推断实体中的每个列的数据类型,我们也可以将一个列类型的字典传递给参数 variable_types 来进行数据类型重写。

# 从付款 dataframe 中创建一个实体
# 该实体还没有一个唯一的索引
es = es.entity_from_dataframe(entity_id = 'payments', 
                              dataframe = payments,
                              variable_types = {'missed': ft.variable_types.Categorical},
                              make_index = True,
                              index = 'payment_id',
                              time_index = 'payment_date')
复制代码

对于这个 dataframe 来说,即使 missed 是一个整型数据,这不是一个数值变量,因为它只能接受两个离散值,所以我们告诉 featuretools 将它是为一个分类变量。在向实体集添加了 dataframs 之后,我们将检查其中的任何一个:

我们指定的修改可以正确地推断列类型。接下来,我们需要指定实体集中的表是如何进行关联的。

表关系

考虑两个表之间的关系的最佳方式是父亲与孩子的类比。这是一对多的关系:每个父亲可以有多个孩子。在表领域中,父亲在每个父表中都有一行,但是子表中可能有多个行对应于同一个父亲的多个孩子。

例如,在我们的数据集中,clients dataframe 是 loans dataframe 的父亲。每个客户在 clients 中只有一行,但在 loans 中可能有多行。同样, loanspayments 的父亲,因为每笔贷款都有多个支付。父亲通过共享变量与孩子相连。当我们执行聚合时,我们将子表按父变量分组,并计算每个父表的子表的统计信息。

在 featuretools 中格式化关系,我们只需指定将两个表链接在一起的变量。 clientsloans 表通过 loan_id 变量链接, loanspayments 通过 loan_id 联系在一起。创建关系并将其添加到实体集的语法如下所示:

# 客户与先前贷款的关系
r_client_previous = ft.Relationship(es['clients']['client_id'],
                                    es['loans']['client_id'])

# 将关系添加到实体集
es = es.add_relationship(r_client_previous)

# 以前的贷款和以前的付款之间的关系
r_payments = ft.Relationship(es['loans']['loan_id'],
                                      es['payments']['loan_id'])

# 将关系添加到实体集
es = es.add_relationship(r_payments)

es
复制代码

实体集现在包含三个实体(或者说是表)和连接这些实体的关系。在添加实体和对关系形式化之后,我们的实体集就准备完成了,我们接下来可以准备创建特征。

特征基元

在深入了解特性合成之前,我们需要了解特征基元。我们已经知道它们是什么了,但是我们只是用不同的名字称呼它们!这些是我们用来形成新特征的基本操作:

  • 聚合:通过父节点对子节点(一对多)关系完成的操作,并计算子节点的统计信息。一个例子是通过 client_idloan 表分组,并为每个客户机找到最大的贷款金额。
  • 转换:在单个表上对一个或多个列执行的操作。举个例子,取一个表中两个列之间的差值,或者取列的绝对值。

新特性是在 featruetools 中创建的,使用这些特征基元本身或叠加多个特征基元。下面是 featuretools 中的一些特征基元列表(我们还可以定义自定义特征基元

特征基元

这些基元可以自己使用或组合来创建特征。要使用指定的基元,我们使用 ft.dfs 函数(代表深度特征合成)。我们传入 实体集目标实体(这两个参数是我们想要加入特征的表)以及 trans_primitives 参数(用于转换)和 agg_primitives 参数(用于聚合):

# 使用指定的基元创建新特征
features, feature_names = ft.dfs(entityset = es, target_entity = 'clients', 
                                 agg_primitives = ['mean', 'max', 'percent_true', 'last'],
                                 trans_primitives = ['years', 'month', 'subtract', 'divide'])
复制代码

以上函数返回结果是每个客户的新特征 dataframe (因为我们把客户定义为目标实体)。例如,我们有每个客户加入的月份,这个月份是一个转换特性基元:

我们还有一些聚合基元,比如每个客户的平均支付金额:

尽管我们只指定了很少一部分的特征基元,但是 featuretools 通过组合和叠加这些基元创建了许多新特征。

完整的 dataframe 有793列新特性!

深度特征合成

现在,我们已经准备好了理解深度特征合成(deep feature synthesis, dfs)的所有部分。事实上,我们已经在前面的函数调用中执行了 dfs 函数!深度特性只是将多个特征基元叠加的特性,而 dfs 是生成这些特性的过程的名称。深度特征的深度是创建该特性所需的特征数量。

例如,MEAN(payments.payment_amount) 列是一个深度为 1 的特征,因为它是使用单个聚合创建的。深度为 2 的特征是 LAST(loans(MEAN(payments.payment_amount)) ,这是通过叠加两个聚合而成的: LAST(most recent) 在均值之上。这表示每个客户最近一次贷款的平均支付金额。

我们可以将特征叠加到任何我们想要的深度,但是在实践中,我从来没有超过 2 的深度。在这之后,这些特征就很难解释了,但我鼓励有兴趣的人尝试“深入研究”


我们不必手工指定特征基元,而是可以让 featuretools 自动为我们选择特性。为此,我们使用相同的 ft.dfs 函数调用,但不传递任何特征基元:

# 执行深度特征合成而不指定特征基元。
features, feature_names = ft.dfs(entityset=es, target_entity='clients', 
                                 max_depth = 2)

features.head()
复制代码

Featuretools 已经为我们构建了许多新的特征供我们使用。虽然这个过程会自动创建新特征,但它不会取代数据科学家,因为我们仍然需要弄清楚如何处理所有这些特征。例如,如果我们的目标是预测客户是否会偿还贷款,我们可以查找与特定结果最相关的特征。此外,如果我们有特殊领域知识,我们可以使用它来选择具有候选特征的特定特征基元或种子深度特征合成

接下来的步骤

自动化的特征工程解决了一个问题,但却创造了另一个问题:创造出太多的特征。虽然说在确定好一个模型之前很难说这些特征中哪些是重要的,但很可能并不是所有的特征都与我们想要训练的任务相关。而且,拥有太多特征可能会让模型的表现下降,因为在训练的过程中一些不太有用的特征会淹没那些更为重要的特征。

太多特征的问题被称为维数的诅咒。随着特征数量的增加(数据的维数增加),模型越来越难以了解特征和目标之间的映射。事实上,模型执行良好所需的数据量(与特性的数量成指数比例)(stats.stackexchange.com/a/65380/157…)。

可以化解维数诅咒的是特征削减(也称为特征选择):移除不相关特性的过程。这可以采取多种形式:主成分分析(PCA),使用 SelectKBest 类,使用从模型引入的特征,或者使用深度神经网络进行自动编码。当然,特征削减则是另一篇文章的另一个主题了。现在,我们知道,我们可以使用 featuretools ,以最少的工作量从许多表中创建大量的特性!

结论

像机器学习领域很多的话题一样,使用 feautretools 的自动特征工程是一个建立在简单想法之上的复杂概念。使用实体集、实体和关系的概念,feautretools 可以执行深度特性合成来创建新特征。深度特征合成反过来又将特征基元堆叠起来 —— 也就是聚合,在表格之间建立起一对多的关系,同时进行转换,在单表中对一列或者多列应用,通过这些方法从很多的表格中构建出新的特征出来。

请持续关注这篇文章,与此同时,阅读关于这个竞赛的介绍 this introduction to get started。我希望您现在可以使用自动化特征工程作为数据科学管道中的辅助工具。我们的模型将和我们提供的数据一样好,自动化的特征工程可以帮助使特征创建过程更有效。

要获取更多关于特征工具的信息,包括这些工具的高级用法,可以查阅在线文档。要查看特征工具如何在实践中应用,可以参见 Feature Labs 的工作成果,这就是开发 featuretools 这个开源库的公司。

我一如既往地欢迎各位的反馈和建设性的批评,你们可以在 Twitter @koehrsen_will 上与我进行交流。

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

猜你喜欢

转载自juejin.im/post/5b6ea0e4e51d4519044adff0