知识点回顾:
- 传统计算机视觉发展史:LeNet-->AlexNet-->VGGNet-->nceptionNet-->ResNet
之所以说传统,是因为现在主要是针对backbone-neck-head这样的范式做文章
- inception模块和网络
- 特征融合方法阶段性总结:逐元素相加、逐元素相乘、concat通道数增加等
- 感受野与卷积核变体:深入理解不同模块和类的设计初衷
-
LeNet-5 (1998)
-
开创者: Yann LeCun, Léon Bottou, Yoshua Bengio, Patrick Haffner。
-
贡献: 奠基之作。第一个成功应用于手写数字识别(MNIST数据集)的卷积神经网络,证明了CNN的有效性。
-
核心结构:
-
卷积层(提取空间特征)
-
池化层(降采样,增加平移不变性)
-
全连接层(最终分类)
-
-
特点: 相对较浅(2个卷积层 + 2个池化层 + 2个全连接层)。使用 Sigmoid/Tanh 激活函数和平均池化。
-
意义: 确立了CNN的基本组件和工作流程,为后续发展奠定了基础。但在当时受限于算力和数据规模,未能广泛应用。
-
-
AlexNet (2012)
-
开创者: Alex Krizhevsky, Ilya Sutskever, Geoffrey Hinton。
-
贡献: 深度学习的“复兴者”和引爆点。在ImageNet大规模视觉识别挑战赛上以巨大优势夺冠,将Top-5错误率从26%大幅降至15.3%,震惊世界。
-
核心创新:
-
深度增加: 比LeNet深得多(5个卷积层 + 3个全连接层)。
-
ReLU激活函数: 替代Sigmoid/Tanh,解决了梯度消失问题,极大加速了训练。
-
Dropout: 在全连接层使用,有效减轻过拟合。
-
重叠最大池化: 替代平均池化,效果更好。
-
数据增强: 随机裁剪、翻转等增加训练数据多样性。
-
GPU训练: 利用双GPU并行训练(受限于当时显存)。
-
-
意义: 证明了深度CNN在大规模复杂图像分类任务上的惊人潜力,彻底点燃了深度学习的热潮,标志着深度学习成为计算机视觉的主流方法。
-
-
VGGNet (2014)
-
开创者: Karen Simonyan, Andrew Zisserman (牛津大学视觉几何组)。
-
贡献: 探索了网络深度的影响并提出了极简统一的结构范式。
-
核心创新:
-
小卷积核堆叠: 几乎全部使用非常小的 3x3卷积核。多个3x3卷积层堆叠能达到更大的感受野(如两层3x3等效于一层5x5,三层等效于7x7),同时参数更少,非线性更强。
-
结构统一: 整个网络结构非常规整,由重复的“卷积块”组成(通常是2-4层3x3卷积 + 1层2x2最大池化)。最著名的是VGG16(16层含参数层)和VGG19(19层)。
-
深度增加: 将网络深度推到了16-19层。
-
-
意义: 证明了增加深度可以显著提升模型性能(在ImageNet上取得亚军,但模型非常简洁优雅)。其统一、模块化的设计理念极大影响了后续网络架构的设计。小卷积核堆叠成为标准做法。预训练的VGG特征在迁移学习中广泛使用。
-
-
InceptionNet (GoogLeNet) (2014)
-
开创者: Christian Szegedy 等 (Google)。
-
贡献: 在增加深度和宽度的同时,高效控制计算量和参数量。提出了革命性的“Inception模块”。
-
核心创新:
-
Inception模块: 核心思想是并行处理不同尺度的特征。在一个模块内同时使用 1x1, 3x3, 5x5 卷积核和 3x3 最大池化层,并将它们的输出在通道维度上拼接起来。这允许网络在同一层捕获不同尺度的信息。
-
1x1卷积(瓶颈层): 在3x3和5x5卷积之前使用1x1卷积进行降维(减少通道数),大幅减少计算量和参数。
-
辅助分类器: 在网络中间层添加额外的分类输出,用于训练时提供额外的梯度信号,缓解深层网络的梯度消失问题(在推理时移除)。
-
全局平均池化: 替代全连接层作为最后的分类层,显著减少参数。
-
-
意义: 在2014年ImageNet竞赛中击败VGG夺冠(Top-5错误率6.67%)。其Inception模块的设计思想(多分支并行、瓶颈层)极具启发性,后续发展出多个变种(Inception v2, v3, v4, Inception-ResNet)。
-
-
ResNet (残差网络) (2015)
-
开创者: Kaiming He 等 (微软亚洲研究院)。
-
贡献: 革命性地解决了极深度网络的退化问题,使训练数百甚至上千层的网络成为可能。是当前最基础、最广泛应用的CNN架构之一。
-
核心创新:
-
残差连接/跳跃连接: 这是最核心的突破。网络不再直接学习目标映射
H(x)
,而是学习残差映射F(x) = H(x) - x
。通过一个恒等快捷连接将输入x
直接加到卷积层的输出F(x)
上,形成最终输出H(x) = F(x) + x
。 -
残差块: 实现上述残差学习的基本构建模块。通常包含2层或3层卷积。
-
-
为什么有效:
-
解决退化问题: 当网络很深时,添加更多层反而导致训练和测试误差增大(不是过拟合,而是优化困难)。残差连接使得梯度可以直接流回浅层,极大地缓解了梯度消失/爆炸问题。
-
恒等映射易于学习: 如果某一层或多层是冗余的,残差块可以轻松学习到
F(x) = 0
,退化成恒等映射H(x) = x
,不会比浅层网络更差。
-
-
意义: 在2015年ImageNet竞赛中以3.57%的Top-5错误率夺冠。成功训练了152层甚至1000层以上的网络。ResNet及其变种(ResNeXt, ResNet in ResNet, Wide ResNet等)成为计算机视觉几乎所有任务(分类、检测、分割等)的基础骨干网络。残差学习的思想被广泛应用于其他深度学习领域。
-
-
总结这条发展脉络:
-
LeNet: 奠基,证明了CNN的基本可行性。
-
AlexNet: 引爆点,证明了深度CNN在大规模任务上的巨大威力,推广了ReLU, Dropout等关键技术。
-
VGGNet: 探索深度,确立小卷积核堆叠的范式,提供简洁统一的设计。
-
InceptionNet: 探索宽度与多尺度,提出高效的Inception模块和瓶颈层,平衡性能与计算量。
-
ResNet: 突破深度极限,通过残差学习解决退化问题,成为现代CNN的基石。
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import numpy as np
from torch.utils.data import DataLoader
from torch.optim.lr_scheduler import ReduceLROnPlateau
import matplotlib.pyplot as plt# 设备配置
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 数据预处理
transform = transforms.Compose([transforms.RandomCrop(32, padding=4),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])# 加载数据集
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)# CBAM模块
class CBAM(nn.Module):def __init__(self, channels, reduction_ratio=16):super(CBAM, self).__init__()self.channel_attention = nn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Conv2d(channels, channels // reduction_ratio, 1),nn.ReLU(inplace=True),nn.Conv2d(channels // reduction_ratio, channels, 1),nn.Sigmoid())self.spatial_attention = nn.Sequential(nn.Conv2d(2, 1, 7, padding=3),nn.Sigmoid())def forward(self, x):# 通道注意力ca = self.channel_attention(x)x_ca = x * ca# 空间注意力sa_avg = torch.mean(x_ca, dim=1, keepdim=True)sa_max, _ = torch.max(x_ca, dim=1, keepdim=True)sa = torch.cat([sa_avg, sa_max], dim=1)sa = self.spatial_attention(sa)return x_ca * sa# 基础Inception模块
class InceptionModule(nn.Module):def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):super(InceptionModule, self).__init__()self.branch1 = nn.Sequential(nn.Conv2d(in_channels, ch1x1, kernel_size=1),nn.BatchNorm2d(ch1x1),nn.ReLU(inplace=True))self.branch2 = nn.Sequential(nn.Conv2d(in_channels, ch3x3red, kernel_size=1),nn.BatchNorm2d(ch3x3red),nn.ReLU(inplace=True),nn.Conv2d(ch3x3red, ch3x3, kernel_size=3, padding=1),nn.BatchNorm2d(ch3x3),nn.ReLU(inplace=True))self.branch3 = nn.Sequential(nn.Conv2d(in_channels, ch5x5red, kernel_size=1),nn.BatchNorm2d(ch5x5red),nn.ReLU(inplace=True),nn.Conv2d(ch5x5red, ch5x5, kernel_size=5, padding=2),nn.BatchNorm2d(ch5x5),nn.ReLU(inplace=True))self.branch4 = nn.Sequential(nn.MaxPool2d(kernel_size=3, stride=1, padding=1),nn.Conv2d(in_channels, pool_proj, kernel_size=1),nn.BatchNorm2d(pool_proj),nn.ReLU(inplace=True))def forward(self, x):branch1 = self.branch1(x)branch2 = self.branch2(x)branch3 = self.branch3(x)branch4 = self.branch4(x)return torch.cat([branch1, branch2, branch3, branch4], 1)# 带残差连接的Inception模块
class ResidualInceptionModule(nn.Module):def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):super(ResidualInceptionModule, self).__init__()self.inception = InceptionModule(in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj)out_channels = ch1x1 + ch3x3 + ch5x5 + pool_proj# 如果输入输出通道数不同,使用1x1卷积进行维度匹配self.shortcut = nn.Sequential()if in_channels != out_channels:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size=1),nn.BatchNorm2d(out_channels))def forward(self, x):identity = self.shortcut(x)out = self.inception(x)return F.relu(out + identity, inplace=True)# 完整的Inception网络
class InceptionNet(nn.Module):def __init__(self, use_residual=False, use_cbam=False):super(InceptionNet, self).__init__()self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)self.bn1 = nn.BatchNorm2d(64)# 第一个Inception模块组self.inception1 = self._make_inception_layer(64, 64, 64, 96, 48, 64, 64, use_residual, use_cbam)# 下采样self.downsample1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)# 第二个Inception模块组self.inception2 = self._make_inception_layer(256, 128, 128, 192, 96, 128, 128, use_residual, use_cbam)# 下采样self.downsample2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)# 第三个Inception模块组self.inception3 = self._make_inception_layer(512, 192, 192, 256, 128, 192, 192, use_residual, use_cbam)self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.fc = nn.Linear(768, 10)def _make_inception_layer(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj, use_residual, use_cbam):layers = []# 使用残差或标准Inception模块if use_residual:layers.append(ResidualInceptionModule(in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj))else:layers.append(InceptionModule(in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj))# 添加CBAM模块if use_cbam:layers.append(CBAM(ch1x1 + ch3x3 + ch5x5 + pool_proj))return nn.Sequential(*layers)def forward(self, x):x = F.relu(self.bn1(self.conv1(x)))x = self.inception1(x)x = self.downsample1(x)x = self.inception2(x)x = self.downsample2(x)x = self.inception3(x)x = self.avgpool(x)x = torch.flatten(x, 1)x = self.fc(x)return x# 训练函数
def train(model, dataloader, criterion, optimizer, epoch):model.train()running_loss = 0.0correct = 0total = 0for batch_idx, (inputs, targets) in enumerate(dataloader):inputs, targets = inputs.to(device), targets.to(device)optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs, targets)loss.backward()optimizer.step()running_loss += loss.item()_, predicted = outputs.max(1)total += targets.size(0)correct += predicted.eq(targets).sum().item()if batch_idx % 100 == 99:print(f'Epoch: {epoch+1}, Batch: {batch_idx+1}, Loss: {running_loss/100:.4f}')running_loss = 0.0acc = 100. * correct / totalprint(f'Train Accuracy: {acc:.2f}%')return acc# 测试函数
def test(model, dataloader, criterion):model.eval()test_loss = 0.0correct = 0total = 0with torch.no_grad():for inputs, targets in dataloader:inputs, targets = inputs.to(device), targets.to(device)outputs = model(inputs)loss = criterion(outputs, targets)test_loss += loss.item()_, predicted = outputs.max(1)total += targets.size(0)correct += predicted.eq(targets).sum().item()acc = 100. * correct / totalprint(f'Test Loss: {test_loss/len(dataloader):.4f}, Test Accuracy: {acc:.2f}%')return acc# 主实验函数
def run_experiment(use_residual=False, use_cbam=False):# 数据加载train_loader = DataLoader(train_set, batch_size=128, shuffle=True, num_workers=2)test_loader = DataLoader(test_set, batch_size=100, shuffle=False, num_workers=2)# 模型初始化model = InceptionNet(use_residual=use_residual, use_cbam=use_cbam).to(device)# 损失函数和优化器criterion = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)scheduler = ReduceLROnPlateau(optimizer, 'max', factor=0.5, patience=3, verbose=True)# 训练参数epochs = 30best_acc = 0.0train_accs, test_accs = [], []print(f"\nStarting experiment: Residual={use_residual}, CBAM={use_cbam}")for epoch in range(epochs):print(f"\nEpoch {epoch+1}/{epochs}")train_acc = train(model, train_loader, criterion, optimizer, epoch)test_acc = test(model, test_loader, criterion)train_accs.append(train_acc)test_accs.append(test_acc)scheduler.step(test_acc)# 保存最佳模型if test_acc > best_acc:best_acc = test_acctorch.save(model.state_dict(), f'model_res{use_residual}_cbam{use_cbam}.pth')print(f"Best Test Accuracy: {best_acc:.2f}%")return best_acc, train_accs, test_accs# 运行所有消融实验
if __name__ == "__main__":results = {}configs = [(False, False), # 基础Inception(True, False), # +残差(False, True), # +CBAM(True, True) # +残差+CBAM]for config in configs:use_residual, use_cbam = configbest_acc, train_accs, test_accs = run_experiment(use_residual, use_cbam)results[f"Residual={use_residual}, CBAM={use_cbam}"] = {"best_acc": best_acc,"train_accs": train_accs,"test_accs": test_accs}# 打印结果比较print("\n=== Final Results Comparison ===")for config, res in results.items():print(f"{config}: Best Accuracy = {res['best_acc']:.2f}%")# 可视化训练过程plt.figure(figsize=(12, 8))for config, res in results.items():plt.plot(res['test_accs'], label=config)plt.title('Test Accuracy Comparison on CIFAR-10')plt.xlabel('Epoch')plt.ylabel('Accuracy (%)')plt.legend()plt.grid(True)plt.savefig('inception_ablation_results.png')plt.show()
@浙大疏锦行