在当今的机器学习 问题中,高维数据(就特征数量而言)越来越常见。要从海量数据中提取有用信息,您需要使用统计技术来降低噪声或冗余数据。这是因为在训练模型时,往往并不需要使用手头的每一个特征。只输入那些不相关且不冗余的特征,反而能提升模型表现。此时,特征选择就显得尤为重要。它不仅能加快模型训练速度,还能降低模型复杂度、提升可解释性,并在准确率、精确率或召回率(无论您的评估指标是什么)上带来改进。
在本教程中,您将学习以下内容:
首先,进一步了解特征选择 :何时使用它、以及有哪些方法可以用来为您的模型挑选最重要的特征!
随后,您将认识Boruta 算法 。您将看到如何通过比较原始属性的重要性与其随机置换副本(影子特征)所能达到的重要性,来执行自顶向下的相关特征搜索,并逐步剔除无关特征。
您还将简要了解 用于执行特征选择的数据集,并学习如何借助 Amelia 包轻松填补缺失值 。
最后,您将进一步了解用于运行该算法的Boruta 包 。
特征选择
通常,当您想要降低数据维度时,会想到诸如主成分分析 (PCA)、奇异值分解等方法。那么,自然会问:为什么还需要其他特征选择方法?这些技术的问题在于,它们是不依赖监督信息的特征选择方式:以 PCA 为例,它通过数据的方差来寻找主成分。这些方法并不考虑特征取值与目标类别或目标值之间的信息。此外,这类方法还伴随着一些假设(例如正态性),在应用之前往往需要进行某些变换。这些限制并不适用于所有类型的数据。
一般来说,特征选择方法可分为三类:
过滤法(Filter Methods) :通常作为预处理步骤使用。特征的选择不依赖任何机器学习算法,而是基于特征在各种统计检验中与结果变量的相关性得分来挑选。一些常见的过滤法包括相关性度量(皮尔逊、斯皮尔曼、距离相关)、卡方检验、方差分析(ANOVA)、Fisher 分数等。
封装法(Wrapper Methods) :在封装法中,您尝试使用一个特征子集来训练模型。基于从先前模型得到的推断,决定在子集中添加或移除特征。前向选择、后向消除等是这类方法的例子。
嵌入法(Embedded Methods) :这类算法自身带有内置的特征选择机制。LASSO 回归就是一个例子。
在本教程中,您将使用一种封装法,它在 R 中通过名为 Boruta 的包即可直接使用。
Boruta 算法
Boruta 算法是围绕随机森林分类算法构建的封装法。它试图在与结果变量相关的前提下,捕获数据集中所有重要且有信息量的特征。
首先,它会复制数据集,并对每一列的取值进行随机打乱。这些值称为影子特征(shadow features)。* 然后,它在数据集上训练一个分类器,比如随机森林分类器。通过这样做,您可以得到每个特征的重要性评估(例如基于平均准确率降低或平均不纯度降低)。分数越高,表示越好、越重要。
接着,算法会检查每个真实特征是否具有更高的重要性。也就是说,该特征的 Z 分数是否高于其影子特征中的最大 Z 分数(即影子特征中的最佳值)。如果是,就在一个向量中记录一次命中(hit)。随后进入下一次迭代。经过预设次数的迭代后,您将得到一张命中表。请记住 :Z 分数表示数据点距离均值有多少个标准差,更多信息请点击这里 。
在每次迭代中,算法都会比较特征的随机置换副本与原始特征的 Z 分数,看看后者是否优于前者。如果是,算法会将该特征标记为重要。本质上,算法通过与随机打乱的副本进行比较来验证特征的重要性,从而提高稳健性。这是通过使用二项分布,简单比较某个特征优于影子特征的次数来完成的。
如果某个特征在例如 15 次迭代中都没有被记录为命中,则拒绝它,并且从原始矩阵中移除。达到设定的迭代次数后,或当所有特征都已被确认或拒绝时,算法停止。
R 中的 Boruta 算法
让我们在一个最常见的数据集上使用 Boruta 算法:银行营销数据。该数据代表一家葡萄牙银行机构的直接营销活动(电话)。分类目标是预测客户是否会订阅定期存款。
提示 :不要忘了在这里 查看不同特征的详细说明。
read_file <- read.csv( './bank_bank.csv' , header= TRUE , sep= ';' , stringsAsFactors = F)
str( read_file)
技术支持
使用 summary() 函数汇总数据集中不同特征的常见描述性统计信息。
summary( read_file)
技术支持
summary() 函数会为连续型特征给出集中趋势的度量,如均值、中位数、分位数等。如果数据集中有分类特征,您还会看到这些特征的 Class 和 Mode。
现在将分类特征转换为因子(factor)类型:
convert <- c( 2 : 5 , 7 : 9 , 11 , 16 : 17 )
read_file[ , convert] <- data.frame( apply( read_file[ convert] , 2 , as.factor) )
str( read_file)
技术支持
由于为了创建影子特征需要对数据点进行打乱,并为每个特征计算 Z 分数,因此在使用 boruta 包之前,务必先处理缺失或空值,否则会抛出错误。
(不)幸的是,该数据集两者都没有。不过,出于教学目的,您将人为引入一些 NA。
使用 prodNA() 函数在数据集中植入缺失值。安装 missForest 包后即可使用该函数。
请记住 ,如有需要,您可以使用 install.packages() 安装缺失的包!
library( missForest)
bank.mis <- prodNA( read_file, noNA = 0.05 ) 技术支持
您也可以再次对新的数据框调用 summary() 来查看填补的 NA 数量,但我们不妨更有创意一些!
使用下面的 ggplot2 代码可视化数据中的缺失情况:
library( reshape2)
library( ggplot2)
library( dplyr)
ggplot_missing <- function ( x) {
x %>%
is.na %>%
melt %>%
ggplot( data = .,
aes( x = Var2,
y = Var1) ) +
geom_raster( aes( fill = value) ) +
scale_fill_grey( name = "" ,
labels = c( "Present" , "Missing" ) ) +
theme_minimal( ) +
theme( axis.text.x = element_text( angle= 45 , vjust= 0.5 ) ) +
labs( x = "Variables in Dataset" ,
y = "Rows / observations" )
}
ggplot_missing( bank.mis) 技术支持
图中的白色线条直观地显示了您已在每个特征中植入了缺失值。不过,您也看到了为了编写这个 ggplot_missing 函数费了不少功夫?好在接下来将要使用的 R 包 Amelia,提供了一行代码即可绘制类似图形的替代方案:
library( Amelia)
missmap( bank.mis) 技术支持
动手试试吧!
使用 Amelia 填补缺失值
现在您可以用多种方式来填补缺失值,比如用均值、中位数或众数(针对分类特征)填补,但我们将使用另一个功能强大的缺失值填补包 Amelia 。
Amelia 采用 m 次自助法(bootstrap)抽样,并对每个样本应用 EMB(期望最大化 + 自助法)算法。m 组均值和方差估计会有所不同。最后,使用第一组估计通过回归填补第一组缺失值,第二组估计用于第二组,以此类推。多重插补有助于降低偏差并提高效率。此外,它支持使用多核 CPU 进行并行插补。
它有 3 个重要参数:
m:要创建的插补数据集数量。
idvars:保留所有 ID 变量及其他不希望被插补的变量。
noms:在此处指定名义(分类)变量。
library( Amelia)
amelia_bank <- amelia( bank.mis, m= 3 , parallel = "multicore" , noms= c( 'job' , 'marital' , 'education' , 'default' , 'housing' , 'loan' , 'contact' , 'month' , 'poutcome' , 'y' ) )
技术支持
要访问插补后的数据框,您可以使用如下子集 操作:
amelia_bank$ imputations[ [ 1 ] ] 技术支持
要将插补后的数据集导出为 csv 文件,请使用:
write.amelia( amelia_bank, file.stem = "imputed_bank_data_set" ) 技术支持
Boruta R 包
现在让我们在其中一个插补数据集上使用 Boruta 算法。您可以借助 Boruta 包来完成:
library( Boruta)
set.seed( 111 )
boruta.bank_train <- Boruta( y~ ., data = amelia_bank$ imputations[ [ 1 ] ] , doTrace = 2 )
print( boruta.bank_train)
技术支持
Boruta 会对数据集中各特征的重要性作出判断。许多特征已被直接归类为重要或不重要,但您会看到还有一些被标记为待定(tentative)。
这意味着什么?
待定特征的重要性与其最佳影子特征非常接近,以至于在默认的随机森林运行次数下,Boruta 无法以所需的置信度作出决定。
那么该如何处理?
如果仍有待定特征,您可以考虑增大 maxRuns 参数。不过,注意 ,您也可以提供 mtry 和 ntree 参数的取值,这些参数会传递给 randomForest() 函数。前者用于指定每次分裂时随机抽样作为候选的变量个数,后者用于指定要生长的树的数量。在指定这些参数后,随机森林分类器会在袋外误差的最小值附近收敛。
请记住 ,袋外误差是用于衡量采用自助聚合(bagging)对训练样本进行子采样的分类器的预测误差的估计值。它是对每个训练样本 X,仅使用未包含 X 的自助样本中训练出的树来进行预测,并取其平均预测误差。
或者,您也可以将 doTrace 参数设置为 1 或 2,以获取过程进展的报告。
boruta 包还提供了 TentativeRoughFix() 函数,可通过比较特征 Z 分数的中位数与最重要影子特征的 Z 分数中位数,来补全缺失的决策:
boruta.bank <- TentativeRoughFix( boruta.bank_train)
print( boruta.bank)
技术支持
Boruta 到此已完成任务:它已成功将每个特征归类为重要或不重要。
您现在可以通过调用 plot(boruta.bank) 绘制 Boruta 变量重要性图。不过,x 轴标签将是水平的,看起来不太美观。
因此,您将像下面的代码块那样,将特征标签垂直添加到 x 轴上:
plot( boruta.bank, xlab = "" , xaxt = "n" )
lz<- lapply( 1 : ncol( boruta.bank$ ImpHistory) , function ( i)
boruta.bank$ ImpHistory[ is.finite( boruta.bank$ ImpHistory[ , i] ) , i] )
names( lz) <- colnames( boruta.bank$ ImpHistory)
Labels <- sort( sapply( lz, median) )
axis( side = 1 , las= 2 , labels = names( Labels) ,
at = 1 : ncol( boruta.bank$ ImpHistory) , cex.axis = 0.7 ) 技术支持
y 轴标签 Importance 表示在打乱后的数据集中,每个特征的 Z 分数。
蓝色箱线图对应影子特征的最小、平均和最大 Z 分数;红色和绿色箱线图分别表示被拒绝和被确认特征的 Z 分数。您可以看到,红色箱线图的 Z 分数低于影子特征的最大 Z 分数,这正是它们被归为不重要类别的原因。
您可以通过输入以下命令来确认特征的重要性:
getSelectedAttributes( boruta.bank, withTentative = F)
bank_df <- attStats( boruta.bank)
print( bank_df)
技术支持
您可以很容易地验证这一结果:特征 duration 获得了最高的重要性,这一点在数据说明中已明确提到(仔细阅读可点这里 )!
结论
大功告成!您仅用几行代码就从数据集中筛选出了最重要的特征。借此,您降低了数据中的噪声,这将有助于任意分类器更准确地为观测赋予标签。基于这些重要特征训练模型,必将提升模型表现——这正是进行特征选择的初衷!
如果您想查看编写本教程所用到的资源,请参阅以下内容:
PACKAGE AMELIA: A Program for Missing Data; James Honaker, Gary King, and Matthew Blackwell
Feature Selection with the Boruta Package; Miron B. Kursa, Witold R. Rudnicki
RDocumentation
UCI 机器学习资料库
在当今的机器学习问题中,高维数据(就特征数量而言)越来越常见。要从海量数据中提取有用信息,您需要使用统计技术来降低噪声或冗余数据。这是因为在训练模型时,往往并不需要使用手头的每一个特征。只输入那些不相关且不冗余的特征,反而能提升模型表现。此时,特征选择就显得尤为重要。它不仅能加快模型训练速度,还能降低模型复杂度、提升可解释性,并在准确率、精确率或召回率(无论您的评估指标是什么)上带来改进。
在本教程中,您将学习以下内容:
首先,进一步了解特征选择:何时使用它、以及有哪些方法可以用来为您的模型挑选最重要的特征!
随后,您将认识Boruta 算法。您将看到如何通过比较原始属性的重要性与其随机置换副本(影子特征)所能达到的重要性,来执行自顶向下的相关特征搜索,并逐步剔除无关特征。
您还将简要了解用于执行特征选择的数据集,并学习如何借助
Amelia包轻松填补缺失值。最后,您将进一步了解用于运行该算法的
Boruta包。特征选择
通常,当您想要降低数据维度时,会想到诸如主成分分析(PCA)、奇异值分解等方法。那么,自然会问:为什么还需要其他特征选择方法?这些技术的问题在于,它们是不依赖监督信息的特征选择方式:以 PCA 为例,它通过数据的方差来寻找主成分。这些方法并不考虑特征取值与目标类别或目标值之间的信息。此外,这类方法还伴随着一些假设(例如正态性),在应用之前往往需要进行某些变换。这些限制并不适用于所有类型的数据。
一般来说,特征选择方法可分为三类:
过滤法(Filter Methods):通常作为预处理步骤使用。特征的选择不依赖任何机器学习算法,而是基于特征在各种统计检验中与结果变量的相关性得分来挑选。一些常见的过滤法包括相关性度量(皮尔逊、斯皮尔曼、距离相关)、卡方检验、方差分析(ANOVA)、Fisher 分数等。
封装法(Wrapper Methods):在封装法中,您尝试使用一个特征子集来训练模型。基于从先前模型得到的推断,决定在子集中添加或移除特征。前向选择、后向消除等是这类方法的例子。
嵌入法(Embedded Methods):这类算法自身带有内置的特征选择机制。LASSO 回归就是一个例子。
在本教程中,您将使用一种封装法,它在 R 中通过名为
Boruta的包即可直接使用。Boruta 算法
Boruta 算法是围绕随机森林分类算法构建的封装法。它试图在与结果变量相关的前提下,捕获数据集中所有重要且有信息量的特征。
R 中的 Boruta 算法
让我们在一个最常见的数据集上使用 Boruta 算法:银行营销数据。该数据代表一家葡萄牙银行机构的直接营销活动(电话)。分类目标是预测客户是否会订阅定期存款。
提示:不要忘了在这里查看不同特征的详细说明。
使用
summary()函数汇总数据集中不同特征的常见描述性统计信息。summary()函数会为连续型特征给出集中趋势的度量,如均值、中位数、分位数等。如果数据集中有分类特征,您还会看到这些特征的 Class 和 Mode。现在将分类特征转换为因子(factor)类型:
由于为了创建影子特征需要对数据点进行打乱,并为每个特征计算 Z 分数,因此在使用
boruta包之前,务必先处理缺失或空值,否则会抛出错误。(不)幸的是,该数据集两者都没有。不过,出于教学目的,您将人为引入一些
NA。使用
prodNA()函数在数据集中植入缺失值。安装missForest包后即可使用该函数。请记住,如有需要,您可以使用
install.packages()安装缺失的包!您也可以再次对新的数据框调用
summary()来查看填补的NA数量,但我们不妨更有创意一些!使用下面的
ggplot2代码可视化数据中的缺失情况:图中的白色线条直观地显示了您已在每个特征中植入了缺失值。不过,您也看到了为了编写这个
ggplot_missing函数费了不少功夫?好在接下来将要使用的R包Amelia,提供了一行代码即可绘制类似图形的替代方案:动手试试吧!
使用
Amelia填补缺失值现在您可以用多种方式来填补缺失值,比如用均值、中位数或众数(针对分类特征)填补,但我们将使用另一个功能强大的缺失值填补包 Amelia。
Amelia 采用
m次自助法(bootstrap)抽样,并对每个样本应用EMB(期望最大化 + 自助法)算法。m组均值和方差估计会有所不同。最后,使用第一组估计通过回归填补第一组缺失值,第二组估计用于第二组,以此类推。多重插补有助于降低偏差并提高效率。此外,它支持使用多核 CPU 进行并行插补。它有 3 个重要参数:
m:要创建的插补数据集数量。idvars:保留所有 ID 变量及其他不希望被插补的变量。noms:在此处指定名义(分类)变量。要访问插补后的数据框,您可以使用如下子集操作:
要将插补后的数据集导出为
csv文件,请使用:BorutaR 包现在让我们在其中一个插补数据集上使用 Boruta 算法。您可以借助 Boruta 包来完成:
Boruta 会对数据集中各特征的重要性作出判断。许多特征已被直接归类为重要或不重要,但您会看到还有一些被标记为待定(tentative)。
这意味着什么?
待定特征的重要性与其最佳影子特征非常接近,以至于在默认的随机森林运行次数下,Boruta 无法以所需的置信度作出决定。
那么该如何处理?
如果仍有待定特征,您可以考虑增大
maxRuns参数。不过,注意,您也可以提供mtry和ntree参数的取值,这些参数会传递给randomForest()函数。前者用于指定每次分裂时随机抽样作为候选的变量个数,后者用于指定要生长的树的数量。在指定这些参数后,随机森林分类器会在袋外误差的最小值附近收敛。请记住,袋外误差是用于衡量采用自助聚合(bagging)对训练样本进行子采样的分类器的预测误差的估计值。它是对每个训练样本 X,仅使用未包含 X 的自助样本中训练出的树来进行预测,并取其平均预测误差。
或者,您也可以将
doTrace参数设置为1或2,以获取过程进展的报告。boruta包还提供了TentativeRoughFix()函数,可通过比较特征 Z 分数的中位数与最重要影子特征的 Z 分数中位数,来补全缺失的决策:Boruta到此已完成任务:它已成功将每个特征归类为重要或不重要。您现在可以通过调用
plot(boruta.bank)绘制 Boruta 变量重要性图。不过,x轴标签将是水平的,看起来不太美观。因此,您将像下面的代码块那样,将特征标签垂直添加到
x轴上:y轴标签Importance表示在打乱后的数据集中,每个特征的Z分数。蓝色箱线图对应影子特征的最小、平均和最大 Z 分数;红色和绿色箱线图分别表示被拒绝和被确认特征的 Z 分数。您可以看到,红色箱线图的
Z分数低于影子特征的最大Z分数,这正是它们被归为不重要类别的原因。您可以通过输入以下命令来确认特征的重要性:
您可以很容易地验证这一结果:特征
duration获得了最高的重要性,这一点在数据说明中已明确提到(仔细阅读可点这里)!结论
大功告成!您仅用几行代码就从数据集中筛选出了最重要的特征。借此,您降低了数据中的噪声,这将有助于任意分类器更准确地为观测赋予标签。基于这些重要特征训练模型,必将提升模型表现——这正是进行特征选择的初衷!
如果您想查看编写本教程所用到的资源,请参阅以下内容: