《精通特征工程》笔记

文章详细介绍了特征工程在机器学习中的重要步骤,包括数据清理、特征选择和转换。讨论了数值型数据的处理,如二值化和区间量化,以及对数变换和特征缩放。此外,还涉及了文本数据的处理,如词袋模型、tf-idf,以及分类变量的编码方法,如one-hot编码和虚拟编码。文章强调了特征工程在提高模型性能和降低计算成本中的作用。
摘要由CSDN通过智能技术生成

封面图

在这里插入图片描述

1 机器学习流程

1.1 数据特性

  • 错误数据:测量时的错误
  • 冗余数据:对同一数据的多次表述
  • 缺失数据

1.2 特征

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

1.3 特征工程

在给定数据、模型和任务的情况下设计出最合适的特征

特征工程在机器学习流程中的位置

2 数值型数据

2.1 二值化

问题:听歌的场景下,不能认为收听次数多就认为他很喜欢,原始的收听次数并不是衡量用户喜好的强壮指标(在统计学术语中,“强壮”意味着该方法适用于各种情况。)

**解决方法:**更强壮的用户偏好表示方法是将收听次数二值化,把所有大于1的次数值设为1。换言之,如果用户收听了某首歌曲至少一次,那么就认为该用户喜欢该歌曲。这样,模型就不用花费开销来预测原始收听次数之前的时间差别。

二值目标变量是一个既简单又强壮的用户偏好衡量指标。

2.2 区间量化(分箱)

点评数量直方图

**问题:**商家点评的场景下,点评的数量跨越好几个数量级,对于线性模型来说是个问题,对于聚类来说,过高的计数值相似度影响会远超其他元素

**解决方法:**区间量化,将连续的数值映射成离散数值分到不同的箱子里

  • 固定宽度分箱
  • 分位数分箱
固定宽度分箱

例如小区间可以用除法处理,跨若干数量级可以用对数映射(例如:最好按照10的幂(或任何常数的幂)来进行分组:09、1099、100999、10009999)

import numpy as np

small_counts = np.random.randint(0, 100, 20)
# 通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是0~9
np.floor_divide(small_counts, 10)

# 结果:
# array([0, 8, 5, 0, 9, 8, 0, 6, 9, 9, 8, 2, 7, 1, 6, 7, 8, 9, 8, 5])

# 横跨若干数量级的计数值数组
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))

# 结果:
# array([2., 3., 4., 1., 0., 2., 2., 3., 3., 4., 4., 1., 1., 3., 2., 2., 4.])
分位数分箱

例如中位数可以把数据分成两半,左小右大,四分位数将数据四等分,十分位数将数据十等分(数据的数量等分)
十等分
使用pandas可以计算分位数

import pandas as pd

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

# 结果:
# array([1, 2, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0, 2, 1, 0, 3])

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

# 结果:
# 0.25     122.0
# 0.50     926.0
# 0.75    8286.0
# dtype: float64

2.3 对数变换

对数函数可以对大数值的范围进行压缩,对小数值的范围进行扩展

对数变换前(上)后(下)的Yelp商家点评数量比较
对数变换前(上)后(下)的Mashable新闻文章单词数比较

2.4 特征缩放/归一化

min-max缩放

x为一个独立的特征值(即某个数据点中的特征值),min-max缩放可以将其值压缩到[0, 1]区间

x − m i n ( x ) m a x ( x ) − m i n ( x ) \frac{x-min(x)}{max(x)-min(x)} max(x)min(x)xmin(x)

特征标准化/方差缩放

x ~ = x − m e a n ( x ) s q r t ( v a r ( x ) ) \tilde{x} = \frac{x-mean(x)}{sqrt(var(x))} x~=sqrt(var(x))xmean(x)

先减去特征的均值(对所有数据点),再除以方差。缩放后的特征均值为0,方差为1

如果初始特征服从高斯分布,那么缩放后的特征也服从高斯分布

方差缩放

l2归一化

l2特征归一化示意图

三种方法对比

import pandas as pd
import sklearn.preprocessing as preproc

cwd = os.getcwd()
# 加载在线新闻流行度数据集
df = pd.read_csv(cwd + '/OnlineNewsPopularity.csv', delimiter=', ')
# 查看原始数据——文章中的单词数量
df['n_tokens_content'].values

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

# 结果:
# array([0.02584376, 0.03009205, 0.02489969, ..., 0.05215955, 0.08048147, 0.01852726])

# 标准化——注意根据标准化的定义,有些结果会是负的
df['standardized'] = preproc.StandardScaler().fit_transform(df[['n_tokens_content']])
df['standardized'].values

