模块 basalt.nn¶
36 个公开类型 · 1 个包内可见类型
手写神经网络层:Dense / Conv2D / RNN / LSTM / GRU / 注意力、序列训练与序列化。
类型概览
| 类型 | 种类 | 说明 |
|---|---|---|
Activation | enum | 强类型激活函数枚举,SOFTMAX 按批内每一行独立归一化。 |
CNN | class | BCHW 输入、BC 输出的卷积网络,由分阶段 Builder 构建。 |
Conv2D | class | BCHW 布局的二维卷积层,权重按 OIHW 排布。 |
Dense | class | 全连接层,权重形状为 (units, inputFeatures)。 |
Flatten | class | 把 BCHW 摊平成 BC,通道、高、宽依次排列。 |
GRU | class | 门控循环层:显式 BPTT 的 many-to-many GRU,输入输出均为 BSF。 |
GradientCheck | class | 用中心差分校验层与序列网络的解析梯度。 |
LSTM | class | 门控循环层:显式 BPTT 的 many-to-many LSTM,输入输出均为 BSF。 |
Layer | interface | 带编译期输入输出布局参数的可微变换接口。 |
Loss | enum | 批量-通道布局上的损失函数,同时产出损失值与输出梯度。 |
LossEvaluation | record | 标量损失与输出梯度的不可变结果,可选携带 softmax-logit 梯度。 |
MaxPooling2D | class | 二维最大池化,缓存 argmax 索引以精确回传梯度。 |
ModelSerializer | class | CNN 的 .bsl 持久化门面,负责层载荷的读写。 |
NeuralNetwork | class | 分阶段构建 CNN:图像层在 Flatten 之前,向量层在其后。 |
Optimizer | enum | 一阶优化算法枚举:SGD、ADAM 与 ADAMW。 |
OptimizerConfig | record | 训练共用的不可变优化器超参。 |
Padding | enum | 卷积补零模式:VALID 不补,SAME 补到按步长整除。 |
ParameterRef | record | 单个可训练张量的实时视图:参数值与最近一次回传的梯度。 |
SelfAttention | class | 单头缩放点积自注意力,支持因果掩码与完整 Softmax 反向传播。 |
SequenceNetwork | class | 端到端序列分类器:BSF → 若干序列层 → 池化 → Dense 头 → BC。 |
SequencePool | class | 沿时间轴把 BSF 序列压缩为每个样本一个 BC 向量。 |
TrainingHistory | record | 逐轮训练损失的不可变快照。 |
VanillaRNN | class | 最简 Elman 循环层:h_t = tanh(W x_t + U h_{t-1} + b)。 |
Conv2D.Builder | class | Conv2D 的分阶段构建器,默认 1 个 3×3 卷积核。 |
Dense.Builder | class | Dense 的构建器,默认 1 个单元且无激活。 |
Flatten.Builder | class | Flatten 的构建器,无任何可配置项。 |
GRU.Builder | class | GRU 构建器,隐藏单元数默认 16,随机种子默认 42。 |
GradientCheck.Report | record | 梯度检验的最大相对误差与检查次数。 |
LSTM.Builder | class | LSTM 构建器,隐藏单元数默认 16,随机种子默认 42。 |
MaxPooling2D.Builder | class | MaxPooling2D 的构建器,默认 2×2 且不重叠。 |
NeuralNetwork.ImageBuilder | class | 图像阶段构建器,累积 Conv2D 与 MaxPooling2D。 |
NeuralNetwork.VectorBuilder | class | 向量阶段构建器,累积 Dense 并配置损失与优化器。 |
SelfAttention.Builder | class | 自注意力构建器,dimension 默认 16、seed 默认 42、非因果。 |
SequenceNetwork.Builder | class | 整网构建器;LAST 池化、分类交叉熵、Adam 0.001、裁剪 5.0。 |
SequencePool.Pooling | enum | 时间轴归约策略。 |
VanillaRNN.Builder | class | VanillaRNN 构建器,隐藏单元数默认 16,随机种子默认 42。 |
- enum Activation¶
强类型激活函数枚举,SOFTMAX 按批内每一行独立归一化。
枚举常量覆盖恒等、ReLU、Sigmoid、Tanh、Softmax、GELU、SiLU 七种。前六种可逐元素计算,SOFTMAX 需要整行数据才能归一化,因此不走标量路径。
三个计算方法都是包级可见:applyScalar 处理单个标量,applyRow 就地处理数组的一段连续区间,derivative 给出局部导数。Dense 与 Conv2D 在各自的前向、反向中调用它们,外部代码只通过 Builder 的 activation 参数间接选择。
GELU 使用 tanh 近似式 0.5x(1 + tanh(√(2/π)(x + 0.044715x³))),其导数按该近似解析求导,不是精确 erf 形式;SILU 为 x·σ(x),导数由 σ 与 x 组合得到。
示例Dense output = Dense.builder() .units(3) .activation(Activation.SOFTMAX) .seed(7L) .build(); Tensor<BC> features = Tensor.ofBC(2, 4, new double[2 * 4]); Tensor<BC> probabilities = output.forward(features); Dense hidden = Dense.builder() .units(8) .activation(Activation.RELU) .build();注意
applyRow 就地改写 values 的 [offset, offset + length) 区间,调用方需保证区间完整覆盖一行
注意
SOFTMAX 的 applyScalar 与 derivative 都会抛 IllegalStateException,其反向必须走完整雅可比,由 Dense 内部处理
注意
applyRow 的 softmax 先减去行内最大值再取指数,归一化因子非有限或非正时抛 IllegalStateException
注意
sigmoid 按输入符号分支计算,负输入不会出现 exp 溢出
参见:basalt.nn.Dense, basalt.nn.Conv2D
枚举成员
- NONE()¶
恒等激活,输出等于输入,导数为 1。
用于输出层或调试,不引入非线性。
- RELU()¶
修正线性单元,负值截断为 0。
导数按激活前取值判断:大于 0 时为 1,否则为 0,因此 0 点导数为 0。
注意
该常量会让 Dense 与 Conv2D 改用 √(6/fanIn) 的初始化幅度
- SIGMOID()¶
逻辑函数 1/(1+e^-x),输出落在 (0,1)。
导数由激活值给出:a·(1-a),无需重新计算指数。
- TANH()¶
双曲正切,输出落在 (-1,1)。
导数由激活值给出:1 - a²。
- SOFTMAX()¶
按行归一化的 softmax,仅支持整行计算。
沿最后一维把一行映射为概率分布,行内元素之和为 1。标量接口不可用,Dense 的反向会用整行点积修正梯度。
注意
Conv2D.Builder 在 build 时拒绝该常量,卷积层只接受逐元素激活
- GELU()¶
tanh 近似的 Gaussian Error Linear Unit。
近似常数 0.044715 与 √(2/π) 直接写入前向与导数,两者一致,梯度检查可通过。
- SILU()¶
SiLU(Swish):x·σ(x)。
导数为 σ(1 + x(1 - σ)),只需一次 sigmoid 计算。
- final class CNN implements Persistable¶
BCHW 输入、BC 输出的卷积网络,由分阶段 Builder 构建。
CNN 持有一个不可变的有序 Layer 列表、一个 Loss 与一个 OptimizerConfig。forward 顺序执行各层,backward 逆序回传,参数更新交给各层自身的 update,因此网络只做调度,不持有梯度。
fit 是全批量训练:每轮执行一次 forward、一次 Loss.evaluate、一次 backward,然后对每层各调用一次 update,并把该轮损失记入 TrainingHistory。没有小批量切分、学习率调度或早停,轮数完全由调用方给定。
训练路径对 Softmax 末端有专门处理:当最后一层是 Activation.SOFTMAX 的 Dense 且损失结果携带 softmax-logit 梯度时,直接把 (p - t) / batch 交给该层,省去在 Softmax 输出上再乘一次雅可比。
示例CNN model = NeuralNetwork.builder() .add(Conv2D.builder().filters(4).kernelSize(3, 3) .activation(Activation.RELU).padding(Padding.SAME).build()) .add(MaxPooling2D.builder().poolSize(2, 2).build()) .add(Flatten.builder().build()) .add(Dense.builder().units(3).activation(Activation.SOFTMAX).build()) .loss(Loss.CATEGORICAL_CROSSENTROPY) .optimizer(Optimizer.ADAM.withLearningRate(0.01)) .build(); Tensor<BCHW> features = Tensor.ofBCHW(8, 1, 4, 4, new double[8 * 16]); double[] labels = new double[8 * 3]; for (int row = 0; row < 8; row++) { labels[row * 3] = 1.0; } Tensor<BC> targets = Tensor.ofBC(8, 3, labels); TrainingHistory history = model.fit(features, targets, 5); double finalLoss = history.lastLoss();注意
构造器与 layers()、loss()、optimizer() 为包内可见,外部只能经 NeuralNetwork 的 Builder 创建模型,读层数用 layerCount()
注意
fit 不校验 features 是否为 BCHW 布局,形状错误会在首次 forward 时由具体层抛出
注意
fit 要求 features 与 targets 的第 0 维一致;损失出现非有限值时抛 IllegalStateException 并中止训练
注意
构建期不校验 loss 与末端激活是否匹配(SequenceNetwork 会校验),用 CATEGORICAL_CROSSENTROPY 时需自行把末端 Dense 设为 SOFTMAX
参见:basalt.nn.NeuralNetwork, basalt.nn.Loss, basalt.nn.ModelSerializer, basalt.nn.TrainingHistory
方法
- predict(Tensor<BCHW> input) → Tensor<BC>¶
forward 的别名,用于推理。
与 forward 完全等价,不切换训练或评估状态,也不影响后续 backward。
- 参数
input (Tensor<BCHW>) – 形状 (batch, channels, height, width) 的输入张量
- 返回
形状 (batch, classes) 的预测
- 抛出
NullPointerException – input 为 null
- fit(Tensor<BCHW> features, Tensor<BC> targets, int epochs) → TrainingHistory¶
执行全批量训练,返回逐轮损失。
每轮按 forward、evaluate、非有限检查、backward、逐层 update 的顺序推进,该轮损失写入结果数组。整批样本一次参与更新,轮数等于返回值的长度。
注意
训练就地修改各层参数,重复调用 fit 会从当前参数继续训练
注意
每轮只有一次参数更新,等效于整批梯度下降
- 参数
features (Tensor<BCHW>) – 训练输入,形状 (batch, channels, height, width)
targets (Tensor<BC>) – 训练目标,形状 (batch, classes),第 0 维须与 features 相同
epochs (int) – 训练轮数,必须为正
- 返回
长度为 epochs 的逐轮损失快照
- 抛出
IllegalArgumentException – epochs 不大于 0,或 features 与 targets 的批大小不一致
IllegalStateException – 某一轮损失出现 NaN 或无穷
- evaluate(Tensor<BCHW> features, Tensor<BC> targets) → LossEvaluation¶
只计算损失与梯度,不更新参数。
先 forward 再调用 loss.evaluate;不执行 backward,因此不会写入任何层的梯度缓冲。
- 参数
features (Tensor<BCHW>) – 输入,形状 (batch, channels, height, width)
targets (Tensor<BC>) – 目标,形状须与预测完全一致
- 返回
标量损失与同形状输出梯度;分类交叉熵还带 softmax-logit 梯度
- 抛出
IllegalArgumentException – 预测与目标形状不一致,或取值超出所选损失的定义域
- layerCount() → int¶
返回层数,含 Flatten 过渡层。
- 返回
层列表长度
- kind() → ModelKind¶
返回模型种类,用于 .bsl 容器头部。
- 返回
常量 ModelKind.CNN
- write(ModelWriter writer)¶
把模型载荷写入 ModelWriter。
转发给 ModelSerializer.writeCNN,依次写出损失序号、优化器类型与四个超参、层数以及每层的配置与已初始化的权重。
注意
尚未见过输入的层只写结构,参数以 -1 标记,加载后需重新 forward 才会初始化权重
- 参数
writer (ModelWriter) – 目标写入器,容器头由 basalt.io.ModelCodec 负责
- final class Conv2D implements Layer<BCHW, BCHW>¶
BCHW 布局的二维卷积层,权重按 OIHW 排布。
输入按 (batch, channels, height, width) 组织,输出为 (batch, filters, outputHeight, outputWidth)。卷积核按 OIHW 顺序连续存放,长度 filters·channels·kH·kW,偏置长度 filters;前向与反向都是显式多重循环。
参数在首次 forward 时惰性创建并锁定通道数,不符时抛 IllegalArgumentException。权重由 Random(seed) 在 [-limit, limit] 上均匀取值,limit 在激活为 RELU 时取 √(6/fanIn),否则取 √(6/(fanIn + fanOut)),偏置置 0。
backward 返回对输入的梯度,同时把卷积核与偏置梯度累加到内部缓冲(每次反向先清零)。update 消费这些缓冲并推进优化器状态,之后梯度标记失效,须重新执行 forward 与 backward。
示例Conv2D conv = Conv2D.builder() .filters(8) .kernelSize(3, 3) .strides(1, 1) .padding(Padding.SAME) .activation(Activation.RELU) .seed(7L) .build(); Tensor<BCHW> input = Tensor.ofBCHW(2, 3, 8, 8, new double[2 * 3 * 8 * 8]); Tensor<BCHW> featureMap = conv.forward(input); Tensor<BCHW> gradient = Tensor.zerosBCHW(2, 8, 8, 8); conv.backward(gradient); conv.update(new OptimizerConfig(Optimizer.ADAM, 0.001, 0.9, 0.999, 1e-8));注意
输入必须是 BCHW,即单样本 NCHW 的批形式;层内不校验 rank,按位置读取第 1 至第 3 维
注意
SAME 的补零是隐式的:越界索引直接跳过,不物化填充缓冲区,补零不占额外内存
注意
反向依赖最近一次 forward 缓存的输入与预激活,连续两次 forward 会覆盖缓存
注意
fanIn 为 channels·kH·kW,fanOut 为 filters·kH·kW,两者都计入卷积核面积
注意
前向与反向都是逐输出位置的显式循环,不做 im2col 或频域变换,因此除输出与输入梯度外不再分配中间缓冲区
注意
filters、卷积核与步长在 build 时校验为正,activation 为 SOFTMAX 时拒绝构建
注意
参数更新不做 batch 缩放,卷积核梯度是批内求和结果
参见:basalt.nn.Layer, basalt.nn.Padding, basalt.nn.MaxPooling2D, basalt.nn.Flatten, basalt.nn.Activation
方法
- builder() → Builderstatic¶
创建 Conv2D 的分阶段构建器。
- 返回
新的 Builder,默认 1 个 3×3 卷积核、步长 1、VALID 补零、RELU 激活、seed 42
- forward(Tensor<BCHW> input) → Tensor<BCHW>¶
执行前向卷积,并缓存反向所需状态。
首次调用时按输入通道数创建卷积核与偏置;随后按 padding 计算输出尺寸与前侧、左侧补零量,逐输出位置累加通道与窗口内的乘积。
注意
输出尺寸公式见 Padding;SAME 下只取 total/2 作为前侧与左侧偏移
- 参数
input (Tensor<BCHW>) – 形状 (batch, channels, height, width) 的输入张量
- 返回
新分配的形状 (batch, filters, outputHeight, outputWidth) 张量
- 抛出
IllegalArgumentException – 通道数与首次 forward 锁定的值不一致,或 VALID 下卷积核大于输入空间尺寸
- backward(Tensor<BCHW> gradient) → Tensor<BCHW>¶
回传梯度,累加卷积核与偏置梯度。
对每个输出位置先用激活导数缩放上游梯度,再按窗口把梯度累加进卷积核缓冲与输入梯度。
注意
每次反向开始时清零卷积核与偏置梯度缓冲,结果不跨批累积
- 参数
gradient (Tensor<BCHW>) – 损失对输出的梯度,形状须为 (batch, filters, outputHeight, outputWidth)
- 返回
对输入的梯度,形状 (batch, channels, height, width)
- 抛出
IllegalStateException – 尚未执行 forward
IllegalArgumentException – 梯度形状与最近一次前向输出不一致
- update(OptimizerConfig optimizer)¶
用最近的梯度更新卷积核与偏置。
按 optimizer 指定的算法就地更新两个参数数组,并推进其内部迭代步。
注意
更新后梯度标记失效,连续两次调用会抛异常
- 参数
optimizer (OptimizerConfig) – 优化器超参数;SGD 只用学习率,ADAM 与 ADAMW 另用 beta1、beta2、epsilon
- 抛出
IllegalStateException – 参数尚未创建,或未完成一次 forward 加 backward
- final class Dense implements Layer<BC, BC>¶
全连接层,权重形状为 (units, inputFeatures)。
Dense 把 (batch, features) 映射为 (batch, units)。权重按 [unit][feature] 行主序连续存放,长度为 units·features;偏置长度 units。每个输出单元先做点积加偏置,再按整行应用激活函数。
参数在首次 forward 时按输入第 1 维创建并锁定特征数。权重在 [-limit, limit] 上均匀取值,limit 在激活为 RELU 时取 √(6/features),否则取 √(6/(features + units));偏置初始化为 0。
反向有两条路径:常规 backward 用激活导数逐元素求局部梯度;backwardFromSoftmaxLoss 把传入梯度视为损失对 logits 的梯度,跳过 softmax 雅可比。SOFTMAX 的常规反向用整行点积 g·a 修正,等价于乘完整雅可比。
示例Dense dense = Dense.builder() .units(10) .activation(Activation.RELU) .seed(1L) .build(); Tensor<BC> input = Tensor.ofBC(4, 6, new double[4 * 6]); Tensor<BC> hidden = dense.forward(input); dense.backward(Tensor.zerosBC(4, 10)); dense.update(Optimizer.SGD.withLearningRate(0.01));注意
特征数在首次 forward 后固定,之后不匹配抛 IllegalArgumentException
注意
权重与偏置梯度按批求和,不做 1/batch 缩放,学习率需自行折算
注意
权重初始化使用 java.util.Random(seed),同一种子与同一形状可复现
注意
层内不校验输入 rank,只读取第 0 维与第 1 维
注意
反向依赖最近一次 forward 缓存的输入、预激活与激活值
参见:basalt.nn.Layer, basalt.nn.Activation, basalt.nn.ParameterRef, basalt.nn.OptimizerConfig
方法
- builder() → Builderstatic¶
创建 Dense 的分阶段构建器。
- 返回
新的 Builder,默认 1 个单元、NONE 激活、seed 42
- forward(Tensor<BC> input) → Tensor<BC>¶
计算线性组合并应用激活。
按行做权重与输入的点积加偏置,得到预激活,再复制一份并就地应用激活;两份缓冲都保留供反向使用。
注意
首次调用创建权重、偏置与梯度缓冲,返回的张量是激活缓冲的副本
- 参数
input (Tensor<BC>) – 形状 (batch, features) 的输入张量;features 在首次调用时被锁定
- 返回
新分配的形状 (batch, units) 张量
- 抛出
IllegalArgumentException – 输入特征数与首次 forward 锁定的值不一致
- backward(Tensor<BC> gradient) → Tensor<BC>¶
回传梯度,累加权重与偏置梯度。
先按激活求局部梯度(SOFTMAX 用整行点积修正),再累加偏置梯度、权重梯度与输入梯度。
注意
每次反向开始时清零权重与偏置梯度缓冲
- 参数
gradient (Tensor<BC>) – 损失对输出的梯度,形状须为 (batch, units)
- 返回
对输入的梯度,形状 (batch, inputFeatures)
- 抛出
IllegalStateException – 尚未执行 forward
IllegalArgumentException – 梯度形状与最近一次前向输出不一致
- update(OptimizerConfig optimizer)¶
用最近的梯度更新权重与偏置。
按 optimizer 指定的算法就地更新两个参数数组,并推进其内部迭代步。
注意
更新后梯度标记失效,连续两次调用会抛异常
- 参数
optimizer (OptimizerConfig) – 优化器超参数;SGD 只用学习率,ADAM 与 ADAMW 另用 beta1、beta2、epsilon
- 抛出
IllegalStateException – 参数尚未创建,或未完成一次 forward 加 backward
- parameters() → List<ParameterRef>¶
暴露权重与偏置的实时参数视图。
返回两个 ParameterRef,名称分别为 dense.weights 与 dense.bias,各含参数数组与最近的梯度缓冲,供梯度检查、梯度裁剪与持久化使用。
注意
数组由层持有,不可重新分配;写入 values 即改变参数值
- 返回
两个参数视图;参数尚未创建时返回空列表
- units() → int¶
返回输出单元数。
注意
公开访问器,序列化与网络构建也会读取
- 返回
build 时给定的 units 值
- final class Flatten implements Layer<BCHW, BC>¶
把 BCHW 摊平成 BC,通道、高、宽依次排列。
Flatten 是无参数的布局转换层。前向把 (batch, channels, height, width) 重排为 (batch, channels·height·width),展平顺序为通道优先:先取通道 0 的全部 height×width 个元素,再取通道 1,依此类推。
BCHW 本身按行主序连续存储,展平不改变元素顺序,只重建形状元信息;实现仍经 Tensor.ofBC 复制一份数据,因此返回值与输入不共享缓冲区。反向按相反方向重建 BCHW 形状,同样复制。
前向记录输入的四维尺寸供反向使用,反向要求梯度第 1 维等于 channels·height·width,否则视为形状不匹配。
示例Flatten flatten = Flatten.builder().build(); Tensor<BCHW> featureMap = Tensor.ofBCHW(2, 4, 3, 3, new double[2 * 4 * 3 * 3]); Tensor<BC> flat = flatten.forward(featureMap); Tensor<BCHW> restored = flatten.backward(flat);注意
前向与反向各复制一次数据,代价与元素总数成正比
注意
反向依赖最近一次前向记录的形状,未前向时抛 IllegalStateException
注意
该层是 Layer<BCHW, BC>,在编译期把卷积链与全连接链衔接起来
参见:basalt.nn.Layer, basalt.nn.Dense, basalt.nn.Conv2D
方法
- builder() → Builderstatic¶
创建 Flatten 的构建器。
- 返回
新的 Builder,无任何可配置项
- forward(Tensor<BCHW> input) → Tensor<BC>¶
把四维张量摊平为二维。
记录输入的 batch、channels、height、width,再用同一份数据的副本构造 (batch, channels·height·width) 张量。
注意
返回值是输入数据的副本,修改它不会影响输入张量
- 参数
input (Tensor<BCHW>) – 形状 (batch, channels, height, width) 的输入张量
- 返回
新分配的形状 (batch, channels·height·width) 张量,元素顺序与原数据一致
- backward(Tensor<BC> gradient) → Tensor<BCHW>¶
把二维梯度还原为四维。
按前向记录的形状把梯度重塑回 BCHW,元素顺序不变。
- 参数
gradient (Tensor<BC>) – 损失对摊平输出的梯度,形状须为 (batch, channels·height·width)
- 返回
新分配的形状 (batch, channels, height, width) 梯度张量
- 抛出
IllegalStateException – 尚未执行 forward,未记录输入形状
IllegalArgumentException – 梯度的 batch 或特征数与记录不符
- final class GRU implements Layer<BSF, BSF>, Persistable¶
门控循环层:显式 BPTT 的 many-to-many GRU,输入输出均为 BSF。
门顺序固定为 update(z)、reset(r)、candidate(n)。z 与 r 经 sigmoid 得到,候选状态经 tanh 得到,最终状态按 h' = (1 - z) * n + z * h 组合,其中 z 越大越保留旧状态。
reset 门按前一时刻隐藏向量的分量逐元素作用于 U_n h,而非整段向量缩放。forward 返回整段序列的全部时间步输出,而不是只返回最后一步;h_0 为零向量,序列之间不携带状态。
示例GRU gru = GRU.builder().hiddenSize(8).seed(42).build(); Tensor<BSF> input = Tensor.zerosBSF(2, 5, 3); Tensor<BSF> output = gru.forward(input); Tensor<BSF> inputGradient = gru.backward(Tensor.zerosBSF(2, 5, 8)); gru.update(Optimizer.ADAM.withLearningRate(0.01));注意
参数规模为 3 * hiddenSize * (features + hiddenSize + 1),权重按门分块连续存储,门块内再按隐藏单元分行。
注意
bias 以全零初始化,因此 z 与 r 在训练开始时的输出均为 0.5。
注意
输入特征数在首次 forward 时确定,之后传入不同特征数会抛出异常。
注意
必须按 forward → backward → update 的顺序调用;update 消费梯度后需重新前向才能再次更新。
参见:basalt.nn.LSTM, basalt.nn.VanillaRNN, basalt.nn.SequenceNetwork
方法
- builder() → Builderstatic¶
创建构建器,hiddenSize 默认 16、seed 默认 42。
- 返回
新的 Builder 实例
- forward(Tensor<BSF> input) → Tensor<BSF>¶
沿时间轴前向传播,返回全序列隐藏状态。
注意
返回的张量是内部缓冲的副本,后续调用不会改写已返回的结果。
注意
内部逐时间步缓冲在批量与序列长度不变时复用,避免重复分配。
- 参数
input (Tensor<BSF>) – 形状 (batch, sequence, features) 的 BSF 张量;features 在首次调用时确定并据此分配参数
- 返回
形状 (batch, sequence, hiddenSize) 的张量,含每个时间步的隐藏状态,t=0 的状态由零初始状态算出
- 抛出
IllegalArgumentException – features 与首次 forward 时确定的值不一致
- backward(Tensor<BSF> gradient) → Tensor<BSF>¶
沿时间轴反向传播,累积参数梯度并返回输入梯度。
注意
梯度写入 inputWeights、recurrentWeights、bias 的缓冲,需再调用 update 才作用于参数。
注意
反向按时间步逆序进行,逐时刻交换隐藏梯度缓冲,不递归。
- 参数
gradient (Tensor<BSF>) – 上游梯度,形状必须为 (batch, sequence, hiddenSize),与最近一次 forward 的输出对齐
- 返回
对输入的梯度,形状 (batch, sequence, features)
- 抛出
IllegalStateException – 尚未调用 forward
IllegalArgumentException – gradient 的形状与最近一次 forward 不匹配
- update(OptimizerConfig optimizer)¶
用累积梯度对三组参数执行一次优化器更新。
注意
更新后清除梯度就绪标记,重复调用会抛出异常。
- 参数
optimizer (OptimizerConfig) – 优化器超参数,同时作用于输入权重、循环权重与偏置
- 抛出
IllegalStateException – 未完成 forward 与 backward,或梯度已被上一次 update 消费
- parameters() → List<ParameterRef>¶
返回三组参数的实时视图。
注意
视图直接引用层内数组,可用于梯度检查、裁剪与持久化,不应调整长度。
- 返回
参数视图列表,名称依次为 gru.inputWeights、gru.recurrentWeights、gru.bias;首次 forward 之前为空列表
- kind() → ModelKind¶
返回模型族标识。
- 返回
固定为 ModelKind.GRU
- write(ModelWriter writer)¶
把配置与权重写入模型流。
注意
未初始化时 inputFeatures 写为 -1,且不写权重数组。
- 参数
writer (ModelWriter) – 目标写入器;依次写入 hiddenSize、seed、inputFeatures,已初始化时再写三组权重
- read(ModelReader reader) → GRUstatic¶
从模型流恢复 GRU 实例。
- 参数
reader (ModelReader) – 源读取器,读取顺序须与 write 一致
- 返回
恢复后的 GRU;若流中记录了特征数,则同时重建并覆盖权重
- 抛出
ModelFormatException – hiddenSize 非正,或特征标记既非 -1 也非正数
- final class GradientCheck¶
用中心差分校验层与序列网络的解析梯度。
对目标 L = <upstream, f(x)>,把解析方向导数与 (L(x+eps) - L(x-eps)) / (2 eps) 比较。相对误差按 max(1, |analytic| + |numeric|) 归一化,因此判定与数值量级无关,量级很小的梯度不会被误判。
输入维度逐元素全量检查;参数维度按 samplesPerParameter 均匀抽样,步长为 max(1, 长度 / samplesPerParameter),以免大层耗时失控。检查过程直接改写 ParameterRef 暴露的实时数组,测完立即恢复原值,末尾再跑一次前向,把层留在前向完成状态。
示例Layer<BSF, BSF> layer = VanillaRNN.builder().hiddenSize(3).seed(7).build(); double[] values = new double[2 * 3 * 2]; for (int i = 0; i < values.length; i++) { values[i] = 0.1 * (i + 1); } double[] weights = new double[2 * 3 * 3]; java.util.Arrays.fill(weights, 1.0); Tensor<BSF> input = Tensor.ofBSF(2, 3, 2, values); Tensor<BSF> upstream = Tensor.ofBSF(2, 3, 3, weights); GradientCheck.Report report = GradientCheck.checkLayer(layer, input, upstream, 1e-6); boolean ok = report.passed(1e-6);注意
samplesPerParameter 默认 8,只在调用不带该参数的重载时生效
注意
epsilon 必须为正;实现按 (plus - minus) / (2 epsilon) 计算,epsilon 过小会放大浮点相消误差
注意
Report.passed 的容差由调用方给出,类内没有内置阈值;测试套件使用 epsilon = 1e-6 与 tolerance = 1e-6
注意
checkNetwork 只校验参数,返回的 Report 中 inputChecks 为 0、maxInputError 为 0.0
注意
扰动依赖层暴露的实时数组,因此要求 Layer.parameters() 返回的视图确实指向内部权重
参见:basalt.nn.ParameterRef, basalt.nn.Layer, basalt.nn.SequenceNetwork
方法
- checkLayer(Layer<BSF, BSF> layer, Tensor<BSF> input, Tensor<BSF> upstream, double epsilon) → Reportstatic¶
以默认抽样数 8 检查单个层的梯度。
注意
等价于 checkLayer(layer, input, upstream, epsilon, 8)
- 参数
layer (Layer<BSF, BSF>) – 待检查的层,输入输出均为 BSF 布局
input (Tensor<BSF>) – 前向输入,作为输入梯度的扰动基点
upstream (Tensor<BSF>) – 加权目标所用的上游梯度,尺寸须与层输出一致
epsilon (double) – 中心差分步长,必须为正
- 返回
输入与参数两个维度的最大相对误差及检查次数
- 抛出
IllegalArgumentException – epsilon 不大于 0,或 upstream 与层输出尺寸不符
NullPointerException – layer、input 或 upstream 为 null
- checkLayer(Layer<BSF, BSF> layer, Tensor<BSF> input, Tensor<BSF> upstream, double epsilon, int samplesPerParameter) → Reportstatic¶
按指定抽样数检查层的输入梯度与参数梯度。
先 forward 取输出并与 upstream 比尺寸,解析输入梯度来自 layer.backward(upstream)。随后逐个输入元素做加减扰动并重算加权和,得到方向导数的中心差分;参数部分从 ParameterRef.gradient() 取解析值,按步长抽样扰动实时数组。
注意
输入检查是逐元素全量扫描,成本与输入规模成正比,每个元素需要两次前向
注意
检查结束后恢复全部参数并重跑一次前向,层可继续用于训练
- 参数
layer (Layer<BSF, BSF>) – 待检查的层,输入输出均为 BSF 布局
input (Tensor<BSF>) – 前向输入,元素个数即输入检查次数
upstream (Tensor<BSF>) – 加权目标所用的上游梯度,尺寸须与层输出一致
epsilon (double) – 中心差分步长,必须为正
samplesPerParameter (int) – 每个参数张量最多抽样的点数;≤0 时按 1 处理,实际步长取 max(1, 长度 / 该值)
- 返回
inputChecks 等于输入元素总数,parameterChecks 等于实际扰动次数
- 抛出
IllegalArgumentException – epsilon 不大于 0,或 upstream 与层输出尺寸不符
NullPointerException – layer、input 或 upstream 为 null
- checkNetwork(SequenceNetwork network, Tensor<BSF> input, Tensor<BC> target, double epsilon) → Reportstatic¶
以默认抽样数 8 检查序列网络的全部参数。
注意
等价于 checkNetwork(network, input, target, epsilon, 8)
- 参数
network (SequenceNetwork) – 待检查的序列网络
input (Tensor<BSF>) – 前向输入,形状 (batch, sequence, features)
target (Tensor<BC>) – 损失计算所用的目标,形状 (batch, classes)
epsilon (double) – 中心差分步长,必须为正
- 返回
参数维度的最大相对误差与检查次数
- 抛出
IllegalArgumentException – epsilon 不大于 0
IllegalStateException – 当前参数下损失不是有限值
- checkNetwork(SequenceNetwork network, Tensor<BSF> input, Tensor<BC> target, double epsilon, int samplesPerParameter) → Reportstatic¶
按指定抽样数检查序列网络的参数梯度。
先 evaluate 得到损失并检查有限性,再 backward 填充各层梯度缓冲。之后对每个参数张量抽样扰动,用两次 evaluate 的损失差做中心差分,与解析梯度比较。
注意
每次扰动都重新执行完整前向,成本约为 2 × 抽样点数 × 单次前向
注意
网络支持全局范数裁剪,但本方法只做数值比对,不修改梯度以外的状态
- 参数
network (SequenceNetwork) – 待检查的序列网络
input (Tensor<BSF>) – 前向输入,形状 (batch, sequence, features)
target (Tensor<BC>) – 损失计算所用的目标,形状 (batch, classes)
epsilon (double) – 中心差分步长,必须为正
samplesPerParameter (int) – 每个参数张量最多抽样的点数;≤0 时按 1 处理
- 返回
Report,其中 inputChecks 恒为 0、maxInputError 恒为 0.0
- 抛出
IllegalArgumentException – epsilon 不大于 0
IllegalStateException – 评估得到的损失不是有限值
- final class LSTM implements Layer<BSF, BSF>, Persistable¶
门控循环层:显式 BPTT 的 many-to-many LSTM,输入输出均为 BSF。
门顺序固定为 input(i)、forget(f)、cell(g)、output(o)。i、f、o 经 sigmoid,g 经 tanh;单元状态按 c_t = f * c_{t-1} + i * g 更新,隐藏状态为 o * tanh(c_t)。
单元状态沿时间轴的雅可比就是 forget 门本身,梯度不必连乘被压缩的导数,这是长序列上梯度得以保留的结构原因。forward 返回整段序列,h_0 与 c_0 均为零。
示例LSTM lstm = LSTM.builder().hiddenSize(8).seed(42).build(); Tensor<BSF> input = Tensor.zerosBSF(2, 5, 3); Tensor<BSF> output = lstm.forward(input); Tensor<BSF> inputGradient = lstm.backward(Tensor.zerosBSF(2, 5, 8)); lstm.update(Optimizer.ADAM.withLearningRate(0.01));注意
参数规模为 4 * hiddenSize * (features + hiddenSize + 1),四门权重在同一矩阵内按 i、f、g、o 分块连续存放。
注意
bias 以全零初始化,因此 forget 门的初始输出为 0.5,而非饱和在 1 附近。
注意
输入特征数在首次 forward 时确定,之后传入不同特征数会抛出异常。
注意
每个时间步的门与状态缓冲在批量形状不变时复用,序列较长时内存占用为 batch * sequence * hiddenSize 的若干倍。
参见:basalt.nn.GRU, basalt.nn.SequenceNetwork, basalt.nn.SequencePool
方法
- builder() → Builderstatic¶
创建构建器,hiddenSize 默认 16、seed 默认 42。
- 返回
新的 Builder 实例
- forward(Tensor<BSF> input) → Tensor<BSF>¶
沿时间轴前向传播,返回全序列隐藏状态。
注意
批次内各样本独立计算,互不共享状态。
注意
返回的张量是内部缓冲的副本,后续调用不会改写已返回的结果。
- 参数
input (Tensor<BSF>) – 形状 (batch, sequence, features) 的 BSF 张量;features 在首次调用时确定并据此分配参数
- 返回
形状 (batch, sequence, hiddenSize) 的张量,逐时间步给出 o * tanh(c_t)
- 抛出
IllegalArgumentException – features 与首次 forward 时确定的值不一致
- backward(Tensor<BSF> gradient) → Tensor<BSF>¶
沿时间轴反向传播,累积参数梯度并返回输入梯度。
注意
反向按时间步逆序进行,同时传播隐藏梯度与单元梯度,隐藏与单元缓冲逐时刻交换。
注意
梯度写入三组参数缓冲,需再调用 update 才作用于参数。
- 参数
gradient (Tensor<BSF>) – 上游梯度,形状必须为 (batch, sequence, hiddenSize),与最近一次 forward 的输出对齐
- 返回
对输入的梯度,形状 (batch, sequence, features)
- 抛出
IllegalStateException – 尚未调用 forward
IllegalArgumentException – gradient 的形状与最近一次 forward 不匹配
- update(OptimizerConfig optimizer)¶
用累积梯度对三组参数执行一次优化器更新。
注意
更新后清除梯度就绪标记,重复调用会抛出异常。
- 参数
optimizer (OptimizerConfig) – 优化器超参数,同时作用于输入权重、循环权重与偏置
- 抛出
IllegalStateException – 未完成 forward 与 backward,或梯度已被上一次 update 消费
- parameters() → List<ParameterRef>¶
返回三组参数的实时视图。
注意
视图直接引用层内数组,可用于梯度检查、裁剪与持久化,不应调整长度。
- 返回
参数视图列表,名称依次为 lstm.inputWeights、lstm.recurrentWeights、lstm.bias;首次 forward 之前为空列表
- kind() → ModelKind¶
返回模型族标识。
- 返回
固定为 ModelKind.LSTM
- write(ModelWriter writer)¶
把配置与权重写入模型流。
注意
未初始化时 inputFeatures 写为 -1,且不写权重数组。
- 参数
writer (ModelWriter) – 目标写入器;依次写入 hiddenSize、seed、inputFeatures,已初始化时再写三组权重
- read(ModelReader reader) → LSTMstatic¶
从模型流恢复 LSTM 实例。
- 参数
reader (ModelReader) – 源读取器,读取顺序须与 write 一致
- 返回
恢复后的 LSTM;若流中记录了特征数,则同时重建并覆盖权重
- 抛出
ModelFormatException – hiddenSize 非正,或特征标记既非 -1 也非正数
- interface Layer<I extends Layout, O extends Layout>¶
带编译期输入输出布局参数的可微变换接口。
Layer<I, O> 用两个布局类型参数约束数据流:forward 接收 Tensor<I> 并返回 Tensor<O>,backward 接收 Tensor<O> 并返回 Tensor<I>。I 与 O 必须实现 sealed 接口 Layout,当前允许 BCHW、BHW、BC、BSF 四种。
布局链在编译期拼接:Conv2D 与 MaxPooling2D 是 Layer<BCHW, BCHW>,Flatten 是 Layer<BCHW, BC>,Dense 是 Layer<BC, BC>,因此把 BCHW 层接在 BC 层之后无法通过编译,不需要运行期检查。
接口只规定前向、反向与更新三件事;参数暴露由 parameters 提供默认空实现,无参数层(Flatten、MaxPooling2D)不必覆写 update 与 parameters。
示例Layer<BC, BC> identity = new Layer<>() { @Override public Tensor<BC> forward(Tensor<BC> input) { return input; } @Override public Tensor<BC> backward(Tensor<BC> gradient) { return gradient; } }; Tensor<BC> output = identity.forward(Tensor.zerosBC(2, 3)); int parameterCount = identity.parameters().size();注意
backward 不是自动微分:各层在 forward 中缓存中间量,调用方必须按相反顺序回传
注意
update 的默认实现为空,无参数层可安全调用
注意
parameters 返回的数组是层内实时存储,写入即改变参数值
注意
同一层的 forward 与 backward 通常不可重入,缓存状态会被下一次调用覆盖
参见:basalt.core.Layout, basalt.core.Tensor, basalt.nn.Dense, basalt.nn.Conv2D
- enum Loss¶
批量-通道布局上的损失函数,同时产出损失值与输出梯度。
枚举常量有 CATEGORICAL_CROSSENTROPY、BINARY_CROSSENTROPY 与 MEAN_SQUARED_ERROR。evaluate 要求 prediction 与 target 形状完全一致、元素全部有限,并且按元素总数(batch × classes)而非批大小取平均。
CATEGORICAL_CROSSENTROPY 把预测当作概率:元素须在 [0, 1],每行目标和与 1 的偏差不得超过 1e-6,并额外给出 (p - t) / batch 的 softmax-logit 梯度。BINARY_CROSSENTROPY 要求预测与目标都在 [0, 1],概率先夹到 [1e-12, 1 - 1e-12] 再取对数。MEAN_SQUARED_ERROR 只要求有限,不限制范围。
梯度统一按元素总数缩放,与 CNN.fit 的全批量语义一致:一次 update 即等价于整批的一次梯度步。
示例Tensor<BC> prediction = Tensor.ofBC(2, 3, 0.7, 0.2, 0.1, 0.1, 0.8, 0.1); Tensor<BC> target = Tensor.ofBC(2, 3, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0); LossEvaluation evaluation = Loss.CATEGORICAL_CROSSENTROPY.evaluate(prediction, target); double loss = evaluation.loss(); boolean hasLogitGradient = evaluation.hasSoftmaxLogitGradient();注意
概率下界 1e-12 用于避免 log(0);预测恰好为 0 时分类交叉熵的梯度会达到 -t / 1e-12 量级
注意
行和校验逐行进行,任一行不满足即整体失败,不会只跳过该行
注意
evaluate 不修改输入张量,返回的梯度是新分配的张量
参见:basalt.nn.LossEvaluation, basalt.nn.CNN
方法
- evaluate(Tensor<BC> prediction, Tensor<BC> target) → LossEvaluationabstract¶
计算损失值与同形状的输出梯度。
形状校验后按所选常量逐元素累加损失并写出梯度;分类交叉熵还会生成 softmax-logit 梯度,使 CNN 能在 Softmax 末端跳过雅可比乘法。
- 参数
prediction (Tensor<BC>) – 网络输出,形状 (batch, classes);分类与二元损失要求元素在 [0, 1]
target (Tensor<BC>) – 目标,形状须与 prediction 完全一致;分类交叉熵要求每行和为 1,二元损失要求元素在 [0, 1]
- 返回
标量损失与同形状梯度;分类交叉熵额外携带 softmax-logit 梯度
- 抛出
IllegalArgumentException – 形状不一致、元素非有限、取值越界,或分类目标的某一行和不为 1
NullPointerException – prediction 或 target 为 null
- record LossEvaluation¶
标量损失与输出梯度的不可变结果,可选携带 softmax-logit 梯度。
记录有三个组件:loss、gradient 与 softmaxLogitGradient。两参构造把第三项置为 null,hasSoftmaxLogitGradient 据此判断是否可用简化路径。
只有 CATEGORICAL_CROSSENTROPY 会填充 softmax-logit 梯度,其值已把 Softmax 的雅可比合并进 (p - t) / batch;CNN 与 SequenceNetwork 在末端为 SOFTMAX 的 Dense 上优先使用它,避免数值放大。
注意
gradient 不可为 null,紧凑构造器会校验;softmaxLogitGradient 允许为 null
注意
记录不复制张量,gradient 与 softmaxLogitGradient 的所有权属于创建方
注意
标准三参构造器同样可用,用于显式携带 softmax-logit 梯度
参见:basalt.nn.Loss, basalt.nn.CNN
构造器
- LossEvaluation(double loss, Tensor<BC> gradient)¶
构造只带输出梯度的评估结果。
注意
softmaxLogitGradient 置为 null,hasSoftmaxLogitGradient 返回 false
- 参数
loss (double) – 标量损失,通常已按元素总数平均
gradient (Tensor<BC>) – 与预测同形状的输出梯度,不可为 null
- 抛出
NullPointerException – gradient 为 null
方法
- hasSoftmaxLogitGradient() → boolean¶
是否携带 softmax-logit 梯度。
注意
为 true 只说明梯度存在,末端层是否 SOFTMAX 由网络自行判断
- 返回
softmaxLogitGradient 非 null 时为 true
- final class MaxPooling2D implements Layer<BCHW, BCHW>¶
二维最大池化,缓存 argmax 索引以精确回传梯度。
在 (batch, channels, height, width) 上做窗口取最大,输出空间尺寸为 (in - pool) / stride + 1,向下取整。层内没有补零,窗口必须完全落在输入内,pool 大于输入空间尺寸时抛 IllegalArgumentException。
步长默认等于窗口尺寸,因此默认行为是不重叠池化;Builder 允许单独指定步长以得到重叠窗口。窗口内出现 NaN 时立即抛 IllegalArgumentException,避免最大值比较被污染。
前向为每个输出位置记录取得最大值的输入线性下标,反向只把这些位置的梯度累加回输入,其余位置为零。并列最大值取扫描顺序中先遇到的那个,比较使用严格大于。
示例MaxPooling2D pool = MaxPooling2D.builder() .poolSize(2, 2) .strides(2, 2) .build(); Tensor<BCHW> input = Tensor.ofBCHW(1, 1, 4, 4, new double[16]); Tensor<BCHW> pooled = pool.forward(input); Tensor<BCHW> gradient = Tensor.zerosBCHW(1, 1, 2, 2); Tensor<BCHW> inputGradient = pool.backward(gradient);注意
无参数层,update 使用接口默认的空实现
注意
argmax 缓存以最近一次前向为准,重复前向会覆盖
注意
反向要求梯度形状与最近一次前向输出完全一致
注意
窗口不补零,右侧与下侧不足一个窗口的像素会被丢弃
参见:basalt.nn.Layer, basalt.nn.Conv2D, basalt.nn.Flatten
方法
- builder() → Builderstatic¶
创建 MaxPooling2D 的构建器。
- 返回
新的 Builder,默认 2×2 窗口且步长等于窗口
- forward(Tensor<BCHW> input) → Tensor<BCHW>¶
执行最大池化并缓存 argmax 下标。
逐窗口扫描取最大值,同时记录该值在输入缓冲区中的线性下标,供反向路由使用。
注意
输出尺寸为 (in - pool) / stride + 1,除法向下取整
- 参数
input (Tensor<BCHW>) – 形状 (batch, channels, height, width) 的输入张量
- 返回
新分配的形状 (batch, channels, outputHeight, outputWidth) 张量
- 抛出
IllegalArgumentException – 窗口大于输入空间尺寸,或窗口内含 NaN
- backward(Tensor<BCHW> gradient) → Tensor<BCHW>¶
按缓存的 argmax 把梯度回传到输入。
对每个输出位置把上游梯度累加到前向记录的最大值位置,其余输入位置保持为零。
注意
同一输入位置可能被多个窗口命中(重叠池化时),梯度会累加
- 参数
gradient (Tensor<BCHW>) – 损失对池化输出的梯度,形状须与最近一次前向输出一致
- 返回
新分配的形状 (batch, channels, inputHeight, inputWidth) 梯度张量
- 抛出
IllegalStateException – 尚未执行 forward,或 argmax 缓存不完整
IllegalArgumentException – 梯度形状或元素总数与前向输出不一致
- final class ModelSerializer¶
CNN 的 .bsl 持久化门面,负责层载荷的读写。
容器格式由 basalt.io.ModelCodec 负责,包含 magic、版本号与 CRC32C 校验和;本类只处理 CNN 载荷:损失序号、优化器类型与四个超参、层数,然后逐层写类型标记、配置,以及已初始化层的参数值。
readCNN 重建各层后校验拓扑:必须恰好出现一个 Flatten,Flatten 之前只允许 Conv2D 与 MaxPooling2D,之后只允许 Dense,层数须落在 1 到 100000 之间。层类型标记为 1 到 4,分别对应卷积、最大池化、展平与全连接。
示例CNN model = NeuralNetwork.builder() .add(Conv2D.builder().filters(4).kernelSize(3, 3).padding(Padding.SAME).build()) .add(MaxPooling2D.builder().poolSize(2, 2).build()) .add(Flatten.builder().build()) .add(Dense.builder().units(3).activation(Activation.SOFTMAX).build()) .build(); model.predict(Tensor.ofBCHW(2, 1, 4, 4, new double[2 * 16])); Path path = Path.of("model.bsl"); ModelSerializer.save(path, model); CNN restored = ModelSerializer.loadCNN(path);注意
save 与 loadCNN 走 ModelCodec,加载时内存映射文件并在不复制载荷的情况下校验 CRC32C
注意
UncheckedIOException 表示文件系统失败,ModelFormatException 表示容器或载荷格式错误
注意
参数只在层已见过输入(inputChannels 或 inputFeatures 大于 0)时写出,未初始化的层以 -1 标记,加载后需重新前向才会生成权重
注意
损失与优化器以枚举序号写入,调整枚举常量顺序会使旧文件解析错误
参见:basalt.nn.CNN, basalt.io.ModelCodec
方法
- save(Path target, CNN model)static¶
把 CNN 写入 .bsl 文件。
先让模型把载荷写进内存缓冲,再由 ModelCodec 补齐 24 字节头部并一次性写入文件;目标文件已存在时被截断覆盖。
注意
未初始化的层只写结构,加载后需重新前向才会生成权重
- 参数
target (Path) – 目标路径,父目录须已存在
model (CNN) – 待保存的网络,不可为 null
- 抛出
UncheckedIOException – 文件打开或写入失败
ModelFormatException – 载荷长度超出 .bsl 容器上限
NullPointerException – model 为 null
- loadCNN(Path source) → CNNstatic¶
从 .bsl 文件读出 CNN。
注意
读取后要求载荷被完整消费,多余字节视为格式错误
- 参数
source (Path) – 源文件路径
- 返回
重建的 CNN,参数值已恢复
- 抛出
ModelFormatException – magic 或版本不符、CRC32C 校验失败、载荷长度不符、文件内模型不是 CNN,或层与拓扑非法
UncheckedIOException – 文件打开或映射失败
- writeCNN(ModelWriter output, CNN model)static¶
把 CNN 载荷写入已有 ModelWriter。
写出损失序号、优化器序号、learningRate、beta1、beta2、epsilon、层数,再逐层写出类型标记与配置。
注意
遇到 Conv2D、MaxPooling2D、Flatten、Dense 之外的层会抛 IllegalStateException
- 参数
output (ModelWriter) – 目标写入器,调用方负责容器头
model (CNN) – 待写入的网络
- readCNN(ModelReader input) → CNNstatic¶
从 ModelReader 读取 CNN 载荷并校验拓扑。
依次读取损失与优化器序号并做范围检查,重建优化器配置与各层,最后校验 Flatten 的位置约束。
注意
拓扑校验与 Builder 的编译期约束一致,因此手写或损坏的文件无法绕过布局检查
- 参数
input (ModelReader) – 载荷读取器,位置须在 CNN 载荷起点
- 返回
重建的 CNN
- 抛出
ModelFormatException – 枚举序号越界、层数不在 1 到 100000 之间、未知层类型、缺少 Flatten、Flatten 重复,或 Flatten 前后出现非法层
- final class NeuralNetwork¶
分阶段构建 CNN:图像层在 Flatten 之前,向量层在其后。
两个阶段各是一个 final 嵌套类。ImageBuilder 只接受 Conv2D 与 MaxPooling2D,唯一出口是 add(Flatten),它返回 VectorBuilder;VectorBuilder 只接受 Dense,并提供 loss、optimizer 与 build。
Dense 没有进入 ImageBuilder 的重载,ImageBuilder 也没有 build 方法,因此“图像层后直接接全连接层”或“忘记 Flatten”都无法通过编译,而不是等到训练时才因维度不符失败。
示例CNN model = NeuralNetwork.builder() .add(Conv2D.builder().filters(8).kernelSize(3, 3).activation(Activation.RELU).build()) .add(MaxPooling2D.builder().poolSize(2, 2).build()) .add(Flatten.builder().build()) .add(Dense.builder().units(10).activation(Activation.SOFTMAX).build()) .loss(Loss.CATEGORICAL_CROSSENTROPY) .optimizer(Optimizer.SGD.withLearningRate(0.05)) .build(); int layers = model.layerCount();注意
build 不做额外校验,直接包装层列表、损失与优化器
注意
默认损失为 CATEGORICAL_CROSSENTROPY,默认优化器为 ADAM 且学习率 0.001
注意
阶段之间没有回退路径:进入 VectorBuilder 后不能再添加卷积或池化层
参见:basalt.nn.CNN, basalt.nn.Conv2D, basalt.nn.Dense, basalt.nn.Flatten
方法
- builder() → ImageBuilderstatic¶
创建图像阶段的构建器。
- 返回
层列表为空的 ImageBuilder
- enum Optimizer¶
一阶优化算法枚举:SGD、ADAM 与 ADAMW。
withLearningRate 是唯一的便捷入口:固定 beta1 = 0.9、beta2 = 0.999、epsilon = 1e-8,只替换学习率,返回新的 OptimizerConfig。
SGD 直接执行 values -= learningRate × gradient,且不递增步计数;ADAM 与 ADAMW 走同一条偏差修正路径,即维护一阶与二阶矩的指数滑动平均,再除以 1 - beta^step。当前内核没有实现解耦权重衰减,两个常量在 Parameter.update 中的分支相同。
示例OptimizerConfig adam = Optimizer.ADAM.withLearningRate(0.001); OptimizerConfig sgd = Optimizer.SGD.withLearningRate(0.05); OptimizerConfig custom = new OptimizerConfig(Optimizer.ADAMW, 0.002, 0.85, 0.995, 1e-7);注意
枚举序号会写进 .bsl 载荷,调整常量顺序会破坏旧文件的兼容性
注意
步计数只在非 SGD 更新时递增,混用 SGD 与 Adam 时偏差修正会沿用此前计数
注意
beta 与 epsilon 对 SGD 分支没有作用,但仍须通过 OptimizerConfig 的校验
参见:basalt.nn.OptimizerConfig, basalt.nn.Parameter
枚举成员
- SGD()¶
随机梯度下降,直接按学习率缩放梯度。
注意
不维护动量,也不递增 Parameter 的步计数
- ADAM()¶
Adam,一阶与二阶矩估计加偏差修正。
注意
每个参数各自保存 firstMoment 与 secondMoment,与参数数组等长
- ADAMW()¶
AdamW 常量,当前内核与 ADAM 共用更新路径。
该常量可正常构造配置并参与序列化,但 Parameter.update 只区分 SGD 与“非 SGD”,因此不含解耦权重衰减项,数值结果与 ADAM 相同。
注意
需要权重衰减时须在更新前自行把衰减项并入梯度
方法
- withLearningRate(double learningRate) → OptimizerConfig¶
以指定学习率创建其余取默认值的配置。
注意
每次调用都新建记录,不复用或缓存
- 参数
learningRate (double) – 学习率,须为正且有限;非正值在构造 OptimizerConfig 时被拒绝
- 返回
beta1 = 0.9、beta2 = 0.999、epsilon = 1e-8 的新配置
- 抛出
IllegalArgumentException – learningRate 不是正有限值
- record OptimizerConfig¶
训练共用的不可变优化器超参。
记录组件依次为 optimizer、learningRate、beta1、beta2、epsilon。紧凑构造器强制 optimizer 非 null、learningRate 为正有限值、两个 beta 落在 [0, 1)、epsilon 为正有限值。
CNN 与 SequenceNetwork 各持有一个实例,并在每次更新时把它传给全部可训练层的 update;beta 与 epsilon 对 SGD 无意义,但仍需满足同一套校验。
注意
记录本身没有默认值,默认组合来自 Optimizer.withLearningRate:beta1 0.9、beta2 0.999、epsilon 1e-8
注意
beta 取 1.0 会被拒绝,因为 1 - beta^step 会退化为 0
注意
全部校验集中在构造器,训练期间不重复检查,因此超参无法在训练中变更
注意
序列化时按 optimizer 序号与四个 double 写入,加载时同样经过构造器校验
- enum Padding¶
卷积补零模式:VALID 不补,SAME 补到按步长整除。
VALID 不做补零,输出尺寸为 (in - k) / s + 1,要求 in ≥ k,否则 Conv2D 抛 IllegalArgumentException。
SAME 让输出尺寸只由步长决定:out = ceil(in / s),即 (in + s - 1) / s。所需补零总量为 max(0, (out - 1)·s + k - in),前侧与左侧各取 total / 2(向下取整),余下的一个像素落在后侧与下侧,因此 total 为奇数时上下、左右不对称。
补零是隐式的:Conv2D 不为填充分配缓冲区,只在采样时跳过越界索引。卷积核不大于步长时 total 常为 0,但 SAME 与 VALID 的输出尺寸仍可能不同,例如 in = 7、k = 2、s = 2 时分别为 4 与 3。
示例Conv2D same = Conv2D.builder() .filters(4) .kernelSize(3, 3) .padding(Padding.SAME) .build(); Conv2D valid = Conv2D.builder() .filters(4) .kernelSize(3, 3) .padding(Padding.VALID) .build(); Tensor<BCHW> input = Tensor.zerosBCHW(1, 2, 7, 7); int sameWidth = same.forward(input).dimension(3); int validWidth = valid.forward(input).dimension(3);注意
由 Conv2D.Builder.padding 选择,默认 VALID
注意
SAME 下输出空间尺寸与卷积核大小无关,只取决于输入与步长
注意
补零值恒为 0,不参与权重更新
注意
该枚举没有方法,仅作为模式标记
参见:basalt.nn.Conv2D, basalt.nn.Conv2D.Builder
枚举成员
- VALID()¶
不补零,输出 (in − k) / stride + 1
- SAME()¶
补零使输出为 ceil(in / stride)
- record ParameterRef¶
单个可训练张量的实时视图:参数值与最近一次回传的梯度。
记录组件为 name、values、gradient,三者都不复制,数组由层持有,本记录只是别名。层通过 Layer.parameters() 暴露这些视图:GradientCheck 借它做中心差分扰动,SequenceNetwork.update 借它做全局范数裁剪,序列化时也经它读写权重。
紧凑构造器要求 name 非空白、values 与 gradient 等长。长度在创建后不可变,使用方只能改写元素,不能扩容。
示例Layer<BSF, BSF> layer = VanillaRNN.builder().hiddenSize(4).build(); List<ParameterRef> parameters = layer.parameters(); for (ParameterRef parameter : parameters) { double[] values = parameter.values(); double[] gradient = parameter.gradient(); double scale = Math.sqrt(parameter.size()); for (int i = 0; i < values.length; i++) { values[i] -= 0.01 * gradient[i] / scale; } }注意
梯度缓冲在每次 backward 中被层清零后重写,读到的始终是最近一次回传的结果
注意
改动 values 会立即影响模型,GradientCheck 依赖这一点并在检查后恢复原值
注意
empty(name) 用于尚未初始化的层,size() 返回 0
注意
无参数层应返回 none() 而不是 null,Layer.parameters() 的默认实现即如此
参见:basalt.nn.Layer, basalt.nn.GradientCheck, basalt.nn.SequenceNetwork, basalt.nn.Parameter
方法
- size() → int¶
返回参数元素个数。
- 返回
values 的长度,与 gradient 长度相同
- empty(String name) → ParameterRefstatic¶
创建零长度的占位视图。
注意
适合表示尚未初始化的参数,size() 返回 0
- 参数
name (String) – 参数名,不可为 null 或空白
- 返回
values 与 gradient 均为空数组的实例
- 抛出
IllegalArgumentException – name 为 null 或空白
- none() → List<ParameterRef>static¶
返回不可变的空列表。
注意
每次调用返回同一个共享的空列表实例
- 返回
List.of() 产生的空列表,供无参数层直接返回
- final class SelfAttention implements Layer<BSF, BSF>, Persistable¶
单头缩放点积自注意力,支持因果掩码与完整 Softmax 反向传播。
对每个时间步做三次线性投影得到 Q、K、V,按 A = softmax(Q K^T / sqrt(d)) 归一化后加权求和 V,输出与输入同为 (batch, sequence, dimension),每个时间步输出仍对应同一位置。
causal 为 true 时第 t 行只在键 0..t 上归一化,t 之后的注意力权重被置零,既不参与输出也不接收梯度;因此位置 t 的输出与未来时间步的输入无关。
示例SelfAttention attention = SelfAttention.builder() .dimension(4) .causal(true) .seed(7) .build(); Tensor<BSF> input = Tensor.zerosBSF(2, 6, 4); Tensor<BSF> output = attention.forward(input); Tensor<BSF> inputGradient = attention.backward(Tensor.zerosBSF(2, 6, 4)); attention.update(Optimizer.ADAM.withLearningRate(0.001));注意
输入特征数必须严格等于 dimension,不做隐式投影;需要改变维度时应在之前串联 LSTM、GRU 或 Dense。
注意
注意力缓冲为 batch * sequence * sequence 个 double,序列长度增加时内存按平方增长。
注意
缩放因子 1 / sqrt(dimension) 在构造时算好,不随输入变化。
注意
三组权重按 query、key、value 顺序用同一随机源连续采样,均匀分布于 ±sqrt(3 / dimension);无偏置项。
参见:basalt.nn.LSTM, basalt.nn.SequenceNetwork, basalt.nn.SequencePool
方法
- builder() → Builderstatic¶
创建构建器,dimension 默认 16、seed 默认 42、非因果。
- 返回
新的 Builder 实例
- forward(Tensor<BSF> input) → Tensor<BSF>¶
计算自注意力输出,形状与输入一致。
注意
首次调用时初始化三组 dimension × dimension 权重矩阵。
注意
Softmax 按行减去该行最大值后再归一化,避免指数溢出。
- 参数
input (Tensor<BSF>) – 形状 (batch, sequence, dimension) 的 BSF 张量,特征轴必须等于 dimension
- 返回
形状 (batch, sequence, dimension) 的注意力输出
- 抛出
IllegalArgumentException – 输入特征数不等于 dimension
- backward(Tensor<BSF> gradient) → Tensor<BSF>¶
反传注意力,累积 Q/K/V 权重梯度并返回输入梯度。
注意
Softmax 使用精确雅可比 A * (dA - rowsum(dA * A)),再乘缩放因子,而不是近似导数。
注意
causal 模式下被掩码的键不参与求和,其梯度恒为零。
- 参数
gradient (Tensor<BSF>) – 上游梯度,形状必须为 (batch, sequence, dimension)
- 返回
对输入的梯度,形状 (batch, sequence, dimension)
- 抛出
IllegalStateException – 尚未调用 forward
IllegalArgumentException – gradient 的形状与最近一次 forward 不匹配
- update(OptimizerConfig optimizer)¶
用累积梯度更新 Q/K/V 三组权重。
- 参数
optimizer (OptimizerConfig) – 优化器超参数,分别作用于 query、key、value 权重
- 抛出
IllegalStateException – 未完成 forward 与 backward,或梯度已被上一次 update 消费
- parameters() → List<ParameterRef>¶
返回 Q/K/V 权重的实时视图。
- 返回
参数视图列表,名称依次为 attention.query、attention.key、attention.value;首次 forward 之前为空列表
- dimension() → int¶
返回每个位置的投影维度。
- 返回
构建时指定的 dimension,同时是输入输出的特征数
- causal() → boolean¶
返回是否启用因果掩码。
- 返回
启用时为 true,此时位置 t 只关注键 0..t
- kind() → ModelKind¶
返回模型族标识。
- 返回
固定为 ModelKind.SELF_ATTENTION
- write(ModelWriter writer)¶
把配置与权重写入模型流。
- 参数
writer (ModelWriter) – 目标写入器;依次写入 dimension、seed、因果标志、是否已初始化,已初始化时再写三组权重
- read(ModelReader reader) → SelfAttentionstatic¶
从模型流恢复自注意力层。
- 参数
reader (ModelReader) – 源读取器,读取顺序须与 write 一致
- 返回
恢复后的实例,未初始化时保留惰性初始化状态
- 抛出
ModelFormatException – dimension 非正,或因果标志、初始化标志不是 0 或 1
- final class SequenceNetwork implements Persistable¶
端到端序列分类器:BSF → 若干序列层 → 池化 → Dense 头 → BC。
前向按 add 顺序串联主体层,再经池化把时间轴压缩为一个向量,最后过 Dense 头得到 (batch, classes) 输出。训练时一次完整前向、计算损失梯度、逆序反传头部与各主体层,再做全局范数裁剪与逐参数更新。
fit 在批量轴上连续切片做小批量训练,时间轴与特征轴保持不动;每轮记录按样本数加权的平均损失。主体层只接受 Layer<BSF, BSF>,因此 Dense 不能放入主体,只能作为头部。
示例SequenceNetwork network = SequenceNetwork.builder() .add(LSTM.builder().hiddenSize(8).seed(1).build()) .add(SelfAttention.builder().dimension(8).causal(true).seed(2).build()) .pooling(SequencePool.Pooling.LAST) .head(Dense.builder().units(2).activation(Activation.SOFTMAX).seed(3).build()) .loss(Loss.CATEGORICAL_CROSSENTROPY) .optimizer(Optimizer.ADAM.withLearningRate(0.01)) .gradientClip(5.0) .build(); Tensor<BSF> features = Tensor.zerosBSF(16, 10, 4); Tensor<BC> scores = network.predict(features);注意
head 必填;CATEGORICAL_CROSSENTROPY 要求 SOFTMAX 头且 units 不少于 2,BINARY_CROSSENTROPY 要求 SIGMOID 头。
注意
默认配置为 LAST 池化、分类交叉熵、Adam(学习率 0.001)、梯度裁剪阈值 5.0。
注意
update 返回裁剪前的全局梯度范数,可用于监控训练稳定性。
注意
backward 只接受最近一次 evaluate 返回的同一个 LossEvaluation 实例,其余会抛出异常。
注意
fit 不打乱样本顺序,批次按批量轴顺序连续切分。
参见:basalt.nn.SequencePool, basalt.nn.LSTM, basalt.nn.Dense, basalt.nn.Loss
方法
- builder() → Builderstatic¶
创建构建器,使用 LAST 池化与分类交叉熵等默认值。
- 返回
新的 Builder 实例
- predict(Tensor<BSF> features) → Tensor<BC>¶
对整批序列推理,输出各类别得分。
注意
会写入各层的前向缓存,但不改动参数。
- 参数
features (Tensor<BSF>) – 形状 (batch, sequence, features) 的 BSF 张量
- 返回
形状 (batch, classes) 的输出,等价于 forward
- forward(Tensor<BSF> features) → Tensor<BC>¶
执行完整前向:主体层、池化与头部。
注意
各主体层与池化层缓存本次前向的中间量,供紧随其后的 backward 使用。
- 参数
features (Tensor<BSF>) – 形状 (batch, sequence, features) 的 BSF 张量,不能为 null
- 返回
形状 (batch, classes) 的头部输出
- evaluate(Tensor<BSF> features, Tensor<BC> targets) → LossEvaluation¶
前向并计算损失,缓存结果供 backward 使用。
注意
每次调用覆盖上一次缓存的评估结果,旧实例随后不能再传给 backward。
- 参数
features (Tensor<BSF>) – 形状 (batch, sequence, features) 的 BSF 张量
targets (Tensor<BC>) – 形状 (batch, classes) 的目标;分类交叉熵要求每行之和为 1
- 返回
损失标量与输出梯度;SOFTMAX 头配分类交叉熵时另带简化的 logit 梯度
- backward(LossEvaluation evaluation) → Tensor<BSF>¶
反传缓存的损失梯度,返回对输入的梯度。
注意
头部为 SOFTMAX 且评估携带 logit 梯度时,走 p - t 形式的简化路径,数值更稳定。
注意
反传顺序为头部、池化层,再由主体层逆序逐层回传。
- 参数
evaluation (LossEvaluation) – 最近一次 evaluate 返回的实例,按引用比较
- 返回
形状与 forward 输入相同的 BSF 梯度
- 抛出
IllegalArgumentException – evaluation 不是最近一次 evaluate 的实例
- update() → double¶
全局范数裁剪后对全部参数执行一次更新。
注意
范数超过 gradientClip 时,所有参数梯度按 clip / norm 等比缩放,方向不变。
注意
逐主体层调用 update,最后更新头部;随后各层清除梯度就绪标记。
- 返回
裁剪前的全局梯度范数
- fit(Tensor<BSF> features, Tensor<BC> targets, int epochs) → TrainingHistory¶
全批量训练指定轮数。
注意
等价于 batchSize 取 batch 的重载,即每轮只做一次参数更新。
- 参数
features (Tensor<BSF>) – 形状 (batch, sequence, features) 的训练序列
targets (Tensor<BC>) – 形状 (batch, classes) 的目标
epochs (int) – 训练轮数,须为正
- 返回
长度为 epochs 的历史,每项为该轮的加权平均损失
- 抛出
IllegalArgumentException – epochs 非正,或特征与目标的批量不一致
IllegalStateException – 某一轮损失出现非有限值
- fit(Tensor<BSF> features, Tensor<BC> targets, int epochs, int batchSize) → TrainingHistory¶
按小批量训练指定轮数。
注意
每个批次执行一次 evaluate、backward 与 update,因此每轮的更新次数为 ceil(batch / batchSize)。
- 参数
features (Tensor<BSF>) – 形状 (batch, sequence, features) 的训练序列
targets (Tensor<BC>) – 形状 (batch, classes) 的目标
epochs (int) – 训练轮数,须为正
batchSize (int) – 每批样本数,须为正;沿批量轴顺序连续切片,最后一批可能不足
- 返回
长度为 epochs 的历史,每项为按样本数加权的平均损失
- 抛出
IllegalArgumentException – epochs 或 batchSize 非正,或特征与目标的批量不一致
IllegalStateException – 某一轮损失出现非有限值
- parameters() → List<ParameterRef>¶
汇总主体层与头部的全部参数视图。
- 返回
主体层参数在前、头部参数在后;尚未初始化的层不贡献条目
- loss() → Loss¶
返回配置的损失函数。
- 返回
构建时指定的 Loss 枚举值
- optimizer() → OptimizerConfig¶
返回配置的优化器超参数。
- 返回
构建时指定的 OptimizerConfig
- pooling() → SequencePool.Pooling¶
返回时间轴池化策略。
- 返回
构建时指定的 SequencePool.Pooling
- bodySize() → int¶
返回主体层数量。
- 返回
add 调用所加入的层数
- kind() → ModelKind¶
返回模型族标识。
- 返回
固定为 ModelKind.SEQUENCE_NETWORK
- write(ModelWriter writer)¶
把整网结构与参数写入模型流。
注意
层类型以字节标记区分:1 为 LSTM、2 为 GRU、3 为 VanillaRNN、4 为 SelfAttention。
- 参数
writer (ModelWriter) – 目标写入器;依次写入主体层数与各层(带类型标记)、池化策略、头部、损失、优化器与裁剪阈值
- 抛出
IllegalStateException – 主体包含 LSTM、GRU、VanillaRNN、SelfAttention 之外的层类型
- read(ModelReader reader) → SequenceNetworkstatic¶
从模型流重建整网。
- 参数
reader (ModelReader) – 源读取器,读取顺序须与 write 一致
- 返回
重建后的网络,主体层按流中顺序恢复
- 抛出
ModelFormatException – 主体层数超出 0..1000、层类型未知,或池化、损失、优化器序号越界
- final class SequencePool implements Layer<BSF, BC>¶
沿时间轴把 BSF 序列压缩为每个样本一个 BC 向量。
Pooling.LAST 取最后一个时间步,是分类任务常用的 many-to-one 头,代价是只使用末步信息;Pooling.MEAN 对全部时间步求平均,每个时间步都参与结果。
反向传播与正向严格对应:LAST 把梯度原样写回最后一个时间步,其余时间步为零;MEAN 把梯度除以序列长度后平均分配到每个时间步。
示例SequencePool pool = SequencePool.of(SequencePool.Pooling.MEAN); Tensor<BSF> sequence = Tensor.zerosBSF(4, 6, 3); Tensor<BC> pooled = pool.forward(sequence); Tensor<BSF> gradient = pool.backward(Tensor.zerosBC(4, 3));注意
只提供 LAST 与 MEAN 两种策略,没有 MAX 归约。
注意
MEAN 对全部时间步取平均,不区分填充位置;变长序列需自行保证有效长度一致。
注意
backward 依赖最近一次 forward 记录的形状,未前向即调用会因形状不匹配而失败。
注意
本层无可训练参数,parameters 继承默认实现返回空列表。
参见:basalt.nn.SequenceNetwork, basalt.nn.LSTM
方法
- of(Pooling pooling) → SequencePoolstatic¶
按指定策略创建池化层。
- 参数
pooling (Pooling) – 时间轴归约策略,不能为 null
- 返回
新的 SequencePool 实例
- 抛出
IllegalArgumentException – pooling 为 null
- forward(Tensor<BSF> input) → Tensor<BC>¶
按策略把序列归约为单个向量。
注意
记录 batch、sequence、features 三个维度,供 backward 校验与还原。
- 参数
input (Tensor<BSF>) – 形状 (batch, sequence, features) 的 BSF 张量,sequence 须至少为 1
- 返回
形状 (batch, features) 的 BC 张量;LAST 取末步,MEAN 取时间均值
- backward(Tensor<BC> gradient) → Tensor<BSF>¶
把池化梯度还原到时间轴。
注意
LAST 只写末步,MEAN 对每个时间步写入 gradient / sequence。
- 参数
gradient (Tensor<BC>) – 形状 (batch, features) 的上游梯度
- 返回
形状 (batch, sequence, features) 的梯度,未参与归约的时间步为零
- 抛出
IllegalArgumentException – gradient 形状与最近一次 forward 不匹配,包括尚未调用 forward 的情况
- pooling() → Pooling¶
返回池化策略。
- 返回
创建时指定的 Pooling 枚举值
- record TrainingHistory¶
逐轮训练损失的不可变快照。
记录组件是一个 double[],紧凑构造器与访问器都做 clone,因此构造后外部持有的数组无法影响快照,取出后修改也不影响内部状态。epochs() 等于数组长度,lastLoss() 取最后一个元素。
注意
每次调用 losses() 都复制数组,循环中反复取值会产生额外分配
注意
长度为 0 时 lastLoss 抛 IllegalStateException
注意
由 CNN.fit 与 SequenceNetwork.fit 返回,长度分别等于 epochs 与轮数
方法
- losses() → double[]¶
返回逐轮损失的副本。
- 返回
按轮次排列的损失副本,修改不影响记录本身
- epochs() → int¶
返回记录的轮数。
- 返回
losses 数组长度
- lastLoss() → double¶
返回最后一轮的损失。
- 返回
数组最后一个元素
- 抛出
IllegalStateException – 没有任何轮次记录
- final class VanillaRNN implements Layer<BSF, BSF>, Persistable¶
最简 Elman 循环层:h_t = tanh(W x_t + U h_{t-1} + b)。
只有一组输入权重、一组循环权重与一个偏置,没有门控。h_0 为零向量,forward 返回整段序列的全部隐藏状态,形状为 (batch, sequence, hiddenSize)。
该层保留作对照基线:BPTT 的雅可比是 U^T diag(1 - h^2) 的连乘,谱范数通常小于 1,早期时间步的梯度随序列长度按几何级数衰减。
示例VanillaRNN rnn = VanillaRNN.builder().hiddenSize(8).seed(42).build(); Tensor<BSF> input = Tensor.zerosBSF(2, 5, 3); Tensor<BSF> output = rnn.forward(input); Tensor<BSF> inputGradient = rnn.backward(Tensor.zerosBSF(2, 5, 8)); rnn.update(Optimizer.SGD.withLearningRate(0.05));注意
参数规模为 hiddenSize * (features + hiddenSize + 1),约为同规模 LSTM 的四分之一。
注意
bias 以全零初始化;输入权重与循环权重分别按 ±sqrt(6 / (features + hiddenSize)) 与 ±sqrt(6 / (2 * hiddenSize)) 的均匀分布采样。
注意
不提供 hiddenSize() 访问器,隐藏单元数只在构建时可见。
注意
必须按 forward → backward → update 的顺序调用。
参见:basalt.nn.LSTM, basalt.nn.GRU, basalt.nn.SequenceNetwork
方法
- builder() → Builderstatic¶
创建构建器,hiddenSize 默认 16、seed 默认 42。
- 返回
新的 Builder 实例
- forward(Tensor<BSF> input) → Tensor<BSF>¶
沿时间轴前向传播,返回全序列隐藏状态。
注意
隐藏缓冲每次前向先清零,t=0 不使用循环项。
- 参数
input (Tensor<BSF>) – 形状 (batch, sequence, features) 的 BSF 张量;features 在首次调用时确定并据此分配参数
- 返回
形状 (batch, sequence, hiddenSize) 的张量,每步为 tanh(W x_t + U h_{t-1} + b)
- 抛出
IllegalArgumentException – features 与首次 forward 时确定的值不一致
- backward(Tensor<BSF> gradient) → Tensor<BSF>¶
沿时间轴反向传播,累积参数梯度并返回输入梯度。
注意
每步梯度乘以 1 - h^2,长序列上乘积累积会使早期时间步的梯度迅速趋近零。
- 参数
gradient (Tensor<BSF>) – 上游梯度,形状必须为 (batch, sequence, hiddenSize)
- 返回
对输入的梯度,形状 (batch, sequence, features)
- 抛出
IllegalStateException – 尚未调用 forward
IllegalArgumentException – gradient 的形状与最近一次 forward 不匹配
- update(OptimizerConfig optimizer)¶
用累积梯度对三组参数执行一次优化器更新。
- 参数
optimizer (OptimizerConfig) – 优化器超参数,同时作用于输入权重、循环权重与偏置
- 抛出
IllegalStateException – 未完成 forward 与 backward,或梯度已被上一次 update 消费
- parameters() → List<ParameterRef>¶
返回三组参数的实时视图。
- 返回
参数视图列表,名称依次为 rnn.inputWeights、rnn.recurrentWeights、rnn.bias;首次 forward 之前为空列表
- kind() → ModelKind¶
返回模型族标识。
- 返回
固定为 ModelKind.VANILLA_RNN
- write(ModelWriter writer)¶
把配置与权重写入模型流。
注意
未初始化时 inputFeatures 写为 -1,且不写权重数组。
- 参数
writer (ModelWriter) – 目标写入器;依次写入 hiddenSize、seed、inputFeatures,已初始化时再写三组权重
- read(ModelReader reader) → VanillaRNNstatic¶
从模型流恢复 VanillaRNN 实例。
- 参数
reader (ModelReader) – 源读取器,读取顺序须与 write 一致
- 返回
恢复后的实例;若流中记录了特征数,则同时重建并覆盖权重
- 抛出
ModelFormatException – hiddenSize 非正,或特征标记既非 -1 也非正数
- static final class Conv2D.Builder¶
Conv2D 的分阶段构建器,默认 1 个 3×3 卷积核。
所有 setter 返回构建器自身,可链式调用;字段默认值为 filters = 1、kernelSize = (3, 3)、strides = (1, 1)、padding = VALID、activation = RELU、seed = 42。
build 时校验 filters、卷积核与步长均为正,并拒绝 SOFTMAX:卷积层按输出位置逐元素激活,无法对单点做整行归一化。
示例Conv2D conv = Conv2D.builder() .filters(16) .kernelSize(5, 5) .strides(2, 2) .padding(Padding.SAME) .activation(Activation.GELU) .seed(2024L) .build();注意
输入通道数不在构建期指定,由首次 forward 从输入推断
注意
每次 build 生成独立的 Conv2D 实例,参数在首次前向时才分配
参见:basalt.nn.Conv2D, basalt.nn.Padding, basalt.nn.Activation
方法
- filters(int filters) → Builder¶
设置输出通道数。
注意
默认 1;同时决定偏置长度与 fanOut
注意
取值在 build 时校验,setter 本身不做检查
- 参数
filters (int) – 卷积核数量,即输出通道数,须为正
- 返回
当前构建器,便于链式调用
- kernelSize(int height, int width) → Builder¶
设置卷积核的空间尺寸。
注意
默认 (3, 3);非方形核会同时影响两个方向的输出尺寸与 fanIn、fanOut
注意
取值在 build 时校验,setter 本身不做检查
- 参数
height (int) – 卷积核高度,须为正
width (int) – 卷积核宽度,须为正
- 返回
当前构建器,便于链式调用
- strides(int height, int width) → Builder¶
设置两个方向的滑动步长。
注意
默认 (1, 1);SAME 下输出尺寸为 ceil(in / stride),与步长直接相关
注意
取值在 build 时校验,setter 本身不做检查
- 参数
height (int) – 垂直步长,须为正
width (int) – 水平步长,须为正
- 返回
当前构建器,便于链式调用
- padding(Padding padding) → Builder¶
设置补零模式。
注意
默认 VALID
- 参数
padding (Padding) – Padding.VALID 或 Padding.SAME
- 返回
当前构建器,便于链式调用
- 抛出
NullPointerException – padding 为 null,由 Objects.requireNonNull 立即抛出
- activation(Activation activation) → Builder¶
设置卷积后逐元素应用的激活函数。
注意
默认 RELU;该选择同时影响权重初始化幅度
- 参数
activation (Activation) – 任意 Activation 常量,SOFTMAX 除外
- 返回
当前构建器,便于链式调用
- 抛出
NullPointerException – activation 为 null,立即抛出
IllegalArgumentException – 在 build 时为 SOFTMAX
- seed(long seed) → Builder¶
设置权重初始化的随机种子。
注意
默认 42;相同种子与相同形状会得到相同初值
- 参数
seed (long) – 传给 java.util.Random 的种子
- 返回
当前构建器,便于链式调用
- build() → Conv2D¶
校验配置并创建 Conv2D。
注意
不校验输入通道数,通道数在首次 forward 时确定
- 返回
新的 Conv2D 实例,参数尚未分配
- 抛出
IllegalArgumentException – filters、卷积核或步长非正,或激活为 SOFTMAX
- static final class Dense.Builder¶
Dense 的构建器,默认 1 个单元且无激活。
字段默认值为 units = 1、activation = NONE、seed = 42。输入特征数不在构建期指定,由首次 forward 从输入第 1 维推断并锁定。
build 只校验 units 为正,特征数一致性留到前向阶段检查;构建期不分配任何数组,因此先建层、后决定输入宽度的写法是安全的。
示例Dense dense = Dense.builder() .units(64) .activation(Activation.TANH) .seed(11L) .build(); Dense head = Dense.builder() .units(3) .activation(Activation.SOFTMAX) .build();注意
默认 NONE 激活意味着构建后即为纯线性层,需要非线性时显式指定
注意
每次 build 生成独立实例,参数在首次前向时才分配
参见:basalt.nn.Dense, basalt.nn.Activation
方法
- units(int units) → Builder¶
设置输出单元数。
注意
默认 1;同时决定偏置长度与 Glorot 初始化的 fanOut
注意
取值在 build 时校验,setter 本身不做检查
- 参数
units (int) – 输出维度,须为正
- 返回
当前构建器,便于链式调用
- activation(Activation activation) → Builder¶
设置该层的激活函数。
注意
默认 NONE;RELU 会改用 √(6/features) 的初始化幅度
- 参数
activation (Activation) – 任意 Activation 常量
- 返回
当前构建器,便于链式调用
- 抛出
NullPointerException – activation 为 null,立即抛出
- seed(long seed) → Builder¶
设置权重初始化的随机种子。
注意
默认 42;相同种子与相同形状会得到相同初值
- 参数
seed (long) – 传给 java.util.Random 的种子
- 返回
当前构建器,便于链式调用
- build() → Dense¶
校验配置并创建 Dense。
注意
特征数由首次 forward 决定,构建期无法校验
- 返回
新的 Dense 实例,参数尚未分配
- 抛出
IllegalArgumentException – units 非正
- static final class Flatten.Builder¶
Flatten 的构建器,无任何可配置项。
Flatten 没有参数与超参数,Builder 只为与其它层保持一致的构造入口而存在,build 不接受任何前置调用。
示例Flatten flatten = Flatten.builder().build();注意
每次 build 返回新的无状态 Flatten 实例,形状在首次 forward 时记录
方法
- build() → Flatten¶
创建 Flatten 实例。
注意
形状缓存由 forward 填充,与构建过程无关
- 返回
新的 Flatten,无状态且可重复使用
- static final class GRU.Builder¶
GRU 构建器,隐藏单元数默认 16,随机种子默认 42。
仅暴露隐藏单元数与随机种子两项配置,其余结构由层自身固定。构建出的 GRU 在首次 forward 前不持有参数,因此构建器不校验输入特征数。
注意
校验发生在 build:hiddenSize 非正时抛出 IllegalArgumentException,而非在 setter 中。
方法
- seed(long seed) → Builder¶
设置权重初始化随机种子,默认 42。
- 参数
seed (long) – 传给 java.util.Random 的种子;相同种子与相同特征数可复现初始权重
- 返回
this,便于链式调用
- build() → GRU¶
校验配置并构造 GRU。
- 返回
新的 GRU 实例,参数尚未分配
- 抛出
IllegalArgumentException – hiddenSize 小于等于 0
- record GradientCheck.Report¶
梯度检验的最大相对误差与检查次数。
记录组件为 maxInputError、maxParameterError、inputChecks 与 parameterChecks,分别是输入维度、参数维度的最大相对误差,以及两个维度的实际检查次数。
注意
只做数据承载,不校验容差;判定交给 passed
注意
checkNetwork 产生的报告 inputChecks 恒为 0、maxInputError 恒为 0.0
方法
- passed(double tolerance) → boolean¶
判断两个最大相对误差是否都不超过容差。
注意
容差取 0 时只有误差恰为 0 才通过
注意
容差由调用方决定,类内没有推荐值
- 参数
tolerance (double) – 相对误差上界,按 max(1, |analytic| + |numeric|) 归一化后的量
- 返回
maxInputError 与 maxParameterError 均不大于 tolerance 时为 true
- toString() → String¶
返回检查次数与最大误差的单行摘要。
注意
使用 Locale.ROOT 格式化,避免区域设置影响小数点
- 返回
形如 inputs=12 maxErr=1.234e-08, parameters=16 maxErr=5.678e-09 的字符串
- static final class LSTM.Builder¶
LSTM 构建器,隐藏单元数默认 16,随机种子默认 42。
仅暴露隐藏单元数与随机种子两项配置。构建出的 LSTM 在首次 forward 前不持有参数,输入特征数与三组权重的形状都推迟到首次前向时确定。
注意
校验发生在 build:hiddenSize 非正时抛出 IllegalArgumentException,而非在 setter 中。
方法
- seed(long seed) → Builder¶
设置权重初始化随机种子,默认 42。
- 参数
seed (long) – 传给 java.util.Random 的种子;相同种子与相同特征数可复现初始权重
- 返回
this,便于链式调用
- build() → LSTM¶
校验配置并构造 LSTM。
- 返回
新的 LSTM 实例,参数尚未分配
- 抛出
IllegalArgumentException – hiddenSize 小于等于 0
- static final class MaxPooling2D.Builder¶
MaxPooling2D 的构建器,默认 2×2 且不重叠。
字段默认值为 poolSize = (2, 2),strideHeight 与 strideWidth 为 0,表示“未指定”。构造层时若步长为 0,则替换为对应的窗口尺寸,从而得到不重叠池化。
build 校验窗口尺寸为正、步长非负,且两个步长必须同时缺省或同时为正;只给一个方向指定步长会被拒绝,负步长同样拒绝。
示例MaxPooling2D pool = MaxPooling2D.builder() .poolSize(3, 3) .strides(1, 1) .build();注意
步长小于窗口尺寸即得到重叠池化,反向时同一输入位置会累加多个窗口的梯度
方法
- poolSize(int height, int width) → Builder¶
设置池化窗口尺寸。
注意
默认 (2, 2);未指定步长时步长取该尺寸
注意
取值在 build 时校验,setter 本身不做检查
- 参数
height (int) – 窗口高度,须为正
width (int) – 窗口宽度,须为正
- 返回
当前构建器,便于链式调用
- strides(int height, int width) → Builder¶
设置两个方向的池化步长。
注意
默认两个方向都为 0,即不重叠池化
注意
取值在 build 时校验,setter 本身不做检查
- 参数
height (int) – 垂直步长;0 表示取 poolHeight
width (int) – 水平步长;0 表示取 poolWidth
- 返回
当前构建器,便于链式调用
- build() → MaxPooling2D¶
校验配置并创建 MaxPooling2D。
注意
构建期不校验输入尺寸,窗口是否放得下在 forward 时检查
- 返回
新的 MaxPooling2D 实例,步长已按窗口尺寸补齐
- 抛出
IllegalArgumentException – 窗口非正、步长为负,或两个步长只指定了一个
- static final class NeuralNetwork.ImageBuilder¶
图像阶段构建器,累积 Conv2D 与 MaxPooling2D。
内部维护一个层列表。两个 add 重载追加层后返回自身以支持链式调用;add(Flatten) 追加后把列表复制给新的 VectorBuilder。该阶段不提供 build,因此缺少 Flatten 的模型无法产出。
注意
没有接受 Dense 的重载,全连接层在类型上无法出现在 Flatten 之前
注意
进入 VectorBuilder 时复制列表,两个构建器不再共享可变状态
参见:basalt.nn.NeuralNetwork.VectorBuilder, basalt.nn.Conv2D, basalt.nn.MaxPooling2D
方法
- add(Conv2D layer) → ImageBuilder¶
追加一个卷积层。
- 参数
layer (Conv2D) – 卷积层,不可为 null
- 返回
this,便于继续链式调用
- 抛出
NullPointerException – layer 为 null
- add(MaxPooling2D layer) → ImageBuilder¶
追加一个最大池化层。
- 参数
layer (MaxPooling2D) – 池化层,不可为 null
- 返回
this,便于继续链式调用
- 抛出
NullPointerException – layer 为 null
- add(Flatten layer) → VectorBuilder¶
追加 Flatten 并切换到向量阶段。
注意
这是图像阶段唯一的出口,之后的构建器不再接受卷积或池化层
- 参数
layer (Flatten) – 展平层,不可为 null
- 返回
持有已累积层列表副本的 VectorBuilder
- 抛出
NullPointerException – layer 为 null
- static final class NeuralNetwork.VectorBuilder¶
向量阶段构建器,累积 Dense 并配置损失与优化器。
构造时复制图像阶段累积的层列表,之后只接受 Dense。loss 与 optimizer 各自持有默认值,build 时原样传给 CNN 构造器,因此模型创建后这两项不可再改。
注意
默认损失 CATEGORICAL_CROSSENTROPY,默认优化器 Optimizer.ADAM.withLearningRate(0.001)
注意
build 不校验损失与末端激活是否匹配,也不检查层数是否为空
参见:basalt.nn.NeuralNetwork.ImageBuilder, basalt.nn.CNN
方法
- add(Dense layer) → VectorBuilder¶
追加一个全连接层。
- 参数
layer (Dense) – 全连接层,不可为 null;单位数在构建该层时已校验
- 返回
this,便于继续链式调用
- 抛出
NullPointerException – layer 为 null
- loss(Loss loss) → VectorBuilder¶
设置训练损失,默认 CATEGORICAL_CROSSENTROPY。
注意
与末端激活的匹配关系不在构建期检查
- 参数
loss (Loss) – 损失函数,不可为 null
- 返回
this
- 抛出
NullPointerException – loss 为 null
- optimizer(OptimizerConfig optimizer) → VectorBuilder¶
设置优化器超参,默认 ADAM 学习率 0.001。
- 参数
optimizer (OptimizerConfig) – 超参配置,不可为 null;其字段在构造时已校验
- 返回
this
- 抛出
NullPointerException – optimizer 为 null
- build() → CNN¶
用当前层列表、损失与优化器创建 CNN。
注意
不做损失与激活的匹配校验,也不校验 Flatten 之后是否至少有一个 Dense
- 返回
层列表已复制为不可变列表的 CNN 实例
- static final class SelfAttention.Builder¶
自注意力构建器,dimension 默认 16、seed 默认 42、非因果。
投影维度同时决定输入特征数、输出特征数与权重矩阵尺寸,三者被强制一致,因此该层不改变特征轴长度。因果标志只影响注意力掩码,不改变参数量。
注意
缩放因子 1 / sqrt(dimension) 在构造时计算,之后不可更改。
方法
- dimension(int dimension) → Builder¶
设置投影维度,默认 16。
- 参数
dimension (int) – 每个位置的 Q/K/V 维度,也是输入输出特征数;必须为正,在 build 时校验
- 返回
this,便于链式调用
- seed(long seed) → Builder¶
设置权重初始化随机种子,默认 42。
- 参数
seed (long) – 传给 java.util.Random 的种子;Q、K、V 三组权重依次从同一随机源采样
- 返回
this,便于链式调用
- causal(boolean causal) → Builder¶
设置是否启用因果掩码,默认关闭。
注意
掩码方向为“看过去”:行索引小于列索引的位置被置零。
- 参数
causal (boolean) – true 时位置 t 只对键 0..t 归一化,屏蔽未来时间步;false 时对全部键归一化
- 返回
this,便于链式调用
- build() → SelfAttention¶
校验配置并构造自注意力层。
- 返回
新的 SelfAttention 实例,权重在首次 forward 时分配
- 抛出
IllegalArgumentException – dimension 小于等于 0
- static final class SequenceNetwork.Builder¶
整网构建器;LAST 池化、分类交叉熵、Adam 0.001、裁剪 5.0。
主体层按 add 调用顺序串联,池化与头部各自只有一个实例。所有 setter 以 requireNonNull 拒绝 null 引用,校验集中在 build,因此配置错误在构建阶段一次性暴露。
注意
build 会交叉校验损失与头部激活:分类交叉熵配 SOFTMAX,二元交叉熵配 SIGMOID。
注意
默认池化 LAST 适合分类;需要利用全部时间步时可改为 MEAN。
参见:basalt.nn.SequenceNetwork, basalt.nn.SequencePool, basalt.nn.Dense
方法
- <L extends Layer<BSF, BSF>> add(L layer) → Builder¶
向主体追加一个序列层,按调用顺序串联。
注意
泛型约束使 Dense 等 BC 布局的层无法加入主体,编译期即被拒绝。
- 参数
layer (L) – 类型为 Layer<BSF, BSF> 的层,如 LSTM、GRU、VanillaRNN、SelfAttention;不能为 null
- 返回
this,便于链式调用
- pooling(SequencePool.Pooling pooling) → Builder¶
设置时间轴池化策略,默认 LAST。
- 参数
pooling (SequencePool.Pooling) – LAST 取末步,MEAN 取时间均值;不能为 null
- 返回
this,便于链式调用
- head(Dense head) → Builder¶
设置输出头,必填项。
注意
头部的 units 决定输出类别数,激活函数需与损失匹配。
- 参数
head (Dense) – Dense 层;其输入维度须等于池化后的特征数,不能为 null
- 返回
this,便于链式调用
- loss(Loss loss) → Builder¶
设置损失函数,默认分类交叉熵。
- 参数
loss (Loss) – Loss 枚举值,不能为 null
- 返回
this,便于链式调用
- optimizer(OptimizerConfig optimizer) → Builder¶
设置优化器超参数,默认 Adam、学习率 0.001。
- 参数
optimizer (OptimizerConfig) – 优化器配置;可用 Optimizer.ADAM.withLearningRate(...) 等工厂方法构造,不能为 null
- 返回
this,便于链式调用
- gradientClip(double gradientClip) → Builder¶
设置全局梯度范数裁剪阈值,默认 5.0。
注意
裁剪作用于整网所有参数的拼接梯度范数,而不是逐参数单独裁剪。
- 参数
gradientClip (double) – 范数上限;须为有限正数,在 build 时校验
- 返回
this,便于链式调用
- build() → SequenceNetwork¶
校验配置并构造整网。
- 返回
新的 SequenceNetwork 实例;主体层列表被复制,后续修改构建器不影响已建网络
- 抛出
IllegalStateException – 未设置 head
IllegalArgumentException – gradientClip 非正、损失与头部激活不匹配,或分类头 units 小于 2
- enum SequencePool.Pooling¶
时间轴归约策略。
LAST 取最后一个时间步的向量,是分类任务默认的 many-to-one 头;MEAN 对全部时间步求平均,每个时间步都影响结果。两者都保持特征轴不变。
枚举声明顺序为 LAST、MEAN,持久化时按序号写入,序号 0 对应 LAST、1 对应 MEAN。反向传播只使用策略本身,不需要额外状态。
注意
调整枚举常量顺序会改变序列化序号,导致旧模型文件被解析为另一种策略。
注意
没有 MAX 归约;需要最大值池化时应在层外自行实现。
参见:basalt.nn.SequencePool, basalt.nn.SequenceNetwork
枚举成员
- LAST()¶
取时间步序列的最后一步
- MEAN()¶
对时间步维取平均
- static final class VanillaRNN.Builder¶
VanillaRNN 构建器,隐藏单元数默认 16,随机种子默认 42。
仅暴露隐藏单元数与随机种子。参数在首次 forward 时按输入特征数分配,构建阶段不校验特征数,因此同一构建器产出的层可以适配不同输入宽度。
注意
校验发生在 build:hiddenSize 非正时抛出 IllegalArgumentException。
方法
- seed(long seed) → Builder¶
设置权重初始化随机种子,默认 42。
- 参数
seed (long) – 传给 java.util.Random 的种子;相同种子与相同特征数可复现初始权重
- 返回
this,便于链式调用
- build() → VanillaRNN¶
校验配置并构造 VanillaRNN。
- 返回
新的 VanillaRNN 实例,参数尚未分配
- 抛出
IllegalArgumentException – hiddenSize 小于等于 0
包内可见类型
以下类型没有 public 修饰符,但出现在公开签名中,因此一并列出。它们不是稳定 API,跨版本可能变化。
- final class Parameter¶
带私有 SGD/Adam 状态的训练参数数组
每个实例持有一份 double[] 权重与两份同长的一阶、二阶矩缓冲,Dense、Conv2D、GRU、LSTM 等层在构造时创建,训练循环里调用 update 原地更新。
update 按 OptimizerConfig 选择 SGD 或 Adam:两者都用 VectorSpecies 做向量化循环并以标量循环收尾;Adam 路径每更新一次把 step 加一,并按 1 - beta^step 做偏差校正。
层的 parameters() 通过 ParameterRef 暴露 values() 数组,因此这一类型会出现在参数列表相关的签名链上,调用方据此读取或检查权重。