news 2026/10/12 2:11:10

Leaf 框架核心概念解读:Layer 与 Network 的术语体系与源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Leaf 框架核心概念解读:Layer 与 Network 的术语体系与源码实现
  • 机器学习
  • 深度学习

【免费下载链接】leaf

Open Machine Intelligence Framework for Hackers. (GPU/CPU)

项目地址:https://gitcode.com/gh_mirrors/le/leaf
点击查看免费下载

本指南围绕 Leaf(Open Machine Intelligence Framework for Hackers,GPU/CPU)官方术语文档(doc/src/deep-learning-glossary.md)中的两个核心概念——Layer(层)与Network(网络)——展开。你将理解 Leaf 对"层"与"网络"的独特定义、二者之间的层级关系,以及"一切皆 Layer"的设计哲学如何在源码层面落地;读完可准确使用LayerConfig构建单个层,并通过容器层(Container Layer)把它们连接成可执行、可训练的神经网络。

从 Glossary 看 Leaf 的术语体系

Leaf 的术语表(Glossary)只收录了两个词条:Layer 与 Network。这不是文档偷懒,而是刻意为之——在 Leaf 中,整个框架只有 Layer 这一种构建块,Network 也不过是"由其他 Layer 组成的 Layer"。理解这两个概念,就掌握了 Leaf 设计的主线。

Layer:神经网络中的最高级构建块

通用理解(In General)

在(深度)神经网络中,Layer 是最高级别的构建块(the highest-level building block)。一个层通常是一个容器:

  • 接收加权输入;
  • 对输入进行变换;
  • 将结果作为输出传递给下一层。

一个层通常只包含一种类型的函数(如 ReLU、池化、卷积等),这样便于与网络的其他部分进行比较。网络中第一个和最后一个层分别被称为输入层(input layer)与输出层(output layer),两者之间的所有层统称为隐藏层(hidden layers)。

Leaf 中的 Layer(In Leaf)

在 Leaf 中,Layer 与上述通用理解高度一致。一个 Leaf 层同样满足三点:

  • 是最高级别的构建块;
  • 需要接收输入,可能对其进行变换,并需要返回结果;
  • 应当是单一职责的(uniform)——它只做一种类型的函数。

关键差异在于:Leaf 层可以实现的远不止神经网络相关的功能。术语文档明确写道:Leaf 层可以实现任何功能,而非仅限于 ReLU、池化、LSTM 等神经网络操作。例如Sequential层就是一个典型例子——它是一个容器层,作用是"连接多个层,从而创建一个网络"。也就是说,"连接其他层"这一行为本身,在 Leaf 中也被实现为一个 Layer。

这一点与文档 doc/src/layers.md 的论述互为印证:"Layers are the only building blocks in Leaf. As we will see later on, everything is a layer. Even when we construct networks, we are still just working with layers composed of smaller layers."(Layer 是 Leaf 中唯一的构建块,即使我们构造网络,也不过是在处理由更小的层组成的层。)

源码佐证:Layer 的通用结构与生命周期

在 src/layer.rs 中,Layer<B: IBackend>是包裹所有具体层实现的通用结构。它的核心字段包括:

字段作用
name: String标识该层,主要用于日志输出
config: Box<LayerConfig>层的配置
worker: Box<ILayer<B>>层的具体实现,负责大部分计算(forward / backward)
weights_data/weights_gradient权重及其梯度的共享引用(SharedTensor)
input_blobs_data/input_blobs_gradient输入数据的共享引用
output_blobs_data/output_blobs_gradient输出数据的共享引用
loss: Vec<f32>每个输出 blob 对网络损失的贡献权重

