https://zhuanlan.zhihu.com/p/26444240
(2018/2/6 更新:修改了部分名词的翻译)
与其说是教程类的科普,不如说是一篇经验向的个人笔记,所以细节上比较懒。其实,我更打算把这篇文章做成一个索引,能够引用原版文档的就引用文档,尽量不重复翻译,毕竟各类文档本身信息量最充足、更新最及时。简洁是本文的第一原则。
一些基础的暂时就不补充了,主要涉及一些奇技淫巧。纯粹是经验之谈,如有不准确之处,请多多包涵;如能帮忙指正,感激不尽。如果觉得内容少的话,等几个月,说不定就养肥了~
想到一点写一点,有思路才更新。有其他点子的话,可以在评论区补充或者私信。链接分享随意,全文转载前请私信。
目录
- 0. 概述
- 1. 探索性数据分析(EDA)
- 2. 预处理(Preprocessing)
- 3. 特征工程:处理已有特征
- 3.1. 类别特征
- 3.2. 数值特征
- 3.3. 时间特征
- 3.4 缺失值处理
- 4. 特征工程:创造新特征
- 5. 特征选择
- 6. 数据泄露(Leakage)
- 7. 参考文献
0. 概述
特征工程(feature engineering):利用领域知识和现有数据,创造出新的特征,用于机器学习算法;可以手动(manual)或自动(automated)。神经网络的自动特征工程,常常不适用于现实中其他的复杂任务。因此,本文主要针对数据挖掘以及传统的机器学习,不会涉及图像识别、自然语言处理等深度学习领域。
俗话说:数据与特征工程决定了模型的上限,改进算法只不过是逼近这个上限而已。
特征工程(维基百科):Feature engineering
1. 探索性数据分析(EDA,Exploratory Data Analysis)
plot,plot,plot,重要的事情说三遍
2. 预处理(Preprocessing)
我们需要将训练集表示为矩阵X(大小:n_samples * n_features)和矢量y(长度:n_samples)。矢量y可以被转换为矩阵Y(大小:n_samples * n_classes)。
scikit-learn完整的API reference:API Reference - scikit-learn 0.18.1 documentation
常用函数:
- sklearn.preprocessing.LabelBinarizer - scikit-learn 0.18.1 documentation,用于one vs all的label encoding,类似于独热编码,生成一个(n_examples * n_classes)大小的0~1矩阵,每个样本仅对应一个label。
- sklearn.preprocessing.MultiLabelBinarizer - scikit-learn 0.18.1 documentation,用于label encoding,生成一个(n_examples * n_classes)大小的0~1矩阵,每个样本可能对应多个label。
- sklearn.preprocessing.LabelEncoder - scikit-learn 0.18.1 documentation,用于label encoding,生成一个(n_examples)大小的0~(n_classes-1)矢量,每个样本仅对应一个label。
3. 特征工程:处理已有特征
3.1. 类别特征
类别特征,表示某个数据点属于某一个类别,或具有某一种类的特性。一列类别特征,默认用自然数表示(可以用LabelEncoder将字符串转化为自然数)。如果一列类别特征里有K种不同类别,其取值范围是。
例:颜色、性别、地址、血型、国籍、省、市、邮政编码。
类别特征(维基百科):Categorical variable
3.1.1. 自然数编码
默认的编码方式(见上,使用LabelEncoder可以得到),消耗内存小,训练时间快,但是特征的质量不高。
3.1.2. 独热编码(One-hot Encoding)
如果类别特征本身有顺序(例:优秀、良好、合格、不合格),那么可以保留单列自然数编码。如果类别特征没有明显的顺序(例:红、黄、蓝),则可以使用以下方法:
sklearn.preprocessing.OneHotEncoder - scikit-learn 0.18.1 documentation,用于类别特征的独热编码(One-Hot Encoding)。运行结果与LabelBinarizer相似,不过在参数以及输入输出的格式上有细微的差别,参见文档。输出的矩阵是稀疏的,含有大量的0。
统计学中,独热编码的变种还有effects coding、contrast coding、nonsense coding等编码方式,但在数据挖掘中都不常用。
3.1.3. 聚类编码
和独热编码相比,聚类编码试图充分利用每一列0与1的信息表达能力。聚类编码时一般需要特定的专业知识(domain knowledge),例如ZIP码可以根据精确度分层为ZIP3、ZIP4、ZIP5、ZIP6,然后按层次进行编码。
(我个人尚未使用过这种方法,仅在读论文的时候看到了类似的思路,所以暂时不知道对于各种算法而言效果如何。)
3.1.4. 平均数编码(mean encoding)
平均数编码(mean encoding),针对高基数类别特征的有监督编码。当一个类别特征列包括了极多不同类别时(如家庭地址,动辄上万)时,可以采用。优点:和独热编码相比,节省内存、减少算法计算时间、有效增强模型表现。
平均数编码:针对高基数类别特征(类别特征)的数据预处理/特征工程 - 知乎专栏
3.1.5. 只出现一次的类别
在类别特征列里,有时会有一些类别,在训练集和测试集中总共只出现一次,例如特别偏僻的郊区地址。此时,保留其原有的自然数编码意义不大,不如将所有频数为1的类别合并到同一个新的类别下。
注意:如果特征列的频数需要被当做一个新的特征加入数据集,请在上述合并之前提取出频数特征。
3.2. 数值特征
数值特征(numerical feature),可以是连续的(continuous),也可以是离散的(discrete),一般表示为一个实数值。
例:年龄、价格、身高、体重、测量数据。
不同算法对于数值特征的处理要求不同。下文中的一些数据处理方法(3.2.1、3.2.2、3.2.3),因为是针对某一特征列的单调变换,所以不会对基于决策树的算法(随机森林、gbdt)产生任何影响。一般而言,决策树类算法不需要预处理数值特征。
3.2.1. 标准化(Standardization)
- sklearn.preprocessing.StandardScaler - scikit-learn 0.18.1 documentation,转换为Z-score,使数值特征列的算数平均为0,方差(以及标准差)为1。不免疫outlier。
- sklearn.preprocessing.RobustScaler - scikit-learn 0.18.1 documentation。如果数值特征列中存在数值极大或极小的outlier(通过EDA发现),应该使用更稳健(robust)的统计数据:用中位数而不是算术平均数,用分位数(quantile)而不是方差。这种标准化方法有一个重要的参数:(分位数下限,分位数上限),最好通过EDA的数据可视化确定。免疫outlier。
3.2.2. 归一化(Normalization)
- sklearn.preprocessing.Normalizer - scikit-learn 0.18.1 documentation,把每一行数据归一化,使之有unit norm,norm的种类可以选l1、l2或max。不免疫outlier。
,其中表示norm函数。
3.2.3. 区间缩放(scaling)
- sklearn.preprocessing.MaxAbsScaler - scikit-learn 0.18.1 documentation,将一列的数值,除以这一列的最大绝对值。不免疫outlier。
3.3. 其他特征工程(待补全)
时间特征:年、月、日、时、分、秒,这是一年的第n天,这是一年的第n周,这是一周的第n天,etc
地理特征:经度、纬度
用户特征:如用户名,每个种类对应一个不同的实体
- NA indicator column
- 时间序列:把昨天的特征加入今天的特征,或者把和昨天相比,特征数值的改变量加入今天的特征。
- 连续特征离散化(这对于决策树类型的模型没太多意义)。一种挺有趣的变种,就是限制浮点数特征的精度。
- clipping:可以用pandas dataframe的.clip(low, upper)方法,把特征值的取值限制在一定范围内
- 一开始就把所有的特征一股脑地扔进模型,费时费力,容易被一些没用的特征误导
- 除非万不得已,不要用PCA或者LDA降维,直接减原始特征就行了。
- 像lightgbm和xgboost这种gbdt,一般都自带cv,early stopping和feature importance,用来同时验证FE是坠吼的,省时间。
3.4 缺失值处理
LightGBM和XGBoost都能将NaN作为数据的一部分进行学习,所以不需要处理缺失值。
其他情况下,我们需要使用
4. 特征工程:创造新特征
4.1. 数值特征的简单变换
- 单独特征列乘以一个常数(constant multiplication)或者加减一个常数:对于创造新的有用特征毫无用处;只能作为对已有特征的处理。
- 任何针对单独特征列的单调变换(如对数):不适用于决策树类算法。对于决策树而言, 、 、 之间没有差异, 、 、 之间没有差异,除非发生了舍入误差。
- 线性组合(linear combination):仅适用于决策树以及基于决策树的ensemble(如gradient boosting, random forest),因为常见的axis-aligned split function不擅长捕获不同特征之间的相关性;不适用于SVM、线性回归、神经网络等。
- 多项式特征(polynomial feature):sklearn.preprocessing.PolynomialFeatures - scikit-learn 0.18.1 documentation。
- 比例特征(ratio feature):
- 绝对值(absolute value)
- ,,
4.2. 类别特征与数值特征的组合
用N1和N2表示数值特征,用C1和C2表示类别特征,利用pandas的groupby操作,可以创造出以下几种有意义的新特征:(其中,C2还可以是离散化了的N1)
median(N1)_by(C1) \\ 中位数
mean(N1)_by(C1) \\ 算术平均数
mode(N1)_by(C1) \\ 众数
min(N1)_by(C1) \\ 最小值
max(N1)_by(C1) \\ 最大值
std(N1)_by(C1) \\ 标准差
var(N1)_by(C1) \\ 方差
freq(C2)_by(C1) \\ 频数
freq(C1) \\这个不需要groupby也有意义
仅仅将已有的类别和数值特征进行以上的有效组合,就能够大量增加优秀的可用特征。
将这种方法和线性组合等基础特征工程方法结合(仅用于决策树),可以得到更多有意义的特征,如:
N1 - median(N1)_by(C1)
N1 - mean(N1)_by(C1)
4.3. 用基因编程创造新特征
Welcome to gplearn’s documentation!
基于genetic programming的symbolic regression,具体的原理和实现参见文档。目前,python环境下最好用的基因编程库为gplearn。基因编程的两大用法:
- 转换(transformation):把已有的特征进行组合转换,组合的方式(一元、二元、多元算子)可以由用户自行定义,也可以使用库中自带的函数(如加减乘除、min、max、三角函数、指数、对数)。组合的目的,是创造出和目标y值最“相关”的新特征。这种相关程度可以用spearman或者pearson的相关系数进行测量。spearman多用于决策树(免疫单特征单调变换),pearson多用于线性回归等其他算法。
- 回归(regression):原理同上,只不过直接用于回归而已。
4.4. 用决策树创造新特征
在决策树系列的算法中(单棵决策树、gbdt、随机森林),每一个样本都会被映射到决策树的一片叶子上。因此,我们可以把样本经过每一棵决策树映射后的index(自然数)或one-hot-vector(哑编码得到的稀疏矢量)作为一项新的特征,加入到模型中。
具体实现:apply()以及decision_path()方法,在scikit-learn和xgboost里都可以用。
决策树、基于决策树的ensemble
- spearman correlation coefficient
线性模型、SVM、神经网络
- 对数(log)
- pearson correlation coefficient
6. 数据泄露(Data Leakage)
在数据挖掘中,数据泄露(leakage)指的是:本来不应该出现在X里的、和目标y有关的数据,出现在了X中。如此一来,机器学习算法就会有好到不真实的表现。
Kaggle Wiki链接:Leakage | Kaggle
6.1. 数据泄露的种类以及影响分析
- 测试集数据被泄露到训练集:过拟合,模型在现实中的表现远不如test accuracy;测试集失去意义。
- 正确的预测(y)被泄露到测试集:严重过拟合,训练出的模型毫无用处,比赛组织者的极大失败
- 未来的信息被泄露到过去:时间序列相关,现实中模型将无法有效根据过去情况预测未来。
- 模型可以获得一些不该获得的信息,比如和目标变量有较大关系的变量、现实里接触不到的变量。例子:y是“病人是否患有癌症”,但是X包括了“病人是否接受肿瘤切除手术”。
- 反向工程,去匿名化,去除数据集中的随机打乱操作,社会工程学。这种行为是数据比赛明令禁止的,而且在现实中也涉嫌侵犯隐私。例子:反向工程“随机的”用户编码,得出用户的真名。
- 第三方信息。例子:已知坐标,利用geocoder类型的服务推出所在城市;在预测金融市场时加入第三方的政策新闻的特征。
6.2. 有效发现和利用数据泄露
数据泄露可以分为两大类:
- 由于自己的疏忽,在交叉验证、训练过程中,产生的数据泄露。这种情况属于失误,应当尽量避免。
- 在数据竞赛中,找到了理论上不能使用(但是也没有明令禁止)的额外数据,从而提升分数。
避免第一种数据泄露的方法,可以参考kaggle的各类比赛。假设有大量数据,我们可以把未处理的数据分为训练集和测试集,其中,测试集包括Public LB和Private LB两部分。
- 在模型的训练、选择和交叉验证时,我们只能接触训练集。
- 在对自己的模型非常自信时,可以偶尔在Public LB上验证。
- 只有模型即将被用于正式商业用途时,才能看模型在Private LB上的表现。
交叉验证误差、public LB误差、private LB误差:如果后者的误差值显著高于前者,那么需要考虑过拟合或第一类数据泄露。
第二类的数据泄露,属于旁门左道。本质上,这相当于在模型训练阶段,干了数据收集阶段的工作。搜集原始数据,或是自己提供数据举办竞赛(试图避免他人利用数据泄露)时,可以参考这种思路。
- 文件夹的创造时间。
- 看似乱码的字符串(如各类id)可能有统计分布的规律。
- 地理位置信息:如果提供了坐标,则可反向地理编码,得出相关地理信息。
这类数据可能会导致过拟合。
大家在收藏的时候也不要忘记点赞,你们的赞是我更新的动力。
觉得有用的话,可以把链接分享出去让更多人看见。
如果你们有其他特征工程的技巧,欢迎在评论区分享。
(顺便,厚颜无耻地求一个关注?我是 @光喻)