2022-02-08 读书笔记:《精通特征工程》1 基础特征工程

《精通特征工程》——速读学习1 基础特征工程

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

time: 2022-02-08

学习目标:“通过Python示例掌握特征工程基本原则和实际应用,增强机器学习算法效果”;

目录

  • 第1章 机器学习流程:数值 模型 特征
  • 第2章 简单而奇妙的数值:数值型数据的基础特征工程,过滤 分箱 缩放 对数变换和幂次变换,以及交互特征
  • 第3章 文本数据:扁平化、过滤和分块:自然文本特征工程,研究词袋 n-gram 短语检测技术
  • 第4章 特征缩放的效果:从词袋到tf-idf:tf-idf(词频-逆文档频率),特征缩放
  • 第5章 分类变量:自动化时代的数据计数:分类变量编码技术,特征散裂化和分箱计数
  • 第6章 数据降维:使用PCA挤压数据:主成分分析PCA
  • 第7章 非线性特征化与-均值模型堆叠:k-均值聚类,模型堆叠
  • 第8章 自动特征生成:图像特征提取和深度学习:图像处理及其特征提取 手动提取技术——SIFT和HOG,最新的特征提取技术——深度学习
  • 第9章 回到特征:建立学术论文推荐器:实例
  • 附录A 线性建模与线性代数基础

前言

为了提取知识和做出预测,机器学习使用数学模型来拟合数据;输入即特征,指原始数据某些方面的数值表示(注意:原始数据不一定是数值型数据);

特征工程:指从原始数据中提取特征并将其转换为适合机器学习模型的格式;是机器学习中的一个环节;

建立机器学习流程的绝大部分时间都耗费在特征工程和数据清洗上;

本书每章阐述了一个数据问题,结合这些问题,对特征工程的一些基本原则进行说明;

  • Numpy:数值向量和矩阵操作
  • Pandas:数据框,作为数据科学的基础数据结构
  • scikit-learn:是一个通用的机器学习包,包含大量模型和特征转换模型
  • Matplotlib和样式库Seaborn:提供绘图和可视化支持

第1章 机器学习流程

数据

数据反应小部分现实,综合起来才能得到完整的描述;描述散乱,由千万小段组成,而且总是存在测量噪声和缺失值;

任务

数据处理流程往往是多阶段的迭代过程;

两个构成机器学习基础的数学实体:模型和特征;

模型

统计模型:错误数据、冗余数据、缺失数据;

特征

特征是原始数据的数值表示;正确的特征应该适合当前的任务,并易于被模型所使用;

特征工程就是在给定数据、模型和任务的情况下设计出最适合的特征的过程;

模型评价

模型和特征相辅相成,对其中一个的选择会影响另一个;好的特征可以使建模更容易;

  • 数据源N收集数据,作为原始数据;
  • 原始数据经过清洗和转换,得到特征(这一步就是特征工程);
  • 通过建模,从特征中提取知识;

第2章 简单而又奇妙的数值

数值型数据作为最简单的数据类型(相较于文本、图像而言),也最容易被数学模型所使用,但往往数值型数据也需要进行特征工程;

好的特征既能表示出数据的主要特点,还应该符合模型的假设,因而通常需要进行数据转换;

数值型数据的特征工程很基本,只要原始数据可以转换为数值型特征,就可以应用这些技术;

合理性检测

  • 数据量级;
  • 特征尺度:最大值、最小值、跨度(是否跨多个数量级);

模型是输入特征的平滑函数,那么它对输入的尺度很敏感:k-均值、最近邻、径向基核函数,以及所有使用欧式距离的方法都属于这种,对于这类模型和模型成分,通常需要对特征进行标准化,以便将输出控制在期望的范围内;

逻辑函数则对输入特征的尺度并不敏感;无论输入如何,这种函数输出总是一个二值变量;另一个例子是阶梯函数(决策树模型中使用了输入特征的阶梯函数);