ILayertrait 则定义了所有层必须遵循的通用行为契约(src/layer.rs):

  • forward:计算前向输出,内部获取输入张量的读锁、输出张量的写锁,再调用compute_output;
  • backward_input:计算关于输入的梯度(反向传播),调用compute_input_gradient;
  • backward_parameters:计算关于权重/偏置等参数的梯度,调用compute_parameters_gradient;
  • 配套的生命周期钩子:init(一次性初始化,如预计算常量)、reshape(根据输入形状调整输出/权重形状)、sync(将各 blob 同步到计算设备)等。

这与术语文档对 Layer "接收输入、变换、返回结果"的定义完全对应:无论层内部做什么,只要实现了这套输入/输出契约,就可以被堆叠进网络。

源码佐证:LayerConfig 与 worker 的分工

Leaf 的层不是直接new出来的,而是通过LayerConfig描述、再由Layer::from_config(backend, &config)生成。LayerConfig(src/layer.rs)包含:

pub struct LayerConfig { pub name: String, // 层名称 pub layer_type: LayerType, // 层类型(决定 worker 的具体实现) pub outputs: Vec<String>, // 每个输出 blob 的名称 pub inputs: Vec<String>, // 每个输入 blob 的名称 pub params: Vec<WeightConfig>, // 每个权重 blob 的训练配置 pub propagate_down: Vec<bool>, // 哪些输入跳过反向传播 }

Layer::from_config会根据layer_type匹配到具体的 worker 实现,例如Linear、ReLU、Sigmoid、Sequential、Reshape、NegativeLogLikelihood等(见worker_from_config的match,src/layer.rs)。因此文档 doc/src/layers.md 中的最小示例是完全可运行的:

// 构造一个输出 500 个节点的全连接层配置 let linear_1: LayerConfig = LayerConfig::new("linear1", LinearConfig { output_size: 500 }); // 将其变成一个已初始化、可直接运行的 Layer let linear_network_with_one_layer: Layer = Layer::from_config(backend, &linear_1);

在layer.rs的worker_from_config实现中,LayerType::Linear(layer_config)会构造Linear::from_config(&layer_config),这正是该示例中 worker 字段是一个Linear实现的原因。

源码佐证:层实现示例

以 src/layers/activation/relu.rs 中的 ReLU 层为例,其注释明确定义:非线性激活函数y = max(0, x),并且它通常比 Sigmoid/Tanh 更快。ReLU 层不持有任何权重,只有 reshape 逻辑(将输入、输出、梯度调整到与输入一致)与relu_plain/relu_grad_plain的计算调用。

其他已实现层同样遵循统一的接口:

  • Linear(全连接层):y = a * x + b,输入一维直接变换、二维时第一维视为 batch size;自动创建权重 blob,并用 Glorot(Xavier)填充器初始化(src/layers/common/linear.rs);
  • Sigmoid(激活层):y = (1 + e^(-x))^(-1)(src/layers/activation/sigmoid.rs);
  • NegativeLogLikelihood(损失层):对比输出与目标值并分配损失,通常是网络的最后一层(src/layers/loss/negative_log_likelihood.rs);
  • Reshape / Flatten(工具层):调整张量形状,如把n * c * h * w展平成n * (c*h*w)(src/layers/utility/reshape.rs、src/layers/utility/flatten.rs)。

测试用例 tests/layer_specs.rs 也验证了由SequentialConfig组合 Linear + Sigmoid + Linear 可以构造出一个完整的简单网络并正常运行。

Network:由 Layer 组成的图

通用理解(In General)

网络,通常也被称为神经网络(Neural Network, NN)或人工神经网络(Artificial Neural Network, ANN),是机器学习方法的一个子集。术语文档还列举了其他机器学习方法作为对照(并非穷举):线性回归(Linear Regression)、SVM、遗传/进化算法(Genetic/Evolution Algorithms)、动态规划(dynamic programming)、确定性算法优化方法(deterministic algorithmic optimization methods)。

也就是说,在 Leaf 的语境里,"网络"并不限定于神经网络,它泛指由层构成的任何计算图。

Leaf 中的 Network(In Leaf)

在 Leaf 中,网络被定义为一个或多个层的图(graph,即一个连接的集合)。这个网络可以是人工神经网络方法,也可以是其他机器学习方法,甚至可以是任何与机器学习无关的方法。

术语文档在这里给出了一个非常关键、也最容易让人困惑的陈述:

"As described in 2. Layers a network in Leaf is actually a layer which connects other layers."(如 Layers 章节所述,Leaf 中的网络实际上是一个连接了其他层的层。)

这正是"一切皆 Layer"哲学的集中体现:Network 不是与 Layer 并列的另一种实体,而是 Layer 的一种特例(容器层)。网络由容器层(如Sequential)持有多个子层的LayerConfig,并在初始化时把它们连接起来。

源码佐证:Sequential 容器如何"造出"网络

以 src/layers/container/sequential.rs 为例,SequentialConfig只包含三个字段:

字段含义默认值
layers: Vec<LayerConfig>容器所包含的各层配置空
inputs: Vec<(String, Vec<usize>)>输入张量的名称与形状,供首层引用空
force_backward: bool是否强制所有层执行反向传播false

Sequential::init_layers的初始化流程清晰地还原了"连接成一个图"的过程:

