Scala - 数值型特征分桶

简介: 机器学习中最基础的一步就是数据的特征工程,这其中最常见的就是数值型特征的分桶,下面使用两种方法对数值型特征分桶并对比效率。

一.引言

机器学习中最基础的一步就是数据的特征工程,这其中最常见的就是数值型特征的分桶,下面使用两种方法对数值型特征分桶并对比效率。给定数值型特征划分的连续递增(保序) boundary 如下:

val boundary = Array(0.0, 1, 2, 3, 5, 10, 20, 50, 75, 99)
val numCuts = boundary.zipWithIndex

image.gif

给定的边界共包含10个分界点,所以可以生成 0-10 共计11个索引。

二.遍历法

遍历法的思想很简单,给定特征值 FeatValue,由于 boundary 是保序的,所以依次遍历分界点与 featValue,当对应值小于分界点时,返回对应位置的 index 即可,这里 index 的获取通过 scala 自带的 zipWithIndex 得到。

def loopCut(featValue: Double, numCuts: Array[(Double, Int)]): Int = {
    // 获取最大分界点
    val maxCuts = numCuts.map(_._1).max
    // 判断异常值
    if (featValue.equals(NaN)) return 0
    var cutNum = 0
    var state = true
    val it = numCuts.iterator
    while (state && it.hasNext) {
      val value = it.next()
      val edge = value._1
      val index = value._2
      if (edge >= featValue.toDouble) {
        cutNum = index
        state = false
      }
    }
    // 特征值超过所有边界点,取 boundary 长度作为 cut
    if (maxCuts < featValue.toDouble) cutNum = numCuts.size
    cutNum
  }

image.gif

三.二分法

遍历的方法容易理解,但是需要靠后的特征值,需要遍历的次数大幅增加,所以可以通过二分法缩减寻找目标值的速度。

def binaryCut(featValue: Double, numCuts: Array[(Double, Int)]): Int = {
    var cutNum = 0
    val cutArr = numCuts.map(_._1)
    // 与起始、终止值相同直接返回
    val numValue = featValue.toDouble
    if (numValue <= cutArr.head) {
      cutNum = 0
      return cutNum
    }
    // 判断边界值
    if (numValue > cutArr.last) {
      cutNum = numCuts.length
      return cutNum
    }
    // 二分查找
    var left = 0
    var right = cutArr.length - 1
    while (left <= right) {
      val middle = left + (right - left) / 2
      if (cutArr.apply(middle) >= numValue && cutArr.apply(middle - 1) < numValue) {
        cutNum = middle
        return cutNum
      } else if (cutArr.apply(middle) < numValue) {
        left = middle + 1
      } else if (cutArr.apply(middle) > numValue) {
        right = middle - 1
      } else {
        return cutNum
      }
    }
    cutNum = numCuts.length
    cutNum
  }

image.gif

四.效率对比

def runCut(epoch: Int, cutValue: Double, numCuts: Array[(Double, Int)]): Unit = {
    val st = System.currentTimeMillis()
    val value = cutFunction(cutValue, numCuts)
    (0 until epoch).foreach(epoch => {
      newCut(cutValue, numCuts)
    })
    val end = System.currentTimeMillis() - st
    println(s"[Log] Num: $cutValue CutNum: $value Epoch: $epoch Cost: $end")
  }

image.gif

[Loop] Num: 88.0 CutNum: 9 Epoch: 1000 Cost: 55
[Binary] Num: 88.0 CutNum: 9 Epoch: 1000 Cost: 6

image.gif

分别选取 loopCut 和 binaryCut 作为 cutFunction 使用不同 FeatValue 进行 epoch 次测试:

Function FeatValue Epoch Cost(ms)
LoopFunction 遍历查找 1.5 1000 59
BinaryFunction 二分查找 1.5 1000 9
LoopFunction 遍历查找 5.5 1000 63
BinaryFunction 二分查找 5.5 1000 13
LoopFunction 遍历查找 88 1000 55
BinaryFunction 二分查找 88 1000 6

选取前,中,后三个数值进行 cut,二分查找效率都优于遍历查找,在极端情况 [所有 FeatValue 均小于第一个分界点时] 下,遍历查找的速度与二分查找相近。

五.扩展

上述方法主要应用于 scala 工程端的特征处理,在 python tf 端可以通过 tf.feature_column 实现数值型特征分桶的特征处理任务,例如: numeric_column + bucketized_column

numeric_column: 获取离散数值

buketized_column: 离散数值分桶,需给定 boundaries

cos_index = tf.feature_column.bucketized_column(
        tf.feature_column.numeric_column(key='cos', shape=(1,),
                                         default_value=0,
                                         dtype=tf.dtypes.float32),
        boundaries=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
    product_dict = {"cos": np.random.random(size=(10, 1))}
    feature_layer = tf.keras.layers.DenseFeatures(cos_index)
    output = feature_layer(product_dict)
    print(output)
    print("=========================")

image.gif

同上,boundary 的分界点有 N=9 个,则特征会被划分至 N+1=10 个 Filed 中:

tf.Tensor(
[[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]], shape=(10, 10), dtype=float32)

image.gif

目录
相关文章
|
1月前
|
Java Scala
Scala Trait(特征)
Scala Trait(特征)
35 2
|
2月前
|
分布式计算 大数据 Java
大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友
大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友
75 5
|
2月前
|
分布式计算 关系型数据库 MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
56 3
|
2月前
|
消息中间件 分布式计算 NoSQL
大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新
大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新
48 0
|
2月前
|
消息中间件 存储 分布式计算
大数据-103 Spark Streaming Kafka Offset管理详解 Scala自定义Offset
大数据-103 Spark Streaming Kafka Offset管理详解 Scala自定义Offset
104 0
|
2月前
|
分布式计算 大数据 Java
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
43 1
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
|
2月前
|
SQL 分布式计算 Java
大数据-96 Spark 集群 SparkSQL Scala编写SQL操作SparkSQL的数据源:JSON、CSV、JDBC、Hive
大数据-96 Spark 集群 SparkSQL Scala编写SQL操作SparkSQL的数据源:JSON、CSV、JDBC、Hive
68 0
|
2月前
|
缓存 分布式计算 大数据
大数据-90 Spark 集群 RDD 编程-高阶 RDD容错机制、RDD的分区、自定义分区器(Scala编写)、RDD创建方式(一)
大数据-90 Spark 集群 RDD 编程-高阶 RDD容错机制、RDD的分区、自定义分区器(Scala编写)、RDD创建方式(一)
64 0
|
2月前
|
分布式计算 算法 大数据
大数据-90 Spark 集群 RDD 编程-高阶 RDD容错机制、RDD的分区、自定义分区器(Scala编写)、RDD创建方式(二)
大数据-90 Spark 集群 RDD 编程-高阶 RDD容错机制、RDD的分区、自定义分区器(Scala编写)、RDD创建方式(二)
64 0
|
6月前
|
分布式计算 资源调度 Java
Scala+Spark+Hadoop+IDEA实现WordCount单词计数,上传并执行任务(简单实例-下)
Scala+Spark+Hadoop+IDEA实现WordCount单词计数,上传并执行任务(简单实例-下)
78 0