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=(xixˉ)2(yiyˉ)2 (xixˉ)(yiyˉ)

斯皮尔曼等级相关,研究两个等级特征(变量)间相关关系的方法,依据两列成等级之差来进行计算,所以又称为“等级差数法”。

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=1n(n21)6di2d
在这里插入图片描述

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=1n(xixˉ)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。

在这里插入图片描述

通过以下代码完成数据读取,特征重要性的计算和存储;

  1. 使用Spark.SQL读取Hive数据;
  2. 通过PySpark.ml构建GBTRegressor评估,计算特征重要性
  3. 把特征重要性结果存入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()
    
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值