  1. 为每个声明的输入张量创建data/gradient两个SharedTensor并注册;
  2. 把容器输入名加到第一个层上,让首层正确连接输入;
  3. 逐层连接:每层输出 blob 命名为SEQUENTIAL_{i}并作为下一层的输入 blob,从而让"上一个层的输出成为下一个层的输入";最后一层输出命名为SEQUENTIAL_OUTPUT_{i};若前一层支持原地计算(in-place),则直接复用其输出名;
  4. 逐层调用Layer::connect,通过registry(blob 注册表)把各层的输入/输出/权重 blob 关联起来;
  5. 从后往前执行init_backprop,判定哪些 blob 对 loss 有贡献、哪些可以跳过反向传播,从而为每层设置needs_backward标志;
  6. 若force_backward == true,再对所有层执行init_force_backward;
  7. 最后一个层的输出即被视为容器(整个网络)的输出。

对应到 src/layer.rs 中的Layer::connect,其职责包括:按配置连接输入 blob、追加输出 blob(支持原地计算与匿名输出 blob)、为需要权重的层创建权重与梯度张量,并调用worker.init、reshape与resize_shared_workspace完成内存分配。

因此,文档 doc/src/building-networks.md 中由SequentialConfig构建卷积网络的代码是标准用法:

let mut net_cfg = SequentialConfig::default(); net_cfg.add_input("data", &vec![batch_size, 28, 28]); net_cfg.add_layer(LayerConfig::new("reshape", ReshapeConfig::of_shape(&vec![batch_size, 1, 28, 28]))); net_cfg.add_layer(LayerConfig::new("conv", ConvolutionConfig { num_output: 20, filter_shape: vec![5], stride: vec![1], padding: vec![0] })); net_cfg.add_layer(LayerConfig::new("pooling", PoolingConfig { mode: PoolingMode::Max, filter_shape: vec![2], stride: vec![2], padding: vec![0] })); net_cfg.add_layer(LayerConfig::new("linear1", LinearConfig { output_size: 500 })); net_cfg.add_layer(LayerConfig::new("sigmoid", LayerType::Sigmoid)); net_cfg.add_layer(LayerConfig::new("linear2", LinearConfig { output_size: 10 })); net_cfg.add_layer(LayerConfig::new("log_softmax", LayerType::LogSoftmax)); // 把配置变成可运行的深度卷积网络 let mut net = Layer::from_config(backend.clone(), &net_cfg);

初始化网络(An initialized network)

术语文档对"初始化网络"给出了明确的定义:

"An initialized network is a network, which is ready to be executed, meaning it is fully constructed e.g. all necessary memory is allocated on the host or device."(已初始化的网络是指可以立即执行的网络,即它已被完整构建,例如所有必要的内存都已在主机或设备上分配。)

换句话说,"初始化"发生在Layer::from_config完成的那一刻——它不仅是把LayerConfig变成层对象,还包括connect阶段的全部动作:分配输入/输出/权重/梯度的SharedTensor、确定反向传播标志、调整张量形状、分配共享工作区(shared workspace)。这正好解释了为什么文档 doc/src/layers.md 会说"Hurray! We just constructed a network with one layer"——单个Layer::from_config返回的对象就是一个已初始化、可直接forward的"一层的网络"。

嵌套组合:网络是更大的网络的构建块

由于网络本质上仍是 Layer,Sequential自然可以作为更大网络的构建块。文档 doc/src/building-networks.md 给出了嵌套示例:先把一个包含 conv + pooling + linear + sigmoid 的conv_net作为LayerConfig加入外层net_cfg,再在外面继续追加linear与log_softmax,得到更大的网络。从源码看,这依赖于Sequential::learnable_weights等接口的扁平化收集——容器层会把内部所有子层的可学习权重、梯度与名称收集汇总(见 src/layers/container/sequential.rs 中learnable_weights()、learnable_weights_gradients()、learnable_weights_names()的flat_map实现),使外部 Solver 可以统一更新整个嵌套网络的参数。

Layer::is_using_in_place、exact_num_output_blobs、min_output_blobs、auto_weight_blobs等钩子(src/layer.rs)则让容器层与其他普通层在行为上保持一致,例如容器层通过is_container() -> true告知框架在 forward/backward 时避免加锁以防止死锁。

小结:一个概念,两种视角

