MLK | 特征工程系统化干货笔记+代码了解一下(上)

640?wx_fmt=png
? 说起特征工程,都说是机器学习建模中最为重要而且费时的一项工作,而且它涉及的知识点会非常地多,经验老道的老司机自然是轻车熟路了,但对于刚刚入门的新手司机,学习到的知识点都是东一点西一点的,不够系统化,本篇文章是在阅读了一本评分极高的特征工程书籍 ? 《特征工程入门与实践》后的一篇笔记文,记录下相对比较系统的知识点以及可运行复现的代码,希望对各位同行有所帮助哈。


640?wx_fmt=png

图:强力推荐这本书 



? 目录

  • ? 特征理解
  • ? 特征增强
  • ? 特征构建(待更新)
  • ✅ 特征选择(待更新)
  • ? 特征转换(待更新)
  • ? 特征学习(待更新)


大家可以先看下思维导图:

640?wx_fmt=png


? 01 特征理解

在拿到数据的时候,我们第一步需要做的是理解它,一般我们可以从下面几个角度入手:
(注:本节用到了两个数据集,分别是Salary_Ranges_by_Job_Classification 和 GlobalLandTemperaturesByCity)


1. 区分结构化数据与非结构化数据
如一些以表格形式进行存储的数据,都是结构化数据;而非结构化数据就是一堆数据,类似于文本、报文、日志之类的。


2. 区分定量和定性数据
定量数据:指的是一些数值,用于衡量某件东西的数量;
定性数据:指的是一些类别,用于描述某件东西的性质。
其实区分了定量和定性数据,还可以继续细分下去,分为定类(nominal)、定序(ordinal)、定距(interval)、定比数据(ratio),下面我们分别对这4类数据进行举例说明,加深大家对它们的印象。
1)定类(nominal)
也就是分类,比如:血型(A/B/O/AB型)、性别(男/女)、货币(人民币/美元/日元),而且值得注意的是这些分类之间没有大小可比性。一般画图的话就只能看下分布占比,可以用条形图、饼图来表示。

640?wx_fmt=png


2)定序(ordinal)
定序相比于定类,也就是多了一个“可排序”的属性,也就是说虽然它们是类别变量,但是它们的变量值之间是存在“大小”之分的。比如:期末绩点(A、B、C、D、E、F)、问卷答案(非常满意、满意、一般、不满意)。可视化方面,和定类一样,不过就是多了一个 箱体图 可以用(因为定序变量可以有中位数)。

640?wx_fmt=png


3)定距(interval)
定距的话,就是变量值之间可以做加减法计算,也就是可以引入均值、方差之类的名词了,而且能够画的图也多了,包括先前的那些,还包括了直方图。

640?wx_fmt=png


4)定比(ratio)
定比相比于定距更加严格,不仅仅有定距的所有属性,同时,有一个 绝对零点 的概念,可以做加减乘除运算,比如说某个商品的价格是另一个的2倍。值得注意的是,温度一般不归入定比,而是定距,没有说20度是10度的两倍这种说法。

640?wx_fmt=png


最后把上面的内容总结一下:

640?wx_fmt=png


3. 关键代码汇集
以下的代码只是核心片段,完整代码可在公众号(SAMshare)后台输入关键字 特征工程 获取。

1)常见简易画图

# 绘制条形图
salary_ranges['Grade'].value_counts().sort_values(ascending=False).head(10).plot(kind='bar')

# 绘制饼图
salary_ranges['Grade'].value_counts().sort_values(ascending=False).head(5).plot(kind='pie')

# 绘制箱体图
salary_ranges['Union Code'].value_counts().sort_values(ascending=False).head(5).plot(kind='box')

# 绘制直方图
climate['AverageTemperature'].hist()

# 为每个世纪(Century)绘制平均温度的直方图
climate_sub_china['AverageTemperature'].hist(by=climate_sub_china['Century'],
                                           sharex=True,
                                           sharey=True,
                                           figsize=(10, 10),
                                           bins=20)

# 绘制散点图
x = climate_sub_china['year']
y = climate_sub_china['AverageTemperature']

fig, ax = plt.subplots(figsize=(10,5))
ax.scatter(x, y)
plt.show()


2)检查缺失情况

# 移除缺失值
climate.dropna(axis=0, inplace=True)

# 检查缺失个数
climate.isnull().sum()


3)变量类别转换

# 日期转换, 将dt 转换为日期,取年份, 注意map的用法
climate['dt'] = pd.to_datetime(climate['dt'])
climate['year'] = climate['dt'].map(lambda value: value.year)

# 只看中国
climate_sub_china = climate.loc[climate['Country'] == 'China']
climate_sub_china['Century'] = climate_sub_china['year'].map(lambda x:int(x/100 +1))
climate_sub_china.head()



? 02 特征增强

这一步其实就是数据清洗了,虽然上一步中也有涉及到部分清洗工作(比如清除空值、日期转换之类的),但却是分散的,这节重点讲讲数据清洗的一些技巧和实践代码,供大家在实际项目中去使用。


Step1: 进行EDA(Exploratory Data Analysis),思路如下:
(1)首先看看目标占比情况(针对二分类问题,也就是0和1的占比情况),直接 value_counts()就可以解决,看看样本是否失衡。
(2)接着看看有没有空值,直接统计 isnull().sum() 的个数,不过需要注意的是,可能统计出来没有缺失,并不是因为真的没有缺失,而且缺失被人用某个特殊值填充了,一般会用 -9、blank、unknown、0之类的,需要注意⚠️识别,后面需要对缺失进行合理填充。
(2.1)怎么识别缺失值呢?一般可以通过 data.describe() 获取基本的描述性统计,根据均值、标准差、极大极小值等指标,结合变量含义来判断。

