机器学习算法竞赛实战:特征工程
决定模型好坏的一个重要工作就是:特征工程
机器学习在本质还是特征,数据和特征决定了机器学习的上限,模型和算法知识逼近这个上限而已。
特征工程介于数据和算法之间,常见的特征工程分为:
- 数据预处理
- 特征转换
- 特征提取
- 特征选择
数据预处理
缺失处处理
缺失值的表现为NaN,NA,None
,还有其他用于表示数值缺失的特殊数值。
如果是少量可接受的比例,可以考虑直接删除;一般是使用填充方式:
- 对于类别型特征:填充众数;或者直接填充一个新类别
- 针对数值特征:可以填充均值、众数、中位数等
- 针对有序数据:可以填充相邻值(next或者previous)
- 模型预测填充:通过回归模型进行预测填充
异常值处理
-
定位异常值:可视化方法、统计分析等方法
-
处理异常值:
- 删除异常值
- 将异常值视为缺失值
- 填充均值或者中位数
- 不处理,使用异常值直接建模
优化内存
- python的内存回收机制:通过gc.collect来释放内存
- 数值类型优化:将pandas读取的数据转成numpy数组;使用不同的数值类型,比如float16,float32,float64等
1 | # 使用np.iinfo查看每个int类型的最小值和最大值 |
1 | np.iinfo(np.int8).min |
-128
1 | np.iinfo(np.int8).max |
127
通过特征的最大值和最小值来判断其所属的字类型:
1 | c_min = df[col].min() |
特征转换
连续变量无量纲化
无量纲化:将不同规格的数据转换到同一个规格,两种方法:标准化和缩放法
标准化:前提是特征值服从正态分布;标准化后,特征值服从标准正态分布。
$$x_{new}=\frac{x-\mu}{\delta}$$
缩放法:利用边界值信息,将特征值缩放某个范围,比如[0,1]
或者[-1,1]
等
$$X_{norm} = \frac{X-X_{min}}{X_{max} - X_{min}}$$
连续变量数据转换
- log转换
取对数log转换可以将倾斜数据变得接近正态分布,一般是使用log(x+1),其中加1是防止数据等于0,同时保证x是正的。
取对数不会改变数据的性质和相关关系。但是压缩了变量的尺度,不仅数据更加平稳,还削弱了模型的共线性、异方差性等。
- cbox-cox变换
cbox-cox
变换:自动寻找最佳正态分布变换函数的方法
- 连续变量离散化
离散化后的特征对异常数据有很强的鲁棒性。比如年龄的离散化:将年龄大于30岁视为1,否则视为0。如果没有离散化,数据中异常值300(可能是录入错误)对模型造成很大干扰。
离散化的两种方式:有监督和无监督
- 无监督的离散化:分桶操作将连续数据离散化,使得数据更加平滑,降低噪声数据的影响:等频和等距
- 有监督的离散化:常用的是使用树模型返回叶子节点来进行离散化。
类别型特征转换
对离散型的特征进行编码,2种常见方式:
- 自然数编码(特征有意义):比如衣服的S、M、L、XL等尺码大小,本身就存在一定的大小顺序
- 独热码(特征无意义):比如红黄绿的颜色类别;类别无顺序
1 | # 1、调用sklearn函数 |
不规则特征
比如身份证信息等(个人信息属于隐私,仅用于举例)
身份证号码 = 6位数字地址码 + 8位数字出生日期码 + 3位数字顺序码 + 1位数字校验码
特征提取(特征衍生)
通过对数据集的分析和理解来创建一些新特征来帮助模型学习。
类别相关的统计特征
- 构造目标编码
- count/nunique/ratio等特征
- 特征交叉组合等
构造目标编码
构造目标编码:使用目标变量(标签)的统计量来对类别特征进行编码;回归问题,可以统计均值、中位数等,分类问题,可以统计正负样本数量和比例等
基于5折交叉验证的实现:
1 | folds = KFold(n_splits=5,shuffle=True,random_state=2023) |
count、nunique、ratio
count:用于统计类别特征的出现频次
nunique、ratio:多个特征的联合构造
类别特征交叉组合
交叉组合能够描述更细粒度的内容,比如年龄_性别
组合。
简单来说,就是对两个特征进行笛卡尔积的操作,产生新的特征。
数值相关的统计特征
- 特征之间的交叉组合
- 类别特征和数值特征的交叉组合
- 按行统计相关特征
时间特征
将给定的时间戳属性转成年月日时分秒等单个属性;还可以构造时间差等
多值特征
某列中包含多个属性的情况,这就是多值特征。多值特征的常见处理方式:完全展开,将特征的n个属性展开成n维稀疏矩阵。使用sklearn中的CountVectorizer函数,考虑每个属性在这个特征的出现频次。
特征选择
增加了新特征后,需要判断它们对提高模型效果是否有用。特征选择算法用于从数据中识别并删除不需要、不相关以及冗余的特征。主要方法:
- 基于先验的特征关联性分析
- 基于后验的特征重要性分析
特征关联性分析
特征关联性分析是使用统计量来为特征之间的相关性进行评分;按照分数的高低来进行排序,选择部分特征。
关联性分析通常是针对单个变量,忽略了变量和变量之间的关系。常用方法:
- 皮尔逊相关系数
- 卡方检验
- 互信息法
- 信息增益
皮尔逊相关系数
- 可以衡量变量和变量间的相关性,解决多重共线性问题
- 可以衡量变量和标签间的相关性
1 | # 提取top300的特征 |
卡方检验
检验特征变量和因变量的关系。对于分类问题,一般假设与标签独立的特征为无关特征,而卡方检验刚好可以进行独立性检验。
如果检验的结果是某个特征和标签独立,则可以删除该特征。
$$X^2=\sum \frac{(A-E)^2} {E}$$
互信息法
互信息是对一个联合分布中两个变量之间相互影响的度量,也可以用来评价两个变量间的相关性。从两个角度解释互信息:基于KL散度和互信息增益。
互信息越大说明变量相关性越高
$$M I\left(x_{i}, y\right)=\sum_{x_{i} \in{0,1}} \sum_{y \in{0,1}} p\left(x_{i}, y\right) \log \left(\frac{p\left(x_{i}, y\right)}{p\left(x_{i}\right) p(y)}\right)$$
特征重要性分析
基于树模型评估特征的重要性分数。比如使用XGBoost模型评估重要性的3种计算方法:weight、gain、cover
1 |
|
封装方法
封装方法是一个比较耗时的特征选择方法:将一组特征视为一个搜索问题,通过准备、评估不同的组合并对这些组合进行比较,从而找出最优的特征子集。搜索过程可以是系统性的(最佳优先搜索),也可以是随机的(随机爬山算法),或者元启发式方法(通过向前或者向后搜索来添加和删除特征,类似剪枝算法)。
常用的方法:
- 启发式方法
- 递归特征消除法RFE
1 | from sklearn.feature_selection import RFE |
使用封装方法的时候,应该先对数据进行采样,再对小数据使用封装方法。
上面三种方法建议使用顺序:特征重要性、特征关联性分析、封装方法
其他不常见的特征选择方法:kaggle上经典的null importance
特征选择方式。
https://www.kaggle.com/code/ogrellier/feature-selection-with-null-importances/notebook
特征工程案例实战
针对特征工程部分
1 | import pandas as pd |
1 | train = pd.read_csv("train.csv") |
数据预处理
缺失值处理
1 | pd.concat([train,test],axis=0, sort=False) |
1 | df = pd.concat([train,test],axis=0, sort=False) |
1 | df.shape |
(2919, 77)
1 | # 2、对object类型特征进行填充unknow |
1 | # 3、对数值型特征填充中位数 |
删除属性分布不均衡的特征
某些特征中的属性分布极不均衡,比如某个属性占比超过95%,此时可以考虑是否删除该特征
1 | plt.figure(figsize=(8,6)) |
在Street特征中,Pave属性远高于Grvl属性,可以考虑删除。
1 | plt.figure(figsize=(8,6)) |
1 | plt.figure(figsize=(8,6)) |
1 | plt.figure(figsize=(8,6)) |
1 | plt.figure(figsize=(8,6)) |
我们直接删除上面的几个特征,属性分布极不均衡:
1 | object_df = object_df.drop(['Heating','RoofMatl','Condition2','Street','Utilities'],axis=1) |
特征提取
从多个角度进行特征构造,构造的特征具有实际意义:
基本特征构造
发现数据中存在异常值:销售日期YrSold 小于建造日期YearBuilt(不符合常理),属于异常。
1 | # 异常值处理 |
1 | numerical_df["TotalBsmtBath"] = numerical_df["BsmtFullBath"] + numerical_df["BsmtHalfBath"] * 0.5 # 浴池 + 半浴池 |
特征编码
不同的分类型特征采用不同的方式:
- 本身存在大小关系的序数特征:进行自然编码,0-N的自然数
- 没有大小关系的特征:独热码one-hot;或者频次编码count
1 | bin_map = {"TA":2, |
1 | sorted(object_df.columns) |
['BldgType',
'BsmtCond',
'BsmtExposure',
'BsmtFinType1',
'BsmtFinType2',
'BsmtQual',
'CentralAir',
'Condition1',
'Electrical',
'ExterCond',
'ExterQual',
'Exterior1st',
'Exterior2nd',
'FireplaceQu',
'Foundation',
'Functional',
'GarageCond',
'GarageFinish',
'GarageQual',
'GarageType',
'HeatingQC',
'HouseStyle',
'KitchenQual',
'LandContour',
'LandSlope',
'LotConfig',
'LotShape',
'MSZoning',
'MasVnrType',
'Neighborhood',
'PavedDrive',
'RoofStyle',
'SaleCondition',
'SaleType']
1 | columns_list = [ |
1 | PavedDrive = {"N":0,"P":1,"Y":2} |
1 | # 选择剩余的object特征 |
合并分类型和数值型特征的数据:
1 | df = pd.concat([numerical_df,object_df],axis=1,sort=False) |
特征选择:基于相关系数
基于相关性评估的方式进行特征选择,过滤掉相似性大于一定阈值的特征,减少特征冗余。
1 | # 相关性评估的辅助函数 |
1 | # 相关系数矩阵 |
{'Age_House',
'Exterior2nd_CmentBd',
'Exterior2nd_MetalSd',
'Exterior2nd_VinylSd',
'Exterior2nd_unknow',
'GarageFinish_unknow',
'LandSlope_Mod',
'RoofStyle_Hip',
'SaleCondition_Partial',
'TotalBath',
'TotalBsmtBath'}