基于空间分割树的模型(决策树、梯度提升机、随机森林)对尺度是不敏感的;但如果特征是某种累计值,最终可能会超出训练树的取值范围,因此需要定期对输入尺度进行调整;

  • 数值型特征的分布;

这种分布表现出一个特征值出现的概率;输入特征的分布对于某些模型来说十分重要;

线性回归模型的训练过程,需要假定预测误差近似地服从高斯分布;在预测目标分布在多个数量级中时,误差符合高斯分布的假定将不会被满足,一种解决方法是对输出目标进行转换,如对数变换就可以使变量的分布更加接近于高斯分布;

除了将特征转换为模型所需或训练假设,还可以将多个特征进行组合;我们希望特征更具信息量(更简洁的捕获原始数据中的重要信息),这样可以使模型本身更简单,更容易训练和评价,也能做出更好的预测;

极端情况,可以使用统计模型的输出作为复杂特征,这种思想称为模型堆叠

标量、向量和空间

  • 标量:单独的数值型特征
  • 向量:标量的有序列表
  • 向量空间:向量位于向量空间中

模型的输入通常表示为 数值向量

在数据世界中,抽象的向量和它的特征维度具有实际意义,所有数据的集合可以在特征空间中形象地表示为一个点云;与在特征空间(使用特征维度)表示数据类似,也可以在数据空间(使用数据维度)中进行特增表示;

处理计数

  • 当数据被大量且快速地生成时,很有可能包含一些极端值,因此需要检查数据尺度;以确定是保留原始数值,还是转成二值数据,或者进行粗粒度的分箱操作;

二值化

  • 一个例子是百万歌曲数据集中的收听次数数据,多的接近1万,少的在0附近,从直方图上可以得到 收听次数越多的范围,记录数量越小,而且带有严重的偏置,如果视图使用实际的收听次数去预测歌曲的受欢迎程度,模型会被严重带偏;
  • 这可理解为,原始的收听次数并不是衡量用户喜好的强壮指标;(如 我们不能认为收听了某歌曲20次的人喜欢该歌的程度肯定是收听了10次的人的两倍)

一种更强壮的用户偏好表示方法是 将收听次数二值化

  • 如把所有大于1的次数值设为1;
  • 二值目标变量是一个简单又强壮的用户偏好衡量指标;

这是一个对模型目标变量处理的例子,值得注意的是,目标变量并不是特征,因为它不是输入;但为了正确地解决问题,有时确实需要修改目标变量

区间量化(分箱)

  • 以商家评分数据集为例,点评数量是一个有用的输入特征,因为人气和高评分之间通常有很强的相关性;

原始点评数量的直方图可视化:

  • 大多数商家的点评数量很少,但是有些商家具有几千条点评;
  • 原始点评数量横跨若干个数量级,这对很多模型来说都是问题;
import pandas as pd
import json

biz_file - open("yelp_academic_dataset_business.json")
biz_df = pd.DataFrame([json.loads(x) for x in biz_file.readlines()])
biz.file.close()


# 
import matplotlib.pyplot as plt
import seaborn as sns

# 绘制点评数量直方图
sns.set_style('whitegrid')
fig, ax = plt.subplots()
biz_df["review_count"].hist(ax=ax, bins = 100)
ax.set_yscale('log')
ax.tick_params(labelsize=14)
ax.set_xlabel("Review Count", fontsize=14)
ax.set_ylabel("Occurrence", fontsize=14)

一种解决方法是对计数值进行区间量化,然后在使用量化后的结果:

  • 将点评数分到多个箱子里面,去掉实际的计数值;
  • 将连续型数值映射为离散型数值,这种离散型数值可以看作一种有序的分箱序列,以表示对密度的测量

对数据进行区间量化,首先需要确定每个分箱的宽度,通常有两种方法:

  • 固定宽度分箱
  • 自适应分箱

固定宽度分箱

  • 每个分箱会包含一个具体范围内的数值;
  • 这些范围可以人工定制,也可以自动分段,可以是线性的,也可以是指数性的;
  • 如 10(0-9,10-19。。。)或10的幂(0-9,10-99。。。)(也可以是任何其他常数)进行分组;