  • Layer 是 Leaf 中唯一的构建块:接收输入、变换、返回输出;既可以实现神经网络算子(ReLU、Sigmoid、Linear、卷积、池化),也可以实现任意其他功能(Reshape、日志等),甚至可以是"连接其他层"这一行为本身。
  • Network 是 Layer 的图:在 Leaf 中它由一个或多个层连接而成,实际由容器层(如Sequential)承载;网络不是独立于层的实体,而是"连接了其他层的层",因此可以无限嵌套组合。
  • 初始化即就绪:已初始化的网络意味着全部内存已在主机或设备上分配完毕,可以立即执行前向与反向计算。

后续可以继续沿着两个方向深入:一是阅读 doc/src/layers.md 了解五类层的划分(Activation / Common / Loss / Utility / Container,对应源码目录 src/layers),二是阅读 doc/src/building-networks.md 学习如何用SequentialConfig组装真实网络,再配合 doc/src/solvers.md 了解网络训练与参数优化。对于需要逐模块验证的行为,tests/layer_specs.rs 与 tests/solver_specs.rs 提供了可运行的行为规范参考。

  • 机器学习
  • 深度学习

【免费下载链接】leaf

Open Machine Intelligence Framework for Hackers. (GPU/CPU)

项目地址:https://gitcode.com/gh_mirrors/le/leaf
点击查看免费下载
上一篇:MZmine 导入 RAW 文件报错?三步自查加四步修复,快速解决
下一篇:SPT-AKI Profile Editor 从入门到精通:塔科夫离线存档编辑的完整实操指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 2:10:38

MySQL数据库课程设计:机票预订系统表结构设计与并发控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/12 2:10:15

ESP32-C3高压电容放电闹钟设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/12 2:09:15

InterviewGuide 堆排序实战:从堆原理到 C++ 手撕代码与 Top K 高频考题

教程 【免费下载链接】InterviewGuide &#x1f525;&#x1f525;「InterviewGuide」是阿秀从校园->职场多年计算机自学过程的记录以及学弟学妹们计算机校招&秋招经验总结文章的汇总&#xff0c;包括但不限于C/C 、Golang、JavaScript、Vue、操作系统、数据结构、计算机…

作者头像 李华