640?wx_fmt=png


(3)再接着看不同类别之间的特征值分布情况,可通过画直方图(数值型变量)和计算变量值占比分布(类别变量)来观察。
(4)观察不同变量之间的相关性情况,可以通过绘制 相关矩阵的热力图 来观察大体情况。


Step2: 处理数据缺失问题
缺失处理的办法有好多种,但最为常用的作者讲到有两种:填充和删除。
而在处理缺失前,我们在上面的小节中识别出来了部分被人工填充的缺失,

需要还原一下:

# 处理被错误填充的缺失值0,还原为 空(单独处理)
pima['serum_insulin'] = pima['serum_insulin'].map(lambda x:x if x !=0 else None)
# 检查变量缺失情况
pima['serum_insulin'].isnull().sum()


# 批量操作 还原缺失值
columns = ['serum_insulin','bmi','plasma_glucose_concentration','diastolic_blood_pressure','triceps_thickness']

for col in columns:
   pima[col].replace([0], [None], inplace=True)

# 检查变量缺失情况
pima.isnull().sum()


1) 删除含有缺失值的行
这里的话比较简单,就是使用 dropna() 来处理即可,同时我们还可以检查下我们到底删除了多少数据量:round(data.shape[0]-data_dropped.shape[0])/float(data.shape[0]) 就可以统计出来了。当然,删除之后,我们还需要看看数据的分布,对比目标占比、特征分布与先前的是否存在明显差异,如果是的话,建议不要使用这种办法。

640?wx_fmt=png


2) 缺失值合理填充
缺失填充,这里介绍的有均值填充、-9填充、中位数填充。这里会比较简单,我们可以通常都是通过 sklearn的 Pipeline以及 Imputer来实现,下面是一个简单的完整

Demo:

# 使用sklearn的 Pipeline以及 Imputer来实现缺失值填充
from sklearn.pipeline import Pipeline
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import Imputer

# 调参候选
knn_params = {'classify__n_neighbors':[1,2,3,4,5,6]}

# 实例化KNN模型
knn = KNeighborsClassifier()

# 管道设计
mean_impute = Pipeline([('imputer', Imputer(strategy='mean')),
                      ('classify',knn)
                      ])

x = pima.drop('onset_disbetes', axis=1) # 丢弃y
y = pima['onset_disbetes']

# 网格搜索
grid = GridSearchCV(mean_impute, knn_params)
grid.fit(x, y)

# 打印模型效果
print(grid.best_score_, grid.best_params_)
# 0.73177


Step3: 标准化和归一化

经过上面的处理,模型的精度可以达到0.73177,但我们可以继续优化吗?那是肯定的。

我们可以先看看所有特征的分布(特征少的时候可以这么看):

pima_imputed_mean.hist(figsize=(15,15))

640?wx_fmt=png


从上图中我们可以看出一个问题,那就是每个特征之间的量纲都是不一样的,这对于knn这种基于距离的模型来说是“致命”的bug,因此我们需要进行标准化和归一化处理。


我们重点关注3种方法:

1)Z分数标准化

最为常用的标准化技术,利用了统计学中的z分数思想,也就是将数据转换为均值为0,标准差为1的分布,其在python中的调用方法:

# z分数标准化(单一特征)
from sklearn.preprocessing import StandardScaler
# 实例化方法
scaler = StandardScaler()
glucose_z_score_standarScaler = scaler.fit_transform(pima[['plasma_glucose_concentration']].fillna(-9))
# 可以看看转换之后的均值和标准差是否为0和1
glucose_z_score_standarScaler.mean(), glucose_z_score_standarScaler.std()


# z分数标准化(全部特征)
from sklearn.preprocessing import StandardScaler
# 实例化方法
scaler = StandardScaler()
pima_imputed_mean_scaled = pd.DataFrame(scaler.fit_transform(pima_imputed_mean), columns=pima_columns)
# 看下标准化之后的分布
pima_imputed_mean_scaled.hist(figsize=(15,15), sharex=True)


# 在Pipeline中使用
model = Pipeline([
  ('imputer', Imputer()),
  ('standardize', StandarScaler())
])


2)min-max标准化

min-max标准化和z-score类似,其公式为:(X - Xmin)/(Xmax - Xmin)

在python中的调用方法:

# min-max标准化
from sklearn.preprocessing import MinMaxScaler
# 实例化方法
min_max = MinMaxScaler()
# 使用min-max标准化
pima_min_maxed = pd.DataFrame(min_max.fit_transform(pima.fillna(-9)), columns=pima_columns)


3)行归一化

行归一化针对的是每一行数据,不同于上面的两种方法(针对列),对行进行处理是为了保证每行的向量长度一样(也就是单位范围,unit norm),有L1、L2范数。

在python中的调用方法:

# 行归一化
from sklearn.preprocessing import Normalizer
# 实例化方法
normalize = Normalizer()
# 使用行归一化
pima_normalized = pd.DataFrame(normalize.fit_transform(pima.fillna(-9)), columns=pima_columns)
# 查看矩阵的平均范数(1)
np.sqrt((pima_normalized**2).sum(axis=1)).mean()


640?wx_fmt=png


640?wx_fmt=gif


? 精彩回顾

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值