- 人工智能
- 深度学习
- 嵌入式
【免费下载链接】tiny-dnn
header only, dependency-free deep learning framework in C++14
导读
tiny-dnn 是一个 header-only、无第三方依赖的 C++14 深度学习框架,但它的模型转换子系统是一个例外:借助 examples/caffe_converter 示例,tiny-dnn 可以解析 Caffe 的 prototxt 网络描述、读取.caffemodel二进制权重,并在本框架内直接完成前向推理。读完本文,你将掌握 tiny-dnn 转换 Caffe 模型的完整流程:环境准备、通过protoc生成 protobuf 代码、编译转换器、按五个命令行参数完成一次真实的图像分类推理,以及转换器的层类型支持范围与结构限制。
一、转换器做什么:从 Caffe 模型到 tiny-dnn 网络
tiny-dnn 本身不提供训练 Caffe 模型的能力,但它提供了读取 Caffe 训练产物的通道。examples/caffe_converter/caffe_converter.cpp是这一能力的完整落地示例,核心调用只有两行:
auto net = tiny_dnn::create_net_from_caffe_prototxt(model_file); // 解析网络结构 tiny_dnn::reload_weight_from_caffe_protobinary(trained_file, net.get()); // 加载权重这两行背后对应 tiny_dnn/io/caffe/layer_factory.h 中的四个公共接口:
| 接口 | 作用 | 输入格式 |
|---|---|---|
create_net_from_caffe_prototxt | 从文本格式 prototxt 构建网络 | .prototxt纯文本 |
create_net_from_caffe_protobinary | 从二进制 NetParameter 构建网络 | .caffemodel(含结构) |
reload_weight_from_caffe_protobinary | 把二进制权重注入已构建的网络 | .caffemodel二进制 |
create_net_from_caffe_net | 统一入口:根据 shape 推断输入尺寸并逐层转换 | caffe::NetParameter |
以create_net_from_caffe_prototxt为例,其实现是先通过detail::read_proto_from_text用 protobuf 的TextFormat解析文本,再交给create_net_from_caffe_net:
inline std::shared_ptr<network<sequential>> create_net_from_caffe_prototxt( const std::string &caffeprototxt, const shape3d &shape = shape3d()) { caffe::NetParameter np; detail::read_proto_from_text(caffeprototxt, &np); return create_net_from_caffe_net(np, shape); }输入尺寸的推导顺序值得注意(见 layer_factory.h):
- 显式传入
shape3d参数时优先使用; - 否则读取已废弃的
input_shape字段(dim顺序为 N × C × H × W); - 否则尝试
Input层的input_param.shape; - 都不存在则抛出
nn_error("input_shape not found in caffemodel. must specify input shape explicitly")。
这也是为什么 CaffeNet 的deploy.prototxt能被直接转换——它本身带input_shape。
注意:这个转换子系统是实验性的。在 tiny_dnn/tiny_dnn.h 中,相关头文件被包裹在
#ifdef CNN_USE_CAFFE_CONVERTER之下,且注释明确写着 "experimental / require google protobuf"。
二、环境准备:Google protobuf 与 OpenCV
原文档列出的前置依赖有两项:
- Google protobuf:转换器依赖 protobuf 的运行时与代码生成器
protoc来解析 Caffe 的NetParameter消息; - OpenCV:示例程序需要读取待分类的图像文件(BGR 格式),并参与均值图像的计算与预处理。
在 CMakeLists.txt 中,protobuf 是作为硬性依赖引入的:
find_package( Protobuf REQUIRED ) PROTOBUF_GENERATE_CPP( PROTO_SRC PROTO_HDR ${CMAKE_CURRENT_SOURCE_DIR}/../../tiny_dnn/io/caffe/caffe.proto ) add_executable( caffe_converter caffe_converter.cpp ${PROTO_SRC} ${PROTO_HDR} ) target_link_libraries( caffe_converter ${PROTOBUF_LIBRARY} )PROTOBUF_GENERATE_CPP会在构建时自动调用protoc生成caffe.pb.cc与caffe.pb.h,因此使用 CMake 构建时无需手工执行protoc。
三、构建步骤
3.1 方式一:手工protoc(原文档做法)
原文档给出的手工流程分两步:
第一步,进入 caffe 的 proto 定义目录并生成 C++ 代码:
cd tiny_dnn/io/caffe protoc caffe.proto --cpp_out=./该命令会在tiny_dnn/io/caffe/下生成caffe.pb.cc与caffe.pb.h。caffe.proto(tiny_dnn/io/caffe/caffe.proto)是 tiny-dnn 仓库内置的一份 Caffe 协议定义,采用syntax = "proto2",覆盖了NetParameter、LayerParameter、BlobProto、ConvolutionParameter、PoolingParameter、InnerProductParameter、BatchNormParameter等核心消息。值得注意的是它还保留了V1LayerParameter、V0LayerParameter等旧版本消息,并在 layer_factory_impl.h 的caffe_layer_vector::upgradev1net中实现了 V1 → V2 的自动升级转换。
第二步,将生成的caffe.pb.cc与示例主文件一起编译并链接:
# 例如(具体编译命令取决于你的编译器与包含路径): g++ -std=c++11 \ tiny_dnn/io/caffe/caffe.pb.cc \ examples/caffe_converter/caffe_converter.cpp \ -I. -lprotobuf -lopencv_core -lopencv_imgproc -lopencv_imgcodecs \ -o caffe_converter.bin注意:转换器的可执行文件名在原文档中约定为caffe_converter.bin。
3.2 方式二:CMake
仓库为示例提供了独立的 CMakeLists.txt,内部已封装protoc调用,并对目标设置了 C++11 标准(CXX_STANDARD 11)。与 tiny-dnn 主框架的 C++14 要求不同,转换器示例的最低标准是 C++11。
四、命令行用法与参数详解
转换器是纯命令行工具,原文档给出的调用格式为:
./caffe_converter.bin [model-file] [trained-file] [mean-file] [label-file] [img-file]五个位置参数在 caffe_converter.cpp 中按顺序依次读取:
| 参数 | 含义 | 在 CaffeNet 示例中的取值 |
|---|---|---|
model-file | 网络结构文件(prototxt 文本) | deploy.prototxt |
trained-file | 训练好的权重文件(二进制) | bvlc_reference_caffenet.caffemodel |
mean-file | 均值文件(binaryproto) | imagenet_mean.binaryproto |
label-file | 类别标签文本文件(每行一个标签) | synset_words.txt |
img-file | 待分类的图像文件 | cat.jpg |
对应的完整命令(原文档中的预训练 CaffeNet 分类示例):
./caffe_converter.bin\ deploy.prototxt\ bvlc_reference_caffenet.caffemodel\ imagenet_mean.binaryproto\ synset_words.txt\ cat.jpg主函数main不做参数校验,直接按序取用argv[1..5];异常通过catch (const tiny_dnn::nn_error &e)捕获并打印e.what(),因此当网络结构不受支持、文件缺失或解析失败时,错误信息会直接输出到标准输出。
五、转换后的推理流程:从图像到 Top-5 结果
test()函数(caffe_converter.cpp)完整展示了转换之后的推理链路,可分为四步:
5.1 加载网络与权重
auto labels = get_label_list(label_file); auto net = tiny_dnn::create_net_from_caffe_prototxt(model_file); tiny_dnn::reload_weight_from_caffe_protobinary(trained_file, net.get());get_label_list逐行读取标签文件;reload_weight_from_caffe_protobinary会根据 Caffe 层类型与 tiny-dnn 层类型的映射表(layer_match)逐层对齐并填充权重,跳过不含权重的激活层(ReLU、Sigmoid、TanH、Softmax、Dropout、LRN 等,见 layer_factory_impl.h 的layer_has_weights)。
5.2 从网络推导输入尺寸
int width = (*net)[0]->in_data_shape()[0].width_; int height = (*net)[0]->in_data_shape()[0].height_;输入图像的宽高直接取自 tiny-dnn 网络第一个层的输入数据形状(in_data_shape),无需在命令行重复指定——这也是读取网络结构后再做预处理的根本原因。
5.3 读取均值文件并预处理图像
compute_mean读取imagenet_mean.binaryproto(Caffe 的BlobProto二进制格式),把其中保存的均值数据构造成 BGR 图像,然后调用mean_image(tiny_dnn/util/image.h)得到逐通道的均值图:
caffe::BlobProto blob; tiny_dnn::detail::read_proto_from_binary(mean_file, &blob); auto data = blob.mutable_data()->mutable_data(); tiny_dnn::image<float> original(data, blob.width(), blob.height(), tiny_dnn::image_type::bgr); return mean_image(original);preprocess完成与 Caffe 分类流程一致的预处理:先用resize_image把图像缩放到网络输入尺寸,再通过subtract_scalar逐像素减去均值;若均值文件为空则直接使用缩放后的像素:
tiny_dnn::image<float> resized = resize_image(img, width, height); if (!mean.empty()) { tiny_dnn::image<float> normalized = subtract_scalar(resized, mean); *dst = normalized.to_vec(); } else { *dst = resized.to_vec(); }需要特别指出:Caffe 官方
cpp_classification的预处理顺序是"先减均值、再缩放",而此示例的实现是"先缩放、再减均值"(resize_image在subtract_scalar之前调用),且没有执行scale缩放系数。如果你期望与 Caffe 官方流程得到完全一致的数值,需要留意这一差异。
5.4 前向推理与 Top-5 输出
auto result = net->predict(vec);推理耗时通过clock()统计并打印Elapsed time(s)。结果向量经过partial_sort取前 5 个最大值,再用find反查其类别下标,最终按"标签,置信度"的格式输出:
goldfish, 0.9xxx tench, 0.0xxx ...该输出形式与 Caffe 官方分类示例的 Top-5 展示风格一致,便于直接对比转换前后两个框架的预测一致性。
六、支持的层类型与转换规则
转换器并非全量支持 Caffe 的所有层。从 layer_factory_impl.h 的layer_supported可以确认,tiny-dnn 转换器只接受以下 15 种 Caffe 层类型:
| 类别 | 支持的 Caffe 层类型 | tiny-dnn 落点 |
|---|---|---|
| 卷积族 | Convolution、Deconvolution | conv/deconv |
| 全连接 | InnerProduct | fully-connected |
| 池化 | Pooling(MAX / AVE) | max-pool/ave-pool |
| 归一化 | LRN、BatchNorm | lrn/batch-norm |
| 激活 | ReLU、ELU、Sigmoid、TanH、Softmax | 对应激活层 |
| 损失 | SoftmaxWithLoss、SigmoidCrossEntropyLoss | 转为激活层(推理时丢弃损失语义) |
| 随机化 | Dropout | dropout(测试阶段) |
| 逐元素运算 | Power | power |
对应关系由create()分发函数与layer_match映射表共同决定,其中Pooling会根据pooling_param.pool是MAX还是AVE分别创建最大池化层或平均池化层。
layer_skipped(layer_factory_impl.h)则会直接跳过以下层:Data、EuclideanLoss、Input、HDF5Data、Split、Accuracy。这些层要么是数据装载层,要么是只在训练/评估阶段才需要的辅助层,对纯推理结构无贡献。
caffe_layer_vector(layer_factory_impl.h)负责把 Caffe 的层图解析为单链:通过bottom/top名称建立层间连接关系,寻找无prev的根节点后沿next指针展开成线性序列,并完成旧版 V1 prototxt 到 V2 的自动升级。该结构决定了转换器只支持线性流水线。
权重加载的细节同样值得关注:
- 全连接层权重按
w[i * dst_out_size + o] = weights.data(curr++)进行转置填充(layer_factory_impl.h),并在层尺寸不匹配时抛出nn_error("layer size mismatch!"); - 卷积层权重支持
group(分组卷积),通过core::connection_table记录连接关系(layer_factory_impl.h); - 平均池化层在 tiny-dnn 中是可训练参数的,转换时会把权重填为
1 / (pool_size_w * pool_size_h)、偏置置 0,并调用set_trainable(false)(layer_factory_impl.h); - BatchNorm 层读取 3 个 blob(均值、方差、缩放因子),并按
scale_factor = 1 / blob2.data(0)还原真实统计量(layer_factory_impl.h); - 支持从 prototxt 中读取
xavier、constant、gaussian三种权重初始化器(create_filler),但权重存在时优先加载权重。
转换正确性的自动化验证
仓库在 test/test_caffe_converter.h 中提供了针对该转换器的单元测试,用内嵌的 prototxt 文本(JSON 字符串形式)直接调用create_net_from_json(其内部复用create_net_from_caffe_prototxt)。其中:
rectangle_input测试验证了非方形输入(40×24)下 conv→fc 结构的形状推导;lenet测试完整覆盖 LeNet 结构,逐层断言输入输出形状与层类型,例如conv1 28x28x1 → 24x24x20、pool1 24x24x20 → 12x12x20、fc 4*4*50 → 500,最终网络深度为 8(conv→max-pool→conv→max-pool→fc→relu→fc→softmax)。
这些测试是理解"哪些 prototxt 写法能被正确解析、各层输出形状如何计算"的绝佳参考。
七、限制与适用范围
原文档末尾明确了一条核心限制:
tiny-dnn's converter only supports single input/single output network without branch.
结合源码可以展开为三条约束:
- 仅支持单输入/单输出:
caffe_layer_vector通过单一根节点沿next线性展开,top多于一个或bottom多于一个的层无法被线性化; - 不支持分支结构:如
Split、Concat、Eltwise、Slice、Reshape等多路拓扑层不在layer_supported列表内,遇到未支持的层类型会抛出nn_error("error: tiny-dnn does not support this layer type:" + type); - 仅限推理场景:
SoftmaxWithLoss、SigmoidCrossEntropyLoss等损失层被转换为纯激活层,转换器定位是"导入训练产物做前向预测",而非复刻 Caffe 训练过程。
此外还有几个实操层面的注意点:卷积/池化的 padding 只接受valid(pad=0)或same(pad=(kernel-1)/2)两种情况,其余 pad 值会报nn_error("conv:not supported padding size");非方形卷积核(kernel_w != kernel_h)同样不受支持。
八、快速上手的完整步骤清单
综合以上内容,将 Caffe 模型导入 tiny-dnn 并完成一次推理的完整流程如下:
- 准备依赖:安装 Google protobuf(含
protoc)与 OpenCV; - 生成 protobuf 代码:
cd tiny_dnn/io/caffe && protoc caffe.proto --cpp_out=./(或直接使用 CMake 构建,见 CMakeLists.txt); - 编译链接:编译
caffe.pb.cc与 caffe_converter.cpp,链接 protobuf 与 OpenCV,输出可执行文件caffe_converter.bin; - 准备五个文件:
deploy.prototxt(网络结构)、*.caffemodel(权重)、mean.binaryproto(均值)、labels.txt(标签)、待分类图片; - 运行:按
[model-file] [trained-file] [mean-file] [label-file] [img-file]顺序传入五个参数; - 解读输出:程序打印推理耗时
Elapsed time(s),随后输出置信度最高的 5 个类别及其分值。
只要网络是单输入单输出、无分支的线性结构,且层类型落在上文的 15 种支持列表内,该工具即可把 Caffe 训练成果无缝迁移到 tiny-dnn 的推理体系中。
- 人工智能
- 深度学习
- 嵌入式
【免费下载链接】tiny-dnn
header only, dependency-free deep learning framework in C++14
相关推荐
终极指南:如何在tiny-dnn中轻松导入Caffe模型
终极指南:如何在tiny dnn中轻松导入Caffe模型 想要将现有的Caffe模型快速迁移到轻量级的tiny dnn框架中吗?🚀 本教程将手把手教你完成Ca
人工智能深度学习嵌入式英灵神殿游戏增强模组怎么选?ValheimPlus 用 3 个场景让建造与联机一步到位
英灵神殿游戏增强模组怎么选?ValheimPlus 用 3 个场景让建造与联机一步到位 凌晨一点,你又一次拆掉那面歪了三度的墙。45 度旋转、说满就满的背包、联
游戏开发Chainer 的 Caffe 模型互操作:CaffeFunction 导入预训练模型与 export() 导出 Caffe 格式
Chainer 的 Caffe 模型互操作:CaffeFunction 导入预训练模型与 export 导出 Caffe 格式 本文围绕 Chainer 官方参
深度学习机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考