在 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 内核

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

相关推荐
Elastic 中国社区官方博客10 分钟前
使用 Discord 和 Elastic Agent Builder A2A 构建游戏社区支持机器人
人工智能·elasticsearch·游戏·搜索引擎·ai·机器人·全文检索
2301_8223827611 分钟前
Python上下文管理器(with语句)的原理与实践
jvm·数据库·python
喵手28 分钟前
Python爬虫实战:从零搭建字体库爬虫 - requests+lxml 实战采集字体网字体信息数据(附 CSV 导出)!
爬虫·python·爬虫实战·零基础python爬虫教学·csv导出·采集字体库数据·字体库字体信息采集
2501_933329551 小时前
企业级AI舆情中台架构实践:Infoseek系统如何实现亿级数据实时监测与智能处置?
人工智能·架构
阿杰学AI1 小时前
AI核心知识70——大语言模型之Context Engineering(简洁且通俗易懂版)
人工智能·ai·语言模型·自然语言处理·aigc·数据处理·上下文工程
赛博鲁迅1 小时前
物理AI元年:AI走出屏幕进入现实,88API为机器人装上“最强大脑“
人工智能·机器人
2301_790300961 小时前
Python深度学习入门:TensorFlow 2.0/Keras实战
jvm·数据库·python
管牛牛1 小时前
图像的卷积操作
人工智能·深度学习·计算机视觉
云卓SKYDROID2 小时前
无人机航线辅助模块技术解析
人工智能·无人机·高科技·云卓科技