Pytorch nn AND nn.Functional
🚀 PyTorch API 技术指南:从核心构建到高级操作原理与实践
你好,PyTorch 探索者!无论你是刚入门的新手还是寻求进阶的开发者,掌握 PyTorch 丰富的 API 都是提升你模型构建能力的关键。
本指南将一些强大而常用的 PyTorch API 分旨在为五大类,从模型的基础骨架到高效的数据操作,为你开发者提供一份清晰、易懂关于 PyTorch 核心 API 的技术参考。让我们开始吧!内容涵盖了从模型架构定义到高级张量操作的常用接口,并对每个 API 的基本原理和标准用法进行了解析,辅以简明的代码示例。
第一部分:核心模型架构 (torch.nn
)
torch.nn
命名空间提供了构建神经网络所需的核心构建所有基础模块 🏗️
。这些模块是构建任何面向对象的设计,封装了可学习的参数和相应的计算逻辑。
1.1 nn.Module
: 所有神经网络模型块的基石。
1. nn.Module
:万物之基类
nn.Module
原理: nn.Module
是 PyTorch 中所有神经网络模块的父类。当你型构建的核心。任何自定义的网络层或完整的模型或层时,都应该继承此类。它提供了参数跟踪、子模块注册、设备转移 (.to(device)
)、模式切换 (.train()
/.eval()
) 等一系列基础功能。
核心功能:
自动参数跟踪: 任何被定义为nn.Parameter的属性都会被自动添加到模型的参数列表中。子模块管理: 它可以像容器一样持有其他nn.Module,形成一个嵌套的树状结构。便捷功能: 提供了如.to(device)(移动模型到 CPU/GPU)、.train()(设为训练模式)、.eval()(设为评估模式)等实用方法。
用法示例:
import torch
import torch.nn as nn
# 创建一个简单的自定义模型
class MySimpleNet(CustomModel(nn.Module):
def __init__(self, input_size,in_features, output_size)out_features):
super(MySimpleNet,CustomModel, self).__init__()
# 将 nn.Linear 层作为子模块
self.linearlayer = nn.Linear(input_size,in_features, output_size)out_features)
def forward(self, x):
return self.linear(layer(x)
# 实例化模型
model = MySimpleNet(input_size=10,CustomModel(128, output_size=2)10)
print(model)
# MySimpleNet(输出:
# CustomModel(
# (linear)layer): Linear(in_features=10,128, out_features=2,10, bias=True)
# )
2.1.2 nn.Parameter
:模型的: 可学习参数的封装
原理: nn.Parameter
是 torch.Tensor
的一个特殊包装类。当它一个 nn.Parameter
对象被赋值为 nn.Module
的属性时,它就会自动被注册为模型的可学习参数,。这意味着在反向传播后,优化器在 optimizer.step() 时会自动计算并更新它其梯度。
核心功能:
向nn.Module注册一个需要计算梯度的张量。可以通过model.parameters()访问到。
用法示例:
class MyCustomLayer(CustomLayer(nn.Module):
def __init__(self, input_features, output_features)size):
super(MyCustomLayer,CustomLayer, self).__init__()
# 手动定义一个可学习的权重矩阵缩放因子
self.weightscale = nn.Parameter(torch.randn(output_features, input_features))
# 也可以定义一个偏置
self.bias = nn.Parameter(torch.zeros(output_features)ones(size))
def forward(self, x):
return x * self.scale
# 使用自定义访问参数进行操作
return F.linear(x, self.weight, self.bias)
layer = MyCustomLayer(5, 3)
# 打印所有可学习参数CustomLayer(5)
for name, param in layer.named_parameters(parameters():
print(f"Name: {name},Parameter Shape: {param.shape}, Requires Grad: {param.requires_grad}")
1.3 第二部分:张量初始化:播下成功的种子nn.Linear
: 🌱
全连接层
nn.Linear
: 正确的初始化是模型成功训练的第原理: nn.Linear
对输入数据应用一步个线性变换:$y = xA^T + b$。它内部维护了权重矩阵 A
和偏置向量 b
作为可学习参数。
1. torch.empty()
& torch.zeros()
:创建张量画布
torch.empty(*size): 创建一个指定形状的张量,但不进行任何初始化。它的值是分配内存时存在的任意值,因此速度很快,但使用前必须填充数据。torch.zeros(*size): 创建一个指定形状且所有元素都为 0 的张量。
用法示例:
# 创建一个未初始化的输入: 2x3(Batch 张量Size, uninitialized_tensorin_features)
input_tensor = torch.empty(2,randn(64, 3)128)
linear_layer = nn.Linear(in_features=128, out_features=10)
output = linear_layer(input_tensor)
print(f"Output shape: {output.shape}"Empty Tensor:\n", uninitialized_tensor)) # 创建一个全为torch.Size([64, 0 的 2x3 张量
zero_tensor = torch.zeros(2, 3)
print("Zero Tensor:\n", zero_tensor)10])
2.1.4 torch.nn.init.kaiming_uniform_Conv2d
:智能的权重初始化: 二维卷积层
这是一种专门为使用 ReLU 及其变体激活函数的网络设计的权重初始化方法(也称 He 初始化)。它可以帮助缓解梯度消失或爆炸问题。函数名末尾的下划线原理: _nn.Conv2d表示它是一个 in-place 操作,会直接修改在输入的张二维信号(如图像)上应用二维卷积操作。它通过滑动一个可学习的卷积核(kernel)来提取局部特征。关键参数包括 in_channels
(输入通道数)、out_channels
(输出通道数,即卷积核数量)、kernel_size
(卷积核尺寸)、stride
和 padding
。
用法示例:
import torch.nn.init as init
import math
# 首先创建一个权重张量输入: weight(Batch, Channels, Height, Width)
input_image = torch.empty(randn(16, 3, 5)224, 224)
conv_layer = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1)
feature_maps = conv_layer(input_image)
print(f"Feature map shape: {feature_maps.shape}") # torch.Size([16, 32, 224, 224])
1.5 nn.Sequential
: 顺序容器
原理: nn.Sequential
是一个模块容器,它会按照模块被传入构造函数的顺序,依次将输入数据传递给每个模块。这是一种快速构建简单线性堆叠模型的便捷方式。
示例:
model = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
input_data = torch.randn(64, 784)
output = model(input_data)
print(f"Sequential model output shape: {output.shape}") # torch.Size([64, 10])
1.6 nn.Dropout
: Dropout 正则化
原理: Dropout 是一种在训练期间使用的正则化技术。它会以指定的概率 Kaimingp
均匀将输入张量中的部分布元素随机置为零,其余元素则按 1/(1-p)
的比例进行缩放,以保持整体期望值不变。这能有效防止神经元之间的共适应,降低过拟合风险。在评估模式 in-place(.eval()
)下,nn.Dropout
初始化不会执行任何操作。
示例:
dropout_layer = nn.Dropout(p=0.5)
input_tensor = torch.ones(1, 10)
dropout_layer.train() # 切换到训练模式
output_train = dropout_layer(input_tensor)
print(f"Train mode output (example): {output_train}"Kaiming) Initialized# Weight:\n",约一半元素为0,其余为2.0
weight)dropout_layer.eval() # 切换到评估模式
output_eval = dropout_layer(input_tensor)
print(f"Eval mode output: {output_eval}") # 所有元素仍为1.0
第三二部分:常用神经网络函数式接口 🧠(torch.nn.functional
)
这些函数位于 torch.nn.functional
(通常别名简写为 F
)中提供了一系列无状态的函数,它们是构建网络计算图 nn
模块的核心部分底层实现。这些函数不包含可学习参数,调用时需要显式传入所有输入,包括权重。
1.2.1 F.linear(input,relu
: weight,ReLU bias=None):无状态的线性变换激活函数
与原理: nn.修正线性单元(Rectified Linear模块不同,F.linear Unit)是一个纯种分段线性函数,其数学表达式为 $f(x) = \max(0, x)$。它不持能有任何内部状态(如权重或偏置)效缓解梯度消失问题,你需要手动将这些参数传入。这在权重是动态生成或需要共享时非常有用且计算开销小。
用法示例:
import torch.nn.functional as F
input_tensor = torch.randn(4,tensor([-1.0, 5)0.0, # (batch_size, in_features)
weight_matrix = torch.randn(3, 5) # (out_features, in_features)
bias_vector = torch.randn(3) # (out_features)
# 应用线性变换2.0])
output = F.linear(input_tensor, weight_matrix, bias_vector)relu(input_tensor)
print(f"ReLU output: {output}"Output shape:", output.shape)) # torch.Size(tensor([4,0., 3]0., 2.])
2.2 torch.Tensor.softmax(dim)F.softmax
:将输出转换为概率: Softmax 激活函数
原理: Softmax 函数能将一组个实数值向量转换为一个概率分布。它对向量中的每个元素应用指数函数,其中然后将结果归一化,使得所有值元素的和为 1。dim
参数至关重要,它指定了在哪进行归一个化的维度上进行 Softmax 操作。
用法示例:
# 假设这是模型对3个类别的原始输出(logits)
logits = torch.tensor([[1.0, 2.0, 0.5]3.0], [3.1.0, 1.0, 1.5]0]])
# 沿着最后一个维度(类别维度)计算概率
probabilities = logits.F.softmax(logits, dim=-1)
print(f"Softmax probabilities:\n{probabilities}"Probabilities:\n")
# 输出:
# tensor([[0.0900, 0.2447, 0.6652],
probabilities)# print("Sum[0.3333, of0.3333, probabilities per sample:", probabilities.sum(dim=-1)0.3333]])
3.2.3 F.one_hot(tensor,cross_entropy
: num_classes=-1):分类任务的好帮手
这个函数可以将一个包含类别索引的整数张量转换为 one-hot 编码的张量。这在计算交叉熵损失或需要二进制
原理: 交叉熵损失函数通常用于多分类任务。它在内部高效地集成了 LogSoftmax
和 NLLLoss
(负对数似然损失)。它衡量的是模型预测的概率分布与真实的类别表示时非标签(通常有用是 one-hot 形式,但函数接口接收类别索引)之间的差异。
用法示例:
# 类别模型输出 (logits) 和真实标签
(batch_size=4)logits = torch.randn(4, 5) # 4个样本, 5个类别
labels = torch.tensor([1, 0, 2,4, 1, 3]2]) # 转换为 one-hot 编码,假设总共有 5 每个样本的真实类别索引
one_hot_labelsloss = F.one_hot(labels,cross_entropy(logits, num_classes=5)labels)
print(f"Cross Entropy Loss: {loss.item()}"One-Hot)
Labels:\n",
2.4 #F.mse_loss
: 均方误差损失
原理: 均方误差(Mean Squared Error)用于回归任务。它计算的是模型预测值与真实值之间差的平方的平均值。
示例:
predictions = torch.tensor([1.5, 2.8, 4.1])
targets = torch.tensor([1, 1.0, 3.0, 0,4.0])
0],loss #= [0,F.mse_loss(predictions, 0,targets)
1,print(f"Mean 0,Squared 0],Error #Loss: [0, 1, 0, 0, 0],
# [0, 0, 0, 1, 0]]{loss.item()}")
第四三部分:张量变形初始化与数学运算 📐操作
3.1 torch.nn.init.*
: 权重初始化
高效原理: torch.nn.init
模块提供了多种权重初始化策略,以帮助模型更好地收敛。kaiming_uniform_
是其中一种,它根据 He 初始化策略从均匀分布中采样,适用于配合 ReLU 激活函数。函数名末尾的数据下划线 _
表示这是一个原地(in-place)操作是 PyTorch 的一大优势。
示例:
import torch.nn.init as init
import math
weight = torch.empty(3, 5)
init.kaiming_uniform_(weight, a=math.sqrt(5))
print("Kaiming Initialized Weight (sample):\n", weight)
1.3.2 torch.Tensor.view(*shape)view
:高效的形状: 张量视图重塑
原理: .view()
方法可以在不改变底层数据存储的情况下,高效地改变张量的形状,只要。新旧形状的元素总数相同即可必须一致。它非常高效,因为它返回的是一个共享原始数据内存的“新张量视图”,而不是一个数据副本。-1 可以用作占位符,自动推断该维度的大小。
用法示例:
x = torch.randn(4, 6)arange(12) # 原始形状tensor([ #0, 重塑为1, 8x3..., 11])
y = x.view(8,3, 3)
# 使用 -1 自动推断
z = x.view(2, 2, -1) # z 的形状将是 (2, 2, 6)4)
print(f"Reshaped tensor:\n{y}"Original shape:", x.shape)
print("View (8, 3) shape:", y.shape)
print("View (2, 2, -1) shape:", z.shape))
2.3.3 torch.topk(input,topk
: k,查找 dim=None):寻找最重要的K个Top-K 元素
此原理: torch.topk
函数用于查找张量在指定维度上查找输入张量中最大(或最小)的 k
个元素值及其对应的索引。
用法示例:
x = torch.tensor([[1, 5,9, 2], [9,8, 3, 6]5]])
# 寻找每行中最大的 2 个元素
values, indices = torch.topk(x, k=2, dim=1)
print("f"Top-2 Values:\n", values){values}") # tensor([[5,9, 2], [9,8, 6]5]])
print("f"Top-2 Indices:\n", indices){indices}") # tensor([[1, 2], [0, 2]])
3.4 sum()torch.Tensor.scatter_add_
,: mean()
, div_()
:基础数学高级索引聚合
原理: scatter_add_
是一个原地操作,它将一个源张量 src
中的值,根据 index
张量指定的位置,累加到目标张量 self
中。这对于实现复杂的稀疏更新或自定义聚合操作非常有用。
示例:
# 目标: 计算每个类别的得分总和
num_classes = 3
scores = torch.tensor([0.9, 0.1, 0.8, 0.5, 0.7])
class_indices = torch.tensor([0, 1, 0, 2, 1])
# 初始化类别总分张量
total_scores = torch.zeros(num_classes)
# 聚合分数
total_scores.scatter_add_(dim=0, index=class_indices, src=scores)
print(f"Total scores per class: {total_scores}") # tensor([1.7000, 0.8000, 0.5000])
第四部分:高级与专用网络层 (torch.nn
)
本部分将介绍更专门化的 nn.Module
,它们是构建现代深度学习架构(如 CNNs、RNNs 和 Transformers)的关键组件。
4.1 nn.BatchNorm2d
: 二维批量归一化
原理: 批量归一化(Batch Normalization)是一种用于稳定和加速深度神经网络训练的技术。nn.BatchNorm2d
专门用于四维输入(典型的如图像数据 [N, C, H, W]
)。在训练时,它对一个 mini-batch 内的数据,沿着通道(Channel)维度进行归一化,使其均值为0,方差为1。此外,它还包含两个可学习的仿射变换参数($\gamma$ 和 $\beta$),用于恢复网络的表达能力。在评估模式下,它会使用在训练过程中累积的全局均值和方差进行归一化。
示例:
# 4个样本, 3个通道, 32x32的图像
input_tensor = torch.randn(4, 3, 32, 32)
# 归一化的特征数量必须与输入通道数匹配
batch_norm_layer = nn.BatchNorm2d(num_features=3)
# 在训练模式下
batch_norm_layer.train()
output_train = batch_norm_layer(input_tensor)
# output_train 的每个通道在 batch 维度上均值接近0,方差接近1
# 在评估模式下
batch_norm_layer.eval()
output_eval = batch_norm_layer(input_tensor)
print(f"Output shape remains the same: {output_eval.shape}")
4.2 nn.LayerNorm
: 层归一化
原理: 层归一化(Layer Normalization)是另一种归一化技术。与批量归一化不同,它在单个样本内部对特征进行归一化,因此其计算完全独立于 batch 中的其他样本。这使得它非常适用于序列数据(如 NLP 任务中的 RNN 和 Transformer),其中序列长度可变,使用批量归一化会很棘手。它对指定 normalized_shape
的维度进行归一化。
示例:
# 2个样本, 序列长度10, 特征维度20
input_seq = torch.randn(2, 10, 20)
# 对最后一个维度(特征维度)进行归一化
layer_norm = nn.LayerNorm(normalized_shape=20)
output_norm = layer_norm(input_seq)
# output_norm 中每个样本的每个时间步的特征向量均值为0,方差为1
print(f"LayerNorm output shape: {output_norm.shape}") # torch.Size([2, 10, 20])
4.3 nn.Embedding
: 嵌入层
原理: nn.Embedding
本质上是一个大型的查找表(lookup table)。它存储了固定大小词汇表或类别库的密集向量(embeddings)。当输入一个由类别索引组成的列表时,它会返回这些索引对应的嵌入向量。这在自然语言处理中用于将单词ID映射为词向量,或在推荐系统中将用户/物品ID映射为特征向量。其内部维护一个大小为 (num_embeddings, embedding_dim)
的可学习权重矩阵。
示例:
# 假设词汇表大小为100, 每个词用一个32维向量表示
embedding_layer = nn.Embedding(num_embeddings=100, embedding_dim=32)
# 输入是一个由单词索引组成的张量 (2个句子, 每个句子4个词)
input_indices = torch.LongTensor([[1, 2, 4, 5], [4, 3, 2, 9]])
embedded_vectors = embedding_layer(input_indices)
print(f"Embedded vectors shape: {embedded_vectors.shape}") # torch.Size([2, 4, 32])
4.4 nn.LSTM
: 长短期记忆网络层
原理: LSTM (Long Short-Term Memory) 是一种特殊的循环神经网络(RNN),旨在解决传统 RNN 中的长期依赖和梯度消失问题。它通过引入一个单元状态(cell state)和三个门(遗忘门、输入门、输出门)来精细地控制信息的流动。这使得模型能够选择性地记忆、更新和输出信息,从而在长序列上表现更佳。
示例:
# 输入特征维度10, 隐藏状态维度20, 堆叠2层
lstm_layer = nn.LSTM(input_size=10, hidden_size=20, num_layers=2)
# 输入序列: 长度为5, batch大小为3, 特征维度10
input_seq = torch.randn(5, 3, 10)
# 初始隐藏状态和单元状态
h0 = torch.randn(2, 3, 20) # (num_layers, batch, hidden_size)
c0 = torch.randn(2, 3, 20)
# 前向传播
output, (hn, cn) = lstm_layer(input_seq, (h0, c0))
print(f"Output sequence shape: {output.shape}") # (seq_len, batch, hidden_size) -> (5, 3, 20)
print(f"Final hidden state shape: {hn.shape}") # (num_layers, batch, hidden_size) -> (2, 3, 20)
第五部分:高级张量操作与数据处理
本部分关注于对张量进行结构性变换和条件化处理的函数,这些是数据预处理和模型内部数据流控制的基础。
5.1 torch.cat
: 张量拼接
原理: torch.cat
用于将一个张量序列沿着一个已存在的维度进行拼接。所有待拼接的张量在非拼接维度上最常必须具有相同的尺寸。
示例:
x = torch.randn(2, 3)
y = torch.randn(4, 3)
z = torch.randn(2, 2)
# 沿维度0 (行) 拼接, 要求列数相同
cat_dim0 = torch.cat([x, y], dim=0)
print(f"Concat on dim 0 shape: {cat_dim0.shape}") # torch.Size([6, 3])
# 沿维度1 (列) 拼接, 要求行数相同
cat_dim1 = torch.cat([x, z], dim=1)
print(f"Concat on dim 1 shape: {cat_dim1.shape}") # torch.Size([2, 5])
5.2 torch.stack
: 张量堆叠
原理: torch.stack
用于将一个张量序列沿着一个新的数学运算:维度进行堆叠。所有待堆叠的张量必须具有完全相同的尺寸。这相当于在拼接前为每个张量增加一个新维度。
示例:
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
# 沿新的维度0进行堆叠
stacked_tensor = torch.stack([a, b], dim=0)
print(f"Stacked tensor:\n{stacked_tensor}")
print(f"Stacked tensor shape: {stacked_tensor.shape}") # torch.Size([2, 3])
5.3 torch.squeeze
& torch.unsqueeze
: 维度压缩与扩展
原理:
:.sum()torch.unsqueeze(input, dim)求和在指定维度dim
上为输入张量增加一个大小为1的新维度。
:.mean()torch.squeeze(input, dim=None)求平均值。.div_():In-place法。带下划线的方法会直接修改输入张量本身中所有大小为1的维度。如果指定了dim
,则只移除该维度(如果其大小为1)。
这两个操作对于调整张量维度以满足特定层的输入要求(如卷积层或批处理)至关重要。
用法示例:
x = torch.randn(3, 4) # Shape: [3, 4]
# 在维度0增加一个批处理维度
y = torch.unsqueeze(x, 0)
print(f"Unsqueeze shape: {y.shape}") # Shape: [1, 3, 4]
# 移除所有大小为1的维度
z = torch.squeeze(y)
print(f"Squeeze shape: {z.shape}") # Shape: [3, 4]
5.4 torch.where
: 条件化选择
原理: torch.where(condition, x, y)
是一个元素级的条件判断函数。它返回一个新的张量,其元素根据 condition
张量(一个布尔张量)来选择:如果 condition
中对应位置的元素为 True
,则从 x
中取值;否则从 y
中取值。
示例:
x = torch.tensor([[1.-2.0, 2.0],0.0, [3.0, 4.-1.0]])
# 沿行求和将所有负数替换为0 (dim=1),并保持维度类似于 row_sumReLU 的一种实现)
output = x.sum(dim=1,torch.where(x keepdim=True)> 0, x, torch.tensor(0.))
print(f"torch.where output: {output}"Row Sum (keepdim=True):\n", row_sum)) # 计算所有元素的平均值
total_mean = x.mean()
print("Total Mean:"tensor([0., total_mean)
# 对 x 进行 in-place 除以 2
x.div_(2)
print("x after in-place division:\n"0., x)3., 0.])
5.5 第五部分:高级索引:scatter_add_torch.permute
: 维度重排
scatter_add_torch.permute
: 维度重排原理: torch.permute
用于根据指定的魔力 ✨
这是维度顺序,对张量的维度进行重排。它返回一个功能强大但相对复杂共享底层数据存储的新视图。这在需要将数据格式从 in-place(N,
转换为 操作,H, W, C)(N, C, H, W)
(PyTorch 标准)时非常有用于实现高级的聚合功能。
torch.Tensor.scatter_add_(dim, index, src)
它的工作方式可以理解为“根据 index 将 src 张量中的值加到 self 张量中”。
self: 目标张量,它将被修改。dim: 进行操作的维度。index: 索引张量,指示src中的每个值应该被添加到self的哪个位置。src: 源张量,其值将被“散布”出去。
一个直观的示例子:
假设你想统计一个班级中,来自不同城市(用 ID 表示)的学生人数。
# 假设有一个典型的 3"channels-last" 个城市格式的图像 batch
x_channels_last = torch.randn(16, 32, 32, 3) # (ID:N, H, W, C)
# 重排为 "channels-first"
x_channels_first = x_channels_last.permute(0, 3, 1, 2),初始化计数器
city_counts = torch.zeros(3, dtype=torch.long) # 8个学生,他们各自的城市ID(N, #C, 学生0来自城市1,学生1来自城市0,学生2来自城市1...H, student_city_ids = torch.tensor([1, 0, 1, 2, 0, 0, 1, 2])
# 我们要为每个学生加 1
ones_to_add = torch.ones(8, dtype=torch.long)
# 使用 scatter_add_ 进行聚合
# 沿着维度 0,根据 student_city_ids,将 ones_to_add 的值加到 city_counts
city_counts.scatter_add_(dim=0, index=student_city_ids, src=ones_to_add)W)
print(f"城市Permuted 0 的学生人数:shape: {city_counts[0]}x_channels_first.shape}") # 3torch.Size([16, print(f"城市3, 132, 的学生人数: {city_counts[1]}"32]) # 3
print(f"城市 2 的学生人数: {city_counts[2]}") # 2
希望这份指南能帮助你更自如地运用 PyTorch。祝你编码愉快!