专栏 | 基于 Jupyter 的特征工程手册:特征选择(二)

数据预处理后,我们生成了大量的新变量(比如独热编码生成了大量仅包含0或1的变量)。但实际上,部分新生成的变量可能是多余:一方面它们本身不一定包含有用的信息,故无法提高模型性能;另一方面过这些多余变量在构建模型时会消耗大量内存和计算能力。因此,我们应该进行特征选择并选择特征子集进行建模。

项目地址:

https://github.com/YC-Coder-Chen/feature-engineering-handbook

本文将介绍特征工程第一种算法:Filter Methods 过滤法(下)。

目录:

1.1.1.5 Mutual Information (regression problem) 互信息 (回归问题)

互信息(Mutual Information)衡量变量间的相互依赖性。其本质为熵差,即 ( )− ( | ),即知道另一个变量信息后混乱的降低程度 。当且仅当两个随机变量独立时MI等于零。MI值越高,两变量之间的相关性则越强。与Pearson相关和F统计量相比,它还捕获了非线性关系。

公式:

  • 若两个变量均为离散变量:

p( , )( , ) 为x和y的联合概率质量函数 (PMF), p ( )则为x的联合概率质量函数 (PMF)。

  • 若两个变量均为连续变量:

p( , )( , ) 为x和y的联合概率密度函数 (PDF),p ( )则为x的概率密度函数 (PDF)。连续变量情形下,在实际操作中,往往先对数据离散化分桶,然后逐个桶进行计算。

但是实际上,一种极有可能的情况是,x和y中的一个可能是离散变量,而另一个是连续变量。因此在sklearn中,它基于[1]和[2]中提出的基于k最临近算法的熵估计非参数方法。

[1] A. Kraskov, H. Stogbauer and P. Grassberger, “Estimating mutual information”. Phys. Rev. E 69, 2004.

[2] B. C. Ross “Mutual Information between Discrete and Continuous Data Sets”. PLoS ONE 9(2), 2014.

import numpy as np
from sklearn.feature_selection import mutual_info_regression
from sklearn.feature_selection import SelectKBest

# 直接载入数据集
from sklearn.datasets import fetch_california_housing
dataset = fetch_california_housing()
X, y = dataset.data, dataset.target # 利用 california_housing 数据集来演示
# 此数据集中,X,y均为连续变量,故此满足使用MI的条件

# 选择前15000个观测点作为训练集
# 剩下的作为测试集
train_set = X[0:15000,:].astype(float)
test_set = X[15000:,].astype(float)
train_y = y[0:15000].astype(float)

# KNN中的临近数是一个非常重要的参数
# 故我们重写了一个新的MI计算方程更好的来控制这一参数
def udf_MI(X, y):
    result = mutual_info_regression(X, y, n_neighbors = 5) # 用户可以输入想要的临近数
    return result

# SelectKBest 将会基于一个判别方程自动选择得分高的变量
# 这里的判别方程为F统计量
selector = SelectKBest(udf_MI, k=2) # k => 我们想要选择的变量数
selector.fit(train_set, train_y) # 在训练集上训练
transformed_train = selector.transform(train_set) # 转换训练集
transformed_train.shape #(15000, 2), 其选择了第一个及第八个变量
assert np.array_equal(transformed_train, train_set[:,[0,7]])

transformed_test = selector.transform(test_set) # 转换测试集
assert np.array_equal(transformed_test, test_set[:,[0,7]]);
# 可见对于测试集,其依然选择了第一个及第八个变量
# 验算上述结果
for idx in range(train_set.shape[1]):
    score = mutual_info_regression(train_set[:,idx].reshape(-1,1), train_y, n_neighbors = 5)
    print(f"第{idx + 1}个变量与因变量的互信息为{round(score[0],2)}")
# 故应选择第一个及第八个变量

第1个变量与因变量的互信息为0.37

第2个变量与因变量的互信息为0.03

第3个变量与因变量的互信息为0.1

第4个变量与因变量的互信息为0.03

第5个变量与因变量的互信息为0.02

第6个变量与因变量的互信息为0.09

第7个变量与因变量的互信息为0.37

第8个变量与因变量的互信息为0.46

1.1.1.6 Chi-squared Statistics (classification problem) 卡方统计量 (分类问题)