# 结果:
# array([-0.69521045, -0.61879381, -0.71219192, ..., -0.2218518 , 0.28759248, -0.82681689])

# L2-归一化
df['l2_normaalized'] = preproc.normalize(df[['n_tokens_content']], axis=0)
df['l2_normaalized'].values

# 结果:
# array([0.00152439, 0.00177498, 0.00146871, ..., 0.00307663, 0.0047472 , 0.00109283])

原始及缩放后的新闻文章单词数量。注意只有x轴的尺度发生了变化,特征缩放后的分布形状保持不变

结论
当一组输入特征的尺度相差很大时,需要进行特征缩放,否则输入特征的尺度差别非常大,就会对模型训练算法带来数值稳定性方面的问题

2.5 交互特征

两个特征的乘积可以组成一对简单的交互特征,这种相乘关系可以用逻辑操作符 AND 来类比,它可以表示出由一对条件形成的结果:“该购买行为来自于邮政编码为 98121 的地区”AND“用户年龄在 18 和 35 岁之间”。

例如将简单的线性模型
y = w 1 x 1 + w 2 x 2 + . . . + w n x n y = w_{1}x_{1} + w_{2}x_{2} + ... + w_{n}x_{n} y=w1x1+w2x2+...+wnxn
扩展成
y = w 1 x 1 + w 2 x 2 + . . . + w n x n + w 1 , 1 x 1 x 1 + w 1 , 2 x 1 x 2 + w 1 , 3 x 1 x 3 + . . . y = w_{1}x_{1} + w_{2}x_{2} + ... + w_{n}x_{n} + w_{1,1}x_{1}x_{1} + w_{1,2}x_{1}x_{2} + w_{1,3}x_{1}x_{3} + ... y=w1x1+w2x2+...+wnxn+w1,1x1x1+w1,2x1x2+w1,3x1x3+...

使之包含输入特征的两两组合,这样,就可以捕获特征之间的交互作用,这些特征对就称为交互特征

部分代码,使用单一特征和使用交互特征后的score

