不进行多余的解释,想看原文直接下载pdf查看,本文是精简提炼了重要的方法写进来。
1.二值化
在百万歌曲数据集中,原始的收听次数并不是衡量用户喜好的强壮指标。(在统计学术语
中,“强壮”意味着该方法适用于各种情况。)不同的用户有不同的收听习惯,有些人会无
限循环地播放他们最喜欢的歌曲,有些人则只是在特定情形下欣赏音乐。我们不能认为收
听了某首歌曲 20 次的人喜欢该歌曲的程度肯定是收听了 10 次的人的两倍。
更强壮的用户偏好表示方法是将收听次数二值化,把所有大于 1 的次数值设为 1,如例 2-1
所示。换言之,如果用户收听了某首歌曲至少一次,那么就认为该用户喜欢该歌曲。这
样,模型就不用花费开销来预测原始收听次数之前的时间差别。二值目标变量是一个既简
单又强壮的用户偏好衡量指标。
读取数据
import pandas as pd
listen_df = pd.read_csv(r"../data/train_triplets.txt",header=None,delimiter='\t')
listen_df.head(3)
# 表中包含有形式为“用户-歌曲-收听次数”的三元组。只包含非零收听次数
print(listen_df.columns)
listen_df[2].unique()
二值化处理:
# 二值化处理:
listen_df[2] = 1
listen_df[2].unique()
2.区间量化(分箱)
一般当数值数据之间的数量级差别比较大的时候,不利于计算、以及分箱对所研究的问题结果没有影响,可以对数据使用分箱得到新特征,新特征可代替原来的特征。
- 当然,可以使用多层分箱,不然的话数量级差别太多,导致这种差别的真实性消失。
import pandas as pd
import json
biz_file = open(r'../data/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)
原始的点评数量横跨了若干个数量级,这对很多模型来说都是个问题。在线性模型中,
同一线性系数应该对所有可能的计数值起作用。过大的计数值对无监督学习方法也会造
成破坏,比如 k-均值聚类,它使用欧氏距离作为相似度函数来测量数据点之间的相似度。
数据向量某个元素中过大的计数值对相似度的影响会远超其他元素,从而破坏整体的相
似度测量。
一种解决方法是对计数值进行区间量化,然后使用量化后的结果。换言之,我们将点评数
量分到多个箱子里面,去掉实际的计数值。区间量化可以将连续型数值映射为离散型数
值,我们可以将这种离散型数值看作一种有序的分箱序列,它表示的是对密度的测量。
为了对数据进行区间量化,必须确定每个分箱的宽度。有两种确定分箱宽度的方法:固定
宽度分箱和自适应分箱。
2.1固定宽度分箱
通过固定宽度分箱,每个分箱中会包含一个具体范围内的数值。这些范围可以人工定制,
也可以通过自动分段来生成,它们可以是线性的,也可以是指数性的。例如,我们可以按
10 年为一段来将人员划分到多个年龄范围中:0~9 岁的在分箱 1 中、10~19 岁的在分箱 2
中,等等。要将计数值映射到分箱,只需用计数值除以分箱的宽度,然后取整数部分。
import numpy as np
# 1.对于数量级比较小的数据来说的分箱
small_counts = np.random.randint(0, 100, 20)
print(small_counts)
# 通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是0~9
small_box = np.floor_divide(small_counts, 10)
small_box
# 2.对于数量级差别比较大的数据(横跨若干数量级的计数值数组)来说的分箱
large_counts = [296, 8286, 64011, 80, 3, 725, 867, 2215, 7689,
11495, 91897,44, 28, 7971, 926, 122, 22222]
print(large_counts)
np.floor(np.log10(large_counts))
2.2自适应分箱(分位数分箱)
固定宽度分箱非常容易计算,但如果计数值中有比较大的缺口,就会产生很多没有任何数
据的空箱子。根据数据的分布特点,进行自适应的箱体定位,就可以解决这个问题。这种
方法可以使用数据分布的分位数来实现。
分位数是可以将数据划分为相等的若干份数的值。例如,中位数(即二分位数)可以将数
据划分为两半,其中一半数据点比中位数小,另一半数据点比中位数大。四分位数将数据
四等分,十分位数将数据十等分,等等。
# 计算biz_df['review_count']的十分位数分别是什么
deciles = biz_df['review_count'].quantile([.1, .2, .3, .4, .5, .6, .7, .8, .9])
deciles
large_counts = [296, 8286, 64011, 80, 3, 725, 867, 2215, 7689,
11495, 91897,44, 28, 7971, 926, 122, 22222]
# 通过分位数对计数值进行分箱: 分成4个箱子: 0、1、2、3
pd.qcut(large_counts, 4, labels=False)
# 查看实际的分位数值是多少
large_counts_series = pd.Series(large_counts)
large_counts_series.quantile([0.25, 0.5, 0.75])
3.对数变换
对数函数是指数函数的反函数,它的定义是 log a (a x ) = x,其中 a 是个正的常数,x 可以是
任意正数。因为 a 0 = 1,所以有 log a (1) = 0。这意味着对数函数可以将 (0, 1) 这个小区间中
的数映射到 (-∞, 0) 这个包括全部负数的大区间上。函数 log 10 (x) 可以将区间 [1, 10] 映射到
[0, 1],将 [10, 100] 映射到 [1, 2],以此类推。换言之,对数函数可以对大数值的范围进行
压缩,对小数值的范围进行扩展。x 越大,log(x) 增长得越慢。
# 添加一列对数变换后的值
biz_df['log_review_count'] = np.log10(biz_df['review_count'] + 1)
biz_df.columns
查看对数变换后的数据:
fig, (ax1, ax2) = plt.subplots(2,1)
biz_df['review_count'].hist(ax=ax1, bins=100)
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=100)
ax2.tick_params(labelsize=14)
ax2.set_xlabel('log10(review_count))', fontsize=14)
ax2.set_ylabel('Occurrence', fontsize=14)
对数变换前(上)后(下)的 Yelp 商家点评数量比较:
对数变换:案例2
df = pd.read_csv(r"../data/OnlineNewsPopularity.csv")
df.head(3)
df.columns
# 对数转换
df['log_n_tokens_content'] = np.log10(df[' n_tokens_content'] + 1)
fig, (ax1, ax2) = plt.subplots(2,1)
df[' n_tokens_content'].hist(ax=ax1, bins=100)
ax1.tick_params(labelsize=14)
ax1.set_xlabel('Number of Words in Article', fontsize=14)
ax1.set_ylabel('Number of Articles', fontsize=14)
df['log_n_tokens_content'].hist(ax=ax2, bins=100)
ax2.tick_params(labelsize=14)
ax2.set_xlabel('Log of Number of Words', fontsize=14)
ax2.set_ylabel('Number of Articles', fontsize=14)
新闻文章流行度分布的可视化,使用对数变换和不使用对数变换:
4.指数变换:对数变换的推广
指数变换是个变换族,对数变换只是它的一个特例。用统计学术语来说,它们都是方差稳
定化变换。指数变换可以改变变量的分布,使得方差不再依赖于均值。例如,假设一个随机变量X 具有泊松分布,如果通过取它的平方根对它进行变换,那么X的方差就近似是一个常数,而不是与均值相等。
平方根(指数)变换和对数变换都可以简单推广为 Box-Cox 变换:λ 小于 1 时,可以压缩高端值;λ 大于 1 时,起的作用是相反的。
只有当数据为正时,Box-Cox 公式才有效。对于非正数据,我们可以加上一个固定的常数,
对数据进行平移。当应用 Box-Cox 变换或更广义的指数变换时,必须确定参数 λ 的值,这
可以通过极大似然方法(找到能使变换后信号的高斯似然最大化的 λ 值)或贝叶斯方法来
完成。
下图 展示了 λ = 0(对数变换)、λ = 0.25、λ = 0.5(平方根变换的一种缩放和平移形式)、
λ = 0.75 和 λ = 1.5 时的 Box-Cox 变换。
初始、对数变换后和 Box-Cox 变换后的点评数量直方图可视化
from scipy import stats
# 确定最小值大于0,即满足Box-Cox变换的输入数据要求
biz_df['review_count'].min()
fig, (ax1, ax2, ax3) = plt.subplots(3,1,figsize=(10, 15))
# 初始点评数量直方图
biz_df['review_count'].hist(ax=ax1, bins=100)
ax1.set_yscale('log')
ax1.tick_params(labelsize=14)
ax1.set_title('Review Counts Histogram', fontsize=14)
ax1.set_xlabel('')
ax1.set_ylabel('Occurrence', fontsize=14)
# 对数变换后的点评数量
rc_log = stats.boxcox(biz_df['review_count'], lmbda=0)
biz_df['rc_log'] = pd.Series(rc_log)
biz_df['rc_log'].hist(ax=ax2, bins=100)
ax2.set_yscale('log')
ax2.tick_params(labelsize=14)
ax2.set_title('Log Transformed Counts Histogram', fontsize=14)
ax2.set_xlabel('')
ax2.set_ylabel('Occurrence', fontsize=14)
# 最优Box-Cox变换后的点评数量
rc_bc, bc_params = stats.boxcox(biz_df['review_count'])
biz_df['rc_bc'] = pd.Series(rc_bc)
biz_df['rc_bc'].hist(ax=ax3, bins=100)
ax3.set_yscale('log')
ax3.tick_params(labelsize=14)
ax3.set_title('Box-Cox Transformed Counts Histogram', fontsize=14)
ax3.set_xlabel('')
ax3.set_ylabel('Occurrence', fontsize=14)
4.1可视化对比: 概率图
概率图(probplot)是一种非常简单的可视化方法,用以比较数据的实际分布与理论分布,
它本质上是一种表示实测分位数和理论分位数的关系的散点图。
# 初始和变换后点评数量的概率图,并和正态分布进行对比
fig2, (ax1, ax2, ax3) = plt.subplots(3,1,figsize=(8,10))
prob1 = stats.probplot(biz_df['review_count'], dist=stats.norm, plot=ax1)
ax1.set_xlabel('')
# 正态分布概率分布
ax1.set_title('Probplot against normal distribution')
rc_log = stats.boxcox(biz_df['review_count'], lmbda=0)
prob2 = stats.probplot(biz_df['rc_log'], dist=stats.norm, plot=ax2)
ax2.set_xlabel('')
ax2.set_title('Probplot after log transform')
rc_bc, bc_params = stats.boxcox(biz_df['review_count'])
prob3 = stats.probplot(biz_df['rc_bc'], dist=stats.norm, plot=ax3)
ax3.set_xlabel('Theoretical quantiles')
ax3.set_title('Probplot after Box-Cox transform')
5.特征缩放/归一化
有些特征的值是有界限的,比如经度和纬度,但有些数值型特征可以无限制地增加,比如
计数值。有些模型是输入的平滑函数,比如线性回归模型、逻辑回归模型或包含矩阵的模
型,它们会受到输入尺度的影响。相反,那些基于树的模型则根本不在乎输入尺度有多
大。如果模型对输入特征的尺度很敏感,就需要进行特征缩放。顾名思义,特征缩放会改
变特征的尺度,有些人将其称为特征归一化。特征缩放通常对每个特征独立进行。
- 不论使用何种缩放方法,特征缩放总是将特征除以一个常数(称为归一化常数)。因此,它不会改变单特征分布的形状。与对数变换不同,特征缩放不改变分布的形状,只有数据尺度发生了变化。所以,归一化之后的绘制图形其实都是一样的,只是尺度发生了变化。
import sklearn.preprocessing as preproc
5.1min-max缩放
min-max 缩放可以将所有特征值压缩(或扩展)到 [0, 1]区间中。
# min-max缩放
df['minmax'] = preproc.minmax_scale(df[[' n_tokens_content']])
df['minmax'].values
5.2特征标准化/方差缩放
它先减去特征的均值(对所有数据点),再除以方差,因此又称为方差缩放。缩放后的特
征均值为 0,方差为 1。如果初始特征服从高斯分布,那么缩放后的特征也服从高斯分布。
注意根据标准化的定义,有些结果会是负的。
# 标准化——注意根据标准化的定义,有些结果会是负的
df['standardized'] = preproc.StandardScaler().fit_transform(df[[' n_tokens_content']])
df['standardized'].values
5.3注意:不要“中心化”稀疏数据!
也就是不要对稀疏数据进行标准化,对稀疏数据进行标准化要慎重!
在稀疏特征上执行 min-max 缩放和标准化时一定要慎重,它们都会从原始特
征值中减去一个量。
- 对于 min-max 缩放,这个平移量是当前特征所有值中的最小值;
- 对于标准化,这个量是均值。
如果平移量不是 0,那么这两种变换会将一个多数元素为 0 的稀疏特征向量变成密集特征向量。根据实现方式的不同,这种改变会给分类器带来巨大的计算负担(按照现在的表示方法,特征向量中包含没有出现在一篇文档中的所有单词,不用说,这种特征向量变为密集向量是非常可怕的)。词袋就是一种稀疏的表示方式,大多数分类算法的实现都针对稀疏输入进行了优化,所以,处理词袋时一般不必进行归一化标准化。
5.4 ℓ 2 缩放 ℓ^2 缩放 ℓ2缩放 /欧几里得范数归一化
这种归一化技术是将初始特征值除以一个称为
ℓ
2
ℓ^2
ℓ2 范数的量,
ℓ
2
ℓ^2
ℓ2 范数又称为欧几里得范数,
ℓ
2
ℓ^2
ℓ2 范数先对所有数据点中该特征的值的平方求和,然后算出平方根。经过
ℓ
2
ℓ^2
ℓ2归一化后,特征列的范数就是 1。
# L2-归一化: 这里是欧几里得第二范数归一化
df['l2_normalized'] = preproc.normalize(df[[' n_tokens_content']], axis=0)
df['l2_normalized'].values
5.5进行特征缩放/标准化的条件:
当一组输入特征的尺度相差很大时,就需要进行特征缩放。例如,一个人气很高的商业网
站的日访问量可能是几十万次,而实际购买行为可能只有几千次。如果这两个特征都被模
型所使用,那么模型就需要在确定如何使用它们时先平衡一下尺度。如果输入特征的尺度
差别非常大,就会对模型训练算法带来数值稳定性方面的问题。在这种情况下,就应该对
特征进行标准化。
6.交互特征
两个特征的乘积可以组成一对简单的交互特征,这种相乘关系可以用逻辑操作符 AND 来
类比,它可以表示出由一对条件形成的结果:“该购买行为来自于邮政编码为 98121 的地
区”AND“用户年龄在 18 和 35 岁之间”。这种特征在基于决策树的模型中极其常见,在
广义线性模型中也经常使用。
from sklearn import linear_model
from sklearn.model_selection import train_test_split
import sklearn.preprocessing as preproc
df.columns
# 选择与内容有关的特征作为模型的单一特征,忽略那些衍生特征
features = [' n_tokens_title', ' n_tokens_content',' n_unique_tokens',
' n_non_stop_words', ' n_non_stop_unique_tokens',
' num_hrefs', ' num_self_hrefs', ' num_imgs', ' num_videos',
' average_token_length', ' num_keywords', ' data_channel_is_lifestyle',
' data_channel_is_entertainment', ' data_channel_is_bus',
' data_channel_is_socmed', ' data_channel_is_tech',
' data_channel_is_world']
X = df[features]
y = df[[' shares']]
# 创建交互特征对,跳过固定偏移项
X2 = preproc.PolynomialFeatures(include_bias=False).fit_transform(X)
X2.shape
# 为两个特征集创建训练集和测试集;两个特征集指的是原本集和交互特征集
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)
# 训练模型,返回模型和R方分数
def evaluate_feature(X_train, X_test, y_train, y_test):
"""Fit a linear regression model on the training set and score
on the test set"""
model = linear_model.LinearRegression().fit(X_train, y_train)
r_score = model.score(X_test, y_test)
return (model, r_score)
R²最大值为1。R²的值越接近1,说明回归直线对观测值的拟合程度越好;反之,R²的值越小,说明回归直线对观测值的拟合程度越差。
# 在两个特征集上训练模型并比较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.0113397007
交互特征的构造非常简单,使用起来却代价不菲。如果线性模型中包含有交互特征对,那
它的训练时间和评分时间就会从 O(n) 增加到 O(n 2 ),其中 n 是单一特征的数量。
有若干种方法可以绕过高阶交互特征所带来的计算成本。我们可以在构造出所有交互特征
之后再执行特征选择,或者,也可以更加精心地设计出少量复杂特征。
这两种策略各有千秋。特征选择使用计算手段为一个具体问题选择出最佳特征。(这种
技术并不局限于交互特征。)但是,一些特征选择技术仍然需要使用大量特征去训练多个
模型。
精心设计的复杂特征需要昂贵的成本,所以数量不能太多,它们可以减少模型的训练时
间,但特征本身会消耗很多计算能力,这增加了模型评分阶段的计算成本。
7.特征选择***
特征选择技术可以精简掉无用的特征,以降低最终模型的复杂性,它的最终目的是得到一个
简约模型,在不降低预测准确率或对预测准确率影响不大的情况下提高计算速度。为了得到这样的模型,有些特征选择技术需要训练不止一个待选模型。换言之,特征选择不是为了减
少训练时间(实际上,一些技术会增加总体训练时间),而是为了减少模型评分时间。
推荐阅读:https://blog.csdn.net/qq_33876194/article/details/88403394
推荐阅读:https://chenruhai.blog.csdn.net/article/details/122014117
7.1过滤Filter
过滤技术对特征进行预处理,以除去那些不太可能对模型有用处的特征。例如,我们可
以计算出每个特征与响应变量之间的相关性或互信息,然后过滤掉那些在某个阈值之下
的特征。过滤技术的成本比下面描述的打包技术低廉得多,但它们没有考虑我们要使用的模型,因此,它们有可能无法为模型选择出正确的特征。我们最好谨慎地使用预过滤技术,以免在有用特征进入到模型训练阶段之
前不经意地将其删除。
- 按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征
7.2包裹法Wrapper/递归特征消除
这些技术的成本非常高昂,但它们可以试验特征的各个子集,这意味着我们不会意外地
删除那些本身不提供什么信息但和其他特征组合起来却非常有用的特征。打包方法将模
型视为一个能对推荐的特征子集给出合理评分的黑盒子。它们使用另外一种方法迭代地
对特征子集进行优化。
- 根据目标函数,每次选择若干特征或者排除若干特征,直到选择出最佳的子集。
7.3嵌入式方法Embedding
这种方法将特征选择作为模型训练过程的一部分。例如,特征选择是决策树与生俱来的
一种功能,因为它在每个训练阶段都要选择一个特征来对树进行分割。另一个例子是
ℓ 1 正则项,它可以添加到任意线性模型的训练目标中。ℓ 1 正则项鼓励模型使用更少的特
征,而不是更多的特征,所以又称为模型的稀疏性约束。嵌入式方法将特征选择整合为
模型训练过程的一部分。它们不如打包方法强大,但成本也远不如打包方法那么高。与
过滤技术相比,嵌入式方法可以选择出特别适合某种模型的特征。从这个意义上说,嵌
入式方法在计算成本和结果质量之间实现了某种平衡。
- 先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。