文章目录
-
- 1. 特征工程概述
- 2. 内容
-
- 1. 数据预处理
-
- a. 填充缺失值
- b. 时间格式数据处理
- c. 对象型数据转换为数值型
- d. 类别特征的处理
- 2. 异常值处理
-
- a. 利用均方差检测异常值
- b. 利用箱型图检测异常值
- 3. 数据分桶
- 4. 特征交互
- 5. 特征编码
-
- a. labelEncode 直接放入树模型中
- b. 逻辑回归等模型要单独增加的特征工程
- 6. 特征选择
- 7. 总结
1. 特征工程概述
特征工程,是指用一系列工程化的方式从原始数据中筛选出更好的数据特征,以提升模型的训练效果。业内有一句广为流传的话是:数据和特征决定了机器学习的上限,而模型和算法是在逼近这个上限而已。由此可见,好的数据和特征是模型和算法发挥更大的作用的前提。特征工程通常包括数据预处理、特征选择、降维等环节。
2. 内容
首先导入相关包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from tqdm import tqdm
import datetime
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.preprocessing import MinMaxScaler
import lightgbm as lgb
import xgboost as xgb
from catboost import CatBoostRegressor
import warnings
from sklearn.model_selection import StratifiedKFold, KFold
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss
warnings.filterwarnings('ignore')
读取数据
train = pd.read_csv('./data/train_0915.csv')
test_a = pd.read_csv('./data/testA_0915.csv')
1. 数据预处理
数据EDA部分我们已经对数据的大概和某些特征分布有了了解,数据预处理部分一般我们要处理一些EDA阶段分析出来的问题,这里介绍了数据缺失值的填充,时间格式特征的转化处理,某些对象类别特征的处理。
# 划分数据类型:数值型(数值型包括连续型和离散型)和对象型
numerical_fea = list(train.select_dtypes(exclude=['object']).columns)
object_fea = list(train.select_dtypes(include=['object']).columns)# isDefault 是待预测值
numerical_fea.remove('isDefault')
在比赛中数据预处理是必不可少的一部分,对于缺失值的填充往往会影响比赛的结果,在比赛中不妨尝试多种填充然后比较结果选择结果最优的一种;
比赛数据相比真实场景的数据相对要“干净”一些,但是还是会有一定的“脏”数据存在,清洗一些异常值往往会获得意想不到的效果。
a. 填充缺失值
在读取数据之后已经对数据特征进行了分类(数值型和对象型),对于两种数据类型采取不同的填充方法。
# 填充缺失值
# 数值型特征用中位数填充
train[numerical_fea] = train[numerical_fea].fillna(train[numerical_fea].median())
test_a[numerical_fea] = test_a[numerical_fea].fillna(test_a[numerical_fea].median())# 对象型特征用众数填充
train[object_fea] = train[object_fea].fillna(train[object_fea].mode())
test_a[object_fea] = test_a[object_fea].fillna(test_a[object_fea].mode())
b. 时间格式数据处理
# issueDate 贷款发放的日期#转化成时间格式
for data in [train, test_a]: data['issueDate'] = pd.to_datetime(data['issueDate'],format='%Y-%m-%d') startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d') #构造时间特征 data['issueDateDT'] = data['issueDate'].apply(lambda x: x-startdate).dt.days
c. 对象型数据转换为数值型
# grade 贷款等级
print(train['grade'].value_counts())
train['grade'].value_counts().plot.bar()
# grade可以自映射或进行one_hot编码(这里选择自映射)
# 转换为数值特征(自映射)
for data in [train, test_a]: data['grade'] = data['grade'].map({
'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})# employmentLength 就业年限(年)
print(train['employmentLength'].value_counts())
train['employmentLength'].value_counts().plot.bar()train['employmentLength'].value_counts(dropna=False).sort_index()def employmentLength_to_int(s): if pd.isnull(s): return s else:return np.int8(s.split()[0])# 转换为数值型特征
for data in [train, test_a]: data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True) data['employmentLength'].replace('< 1 year', '0 years', inplace=True) data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int)train['employmentLength'].value_counts(dropna=False).sort_index()
train['employmentLength'] = train['employmentLength'].fillna(train['employmentLength'].mode())# earliesCreditLine 借款人最早报告的信用额度开立的月份
train['earliesCreditLine'].sample(5)# # 将年月划分为两个特征,其中月份还需要进一步转换为数值型特征
for data in [train, test_a]: data['earliesCreditLine_year'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:]))data['earliesCreditLine_month'] = data['earliesCreditLine'].apply(lambda s: s[:3])data.drop(['earliesCreditLine'], axis=1, inplace=True)# 月份转换为数值型特征
for data in [train, test_a]: data['earliesCreditLine_month'] = data['earliesCreditLine_month'].map({
'Jan':1,'Feb':2,'Mar':3,'Apr':4,'May':5,'Jun':6,'Jul':7,'Aug':8,'Sep':9,'Oct':10,'Nov':11,'Dec':12})
data['earliesCreditLine_month'].value_counts()
d. 类别特征的处理
# 类型数在2之上,又不是高维稀疏的,且纯分类特征
for data in [data_train, data_test_a]: data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
2. 异常值处理
当你发现异常值后,一定要先分清是什么原因导致的异常值,然后再考虑如何处理。首先,如果这一异常值并不代表一种规律性的,而是极其偶然的现象,或者说你并不想研究这种偶然的现象,这时可以将其删除。其次,如果异常值存在且代表了一种真实存在的现象,那就不能随便删除。在现有的欺诈场景中很多时候欺诈数据本身相对于正常数据勒说就是异常的,我们要把这些异常点纳入,重新拟合模型,研究其规律。能用监督的用监督模型,不能用的还可以考虑用异常检测的算法来做。
注意test的数据不能删。
a. 利用均方差检测异常值
在统计学中,如果一个数据分布近似正态,那么大约 68% 的数据值会在均值的一个标准差范围内,大约 95% 会在两个标准差范围内,大约 99.7% 会在三个标准差范围内。
# 异常值处理 通过均方差检测异常值
def find_outliers_by_3segama(data,fea): data_std = np.std(data[fea]) data_mean = np.mean(data[fea]) outliers_cut_off = data_std * 3 lower_rule = data_mean - outliers_cut_off upper_rule = data_mean + outliers_cut_off data[fea+'_outliers'] = data[fea].apply(lambda x:str('异常值') if x > upper_rule or x < lower_rule else '正常值') return datatrain = train.copy()
for fea in numerical_fea: train = find_outliers_by_3segama(train,fea) print(train[fea+'_outliers'].value_counts()) print(train.groupby(fea+'_outliers')['isDefault'].sum()) print('*'*10)#删除异常值
for fea in numerical_fea: train = train[train[fea+'_outliers']=='正常值'] train = train.reset_index(drop=True)#删除outliers
for fea in numerical_fea: train.drop([fea+'_outliers'], axis=1, inplace=True)train = train.reset_index(drop=True)
b. 利用箱型图检测异常值
总结一句话:四分位数会将数据分为三个点和四个区间,IQR = Q3 -Q1,下触须=Q1 ? 1.5x IQR,上触须=Q3 + 1.5x IQR
3. 数据分桶
- 特征分箱的目的:
a. 从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量
和因变量的相关度。从而使模型更加稳定。 - 数据分桶的对象:
a. 将连续变量离散化
b. 将多状态的离散变量合并成少状态 - 分箱的原因:
a. 数据的特征内的值跨度可能比较大,对有监督和无监督中如k-均值聚类它使用欧氏距离作为相似度函数来
测量数据点之间的相似度。都会造成大吃小的影响,其中一种解决方法是对计数值进行区间量化即数据
分桶也叫做数据分箱,然后使用量化后的结果。 - 分箱的优点:
a. 处理缺失值:当数据源可能存在缺失值,此时可以把null单独作为一个分箱。
b. 处理异常值:当数据中存在离群点时,可以把其通过分箱离散化处理,从而提高变量的鲁棒性(抗干扰
能力)。例如,age若出现200这种异常值,可分入“age > 60”这个分箱里,排除影响。
c. 业务解释性:我们习惯于线性判断变量的作用,当x越来越大,y就越来越大。但实际x与y之间经常存在
着非线性关系,此时可经过WOE变换。 - 特别要注意一下分箱的基本原则:
a. (1)最小分箱占比不低于5%
b. (2)箱内不能全部是好客户
c. (3)连续箱单调
- 固定宽度分箱
当数值横跨多个数量级时,最好按照 10 的幂(或任何常数的幂)来进行分组:09、1099、100999、10009999,等等。固定宽度分箱非常容易计算,但如果计数值中有比较大的缺口,就会产生很多没有任何数据的空箱子。
# 通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是loanAmnt/1000
data['loanAmnt_bin1'] = np.floor_divide(data['loanAmnt'], 1000)
## 通过对数函数映射到指数宽度分箱
data['loanAmnt_bin2'] = np.floor(np.log10(data['loanAmnt']))
- 分位数分箱
data['loanAmnt_bin3'] = pd.qcut(data['loanAmnt'], 10, labels=False)
- 卡方分箱及其他分箱方法的尝试
4. 特征交互
交互特征的构造非常简单,使用起来却代价不菲。如果线性模型中包含有交互特征对,那它的训练时间和评分时间就会从 O(n) 增加到 O(n2),其中 n 是单一特征的数量。
for col in ['grade', 'subGrade']: temp_dict = data_train.groupby([col]) ['isDefault'].agg(['mean']).reset_index().rename(columns={
'mean': col + '_target_mean'}) temp_dict.index = temp_dict[col].values temp_dict = temp_dict[col + '_target_mean'].to_dict() data_train[col + '_target_mean'] = data_train[col].map(temp_dict) data_test_a[col + '_target_mean'] = data_test_a[col].map(temp_dict)# 其他衍生变量 mean 和 std
for df in [data_train, data_test_a]: for item in ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']: df['grade_to_mean_' + item] = df['grade'] / df.groupby([item]) ['grade'].transform('mean') df['grade_to_std_' + item] = df['grade'] / df.groupby([item]) ['grade'].transform('std')
这里给出一些特征交互的思路,但特征和特征间的交互衍生出新的特征还远远不止于此。
5. 特征编码
a. labelEncode 直接放入树模型中
#label-encode:subGrade,postCode,title
# 高维类别特征需要进行转换
for col in tqdm(['employmentTitle', 'postCode', 'title','subGrade']): le = LabelEncoder() le.fit(list(data_train[col].astype(str).values) + list(data_test_a[col].astype(str).values)) data_train[col] = le.transform(list(data_train[col].astype(str).values)) data_test_a[col] = le.transform(list(data_test_a[col].astype(str).values))
print('Label Encoding 完成')
b. 逻辑回归等模型要单独增加的特征工程
- 对特征做归一化,去除相关性高的特征
- 归一化目的是让训练过程更好更快的收敛,避免特征大吃小的问题
- 去除相关性是增加模型的可解释性,加快预测过程。
# 举例归一化过程
#伪代码
for fea in [要归一化的特征列表]: data[fea] = ((data[fea] - np.min(data[fea])) / (np.max(data[fea]) - np.min(data[fea])))
6. 特征选择
特征选择技术可以精简掉无用的特征,以降低最终模型的复杂性,它的最终目的是得到一个简约模型,在不降低预测准确率或对预测准确率影响不大的情况下提高计算速度。特征选择不是为了减少训练时间(实际上,一些技术会增加总体训练时间),而是为了减少模型评分时间。
特征选择的方法:
- Filter
a. 方差选择法
b. 相关系数法(pearson 相关系数)
c. 卡方检验
d. 互信息法 - Wrapper (RFE)
a. 递归特征消除法 - Embedded
a. 基于惩罚项的特征选择法
b. 基于树模型的特征选择
7. 总结
特征工程是机器学习,甚至是深度学习中最为重要的一部分,在实际应用中往往也是所花费时间最多的一步。各种算法书中对特征工程部分的讲解往往少得可怜,因为特征工程和具体的数据结合的太紧密,很难系统地覆盖所有场景。本章主要是通过一些常用的方法来做介绍,例如缺失值异常值的处理方法详细对任何数据集来说都是适用的。但对于分箱等操作本章给出了具体的几种思路,需要读者自己探索。在特征工程中比赛和具体的应用还是有所不同的,在实际的金融风控评分卡制作过程中,由于强调特征的可解释性,特征分箱尤其重要。学有余力同学可以自行多尝试,希望大家在本节学习中有所收获。