PyTorch框架ResNet18模型各层特征图可视化与图像重构方法问询
问题描述
我希望实现模型特定层的信息可视化,本次使用的是PyTorch框架实现的ResNet18模型。
核心需求如下:
- 提取模型训练、推理过程中,任意输入图像在各层存储的表征信息
- 基于指定卷积层(Conv Layer)输出的特征图,利用该层保留的输入图像信息完成原图重构
- 定位CNN各层卷积核对应的图像语义属性,例如识别出匹配“狗耳朵”特征的卷积核所属的网络层
实现的核心原理基础:给定输入图像向量x,CNN的每一层都会通过滤波器响应对x进行编码;若某一层包含Nl个独立滤波器,则该层会输出Nl个尺寸为Ml的特征图,其中Ml为特征图高度与宽度的乘积。
目前已完成数据加载、模型训练两部分的代码编写。
已实现代码:数据加载模块
data_transforms = { 'train': transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), 'val': transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), } data_dir = '/content/drive/MyDrive/Colab Notebooks/Animal Data/' image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'val']} dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4, shuffle=True, num_workers=4) for x in ['train', 'val']} dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']} class_names = image_datasets['train'].classes device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
已实现代码:模型训练逻辑
def train_model(model, criterion, optimizer, scheduler, num_epochs=25): since = time.time() best_model_wts = copy.deepcopy(model.state_dict()) best_acc = 0.0 for epoch in range(num_epochs): print(f'Epoch {epoch}/{num_epochs - 1}') print('-' * 10) # 每个epoch包含训练、验证两个阶段 for phase in ['train', 'val']: if phase == 'train': model.train() # 切换为训练模式 else: model.eval() # 切换为评估模式 running_loss = 0.0 running_corrects = 0 # 遍历数据集 for inputs, labels in dataloaders[phase]: inputs = inputs.to(device) labels = labels.to(device) # 梯度清零 optimizer.zero_grad() # 前向传播,仅训练阶段开启梯度记录 with torch.set_grad_enabled(phase == 'train'): outputs = model(inputs) _, preds = torch.max(outputs, 1) loss = criterion(outputs, labels) # 仅训练阶段执行反向传播、参数更新 if phase == 'train': loss.backward() optimizer.step() # 统计损失与准确率 running_loss += loss.item() * inputs.size(0) running_corrects += torch.sum(preds == labels.data) if phase == 'train': scheduler.step() epoch_loss = running_loss / dataset_sizes[phase] epoch_acc = running_corrects.double() / dataset_sizes[phase] print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}') # 保存验证集上表现最优的模型权重 if phase == 'val' and epoch_acc > best_acc: best_acc = epoch_acc best_model_wts = copy.deepcopy(model.state_dict()) print() time_elapsed = time.time() - since print(f'Training complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s') print(f'Best val Acc: {best_acc:4f}') # 加载最优权重后返回模型 model.load_state_dict(best_model_wts) return model
当前卡点:已知实现上述可视化功能需要获取模型中间层输出,但不清楚具体入手路径与实现方法。
实现方案
整套方案完全基于PyTorch原生API实现,不需要修改你已经写完的数据加载、训练逻辑,三个核心需求对应三个独立实现模块:
1. 提取任意中间层的特征图输出
两种零侵入的实现方式,优先选第一种:
- 方式1:用
register_forward_hook前向钩子,不需要修改原模型结构,给目标层注册回调函数即可自动捕获输出,示例代码:# 缓存特征图的字典 feature_maps = {} def build_hook(layer_name): def hook_fn(module, input, output): # 把特征从GPU迁到CPU,切断梯度避免显存泄漏 feature_maps[layer_name] = output.detach().cpu() return hook_fn # 加载训练完成的ResNet18模型 model = train_model(...) # 调用你已经写好的训练函数拿到训练完的模型 # 定位目标层:直接print(model)就能看到所有层的命名和层级结构 # 例如下方是获取layer3最后一个残差块的第二个卷积层输出 target_layer = model.layer3[-1].conv2 # 注册钩子 hook_handle = target_layer.register_forward_hook(build_hook('layer3_last_conv')) # 正常执行前向推理即可,特征会自动存入feature_maps test_imgs, _ = next(iter(dataloaders['val'])) test_imgs = test_imgs.to(device) model.eval() with torch.no_grad(): _ = model(test_imgs) # 取完特征必须移除钩子,避免后续训练时累积显存占用 hook_handle.remove() # 此时feature_maps['layer3_last_conv']就是目标层输出,形状为[batch_size, 卷积核数, 特征图高, 特征图宽] - 方式2:如果需要同时提取多层输出,直接用PyTorch自带的特征提取工具,一次性指定需要提取的层名即可,不需要手动注册多个钩子。
2. 基于特征图重构输入图像
重构本质是把中间层编码的特征解码回原图空间,两种实现路径按需选择:
- 快速验证方案:不需要额外训练解码器,用梯度上升做特征反转即可。固定模型参数,随机初始化一张噪声图作为待优化输入,优化目标是让噪声图前向传播到目标层的输出,和待重构原图的对应特征图的MSE损失最小,迭代300-500次后就能得到重构结果。注意越靠近输入的浅层特征重构还原度越高,深层特征因为丢失了细粒度纹理,重构结果会保留整体语义结构但细节模糊。优化时可以加入总变分损失做正则,减少重构图像的噪声。
- 高还原度方案:单独搭建一个和目标层到输入路径对称的转置卷积解码器,训练时固定ResNet18的参数,把目标层输出的特征图作为解码器输入,原始图像作为监督标签,只优化解码器参数即可。训练完成后可以直接把任意特征图输入解码器,快速得到高还原度的重构图像。
3. 卷积核语义属性定位
对应查找“狗耳朵”对应卷积核的需求,两种常用方法:
- 激活最大化法:选定待探查的单个卷积核,固定模型参数,随机初始化噪声输入,优化目标是最大化该卷积核输出特征图的平均激活值,迭代优化后的输入图像就是该卷积核最敏感的视觉模式,直接可视化就能看到卷积核对应的语义——浅层卷积核一般对应边缘、颜色、纹理,深层卷积核对应物体部件、整体类别等语义。
- 激活统计法:如果要找特定语义(比如狗耳朵)对应的卷积核,先收集一批包含目标语义的图像,批量做前向推理,统计每个卷积核在这批图像上的平均激活值,激活值排名靠前的卷积核就是和该语义强相关的卷积核。
实用技巧
- 做特征提取、可视化时必须把模型设为
eval()模式,关闭BatchNorm、Dropout的随机行为,保证结果可复现 - 单通道特征图可视化时,先把二维矩阵归一化到0-255的数值范围,即可直接用绘图工具渲染为灰度图
- 不要在训练过程中长期挂载钩子,钩子会缓存前向传播的输出,长时间不清理会占用大量显存导致OOM
- 做激活最大化、特征反转时,除了核心损失外添加L2正则、总变分正则,能大幅减少生成图像的噪声,让可视化结果更清晰
内容的提问来源于stack exchange,提问作者Manu Dwivedi
相关产品推荐
相关产品推荐