>>> 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)
# 在两个特征集上训练模型并比较R方分数
>>> (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: %0.5f" % r1)
>>> print("R-squared score with pairwise features: %0.10f" % r2)
R-squared score with singleton features: 0.00924
R-squared score with pairwise features: 0.0113276523

结论
通过实验,与单一特征相比,交互特征使准确率有了一定提升,但线性模型中包含有交互特征对,那它的训练时间和评分时间就会从 O ( n ) O(n) O(n)增加到 O ( n 2 ) O(n^2) O(n2),其中n是单一特征的数量

2.6 特征选择

特征选择为了精简无用的特征,降低最终模型的复杂度,特征选择并不是为了减少训练时间,而是为了减少模型评分时间

  • 过滤
  • 打包方法
  • 嵌入式方法
过滤

可以计算出每个特征与响应变量质检的相关性或互信息,过滤掉那些在某阈值之下的特征,但是这种做法并没有考虑我们要使用的模型,所以需要谨慎使用,以免在有用特征进入到模型训练阶段之前不经意地将其删除

打包方法(理解困难)

这些技术的成本非常高昂,但它们可以试验特征的各个子集,这意味着我们不会意外地删除那些本身不提供什么信息但和其他特征组合起来却非常有用的特征。打包方法将模型视为一个能对推荐的特征子集给出合理评分的黑盒子。它们使用另外一种方法迭代地对特征子集进行优化。

嵌入式方法(理解困难)

这种方法将特征选择作为模型训练过程的一部分。例如,特征选择是决策树与生俱来的一种功能,因为它在每个训练阶段都要选择一个特征来对树进行分割。另一个例子是ℓ1 正则项,它可以添加到任意线性模型的训练目标中。ℓ1 正则项鼓励模型使用更少的特征,而不是更多的特征,所以又称为模型的稀疏性约束。嵌入式方法将特征选择整合为模型训练过程的一部分。它们不如打包方法强大,但成本也远不如打包方法那么高。与过滤技术相比,嵌入式方法可以选择出特别适合某种模型的特征。从这个意义上说,嵌入式方法在计算成本和结果质量之间实现了某种平衡。

3 文本数据:扁平化、过滤和分块

3.1 词袋

词袋特征化中,一篇文本文档会被转化为一个计数向量

词袋向量中,每个单词都是向量的一个维度,若词汇表中有n个单词,那么一篇文档就是n维空间中的一个点

3.2 n元词袋

n元词袋(bag-of-n-grams)是词袋的一种自然扩展。n-gram(n 元词)是由 n 个标记(token)组成的序列。1-gram 就是一个单词(word),又称为一元词(unigram)。经过分词(tokenization)之后,计数机制会将单独标记转换为单词计数,或将有重叠的序列作为 n-gram 进行计数

例如,句子“Emma knocked on the door”会生成 n-gram“Emma knocked”“knocked on”“on the”和“the door”

n-gram能够更多保留文本中的初始序列结构,以2-gram为例,有k个不同的单词,就会有 k 2 k^2 k2个不同的2-gram,成本越高

3.3 使用过滤获取清洁特征

  • 停用词
  • 基于频率过滤(高频词和罕见词)
  • 词干提取
罕见词

罕见词不仅无法作为预测的凭据,还会增加计算上的开销。Yelp 点评数据集中有 160 万条点评数据,包括 357481 个单词(根据空格和标点符号进行分词),其中有 189 915 个单词只出现在一条点评中,有 41162 个单词出现在两条点评中。词汇表中 60% 以上的词都是罕见词。这就是所谓的重尾分布,在实际数据中这种分布屡见不鲜。很多统计机器学习模型的训练时间是随着特征数量线性增长的,但有些模型则是平方增长或更糟糕。罕见词带来了很大的计算和存储成本,却收效甚微

**结论:**常见词使用本身计数,使用停用词过滤,罕见词可以不做区分统一放进垃圾箱特征中
带垃圾箱的词袋特征向量

词干提取

将不同变体映射成同一个单词,例如"flower"和"flowers",“swimmer"和"swimming”,文本解析的效果更好

from nltk.stem.porter import PorterStemmer

stemmer = PorterStemmer()

print(stemmer.stem('zeroes'))
print(stemmer.stem('news'))
print(stemmer.stem('swimming'))

# 结果:
# zero
# news
# swim

结论
文中提出,“news”和“new”都会被提取为“new”,类似的例子也有不少,所以词干提取并不是非做不可

总结:
n元词袋是词袋的自然推广,可以生成不同的n元词,也增加了特征储存成本,通常使用二元词和三元词

只是简单介绍了一下文本特征化技术,过滤,还有n元词和搭配提取,将它们作为一种向扁平向量中添加一点结构的方法

4 从词袋到tf-idf

4.1 tf-idf

词频-逆文档频率

词频
b o w ( w , d ) = 单词 w 在文档 d 中出现的次数 bow(w, d) = 单词 w 在文档 d 中出现的次数 bow(w,d)=单词w在文档d中出现的次数

逆文档频率
N / ( 单词 w 出现在其中的文档数量 ) N / ( 单词 w 出现在其中的文档数量 ) N/(单词w出现在其中的文档数量)

若一个单词出现在很多文档中,逆文档频率就接近1,如果只出现在少数文档中,则会高

tf-idf
t f − i d f ( w , d ) = b o w ( w , d ) ∗ l o g ( N / ( 单词 w 出现在其中的文档数量 ) ) tf-idf(w, d) = bow( w, d ) * log( N / ( 单词 w 出现在其中的文档数量 ) ) tfidf(w,d)=bow(w,d)log(N/(单词w出现在其中的文档数量))

加入log,使得高频率出现的单词计数归零,只出现在少数文档的单词计数被放大

4.2 tf-idf方法测试

做完 tf-idf 后再进行 l2 归一化等同于只做 l2 归一化。所以,我们只需要测试三组特征:词袋、tf-idf,以及词袋基础上的 l2 归一化

实验结果为
交叉验证分类器准确率的平均得分

结论(理解困难)
特征缩放(包括 ℓ2 归一化和 tf-idf)的真正用武之地是加快解的收敛速度,正确的特征缩放有助于分类问题。正确缩放可以突出有信息量的单词,并削弱普通单词的影响。它还可以减少数据矩阵的条件数。正确的缩放不一定是标准的列缩放

5 分类变量

分类变量通常都不是数值型的,例如眼睛的颜色可以是“黑色”“蓝色”和“褐色”,所以需要一种编码方法来将非数值型的类别转换为数值

5.1 one-hot编码

表示3个城市的分类变量的one-hot编码

one-hot编码所有位的和必须等于1

5.2 虚拟编码

one-hot编码有k个自由度,但变量本身只要k-1个自由度,所以虚拟编码只使用了k-1个特征,也就是没有被使用的那个就用全0向量表示

表示3个城市的分类变量的虚拟编码

5.3 效果编码

效果编码与虚拟编码非常相似,区别在于参照类是用全部由-1 组成的向量表示的

表示3个城市的分类变量的效果编码

它的线性回归模型更容易解释

结论

  1. one-hot编码使得特征的不同线性组合可以做出相同的预测,非唯一性难以解释
  2. 虚拟编码没有冗余,但有些不直观
  3. 效果编码有一行(参照类)全由-1组成的密集向量,计算和存储成本较高,所以像 Pandas 和 scikit-learn更喜欢使用虚拟编码或one-hot 编码

5.4 特征散列化

  • 散列函数是一种确定性函数,它可以将一个可能无界的整数映射到一个有限的整数范围[1, m] 中
  • 可能有多个值被映射为同样的输出,称为碰撞
  • 均匀散列函数可以确保将大致相同数量的数值映射到m个分箱中
  • 散列函数在保持特征空间的同时,又可以在机器学习的训练和评价周期中减少存储空间和处理时间

散列化可以减少存储空间并可以将基数很大的特征映射到较低的维度中,避免了特征冗余或者维度爆炸的问题,但失去了可解释性,只是初始特征的某种聚合

可以参考特征工程中对高基数类别特征的一种处理方法:特征哈希(FeatureHasher)

5.5 分箱计数


从广告点击率预测到硬件分支预测,很多应用都对它进行了改造并使用。
不使用分类变量的值作为特征,而是使用目标变量取这个值的条件概率。换句话说,我们不对分类变量的值进行编码,而是要计算分类变量值与要预测的目标变量之间的相关统计量。

分箱计数将一个分类变量转换为与其值相关的统计量,它可以将一个大型的、稀疏的、二值的分类变量表示(如one-hot 编码生成的结果)转换为一个小巧的、密集的、实数型的数值表示。

5.6 如何处理稀有类

1)back-off:
将所有稀有类的计数累加到一个特殊分箱中的简单技术。如果类别的计数大于一个确定的阈值,那么就使用它自己的计数统计量;否则,就使用back-off 分箱的统计量。
2)最小计数图:
不管是稀有类还是频繁类,所有类别都通过多个散列函数进行映射,每个散列函数的输出范围m 都远远小于类别数量k。在计算统计量时,需要使用所有散列函数进行计算,并返回结果中最小的那个统计量。

