引言
深度学习模型的搭建就像搭积木。在 PyTorch 中,torch.nn模块为我们提供了各种形状和功能
的“积木”(层)。很多初学者能跑通复杂的 GitHub 代码,却在自己手写nn.Linear或nn.Conv2d
时因为维度对不上而报错。本文将带你通过“代码+实战”的方式,彻底搞懂 PyTorch 中最常见的 7
种层结构及其输入输出逻辑。
核心容器:一切的容器
在 PyTorch 中,所有的层都继承自 nn.Module。但在实际组装时,我们最常用的是
nn.Sequential,它像一个传送带,让数据按顺序流过。
🚀举个例子
一个样本有10个特征,深度学习中层的作用就是不断的把特征在各个空间中进行维度变化。
比如经过第一个线性层的作用,特征会变成20,然后经过一个激活函数进行非线性变化,再经过一
个线性层,变成1个特征。【本质就是对特征进行变换,直到变换到样本对应的标签】
import torch import torch.nn as nn # 实战:像搭积木一样串联层 # 输入 -> [全连接] -> [激活] -> [全连接] -> 输出 net = nn.Sequential( nn.Linear(10, 20), nn.ReLU(), nn.Linear(20, 1) ) # 模拟 Batch=4, 特征=10 的输入 x = torch.randn(4, 10) print(f"输入形状: {x.shape}") # torch.Size([4, 10]) print(f"输出形状: {net(x).shape}") # torch.Size([4, 1])
本文所用到的数据
下面我们就以一张海绵宝宝的照片为例'haimian.png'。这张图片的原始大小是图片形状 (高×宽
×通道): (533, 533, 3),神经网络输入需batch维度,格式为[batch, C, H, W],标注化之后的大小是
[1, 3, 224, 224]。这里进行一个简单的说明,因为你看到的彩色,是由红绿蓝三个通道合成出来
的。而你所看到的单个通道的黑白并不是色彩,而是红绿蓝颜色数值的形象化表示。黑代表0,白
色代表255,各种灰代表0-255之间的所有数灰。
具体的变换过程如下图所示:
import cv2 import torch import numpy as np from PIL import Image import torchvision.transforms as transforms # ===================== 1. 基础配置 ===================== # 图片路径(当前目录下的haimian.png) img_path = "haimian.png" # 设备配置(优先使用GPU) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # ===================== 2. 方法1:用PIL+TorchVision读取(推荐,适配PyTorch) ===================== print("="*50) print("方法1:PIL + TorchVision 读取(适配PyTorch)") print("="*50) # 步骤1:读取原始图片并查看基础信息 img_pil = Image.open(img_path).convert("RGB") # 强制转为RGB(避免灰度图/透明通道问题) print(f"【原始图片信息】") print(f"图片格式: {img_pil.mode}") # 色彩模式(RGB/灰度等) print(f"图片尺寸 (宽×高): {img_pil.size}") # (width, height) # 步骤2:定义转换流程(转为张量+归一化,适配神经网络输入) # 注:归一化均值/方差可根据任务调整(如ImageNet的均值[0.485, 0.456, 0.406],方差[0.229, 0.224, 0.225]) transform = transforms.Compose([ transforms.Resize((224, 224)), # 缩放到神经网络常用的224×224(可选,根据模型调整) transforms.ToTensor(), # 转为张量:(H,W,C)→(C,H,W),数值归一化到[0,1] transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) # 归一化到[-1,1](可选) ]) # 步骤3:转换为神经网络可用的张量 img_tensor = transform(img_pil).to(device) # 移至指定设备(CPU/GPU) # 步骤4:添加batch维度(神经网络输入需batch维度,格式为[batch, C, H, W]) img_tensor_batch = img_tensor.unsqueeze(0) # 从[3,224,224]→[1,3,224,224] # 输出张量关键信息 print(f"\n【张量核心信息】") print(f"张量形状 (无batch维度): {img_tensor.shape}") # (C, H, W) print(f"张量形状 (含batch维度): {img_tensor_batch.shape}") # (batch, C, H, W) print(f"张量数据类型: {img_tensor.dtype}") # 通常为torch.float32 print(f"张量设备: {img_tensor.device}") # cpu/cuda:0 print(f"张量数值范围: [{img_tensor.min().item():.4f}, {img_tensor.max().item():.4f}]") # ===================== 3. 方法2:用OpenCV读取(备用,兼容CV任务) ===================== print("\n" + "="*50) print("方法2:OpenCV 读取(兼容CV任务)") print("="*50) # 步骤1:读取图片(OpenCV默认BGR格式) img_cv = cv2.imread(img_path) # 转换为RGB(与PIL统一) img_cv_rgb = cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB) # 步骤2:查看原始信息 print(f"【原始图片信息】") print(f"图片形状 (高×宽×通道): {img_cv.shape}") # (H, W, C) print(f"图片数据类型: {img_cv.dtype}") # uint8(0-255) print(f"像素值范围: [{img_cv.min()}, {img_cv.max()}]") # 步骤3:转换为PyTorch张量 # 调整维度:(H,W,C)→(C,H,W),并归一化到[0,1] img_cv_tensor = torch.from_numpy(img_cv_rgb).permute(2, 0, 1).float() / 255.0 img_cv_tensor = img_cv_tensor.to(device) # 添加batch维度 img_cv_tensor_batch = img_cv_tensor.unsqueeze(0) # 输出张量信息 print(f"\n【张量核心信息】") print(f"张量形状 (无batch维度): {img_cv_tensor.shape}") print(f"张量形状 (含batch维度): {img_cv_tensor_batch.shape}") print(f"张量数据类型: {img_cv_tensor.dtype}") print(f"张量设备: {img_cv_tensor.device}")
==================================================
方法1:PIL + TorchVision 读取(适配PyTorch)
==================================================
【原始图片信息】
图片格式: RGB
图片尺寸 (宽×高): (533, 533)
【张量核心信息】
张量形状 (无batch维度): torch.Size([3, 224, 224])
张量形状 (含batch维度): torch.Size([1, 3, 224, 224])
张量数据类型: torch.float32
张量设备: cpu
张量数值范围: [-1.0000, 1.0000]
==================================================
方法2:OpenCV 读取(兼容CV任务)
==================================================
【原始图片信息】
图片形状 (高×宽×通道): (533, 533, 3)
图片数据类型: uint8
像素值范围: [0, 255]
【张量核心信息】
张量形状 (无batch维度): torch.Size([3, 533, 533])
张量形状 (含batch维度): torch.Size([1, 3, 533, 533])
张量数据类型: torch.float32
张量设备: cpu
神经网络层
💾本质就是: 对一维的数据进行特征维度的变化
线性层(nn.Linear)输入是[batch_size, in_features](2 维,批量一维特征向量),输出是
[batch_size, out_features](2 维,批量映射后的特征向量)。
🚀举个例子
# 定义:将 784 维特征压缩到 256 维 fc = nn.Linear(in_features=784, out_features=256) # 实战:模拟处理一批 MNIST 数据 (64张) # 注意:输入必须是 2D 张量 [Batch_Size, Features] x = torch.randn(64, 784) out = fc(x) print(f"全连接层变换: {x.shape} -> {out.shape}") # 输出: [64, 784] -> [64, 256]
比如这里我们原始图片经过标准化之后的的大小是(3,224,224)这里我们需要把他变成一维度
的表示(1,150528)。(batch_size, in_features),经过线性层之后就变成了(1, 100),
(batch_size, in_features)。
卷积神经网络层
💾本质是:CNN 提取空间特征
二维卷积层(nn.Conv2d)输入是[batch_size, in_channels, height, width](4 维,CHW 格
式),输出是[batch_size, out_channels, out_h, out_w](4 维,通道数由卷积核数量决定,尺
寸由卷积参数计算)。
关键参数:
in_channels: 输入通道数 (RGB=3, 灰度=1)。
out_channels: 输出通道数 (即卷积核的数量)。
kernel_size: 卷积核大小 (如 3x3)。
stride (步长) 和 padding (填充)。
🚀举个例子
# 定义:输入3通道(RGB),输出16通道,3x3卷积,保持尺寸不变(padding=1) conv = nn.Conv2d(in_channels=3, out_channels=5, kernel_size=3, padding=1) # 实战:模拟一张 224x224 的彩色图片 (Batch=1) img = torch.randn(1, 3, 224, 224) out = conv(img) print(f"卷积层变换: {img.shape} -> {out.shape}") # 输出: [1, 3, 224, 224] -> [1, 5, 224, 224]
循环神经网络数据层
💾LSTM 捕捉序列特征
LSTM 层(batch_first=True)输入是[batch_size, seq_len, input_size](3 维,批量 - 序列长度
- 输入特征维度),输出包含输出序列[batch_size, seq_len, hidden_size] 和 最终隐状态 / 细胞
状态(各为[num_layers, batch_size, hidden_size])。
一般用于处理时间序列(如股票、文本、语音)。
关键参数:input_size (特征维), hidden_size (隐藏层维)。
避坑指南:默认输入格式是 (Seq_Len, Batch, Feature)。强烈建议开启 batch_first=True,让输入
变成符合直觉的 (Batch, Seq_Len, Feature)
🚀举个例子
# 定义:输入特征10,隐藏层20,双层LSTM lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2, batch_first=True) # 模拟: Batch=3, 时间步=5, 特征=10 x_seq = torch.randn(1, 750, 10) out, (h_n, c_n) = lstm(x_seq) print(f"序列输出: {out.shape}") # [1, 5, 20] (包含所有时间步) print(f"最后状态: {h_n.shape}") # [2, 3, 20] (层数, Batch, 隐藏层)
图片→LSTM 输入的关键转换
LSTM 只能处理[batch, seq_len, input_size]的序列数据,而图片是[C, H, W]的空间数据,因此
需要:空间转序列:将图片按行展开(每行作为一个 “时间步”),比如:50x50x3 的图片 → 50
行 × (3×50)=150 维 / 行;特征维度匹配:将 150 维 / 行拆分为 10 维 ×15 组 → 序列长度变为
750,满足input_size=10;添加 batch 维度:单张图片→batch_size=1,最终输入形状[1, 750,
10]。
图神经网络层
💾提取图结构的关联特征
| 张量名称 | 形状 (Shape) | 含义 | 备注 |
| Input: x | [N, F_in] |
节点特征 | N=节点数, F_in=输入特征数 |
| Input: edge_index | [2, E] |
边连接关系 | E=边数, 0行=起点, 1行=终点 |
| Output: out | [N, F_out] |
新的节点特征 | N不变, F_out=输出特征数 |
| (可选) Input: edge_attr | [E, F_edge] |
边的特征 | 有些层(如GAT)支持边的权重或属性 |
🚀举个例子
import torch from torch_geometric.nn import GCNConv # ================= 输入 ================= # 1. 节点特征 x: [N=3, F_in=2] x = torch.tensor([ [1.75, 65.0], # Node 0 [1.80, 75.0], # Node 1 [1.65, 55.0] # Node 2 ], dtype=torch.float) # 2. 边索引 edge_index: [2, E=4] # 意思: 0连1, 1连0, 1连2, 2连1 edge_index = torch.tensor([ [0, 1, 1, 2], # Source 节点索引 [1, 0, 2, 1] # Target 节点索引 ], dtype=torch.long) # ================= 定义层 ================= # 定义层: 输入特征2 -> 输出特征8 conv = GCNConv(in_channels=2, out_channels=8) # ================= 输出 ================= out = conv(x, edge_index) print(f"输入 x 形状: {x.shape}") # torch.Size([3, 2]) print(f"输入 edge_index 形状: {edge_index.shape}") # torch.Size([2, 4]) print(f"输出 out 形状: {out.shape}") # torch.Size([3, 8])
Transformer层
💾提取全局上下文关联特征
| 张量 | 形状 (batch_first=True) | 含义 |
| Input | [Batch, Seq_Len, d_model] |
Batch: 批次大小 Seq_Len: 序列长度(如单词数、像素块数) d_model: 特征维度 |
| Output | [Batch, Seq_Len, d_model] |
形状不变。现在的每个向量都融合了上下文信息。 |
🚀举个例子
d_model: 输入特征的维度(例如词向量的长度,如 512)。
nhead: 多头注意力的头数(必须能被 d_model 整除)。
batch_first: 非常重要! 默认为 False。如果设置为 True,输入格式为 (Batch, Seq, Feat),更符合人类直觉。
import torch import torch.nn as nn # ================= 配置参数 ================= batch_size = 2 # 比如有 2 句话 seq_len = 5 # 每句话有 5 个单词 d_model = 512 # 每个单词用 512 维向量表示 nhead = 8 # 8 个注意力头 # ================= 定义 Transformer 层 ================= # 这是一个单独的层(Block) transformer_layer = nn.TransformerEncoderLayer( d_model=d_model, nhead=nhead, batch_first=True # 强烈建议设为 True,否则输入维度需要换位置 ) # ================= 输入数据 ================= # 形状: [Batch_Size, Sequence_Length, Embedding_Dimension] # 模拟: 2句话,每句5个词,每个词512维 src = torch.randn(batch_size, seq_len, d_model) # ================= 前向传播 ================= out = transformer_layer(src) # ================= 输出 ================= print(f"输入形状: {src.shape}") # torch.Size([2, 5, 512]) print(f"输出形状: {out.shape}") # torch.Size([2, 5, 512])