树莓派用来做网站,dw设计软件,有源码后怎么做网站,网站年费Day46 通道注意力(SE注意力)
不同CNN层的特征图#xff1a;不同通道的特征图什么是注意力#xff1a;注意力家族#xff0c;类似于动物园#xff0c;都是不同的模块#xff0c;好不好试了才知道。通道注意力#xff1a;模型的定义和插入的位置通道注意力后的特征图和热力…Day46 通道注意力(SE注意力)
不同CNN层的特征图不同通道的特征图什么是注意力注意力家族类似于动物园都是不同的模块好不好试了才知道。通道注意力模型的定义和插入的位置通道注意力后的特征图和热力图
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np# 设置中文字体支持
plt.rcParams[font.family] [SimHei]
plt.rcParams[axes.unicode_minus] False # 解决负号显示问题# 检查GPU是否可用
device torch.device(cuda if torch.cuda.is_available() else cpu)
print(f使用设备: {device})# 1. 数据预处理
# 训练集使用多种数据增强方法提高模型泛化能力
train_transform transforms.Compose([# 随机裁剪图像从原图中随机截取32x32大小的区域transforms.RandomCrop(32, padding4),# 随机水平翻转图像概率0.5transforms.RandomHorizontalFlip(),# 随机颜色抖动亮度、对比度、饱和度和色调随机变化transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.1),# 随机旋转图像最大角度15度transforms.RandomRotation(15),# 将PIL图像或numpy数组转换为张量transforms.ToTensor(),# 标准化处理每个通道的均值和标准差使数据分布更合理transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])# 测试集仅进行必要的标准化保持数据原始特性标准化不损失数据信息可还原
test_transform transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])# 2. 加载CIFAR-10数据集
train_dataset datasets.CIFAR10(root./data,trainTrue,downloadTrue,transformtrain_transform # 使用增强后的预处理
)test_dataset datasets.CIFAR10(root./data,trainFalse,transformtest_transform # 测试集不使用增强
)# 3. 创建数据加载器
batch_size 64
train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue)
test_loader DataLoader(test_dataset, batch_sizebatch_size, shuffleFalse)
# 4. 定义CNN模型的定义替代原MLP
class CNN(nn.Module):def __init__(self):super(CNN, self).__init__() # 继承父类初始化# ---------------------- 第一个卷积块 ----------------------# 卷积层1输入3通道RGB输出32个特征图卷积核3x3边缘填充1像素self.conv1 nn.Conv2d(in_channels3, # 输入通道数图像的RGB通道out_channels32, # 输出通道数生成32个新特征图kernel_size3, # 卷积核尺寸3x3像素padding1 # 边缘填充1像素保持输出尺寸与输入相同)# 批量归一化层对32个输出通道进行归一化加速训练self.bn1 nn.BatchNorm2d(num_features32)# ReLU激活函数引入非线性公式max(0, x)self.relu1 nn.ReLU()# 最大池化层窗口2x2步长2特征图尺寸减半32x32→16x16self.pool1 nn.MaxPool2d(kernel_size2, stride2) # stride默认等于kernel_size# ---------------------- 第二个卷积块 ----------------------# 卷积层2输入32通道来自conv1的输出输出64通道self.conv2 nn.Conv2d(in_channels32, # 输入通道数前一层的输出通道数out_channels64, # 输出通道数特征图数量翻倍kernel_size3, # 卷积核尺寸不变padding1 # 保持尺寸16x16→16x16卷积后→8x8池化后)self.bn2 nn.BatchNorm2d(num_features64)self.relu2 nn.ReLU()self.pool2 nn.MaxPool2d(kernel_size2) # 尺寸减半16x16→8x8# ---------------------- 第三个卷积块 ----------------------# 卷积层3输入64通道输出128通道self.conv3 nn.Conv2d(in_channels64, # 输入通道数前一层的输出通道数out_channels128, # 输出通道数特征图数量再次翻倍kernel_size3,padding1 # 保持尺寸8x8→8x8卷积后→4x4池化后)self.bn3 nn.BatchNorm2d(num_features128)self.relu3 nn.ReLU() # 复用激活函数对象节省内存self.pool3 nn.MaxPool2d(kernel_size2) # 尺寸减半8x8→4x4# ---------------------- 全连接层分类器 ----------------------# 计算展平后的特征维度128通道 × 4x4尺寸 128×162048维self.fc1 nn.Linear(in_features128 * 4 * 4, # 输入维度卷积层输出的特征数out_features512 # 输出维度隐藏层神经元数)# Dropout层训练时随机丢弃50%神经元防止过拟合self.dropout nn.Dropout(p0.5)# 输出层将512维特征映射到10个类别CIFAR-10的类别数self.fc2 nn.Linear(in_features512, out_features10)def forward(self, x):# 输入尺寸[batch_size, 3, 32, 32]batch_size批量大小3通道数32x32图像尺寸# ---------- 卷积块1处理 ----------x self.conv1(x) # 卷积后尺寸[batch_size, 32, 32, 32]padding1保持尺寸x self.bn1(x) # 批量归一化不改变尺寸x self.relu1(x) # 激活函数不改变尺寸x self.pool1(x) # 池化后尺寸[batch_size, 32, 16, 16]32→16是因为池化窗口2x2# ---------- 卷积块2处理 ----------x self.conv2(x) # 卷积后尺寸[batch_size, 64, 16, 16]padding1保持尺寸x self.bn2(x)x self.relu2(x)x self.pool2(x) # 池化后尺寸[batch_size, 64, 8, 8]# ---------- 卷积块3处理 ----------x self.conv3(x) # 卷积后尺寸[batch_size, 128, 8, 8]padding1保持尺寸x self.bn3(x)x self.relu3(x)x self.pool3(x) # 池化后尺寸[batch_size, 128, 4, 4]# ---------- 展平与全连接层 ----------# 将多维特征图展平为一维向量[batch_size, 128*4*4] [batch_size, 2048]x x.view(-1, 128 * 4 * 4) # -1自动计算批量维度保持批量大小不变x self.fc1(x) # 全连接层2048→512尺寸变为[batch_size, 512]x self.relu3(x) # 激活函数复用relu3与卷积块3共用x self.dropout(x) # Dropout随机丢弃神经元不改变尺寸x self.fc2(x) # 全连接层512→10尺寸变为[batch_size, 10]未激活直接输出logitsreturn x # 输出未经过Softmax的logits适用于交叉熵损失函数# 初始化模型
model CNN()
model model.to(device) # 将模型移至GPU如果可用criterion nn.CrossEntropyLoss() # 交叉熵损失函数
optimizer optim.Adam(model.parameters(), lr0.001) # Adam优化器# 引入学习率调度器在训练过程中动态调整学习率--训练初期使用较大的 LR 快速降低损失训练后期使用较小的 LR 更精细地逼近全局最优解。
# 在每个 epoch 结束后需要手动调用调度器来更新学习率可以在训练过程中调用 scheduler.step()
scheduler optim.lr_scheduler.ReduceLROnPlateau(optimizer, # 指定要控制的优化器这里是Adammodemin, # 监测的指标是最小化如损失函数patience3, # 如果连续3个epoch指标没有改善才降低LRfactor0.5 # 降低LR的比例新LR 旧LR × 0.5
)
# 5. 训练模型记录每个 iteration 的损失
def train(model, train_loader, test_loader, criterion, optimizer, scheduler, device, epochs):model.train() # 设置为训练模式# 记录每个 iteration 的损失all_iter_losses [] # 存储所有 batch 的损失iter_indices [] # 存储 iteration 序号# 记录每个 epoch 的准确率和损失train_acc_history []test_acc_history []train_loss_history []test_loss_history []for epoch in range(epochs):running_loss 0.0correct 0total 0for batch_idx, (data, target) in enumerate(train_loader):data, target data.to(device), target.to(device) # 移至GPUoptimizer.zero_grad() # 梯度清零output model(data) # 前向传播loss criterion(output, target) # 计算损失loss.backward() # 反向传播optimizer.step() # 更新参数# 记录当前 iteration 的损失iter_loss loss.item()all_iter_losses.append(iter_loss)iter_indices.append(epoch * len(train_loader) batch_idx 1)# 统计准确率和损失running_loss iter_loss_, predicted output.max(1)total target.size(0)correct predicted.eq(target).sum().item()# 每100个批次打印一次训练信息if (batch_idx 1) % 100 0:print(fEpoch: {epoch1}/{epochs} | Batch: {batch_idx1}/{len(train_loader)} f| 单Batch损失: {iter_loss:.4f} | 累计平均损失: {running_loss/(batch_idx1):.4f})# 计算当前epoch的平均训练损失和准确率epoch_train_loss running_loss / len(train_loader)epoch_train_acc 100. * correct / totaltrain_acc_history.append(epoch_train_acc)train_loss_history.append(epoch_train_loss)# 测试阶段model.eval() # 设置为评估模式test_loss 0correct_test 0total_test 0with torch.no_grad():for data, target in test_loader:data, target data.to(device), target.to(device)output model(data)test_loss criterion(output, target).item()_, predicted output.max(1)total_test target.size(0)correct_test predicted.eq(target).sum().item()epoch_test_loss test_loss / len(test_loader)epoch_test_acc 100. * correct_test / total_testtest_acc_history.append(epoch_test_acc)test_loss_history.append(epoch_test_loss)# 更新学习率调度器scheduler.step(epoch_test_loss)print(fEpoch {epoch1}/{epochs} 完成 | 训练准确率: {epoch_train_acc:.2f}% | 测试准确率: {epoch_test_acc:.2f}%)# 绘制所有 iteration 的损失曲线plot_iter_losses(all_iter_losses, iter_indices)# 绘制每个 epoch 的准确率和损失曲线plot_epoch_metrics(train_acc_history, test_acc_history, train_loss_history, test_loss_history)return epoch_test_acc # 返回最终测试准确率# 6. 绘制每个 iteration 的损失曲线
def plot_iter_losses(losses, indices):plt.figure(figsize(10, 4))plt.plot(indices, losses, b-, alpha0.7, labelIteration Loss)plt.xlabel(IterationBatch序号)plt.ylabel(损失值)plt.title(每个 Iteration 的训练损失)plt.legend()plt.grid(True)plt.tight_layout()plt.show()# 7. 绘制每个 epoch 的准确率和损失曲线
def plot_epoch_metrics(train_acc, test_acc, train_loss, test_loss):epochs range(1, len(train_acc) 1)plt.figure(figsize(12, 4))# 绘制准确率曲线plt.subplot(1, 2, 1)plt.plot(epochs, train_acc, b-, label训练准确率)plt.plot(epochs, test_acc, r-, label测试准确率)plt.xlabel(Epoch)plt.ylabel(准确率 (%))plt.title(训练和测试准确率)plt.legend()plt.grid(True)# 绘制损失曲线plt.subplot(1, 2, 2)plt.plot(epochs, train_loss, b-, label训练损失)plt.plot(epochs, test_loss, r-, label测试损失)plt.xlabel(Epoch)plt.ylabel(损失值)plt.title(训练和测试损失)plt.legend()plt.grid(True)plt.tight_layout()plt.show()# 8. 执行训练和测试
epochs 50 # 增加训练轮次为了确保收敛
print(开始使用CNN训练模型...)
final_accuracy train(model, train_loader, test_loader, criterion, optimizer, scheduler, device, epochs)
print(f训练完成最终测试准确率: {final_accuracy:.2f}%)# # 保存模型
# torch.save(model.state_dict(), cifar10_cnn_model.pth)
# print(模型已保存为: cifar10_cnn_model.pth)def visualize_feature_maps(model, test_loader, device, layer_names, num_images3, num_channels9):可视化指定层的特征图修复循环冗余问题参数:model: 模型test_loader: 测试数据加载器layer_names: 要可视化的层名称如[conv1, conv2, conv3]num_images: 可视化的图像总数num_channels: 每个图像显示的通道数取前num_channels个通道model.eval() # 设置为评估模式class_names [飞机, 汽车, 鸟, 猫, 鹿, 狗, 青蛙, 马, 船, 卡车]# 从测试集加载器中提取指定数量的图像避免嵌套循环images_list, labels_list [], []for images, labels in test_loader:images_list.append(images)labels_list.append(labels)if len(images_list) * test_loader.batch_size num_images:break# 拼接并截取到目标数量images torch.cat(images_list, dim0)[:num_images].to(device)labels torch.cat(labels_list, dim0)[:num_images].to(device)with torch.no_grad():# 存储各层特征图feature_maps {}# 保存钩子句柄hooks []# 定义钩子函数捕获指定层的输出def hook(module, input, output, name):feature_maps[name] output.cpu() # 保存特征图到字典# 为每个目标层注册钩子并保存钩子句柄for name in layer_names:module getattr(model, name)hook_handle module.register_forward_hook(lambda m, i, o, nname: hook(m, i, o, n))hooks.append(hook_handle)# 前向传播触发钩子_ model(images)# 正确移除钩子for hook_handle in hooks:hook_handle.remove()# 可视化每个图像的各层特征图仅一层循环for img_idx in range(num_images):img images[img_idx].cpu().permute(1, 2, 0).numpy()# 反标准化处理恢复原始像素值img img * np.array([0.2023, 0.1994, 0.2010]).reshape(1, 1, 3) np.array([0.4914, 0.4822, 0.4465]).reshape(1, 1, 3)img np.clip(img, 0, 1) # 确保像素值在[0,1]范围内# 创建子图num_layers len(layer_names)fig, axes plt.subplots(1, num_layers 1, figsize(4 * (num_layers 1), 4))# 显示原始图像axes[0].imshow(img)axes[0].set_title(f原始图像\n类别: {class_names[labels[img_idx]]})axes[0].axis(off)# 显示各层特征图for layer_idx, layer_name in enumerate(layer_names):fm feature_maps[layer_name][img_idx] # 取第img_idx张图像的特征图fm fm[:num_channels] # 仅取前num_channels个通道num_rows int(np.sqrt(num_channels))num_cols num_channels // num_rows if num_rows ! 0 else 1# 创建子图网格layer_ax axes[layer_idx 1]layer_ax.set_title(f{layer_name}特征图 \n)# 加个换行让文字分离上去layer_ax.axis(off) # 关闭大子图的坐标轴# 在大子图内创建小网格for ch_idx, channel in enumerate(fm):ax layer_ax.inset_axes([ch_idx % num_cols / num_cols, (num_rows - 1 - ch_idx // num_cols) / num_rows, 1/num_cols, 1/num_rows])ax.imshow(channel.numpy(), cmapviridis)ax.set_title(f通道 {ch_idx 1})ax.axis(off)plt.tight_layout()plt.show()# 调用示例按需修改参数
layer_names [conv1, conv2, conv3]
visualize_feature_maps(modelmodel,test_loadertest_loader,devicedevice,layer_nameslayer_names,num_images5, # 可视化5张测试图像 → 输出5张大图num_channels9 # 每张图像显示前9个通道的特征图
)# 新增通道注意力模块SE模块
class ChannelAttention(nn.Module):通道注意力模块(Squeeze-and-Excitation)def __init__(self, in_channels, reduction_ratio16):参数:in_channels: 输入特征图的通道数reduction_ratio: 降维比例用于减少参数量super(ChannelAttention, self).__init__()# 全局平均池化 - 将空间维度压缩为1x1保留通道信息self.avg_pool nn.AdaptiveAvgPool2d(1)# 全连接层 激活函数用于学习通道间的依赖关系self.fc nn.Sequential(# 降维压缩通道数减少计算量nn.Linear(in_channels, in_channels // reduction_ratio, biasFalse),nn.ReLU(inplaceTrue),# 升维恢复原始通道数nn.Linear(in_channels // reduction_ratio, in_channels, biasFalse),# Sigmoid将输出值归一化到[0,1]表示通道重要性权重nn.Sigmoid())def forward(self, x):参数:x: 输入特征图形状为 [batch_size, channels, height, width]返回:加权后的特征图形状不变batch_size, channels, height, width x.size()# 1. 全局平均池化[batch_size, channels, height, width] → [batch_size, channels, 1, 1]avg_pool_output self.avg_pool(x)# 2. 展平为一维向量[batch_size, channels, 1, 1] → [batch_size, channels]avg_pool_output avg_pool_output.view(batch_size, channels)# 3. 通过全连接层学习通道权重[batch_size, channels] → [batch_size, channels]channel_weights self.fc(avg_pool_output)# 4. 重塑为二维张量[batch_size, channels] → [batch_size, channels, 1, 1]channel_weights channel_weights.view(batch_size, channels, 1, 1)# 5. 将权重应用到原始特征图上逐通道相乘return x * channel_weights # 输出形状[batch_size, channels, height, width]class CNN(nn.Module):def __init__(self):super(CNN, self).__init__() # ---------------------- 第一个卷积块 ----------------------self.conv1 nn.Conv2d(3, 32, 3, padding1)self.bn1 nn.BatchNorm2d(32)self.relu1 nn.ReLU()# 新增插入通道注意力模块SE模块self.ca1 ChannelAttention(in_channels32, reduction_ratio16) self.pool1 nn.MaxPool2d(2, 2) # ---------------------- 第二个卷积块 ----------------------self.conv2 nn.Conv2d(32, 64, 3, padding1)self.bn2 nn.BatchNorm2d(64)self.relu2 nn.ReLU()# 新增插入通道注意力模块SE模块self.ca2 ChannelAttention(in_channels64, reduction_ratio16) self.pool2 nn.MaxPool2d(2) # ---------------------- 第三个卷积块 ----------------------self.conv3 nn.Conv2d(64, 128, 3, padding1)self.bn3 nn.BatchNorm2d(128)self.relu3 nn.ReLU()# 新增插入通道注意力模块SE模块self.ca3 ChannelAttention(in_channels128, reduction_ratio16) self.pool3 nn.MaxPool2d(2) # ---------------------- 全连接层分类器 ----------------------self.fc1 nn.Linear(128 * 4 * 4, 512)self.dropout nn.Dropout(p0.5)self.fc2 nn.Linear(512, 10)def forward(self, x):# ---------- 卷积块1处理 ----------x self.conv1(x) x self.bn1(x) x self.relu1(x) x self.ca1(x) # 应用通道注意力x self.pool1(x) # ---------- 卷积块2处理 ----------x self.conv2(x) x self.bn2(x) x self.relu2(x) x self.ca2(x) # 应用通道注意力x self.pool2(x) # ---------- 卷积块3处理 ----------x self.conv3(x) x self.bn3(x) x self.relu3(x) x self.ca3(x) # 应用通道注意力x self.pool3(x) # ---------- 展平与全连接层 ----------x x.view(-1, 128 * 4 * 4) x self.fc1(x) x self.relu3(x) x self.dropout(x) x self.fc2(x) return x # 重新初始化模型包含通道注意力模块
model CNN()
model model.to(device) # 将模型移至GPU如果可用criterion nn.CrossEntropyLoss() # 交叉熵损失函数
optimizer optim.Adam(model.parameters(), lr0.001) # Adam优化器# 引入学习率调度器在训练过程中动态调整学习率--训练初期使用较大的 LR 快速降低损失训练后期使用较小的 LR 更精细地逼近全局最优解。
# 在每个 epoch 结束后需要手动调用调度器来更新学习率可以在训练过程中调用 scheduler.step()
scheduler optim.lr_scheduler.ReduceLROnPlateau(optimizer, # 指定要控制的优化器这里是Adammodemin, # 监测的指标是最小化如损失函数patience3, # 如果连续3个epoch指标没有改善才降低LRfactor0.5 # 降低LR的比例新LR 旧LR × 0.5
)# 训练模型复用原有的train函数
print(开始训练带通道注意力的CNN模型...)
final_accuracy train(model, train_loader, test_loader, criterion, optimizer, scheduler, device, epochs50)
print(f训练完成最终测试准确率: {final_accuracy:.2f}%)# 可视化空间注意力热力图显示模型关注的图像区域
def visualize_attention_map(model, test_loader, device, class_names, num_samples3):可视化模型的注意力热力图展示模型关注的图像区域model.eval() # 设置为评估模式with torch.no_grad():for i, (images, labels) in enumerate(test_loader):if i num_samples: # 只可视化前几个样本breakimages, labels images.to(device), labels.to(device)# 创建一个钩子捕获中间特征图activation_maps []def hook(module, input, output):activation_maps.append(output.cpu())# 为最后一个卷积层注册钩子获取特征图hook_handle model.conv3.register_forward_hook(hook)# 前向传播触发钩子outputs model(images)# 移除钩子hook_handle.remove()# 获取预测结果_, predicted torch.max(outputs, 1)# 获取原始图像img images[0].cpu().permute(1, 2, 0).numpy()# 反标准化处理img img * np.array([0.2023, 0.1994, 0.2010]).reshape(1, 1, 3) np.array([0.4914, 0.4822, 0.4465]).reshape(1, 1, 3)img np.clip(img, 0, 1)# 获取激活图最后一个卷积层的输出feature_map activation_maps[0][0].cpu() # 取第一个样本# 计算通道注意力权重使用SE模块的全局平均池化channel_weights torch.mean(feature_map, dim(1, 2)) # [C]# 按权重对通道排序sorted_indices torch.argsort(channel_weights, descendingTrue)# 创建子图fig, axes plt.subplots(1, 4, figsize(16, 4))# 显示原始图像axes[0].imshow(img)axes[0].set_title(f原始图像\n真实: {class_names[labels[0]]}\n预测: {class_names[predicted[0]]})axes[0].axis(off)# 显示前3个最活跃通道的热力图for j in range(3):channel_idx sorted_indices[j]# 获取对应通道的特征图channel_map feature_map[channel_idx].numpy()# 归一化到[0,1]channel_map (channel_map - channel_map.min()) / (channel_map.max() - channel_map.min() 1e-8)# 调整热力图大小以匹配原始图像from scipy.ndimage import zoomheatmap zoom(channel_map, (32/feature_map.shape[1], 32/feature_map.shape[2]))# 显示热力图axes[j1].imshow(img)axes[j1].imshow(heatmap, alpha0.5, cmapjet)axes[j1].set_title(f注意力热力图 - 通道 {channel_idx})axes[j1].axis(off)plt.tight_layout()plt.show()# 调用可视化函数
visualize_attention_map(model, test_loader, device, class_names, num_samples3) 浙大疏锦行