5.7 防止数据泄露

数据泄露会使模型中包含一些不应该有的信息,这些信息可以使模型获得某种不现实的优势,从而做出更加精确的预测。出现数据泄露有多种原因,比如测试数据泄露到训练数据中,或者未来数据泄露到过去数据中。只要模型获得了在生产环境中实时预测时不应该接触到的信息,就会发生数据泄露。
为了防止出现这个问题,需要严格隔离计数收集(用来计算分箱计数统计量)和训练,使用过去的数据点进行计数,使用当前的数据点进行训练(将分类变量映射到我们前面收集到的历史统计量上),再使用未来的数据点进行测试。这可以解决数据泄露问题,但会引发前面提过的流程延迟问题(输入统计量以及模型会滞后于当前数据)。

5.8 无界计数

通常更好的做法是使用归一化后的计数,这样就可以保证把计数值限制在一个可知的区间中。
另一种方法是进行对数变换,这样可以强加一个严格的边界,但当计数值非常大时,变换结果的增加速度是非常慢的

5.9 总结

6 数据降维

PCA主成分分析,核心思想是,使用一些新特征代替冗余特征,这些新特征能恰当地总结初始特征空间中包含的信息

6.1 整个过程

(由于机器学习已经学习过了,所以不再看数学推导)
PCA 示意图:(a) 特征空间中的初始数据;(b) 数据中心化;(c) 将数据向量 x 投影到另一个向量 v 上;(d) 投影坐标方差最大的方向(等于 XTX 的主特征向量)

6.2 PCA实现

步骤

PCA的缺点:

  1. 转换过程复杂
  2. 得到的结果难以解释
  3. 计算成本昂贵
  4. SVD流式计算、SVD更新、根据子样本计算SVD非常困难
  5. 由于巨大的异常值的存在,最好不要对原始计数使用PCA
  6. 对于数量超过几千的特征,PCA的计算成本非常昂贵
  7. PCA转换丢弃了数据中的一些信息,下游模型的训练成本更低,但精确度也下降了

6.3 应用例子(copy)

PCA 最精彩的应用之一是在时间序列中进行异常检测。Lakhina 等人使用 PCA 在互联网流量中检测和诊断异常。他们重点关注流量异常,即什么时候从一个网络区域到另一个网络区域的流量会发生剧烈的变化。这些突变可能就是网络错误配置或拒绝服务攻击的信号,不管是哪一种情况,知道这种变化在什么时候和在哪里发生对于互联网管理员来说都是非常有价值的。因为互联网上的流量太多了,所以小型区域内的孤立突变很难探测,但多数流量都是在一个相对较小的主干链接集合中处理的。关键在于,流量异常会同时影响多个链接(因为数据包要经过多个节点才能到达它们的目标)。我们可以将每个链接作为一个特征,并将每个时间段的流量作为测量值,那么一个数据点就是在一个时间片内通过网络上所有链接的流量的测量。这种矩阵的主成分可以表明网络上的整体流量趋势,余下的成分表示还有残差信号,其中就包括异常。

