模块 basalt.data¶
6 个公开类型
BasaltD 数据管线:流式与内存映射两条读取路径、标准化参数。
类型概览
| 类型 | 种类 | 说明 |
|---|---|---|
DataLoader | class | BasaltD 记录的批式读取入口,附带统计与写盘工具。 |
Normalization | class | 不可变的标准化参数,应用时零分配。 |
DataLoader.Batch | class | 单批数据的可复用视图,随读取器推进被覆盖。 |
DataLoader.BatchStream | class | 顺序分批读取 BasaltD,可回绕重读。 |
DataLoader.Builder | class | 两个读取器共享的配置构建器。 |
DataLoader.MappedBatchStream | class | 内存映射读取器,按 epoch 打乱且不重复分配。 |
- final class DataLoader¶
BasaltD 记录的批式读取入口,附带统计与写盘工具。
文件头固定 16 字节:magic 0x4A534C44("BasaltD")、version 1、featureCount 与保留位(写入为 0,读取要求为 0)。其后每条记录为 featureCount 个 double 特征加一个 double 标签,记录宽度 (featureCount + 1) * 8 字节。
两个读取器共享同一 Batch 契约。BatchStream 顺序读入按批大小分配的直接缓冲区,再用 DoubleBuffer 视图解码;MappedBatchStream 映射整个文件,以绝对批量读取汇集行,映射之后一个 epoch 内无系统调用。批次、特征数组、标签数组与字节缓冲只在构造期分配一次。
除读取器外,类还提供三个静态工具:write 生成 BasaltD 文件,estimateNormalization 单遍统计均值与样本标准差,rowCount 由文件大小推算记录数。
示例Path file = Path.of("dataset.bsld"); Matrix features = Matrix.fromRows(new double[][]{ {1.0, 2.0}, {3.0, 4.0}, {5.0, 7.0}, {2.0, 1.0}}); DataLoader.write(file, features, new double[]{0.0, 1.0, 1.0, 0.0}); Normalization normalization = DataLoader.estimateNormalization(file, 2); try (DataLoader.BatchStream stream = DataLoader.builder(file) .featureCount(2) .batchSize(2) .normalization(normalization) .open()) { while (stream.next()) { DataLoader.Batch batch = stream.batch(); for (int row = 0; row < batch.rows(); row++) { double label = batch.label(row); double first = batch.feature(row, 0); } } }注意
类本身无实例(构造器私有),Builder、Batch、BatchStream、MappedBatchStream 均为静态嵌套类型。
注意
读取器只产出 double[],不构造张量、不涉及计算图;需要矩阵时由调用方转换。
注意
批次数组在流的生命周期内复用,next() 会覆盖上一批内容,跨批保存须自行拷贝。
注意
每个流都不是线程安全的,并发读取应为每个线程建立独立的流。
参见:basalt.data.Normalization, basalt.data.DataLoader.Builder, basalt.data.DataLoader.Batch, basalt.data.DataLoader.BatchStream, basalt.data.DataLoader.MappedBatchStream, basalt.io.ModelCodec
方法
- builder(Path source) → Builderstatic¶
为指定 BasaltD 文件创建读取器配置。
注意
此时不打开文件,路径合法性推迟到 open()/openMapped() 才校验
- 参数
source (Path) – BasaltD 文件路径;为 null 时构造 Builder 立即抛 NullPointerException
- 返回
尚未设置 featureCount 的 Builder,后续用 open() 或 openMapped() 打开流
- estimateNormalization(Path source, int featureCount) → Normalizationstatic¶
单遍统计各特征均值与样本标准差。
内部以 batchSize 固定为 1024 的顺序读取器遍历全部记录,用 Welford 增量公式累计均值与二阶中心矩,不缓存整份数据;标准差按样本口径计算,即除以 count - 1。
样本数少于 2 时抛 IllegalArgumentException。读取失败或文件头与 featureCount 不匹配时抛 UncheckedIOException。标准差为 0 的特征在后续应用时只做中心化。
注意
只读取特征列,标签列被跳过
注意
统计过程复用单个批次数组,内存占用与记录数无关
- 参数
source (Path) – BasaltD 文件路径
featureCount (int) – 每条记录的特征数,必须为正且与文件头中的值一致
- 返回
持有均值和标准差副本的不可变 Normalization
- write(Path target, Matrix features, double[] labels)static¶
将特征矩阵与标签写成 BasaltD 文件。
以 CREATE、WRITE、TRUNCATE_EXISTING 打开目标文件,先写 16 字节头(magic、version 1、features.columns()、保留位 0),再逐行写出特征与标签。缓冲容量取 1 MiB 与两条记录宽度中的较大者,写满即刷出。
features 或 labels 为 null 时抛 NullPointerException;labels.length 与 features.rows() 不等时抛 IllegalArgumentException;通道打开或写入失败时抛 UncheckedIOException。记录宽度计算溢出 int 时抛 ArithmeticException。
注意
每条记录先在复用的一维数组内拼装再整体写入缓冲
注意
写入的浮点数为 ByteBuffer 默认的大端序
- 参数
target (Path) – 输出路径,已存在的文件会被截断覆盖
features (Matrix) – 特征矩阵,行数即记录数,列数即文件头中的 featureCount
labels (double[]) – 标签数组,长度必须等于 features.rows(),逐行写入记录末尾
- rowCount(Path source, int featureCount) → longstatic¶
按文件大小推算记录数。
取文件字节数减去 16 字节头,再除以 (featureCount + 1) * 8。余数非零或结果载荷为负时视为截断,抛 UncheckedIOException(cause 为 IOException);文件不存在或不可读同样包装为 UncheckedIOException。
实现只依赖 Files.size,不打开通道、不读取也不校验 magic、version 与保留位,因此对头部损坏的文件仍可能返回一个数值。
注意
需要头部校验时改用 builder(source).featureCount(...).open(),其会比对文件头
注意
结果以 long 返回,但 openMapped() 只接受不超过 Integer.MAX_VALUE 的行数
- 参数
source (Path) – BasaltD 文件路径
featureCount (int) – 每条记录的特征数,用于推导记录宽度;加一后乘 8 溢出 int 时抛 ArithmeticException
- 返回
记录条数,未做任何上界限制
- final class Normalization¶
不可变的标准化参数,应用时零分配。
实例保存每特征均值、标准差,以及构造时预先算好的标准差倒数。应用公式为 (x - mean) * inverseStd,原地写回传入数组,不产生临时对象。标准差为 0 时倒数取 1.0,该特征只做中心化,不会产生无穷大。
构造入口只有 standardize 与 DataLoader.estimateNormalization。standardize 会克隆两个入参数组,means() 与 standardDeviations() 也返回副本,因此外部对数组的修改不会影响实例,实例可安全共享。
示例Path file = Path.of("dataset.bsld"); DataLoader.write(file, Matrix.fromRows(new double[][]{{1.0, 2.0}, {3.0, 4.0}, {5.0, 7.0}}), new double[]{0.0, 1.0, 0.0}); Normalization normalization = DataLoader.estimateNormalization(file, 2); int features = normalization.featureCount(); double[] means = normalization.means(); double[] deviations = normalization.standardDeviations();注意
apply 是包级方法,仅由 BatchStream 与 MappedBatchStream 在解码一行后调用,库外无法直接调用。
注意
实例不可变、无状态,可跨线程共享;但 apply 会就地覆盖调用方数组。
注意
特征数在构造时固定,不能对长度不同的记录复用同一实例。
参见:
basalt.data.DataLoader.estimateNormalization(Path, int),basalt.data.DataLoader.Builder.normalization(Normalization)方法
- standardize(double[] means, double[] standardDeviations) → Normalizationstatic¶
由均值与标准差构造标准化参数。
两个数组长度必须相同且非空,所有元素必须有限,标准差还须非负,否则抛 IllegalArgumentException。通过校验后克隆入参并预计算倒数,返回的实例与入参数组不再共享存储。
标准差为 0 的位置倒数记为 1.0,使该特征在应用时减去均值后保持原值。
注意
负数、NaN 与无穷都会被拒绝,不会静默传播到训练数据
- 参数
means (double[]) – 每个特征的均值,长度即特征数,不能为空
standardDeviations (double[]) – 每个特征的标准差,长度须与 means 一致,取值须有限且非负
- 返回
持有两份副本与倒数表的不可变实例
- featureCount() → int¶
特征数,等于均值数组长度。
注意
Builder 用该值校验归一化参数与读取器配置是否匹配
- 返回
构造时确定的特征数
- means() → double[]¶
返回均值的防御性副本。
注意
每次调用都克隆,循环中反复调用会产生额外分配
- 返回
长度为 featureCount() 的新数组,修改不影响实例
- standardDeviations() → double[]¶
返回标准差的防御性副本。
注意
返回的是标准差本身,不是内部使用的倒数
- 返回
长度为 featureCount() 的新数组,元素非负
- static final class DataLoader.Batch¶
单批数据的可复用视图,随读取器推进被覆盖。
特征以行优先方式存放在长度为 featureCount * batchSize 的一维数组中,标签数组长度为 batchSize。两个数组在构造时按批大小一次性分配,之后每次 next() 只改写前部内容,rows() 指明本批真正有效的行数。
有效区间为:特征前 rows() * featureCount() 个元素,标签前 rows() 个元素。数组尾部残留上一批的数据,不应读取。实例由读取器创建并长期持有,调用方只做只读访问。
示例try (DataLoader.BatchStream stream = DataLoader.builder(Path.of("dataset.bsld")) .featureCount(2) .batchSize(64) .open()) { while (stream.next()) { DataLoader.Batch batch = stream.batch(); for (int row = 0; row < batch.rows(); row++) { double label = batch.label(row); double first = batch.feature(row, 0); } } }注意
features() 与 labels() 返回内部数组本身,不复制,跨批保留引用会读到被覆盖的内容。
注意
实例与流一一绑定,非线程安全;批次内容在下次 next() 后失效。
注意
需要跨批留存时,用 System.arraycopy 复制有效区间。
参见:
basalt.data.DataLoader.BatchStream.batch(),basalt.data.DataLoader.MappedBatchStream.batch()方法
- rows() → int¶
当前批的有效行数。
注意
末批通常小于 batchSize,遍历必须以该值为上界
- 返回
本批行数,取值不超过 batchSize;流耗尽后为 0
- featureCount() → int¶
每条记录的特征数。
- 返回
构造流时指定的特征数,整条流内不变
- features() → double[]¶
返回内部特征数组,行优先且复用。
数组长度恒为 featureCount() * batchSize,仅前 rows() * featureCount() 个元素属于当前批。已配置归一化时,数组中是标准化后的值,而非文件原始值。
注意
下一次 next() 会覆盖前部内容
注意
直接传给需要整块特征的 API 时,须按 rows() * featureCount() 截取
- 返回
内部数组本身,不复制
- labels() → double[]¶
返回内部标签数组,复用。
数组长度恒为 batchSize,仅前 rows() 个元素属于当前批;标签不参与归一化,与文件中的值一致。
注意
下一次 next() 会覆盖前部内容
- 返回
内部数组本身,不复制
- feature(int row, int feature) → double¶
读取批内某行某列的特征值。
以 row * featureCount() + feature 定位数组元素;越界检查同时约束行与列,row 不小于 rows() 或 feature 不小于 featureCount() 时抛 IndexOutOfBoundsException。
注意
逐元素访问有下标运算开销,整批处理时优先取 features() 数组
- 参数
row (int) – 批内行号,有效范围 [0, rows())
feature (int) – 特征下标,有效范围 [0, featureCount())
- 返回
该位置的特征值,已应用归一化(若配置)
- label(int row) → double¶
读取批内某行的标签。
row 超出 [0, rows()) 时抛 IndexOutOfBoundsException,错误信息与特征越界区分。
- 参数
row (int) – 批内行号,有效范围 [0, rows())
- 返回
该行记录的标签值,未做归一化
- static final class DataLoader.BatchStream implements AutoCloseable¶
顺序分批读取 BasaltD,可回绕重读。
构造时按 recordBytes * batchSize 分配直接缓冲区,并创建批次、特征数组与标签数组,之后不再分配。每次 next() 先压缩缓冲区,从通道循环读取直到缓冲填满或文件结束,再把完整的记录解码进批次;不足一条记录的尾部字节在文件结束时抛 EOFException。
rewind() 把通道位置重置到 16 字节头之后、清空缓冲区并复位结束标志,缓冲与批次对象继续复用,不重新打开文件。流在关闭后不可再用。
示例try (DataLoader.BatchStream stream = DataLoader.builder(Path.of("dataset.bsld")) .featureCount(8) .batchSize(128) .open()) { for (int epoch = 0; epoch < 3; epoch++) { while (stream.next()) { DataLoader.Batch batch = stream.batch(); int rows = batch.rows(); } stream.rewind(); } }注意
必须用 try-with-resources 或显式 close() 释放文件通道。
注意
shuffle 与 seed 只影响 openMapped(),本读取器始终按文件顺序返回。
注意
非线程安全,且不支持与另一读取器共享同一批次。
参见:
basalt.data.DataLoader.Builder.open(), basalt.data.DataLoader.MappedBatchStream方法
- next() → boolean¶
解码下一批,流耗尽返回 false。
从通道补齐缓冲后按记录宽度切分,最多解码 batchSize 行,逐行写入特征数组、标签数组,并在配置了归一化时就地标准化该行。返回 false 时批次行数被置 0。
流已关闭时抛 IOException;文件尾剩余字节不是记录宽度的整数倍时抛 EOFException,用于识别截断文件。
注意
每次调用都复用同一 Batch 实例,返回的引用在下次调用后内容改变
- 返回
是否读到至少一行
- 抛出
IOException – 流已关闭,或文件尾部存在不完整记录(EOFException)
- batch() → Batch¶
返回与流绑定的批次视图。
注意
首次 next() 之前 rows() 为 0
- 返回
构造流时创建并持续复用的 Batch,永不为 null
- rewind()¶
回到第一条记录,缓冲保持不变。
将通道位置设为 16,清空缓冲并复位结束标志,批次行数归零。缓冲区、批次与数组均保留,因此回绕不产生分配,也不重新解析文件头。
注意
回绕后仍需重新调用 next() 才能取到第一批
注意
归一化参数保持不变
- 抛出
IOException – 流已关闭,或通道定位失败
- close()¶
关闭底层文件通道。
首次调用置关闭标志并关闭通道,重复调用不做任何事。关闭后 next() 与 rewind() 均抛 IOException,批次数组仍可读取但内容为最后一次解码结果。
- 抛出
IOException – 关闭通道时发生 I/O 错误
- static final class DataLoader.Builder¶
两个读取器共享的配置构建器。
字段初值:featureCount 为 0(没有默认值,必须显式设置),batchSize 为 1024,normalization 为 null,shuffle 为 false,seed 为 42。所有设置方法返回 this,可链式调用。
open() 与 openMapped() 先校验配置:featureCount 或 batchSize 非正、归一化特征数不匹配时抛 IllegalArgumentException;随后打开通道并比对 16 字节头,I/O 失败一律包装为 UncheckedIOException。
示例DataLoader.Builder builder = DataLoader.builder(Path.of("dataset.bsld")) .featureCount(8) .batchSize(256) .shuffle(true) .seed(7); try (DataLoader.MappedBatchStream stream = builder.openMapped()) { while (stream.next()) { int rows = stream.batch().rows(); } }注意
Builder 本身不持有打开的通道,可在多次 open() 之间复用,每次得到独立的流。
注意
非线程安全,构建完成后不应再修改。
注意
shuffle 与 seed 只对 openMapped() 生效,open() 会静默忽略。
参见:
basalt.data.DataLoader.builder(Path), basalt.data.DataLoader.BatchStream, basalt.data.DataLoader.MappedBatchStream方法
- featureCount(int featureCount) → Builder¶
设置每条记录的特征数。
注意
未设置时 open()/openMapped() 抛 IllegalArgumentException
注意
与文件头不一致时读取阶段抛 UncheckedIOException(Invalid or incompatible BasaltD header)
- 参数
featureCount (int) – 每条记录的特征数,无默认值(初值 0),必须为正且与文件头一致
- 返回
this,便于链式调用
- batchSize(int batchSize) → Builder¶
设置每批最多行数,默认 1024。
注意
同时决定顺序读取器的直接缓冲容量与批次特征数组长度,过大将按 recordBytes * batchSize 一次性分配
- 参数
batchSize (int) – 每批行数上限,默认 1024,必须为正;末批可能更少
- 返回
this,便于链式调用
- normalization(Normalization normalization) → Builder¶
设置读取时应用的标准化参数。
注意
其 featureCount() 必须等于 featureCount 设置,否则 open() 抛 IllegalArgumentException
注意
只影响特征列,标签原样返回
- 参数
normalization (Normalization) – 逐行就地应用的标准化参数;null 表示不归一化,也是默认值
- 返回
this,便于链式调用
- shuffle(boolean shuffle) → Builder¶
是否按 epoch 打乱行序,默认 false。
注意
仅对 openMapped() 生效,open() 忽略该设置
注意
打乱只置换 int[] 索引数组,不移动映射区中的数据
- 参数
shuffle (boolean) – true 时每个 epoch 置换行索引;默认 false 即按文件顺序
- 返回
this,便于链式调用
- seed(long seed) → Builder¶
设置打乱所用随机种子,默认 42。
注意
仅对 openMapped() 且 shuffle 为 true 时有意义,相同 seed 得到相同的行序
- 参数
seed (long) – 初始 epoch 的置换种子,默认 42;后续 epoch 使用 seed + epoch * 1000003
- 返回
this,便于链式调用
- open() → BatchStream¶
打开顺序读取器。
以只读方式打开通道,读取并校验 16 字节头(magic、version、featureCount、保留位),然后按 recordBytes * batchSize 分配直接缓冲区与批次,返回 BatchStream。头部校验失败时通道被关闭并抛 UncheckedIOException;配置非法时在打开通道前抛 IllegalArgumentException。
注意
头部截断会以 EOFException 作为 cause 包装为 UncheckedIOException
注意
不检查文件长度是否为记录宽度的整数倍,截断要到读取尾部才暴露
- 返回
顺序读取器,需由调用方关闭
- openMapped() → MappedBatchStream¶
打开内存映射读取器。
校验头部后把整个文件映射为只读 MappedByteBuffer,跳过 16 字节头得到 DoubleBuffer 切片,并由 (size - 16) / recordBytes 推出总行数。载荷长度不是记录宽度整数倍、行数超过 Integer.MAX_VALUE、或映射失败时抛 UncheckedIOException。
与 open() 的差异:数据由操作系统按页装入,不占用用户态缓冲,也不经过通道读取;批次与索引数组仍然分配,但之后每个 epoch 不再产生系统调用。
注意
映射区在流关闭后由 GC 回收,close() 不会立即解除映射
注意
文件在流使用期间被外部修改会导致内容与校验不一致,加载阶段不做检测
- 返回
内存映射读取器,需由调用方关闭
- static final class DataLoader.MappedBatchStream implements AutoCloseable¶
内存映射读取器,按 epoch 打乱且不重复分配。
构造时把整个文件映射为只读缓冲,并跳过 16 字节头取出 DoubleBuffer 视图;同时建立长度为 totalRows 的 int 索引数组,初值为文件顺序,shuffle 开启时立刻做一次 Fisher-Yates 置换。
每次 next() 用绝对批量读取把记录宽度中的特征段复制进批次,标签单独取一个元素;游标按索引数组推进,一个 epoch 内不再访问通道,也没有字节缓冲。rewind() 让游标归零、epoch 自增,并按 seed + epoch * 1000003 重新置换索引。
示例try (DataLoader.MappedBatchStream stream = DataLoader.builder(Path.of("dataset.bsld")) .featureCount(8) .batchSize(256) .shuffle(true) .seed(7) .openMapped()) { for (int epoch = 0; epoch < 5; epoch++) { while (stream.next()) { DataLoader.Batch batch = stream.batch(); int rows = batch.rows(); } stream.rewind(); } }注意
批次、特征数组与标签数组各分配一次,之后只改写内容。
注意
打乱作用于索引数组,映射区中的原始顺序不变。
注意
close() 只置关闭标志,不显式解除映射;关闭后 next()/rewind() 抛 IllegalStateException(与 BatchStream 的 IOException 不同)。
注意
总行数受 int 索引限制,超过 Integer.MAX_VALUE 的文件在 openMapped() 阶段即被拒绝。
参见:
basalt.data.DataLoader.Builder.openMapped(), basalt.data.DataLoader.BatchStream方法
- next() → boolean¶
汇集下一批,耗尽返回 false。
取 min(batchSize, totalRows - cursor) 行,按索引数组找到源记录,把特征段批量复制进批次数组并逐行取标签,配置了归一化时就地标准化。行数为 0 时返回 false 并把批次行数置 0。
本方法不声明受检异常;流已关闭时抛 IllegalStateException。
注意
不抛出 IOException:映射建立后读取只触及已映射内存
- 返回
是否读到至少一行
- batch() → Batch¶
返回与流绑定的批次视图。
注意
内容在每次 next() 后被覆盖
- 返回
构造时创建并持续复用的 Batch,永不为 null
- rewind()¶
开始新 epoch,必要时重新打乱。
游标归零、epoch 加一;shuffle 为 true 时用 seed + epoch * 1000003 新建 Random 并再次做 Fisher-Yates 置换,因此相邻 epoch 的顺序不同,而相同 epoch 号在相同 seed 下可复现。shuffle 为 false 时只重置游标。
不重新映射文件,也不重新读取文件头;流已关闭时抛 IllegalStateException。
注意
epoch 从 0 开始,首次 rewind() 后为 1
注意
置换就地复用同一 int 数组,不产生分配
- totalRows() → int¶
映射到的总记录数。
注意
与 epoch 无关,整个生命周期内不变
- 返回
由 (文件大小 - 16) / 记录宽度 推出的行数
- epoch() → int¶
已开始的 epoch 计数。
注意
该值同时参与下一轮置换种子的计算
- 返回
初始为 0,每次 rewind() 加一
- close()¶
置关闭标志并停止后续读取。
只把内部标志设为已关闭,不关闭通道、不解除映射;映射区在缓冲区不可达后由 GC 回收。重复调用无副作用,关闭后再调用 next() 或 rewind() 抛 IllegalStateException。
注意
不声明受检异常,适合放在 try-with-resources 中使用