Flink ML Bucketizer 连续特征分桶(多列映射、splitsArray、handleInvalid)+ Java 示例解读

1. Bucketizer 做什么?

Bucketizer 会把输入列(连续值)按你配置的 splitsArray 切分成区间,并输出区间编号:

  • 输出的桶索引范围:[0, numSplitsInThisColumn - 2](也就是区间数量-1)
  • 文档描述为 [0, numSplitsInThisColumn - 1],但通常 splits 的长度为 N,会产生 N-1 个区间,因此索引通常落在 [0, N-2](你可以以实际输出为准)

例如 splits 为 [-0.5, 0.0, 0.5]

  • 区间1:[-0.5, 0.0)
  • 区间2:[0.0, 0.5]
    最终输出 bucketIndex 为 0 或 1(两段)

2. 输入列与输出列

输入列(Input Columns)

参数名 类型 默认值 说明
inputCols Number null 需要分桶的连续特征列(可多列)

输出列(Output Columns)

参数名 类型 默认值 说明
outputCols Double null 分桶后的离散特征(桶索引)列(可多列)

输出是 Double 类型的桶索引(有些场景你也会想转成 Int/Long 或进一步 one-hot)。

3. 核心参数(Parameters)详解

Key 默认值 必填 说明
inputCols null 输入列名数组
outputCols null 输出列名数组
splitsArray null 每个输入列的切分点数组(二维数组)
handleInvalid ERROR_INVALID 遇到非法值(NaN、越界等)如何处理

3.1 splitsArray:分桶切分点怎么写?

splitsArrayDouble[][],它的长度必须等于 inputCols.length,每个子数组对应一个输入列的切分点。

要求/建议:

  • 每个切分点数组要单调递增
  • 通常建议首尾用 -INF / +INF 包住,避免边界外数据出错
    比如:[-INF, 10.0, +INF]

3.2 handleInvalid:非法值如何处理?

常见策略(示例中使用了 SKIP_INVALID):

  • ERROR_INVALID:遇到非法值直接报错(默认)
  • SKIP_INVALID:跳过包含非法值的记录(整行被丢弃)
  • 有的体系还会支持 KEEP_INVALID(保留并映射到特殊桶),具体以 Flink ML 实现为准

工程建议:

  • 离线训练:可以用 ERROR_INVALID,尽早暴露数据质量问题
  • 在线/生产:更常用 SKIP_INVALID 或"先做清洗再分桶",避免任务被脏数据打挂

4. Java 示例逐段解读

你的示例做了 4 列特征同时分桶:

4.1 输入数据

java 复制代码
DataStream<Row> inputStream = env.fromElements(Row.of(-0.5, 0.0, 1.0, 0.0));
Table inputTable = tEnv.fromDataStream(inputStream).as("f1", "f2", "f3", "f4");

只有一行数据,四个连续特征:f1=-0.5, f2=0.0, f3=1.0, f4=0.0

4.2 配置 splitsArray

java 复制代码
Double[][] splitsArray =
        new Double[][] {
            new Double[] {-0.5, 0.0, 0.5}, // f1:两段
            new Double[] {-1.0, 0.0, 2.0}, // f2:两段
            new Double[] {Double.NEGATIVE_INFINITY, 10.0, Double.POSITIVE_INFINITY}, // f3:两段
            new Double[] {Double.NEGATIVE_INFINITY, 1.5, Double.POSITIVE_INFINITY}  // f4:两段
        };

可以把它理解为:

  • f1 在 [-0.5,0) 或 [0,0.5] -> bucket 0/1
  • f2 在 [-1,0) 或 [0,2] -> bucket 0/1
  • f3 在 (-inf,10) 或 [10,inf) -> bucket 0/1
  • f4 在 (-inf,1.5) 或 [1.5,inf) -> bucket 0/1

4.3 创建 Bucketizer 并 transform

java 复制代码
Bucketizer bucketizer =
        new Bucketizer()
                .setInputCols("f1", "f2", "f3", "f4")
                .setOutputCols("o1", "o2", "o3", "o4")
                .setSplitsArray(splitsArray)
                .setHandleInvalid(HasHandleInvalid.SKIP_INVALID);

Table outputTable = bucketizer.transform(inputTable)[0];

输出表会保留原列 + 新增 o1~o4 四个桶索引列。

4.4 打印输入与输出

示例把每列输入与输出读出来并打印:

java 复制代码
inputValues[i] = (double) row.getField(bucketizer.getInputCols()[i]);
outputValues[i] = (double) row.getField(bucketizer.getOutputCols()[i]);

最终打印类似:

  • Input Values: [-0.5, 0.0, 1.0, 0.0]
  • Output Values: [?, ?, ?, ?](取决于边界规则,通常 0.0 会落在右边区间)

5. 实战建议:分桶到底怎么设计?

1)优先用 -INF / +INF 包住区间

避免线上出现"越界数据导致报错"。

2)分桶边界最好来自数据分布

常见做法:

  • 等频分桶(按分位数)
  • 等宽分桶(按范围均分)
  • 业务阈值分桶(如价格档位、风控评分段)

3)Bucketizer 通常不是最后一步

分桶后的输出是"桶索引",很多模型更喜欢 one-hot 或 embedding:

  • LR / 线性模型:桶索引 → one-hot(或直接当作类别特征处理)
  • 树模型:可以直接用数值,但分桶可能帮助稳定
相关推荐
黎阳之光26 分钟前
黎阳之光:以视频孪生重构智慧医院信息化,打造高标项目核心竞争力
大数据·人工智能·物联网·算法·数字孪生
她的男孩28 分钟前
从零搭一个企业后台,为什么我把能力拆成 Starter 和 Plugin
java·后端·架构
RainCity31 分钟前
Java Swing 自定义组件库分享(七)
java·笔记·后端
Sam_Deep_Thinking36 分钟前
连锁门店的外卖订单平台对接
java·微服务·架构·系统架构
丷丩1 小时前
三级缓存下MVT地图瓦片服务性能优化策略
算法·缓存·性能优化·gis·geoai-up
_遥远的救世主_1 小时前
从一次结果集密集型查询 OOM 看 Java 服务的稳定性架构治理
java·后端
m0_629494731 小时前
LeetCode 热题 100-----25.回文链表
数据结构·算法·leetcode·链表
一楼的猫1 小时前
从工具链视角对比:番茄作家助手 vs 第三方写作辅助方案
java·服务器·开发语言·前端·学习·chatgpt·ai写作
清平乐的技术专栏2 小时前
【Flink学习】(五)Flink 并行度与任务链,任务运行核心原理
flink
likerhood2 小时前
Java static 关键字从浅入深
java·开发语言