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

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

相关推荐
予枫的编程笔记几秒前
Elasticsearch深度搜索与查询DSL实战:精准定位数据的核心技法
java·大数据·人工智能·elasticsearch·搜索引擎·全文检索
小北方城市网几秒前
第 6 课:云原生架构终极落地|K8s 全栈编排与高可用架构设计实战
大数据·人工智能·python·云原生·架构·kubernetes·geo
创作者mateo2 分钟前
机器学习基本概念简介(全)
人工智能·机器学习
飞睿科技4 分钟前
乐鑫ESP32-S3-BOX-3,面向AIoT与边缘智能的新一代开发套件
人工智能·嵌入式硬件·esp32·智能家居·乐鑫科技
智航GIS4 分钟前
10.1 网站防爬与伪装策略
python
Rabbit_QL6 分钟前
【数学基础】机器学习中的抽样:你的数据是样本,不是世界
人工智能·机器学习
belldeep11 分钟前
python:pyTorch 入门教程
pytorch·python·ai·torch
金融RPA机器人丨实在智能11 分钟前
深度拆解 RPA 机器人:定义、应用、价值与未来方向
人工智能·rpa·实在rpa
青主创享阁12 分钟前
技术破局农业利润困局:玄晶引擎AI数字化解决方案的架构设计与落地实践
大数据·人工智能
YJlio13 分钟前
Registry Usage (RU) 学习笔记(15.5):注册表内存占用体检与 Hive 体量分析
服务器·windows·笔记·python·学习·tcp/ip·django