<jupyter_start><jupyter_text>1 - 自动化特征工程```    描述：自动化机器学习的前提 --- 自动化特征工程。    作者：Chenyyx    时间：2020-01-09``````目录：    1 - 特征工程简要介绍    2 - 3种自动化特征工程的生成方法    3 - Featuretools 库使用规则        - 3.1 - 实体和实体集        - 3.2 - DFS        - 3.3 - 特征基元    4 - Featuretools 操作小实例    5 - 总结    6 - 参考链接``` 1 - 特征工程简要介绍**features（特征）** 是从数据中抽取出来对最终结果的预测有帮助的信息，**feature engineering（特征工程）** 则是特征在机器学习问题中使其算法和模型可以发挥更好的过程，该过程通常需要数据科学家根据经验找出最佳的特征组合形式，因为人的能力有限，所以找到的特征组合往往也不够全面，造成了效果和效率的局限性。而自动化特征工程可以根据数据特征进行自动组合，有效地解决了认为组合特征不全面和耗时的问题。特征工程是一个与具体场景绑定的事情，因此自动化特征工程应该是一件根据模型选择数据类型等背景信息并进行自动化的工作。如果把自动化理解为不需要人工参与设计，那么实现自动化的方式多种多样，最简单的方式为遍历搜索，通过计算机遍历所有的可能组合也是一种自动化；通过模型的方法去完成同样是一种自动化，如通过神经网络自动完成图像与文本等的特征工程。**特征工程也被称为特征构造，是从现有数据中构造新的特征从而训练机器学习模型的过程**。这一步可能比实际上使用的模型更重要，因为一个机器学习算法只能从我们给定的数据中学习，所以构造一个和任务相关的特征是至关重要的。因此，我们可以把自动化特征工程定义为如何根据具体场景去自动构建流程，而无需人工参与完成特征工程的一种方法。 2 - 3种自动化特征工程的生成方法自动化特征工程的生成方法，分别是深度特征合成算法，`FeatureTools` 自动特征提取，基于时序特征的自动化特征工程。 2.1 - 深度特征合成算法深度特征合成（Deep Feature Synthesis，DFS）是一种用于对关系数据和时间数据执行特征工程的自动方法。深度特征合成并不是通过深度学习生成特征，而是通过多重的特征叠加，一个特征的深度是构建这个特征所用到的基元的个数。关于 DFS 有如下 3 个关键的概念： - 特征主要来源是数据集中数据点之间的关系。 - 对于不同的数据集，许多特征的构建是通过相似的数学运算得到的。 - 新的特征是基于先前获取的特征构建的。 2.2 - Featuretools 自动特征提取Featuretools 是一个自动执行特征工程的开源库，它可以提高特征工程的效率，采用客观的方法来创建新的特征并将其用于机器学习的任务。Featuretools 将深度特征合成算法作为库的核心内容，以特征基元作为基本操作，通过叠加基元操作得到新的特征。 2.3 - 基于时序数据的自动化特征工程时序数据即时间序列数据，是同一指标按时间顺序记录的数据列。时序数据在数据分析和机器学习中具有很重要的意义，从时序数据中提取特征也是数据预处理中的关键一步。 TSFRESH 是一种兼容于 Pandas 和 scikit-learn 接口的 Python 包，用于提取时序数据特征。下面，我会主要介绍 `FeatureTools` 自动特征提取。其余两个简要介绍一下。下面会有对应的 `Python` 代码，记得结合使用规则一起查看。 3 - Featuretools 库使用规则`Featuretools` 是基于数据实体和实体时间的关系，基于 `DFS` 算法使用特征基元等操作来实现自动化的特征提取。 3.1 - 实体和实体集一个实体集（entity set）是实体（entity）和实体之间关系（relationship）的集合，在 Featuretools 中，实体集有利于准备和结构化数据集用作特征工程。原始的数据表一般以 DataFrame 的形式保存，每一张数据表都是一个实体，而每个实体都必须含有唯一元素的索引，用来区分不同的数据。我们需要为这些实体构建有利于 Featuretools 使用的实体集。 - 1 - 创建实体集 实体集的初始化可以使用 `ft.EntitySet(id="name")` 函数来构建一个新的实体集，如果需要对实体集命名，修改 `id` 参数即可对实体集命名。  - 2 - 添加实体 实体集创建成功后需要像实体集内添加实体，使用 `ft.entity_from_dataframe()` 函数卡伊通过修改以下参数，来实现对函数功能的选择：     - `entity_id`：设置添加实体的名字。     - `dataframe`：表。     - `index` 和 `time_index`：实体的索引。     - `variable_types`：字典可以对实体中的数据类型进行定义。 - 3 - 添加实体间的关系 最后我们需要添加实体集中各个实体之间的关系，通过 `ft.relationship()` 函数添加实体之间的关系，也可以通过 `EntitySet.add_relationship(new_relationship)` 来添加新的实体关系。 创建好实体集后就可以将实体集用于后续的特征提取。 3.2 - DFS如果不使用自动化特征工程，则可以直接运行 `DFS` 来生成特征，通过 `ft.dfs()` 函数来生成特征矩阵，`dfs` 函数具有以下参数： - `Entityset`：实体集名称。 - `target_entity`：实体集中的目标实体。 - `primitives`：选择使用的特征基元操作列表，这里的基元操作有两种，一种是聚合基元，一种是转换基元。 - `max_depth`：特征叠加深度。通过叠加产生的特征比通过单个特征基元产生的特征更加具有表现力，这就能够为机器学习模型提供更为复杂有效的特征。  3.3 - 特征基元特征基元是 `Featuretools` 用来自动构建特征的基础操作，通过单独使用或者叠加使用特征基元构造新的特征。使用特征基元的意义在于，只要限制输入和输出的数据类型，就可以在不同的数据集中采用相同的特征基元操作。特征基元有如下两种： - 聚合基元：根据父表与子表的关联，在不同的实体间完成对子表的统计操作。 - 转换基元：转换基元是对单个实体进行的操作，对实体的一个或者多个变量进行操作，并为该实体统计出一个新的变量。如果需要自动提取特征，只需要调用相同的 `ft.dfs()` 函数，但是不传入 `agg_primitives` 选择特征基元，就可以让 `Featuretools` 自动生成特征。除此之外，我们还可以通过 API 来定义自己的特征基元，确定特征基元的类型，定义输入和输出的数据类型，在 Python 中编写该特征基元的功能函数，就可以实现特征基元，并可以在和其他基元的叠加中使用。`Featuretools` 库以 `DFS` 为核心，通过叠加使用特征基元操作，能够构建大量有效的特征，为自动化特征工程提供了很大的帮助。 4 - Featuretools 操作小实例下面我们看一下怎么使用 `Featuretools` 这个工具库。<jupyter_code># 导入对应的库
import pandas as pd
import numpy as np
import featuretools as ft<jupyter_output><empty_output><jupyter_text>导入了对应的科学计算库，接下来我们加载数据，实验数据是我根据网上的一些数据，将大数据文件，只截取一部分变成小数据文件而来的。<jupyter_code># 加载数据
clients = pd.read_csv('data/clients.csv', parse_dates=['joined'])
loans = pd.read_csv('data/loans.csv', parse_dates=['loan_start', 'loan_end'])
payments = pd.read_csv('data/payments.csv', parse_dates=['payment_date'])
# 查看一下 clients 的前5行数据
clients.head()
# 查看一下 loans 的前5行数据
loans.head()
# 查看一下 payments 的前5行数据
payments.head()<jupyter_output><empty_output><jupyter_text>构造特征是一个非常耗时的过程，因为每个新的特征通常需要几步才能构造，特别是当使用多张表的信息时。我们可以将特征构造的操作分为两类：【转换】 和 【聚合】。后面我们通过例子来看看这些概念的实际应用。通过从一或多列中构造新的特征，【转换】作用于单张表（在 Python 中，表是一个 Pandas DataFrame）。举个例子，如有以上的 clients 表数据。我们可以通过查找 joined 列中的月份或是自然对数化 income 列的数据来构造新的特征。这些都是转换操作，因为它只使用了一张表的信息。另一方面，【聚合】是跨表实现的，并使用一对多的关联来对观测值分组，然后计算统计量。例如，仍然看上面的结果，若我们有另外一张包含客户贷款信息的表格，其中每个客户可能有多项贷款，我们便可以计算每个客户贷款的平均值，最大值和最小值等统计量。这个过程包括根据不同客户对贷款表进行分组并计算聚合后的统计量，然后将结果整合到客户数据中。实际上，这些操作本身并不困难，但是如果有数百个变量分布在数十张表中，这个过程将无法通过人工完成。理想情况下，我们希望有一个解决方案能够在不同表间自动执行转换和聚合操作，并将结果整合到一张表中。--------------------------------------------------------------------------------------幸运的是，Featuretools 正是我们正在寻找的解决方案。我们首先明确一下接下来我们要进行操作的数据信息： - clients：关于信用社客户的基本信息。每个客户只对应数据框中的一行。 - loans：向用户提供的贷款。每项贷款只对应数据框中的一行，但是客户可能有多项贷款。 - payments：贷款还本的支付。每笔支付只对应一行，但是每项贷款可以有多笔支付。如果我们有一个机器学习任务，例如预测客户未来是否会偿还一项贷款，我们希望将所有关于客户的信息整合到一张表中。这些表是相关的（通过 client_id 和 loan_id 变量），并且我们可以通过一系列转换和聚合操作来人工实现这个过程。然而，我们很快就可以使用特征工具来自动实现这个过程。使用特征工具的前两个概念是 【实体】 和 【实体集】。一个实体就是一张表（或是 Pandas 中的一个 DataFrame（数据框））。一个实体集是一组表以及它们之间的关联。将一个实体集看成另一种 Python 数据结构，并带有自己的方法和属性。现在我们需要整合两个实体。每个实体都必须带有一个索引，它是一个包含所有唯一元素的列。就是说，索引中的每个值只能在表中出现一次。在 clients 数据框中的索引是 client_id，因为每个客户在该数据框中只对应一行。我们使用 `entity_from_dataframe` 将一个带有索引的实体添加一个实体集中：<jupyter_code># 创建实体集，名称叫 clients
es = ft.EntitySet(id='clients')

