[Spark/ML] 特征取值分布与特征分桶

[Spark/ML] 特征取值分布与特征分桶

2020/10/17

分桶
  1. 将连续型特征离散化为离散特征。当数值特征跨越不同的数量级时,模型可能会只对大的特征值敏感,这种情况可以考虑分桶操作。
  2. 分桶后得到的稀疏向量,内积乘法运算速度更快,计算结果更方便存储;对异常数据有很强的鲁棒性
分桶方法
  1. 等频分桶

    每个桶内的数据量严格相等,可能存在的问题是同一个桶内的数据取值差异较大。

  2. 等距分桶

    根据值域等距截取,相同数值范围内的数据落入同一个桶。适用于数据分布均匀的情况,否则可能会导致各个桶内数据量不均匀。

    对于重尾分布的正数,使用对数变换处理,它将分布在高端的长尾压缩成较短的尾部,并将低端扩展成较长的头部。

  3. 模型分桶。使用模型找到最佳分桶,比如聚类,将特征分成多个类别,或者树模型,这种非线性模型天生具有对连续型特征切分的能力,利用特征分割点进行离散化。

注意事项
  • 要让桶内的属性取值变化对样本标签的影响基本在一个不大的范围,即不能出现单个桶内,样本取值变化很大的情况;
  • 每个桶内都有足够的样本,如果样本太少,随机性太大,不具有统计意义上的说服力;
  • 每个桶内的样本进行分布均匀;
实战
  1. 绘制特征取值分布图

    select day, 
         get_json_object(json_data, '$.name') as name,
         get_json_object(json_data, '$.sex') as sex,
         get_json_object(json_data, '$.age') as age,
         get_json_object(json_data, '$.height') as height,
         get_json_object(json_data, '$.weight') as weight
    from database.table
    where day between '2020-08-01' and '2020-10-12'
    

注意:绘制特征值的分布图时,要体现出特征的数量(横坐标)。方法为:先通过sql将指定范围内的特征数值取出。将数据放入excel,然后将各个特征的取值按照升序排列后,绘制分布图。

  1. 等距分桶和等频分桶代码

    • 等距分桶:关键在于根据值域取值,确定分位点
    • 等频分桶:关键在于根据数据量取值,确定分位点
    object BucketTest {
        
        val BUCKET_NUMBER = 5 // 可设置
        
        val DATA_TOTAL_NUMBER = 10000 // 可设置
        
        case class PeopleInfo(name: String, age: Double, height: Double, weight: Double)
        
        /**
         * 等距分桶的分段值域区间, 及对应桶编号(0,1,2,3,..., BUCKET_NUMBER-1)
         * @param sourceData 原始数据
         * @param fun case class PeopleInfo 到 字段名称的映射函数
         * @return
         */
        def equalDistanceBucket(sourceData: RDD[PeopleInfo], fun: PeopleInfo => Double, fieldName:String): mutable.HashMap[(Double, Double), Int] = {
          val value_sorted_array = sourceData.map(fun).collect().sorted
          val max_value = value_sorted_array.last
    
          // 针对某些特殊字段, 处理头部的陡变部分, 单独分桶
          val min_value = fieldName match {
              case  "height" => 100D
              case  "weight" => 50D
          }
    
          val step = (max_value - min_value) / BUCKET_NUMBER // step表示等距步长
          println(s"equalDistanceBucket step = ${step}")
    
          val valueWindow = new Array[Double](BUCKET_NUMBER + 1) // 等距分桶的分位信息, 长度为 BUCKET_NUMBER + 1
          for (i <- 0 until (BUCKET_NUMBER - 1)) {
              valueWindow(i) = min_value + step * i
          }
          valueWindow(BUCKET_NUMBER - 1) = max_value // 共 N - 1 个桶
    
          val bucketMap = new mutable.HashMap[(Double, Double), Int]()
          for (i <- 0 until (BUCKET_NUMBER - 1)) {
              val duration = (valueWindow(i), valueWindow(i + 1))
              bucketMap.put(duration, i + 1)
          }
          bucketMap.put((0.0D, valueWindow(0)), 0) // 再加第 0 个桶(头部陡变部分单独分桶), 共 N -1 + 1 个桶
    
          bucketMap
      }
        
        /**
         * 等频分桶的分段值域区间, 以及对应桶编号(0,1,2,3,..., BUCKET_NUMBER-1)
         * @param sourceData
         * @param fun case class PictureInfo 到 字段名称的映射函数
         * @return [(分段起始值, 分段终止值), 对应分桶编号]
         */
        def equalFrequencyBucket(sourceData: RDD[PeopleInfo], fun: PeopleInfo => Double): mutable.HashMap[(Double, Double), Int] = {
          val value_sorted_array = sourceData.map(fun).collect().sorted
          val max_value = value_sorted_array.last
    
          val step = Math.floorDiv(DATA_TOTAL_NUMBER, BUCKET_NUMBER).toInt // step表示等频步长
          println(s"equalFrequencyBucket step = ${step}")
    
          val valueWindow = new Array[Double](BUCKET_NUMBER + 1) // 将分位点的频次位置, 映射到值域
          for (i <- 0 until BUCKET_NUMBER) {
              valueWindow(i) = value_sorted_array(i * step)
          }
          valueWindow(BUCKET_NUMBER) = max_value
    
          val bucketMap = new mutable.HashMap[(Double, Double), Int]()
          for (i <- 0 until BUCKET_NUMBER) {
              val duration = (valueWindow(i), valueWindow(i + 1))
              bucketMap.put(duration, i)
          }
          bucketMap
      }
      
      // 调用方法(只演示用法,不考虑实际意义)
      val heightBucket = equalFrequencyBucket(sourceData, fun = (x: PeopleInfo) => x.height, "height") // 对height采用等频分桶
      val weightBucket = equalDistanceBucket(sourceData, fun = (x: PeopleInfo) => Math.log(x.weight) / Math.log(2), "weight") // 对weight采用取对数后的等距分桶
    }
    
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值