卡方统计量主要用于衡量两个类别特征之间的相关性。sklearn提供了chi2方程用于计算卡方统计量。其输入的特征变量必须为布尔值或频率(故对于类别变量应考虑独热编码)。卡方统计量的零假设为两个变量是独立的,因为卡方统计量值越高,则两个类别变量的相关性越强。因此,我们应该选择具有较高卡方统计量的特征。

公式:

其中, , 为在变量X上具有i-th类别值且在变量Y上具有j-th类别值的实际观测点计数。 , 为利用概率估计的应在在变量X上具有i-th类别值且在变量Y上具有j-th类别值的观测点数量。n为总观测数, 为在变量X上具有i-th类别值的概率, 为在变量Y上具有j-th类别值的概率。

值得注意的是,通过解析源代码,我们发现在sklearn中利用chi2计算出来的卡方统计量并不是统计意义上的卡方统计量。当输入变量为布尔变量时,chi2计算值为该布尔变量为True时候的卡方统计量(我们将会在下文举例说明)。这样的优势是,独热编码生成的所有布尔值变量的chi2值之和将等于原始变量统计意义上的卡方统计量。

举个简单的例子,假设一个变量I有0,1,2两种可能的值,则独特编码后一共会产生3个新的布尔值变量。这三个布尔值变量的chi2计算出来的值之和,将等于变量I与因变量直接计算得出的统计意义上的卡方统计量。

解析sklearn中chi2的计算

# 首先,随机生成一个数据集
import pandas as pd
sample_dict = {'Type': ['J','J','J',
                        'B','B','B',
                        'C','C','C','C','C'], 
               'Output': [0, 1, 0, 
                          2, 0, 1,  
                          0, 0, 1, 2, 2,]}
sample_raw = pd.DataFrame(sample_dict)
sample_raw #原始数据,Output是我们的目标变量,Type为类别变量

# 下面利用独热编码生成布尔变量,并利用sklearn计算每一个布尔变量的chi2值
sample = pd.get_dummies(sample_raw)
from sklearn.feature_selection import chi2
chi2(sample.values[:,[1,2,3]],sample.values[:,[0]])
# 第一行为每一个布尔变量的chi2值

(array([0.17777778, 0.42666667, 1.15555556]),
array([0.91494723, 0.8078868 , 0.56114397]))

# 下面直接计算原始变量Type与output统计学意义上的卡方统计量
# 首先,先统计每一个类别下出现的观测数,用于创建列联表
obs_df = sample_raw.groupby(['Type','Output']).size().reset_index()
obs_df.columns = ['Type','Output','Count']
obs_df

即列联表(contingency table)为:

from scipy.stats import chi2_contingency
obs = np.array([[1, 1, 1], [2, 1, 2],[2, 1, 0]])
chi2_contingency(obs) # 第一个值即为变量Type与output统计学意义上的卡方统计量

(1.7600000000000002,

0.779791873961373,

4,

array([[1.36363636, 0.81818182, 0.81818182], [2.27272727, 1.36363636, 1.36363636], [1.36363636, 0.81818182, 0.81818182]]))

# 而chi2方程算出来的布尔值之和为即为原始变量的统计意义上的卡方统计量
chi2(sample.values[:,[1,2,3]],sample.values[:,[0]])[0].sum() == chi2_contingency(obs)[0]

True

# 那么sklearn中的chi2是如何计算的呢?
# 不妨以第一个生成的布尔值为例,即Type为B
# chi2出来的值为0.17777778
# 而这与利用scipy以下代码计算出的计算一致
from scipy.stats import chisquare
f_exp = np.array([5/11, 3/11, 3/11]) * 3 # 预期频数为 output的先验概率 * Type为B 的样本数
chisquare([1,1,1], f_exp=f_exp) # [1,1,1] 即Type为B 的样本实际频数
# 即sklearn 中的chi2 仅考虑了Type为B情形下的列连表

Power_divergenceResult(statistic=0.17777777777777778,pvalue=0.9149472287300311)

如何利用sklearn 来进行特征选择

import numpy as np
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest

# 直接载入数据集
from sklearn.datasets import load_iris # 利用iris数据作为演示数据集
iris = load_iris()
X, y = iris.data, iris.target
# 此数据集中,X为连续变量,y为类别变量
# 不满足chi2的使用条件

