- 人工智能
- 深度学习
- 嵌入式
【免费下载链接】tiny-dnn
header only, dependency-free deep learning framework in C++14
导读
本文基于 docs/update_log/v0_0_1-to-v0_1_0.md 整理 tiny-dnn 从 v0.0.1 升级到 v0.1.0 时发生的三处核心训练 API 变更:损失函数与优化器从网络模板参数改为 train/fit 的实参、回归(fit)与分类(train)训练接口的正式分离、以及权重重新初始化的默认行为反转。读完本文,你将能够把 v0.0.1 时代的训练代码无缝迁移到 v0.1.0,并理解这些变更背后的源码设计逻辑。
一、升级背景:v0.1.0 为什么动训练 API
在 v0.0.1 中,tiny-dnn 的network是一个以损失函数类型和优化算法类型为模板参数的类,例如network<mse, adagrad>。这种设计的缺点是:损失函数与优化器在编译期被"焊死"进网络类型,同一个网络对象无法在运行期切换损失函数或优化器,组合爆炸也使得类型签名越来越复杂。
v0.1.0 改为network<sequential>/network<graph>只描述网络拓扑,损失函数(Error)与优化器(Optimizer)全部下沉为train/fit的模板参数与函数实参。从 tiny_dnn/network.h 的源码可以看到,network类模板如今只有一个NetType参数,而train/fit均为成员函数模板(template <typename Error, typename Optimizer>)。
二、变更一:损失函数与优化器从模板参数变为实参
v0.0.1 的旧写法
在 v0.0.1 中,损失函数与优化算法是network的模板参数,训练直接调用train:
// v0.0.1 network<mse, adagrad> net; net.train(x_data, y_label, n_batch, n_epoch);v0.1.0 的新写法
从 v0.1.0 起,网络类型只保留拓扑信息,损失函数通过模板参数传入fit/train,优化器作为第一个实参传入:
// v0.1.0 network<sequential> net; adagrad opt; net.fit<mse>(opt, x_data, y_label, n_batch, n_epoch);源码印证
在 tiny_dnn/network.h 中,类模板声明为template <typename NetType> class network,NetType仅用于区分sequential与graph两种拓扑(源码注释明确说明:sequential 表示无分支/合并的网络,graph 用于有分支/合并的网络)。而 tiny_dnn/network.h 给出的train简化重载签名如下:
template <typename Error, typename Optimizer> bool train(Optimizer &optimizer, const std::vector<vec_t> &inputs, const std::vector<label_t> &class_labels, size_t batch_size = 1, int epoch = 1);可见损失类型Error是模板参数(如mse、cross_entropy),优化器Optimizer同时是模板参数和按引用传入的实参对象。
配套要点:
- 损失函数定义在 tiny_dnn/lossfunctions/loss_function.h 中,v0.1.0 提供
mse、absolute、absolute_eps、cross_entropy、cross_entropy_multiclass等实现。每个损失类静态实现f(损失值)与df(梯度)两个方法,例如mse的f返回均方误差、df返回2*(y-t)/n。 - 优化器定义在 tiny_dnn/optimizers/optimizer.h 中,均继承自基类
optimizer(virtual void update(const vec_t &dW, vec_t &W, bool parallelize) = 0;),并提供reset()用于训练前清理状态。内置实现包括adagrad(默认学习率 0.01)、RMSprop(默认学习率 0.0001、衰减 0.99)、adam(默认学习率 0.001、b1=0.9、b2=0.999)等。由于优化器现在是普通对象,你可以为同一网络在不同训练阶段传入不同优化器实例。
三、变更二:回归与分类训练接口正式分离(fit vs train)
v0.0.1 的痛点
在 v0.0.1 中,回归与分类共用同一个trainAPI,仅靠数据形状隐式区分:
// v0.0.1 net.train(x_data, y_data, n_batch, n_epoch); // 回归 net.train(x_data, y_label, n_batch, n_epoch); // 分类v0.1.0 的分离
从 v0.1.0 起,两者被明确拆分为fit与train:
// v0.1.0 net.fit<mse>(opt, x_data, y_data, n_batch, n_epoch); // 回归:目标为连续输出向量 net.train<mse>(opt, x_data, y_label, n_batch, n_epoch); // 分类:目标为类别标签 id源码印证
在 tiny_dnn/network.h 中,train的文档注释直接说明了二者差异:"The difference between train and fit method is how to specify desired output. This method takes label_t argument and convert to target vector automatically."(train 与 fit 的区别在于指定目标输出的方式:train 接收label_t标签并自动转换为目标向量)。
train(分类)接收std::vector<label_t> class_labels,要求标签从 0 开始编号(0-origin),且最后一层输出维度必须大于等于标签类别数(见 tiny_dnn/network.h)。它内部会把标签归一化后转交给fit完成实际训练。fit(回归)接收与网络输入同形状的desired_outputs连续目标向量(见 tiny_dnn/network.h 的说明与std::vector<vec_t>/std::vector<tensor_t>两种写法示例)。- 为了兼容旧代码,tiny_dnn/network.h 中仍保留了一个接收
std::vector<vec_t>目标的train重载,但其文档明确标注@deprecated use fit instead for regression task(已废弃,回归请改用 fit),并直接转发给fit实现。
实战示例:仓库中的 examples/sinus_fit/sinus_fit.cpp 即用net.fit<tiny_dnn::mse>(opt, X, sinusX, batch_size, epochs, ...)训练正弦函数拟合网络,正是回归任务的规范写法;而 examples/mnist/test.cpp 等分类示例则使用net.train<cross_entropy>(opt, ...)风格的分类接口。
四、变更三:权重重新初始化的默认行为反转
v0.0.1 的默认行为:自动重置权重
在 v0.0.1 中,train默认以reset_weights=true运行,这意味着即使你从磁盘加载了已训练好的模型,train也会在训练前自动把权重打乱重置,导致加载的权重白费:
// v0.0.1 std::ifstream is("model"); is >> net; net.train(x_data, y_data, n_batch, n_epoch); // 加载的权重被自动重置! net.train(x_data, y_data, n_batch, n_epoch); // 上次训练得到的参数再次丢失v0.1.0 的默认行为:保留现有权重
从 v0.1.0 起,reset_weights的默认值改为false,训练默认保留当前网络权重,加载模型后可以接着上次的训练继续:
// v0.1.0 std::ifstream is("model"); is >> net; net.train<mse>(opt, x_data, y_data, n_batch, n_epoch); // 保留已加载的权重 net.train<mse>(opt, x_data, y_data, n_batch, n_epoch); // 从上一次训练状态继续 // 未加载模型直接训练时,权重会自动初始化 net2.train<mse>(opt, x_data, y_data, n_batch, n_epoch);源码印证
在 tiny_dnn/network.h 的完整版train签名中,reset_weights参数的默认值正是false:
template <typename Error, typename Optimizer, typename OnBatchEnumerate, typename OnEpochEnumerate> bool train(Optimizer &optimizer, const std::vector<vec_t> &inputs, const std::vector<label_t> &class_labels, size_t batch_size, int epoch, OnBatchEnumerate on_batch_enumerate, OnEpochEnumerate on_epoch_enumerate, const bool reset_weights = false, const int n_threads = CNN_TASK_SIZE, const std::vector<vec_t> &t_cost = std::vector<vec_t>());fit的完整版签名同样以const bool reset_weights = false为默认值(见 tiny_dnn/network.h)。训练循环最终会调用net_.setup(reset_weights)(见 tiny_dnn/network.h):setup(true)会重新初始化各层权重,setup(false)则保持已有参数。
此外,如果你确实想主动清零并重新初始化权重,v0.1.0 提供了显式接口void init_weight() { net_.setup(true); }(见 tiny_dnn/network.h),不再需要依赖训练时的隐式重置。
注:原文档中"v0.1.0"示例代码把
train误写为trian,本文已按实际 API 更正为train。
五、迁移核对清单:从 v0.0.1 升级到 v0.1.0
升级现有代码时,请对照以下清单逐项检查:
| 检查项 | v0.0.1 写法 | v0.1.0 写法 |
|---|---|---|
| 网络类型声明 | network<mse, adagrad> net; | network<sequential> net;(拓扑复杂用network<graph>) |
| 优化器准备 | 内嵌于网络类型 | adagrad opt;独立声明,传入训练函数 |
| 损失函数指定 | 内嵌于网络类型 | 作为train/fit的模板参数,如net.train<mse>(opt, ...) |
| 回归训练 | net.train(x, y, ...) | net.fit<mse>(opt, x, y, ...) |
| 分类训练 | net.train(x, label, ...) | net.train<mse>(opt, x, label, ...) |
| 加载模型后继续训练 | 默认重置权重,无法续训 | 默认保留权重,可直接续训;需要重置时传true或调用init_weight() |
容易踩的坑:
- 回归任务误用
train:v0.1.0 中train期望label_t标签并自动转换为 one-hot 风格目标向量;回归请务必使用fit传入连续目标值(旧版train回归重载已被标记@deprecated)。 - 分类时输出维度不足:
train要求最后一层输出维度 ≥ 标签类别数(标签从 0 编号),否则目标向量无法正确构造。 - 忘记传递优化器对象:v0.1.0 的
train/fit第一参必须是一个优化器实例(如adagrad、RMSprop、adam),它按引用传入并在训练中被实际调用。 - 断点续训语义变化:v0.0.1 中加载模型后一训练就丢权重,v0.1.0 中默认续训——若你依赖旧的"自动重置"行为,请显式传
reset_weights = true。
六、深入:train/fit 完整参数签名与扩展能力
v0.1.0 的训练接口除了上述核心参数外,还提供了一组进阶能力(均在 tiny_dnn/network.h 中定义),供需要细粒度控制的场景使用:
template <typename Error, typename Optimizer, typename OnBatchEnumerate, typename OnEpochEnumerate> bool fit(Optimizer &optimizer, const std::vector<T> &inputs, const std::vector<U> &desired_outputs, size_t batch_size, // 每个 minibatch 的样本数 int epoch, // 训练轮数 OnBatchEnumerate on_batch_enumerate, // 每个 minibatch 结束回调 OnEpochEnumerate on_epoch_enumerate, // 每个 epoch 结束回调 const bool reset_weights = false, // 是否重置权重 const int n_threads = CNN_TASK_SIZE, // 并行任务数 const std::vector<U> &t_cost = std::vector<U>()); // 各样本的自定义代价权重- 回调机制:
on_batch_enumerate与on_epoch_enumerate让外部可以在每个 minibatch / 每个 epoch 结束时接管控制流(例如打印日志、记录中间模型、提前终止)。不关心回调时可传nop(默认重载已经替你填好)。 - 并行训练:
n_threads控制训练并行度,默认取CNN_TASK_SIZE;在 tiny_dnn/network.h 的train_once中可以看到,batch 大小为 1 时走单样本路径,否则走train_onebatch批量路径。 - 自定义代价(t_cost):允许为每个训练样本指定不同的损失权重,缺省时所有样本代价相同。
- 输入形状:
fit既接受std::vector<vec_t>(普通向量列表),也接受std::vector<tensor_t>(tiny_dnn/network.h 的官方注释示例展示了两种写法),内部通过normalize_tensor统一为张量格式。
从源码结构看,train(分类)最终也是把label_t标签归一化后转调fit(见 tiny_dnn/network.h),因此fit是 v0.1.0 训练体系的真正核心实现,train只是它的分类语义包装层。
七、继续深入仓库
- 迁移说明原文:docs/update_log/v0_0_1-to-v0_1_0.md(本文依据的官方变更记录)
- 训练 API 完整实现:tiny_dnn/network.h
- 损失函数清单:tiny_dnn/lossfunctions/loss_function.h
- 优化器实现:tiny_dnn/optimizers/optimizer.h
- 回归实战范例:examples/sinus_fit/sinus_fit.cpp
- 分类实战范例:examples/mnist/test.cpp、examples/mnist/train.cpp
- 官方入门教程:docs/getting_started/Getting-started.md
- 人工智能
- 深度学习
- 嵌入式
【免费下载链接】tiny-dnn
header only, dependency-free deep learning framework in C++14
相关推荐
Backbone.Marionette 版本演进全史:从 v0.1.0 到 v4.1.3 的 API 变迁与升级指南
Backbone.Marionette 版本演进全史:从 v0.1.0 到 v4.1.3 的 API 变迁与升级指南 导读 本文基于当前仓库的 changelo
前端Moodle report_eventlist 插件升级指南:事件列表生成 API 的演进与迁移实践
Moodle report_eventlist 插件升级指南:事件列表生成 API 的演进与迁移实践 导读 本文以 Moodle 仓库中 report_even
教育后端前端PyGithub 版本演进与升级迁移指南:从 Change Log 解读 API 演进、破坏性变更与最佳实践
PyGithub 版本演进与升级迁移指南:从 Change Log 解读 API 演进、破坏性变更与最佳实践 本指南以 PyGithub 官方变更日志( doc
云原生后端前端运维可观测性开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考