一个通过固定宽度分箱对计数值进行区间量化的代码段:

import numpy as np

small_counts = np.random.randint(0, 100, 20)

# 通过除法随机映射到间隔均匀的分箱中,每个分箱取值范围为0~9
np.floor_divide(small_counts, 10) # 整除 抛开余数

# 横跨若干个数量级的技术数组
large_counts = [296,8286,64011,80,3,725,867,2215,7689,11495,91897,...44,28,7971,926,122,22222]

np.floor(np.log10(large_counts)) # 向下取整

分箱操作实际是将原始数值 拉回同一量级 以便处理;

分位数分箱

  • 固定宽度分箱容易计算,但如果计数值中有比较大的缺口,就会产生没有任何数据的空箱子;
  • 根据数据的分布特点,进行自适应的箱体定位,就可以解决这个问题,具体的可以使用数据分布的分位数实现;

分位数是可以将数据划分为相等的若干份数的值;对应回直方图上,可以看出,分位数的数值是向较小的计数值偏斜的;

点评数量十分位数的代码示例:

import warnings

warnings.filterwarnings('ignore')

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns

small_counts = np.random.randint(0, 10000, 200)
small_counts_log = np.log10(small_counts)

biz_df = pd.DataFrame({'review_count':small_counts,'log_review_count':small_counts_log})


deciles = biz_df["review_count"].quantile([.1,.2,.3,.4,.5,.6,.7,.8,.9]) 

sns.set_style("whitegrid")
fig, ax = plt.subplots()
biz_df["review_count"].hist(ax=ax, bins=10)
# 在直方图上画出十个分位数
for pos in deciles:
    print('pos', pos)
    handle = plt.axvline(pos, color="r")
ax.legend([handle], ['deciles'], fontsize=14)
# ax.set_yscale('log')
# ax.set_xscale('log')
ax.tick_params(labelsize=14)
ax.set_xlabel("Review Count", fontsize=14)
ax.set_ylabel("Occurence", fontsize=14)

计算分位数并将数据映射到分位数分箱中,可以使用Pandas库:

  • pandas.DataFrame.quantilepandas.Series.quantile可以计算分位数;
  • pandas.qcut可以将数据映射为所需的分位数值;
import pandas as pd 

# 将计数值映射为分位数
pd.qcut(large_counts, 4 , labels=False)

# 计算实际的分位数值
large_counts_series = pd.Series(large_counts)
large_counts_series.quantile( [0.25,0.5,0.75] )


对数变换

之前简要提到了 通过取计数值的对数将数据映射到指数宽度分箱的方法,我们继续;

