在 PyTorch 里,torch.nn 和 torch.nn.functional 到底什么关系?

目录

一句话抓住本质区别

用法上的核心差异(配代码)

[1️⃣ 谁负责"自动创建和管理参数"?](#1️⃣ 谁负责“自动创建和管理参数”?)

[使用 nn:参数自动生成并注册](#使用 nn:参数自动生成并注册)

[使用 functional:参数你自己来](#使用 functional:参数你自己来)

[2️⃣ 谁负责"运行状态"(buffer)?](#2️⃣ 谁负责“运行状态”(buffer)?)

[BatchNorm 的区别](#BatchNorm 的区别)

[Dropout 也是一样的逻辑](#Dropout 也是一样的逻辑)

[3️⃣ 组合方式不同:谁更适合"搭结构",谁更适合"插操作"?](#3️⃣ 组合方式不同:谁更适合“搭结构”,谁更适合“插操作”?)

[用 nn 搭网络,结构一眼就懂](#用 nn 搭网络,结构一眼就懂)

[用 F,灵活度更高](#用 F,灵活度更高)

从源码角度看:它们其实是"父子关系"

[1️⃣ 大多数 nn.Module 的 forward(),内部都会调用 F](#1️⃣ 大多数 nn.Module 的 forward(),内部都会调用 F)

[2️⃣ 很多 F 本身也是"算子组合"](#2️⃣ 很多 F 本身也是“算子组合”)

实战中该怎么选?

[✅ 优先用 nn 的场景](#✅ 优先用 nn 的场景)

[✅ 更偏向用 F 的场景](#✅ 更偏向用 F 的场景)

一个非常容易踩的坑

小结一句话版


很多人刚学 PyTorch 时都会有一个疑惑:

为什么有的地方用 nn.ReLU()

有的地方却写 F.relu(x)

它们是不是重复设计了?

结论先给出来一句话版本:

torch.nntorch.nn.functional 的关系,可以理解为:
👉「带状态的积木」 vs 「纯函数工具箱」

下面我们慢慢拆开讲。


一句话抓住本质区别

在 PyTorch 里,这两者分工非常明确:

  • torch.nn

    • 提供的是 Module(模块 / 层)

    • 通常 自带参数、状态

    • 参数会被自动注册,能被 optimizer 更新

    • 能进 state_dict(),能保存 / 加载 / .to(device)

    • 非常适合 搭网络结构

  • torch.nn.functional(通常写作 F

    • 提供的是 函数式接口

    • 本身 不保存参数、不保存状态

    • 你传什么,它就算什么

    • 非常适合在 forward() 里做 临时计算

一句更直观的比喻:

  • nn:已经封装好的"层级积木"

  • F:一把"算子瑞士军刀"


用法上的核心差异(配代码)

1️⃣ 谁负责"自动创建和管理参数"?

这是最关键的差别。

使用 nn:参数自动生成并注册
python 复制代码
import torch.nn as nn

conv = nn.Conv2d(3, 16, 3)

# conv.weight / conv.bias 已经存在
# 并且会出现在 model.parameters() 里

你什么都不用操心:

  • 参数自动创建

  • optimizer 会自动更新

  • 保存模型时会进 state_dict()

使用 functional:参数你自己来
python 复制代码
import torch.nn.functional as F

y = F.conv2d(x, weight, bias, stride=1, padding=0)

这里的 weightbias

  • PyTorch 不会帮你创建

  • 也不会自动注册

  • 更不会自动更新

所以经验法则很简单:

  • 你想要一个"层" → 用 nn

  • 你只想"算一下" → 用 F


2️⃣ 谁负责"运行状态"(buffer)?

这在 BatchNorm / Dropout 上特别明显。

BatchNorm 的区别
  • nn.BatchNorm2d

    • 内部有 running_mean / running_var

    • 自动跟随 model.train() / model.eval()

    • 这些统计量会进 state_dict()

  • F.batch_norm

    • 你要自己把均值、方差传进去

    • 还要自己决定 training=True/False

Dropout 也是一样的逻辑
python 复制代码
# nn 版本
self.drop = nn.Dropout(p=0.5)

# functional 版本
x = F.dropout(x, p=0.5, training=self.training)

nn.Dropout 会自动"看懂"当前模型是在训练还是推理
F.dropout 则需要你显式告诉它

这也是为什么:

有状态的层,优先用 nn,更安全


3️⃣ 组合方式不同:谁更适合"搭结构",谁更适合"插操作"?

nn 搭网络,结构一眼就懂
python 复制代码
class M(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(128, 64)
        self.act = nn.ReLU()

    def forward(self, x):
        return self.act(self.fc(x))

这种写法:

  • 结构清晰

  • 方便调试

  • 非常适合主干网络

F,灵活度更高
python 复制代码
def forward(self, x):
    x = self.fc(x)
    x = F.relu(x)
    x = F.dropout(x, p=0.5, training=self.training)
    return x

这里你可以:

  • 临时加个操作

  • 不定义成员变量

  • 做更复杂的控制逻辑

这在 自监督、损失函数、mask 操作 里非常常见。


从源码角度看:它们其实是"父子关系"

这是很多人不知道、但非常重要的一点。

1️⃣ 大多数 nn.Moduleforward(),内部都会调用 F

也就是说:

nn.Module = 参数 / 状态管理 + 调用 functional 做计算

概念上大致是这样:

  • nn.ReLU.forward(x)

    F.relu(x, inplace=self.inplace)

  • nn.Linear.forward(x)

    F.linear(x, self.weight, self.bias)

  • nn.Conv2d.forward(x)

    F.conv2d(x, self.weight, self.bias, ...)

所以真正的分工是:

  • torch.nn

    • 管参数

    • 管状态

    • 管保存 / 加载 / 设备迁移

  • torch.nn.functional

    • 负责 真正的数值计算

再往下,F 的实现通常会继续调用:

  • aten 算子

  • 最终落到 C++ / CUDA kernel


2️⃣ 很多 F 本身也是"算子组合"

例如:

  • F.relu

    aten::relu

  • F.linear

    addmm 等底层算子

  • F.cross_entropy

    log_softmax + nll_loss(不同版本可能融合优化)

整体调用链常常是:

复制代码
nn.Module.forward
    ↓
torch.nn.functional.xxx
    ↓
aten / torch.ops
    ↓
C++ / CUDA kernel

如果你翻源码,经常能看到 forward() 里直接调用 F.xxx


实战中该怎么选?

一个非常实用的判断标准是:

✅ 优先用 nn 的场景

  • 正常搭网络结构(Conv / Linear / BN / Embedding / RNN)

  • 希望:

    • 自动收参数

    • 自动保存加载

    • 自动处理 train / eval 行为

  • 多卡训练、模型复用

这是 99% 模型代码的主流写法


✅ 更偏向用 F 的场景

  • 自定义 forward

  • 自监督 / 对比学习 / 医疗分割里的各种 loss、mask、相似度

  • 权重共享、动态权重(meta-learning、hypernetwork、LoRA)

  • 不想把一个操作"升级成层",只是临时用一下


一个非常容易踩的坑

不要用 functional 去"硬写"有状态的层,尤其是:

  • BatchNorm

  • Dropout

典型问题是:

  • 忘了传 training=self.training

  • 推理时还在 dropout

  • BN 统计行为不对

如果你不是刻意要手动控制行为

直接用 nn.BatchNorm / nn.Dropout,更稳、更不容易出 bug


小结一句话版

  • torch.nn:负责结构、参数、状态

  • torch.nn.functional:负责计算本身

  • 多数情况下:nn 外壳 + F 内核

理解了这一点,你在读源码、写自定义模型、调试诡异训练行为时,思路会清晰很多。

相关推荐
是小蟹呀^7 小时前
从稀疏到自适应:人脸识别中稀疏表示的核心演进
人工智能·分类
云边有个稻草人7 小时前
CANN ops-nn:筑牢AIGC的神经网络算子算力底座
人工智能·神经网络·aigc·cann
island13147 小时前
CANN Catlass 算子模板库深度解析:高性能 GEMM 架构、模板元编程与融合算子的显存管理策略
人工智能·神经网络·架构·智能路由器
结局无敌7 小时前
从算子到生态:cann/ops-nn 如何编织一张高性能AI的协作之网
人工智能
chaser&upper7 小时前
击穿长文本极限:在 AtomGit 破译 CANN ops-nn 的注意力加速密码
人工智能·深度学习·神经网络
玄同7657 小时前
Python 后端三剑客:FastAPI/Flask/Django 对比与 LLM 开发选型指南
人工智能·python·机器学习·自然语言处理·django·flask·fastapi
慢半拍iii7 小时前
ops-nn算子库深度解析:昇腾神经网络计算的基础
人工智能·深度学习·神经网络·ai·cann
程序员猫哥_7 小时前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html
哈__7 小时前
CANN优化Diffusion扩散模型推理:去噪过程与采样策略加速
人工智能
永远都不秃头的程序员(互关)7 小时前
CANN DVPP赋能AIGC:硬件加速视觉处理,打造极致生成式视觉工作流
人工智能·aigc