说明¶
说明
tiny_optimizer 提供两种针对 ESP32-S3 内存预算优化的梯度下降优化器:带动量与 L2 正则的 SGD,以及 Adam(lite 版)。所有优化器都通过 ParamGroup 组成的 std::vector 与各层的可学习参数 / 梯度对接。
Optimizer — 沿着梯度下山
损失函数的梯度告诉优化器参数该往哪个方向调。优化器决定走多大步、怎么走。
算法直觉¶
梯度下降 = 蒙眼下山¶
想象你被蒙住眼睛站在山上,目标是走到谷底。每次用脚探一下哪边最陡(梯度方向),朝那个方向迈一步(更新参数)。这就是梯度下降。
三种优化器¶
| 优化器 | 更新公式直觉 | 特点 | 适用场景 |
|---|---|---|---|
| SGD | \(\text{param} = \text{param} - \text{lr} \cdot \text{grad}\) | 原始梯度下降,只按当前梯度走 | 简单问题、小数据集 |
| SGD + Momentum | 加入"惯性":保留上次方向,当前梯度做修正 | 减少震荡,加速收敛 | 大多数场景默认 |
| Adam | 自适应学习率 + 动量 | 每个参数有自己的步长 | 调参困难、复杂网络 |
动手调参
- 学习率 lr:太大→震荡不收敛;太小→训练极慢。常用 0.01 ~ 0.0001
- momentum:0.9 是经典值。越大惯性越强
- weight_decay:L2 正则化,防止过拟合
学习率是最重要的超参数
一个常见调试顺序:先用 lr=0.01 看看损失是否下降。如果不下降(或震荡),调成 0.001。如果太慢,调成 0.1。
ParamGroup¶
每个可训练层(Dense、Conv1D、Conv2D、LayerNorm、Attention)都重载 Layer::collect_params(),把自己的 (weight, dweight)、(bias, dbias) 等成对压入 std::vector<ParamGroup>。Sequential::collect_params() 自动汇总整个网络。
Optimizer 抽象基类¶
class Optimizer
{
public:
virtual void init(const std::vector<ParamGroup> &groups) = 0; // 初始化内部缓冲
virtual void step(std::vector<ParamGroup> &groups) = 0; // 一步更新
virtual void zero_grad(std::vector<ParamGroup> &groups); // 清零梯度
};
调用顺序:
- 构造:
SGD opt(lr, mom)或Adam opt(lr, β1, β2, ε)。 - 采集参数:
model.collect_params(params)。 - 初始化:
opt.init(params)。仅在此时根据params.size()与每个张量形状分配动量 / 一二阶矩缓冲。 - 训练循环:每个 batch 执行
opt.zero_grad(params)→ forward → backward →opt.step(params)。
SGD(带动量与 L2)¶
更新公式:
参数:
lr:学习率 \(\eta\)。momentum:动量系数 \(\mu\)。设为 0 时退化为标准 SGD。weight_decay:L2 正则系数 \(\lambda\)。
init() 会为每个参数分配同形状的 velocity 张量;zero_grad() 默认实现已经在基类提供。
Adam(lite 版)¶
Adam(float lr = 1e-3f,
float beta1 = 0.9f,
float beta2 = 0.999f,
float epsilon = 1e-8f,
float weight_decay = 0.0f);
每步:
实现采用「整体 lr 偏差校正」节省每元素计算:
init() 为每个参数分配 m 与 v 两个张量;step() 内部时间步 t_ 自动 +1。
实战建议
- 对结构健康监测、生物信号等小数据 / 不稳定数据,优先用 Adam(默认参数即可)。
- 对高度稀疏 / 大 batch 训练,可试 SGD + 较大 lr + 动量 0.9。
weight_decay > 0等价于 PyTorch 的 L2 正则;只对权重生效,建议不要把 bias 一起 decay(tiny_ai中 bias 也参与,但量级可忽略)。
显存与 PSRAM 影响¶
- SGD:每个参数额外一份 velocity → 内存约 ×2。
- Adam:每个参数额外两份 (m, v) → 内存约 ×3。
如果模型权重已经放进 PSRAM,建议同步把动量缓冲也放 PSRAM。Tensor 默认走 TINY_AI_MALLOC,需要时可在外层把权重张量替换为 Tensor::from_data(psram_buf, ...) 视图。
与 Trainer 的协作¶
Trainer::ensure_params_collected() 在第一次 fit() 时执行:
之后每个 batch:
optimizer_->zero_grad(params_);
auto logits = model_->forward(X_batch);
auto grad = loss_backward(logits, ..., loss_type_, y_batch);
model_->backward(grad);
optimizer_->step(params_);
因此你完全可以重新实现一个自定义优化器(继承 Optimizer 并实现 init / step 即可),无需改动模型层或 Trainer。