6.4 总结

  • 关于PCA的主要两点是它的原理(线性投影)和目标(最大化投影数据的方差)
  • PCA的解要用到协方差矩阵的特征值分解,它与数据矩阵的SVD 联系非常紧密
  • 适合应用在预处理阶段,特别是特征之间存在线性相关性的时候

7 非线性特征化与k-均值模型堆叠

当数据位于一个薄饼状的线性子空间时,PCA是很有用的,但是像瑞士卷一样非线性流行空间,就需要通过非线性数据降维的方式将其展开,恢复二维平面,非线性数据降维也称为非线性嵌入流形学习。非线性嵌入可以非常有效地将高维数据压缩为低维数据,常用于二维空间或三维空间中的可视化

7.1 k-均值聚类

import numpy as np
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
n_data = 1000
seed = 1
n_clusters = 4
n_centers = 4
# 生成符合高斯随机分布的点团,并运行k-均值算法
blobs, blob_labels = make_blobs(n_samples=n_data, n_features=2, centers=n_centers, random_state=seed)
clusters_blob = KMeans(n_clusters=n_centers, random_state=seed).fit_predict(blobs)
# 生成完全随机的数据,并运行k-均值算法
uniform = np.random.rand(n_data, 2)
clusters_uniform = KMeans(n_clusters=n_clusters, random_state=seed).fit_predict(uniform)
# 对结果进行可视化的Matplotlib代码
figure = plt.figure()
plt.subplot(221)
plt.scatter(blobs[:, 0], blobs[:, 1], c=blob_labels, cmap='gist_rainbow')
plt.title("(a) Four randomly generated blobs", fontsize=14)
plt.axis('off')
plt.subplot(222)
plt.scatter(blobs[:, 0], blobs[:, 1], c=clusters_blob, cmap='gist_rainbow')
plt.title("(b) Clusters found via K-means", fontsize=14)
plt.axis('off')
plt.subplot(223)
plt.scatter(uniform[:, 0], uniform[:, 1])
plt.title("(c) 1000 randomly generated points", fontsize=14)
plt.axis('off')
plt.subplot(224)
plt.scatter(uniform[:, 0], uniform[:, 1], c=clusters_uniform, cmap='gist_rainbow')
plt.title("(d) Clusters found via K-means", fontsize=14)
plt.axis('off')

from mpl_toolkits.mplot3d import Axes3D
from sklearn import manifold, datasets

# 生成带噪声的瑞士卷数据集
X, color = datasets._samples_generator.make_swiss_roll(n_samples=1500)
# 使用100个k-均值簇对数据进行近似
clusters_swiss_roll = KMeans(n_clusters=100, random_state=1).fit_predict(X)
# 使用k-均值簇ID作为颜色来绘制数据集
fig2 = plt.figure()
ax = fig2.add_subplot(111, projection='3d')
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=clusters_swiss_roll, cmap='Spectral')

7.2 模型堆叠

使用k-均值将空间数据转换为特征是模型堆叠的一个实例,其中一个模型的输入是另一个模型的输出。另一个堆叠实例是使用决策树类型模型(随机森林或梯度提升树)的输出作为线性分类器的输入。近年来,模型堆叠已经成为了一种越来越流行的技术,因为训练和维护非线性模型的成本非常高昂。堆叠的核心思想是将非线性放入特征中,再使用一种非常简单的、通常是线性的模型作为最后一层。特征生成器可以在线下训练,这意味着我们可以使用昂贵的、需要更多计算能力和内存的模型来生成有用的特征。位于顶层的简单模型可以快速适应在线数据中快速的分布变化。这是一种精确度和速度之间的权衡,这种策略通常使用在像定向广告这样需要快速适应数据分布变化的应用中

模型堆叠的核心思想

先使用复杂的基础层(通常带有昂贵的模型)生成良好的(通常是非线性的)特征,再与简单快速的顶层模型组合起来。这样通常能实现模型准确率和速度之间的正确平衡

8 自动特征生成:图像的特征提取和深度学习

(学过的内容略过)

SIFT 体系——原始图像中一块感兴趣区域的特征向量生成步骤

9 建立学术论文推荐器

参考链接:回到特征:将它们放到一起.ipynb

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值