# 将连续变量变为布尔值变量以满足chi2使用条件
# 不妨利用其是否大于均值来生成布尔值(仅作为演示用)
X = X > X.mean(0)

# iris 数据集使用前需要被打乱顺序
np.random.seed(1234)
idx = np.random.permutation(len(X))
X = X[idx]
y = y[idx]

# 选择前100个观测点作为训练集
# 剩下的作为测试集
train_set = X[0:100,:]
test_set = X[100:,]
train_y = y[0:100]

# sklearn 中直接提供了方程用于计算卡方统计量
# SelectKBest 将会基于一个判别方程自动选择得分高的变量
# 这里的判别方程为F统计量
selector = SelectKBest(chi2, k=2) # k => 我们想要选择的变量数
selector.fit(train_set, train_y) # 在训练集上训练
transformed_train = selector.transform(train_set) # 转换训练集
transformed_train.shape #(100, 2), 其选择了第三个及第四个变量 
assert np.array_equal(transformed_train, train_set[:,[2,3]])

transformed_test = selector.transform(test_set) # 转换测试集
assert np.array_equal(transformed_test, test_set[:,[2,3]]);
# 可见对于测试集,其依然选择了第三个及第四个变量
# 验证上述结果
for idx in range(train_set.shape[1]):
    score, p_value = chi2(train_set[:,idx].reshape(-1,1), train_y)
    print(f"第{idx + 1}个变量与因变量的卡方统计量为{round(score[0],2)},p值为{round(p_value[0],3)}")
# 故应选择第三个及第四个变量

第1个变量与因变量的卡方统计量为29.69,p值为0.0

第2个变量与因变量的卡方统计量为19.42,p值为0.0

第3个变量与因变量的卡方统计量为31.97,p值为0.0

第4个变量与因变量的卡方统计量为31.71,p值为0.0

1.1.1.7 F-Score (classification problem) F-统计量 (分类问题)

在分类机器学习问题中,若变量特征为类别特征,则我们可以使用独热编码配合上述chi2方法选择最重要的特征。但若特征为连续变量,则我们可以使用ANOVA-F值。ANOVA F统计量的零假设是若按目标变量(类别)分组,则连续变量的总体均值是相同的。故我们应选择具有高ANOVA-F统计量的连续变量,因为这些连续变量与目标变量的关联性强。

公式:

其中,SS(between)为组间的平方和,即组均值和总体均值之间的平方和。SS(error)是组内的平方和,即数据与组均值之间的平方和。m是目标变量的总类别数,n是观测数。

import numpy as np
from sklearn.feature_selection import f_classif
from sklearn.feature_selection import SelectKBest

# 直接载入数据集
from sklearn.datasets import load_iris # 利用iris数据作为演示数据集
iris = load_iris()
X, y = iris.data, iris.target
# 此数据集中,X为连续变量,y为类别变量
# 满足ANOVA-F的使用条件

# iris 数据集使用前需要被打乱顺序
np.random.seed(1234)
idx = np.random.permutation(len(X))
X = X[idx]
y = y[idx]

# 选择前100个观测点作为训练集
# 剩下的作为测试集
train_set = X[0:100,:]
test_set = X[100:,]
train_y = y[0:100]

# sklearn 中直接提供了方程用于计算ANOVA-F
# SelectKBest 将会基于一个判别方程自动选择得分高的变量
# 这里的判别方程为F统计量
selector = SelectKBest(f_classif, k=2) # k => 我们想要选择的变量数
selector.fit(train_set, train_y) # 在训练集上训练
transformed_train = selector.transform(train_set) # 转换训练集
transformed_train.shape #(100, 2), 其选择了第三个及第四个变量 
assert np.array_equal(transformed_train, train_set[:,[2,3]])

transformed_test = selector.transform(test_set) # 转换测试集
assert np.array_equal(transformed_test, test_set[:,[2,3]]);
# 可见对于测试集,其依然选择了第三个及第四个变量
# 验证上述结果
for idx in range(train_set.shape[1]):
    score, p_value = f_classif(train_set[:,idx].reshape(-1,1), train_y)
    print(f"第{idx + 1}个变量与因变量的ANOVA-F统计量为{round(score[0],2)},p值为{round(p_value[0],3)}")
