PySpark机器学习特征选择
本文为销量预测第5篇:特征选择
第1篇:PySpark与DataFrame简介
第2篇:PySpark时间序列数据统计描述,分布特性与内部特性
第3篇:缺失值填充与异常值处理
第4篇:时间序列特征工程
第6篇:简单预测模型
第7篇:线性回归与广义线性模型
第8篇:机器学习调参方法
第9篇:销量预测建模中常用的损失函数与模型评估指标
本文基于SPARK.SQL和SPARK.ML实现常见的4种结构化数据特征选择方法,并给出基于树模型的特征选择代码。
机器学习中常见的特征选择方法分为以下三类:
-
过滤法(Filter):按照发散性或者相关性,设定阈值,剔除部分特征;
-
包裹法(Wrapper):根据目标函数,每次选择或者排除若干特征,选择出最佳特征子集,常见的方法为为递归特征消除算法。
-
嵌入法(Embedding):先使用某种机器学习算法进行模型训练,得到各个特征的权值系数,根据系数从大到小选择特征,该方法通过模型训练来确定特征的优劣,把特征选择的过程与模型训练的过程融合一起,最常见的如使用线性回归LASSO中的L1正则化过程。
虽然使用机器学习算法比如LASSO或者XGBOOST,可以起到特征筛选的作用,但出于计算性能、模型稳定以及可解释的角度,在正式上线的算法任务中,模型训练之前应该初步筛选出无效部分特征。
本文重点讲解借助SPARK.SQL和SPARK.ML实现的第一种方法,不涉及第二部分,第三种方式在后面的模型阶段会介绍。
1.相关系数
皮尔逊相关系数,广泛用于度量两个连续变量之间的线性相关程度。
r = ∑ ( x i − x ˉ ) ( y i − y ˉ ) ∑ ( x i − x ˉ ) 2 ∑ ( y i − y ˉ ) 2 r=\frac{\sum\left(x_{i}-\bar{x}\right)\left(y_{i}-\bar{y}\right)}{\sqrt{\sum\left(x_{i}-\bar{x}\right)^{2} \sum\left(y_{i}-\bar{y}\right)^{2}}} r=∑(xi−xˉ)2∑(yi−yˉ)2∑(xi−xˉ)(yi−yˉ)
斯皮尔曼等级相关,研究两个等级特征(变量)间相关关系的方法,依据两列成等级之差来进行计算,所以又称为“等级差数法”。
r
s
=
1
−
6
∑
d
i
2
n
(
n
2
−
1
)
d
为
两
列
等
级
之
差
r_{s}=1-\frac{6 \sum d_{i}^{2}}{n\left(n^{2}-1\right)}\\ d为两列等级之差
rs=1−n(n2−1)6∑di2d为两列等级之差
from pyspark.sql import SparkSession
from pyspark.ml.stat import Correlation
spark = SparkSession. \
Builder(). \
config("spark.sql.crossJoin.enabled", "true"). \
config("spark.sql.execution.arrow.enabled", "false"). \
enableHiveSupport(). \
getOrCreate()
data = [(Vectors.sparse(4, [(0, 1.0), (3, -2.0)]),),
(Vectors.dense([4.0, 5.0, 0.0, 3.0]),),
(Vectors.dense([6.0, 7.0, 0.0, 8.0]),),
(Vectors.sparse(4, [(0, 9.0), (3, 1.0)]),)]
corr_df = spark.createDataFrame(data, ["features"])
r1 = Correlation.corr(corr_df, "features").collect()
r2 = Correlation.corr(corr_df, "features", "spearman")
2.方差
计算特征本身取值的分散程度,可以使用方差进行度量,方差选择方法是计算各个特征的方差,如果一个特征的方差非常小甚至为0,则这个特征作为输入,很难对输出产生影响。根据阈值,选择方差大于阈值的特征,比如,应当移除所有的零方差特征,或者接近0方差的特征,即那些在所有的样本上的取值都无变化的特征,此种做法是sklearn.feature_selection中VarianceThreshold函数中的默认方法。
σ
2
=
1
n
∑
i
=
1
n
(
x
i
−
x
ˉ
)
2
\sigma^{2}=\frac{1}{n} \sum_{i=1}^{n}\left(x_{i}-\bar{x}\right)^{2}
σ2=n1i=1∑n(xi−xˉ)2
from pyspark.sql.functions import stddev
3.卡方
卡方特征选择来源于统计学中的卡方检验,适用于特征为分类数据同时标签也为分类数据,根据卡方检验特征与Label分布的独立性,选取与类别标签存在关联的特征。该方法支持三种不同的选择方式:numTopFeatures,percentile,fpr。
-
numTopFeatures:根据卡方检验选择固定数量的topK(K=numTopFeatures)特征,该方法为默认选择,默认topK为50。
-
percentile:百分位数选择,跟numTopFeatures类似,但按比例选择所有特征的一部分。
-
fpr:选择p-value值低于阈值的所有特征。
from pyspark.ml.feature import ChiSqSelector
from pyspark.ml.linalg import Vectors
chisq_df = spark.createDataFrame(
[(Vectors.dense([0.0, 0.0, 18.0, 1.0]), 1.0),
(Vectors.dense([0.0, 1.0, 12.0, 0.0]), 0.0),
(Vectors.dense([1.0, 0.0, 15.0, 0.1]), 0.0)],
["features", "label"])
chisq_selector = ChiSqSelector(numTopFeatures=2, outputCol="chisq_Features")
chisq_model = chisq_selector.fit(chisq_df)
chisq_feature=chisq_model.transform(chisq_df)
4.基于树模型的特征选择
以上所列举的基于统计学意义的特征选择,比如相关系数一般假定特征和label之间是线性关系,但针对二者并非是确定的线性的时候,可以用基于树模型的方法进行高效便捷的特征选择,不需要太多调试,可以按照默认参数构建树模型,多次训练模型,计算特征重要性的平均值,以此得到特征重要性。在Spark.ML中使用GBDT,依据分裂前后节点的impurity减少量来评估特征重要性,因GBDT中的树为回归树,所以impurity计算和节点的分裂标准是MSE或MAE。
通过以下代码完成数据读取,特征重要性的计算和存储;
- 使用Spark.SQL读取Hive数据;
- 通过PySpark.ml构建GBTRegressor评估,计算特征重要性
- 把特征重要性结果存入Hive表中。
import datetime
import numpy as np
import pandas as pd
from pyspark.sql import SparkSession
from pyspark.ml.feature import VectorAssembler
from pyspark.sql.functions import *
from pyspark.sql.types import *
from pyspark.ml.regression import GBTRegressor
spark = SparkSession. \
Builder(). \
config("spark.sql.crossJoin.enabled", "true"). \
config("spark.sql.execution.arrow.enabled", "false"). \
enableHiveSupport(). \
getOrCreate()
class feature_selection(object):
def __init__(self,n_iterations=5):
"""
data: all of feature dataset
remove_cols: useless columns which not input cal feature importance
n_iterations : loop times
"""
self.n_iterations=n_iterations
self.today=(datetime.datetime.today()).strftime('%Y-%m-%d')
self.update_time=str(datetime.datetime.now().strftime('%b-%m-%y %H:%M:%S')).split(' ')[-1]
def gdbt_selection(self,data):
"""
:param data:importance_table
:param n_iterations:
:return:
"""
n_iterations=self.n_iterations
data = data.na.fill(0)
data_all_col = data.dtypes
inputCols = [i[0] for i in data_all_col if i[1] in ('bigint', 'double', 'float','int')]
feature_vec = VectorAssembler(inputCols=inputCols, outputCol="features")
output = feature_vec.transform(data)
label_features = output.select("features", "label")
importance = np.zeros(len(inputCols))
for _ in range(n_iterations):
dtm = GBTRegressor(subsamplingRate=0.8, seed=1688)
model = dtm.fit(label_features)
importance_tmp = model.featureImportances
importance += np.array([i for i in importance_tmp]) / n_iterations
feature_df = pd.DataFrame({'feature': inputCols, "importance": importance})
feature_df.sort_values(by=['importance'], ascending=False, inplace=True)
feature_df['cum_sum'] = feature_df['importance'].cumsum()
feature_df['update_date']=self.today
feature_df['update_time']=self.update_time
feature_df=feature_df[['feature','importance','cum_sum','update_date','update_time']]
feature_df_tab = spark.createDataFrame(feature_df)
try:
feature_df_tab.write.mode("append").format('hive').saveAsTable('app.selection_result')
except:
feature_df_tab.createOrReplaceTempView('feature_df_tab')
spark.sql("drop table if exists app.selection_result")
spark.sql("create table app.selection_result select * from feature_df_tab")
def main():
data=spark.sql("select * from app.selection_result")
fs=feature_selection()
fs.gdbt_selection(data)
if __name__ == '__main__':
main()