You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 20:39:20