# 故应选择第三个及第四个变量

第1个变量与因变量的ANOVA-F统计量为91.39,p值为0.0

第2个变量与因变量的ANOVA-F统计量为33.18,p值为0.0

第3个变量与因变量的ANOVA-F统计量为733.94,p值为0.0

第4个变量与因变量的ANOVA-F统计量为608.95,p值为0.0

1.1.1.7 Mutual Information (classification problem) 互信息 (分类问题)

【与1.1.1.5一样】互信息(Mutual Information)衡量变量间的相互依赖性。其本质为熵差,即 ( )− ( | ),即知道另一个变量信息后混乱的降低程度 。当且仅当两个随机变量独立时MI等于零。MI值越高,两变量之间的相关性则越强。与Pearson相关和F统计量相比,它还捕获了非线性关系。

公式:

  • 若两个变量均为离散变量:

p( , )( , ) 为x和y的联合概率质量函数 (PMF), p ( )则为x的的联合概率质量函数 (PMF)。

  • 若两个变量均为连续变量:

p( , )( , ) 为x和y的联合概率密度函数 (PDF),p ( )则为x的的联合概率密度函数 (PDF)。连续变量情形下,在实际操作中,往往先对数据离散化分桶,然后逐个桶进行计算。

但是实际上,一种极有可能的情况是,x和y中的一个可能是离散变量,而另一个是连续变量。因此在sklearn中,它基于[1]和[2]中提出的基于k最临近算法的熵估计非参数方法。

[1] A. Kraskov, H. Stogbauer and P. Grassberger, “Estimating mutual information”. Phys. Rev. E 69, 2004.

[2] B. C. Ross “Mutual Information between Discrete and Continuous Data Sets”. PLoS ONE 9(2), 2014.

import numpy as np
from sklearn.feature_selection import mutual_info_classif
from sklearn.feature_selection import SelectKBest

# 直接载入数据集
from sklearn.datasets import load_iris # 利用iris数据作为演示数据集
iris = load_iris()
X, y = iris.data, iris.target
# 此数据集中,X为连续变量,y为类别变量
# 满足MI的使用条件

# iris 数据集使用前需要被打乱顺序
np.random.seed(1234)
idx = np.random.permutation(len(X))
X = X[idx]
y = y[idx]

# 选择前100个观测点作为训练集
# 剩下的作为测试集
train_set = X[0:100,:]
test_set = X[100:,]
train_y = y[0:100]

# KNN中的临近数是一个非常重要的参数
# 故我们重写了一个新的MI计算方程更好的来控制这一参数
def udf_MI(X, y):
    result = mutual_info_classif(X, y, n_neighbors = 5) # 用户可以输入想要的临近数
    return result

# SelectKBest 将会基于一个判别方程自动选择得分高的变量
# 这里的判别方程为F统计量
selector = SelectKBest(udf_MI, k=2) # k => 我们想要选择的变量数
selector.fit(train_set, train_y) # 在训练集上训练
transformed_train = selector.transform(train_set) # 转换训练集
transformed_train.shape #(100, 2), 其选择了第三个及第四个变量 
assert np.array_equal(transformed_train, train_set[:,[2,3]])

transformed_test = selector.transform(test_set) # 转换测试集
assert np.array_equal(transformed_test, test_set[:,[2,3]]);
# 可见对于测试集,其依然选择了第三个及第四个变量
# 验算上述结果
for idx in range(train_set.shape[1]):
    score = mutual_info_classif(train_set[:,idx].reshape(-1,1), train_y, n_neighbors = 5)
    print(f"第{idx + 1}个变量与因变量的互信息为{round(score[0],2)}")
# 故应选择第三个及第四个变量

第1个变量与因变量的互信息为0.56
第2个变量与因变量的互信息为0.28
第3个变量与因变量的互信息为0.99
第4个变量与因变量的互信息为1.02


本文首发于公众号:AI有道(ID: redstonewill),欢迎关注!

未经允许不得转载:红色石头的个人博客 » 专栏 | 基于 Jupyter 的特征工程手册:特征选择(二)

赞 (1) 打赏

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