特征工程究竟是什么呢?顾名思义,其本质是一项工程活动,目的是最大限度地从原始数据中提取特征以供算法和模型使用。git
此以前,我已经写了如下几篇AI基础的快速入门,本篇文章讲解特征工程基础第一部分:(类别特征)。github
已发布:算法
AI 基础:Python 简易入门apache
AI 基础:Numpy 简易入门json
AI 基础:Pandas 简易入门app
AI 基础:Scipy(科学计算库) 简易入门机器学习
AI基础:数据可视化简易入门(matplotlib和seaborn)分布式
后续持续更新ide
参考资料:函数
[1]原版(英文)图书地址:
https://www.oreilly.com/library/view/feature-engineering-for/9781491953235/
[2]翻译来源apachecn:
https://github.com/apachecn
[3]翻译做者@ZhenLeiXu:
代码修改和整理:黄海广,原文修改为jupyter notebook格式,并增长和修改了部分代码,测试所有经过,全部数据集已经放在百度云下载。
本文代码能够在github下载:
https://github.com/fengdu78/Data-Science-Notes/tree/master/9.feature-engineering
数据集的百度云:
连接:https://pan.baidu.com/s/1uDXt5jWUOfI0fS7hD91vBQ 提取码:8p5d
一个类别特征,见名思义,就是用来表达一种类别或标签。好比,一个类别特征可以表达世界上的主要城市,一年四季,或者说一个公司的产品(石油、路程、技术)。在真实世界的数据集中,类别值的数量老是无限的。同时这些值通常能够用数值来表示。可是,与其余数值变量不同的是,类别特征的数值变量没法与其余数值变量进行比较大小。(做为行业类型,石油与旅行没法进行比较)它们被称之为非序的。
一个简单的问题能够做为测试是否应该是一个分类变量的试金石测试:“两个价值有多么不一样,或者只是它们不一样?”500美圆的股票价格比100美圆的价格高5倍。因此股票价格应该用一个连续的数字变量表示。另外一方面,公司的产业(石油,旅游,技术等)应该没法被比较的,也就是类别特征。
大的分类变量在交易记录中特别常见。对于实例中,许多Web服务使用id做为分类变量来跟踪用户具备数百至数百万的值,取决于惟一的数量服务的用户。互联网交易的IP地址是另外一个例子一个很大的分类变量。它们是分类变量,由于即便用户ID和IP地址是数字,它们的大小一般与任务无关在眼前。例如,在进行欺诈检测时,IP地址多是相关的我的交易。某些IP地址或子网可能会产生更多欺骗性交易比其余人。可是164.203.x.x的子网本质上并很少欺诈性比164.202.x.x; 子网的数值可有可无。
文档语料库的词汇能够被解释为一个大的分类变量,类别是惟一的单词。它可能在计算上很昂贵表明如此多的不一样类别。若是一个类别(例如,单词)出现多个数据点(文档)中的时间,而后咱们能够将它表示为一个计数并表示全部的类别经过他们的统计数字。这被称为bin-counting。咱们用分类变量的共同表示开始讨论,而且最终蜿蜒曲折地讨论了大范围的bin-counting问题变量,这在现代数据集中很是广泛。
分类变量的类别一般不是数字。例如,眼睛的颜色能够是“黑色”,“蓝色”,“棕色”等。所以,须要使用编码方法将这些非数字类别变为数字。简单地将一个整数(好比1到k)分配给k个可能的类别中的每个都是诱人的。可是,由此产生的价值观能够互相受权,这在类别中不该该被容许。
将类别特征进行表示一个最好的办法就是使用一组比特位来表达。每一位表明一个可能的类别。若是该变量不能一次成为多个类别,那么该组中只有一位能够是1。这被称为独热编码,它在Scikit Learn中实现sklearn.preprocessing.OneHotEncoder。每一个位都是一个特征。所以是一个绝对的具备k个可能类别的变量被编码为长度为k的特征向量。
表1-1 对3个城市的类别进行独热编码
City | e1 | e2 | e3 |
---|---|---|---|
San Francisco | 1 | 0 | 0 |
New York | 0 | 1 | 0 |
Seattle | 0 | 0 | 1 |
独热编码很是易于理解。但它使用的是比严格必要的更多的一点。若是咱们看到k-1位是零,那么最后一位必须是1,由于变量必须具备k个值中的一个。在数学上,能够写下这个约束条件为“全部位的和必须等于1”。
等式 5-1. 独热编码e1,e2,e3限制条件。
所以,咱们有一个线性的依赖性。线性相关特征,就像咱们同样在tfidf中发现,有点烦人,由于它意味着训练线性模型不会是惟一的。特征的不一样线性组合能够作出一样的预测,因此咱们须要跳过额外条件的来理解特征对预测的影响。
独热编码的问题是它容许
个自由度,其中变量自己只须要。虚拟编码经过仅使用表示中的
个特征来消除额外的自由度。
公共汽车下面有一个特征,由全零向量表示。这被称为参考类别。虚拟编码和独热编码都是在Pandas中以pandas.get_dummies的形式实现的。
表1-2 对3个城市的类别进行dummy编码
图1-1 公寓租金价格在one-hot编码中的向量空间表示。点的大小表达了数据集中租金不一样价格的平均数。
咱们这时可以仅仅依靠城市这一个变量来创建线性回归来预测租金的价格。
线性回归模型能够这样写
例1-1.在独热编码上的线性回归
import pandas as pd from sklearn import linear_model df = pd.DataFrame({ 'City': ['SF', 'SF', 'SF', 'NYC', 'NYC', 'NYC', 'Seattle', 'Seattle', 'Seattle'], 'Rent': [3999, 4000, 4001, 3499, 3500, 3501, 2499, 2500, 2501] }) df['Rent'].mean()
3333.3333333333335
one_hot_df = pd.get_dummies(df, prefix=['city']) one_hot_df
model = linear_model.LinearRegression() model.fit(one_hot_df[['city_NYC', 'city_SF', 'city_Seattle']], one_hot_df[['Rent']]) model.coef_
array([[ 166.66666667, 666.66666667, -833.33333333]])
model.intercept_
array([3333.33333333])
使用dummy code进行回归
dummy_df = pd.get_dummies(df, prefix=['city'], drop_first=True) dummy_df
model.fit(dummy_df[['city_SF', 'city_Seattle']], dummy_df['Rent'])
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,
normalize=False)
model.coef_
array([ 500., -1000.])
model.intercept_
3500.0
经过独热编码,截距项表示目标变量的全局均值租金价格,而且每一个线性系数表示该城市的平均租金与全局平均值的差别。
经过虚拟编码,误差系数表明响应的平均值参考类别的变量y,在这个例子中是纽约市。该第i个特征的系数等于平均响应之间的差别第i类别的值和参考类别的平均值。
表1-4:线性回归学得的系数
id | x1 | x2 | x3 | b |
---|---|---|---|---|
one-hot | 166.67 | 666.67 | -833.33 | 3333.33 |
dummy | coding | 0 | 500 -1000 | 3500 |
分类变量编码的另外一种变体称为Effect编码。Effect编码与虚拟编码很是类似,区别在于参考类别如今由全部-1的向量表示。
表1-5: Effect编码表示3个城市
City | e1 | e2 |
---|---|---|
San Francisco | 1 | 0 |
New York | 0 | 1 |
Seattle | -1 | -1 |
Effect编码与虚拟编码很是类似,可是在线性回归中更容易被拟合。例1-2表达了运行机理。截距项表示目标的全球平均值变量,单个系数表示各个类别的平均值与全球平均值有多少差别。(这被称为类别或级别的主要效果,所以名称为“效果编码”。)独热编码实际上具备相同的截距和系数,但在这种状况下,每一个城市都有线性系数。在效果编码中,没有单一特征表明参考类别。所以,参考类别的影响须要分别计算为全部其余类别的系数的负和。(查看what is effect coding?)
例1-2 Effect编码的线性回归
effect_df = dummy_df.copy() effect_df.loc[3:5, ['city_SF', 'city_Seattle']] = -1.0 effect_df
Rent | city_SF | city_Seattle | |
---|---|---|---|
0 | 3999 | 1.0 | 0.0 |
1 | 4000 | 1.0 | 0.0 |
2 | 4001 | 1.0 | 0.0 |
3 | 3499 | -1.0 | -1.0 |
4 | 3500 | -1.0 | -1.0 |
5 | 3501 | -1.0 | -1.0 |
6 | 2499 | 0.0 | 1.0 |
7 | 2500 | 0.0 | 1.0 |
8 | 2501 | 0.0 | 1.0 |
model.fit(effect_df[['city_SF', 'city_Seattle']], effect_df['Rent'])
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,
normalize=False)
model.coef_
array([ 666.66666667, -833.33333333])
model.intercept_
3333.3333333333335
独热,虚拟和效果编码很是类似。他们每一个人都有优势和缺点。独热编码是多余的,它容许多个有效模型同样的问题。非惟一性有时候对解释有问题。该优势是每一个特征都明显对应于一个类别。此外,失踪数据能够编码为全零矢量,输出应该是总体目标变量的平均值。
虚拟编码和效果编码不是多余的。他们产生独特和可解释的模型。虚拟编码的缺点是它不能轻易处理缺乏数据,由于全零矢量已经映射到参考类别。它还编码每一个类别相对于参考类别的影响,其中看起来很奇怪。效果编码经过使用不一样的代码来避免此问题参考类别。可是,全部-1的矢量都是一个密集的矢量,对于存储和计算来讲都很昂贵。所以,Pandas和Scikit Learn等流行的ML软件包选择了虚拟编码或独热编码,而不是效应编码。当类别数量变得很是多时,全部三种编码技术都会失效大。须要不一样的策略来处理很是大的分类变量。
互联网上的自动数据收集能够生成大量的分类变量。这在诸如定向广告和欺诈检测等应用中很常见。在有针对性的广告中,任务是根据用户的搜索查询或当前页面将用户与一组广告进行匹配。功能包括用户ID,广告的网站域,搜索查询,当前页面以及这些功能的全部可能的成对连词。(查询是一个文本字符串,能够切分红经常使用的文本特征,但查询一般很短,一般由短语组成,所以在这种状况下最好的行为一般是保持完整,或 经过哈希函数来简化存储和比较,咱们将在下面更详细地讨论哈希。)其中每个都是一个很是大的分类变量。咱们面临的挑战是如何找到一个可以提升内存效率的优秀特征表示,并生成训练速度快的准确模型。
对于这种类别特征处理的方案有:
对编码不作任何事情。使用便宜的训练简单模型。在许多机器上将独热编码引入线性模型(逻辑回归或线性支持向量机)。
压缩编码,有两种方式
a. 对特征进行哈希--在线性回归中特别常见
b. bin-counting--在线性回归中与树模型都常见
使用one-hot编码是可行的。在微软搜索广告研究中,Graepel等人 [2010]报告在贝叶斯几率回归模型中使用这种二值特征,可使用简单更新在线进行培训。与此同时,其余组织则争论压缩方法。来自雅虎的研究人员 经过特征散列方式[Weinberger et al.2009年]。尽管McMahan等人[2013]在谷歌的广告引擎上尝试了功能哈希,并无找到显着的改进。然而,微软的其余人则被认为是计数[Bilenko,2015]。
咱们将会看到,全部这些想法都有利有弊。咱们将首先描述解决方案自己,而后讨论他们的权衡。
散列函数是一个肯定性函数,它映射一个潜在的***整数到有限整数范围
,。因为输入域可能大于输出范围,多个数字可能会映射到相同的输出。这被称为a碰撞。统一的散列函数可确保大体相同数量的数字被映射到每一个
箱。在视觉上,咱们能够将散列函数视为一台机器能够吸入编号的球并将它们传送到一个m箱。球与相同的号码将始终被路由到同一个bin。
散列函数能够为任何能够用数字表示的对象构造(对于能够存储在计算机上的任何数据都是如此):数字,字符串,复杂的结构等。
图1-2 哈希编码
当有不少特征时,存储特征向量可能占用不少空间。特征散列将原始特征向量压缩为m维经过对特征ID应用散列函数来建立矢量。例如,若是原件特征是文档中的单词,那么散列版本将具备固定的词汇大小为m,不管输入中有多少独特词汇。
例1-3 对单词的特征哈希
def hash_features(word_list, m): output = [0] * m for word in word_list: index = hash_fcn(word) % m output[index] += 1 return output
功能散列的另外一个变体添加了一个符号组件,所以计数也是从哈希箱中增长或减小。这确保了内部产品之间散列特征与原始特征的指望值相同。
def hash_features(word_list, m): output = [0] * m for word in word_list: index = hash_fcn(word) % m sign_bit = sign_hash(word) % 2 if (sign_bit == 0): output[index] -= 1 else: output[index] += 1 return output
哈希后内积的值在时间复杂度在O(1/(m**0.5)).因此哈希表m的大小能够根据可接受的错误来选择。在实践中,选择合适的m可能须要一些试验和错误。特征哈希能够用于涉及特征内积的模型矢量和系数,例如线性模型和核心方法。它一直证实在垃圾邮件过滤任务中取得成功[Weinberger等,2009]。在有针对性的广告案例中,McMahan et al. [2013年]报告不能将预测偏差下降到可接受的水平,除非m的数量级为数十亿。散列特征的一个缺点是散列特征是聚合的原始特征,再也不可解释。
在这个例子中,咱们将使用Yelp评论数据集来演示存储和,解释性使用的为sklearn的库FeatureHasher。
import pandas as pd import json js = [] with open('data/yelp_academic_dataset_review.json') as f: for i in range(10000): js.append(json.loads(f.readline())) review_df = pd.DataFrame(js) m = len(review_df.business_id.unique())
m
4174
from sklearn.feature_extraction import FeatureHasher h = FeatureHasher(n_features=m, input_type='string') f = h.transform(review_df['business_id'])
review_df['business_id'].unique().tolist()[0:5]
['9yKzy9PApeiPPOUJEtnvkg',
'ZRJwVLyzEJq1VAihDhYiow',
'6oRAC4uyJCsJl1X0WZpVSA',
'_1QQZuf4zZOyFCvXc0o6Vg',
'6ozycU1RpktNG2-1BroVtw']
f.toarray()
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]])
咱们看看特征的存储
from sys import getsizeof
print('Our pandas Series, in bytes: ', getsizeof(review_df['business_id'])) print('Our hashed numpy array, in bytes: ', getsizeof(f))
Our pandas Series, in bytes: 790104
Our hashed numpy array, in bytes: 56
咱们能够清楚地看到如何使用特征散列会以计算方式使咱们受益,牺牲直接的用户解释能力。这是一个容易的权衡来接受什么时候从数据探索和可视化发展到机器学习管道对于大型数据集。
Bin-counting是机器学习中常见的从新发现之一。从广告点击率预测到硬件分支预测,它已经被从新建立并用于各类应用[Yeh and Patt,1991; Lee等人,1998; Pavlov等,2009; 李等人,2010]。然而,由于它是一种特征工程技术,而不是一种建模或优化方法,因此没有关于该主题的研究论文。关于该技术最详细的描述能够在Misha Bilenko的博客文章“Big Learning Made with Easy”以及相关的幻灯片中找到。
bin-counting的想法很是简单:而不是使用分类变量做为特征,而不是使用条件几率的目标在该价值下。换句话说,而不是编码的身份分类值,计算该值和该值之间的关联统计量咱们但愿预测的目标。对于那些熟悉Na?veBayes分类器的人来讲,这个统计学应该敲响一下钟,由于它是该类的条件几率假设全部功能都是独立的。最好用一个例。
表1-6. bin-counting的例子
Bin-counting假定历史数据可用于计算统计。表1-6包含分类变量每一个可能值的汇总历史计数。根据用户点击任何广告的次数以及未点击的次数,咱们能够计算用户“Alice”点击任何广告的几率。一样,咱们能够计算任何查询 - 广告 - 域组合的点击几率。在训练时,每当咱们看到“爱丽丝”时,都使用她的点击几率做为模型的输入特征。QueryHash-AdDomain对也是如此,例如“0x437a45e1,qux.net”。
假设有10,000个用户。独热编码会生成一个稀疏矢量长度为10,000,在列中对应于值的单个1当前数据点。Bin-counting将全部10,000个二进制列编码为一个功能的真实值介于0和1之间。
除了历史点击几率外,咱们还能够包含其余功能:原始计数自己(点击次数和非点击次数),对数比率或任何其余几率的衍生物。咱们的例子是预测广告点击率,经过率。但该技术很容易应用于通常的二元分类。它也可使用一般的技术容易地扩展到多级分类将二元分类器扩展到多个类,即经过一对多优点比或其余多类标签编码。
比值比一般定义在两个二元变量之间。它经过提出这样一个问题来看待他们的联想强度:“当
为真时,有多大多是真的”。例如,咱们可能会问,“Alice点击广告的可能性大于通常人口?“在这里,是二进制变量”是Alice是当前用户“,而是变量”点击广告与否“。该计算使用所谓的双向列联表(基本上,四个数字对应于和
的四种可能组合)。
表1-7. 偶然发生的用户点击事件
图1-3 分类变量的独热编码与二进制计数统计的说明。
在实施方面,垃圾箱计数须要在每一个类别之间存储地图及其相关计数。(其他的统计数据能够从中获得原始计数)。所以它须要
空间,其中
是惟一值的数量的分类变量。
咱们采用Kaggle的比赛Avazu举个例子。
mAvazu竞赛使用广告数据来预测点击率,但咱们将使用它来演示如何bin计数能够大大减小大的特征空间流数据量。
编者注:这个数据集特别大,大概6g,咱们读取前10k行存储为train_subset.csv,并传到了百度云,可能跑出来的结果与原书不一致,但不影响学习。
例1-6 Bin-counting例子
import pandas as pd #读取前面的10k行 df = pd.read_csv('data/train_subset.csv') #有多少独立的特征 len(df['device_id'].unique())
1075
df.head()
5 rows × 24 columns
def click_counting(x, bin_column): clicks = pd.Series( x[x['click'] > 0][bin_column].value_counts(), name='clicks') no_clicks = pd.Series( x[x['click'] < 1][bin_column].value_counts(), name='no_clicks') counts = pd.DataFrame([clicks, no_clicks]).T.fillna('0') counts['total'] = counts['clicks'].astype( 'int64') + counts['no_clicks'].astype('int64') return counts def bin_counting(counts): counts['N+'] = counts['clicks'].astype('int64').divide( counts['total'].astype('int64')) counts['N-'] = counts['no_clicks'].astype('int64').divide( counts['total'].astype('int64')) counts['log_N+'] = counts['N+'].divide(counts['N-']) # If we wanted to only return bin-counting properties, we would filter here bin_counts = counts.filter(items=['N+', 'N-', 'log_N+']) return counts, bin_counts
bin_column = 'device_id' device_clicks = click_counting(df.filter(items= [bin_column, 'click']), bin_column) device_all, device_bin_counts = bin_counting(device_clicks)
# check to make sure we have all the devices len(device_bin_counts)
1075
device_all.sort_values(by = 'total', ascending=False).head(4)
# We can see how this can change model evaluation time by comparing raw vs. bin-counting size from sys import getsizeof print('Our pandas Series, in bytes: ', getsizeof(df.filter(items=['device_id', 'click']))) print('Our bin-counting feature, in bytes: ', getsizeof(device_bin_counts))
Our pandas Series, in bytes: 730104
Our bin-counting feature, in bytes: 95699
就像罕见的词,罕见的类别须要特殊的处理。想一想一个用户每一年登陆一次:几乎没有数据能够可靠估计她广告的点击率。并且,稀有类别会在计数表中浪费空间。解决这个问题的一种方法是经过补偿,一种积累的简单技术一个特殊垃圾箱中全部稀有类别的数量。若是计数大于必定的门槛,那么这个类别就有本身的统计数字。不然,使用来自回退箱的统计数据。这基本上会恢复单个的统计信息罕见类别与全部罕见类别的统计数据进行比较。当使用back-off方法,它有助于为统计信息添加二进制指标来自后退箱。
图1-4
若是罕见类别得到收益,它可使用本身的统计数据进行建模,从而超过回退库的阈值。
还有另外一种方法来处理这个问题,称为count-min sketch [Cormode和Muthukrishnan,2005]。在这种方法中,全部类别,罕见或频繁相似经过多个散列函数进行映射,输出范围为
,远小于类别的数量。当检索一个统计量时,计算全部的哈希值该类别,并返回最小的统计量。拥有多个散列函数减轻单个散列函数内碰撞的可能性。该计划有效由于能够作出散列函数次数,散列表大小小于
,类别的数量,仍然保持较低的总体碰撞可能性。
因为二进制计数依赖于历史数据来生成必要的统计数据须要经过数据收集期等待,致使了数据收集时间的轻微延迟学习管道。这也意味着当数据分布发生变化时,计数须要更新。数据变化越快,计数须要的次数越多从新计算。这对于目标应用程序尤为重要广告,用户偏好和热门查询变化很是快,并且缺少适应当前的分布可能意味着广告的巨大损失平台。
有人可能会问,为何不使用相同的数据集来计算相关统计量并训练模型?这个想法看起来很无辜。这里最大的问题是统计涉及目标变量,这是模型试图预测的。使用输出来计算输入特征会致使一个称为泄漏的有害问题。简而言之,泄漏意味着信息被揭示给模型,从而使它有更好的预测的不切实际的优点。当测试数据泄露到训练集中,或者将来的数据泄漏到过去时,可能会发生这种状况。任什么时候候都会向模型提供在生产中实时进行预测时应该没法访问的信息,这会致使泄漏。Kaggle的维基提供了更多泄漏示例以及为何它对机器学习应用程序不利。
若是二进制计数程序使用当前数据点的标签来计算输入统计量的一部分,则这构成直接泄漏。防止这种状况的一种方法是在计数收集(用于计算箱计数统计)和训练之间进行严格分离,即便用较早批次的数据点进行计数,将当前数据点用于训练(将分类变量映射到历史统计咱们刚刚收集),并使用将来的数据点进行测试。这解决了泄漏问题,但引入了上述延迟(输入统计信息,所以模型将跟踪当前数据)。
事实证实,还有另外一种基于差异隐私的解决方案。若是统计数据的分布保持大体相同或不存在任何一个数据点,则该统计近似是防漏的。在实践中,增长一个分布拉普拉斯
的小随机噪声足以掩盖单个数据点的任何潜在泄漏。这个想法能够结合一次性计算来制定当前数据的统计数据。
Owen Zhang在他的“赢得数据科学竞赛”的演讲中详细介绍了这个技巧。
若是在愈来愈多的历史数据下统计数据不断更新,原始数量将无限增加。这多是模型的问题。训练有素的模型“知道”输入数据直至观察到的比例。一个训练有素的决策树可能会说“当x大于3时,预测为1”。一个通过训练的线性模型可能会说“乘以0.7的多个x并查看结果是否大于全局平均值”。这些多是x介于0和5之间。可是除此以外会发生什么?没有人知道。当输入计数增长时,模型将须要从新训练以适应当前的比例。若是计数积累得至关缓慢,那么有效量表不会变得太快,而且模型不须要过于频繁地从新训练。可是当计数增长很快时,频繁的再培训将是一个麻烦。
出于这个缘由,使用标准化计数一般会更好。
以已知的时间间隔为界。例如,估计的点击率是介于
之间。另外一种方法是采起对数变换,即施加一个严格的限制,可是当数量很是大时,增长速度会很慢。这两种方法都不能防止转移投入分布,例如,去年的芭比娃娃如今已通过时,人们将再也不点击这些广告。该模型须要从新训练以适应输入数据分布中的这些更根本性的变化,不然整个流程将须要迁移到模型不断适应输入的在线学习环境。
空间复杂度:
时间复杂度:
优势:
缺点
Feature hashing
空间复杂度:
时间复杂度:
优势:
缺点:
空间复杂度:O(n+k)
时间复杂度:O(n)
优势:
缺点:
正如咱们所看到的,没有任何方法是完美的。选择使用哪个取决于所需的型号。
线性模型比较简单,所以能够进行训练处理非压缩表示,例如独热编码。
基于树的模型,另外一方面,须要反复搜索右侧分割的全部特征,而且是所以限于小型表示,如箱计数。
哈希函数处于在这两个极端之间,可是由此产生的精确度各有不一样。
原版(英文)图书地址:
https://www.oreilly.com/library/view/feature-engineering-for/9781491953235/
本文代码能够在github下载:
https://github.com/fengdu78/Data-Science-Notes/tree/master/9.feature-engineering
数据集的百度云:
连接:https://pan.baidu.com/s/1uDXt5jWUOfI0fS7hD91vBQ 提取码:8p5d