# 添加 clients 实体
es = es.entity_from_dataframe(entity_id = 'clients', dataframe = clients, 
                              index = 'client_id', time_index = 'joined')<jupyter_output><empty_output><jupyter_text>loans 数据框中还有另外一个唯一的索引，loan_id，同时将其添加到实体集的语法与 clients 一样。然而，payments 数据框不存在唯一索引。当我们把 payments 数据框添加到实体集中时，我们需要传入参数 make_index=True，同时指定索引的名字。另外，尽管特征工具能自动推断实体中每列的数据类型，但是我们可以通过将列数据类型的字典传递给参数 variable_types 来覆盖它。<jupyter_code># 添加 loans 实体
es = es.entity_from_dataframe(entity_id = 'loans', dataframe = loans, 
                              variable_types = {'repaid': ft.variable_types.Categorical},
                              index = 'loan_id', 
                              time_index = 'loan_start')

# 添加 payments 实体
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')

# 添加完成了实体集，将实体集打印出来看看
es<jupyter_output><empty_output><jupyter_text>对于此数据框，尽管 missed 是一个整数，但是它不是一个数值变量，因为它只能取 2 个离散的数值，所以在特征工具中，将其看成一个分类变量。接下来，我们考虑两张表之间 【关联】的最好方法是类比父子之间的关联。这是一种一对多的关联：每个父亲可以有多个儿子。对表来说，每个父亲对应一张父表中的一行，但是子表中可能有多行对应于同一张父表中的多个儿子。例如，在我们的数据集中，clients 数据框是 loans 数据框的一张父表。每个客户只对应 clients 表中的一行，但是可能对应 loans 表中的多行。同样，loans 表是 payments 表的一张父表，因为每项贷款可以有多项支付。父亲通过共享变量与儿子相关联。当我们执行聚合操作的时候，我们根据父变量对子表进行分组，并计算每个父亲的儿子的统计量。为了形式化特征工具中的关联规则，我们仅需指定链接两张表的变量。clients 表和 loans 表通过 client_id 变量连接，同时 loans 和 payments 表通过 loan_id 变量连接。创建关联并将其添加到实体集中的语法如下所示：<jupyter_code># 添加实体关系
# 通过 client_id 关联 clients 和 loans 实体
r_client_previous = ft.Relationship(es['clients']['client_id'],
                                    es['loans']['client_id'])