对数函数是指数函数的反函数:㏒a(b) = c, 则 a^c = b

  • 对数函数可以将(0,1)这个小区间的数映射到(-∞,0)这个包括全部负数的大区间上;
  • ``㏒10(x)可以将区间[1,10]映射到[0,1],将[10,100]映射到[1,2]`;
  • 换言之,对数函数可以对大数值的范围进行压缩,对小数值的范围进行扩展

对于重尾分布(质量更多的分布在尾部)的整数值的处理,对数变换是一个非常强大的工具;

经过对数变换,评论数据的直方图在低计数值的集中趋势会被减弱:

fig, (ax1, ax2) = plt.subplots(2,1)
biz_df["review_count"].hist(ax=ax1, bins=10)
ax1.tick_params(labelsize=14)
ax1.set_xlabel("review_count", fontsize=14)
ax1.set_ylabel("Occurrence", fontsize=14)


biz_df["log_review_count"].hist(ax=ax2, bins=10)
ax2.tick_params(labelsize=14)
ax2.set_xlabel('log10(review_count)', fontsize=14)
ax2.set_ylabel('Occurrence', fontsize=14)

实战过程:

  • 使用商家点评数量 来预测 商家的平均评分

  • 使用文章单词数量 来预测 文章流行程度

  • 预测输出是连续值,可以使用简单线性回归来构造模型;

  • 使用scikit-learn,对 进行了对数变换和未进行对数变换的特征上 进行10-折交叉验证的线性回归;

  • 使用R2分数评价模型,以衡量训练出来的回归模型预测新数据的能力(R方分数越大越好,完美模型的R方值为1,它也可以是负数,说明模型很糟糕);

# 实例1

import pandas as pd
import numpy as np

import json
from sklearn import linear_model
from sklearn.model_selection import cross_val_score

# 提前计算对数变换值,且对点评数量+1,以避免当点评数为0时,对数运算结果得到负无穷大
biz_df["log_review_count"] = np.log10(biz_df["review_count"] + 1)

# 训练线性回归模型
m_orig = linear_model.LinearRegression()
scores_orig = cross_val_score(m_orig, biz_df[["review_count"]], biz_df["stars"], cv=10)

m_log = linear_model.LinearRegression()
scores_log = cross_val_score(m_log, biz_df[["log_review_count"]], biz_df["stars"], cv=10)


print("R-squared score without log transform: %.5f (+/- %.5f)"%(scores_orig.mean(), scores_orig.std() * 2))
print("R-squared score with log transform: %.5f (+/- %.5f)"%(scores_log.mean(), scores_log.std() * 2))

# 平均星级(离散) 与 评价数量 的关系 原非线性

# 实例2

# 新闻流行程度预测问题 输入和输出相关性可视化
fig2, (ax1, ax2) = plt.subplots(2,1)
ax1.scatter(df["n_tokens_count"],df["shares"])
ax1.tick_params(labelsize=14)
ax1.set_xlabel("Number of Words in Article", fontsize=14)
ax1.set_ylabel('Number of Shares', fontsize=14)

ax2.scatter(df['log_n_tokens_count'],df["shares"])
ax2.tick_params(labelsize=14)
ax1.set_xlabel("Log of the Number of Words in Article", fontsize=14)
ax1.set_ylabel('Number of Shares', fontsize=14)

值得关注的是 数据的可视化十分重要,比如 上面选择的线性回归模型 与 可视化的图形中得出数据的输入和目标之间关系是否相符;在构建模型时,使用可视化方法查看一下输入和输出之间以及各个输入特征之间的关系是一种非常好的做法;

指数变换

  • 指数变换是一个变换族,对数变换只是它的一个特例;
  • 它们都是方差稳定化变换

泊松分布(了解):

  • 是一种重尾分布,它的方差等于它的均值;其质心越大,方差就越大,重尾程度也越大;
  • 指数变换可以改变变量的分布,使得方差不再依赖于均值;

λ表示泊松分布的均值,当其变大时,不仅整个分布向右移动,质量也更分散,方差随之变大;

平方根变换和对数变换都可以简单推广为Box-Cox变换:

  • = (x^λ - 1)/λ , 当(λ != 0)

  • = ln(x) , 当(λ = 0)

  • λ=0 即对数变换,λ=0.25或0.5(对应的是平方根变换的一种缩放和平移形式);

  • λ值小于1时,可以压缩高端值,大于1时,起的作用相反;

只有当数据为时,Box-Cox公式才有效;对非正数据,可以加一个固定常数,对数据进行平移;

可以通过极大似然方法(找到能使变换后信号的高斯似然最大化的λ值)找到λSciPystats包中有Box-Cox变换的实现方法,并包括找到最优BoxCox变换参数的功能;

from scipy import stats

# 检查最小值 Box-Cox假定输入数据为正
biz_df['review_count'].min()

rc_log = stats.boxcos(biz_df['review_count'], lmbda=0) # 指定 λ = 0,此时对应的是对数变换

# 默认 进行Box-Cox变换时 会找到使得输出最接近正太分布的λ值
rc_bc, bc_params = stats.boxcox(biz_df['review_count'])
bc_params # 即 最终的 λ 值

概率图(probplot)

  • 是一种简单的可视化方法,用于比较数据的实际分布与理论分布;
  • 实际上是一种表示实测分位数和理论分位数的关系的散点图;

原始点评数量的概率图具有明显的重尾特征,相比对数变换,最优Box-Cox变换对尾部的压缩更强:

prob1 = stats.probplot(biz_df['review_count'], dist=stats.norm, plot=ax1)

prob1 = stats.probplot(biz_df['rc_log'], dist=stats.norm, plot=ax2)

prob1 = stats.probplot(biz_df['rc_bc'], dist=stats.norm, plot=ax3)

更多的图形绘制信息 参考matplotlib

特征缩放/归一化

  • 有些特征的值 具有明显的界限,有些则可以无限增加;
  • 有些模型的输入是平滑函数,比如线性回归、逻辑回归、或包含矩阵的模型;
  • 那些基于树的模型,不在乎输入的尺度有多大;

如果模型对输入特征的尺度很敏感,就需要进行特征缩放;特征缩放可以改变特征的尺度,说是些人将之称为特征归一化

特征缩放通常对每个特征独立进行,一下是集中常用的特征缩放,他们会产生不同的特征分布;

min-max缩放:

  • min-max缩放 可以将所有特征值压缩(或扩展)到[0,1]之间;
  • 公式:x_n = (x-min)/(max-min)

特征标准化/方差缩放

  • 公式:x_n = (x - mean)/sqrt(var);
  • 先减去均值,再除以方差;
  • 缩放后的特征均值为0,方差为1;如果初始特征服从高斯分布,那么缩放后的特征也服从高斯分布;

在稀疏特征上执行min-max缩放和标准化的时候要慎重,他们都会从原始特征值中减去一个量;对于min-max缩放平移量是最小值,对于标准化,这个量是均值;
如果平移量不是0,那么这两种变换会将一个多数元素为0的稀疏特征向量变成密集特征向量;
词袋就是一种稀疏的表示方式,大多数分类算法的实现都针对稀疏输入进行了优化;

归一化

  • 将初始特征值除以一个被称为l2范数的量,l2范数又称为欧几里得范数
  • 定义:x_n = x / (||x||2)

l2范数是坐标空间中向量长度的一种测量;

  • l2 = ||x||2 = (x1^2 + x2^2 +... + xm^2)^(1/2)
  • 先对所有数据点中该特征的值进行平方求和,再计算平方根;
  • 经过l2规范化后,特征列的范数就是1,有时候这种处理也称为l2缩放;

注意:除了可以对特征进行l2归一化,也可以对数据点进行l2归一化,最终会得到带有单位范数(1)的数据向量;

特征缩放总是将特征除以一个常数(即归一化常数);因而他不会改变单特征分布的形状

as_matrix方法的作用:
很多时候在提取完数据后其自身就是数组形式(),这只是习惯性的谨慎。很多时候取得的数据是DataFrame的形式,这个时候要记得转换成数组;
x = datas.iloc[:,:].as_matrix() not predict
x = datas.iloc[:,:].values predict

特征缩放的示例代码:

import pandas as pd
import sklearn.preprocessing as preproc

df = pd.read_csv("OnlineNewsPopularity.csv", delimiter=", ")

print(df["n_token_content"].as_matrix())


# min-max缩放
df['minmax'] = preproc.minmax_scale(df[['n_token_content']])
df['minmax'].as_matrix() # 或 .values

# 标准化
df['standardized'] = preproc.StandardScaler().fit_transform(df[['n_token_content']])
df['standardized'].as_matrix() 

# L2归一化
df['l2_normalized'] = preproc.normalize(df[['n_token_content']], axis=0)
df['standardized'].as_matrix() 

当一组输入特征的尺度相差很大,就会对模型的训练算法带来数值稳定性方面的问题,因此需要对特征进行缩放;

交互特征

  • 两个特征的乘积可以组成一对简单的交互特征,这种关系类比与逻辑运算AND;

  • 表示由一对条件行程的结果:地区A and 年龄B

  • 这种特征在决策树中极其常见,广义线性模型中也常用;

  • y = w1x1 + ... + wnxn

  • y = w1x1 + ... + wnxn + w11x1x1 + w12x1x2 + ...

  • 这样可以捕获特征间交互作用,这些特征对 就称为交互特征

如果x1和x2是二值特征,那么他们的积就是逻辑与;

如果我们的问题是基于客户档案信息来预测客户偏好,那么除了更具用户年龄或地点这些单独的特征来进行预测,还可以使用交互特征来根据用户位于某个年龄段并位于某个特征的地点来进行预测;

# 交互特征示例

from sklearn import linear_model
from sklearn.model_selection import train_test_split
import sklearn.preprocessing as preproc

# 假设一个pandas数据框
df.columns # 包含许多特征列

# 选择与内容有关的特征作为模型的单一特征,忽略那些衍生特征
features = ['n_token_title','n_token_content','num_videos']

X = df[features]
y = df[['shares']]

# 创建交互特征 (跳过固定偏移项)
X2 = preproc.PolynomialFeatures(include_bias=False).fit_transform(X)

# 为两个特征集 创建 训练集和测试集
X1_train, X1_test, X2_train, X2_test, y_train_, y_test = train_test_split(X,X2,y, test_size=0.3, random_state=123)

def evaluate_feature(X_train, X_test, y_train, y_test):
  model = linear_model.LinearRegression().fit(X_train, y_train)
  r_score = model.score(X_test, y_test)
  return (model, r_score)

(m1, r1) =   evaluate_feature(X1_train, X1_test,y_train_, y_test)
(m2, r2) =   evaluate_feature(X2_train, X2_test,y_train_, y_test)

print("R-squared score with singleton features: %.5f"% r1)
print("R-squared score with pairwise features: %.10f"% r2)


交互特征虽然构造简单,但代价并不低,如果线性模型中包含有交互特征,那么它的训练时间复杂度会从O(N)增加到O(N^2),其中n是单一特征的数量;

精心设计的复杂特征需要昂贵的成本,所以数量不能太多,它们可以减少模型的训练时间,但特征本身会消耗很多计算能力,这增加了模型评分阶段的计算成本;(第8章会介绍若干复杂特征的示例)

特征选择

特征选择技术可以精简掉无用的特征,以降低最终模型的复杂性,它的最终目的是得到一个简约模型,在不降低预测准确率或对预测准确率影响不大的情况下提高计算速度;

为了得到这样的模型,有些特征选择技术需要训练不止一个优选模型,换言之,特征选择不是为了减少训练时间,而是为了减少模型评分时间;

粗略的特征选择技术可分为三类:

  • 过滤:过滤技术对特征进行预处理,以除去那些不太可能对模型有用的特征;
    • 计算每个特征与响应变量之间的相互性,然后过滤掉某个阀值之下的特征;
    • 过滤技术的使用需要谨慎,以免有些有用的特征在进入到模型训练阶段之前被不经意地删除;
  • 打包方法:
    • 打包方法将模型视为一个能对推荐的特征子集给出合理评分的黑盒子;
    • 它们可以试验特征的各个子集,这意味着我们不会意外地删除那些本身不提供什么信息但和其他特征组合起来却非常有用的特征;
    • 它们使用另外一种方法迭代地对特征子集进行优化;
  • 嵌入式方法:
    • 这种方法将特征选择作为模型训练过程的一部分;
    • 例如,特征选择是决策树与生俱来的一种功能,因为它在每个训练阶段都要选择一个特征来对树进行分割;

嵌入式方法不如打包方法强大,但成本远不如打包方法那么高;相比于过滤技术,嵌入式方法可以选择出特别适合某种模型的特征;从这个意义上说嵌入式方法在计算成本和结果质量之间实现了某种平衡

小结

几种常用的数值型特征工程技术:区间量化、缩放(即归一化)、对数变换(指数变换的一种)和交互特征;
并简要介绍了特征选择技术,它对于处理大量交互特征是必需的;

在统计机器学习中,所有数据最终都会转化为数值型特征;因此,所有特征工程最终都会归结为某种数值型特征工程技术。

你可能感兴趣的:(深度学习,学习,python,机器学习,特征工程)