想象你是一位主厨,准备开一家主打创新菜的餐厅。后厨是全新装修的——地板、水电都已就绪,但灶台、烤箱、料理机还都没着落。你面临一个关键选择:是买一套功能齐全但每个零件都笨重的“集成灶”,还是搭一套每个设备性能顶级、还能随意拼装的“模块化厨房”?Julia语言与Flux.jl框架,就是后者的代表。 Julia是一门速度接近C语言、写起来却像Python一样轻松的编程语言;Flux.jl则是这个生态里最顺手的深度学习框架。它最大的魅力在于“所见即所得”:你写的代码就是网络结构本身,没有厚重的抽象封装。构建模型、定义损失、训练循环、迁移GPU,每一步都透明可见,特别适合搞科研实验、探索新算法、以及想彻底搞懂“深度学习”到底在做什么的开发者。 我们将用一套完整的“厨房流水线”来贯穿全文:先认识厨房布局(框架概览),再架设加工设备(模型构建与层)、制定评分标准(损失函数)、学习调火候(优化器)、反复试菜(训练循环),最后装上自动化传感器(回调与早停)和大火力灶(GPU加速)。一路下来,你不仅会写Flux.jl,还能理解每一个环节背后的直觉。
学习Flux.jl能让你在Julia生态中走通从模型构建、训练调优到GPU加速的完整深度学习流程,尤其适合科研实验、新算法验证和高性能计算场景,为AI从业者提供一个轻量而强大的技术选项。
目录
可视化演示
展示Flux.jl模型层定义、损失函数与优化器配置、训练循环与回调机制,切换模式掌握Julia深度学习框架核心原理。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Flux.jl模型构建与层定义 Chain组合Dense层构成前馈网络,激活函数(relu/sigmoid)作为独立层,参数自动初始化 自定义层通过struct定义参数,@functor宏标记可训练参数,梯度通过Zygote自动微分计算 Chain(Dense(in,act), Dense(out,act), ...) Flux.jl以Julia函数式风格定义模型,简洁表达力强
1. 框架速览:走进Flux的厨房
你准备开一家主打创新菜的餐厅,后厨是全新装修的,但设备还没买。是买一套功能齐全却笨重的集成灶,还是搭一套每个设备性能顶尖、还能自由拼装的模块化厨房?Julia语言与Flux.jl就是后者的代表。Julia是一门年轻的编程语言,它既能像脚本语言一样写起来流畅,又通过即时编译技术让运行速度逼近C语言。对深度学习而言,这意味着同样的实验迭代,你能跑更多轮、试更多想法。
Flux.jl是这个生态中最受欢迎的深度学习框架。它的设计哲学很朴素:模型就是函数,层就是带参数的结构体,一切都是数组运算。你不需要学一套复杂的“模型配置文件语法”,因为代码本身就是网络结构。安装也很简单,在Julia环境中执行 using Pkg; Pkg.add("Flux") 即可。随后一行代码就能定义一个像模像样的小模型:
这段代码定义了一个输入784维、中间64维、输出10维的两层网络。那为什么网络总喜欢搭成“链条”?因为最基本的神经网络,就是一系列函数依次作用的结果:
这段链条就像餐厅后厨的动线:食材从入口进,经过一道道工位变成菜品。Flux的 Chain 做的就是这件事——把工位依次串联,前一个的输出自动喂给后一个。好的布局是出菜快的基础,而下一步是架设具体的工位设备。
- 原始输入$x$ 是进入网络的数据,比如一张28×28手写数字图片被展平成784维向量。它是流水线入口处的“生食材”。
- 首层变换$f_1$ 是第一个加工工位,比如一个全连接层,负责对原始像素做初步线性变换与激活。
- 中间工序$f_2, \ldots, f_{L-1}$ 是中间加工步骤,每一层都在前一层基础上提炼更有用的特征。
- 输出层收尾$f_L$ 是最后一道工序,把特征转成最终输出,比如分类概率或回归数值。
model = Chain(Dense(784, 64, relu), Dense(64, 10))2. 模型构建:架起后厨流水线
确定动线后,要选购设备并按顺序安装。在Flux中,这个“安装”动作由 Chain 完成:它接收一组层对象,依次调用它们。这就像把洗菜台、切配台、灶台、装盘区一字排开,食材在传动带上自动传递。最常见的基础工位是 Dense,也就是全连接层。它的工作极简单:对输入做一次线性变换,再加一个偏置。假如输入为向量 $x$,输出维度为 $m$,则:
这里的 $W$ 是权重矩阵,形状为 $m \times n$($n$ 是输入维度),$b$ 是长度为 $m$ 的偏置向量。权重决定“每条输入通道对每条输出通道的影响强度”,偏置则相当于工位的“初始偏移量”——即使输入全为零,输出也可以不为零。
定义好模型后,常需要访问或修改参数。Flux.params(model) 可以收集模型所有可训练参数,梯度更新与参数可视化都靠它。打印 model 时,Flux会清晰列出每层结构和参数量。至此,流水线设备齐了,但每个工位还需要“加工功能”——这引出了层与激活函数。
- 输入数据$x$ 是进入本层的数据向量,长度为 $n$。可能是上一层的输出,也可能是网络的原始特征。
- 权重矩阵$W$ 存储本工位的“加工参数”,决定每个输入分量如何加权组合得到输出。训练要调整的核心就是它。
- 偏置向量$b$ 是一个平移量,让层在没有输入信号时也能给出初始输出,增加模型表达能力。
- 线性组合$Wx + b$ 先做矩阵乘法,再逐元素加偏置。在Julia中写作
W * x .+ b。
3. 层与激活:给工位配齐加工功能
一条流水线不能只有切菜机。Flux拥有一系列常用层:Conv 卷积层擅长提取图像局部特征,就像用模具在面团上压出统一花纹;Dropout 在训练时随机“失活”部分神经元,防止模型过度依赖某些特征,好比偶尔让某位帮厨休息,避免菜品口味被单一做法绑架。创建层时还能自定义初始化方式,如 Flux.glorot_uniform、Flux.zeros,相当于为不同工序设置不同的初始刀具锋利度。
但只有线性变换是不够的:再多线性层叠加,数学上仍等价于单层。为了让网络具备拟合复杂模式的能力,每层之后要加入激活函数——非线性的加工手法。最常用的是ReLU:
它把所有负数归零、正数保持不变。在厨房比喻中,这相当于“把不符合标准的食材直接丢弃,留下合格的部分”,简单高效。
除了ReLU,Flux还内置 tanh、sigmoid、softmax 等激活,分别适合回归、二分类、多分类等任务。选对激活函数就像选对火候:蒸、炒、烤各有适用食材。设备齐全、工序清楚之后,下一步需要一套“评分标准”来判断出品好坏——这就是损失函数。
- 输入值$x$ 是激活函数的输入,通常是上一层线性变换的输出。
- 取最大值$\max(0, x)$ 比较 $x$ 与 $0$,哪个大取哪个。负数一律归零。
- 输出范围ReLU的输出域是 $[0, +\infty)$,不含负值,这让后续的梯度传播更稳定。
4. 损失与优化:试菜与调火候
主厨得能评价一道菜好不好。在深度学习中,这个评价者叫损失函数,它衡量模型预测与真实答案的差距。最常用的回归损失是MSE(均方误差):
其中 $y_i$ 是真实值,$\hat{y}_i$ 是预测值。差值平方意味着“偏得太远”的预测会被更重地惩罚——就像试菜时,咸了0.5克可以忍,咸了2克整道菜就毁了。
有了评分标准,大厨就要开始调整火候,这由优化器完成。最基础的SGD(随机梯度下降)让参数沿梯度反方向移动;而实际工作中最常用的Adam则像一位经验丰富的大厨,会根据历史梯度的震荡幅度自动调节每一步大小:陡峭处不冒进,平坦处不拖延。在Flux中使用只需一行:opt = Adam(0.001)。
- 真实答案$y_i$ 是第 $i$ 个样本的真实标签,比如标准味道指标。
- 模型预测$\hat{y}_i$ 是模型当前给出的预测值,相当于这道菜现在的实际味道。
- 误差平方$(y_i - \hat{y}_i)^2$ 放大误差:偏差0.5时损失0.25,偏差2时损失4,差得越远罚得越重。
- 全体平均$\frac{1}{n}\sum_{i=1}^{n}$ 对所有 $n$ 个样本的误差取平均,得到标量损失,便于跨批次比较。
5. 训练循环:反复试菜直到满意
深度学习的“学习”本质就是反复执行:喂数据(做菜)→ 算损失(试菜)→ 算梯度(找调整方向)→ 更新参数(动刀调整)。Flux提供了轻量的 Flux.train! 封装这个循环,也允许你完全手写。手写骨架非常直白,就像主厨亲自盯着每道工序:
每一次迭代的参数更新在数学上可以概括为:
它表示从第 $t$ 轮参数出发,沿着损失下降最快的方向走一步,到达第 $t+1$ 轮。
每轮(epoch)结束后,通常用验证集评估模型,输出损失、准确率等指标,判断训练进展。就这样一轮轮尝、一次次调,直到损失收敛到满意。但一直手动盯着太累了——我们需要一些“智能传感器”来自动监控,这就是自定义层、回调与早停。
- 当前参数$\theta_t$ 是第 $t$ 轮迭代时的参数状态,好比当下炉灶温度旋钮的位置。
- 本轮梯度$\nabla_\theta \mathcal{L}_t$ 是在当前参数下、基于本批次数据算出的梯度,是本批次数据对“该往哪个方向调整”的建议。
- 更新步长$\eta \cdot \nabla_\theta \mathcal{L}_t$ 用学习率缩放梯度,得到这一步实际移动的距离。
- 参数滚动$\theta_{t+1} = \theta_t - (\cdots)$ 完成一次参数滚动,模型进入下一轮迭代。
for epoch in 1:epochs for (x, y) in data grad = gradient(() -> loss(model(x), y), params) Flux.update!(opt, params, grad) endend
6. 高级定制:自创设备与外挂助手
标准层能满足大部分需求,但科研与工业场景常需要特殊工序。Flux允许你用Julia的结构体加 @functor 宏轻松定义自定义层。假如你需要一个带可学习参数“缩放因子 $a$”的层:
这个层就像你自己发明的“自定义调味机”——只有一个旋钮(参数 $a$),能把所有食材统一放大或缩小。这种扩展能力让Flux在复现论文新模块时格外顺手,几十行Julia就能实现一个全新结构。
训练过程中,回调机制像一个传感器网络:每隔若干轮自动记录损失、调整学习率,或是在验证损失不再下降时触发“早停”。早停防止过拟合的思路很直观:当验证集损失连续 $p$ 轮不创新低,主厨就知道“再煮要糊了”,该立刻关火:
训练完成后,可借助 BSON.jl 把模型参数保存到磁盘,下次直接加载,就像把秘制酱料封进罐子随时开封使用。自动化到位了,最后要面对最现实的问题:食材处理速度与厨房火力。
- 验证损失序列$L_{\text{val}}^{(s)}$ 表示第 $s$ 轮在验证集上的损失,是每隔一阵试菜打的分数。
- 观察窗口$[t-p+1, t]$ 是最近 $p$ 轮的区间。主厨只看最近这些记录,而不是远古记忆。
- 历史基准$L_{\text{val}}^{(t-p)}$ 是窗口起点前的损失,代表“曾经达到的好成绩”。$\epsilon$ 是对好成绩的容忍浮动。
- 早停判定若最近 $p$ 轮里最好的分数都不如历史基准,说明继续练不再有益,应停机。
struct ScaleLayer aendFlux.@functor ScaleLayer(l::ScaleLayer)(x) = l.a .* x
7. 数据与性能:备菜提速与加大火力
高效厨房不只做菜快,备菜也不能拖后腿。Flux的 DataLoader 负责把数据集分批、打乱顺序地喂给模型,就像备菜流水线把食材按份分装、以合适节奏递到灶台:
当数据规模变大,CPU(家用小灶)不够用了,就要请出GPU(饭店专业大火灶)。Flux与CUDA生态无缝衔接:gpu(model) 一行即可把模型迁到显卡,数据同样用 gpu(data) 迁过去;训练结束后用 cpu(model) 回到普通环境做推理。GPU带来的提速效果可以用加速比量化:
例如某模型在CPU上训练一轮要120秒,GPU上只要6秒,加速比就是20,意味着同样时间能做20次实验——对调参、搜超参、跑论文实验是质的飞跃。推荐实践是:先用小数据子集在CPU上验证模型能跑通,再迁移GPU训练,最后用基准测试记录性能。至此,从厨房布局、设备选购、评分标准、火候调整到自动化监控和火力升级,一套完整的Flux.jl深度学习流水线就搭建完成了。
- CPU训练耗时$T_{\text{CPU}}$ 是在纯CPU环境完成一轮训练所需的时间。家用小灶慢慢炖。
- GPU训练耗时$T_{\text{GPU}}$ 是GPU加速下完成同样工作的时间。专业炉灶大火猛攻。
- 加速比$S$ 是两个耗时的比值,大于1表示GPU更快,它衡量从普通计算环境切到加速环境后的性能提升倍数。
train_loader = DataLoader((x, y), batchsize = 64, shuffle = true)AI 应用场景
科研快速原型:物理信息神经网络
阶段:模型构建与迭代实验
解决的问题:科研人员在验证新算法时,传统做法需要写大量样板代码,或受限于重型框架的抽象设计。Flux.jl的轻量特性让新结构能快速落地,几十行代码即可完成从构想到实验的闭环,且Julia的编译速度让反复实验的成本更低。
具体案例:一位计算物理学家用Flux.jl搭建物理信息神经网络(PINN),把偏微分方程残差直接并入损失函数,在GPU上快速求解复杂流体方程。借助自定义层表达方程中的微分算符,再用早停回调控制训练轮数,整个原型一天内跑通。
医疗影像分割:数据加载与性能优化
阶段:数据加载与GPU迁移
解决的问题:医疗影像数据量大、标注成本高,训练U-Net等分割模型时,数据加载与预处理常成为瓶颈。Flux的 DataLoader 对批次、打乱的封装,搭配 gpu() 一键迁移,大幅提升迭代效率,让团队把时间花在模型设计而非工程琐事上。
具体案例:某研究团队用Flux.jl搭建U-Net训练肺结节分割模型。他们在回调中记录每轮的Dice系数,当验证指标连续10轮不提升时触发早停,最终在保持精度的前提下将训练时间缩短约60%。
金融时序预测:自定义模型与训练控制
阶段:自定义层与回调控制
解决的问题:金融数据强时序、噪声大,标准层难以直接建模。Flux允许用 @functor 自定义带注意力机制的时序层,并通过回调动态调整学习率,让模型适应不同市场环境,避免极端行情下的剧烈震荡。
具体案例:量化团队用Flux.jl构建含自注意力层的LSTM预测次日收益方向。他们定义一个学习率调度回调:当市场波动率上升时自动降低学习率,模型在回测中相比固定学习率方案实现了更稳定的超额收益。
Python 演示
import numpy as np class Dense: def __init__(self, in_dim, out_dim): self.W = np.random.randn(in_dim, out_dim) * 0.1 self.b = np.zeros(out_dim) self.x = None self.gW = None self.gb = None def forward(self, x): self.x = x return x @ self.W + self.b def backward(self, grad): self.gW = self.x.T @ grad self.gb = grad.sum(axis=0) return grad @ self.W.T class MLP: def __init__(self): self.fc1 = Dense(1, 16) self.fc2 = Dense(16, 1) def forward(self, x): self.h = np.maximum(self.fc1.forward(x), 0.0) return self.fc2.forward(self.h) def backward(self, grad): grad = self.fc2.backward(grad) grad = grad * (self.h > 0.0) return self.fc1.backward(grad) def mse_loss(pred, target): return np.mean((pred - target) ** 2) def main(): x = np.linspace(-2, 2, 100).reshape(-1, 1) y = 0.1 * x ** 2 + 0.3 * x + 0.2 model = MLP() lr = 0.01 for epoch in range(300): pred = model.forward(x) loss = mse_loss(pred, y) grad = 2 * (pred - y) / len(x) model.backward(grad) for layer in (model.fc1, model.fc2): layer.W -= lr * layer.gW layer.b -= lr * layer.gb if epoch % 30 == 0: print(f"epoch {epoch:3d} loss {loss:.6f}") if __name__ == "__main__": main()
self.x = x缓存前向输入,反向传播算梯度时要用它,相当于Flux在Chain中自动保存的中间结果。return x @ self.W + self.b矩阵乘法加偏置,正是正文中的 $y = Wx + b$。grad @ self.W.T把梯度传回上一层,这就是链式法则在代码里的体现。np.maximum(self.fc1.forward(x), 0.0)对应ReLU激活:负数归零,正数保留。layer.W -= lr * layer.gW最朴素的梯度下降更新,对应 $\theta \leftarrow \theta - \eta \nabla_\theta \mathcal{L}$。
常见误区
误区“Flux.jl是不是和PyTorch一样,是Python里的一个包?那我不学Python是不是就用不了?”
正解Flux.jl是纯Julia生态的原生深度学习框架,不依赖Python解释器。它和PyTorch只是“长得像”——都提供张量运算与自动微分,但Flux构建在Julia的 Zygote.jl 自动微分引擎之上,代码风格遵循Julia的函数式习惯。这就好比“仿古瓷砖”和“真古砖窑烧的砖”,外表相似,底子完全不同。
为何常见:主流深度学习框架如PyTorch、TensorFlow都以Python为入口,初学者自然会把任何“深度学习框架”都默认归入Python阵营,忽略了Julia独立生态的存在。
误区“模型训练到一半,我想把某一层权重手动改成特定值,直接 `model.W = ...` 就行吧?”
正解在Flux中,模型的参数由 Flux.params(model) 统一管理。训练过程中,优化器持有这些参数的引用并负责更新;若绕开优化器直接给 model.W 赋值,训练日志里参数状态可能与实际值不一致,甚至后续更新会把你的手动修改覆盖掉。正确做法是通过 Flux.update! 或调整梯度来改变参数,就像改菜谱要先通知后厨统一改配方,而不是偷偷换掉某个调料瓶。
为何常见:在简单脚本中,直接改Python对象属性很常见,习惯被带了进来;但Flux的优化器与参数引用关系是“管理式”的,手动改属性绕过了这套管理机制。
误区“Flux提供 `Flux.train!`,那我是不是只能用它来训练模型?想自定义训练细节是不是得造轮子?”
正解Flux.train! 只是Flux提供的一个便捷封装,Flux社区非常鼓励手写训练循环。你完全可以自己编写 for 循环、调用 gradient 与 Flux.update!,甚至自定义损失更复杂的训练流程。手写循环就像主厨亲自掌勺,调料、火候全在自己手里;Flux.train! 则像自动炒菜机,约定好流程就能跑,但想加私房步骤就受限。
为何常见:框架自带的“高级API”容易让人误以为它是唯一途径;实际上Flux的设计哲学就是“层层可拆,处处可改”。
误区“`DataLoader((x, y), batchsize = 64)` 是不是把64个样本一次性全部丢进模型?batchsize越大越好?”
正解batchsize = 64 表示每个批次包含64个样本,DataLoader 会把全部数据切成若干个64样本的小批,逐个批次送入训练;一次性把所有数据都喂进去的做法叫全批量梯度下降,通常内存压力大且收敛不稳定。batchsize并非越大越好,过大会让梯度更平滑但更吃显存,过小则梯度噪声大,需要像调火候一样找平衡点。
为何常见:字面上“batch”容易让人以为是“一大包全倒进去”,忽略了它“按批切分、逐批处理”的本质。
误区“模型调用 `gpu(model)` 之后就万事大吉,训练数据自动就在GPU上了吧?”
正解gpu(model) 只把模型参数迁移到GPU。数据、标签若仍留在CPU内存,训练时会在每一次前向/反向传播中触发隐式拷贝,不仅慢,甚至可能报设备不一致错误。正确做法是把数据也显式迁移:gpu(data),训练结束后再用 cpu(model) 迁回CPU做推理。就像换了大火灶,也得把锅和食材都端到新灶台上,光把菜谱拿过去没用。
为何常见:gpu() 这个函数名太像“一键加速全部”,让人误以为它会自动处理所有张量;实际上它只负责模型参数这一部分。
面试题
Q1. 在Flux.jl中,要将多个层串联成一个模型,最常用的容器结构是?
- A.Chain← 正确答案
- B.Tuple
- C.Vector
- D.Dict
答案:A。Chain是Flux的核心容器,按顺序串联多个层,前一层输出自动作为后一层输入,就像厨房里依次排列的加工工位。Tuple、Vector、Dict虽然也能装对象,但它们不提供层与层之间的自动数据传递机制。
Q2. Dense层对输入 x 进行线性变换,数学表达式是?
- A.y = W ⋅ x + b← 正确答案
- B.y = φ(Wx + b)
- C.y = Wx ⊙ b
- D.y = ReLU(Wx)
答案:A。Dense层的核心是线性变换 $y = Wx + b$,权重矩阵W与输入x做矩阵乘法,再加上偏置b。B和D都在线性变换基础上加了激活函数,属于“加了非线性的Dense”;C的逐元素乘与Dense的矩阵乘法完全不同。
Q3. 下列哪个不是激活函数?
- A.ReLU 激活
- B.tanh 激活
- C.Sigmoid 激活
- D.Dropout 操作← 正确答案
答案:D。ReLU、tanh、Sigmoid都是给网络引入非线性的激活函数;Dropout是正则化手段,训练时随机失活部分神经元以缓解过拟合,它不负责非线性变换,更像厨房里“让帮厨轮流休息”的管理措施。
Q4. 在Flux.jl中,Flux.params(model) 的主要作用是?
- A.获取模型的可训练参数集合← 正确答案
- B.为模型添加新的网络层
- C.计算损失函数的梯度值
- D.将模型参数保存到磁盘
答案:A。Flux.params 遍历模型并收集所有标记为可训练的参数(如W、b),供优化器更新和自动微分使用。它不做网络层添加、不计算梯度、不负责持久化。参数集合之于优化器,相当于配料清单之于大厨——清单列得清,火候才调得准。
Q5. 在梯度下降中,学习率设置过大会导致什么后果?
- A.训练损失发散导致震荡← 正确答案
- B.模型训练速度不断加快
- C.损失函数值自动归零
- D.模型参数数量变少
答案:A。学习率决定每一步参数移动的幅度。步子太大,可能直接跨过最低点、在谷底两侧反复震荡甚至发散,就像炒菜时调料一下倒太多,整锅菜反而没法吃。B和C是对学习率作用的误解,D与学习率无关。
Q6. 下列关于Flux.jl训练循环的说法,正确的有?
- A.可调用Flux.train!自动更新← 正确答案
- B.可手写梯度手动更新← 正确答案
- C.必须使用GPU才可训练
- D.优化器在参数opt中传入← 正确答案
答案:ABD。Flux.train! 是便捷封装,自动执行“计算梯度→更新参数”;Flux也鼓励手写循环,通过 gradient 与 Flux.update! 实现完全控制。GPU是可选加速手段,并非训练必需。优化器确实通过 train! 的 opt 参数传入,这对应大厨手里的“调味策略”。
Q7. Adam优化器的核心特点是?
- A.每个参数统一固定步长
- B.根据历史梯度自适应步长← 正确答案
- C.每次只更新一个参数
- D.完全不需要计算梯度
答案:B。Adam维护了梯度的一阶矩与二阶矩估计,为每个参数自适应调整更新幅度:梯度大且持续同向时走得快,梯度震荡时走得稳。这就像有经验的大厨根据菜的状态动态调火候,而不是全程定死一个火力等级。A是朴素SGD的简化视图,C、D更是不符合Adam机制。
Q8. 在Flux.jl中将训练迁移到GPU,正确的做法有?
- A.gpu(model)迁移模型← 正确答案
- B.gpu(data)迁移数据← 正确答案
- C.无需安装CUDA库
- D.cpu(model)迁回CPU← 正确答案
答案:ABD。模型和数据都要显式迁移到GPU,才能避免隐式拷贝的性能损耗;训练结束后用 cpu(model) 迁回CPU方便部署。GPU加速依赖CUDA生态,需要安装相关驱动与库。好比换大火灶,锅和食材都得端过去,还要确认燃气管道(CUDA)是通的。
Q9. 过拟合在验证损失上的典型表现是?
- A.训练和验证损失同时上升
- B.训练降但验证损失不降
- C.训练降且验证损失上升← 正确答案
- D.训练和验证损失均为零
答案:C。过拟合意味着模型把训练集的细节背了下来,却对验证集失去泛化能力,表现为训练损失继续下降、验证损失反而上升——就像厨师只记得某几位熟客的口味,换了新客人就不会做菜了。B属于欠拟合或正常收敛的边界情况,A通常是学习率问题。
Q10. 关于Flux.jl的自定义层,正确的理解有?
- A.需继承特定基类
- B.@functor标记识别参数← 正确答案
- C.前向逻辑普通函数实现← 正确答案
- D.不能包含任何标准层
答案:BC。Flux的自定义层不要求继承特定基类,只需定义一个结构体并用 @functor 标记,Flux就能识别其中的可训练参数;前向逻辑就是普通的Julia函数(如 (l::MyLayer)(x) = ...),非常灵活。自定义层完全可以内部组合标准层,这是Flux“积木式扩展”的体现。