共计 2953 个字符,预计需要花费 8 分钟才能阅读完成。
在深度学习的计算机视觉(CV)和自然语言处理(NLP)领域,卷积神经网络(CNN)和 Transformer 结构已成为两大主流架构。对于刚入门的新手来说,常常会面临选择困难:什么时候该用 CNN?什么时候 Transformer 更合适?本文将从原理到代码实战,帮你理清这两种结构的核心差异和应用场景。

核心原理对比
计算复杂度分析
-
CNN 的 FLOPs 计算:
对于输入尺寸 $H \times W \times C_{in}$,卷积核 $K \times K \times C_{out}$,计算量为:
$$FLOPs_{conv} = H \times W \times C_{in} \times C_{out} \times K^2$$ -
Transformer 的 FLOPs 计算:
自注意力机制的计算复杂度为:
$$FLOPs_{attention} = 4 \times L \times d^2 + 2 \times L^2 \times d$$
其中 $L$ 是序列长度,$d$ 是特征维度
特征提取方式
-
CNN 的局部感知:
通过卷积核在局部感受野上滑动,天然具有平移不变性(translation equivariance) -
Transformer 的全局注意力:
通过 query-key-value 机制建立全局依赖关系,擅长捕捉长距离特征关联
位置编码 vs 卷积归纳偏置
-
CNN 的归纳偏置:
通过卷积核的局部连接和权重共享,隐含了空间局部性先验 -
Transformer 的位置编码:
需要显式添加位置信息,常用正弦编码:
$$PE_{(pos,2i)} = \sin(pos/10000^{2i/d})$$
$$PE_{(pos,2i+1)} = \cos(pos/10000^{2i/d})$$
PyTorch 实战对比
CNN 实现(5 层结构)
import torch
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
# 卷积层参数选择:3x3 是最常用的小卷积核,padding= 1 保持特征图尺寸
self.features = nn.Sequential(nn.Conv2d(3, 64, kernel_size=3, padding=1), # 输入 3 通道,输出 64 通道
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# 最后三层逐步压缩空间维度
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.AdaptiveAvgPool2d((1,1)) # 全局平均池化
)
self.classifier = nn.Linear(512, num_classes)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
Transformer 实现(Encoder 结构)
import math
from torch import nn, Tensor
class TransformerEncoder(nn.Module):
def __init__(self,
input_dim: int = 32*32,
embed_dim: int = 512,
num_heads: int = 8,
num_layers: int = 6,
num_classes: int = 10):
super().__init__()
# 将图像展平为序列
self.patch_embed = nn.Linear(3, embed_dim) # CIFAR-10 是 3 通道
# 可学习的位置编码(比正弦编码更灵活)self.pos_embed = nn.Parameter(torch.randn(1, input_dim, embed_dim))
# Transformer Encoder 层
encoder_layer = nn.TransformerEncoderLayer(
d_model=embed_dim,
nhead=num_heads,
dim_feedforward=2048,
dropout=0.1
)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers)
# 分类头
self.classifier = nn.Linear(embed_dim, num_classes)
def forward(self, x):
# 输入尺寸转换:[B,C,H,W] -> [B,H*W,C]
x = x.permute(0,2,3,1).flatten(1,2)
x = self.patch_embed(x) # [B,L,D]
x = x + self.pos_embed
x = self.transformer(x)
# 取[CLS] token 或平均池化
x = x.mean(dim=1)
return self.classifier(x)
性能对比测试
CIFAR-10 实验结果
| 指标 | CNN 模型 | Transformer 模型 |
|---|---|---|
| 准确率 | 89.2% | 85.7% |
| 训练时间 /epoch | 45s | 128s |
| 显存占用 | 1.2GB | 3.8GB |
内存测量方法:
torch.cuda.reset_peak_memory_stats()
# ... 运行模型...
max_mem = torch.cuda.max_memory_allocated() / 1024**3 # 转换为 GB
实战避坑指南
- Transformer 学习率设置
- 建议使用 warmup 策略,初始学习率设为 3e- 5 逐步提升
-
Adam 优化器的 beta2 参数建议设为 0.98(默认 0.999 可能导致不稳定)
-
CNN 的 padding 选择
samepadding 保持特征图尺寸,但边缘信息可能被过度平滑-
validpadding 更保留真实特征,但需要处理尺寸变化 -
混合精度训练
- 必须使用
GradScaler防止梯度下溢 - CNN 通常比 Transformer 更适合混合精度训练
开放性问题思考
-
ViT 的预训练需求
Transformer 在小数据集上缺乏 CNN 的归纳偏置,需要大规模预训练补偿 -
长距离依赖验证实验
可以设计遮挡测试(occlusion test),观察模型对远距离特征的利用能力
总结建议
对于初学者,建议先从 CNN 入手理解基础特征提取,再逐步过渡到 Transformer。实际项目中可以:
– 图像分类:优先尝试 ResNet 等 CNN 变体
– 序列数据:Transformer 通常是更好选择
– 计算资源有限时:CNN 仍是更经济的选择
两种架构也在不断融合(如 ConvNeXt、MobileViT),掌握它们的核心思想比争论孰优孰劣更有价值。
