ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

PyTorch Hook机制实战:可视化神经网络中间层特征图

PyTorch Hook机制实战:可视化神经网络中间层特征图 1. 为什么要可视化神经网络的中间层输出很多初学者在跑 PyTorch 模型时往往只关注最后的 loss 和 accuracy对网络内部发生了什么基本“两眼一抹黑”。模型训练好了、测试准确率也挺高但一旦效果不好就不知道问题出在哪里到底是数据没处理好还是网络结构有 bug或者是特征提取根本没有学到有效信息这时候可视化神经网络的中间层输出就显得非常重要了。所谓中间层输出是指输入数据经过某一层或某几个层计算后得到的特征图Feature Map。比如一个卷积神经网络输入一张图片经过第一个卷积层后会得到一组特征图这些特征图保留了原始图片的局部纹理、边缘、颜色等信息经过更深层的卷积后特征图越来越抽象可能表示的是“眼睛”“轮子”“窗户”这类语义信息。可视化这些中间层输出主要有以下几个作用调试模型结构确认每一层的输出形状是否符合预期有没有出现维度爆炸或维度归零的情况。检查特征提取质量通过观察特征图判断模型是否学习到了有意义的特征而不是噪声或者纯背景。理解网络行为对深度学习初学者来说直观看到“网络是怎么一步步理解输入图片的”比看任何理论讲解都有效。辅助模型优化如果某个中间层的特征图全部为黑色或全部为白色说明该层可能失效可以考虑调整结构、初始化方式或学习率。论文和项目展示很多深度学习项目汇报或论文中都会展示中间层特征图来佐证模型设计的合理性。本文就围绕“PyTorch 可视化神经网络中间层输出”展开一个完整实战我会从原理讲解到代码实现再到常见问题排查一步步带你完成特征图的可视化。2. 环境准备与版本说明在写代码之前先把环境准备好。本文示例以常见 PyTorch 环境为例重点演示实现思路你可以根据实际环境调整版本。2.1 环境依赖需要安装以下 Python 库库名用途torchPyTorch 深度学习框架torchvision提供预训练模型、数据集、图像变换工具matplotlib绘制特征图numpy数组运算PIL图片读取和处理建议使用 Anaconda 创建独立环境避免依赖冲突。示例命令如下conda create -n viz_feature python3.10 conda activate viz_feature pip install torch torchvision matplotlib numpy pillow如果你有 GPU 环境可以安装对应 CUDA 版本的 PyTorch如果没有 GPUCPU 版本同样可以运行本文代码。2.2 版本说明本文示例基于 PyTorch 2.x 编写但核心 API如 hook、tensor 操作在 PyTorch 1.x 中同样适用。由于 PyTorch 版本迭代较快部分API可能有细微变化如果你的版本较低建议先升级到较新的稳定版。2.3 本文项目结构vis_mid_layer/ ├── main.py # 可视化主脚本 ├── utils.py # 辅助函数图片加载、特征图拼接等 └── images/ └── cat.jpg # 测试图片可自行替换如果不想写两张图也可以把所有代码放一个脚本里本文为了讲解清晰拆分成两个文件。3. 核心原理PyTorch Hook 机制可视化中间层输出最核心的技术就是 PyTorch 的hook机制。3.1 什么是 Hook在深度学习中一个模型的前向传播过程就像一条流水线数据从输入层进入依次经过每个层最终输出结果。默认情况下PyTorch 为了节省内存只保存输入输出张量中间层计算完毕后的特征图并不会单独保存。那如果我们想看某一层计算完的结果怎么办两种思路改变网络结构把这一层的输出“抠”出来直接返回。使用 hook在模型前向传播时“偷窥”这一层的输入和输出记录下来但不影响正常计算。改动网络结构虽然直观但需要重新定义模型很麻烦。而 hook 就像在流水线上装了一个摄像头不动生产线就能观察中间产物。在 PyTorch 中hook 分为两种register_forward_hook在前向传播时触发可以获取该层的输入和输出。register_backward_hook在反向传播时触发一般用于观察梯度。我们可视化中间层输出主要使用register_forward_hook。3.2 register_forward_hook 的使用方法register_forward_hook是nn.Module的方法它的用法如下def hook_fn(module, input, output): # module: 当前层 # input: 当前层的输入元组类型 # output: 当前层的输出张量 pass handle layer.register_forward_hook(hook_fn)其中hook_fn是我们自己定义的回调函数当该层前向传播完成后PyTorch 会自动调用这个函数并把当前层、输入、输出作为参数传进来。我们只需要在函数内部把输出保存到全局列表或字典中即可。需要注意的是input是一个元组通常取input[0]才是真正的输入张量。output通常是张量但个别层如某些自定义层可能返回元组需要根据实际情况处理。hook 注册后会一直生效如果不再需要可以调用handle.remove()移除。3.3 完整流程可视化中间层输出的整体流程如下定义/加载模型。确定要可视化的层并注册 forward hook。对输入图片做预处理得到符合模型输入要求的张量。将图片输入模型触发前向传播hook 自动记录中间层输出。拿到中间层输出的张量从 GPU 搬回 CPU转成 numpy 数组。使用 matplotlib 画图显示。4. 完整实战可视化 CNN 中间层特征图下面进入代码实战。这里我们用一个经典的卷积神经网络作为演示模型。为了让读者容易理解我不直接用 torchvision 里自带的现成模型而是手动定义一个简单的 CNN 模型这样每一层的结构、尺寸变化都一目了然。4.1 创建项目结构先创建项目目录mkdir vis_mid_layer cd vis_mid_layer mkdir images把一张测试图片放到images目录下。可以使用小猫、小狗等日常图片最好不要太小的截图建议分辨率在 224x224 或更大。下面是完整的utils.py负责图片读取和预处理# 文件路径vis_mid_layer/utils.py import torch from torchvision import transforms from PIL import Image def load_image(image_path, size224): 加载图片并做预处理返回一个 batch 为 1 的张量。 # 1. 定义预处理流程 transform transforms.Compose([ transforms.Resize((size, size)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 2. 读取图片 img Image.open(image_path).convert(RGB) img_tensor transform(img) # 3. 增加 batch 维度 img_tensor img_tensor.unsqueeze(0) # shape: [1, 3, size, size] return img_tensor def denormalize(tensor): 将归一化后的 tensor 转回 0~255 范围的 numpy 图片便于显示。 mean [0.485, 0.456, 0.406] std [0.229, 0.224, 0.225] # 先 clone 到 CPU再转 numpy tensor tensor.detach().cpu().clone() # 反归一化 for t, m, s in zip(tensor, mean, std): t.mul_(s).add_(m) # 把 CHW - HWC并限制在 0~1 img torch.clamp(tensor, min0, max1).numpy() img img.transpose(1, 2, 0) return img这里需要注意Resize((size, size))会把图片统一缩放到固定尺寸方便输入网络。ToTensor()会把 PIL 图片从HWC转成CHW像素值从 0~255 缩放到 0.0~1.0。Normalize使用 ImageNet 的均值和标准差这是预训练模型的常用处理方式。4.2 定义模型接下来定义我们的 CNN 模型并注册 hook。创建主脚本main.py# 文件路径vis_mid_layer/main.py import torch import torch.nn as nn import matplotlib.pyplot as plt from utils import load_image, denormalize class SimpleCNN(nn.Module): 一个简单的 CNN 模型包含 3 个卷积块 全连接分类层。 def __init__(self, num_classes10): super(SimpleCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(3, 16, kernel_size3, stride1, padding1), # 输出: [N, 16, H, W] nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), # 输出: [N, 16, H/2, W/2] nn.Conv2d(16, 32, kernel_size3, stride1, padding1), # 输出: [N, 32, H/2, W/2] nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), # 输出: [N, 32, H/4, W/4] nn.Conv2d(32, 64, kernel_size3, stride1, padding1), # 输出: [N, 64, H/4, W/4] nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), # 输出: [N, 64, H/8, W/8] ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(64, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x这个模型结构非常简单第一个卷积块输出 16 个通道。第二个卷积块输出 32 个通道。第三个卷积块输出 64 个通道。每个卷积块后面都有一个ReLU激活和MaxPool2d下采样。如果输入图是 224x224那么第一层池化后变成 112x112第二层变成 56x56第三层变成 28x28。4.3 注册 Hook 并收集中间层输出接下来我们定义 hook 函数并注册到模型的指定层上。# 文件路径vis_mid_layer/main.py # 用于保存特征图的字典 feature_maps {} def get_hook(name): 根据层名返回一个 hook 函数。 hook 函数会把该层的输出保存到 feature_maps 中。 def hook_fn(module, input, output): feature_maps[name] output.detach().cpu() print(f[Hook] {name} 输出形状: {output.shape}) return hook_fn这里output.detach().cpu()很关键detach()让张量脱离计算图避免存储梯度防止内存泄漏。cpu()把 GPU 上的张量移动到 CPU方便后续转 numpy 和画图。然后在main中完成模型实例化并注册 hookif __name__ __main__: # 1. 加载模型 model SimpleCNN(num_classes10) model.eval() # 切换到评估模式关闭 dropout 和 batch norm 的训练状态 # 2. 注册 hook model.features[0].register_forward_hook(get_hook(conv1)) model.features[2].register_forward_hook(get_hook(conv2)) model.features[5].register_forward_hook(get_hook(conv3))我们分别对三个不同层model.features[0]第一个卷积层model.features[2]第二个卷积层model.features[5]第三个卷积层分别注册 hook。注意features是一个nn.Sequential按顺序索引0: nn.Conv2d(3, 16, 3, 1, 1) 1: nn.ReLU(inplaceTrue) 2: nn.MaxPool2d(kernel_size2, stride2) 3: nn.Conv2d(16, 32, 3, 1, 1) 4: nn.ReLU(inplaceTrue) 5: nn.MaxPool2d(kernel_size2, stride2) 6: nn.Conv2d(32, 64, 3, 1, 1) 7: nn.ReLU(inplaceTrue) 8: nn.MaxPool2d(kernel_size2, stride2)所以model.features[0]是第一个卷积层。model.features[2]是第一个 MaxPool。model.features[5]是第二个 MaxPool。我这里为了演示中不同深度层的输出故意在三个不同深度位置注册 hook第一个卷积输出16通道、第一个池化后16通道但尺寸减半、第二个池化后32通道尺寸继续减半。这样可视化后对比效果更明显。注意如果你希望在 ReLU 之后、MaxPool 之前的特征图可以调整索引注册到model.features[1]或model.features[4]。4.4 加载图片并前向传播接下来加载测试图片并跑一次前向传播# 3. 加载图片 img_path images/cat.jpg input_tensor load_image(img_path, size224) # 4. 前向传播 with torch.no_grad(): output model(input_tensor) print(模型输出形状:, output.shape)这里使用torch.no_grad()是因为我们只是做推理和可视化不需要计算梯度可以省内存、加快速度。运行前向传播后hook 会被自动调用feature_maps中就会保存我们需要的中间层输出。4.5 可视化特征图最后把特征图绘制出来。对于一个卷积层的输出形状是[N, C, H, W]其中N是 batch size这里为 1。C是通道数。H和W是特征图的高和宽。为了可视化我们需要取出第一个样本feature_map[0]。选择若干通道进行显示。如果通道数很多建议挑前 16 个或 32 个通道绘制成网格不用全部画出来否则图太多、看不清。对每个通道的特征图做归一化把数值范围缩放到 0~1这样显示出来更清晰。定义一个画图函数def plot_feature_maps(feature_maps, max_channels16): 绘制多个层的特征图。 feature_maps: dictkey 为层名value 为特征图张量 [1, C, H, W] for layer_name, feat in feature_maps.items(): # 去掉 batch 维度得到 [C, H, W] feat feat[0] C feat.shape[0] channels_to_show min(C, max_channels) # 计算网格行列数 cols 4 rows (channels_to_show cols - 1) // cols fig, axes plt.subplots(rows, cols, figsize(cols * 2.5, rows * 2.5)) axes axes.flatten() if rows * cols 1 else [axes] for i in range(channels_to_show): ax axes[i] channel_map feat[i].numpy() # 归一化到 0~1便于显示 min_val channel_map.min() max_val channel_map.max() if max_val - min_val 1e-8: channel_map (channel_map - min_val) / (max_val - min_val) else: channel_map channel_map * 0 # 如果特征图全 0显示为黑 ax.imshow(channel_map, cmapviridis) ax.set_title(fch {i}) ax.axis(off) # 隐藏多余的子图 for i in range(channels_to_show, len(axes)): axes[i].axis(off) plt.suptitle(fLayer: {layer_name}, shape: {feat.shape}) plt.tight_layout() plt.show()在主函数中调用# 5. 可视化 plot_feature_maps(feature_maps, max_channels16)4.6 完整代码汇总为了方便直接运行把main.py完整代码再贴一遍# 文件路径vis_mid_layer/main.py import torch import torch.nn as nn import matplotlib.pyplot as plt from utils import load_image, denormalize class SimpleCNN(nn.Module): def __init__(self, num_classes10): super(SimpleCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(3, 16, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(16, 32, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(32, 64, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(64, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x feature_maps {} def get_hook(name): def hook_fn(module, input, output): feature_maps[name] output.detach().cpu() print(f[Hook] {name} 输出形状: {output.shape}) return hook_fn def plot_feature_maps(feature_maps, max_channels16): for layer_name, feat in feature_maps.items(): feat feat[0] C feat.shape[0] channels_to_show min(C, max_channels) cols 4 rows (channels_to_show cols - 1) // cols fig, axes plt.subplots(rows, cols, figsize(cols * 2.5, rows * 2.5)) axes axes.flatten() if rows * cols 1 else [axes] for i in range(channels_to_show): ax axes[i] channel_map feat[i].numpy() min_val channel_map.min() max_val channel_map.max() if max_val - min_val 1e-8: channel_map (channel_map - min_val) / (max_val - min_val) else: channel_map channel_map * 0 ax.imshow(channel_map, cmapviridis) ax.set_title(fch {i}) ax.axis(off) for i in range(channels_to_show, len(axes)): axes[i].axis(off) plt.suptitle(fLayer: {layer_name}, shape: {feat.shape}) plt.tight_layout() plt.show() if __name__ __main__: # 1. 加载模型 model SimpleCNN(num_classes10) model.eval() # 2. 注册 hook model.features[0].register_forward_hook(get_hook(conv1)) model.features[2].register_forward_hook(get_hook(pool1)) model.features[5].register_forward_hook(get_hook(pool2)) # 3. 加载图片 img_path images/cat.jpg input_tensor load_image(img_path, size224) # 4. 前向传播 with torch.no_grad(): output model(input_tensor) print(模型输出形状:, output.shape) # 5. 可视化 plot_feature_maps(feature_maps, max_channels16)如果你的环境缺少 matplotlib可以提前安装pip install matplotlib4.7 运行与预期结果运行脚本python main.py预期输出类似如下[Hook] conv1 输出形状: torch.Size([1, 16, 224, 224]) [Hook] pool1 输出形状: torch.Size([1, 16, 112, 112]) [Hook] pool2 输出形状: torch.Size([1, 32, 56, 56]) 模型输出形状: torch.Size([1, 10])同时会弹出三张特征图网格第一张是conv1的特征图16 个通道每个通道 224x224通常会显示边缘、轮廓、颜色斑块等信息。第二张是pool1的特征图16 个通道大小为 112x112特征图比 conv1 更紧凑轮廓更明显。第三张是pool2的特征图32 个通道大小为 56x56特征图更抽象能看出某些通道响应的是整只猫的形状区域。这就是“可视化中间层输出”的价值你可以直观看到每一层学到了什么。5. 进阶可视化指定任意层的输出如果你不想在模型外部手动注册 hook而是希望更灵活地指定任意一层可以通过遍历模型的named_modules()来实现。5.1 按模块名称注册 hookPyTorch 的每个nn.Module都有一个名字我们可以通过模块名称精确找到目标层。改造后的代码如下def register_hooks_by_names(model, layer_names, feature_maps): 根据层名称列表注册 hook。 layer_names: 例如 [features.0, features.3, features.6] handles [] for name, module in model.named_modules(): if name in layer_names: handle module.register_forward_hook(get_hook(name)) handles.append(handle) return handles然后在主函数中调用handles register_hooks_by_names(model, [features.0, features.3, features.6], feature_maps)这种方式在复杂模型中特别有用因为你不用去数Sequential的下标直接按字符串名称匹配即可。5.2 自定义模型注册 Hook 的时机需要注意的是注册 hook 最好在模型创建之后、前向传播之前完成。如果模型结构在 forward 中动态创建比如一些动态图模型hook 的注册方式会更复杂这时可以考虑直接修改 forward 或在模块内部注册。5.3 可视化最后一层全连接输出有时候我们不仅想看卷积层的特征图还想看全连接层输出的特征向量。全连接层的输出维数通常比较高无法直接画图但可以用matplotlib绘制成条形图或折线图# 假设 fc 层输出形状为 [1, 10] fc_output feature_maps[classifier.2] # 根据实际注册位置取 fc_output fc_output[0].numpy() plt.figure(figsize(10, 4)) plt.bar(range(len(fc_output)), fc_output) plt.title(Fully Connected Layer Output) plt.show()这种方式有助于观察模型对各类别的响应分数。6. 常见问题与排查思路在实际操作中可视化中间层输出会遇到一些典型问题。下面整理成表格方便快速排查。问题现象常见原因解决思路运行后没有特征图输出Hook 注册位置不对或前向传播没有走到该层检查named_modules()中的模块名确认注册的层确实参与前向传播feature_maps 为空字典Hook 函数没有被调用确认模型执行了前向传播确认 hook 注册发生在 forward 之前特征图全黑或全白特征图数值范围异常或激活值全为 0检查是否使用了detach()和归一化检查输入图片是否全黑检查网络权重是否初始化异常报错CUDA tensor cannot be converted to numpy张量仍在 GPU 上没有调用.cpu()在转 numpy 前先调用tensor.detach().cpu()图片显示颜色异常反归一化或维度顺序错误检查mean、std是否正确检查 CHW 和 HWC 转换关系显示层数太多窗口太大特征图通道数非常多限制max_channels比如只画前 16 个或 32 个通道报错Output shape is not matched输入图片尺寸与模型不匹配检查输入尺寸必要时在模型中加入AdaptiveAvgPool或调整Resize尺寸模型有 Dropout 或 BatchNorm结果每次不同忘记切换model.eval()前向传播前调用model.eval()6.1 重点排查Hook 为什么没有生效register_forward_hook最常见的坑就是注册的层没有在前向传播时被执行。比如模型内部有if分支某些层只在特定条件下执行。注册的层在nn.Sequential中但输入没有走那个Sequential。模型在forward中重新构造了层对象而不是使用self.xxx。解决方法是打印named_modules()先确认目标层的名字是否存在再确认它是否在 forward 路径上。for name, module in model.named_modules(): print(name, module)6.2 重点排查特征图数值问题如果特征图全黑大概率是数值范围问题。卷积层输出的值有可能是负数经过ReLU后为非负值但我们直接画特征图时如果是负数matplotlib 默认会裁剪成 0 到 1 之间显示出来可能整体偏黑。因此一般会对特征图做逐通道归一化channel_map (channel_map - channel_map.min()) / (channel_map.max() - channel_map.min() 1e-8)这样可以避免因为数值范围导致的可视化失真。6.3 重点排查内存和性能问题可视化中间层输出需要把每一层的输出张量保存下来。如果网络层很深、通道数很多比如 ResNet 的最后几层有 2048 个通道全部保存会占用大量内存。建议只注册你关心的少数几个层不要一次性把所有层都注册。如果确实需要保存很多层可以考虑每隔几步就保存一次并删除不再需要的临时张量。7. 最佳实践与工程建议可视化中间层输出虽然看起来只是一个小功能但在实际项目中使用得当可以显著提升调试效率。这里分享一些工程经验。7.1 使用torch.no_grad()节省内存做推理和可视化时不需要计算梯度。如果不在with torch.no_grad():下运行PyTorch 会保存前向传播过程中的中间张量用于可能的反向传播非常消耗显存。7.2 Hook 函数尽量只做保存操作Hook 函数中应该尽量避免做复杂运算尤其是不要修改原始输入的张量inplace 操作。否则可能会影响模型的前向传播结果造成很难排查的 bug。简单说hook 应该做一个“观察者”而不是“参与者”。7.3 选择合适的可视化层不同层的特征图语义不同浅层特征图保留了较多边缘、纹理、颜色信息。中间层特征图开始出现部分语义信息比如某些通道对眼睛、轮子敏感。深层特征图更抽象通常与类别语义高度相关。如果是为了调试模型建议同时看浅层和深层特征图对比分析。7.4 批量可视化时保存到本地如果你需要可视化大量图片的特征图不要在 Jupyter Notebook 或弹窗中一张张看。建议把特征图保存成文件方便后续分析。plt.imsave(foutput_{layer_name}_ch{i}.png, channel_map, cmapviridis)也可以用torchvision.utils.make_grid把多张特征图拼接成一张大图一次性保存。7.5 结合 Grad-CAM 做进一步分析可视化中间层输出只是“看特征图”如果想要更直观地定位模型“看”的是图片的哪个区域可以进一步使用 Grad-CAM梯度加权类激活映射技术。Grad-CAM 会把深层卷积特征图和梯度结合起来生成一张热力图叠加到原图上能直观展示模型分类时重点关注的区域。如果本文这种基础特征图可视化你已经掌握了下一步可以学习 Grad-CAM它属于“可视化神经网络”的进阶方向。7.6 注意模型状态切换在注册 hook 后如果是测试阶段一定要调用model.eval()。因为 Dropout 和 BatchNorm 在训练和测试阶段的行为不同如果不切换到评估模式同样的输入每次前向传播的输出可能不一样特征图也会不稳定。7.7 关于 hook 的释放一次可视化任务结束后如果不再需要 hook建议通过handle.remove()移除。尤其是在写大型循环或训练脚本时不移除 hook 可能会引起内存泄漏或者影响后续实验。for handle in handles: handle.remove()7.8 为特征图添加颜色映射在绘制特征图时建议使用viridis、jet或hot等颜色映射不要直接用默认的灰色。合理选择 colormap 可以让低值和高值区域对比更明显特征纹理更清晰。8. 总结与进一步学习方向在本文中我们完成了一套完整的 PyTorch 神经网络中间层输出可视化方案涉及以下几个关键点理解了中间层输出特征图的含义和可视化价值。掌握了 PyTorch 的register_forward_hook机制理解了 hook 的触发时机和参数含义。实现了一个简单 CNN 模型并在多个深度位置注册 hook收集前向传播过程中间的特征图。学会了用 matplotlib 绘制特征图网格并基于“归一化 通道选择”的方法展示特征图细节。了解了一些常见问题例如 hook 不生效、特征图全黑、GPU 张量转 numpy 报错等。这套方法不仅适用于简单 CNN对于 ResNet、VGG、MobileNet 等常见模型同样适用。你只需要通过named_modules()找到目标卷积层然后注册 hook代码逻辑保持不变。如果你接下来想深入学习和可视化相关的技术可以按以下路线继续扩展模型结构可视化用torchsummary或netron查看模型结构和参数信息。训练过程可视化用 TensorBoard 记录 loss、accuracy、权重直方图等。卷积核可视化直接查看每个卷积核的权重理解滤波器学到的模式。Grad-CAM 类激活热力图定位模型关注区域这对图像分类、目标检测、医学影像分析等项目非常有价值。注意力可视化对于 Transformer 类模型可视化注意力矩阵理解模型关注不同 token 的方式。不同的可视化手段解决的是不同层面的问题特征图可视化解决的是“网络怎么理解输入”的问题。卷积核可视化解决的是“网络学到了什么滤波器”的问题。Grad-CAM 解决的是“网络依据什么区域做决策”的问题。注意力可视化解决的是“序列模型中 token 之间的关联”的问题。建议你把自己训练的模型拿出来亲自动手注册几个 hook跑一遍特征图可视化。不用纠结于跑多复杂的模型先用简单的 CNN 看效果理解流程再逐步迁移到复杂模型和真实业务数据上。
返回列表