LanceDB Node.js API 详解:使用 permutationBuilder 构建可复现的训练/测试数据切分流水线
【免费下载链接】lancedbDeveloper-friendly OSS embedded retrieval library for multimodal AI. Search More; Manage Less.项目地址: https://gitcode.com/gh_mirrors/la/lancedb
导读
permutationBuilder()是 LanceDB JavaScript SDK 中用于创建数据排列(Permutation)的入口函数:它以一张已存在的表为数据源,通过链式配置完成过滤(filter)、切分(split)与打乱(shuffle),最终生成一张轻量的"排列表",用于机器学习训练集/验证集/测试集的划分与数据加载。读完本文,你将掌握permutationBuilder的完整用法、五种切分策略(随机、哈希、顺序、计算表达式与默认单分片)、可复现的随机种子机制,以及其底层 Rust 实现原理。
函数签名与基本概念
在@lancedb/lancedb的全局命名空间中,permutationBuilder的定义如下(见 函数文档):
function permutationBuilder(table): PermutationBuilder- 参数
table:Table类型,即要生成排列的源表; - 返回值:
PermutationBuilder实例,可通过链式方法继续配置。
函数文档中给出的经典用法示例:
const builder = permutationBuilder(sourceTable, "training_data") .splitRandom({ ratios: [0.8, 0.2], seed: 42 }) .shuffle({ seed: 123 }); const trainingTable = await builder.execute();注意:对照当前仓库源码 permutation.ts,
permutationBuilder的实际实现只接收一个参数(table: Table),文档示例中的第二个字符串参数(表名)在现行版本中已由.persist(connection, tableName)方法替代。以下内容均以当前源码为准。
什么是"排列表"(Permutation Table)?
从 Rust 核心层看,排列表是"对已有表的一种排列视图",见模块文档 permutation.rs:
A permutation view can apply a filter, divide the data into splits, and shuffle the data. The permutation table only stores the split ids and row ids. It is not a materialized copy of the underlying data and can be very lightweight.
即:排列表只保存row_id和split_id两列,不是底层数据的物化副本,因此非常轻量。构建排列表是 O(N) 操作(N 为源表行数),即使面对数十亿行数据也足够高效省内存。这个结论有单元测试直接印证——builder.rs 中的测试 断言排列表的 schema 字段名恰好为["row_id", "split_id"]。
快速上手:构建你的第一个排列表
在本地环境中,先连接数据库并创建源表,然后构造排列表:
import { connect, permutationBuilder, makeArrowTable } from "@lancedb/lancedb"; const db = await connect("./data"); const data = makeArrowTable( [ { id: 1, value: 10 }, { id: 2, value: 20 }, // ...更多行 ], { vectorColumns: {} }, ); const sourceTable = await db.createTable("test_table", data); // 基础排列:不切分、不打乱,只生成 row_id + split_id const builder = permutationBuilder(sourceTable); const permutationTable = await builder.execute(); console.log(await permutationTable.countRows()); // 10对应测试见 permutation.test.ts,其中execute()返回的permutationTable可直接用countRows()统计行数,也可传入 SQL 谓词统计特定分片,例如countRows("split_id = 0")。
链式配置方法与参数详解
PermutationBuilder类(见 permutation.ts 与 类文档)提供以下方法,所有方法都返回新的PermutationBuilder实例,支持连续调用。
execute():执行并创建目标表
execute(): Promise<Table>执行排列并创建目标表。返回 Promise 解析为新的Table实例。注意 builder 是一次性消费的:从 NAPI 层源码 permutation.rs 可以看到,execute()会通过take()取出内部 builder 状态,若再次调用会抛出"Builder already consumed"错误。
const permutationTable = await builder.execute(); console.log(`Created table: ${permutationTable.name}`);filter():SQL 过滤
filter(filter: string): PermutationBuilder配置过滤条件,只有匹配的行才会进入排列表。底层调用with_filter(permutation.rs),在 Rust 核心层通过query().only_if(filter)完成扫描过滤(builder.rs)。
builder.filter("age > 18 AND status = 'active'");persist():持久化排列表
persist(connection: Connection, tableName: string): PermutationBuilder默认情况下排列表保存在内存中的临时数据库(memory:///,表名为"permutation"),见 builder.rs。调用persist后,排列表会被写入指定连接对应的数据库,作为永久表存在,便于后续 DataLoader 工作进程按名字重新打开。
builder.persist(connection, "permutation_table");splitRandom():随机切分
splitRandom(options: SplitRandomOptions): PermutationBuilder按随机方式将行分配到各分片,最常用于 train/test 切分。选项接口见 SplitRandomOptions,字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
ratios | number[](可选) | 各分片的比例,如[0.7, 0.3] |
counts | number[](可选) | 各分片的精确行数,如[1000, 500] |
fixed | number(可选) | 固定大小:第一分片取 N 行,其余进入第二分片 |
seed | number(可选) | 随机种子,保证结果可复现 |
clumpSize | number(可选) | 按连续行块打乱/分配,改善云端 I/O 性能 |
splitNames | string[](可选) | 分片名称,存储在排列表的配置元数据中 |
三种切分方式示例:
// 按比例切分 builder.splitRandom({ ratios: [0.7, 0.3], seed: 42 }); // 按精确行数切分 builder.splitRandom({ counts: [1000, 500], seed: 42 }); // 固定大小切分:前 100 行为 split 0,其余为 split 1 builder.splitRandom({ fixed: 100, seed: 42 });校验规则:NAPI 层强制要求ratios、counts、fixed三者中恰好提供一项,否则抛出"Exactly one of 'ratios', 'counts', or 'fixed' must be provided"(permutation.rs),并映射为 Rust 核心的SplitSizes::Percentages / Counts / Fixed枚举(split.rs)。
splitHash():哈希切分(防数据泄漏)
splitHash(options: SplitHashOptions): PermutationBuilder基于指定列的哈希值将行分配到分片,保证同一实体(如 user_id)永远落入同一分片,这对避免训练/测试数据泄漏至关重要。选项见 SplitHashOptions:
| 字段 | 类型 | 说明 |
|---|---|---|
columns | string[](必填) | 参与哈希的列名 |
splitWeights | number[](必填) | 各分片的哈希空间权重,决定分片大致行数比例 |
discardWeight | number(可选) | 丢弃权重,控制被剔除行的比例,默认 0 |
splitNames | string[](可选) | 分片名称 |
builder.splitHash({ columns: ["user_id"], splitWeights: [70, 30], discardWeight: 0, });从 Rust 核心的注释(split.rs)可以理解权重语义:split_weights决定如何划分 u64 哈希空间以近似控制各分片行数,但不保证精确比例(例如所有行哈希值相同则全部落入同一分片)。discard_weight用于丢弃一部分行——比如想要分片 1 约占 5%、分片 2 约占 10% 时,可设splitWeights: [1, 2]、discardWeight: 17。校验规则要求columns与splitWeights非空且权重必须大于 0(split.rs)。
splitSequential():顺序切分
splitSequential(options: SplitSequentialOptions): PermutationBuilder按行序切分:前 N1 行进分片 0,接下来 N2 行进分片 1,依此类推,主要用于调试和测试(split.rs)。选项见 SplitSequentialOptions,支持ratios/counts/fixed/splitNames,同样要求三者恰好提供一项。
// 按比例 builder.splitSequential({ ratios: [0.8, 0.2] }); // 按行数 builder.splitSequential({ counts: [800, 200] }); // 固定大小 builder.splitSequential({ fixed: 1000 });splitCalculated():基于计算表达式切分
splitCalculated(options: SplitCalculatedOptions): PermutationBuilder当数据集中已经存在切分字段(如已有split列)时,直接用一个返回整数(0 到分片数减 1)的 SQL 计算表达式来分配分片;此时counts/ratios会被忽略(split.rs)。选项见 SplitCalculatedOptions:
| 字段 | 类型 | 说明 |
|---|---|---|
calculation | string(必填) | 返回分片编号(0 起)的 SQL 表达式 |
splitNames | string[](可选) | 分片名称 |
builder.splitCalculated({ calculation: "user_id % 3" });shuffle():打乱行序
shuffle(options: ShuffleOptions): PermutationBuilder对排列结果做随机打乱,对随机梯度下降等训练场景尤为重要。选项见 ShuffleOptions:
| 字段 | 类型 | 说明 |
|---|---|---|
seed | number(可选) | 随机种子,使打乱可复现 |
clumpSize | number(可选) | 以连续行块为单位打乱,牺牲部分随机性换取 I/O 性能 |
// 基础打乱 builder.shuffle({ seed: 42 }); // 按 clump 打乱 builder.shuffle({ seed: 42, clumpSize: 10 });关于clumpSize的取舍,Rust 核心有明确说明(builder.rs):例如clumpSize: 16意味着按 16 行连续块打乱,IOPS 可减少约 16 倍,但这 16 行在训练时始终相邻,可能影响模型训练效果;读取时的局部 shuffle 无法替代全局 shuffle。若不配置 shuffle,默认策略为None(不排序,便于调试)。
排列表的元数据与版本一致性
生成的排列表 schema 元数据中会记录构建时的基表信息(builder.rs):
base_version:排列基于的基表版本号(必写);base_branch:基表所在分支(仅非 main 分支时写入,main 分支缺省该键);split_names:分片名称的 JSON 序列化(仅在配置了splitNames时写入)。
这些信息保证 DataLoader 工作进程在基表后续被写入后,仍能按记录的快照版本解析 row 地址。从源码注释可以推断,build()流程还会:为远程表固定快照版本(snapshot_at_current_version)、拒绝带 LSM 写规格的表(未刷盘的行没有 row_id,无法被排列引用,直接报"the data loader does not support tables with an LSM write spec"错误)、在扫描顺序不确定时按row_id排序以保证分片分配的确定性(builder.rs)。
底层实现:Rust 到 JavaScript 的调用链
理解完整调用链有助于排查问题:
- JS 层:
permutationBuilder(table)从LocalTable包装对象中取出内部原生表,调用原生模块的nativePermutationBuilder,再包成 TS 的PermutationBuilder(permutation.ts); - NAPI 层:
permutation_builder从 RustTable克隆内部句柄,创建LancePermutationBuilder::new(inner_table)(permutation.rs),各方法通过modify()函数式更新配置(builder 被消费后再次使用会报错); - 核心层:
build()依次执行——固定快照 → 校验 LSM 写规格 → 按row_id投影 + 过滤 →Splitter::apply分配分片 →Shuffler::shuffle打乱(外部排序,默认单文件上限10 * 1024 * 1024行)→ 按split_id排序 → 重命名_rowid为row_id→ 写入目标数据库(builder.rs)。
此外,排序过程中内存上限可通过环境变量LANCEDB_PERM_BUILDER_MEMORY_LIMIT调整,默认值为 100 MiB(DEFAULT_MEMORY_LIMIT,builder.rs)。
典型应用场景
- 训练/验证/测试切分:
splitRandom({ ratios: [0.8, 0.1, 0.1], seed: 42 })配合.shuffle({ seed: 123 })生成固定随机种子下的可复现划分; - 防泄漏的按实体切分:多用户数据使用
splitHash({ columns: ["user_id"], splitWeights: [80, 20] }),确保同一用户的行不会同时出现在训练与测试集; - 利用已有切分字段:数据集已带
split列时,用splitCalculated({ calculation: "split" })直接复用; - 仅取子集:通过
splitRandom({ ratios: [0.1] })或filter()快速构建 10% 数据的小型排列,用于快速实验; - 分布式数据加载:排列表是轻量的
row_id + split_id映射,可持久化后供多进程、多节点并行读取同一分片(分片并非并行处理的前提,单个分片同样可被并行加载)。
总结
permutationBuilder将"过滤—切分—打乱"这一 ML 数据准备的核心流水线封装为一次链式调用:它不复制底层数据,只产出轻量的row_id + split_id排列表,并通过seed保证可复现性。结合 permutation.test.ts、permutation.ts 与 Rust 核心 builder.rs,开发者可以快速上手并将其集成到自己的训练数据加载流程中。
【免费下载链接】lancedbDeveloper-friendly OSS embedded retrieval library for multimodal AI. Search More; Manage Less.项目地址: https://gitcode.com/gh_mirrors/la/lancedb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考