# 将 relationship 加入到实体集中
es = es.add_relationship(r_client_previous)

# 通过 loan_id 关联 payments 和 loans 实体
r_payments = ft.Relationship(es['loans']['loan_id'],
                            es['payments']['loan_id'])

es = es.add_relationship(r_payments)

# 为实体集中的实体添加了关联，将实体集打印出来看看
es<jupyter_output><empty_output><jupyter_text>该实体集现在包含三个实体（表），以及将这些表连接在一起的关联规则。在添加实体和形式化关联规则之后，实体集就完整了并准备好从中构造新的特征。**特征基元**在我们深入了解深度特征合成之前，我们需要了解特征基元的概念。我们其实早就知道是什么了，只是我们刚刚用不同的名字来称呼它们！它们只是我们用来构造新特征的操作： - 聚合：根据父与子（一对多）的关联完成的操作，也就是根据父亲分组并计算儿子的统计量。一个例子就是根据 client_id 对 loan 表分组并找到每个客户的最大贷款额。 - 转换：对一张表中一或多列完成的操作。一个例子就是取一张表中两列之间的差值或者取一列的绝对值。在 featuretools 中单独使用这些基元或者叠加使用这些基元可以构造新的特征。以下是特征工具中的一些特征基元的列表，也可以自定义特征基元。![](imgs/auto_1.jpg)这些基元可以单独使用或者是组合使用以构造新的特征。为了使用特定的基元构造新的特征，我们使用 `ft.dfs` 函数（代表深度特征合成）。我们传入 `entityset` 和 `target_entity`，这是我们想要在其中添加特征的表，被选参数 `trans_primitives（转换）` 和 `agg_primitives（聚合）`。<jupyter_code># 聚合特征，并生成新的特征
features, feature_name = ft.dfs(entityset=es, target_entity='clients')
features.head()
# 查看一下所有 primitives 的操作，以便对下面的操作有个简单的了解
primitives = ft.list_primitives()
pd.options.display.max_colwidth = 100

primitives[primitives['type'] == 'aggregation'].head(30)
primitives[primitives['type'] == 'transform'].head(60)
# 聚合特征，通过制定聚合 agg_primitives 和转换 trans_primitives 生成新特征
features_2, feature_names_2 = ft.dfs(entityset=es, target_entity='clients', 
                                     agg_primitives=['mean', 'max', 'percent_true', 'last'],
                                    trans_primitives=['year', 'month', 'subtract_numeric', 'divide_numeric'])

features_2.head()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11217412102367855, 'fraction_numerical': 0.006936139679502512, 'mean_word_length': 6.51392632524708, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '509655', 'license': 'Apache-2.0', 'n_tokens_mistral': 5627, 'n_tokens_neox': 6220, 'n_words': 375, 'path': '1_auto_feature.ipynb', 'repo_name': 'chenyaoysu/auto_ml_dl', 'size': '8362'}