用 model.layers[i].get_weights() 获取卷积核的 numpy 数组,形状为 (h,w,in_c,out_c),再按输出通道取均值或单通道投影(如 kernel[:,:,0,:]),用 sns.heatmap(np.flipud(k[:,:,i]), cmap='rdbu_r', robust=true) 绘制并保存为高 dpi png。

如何用 tf.keras 获取卷积层的权重张量
卷积核权重不是直接可画的数组,而是绑定在 Conv2D 层实例上的 kernel 变量(类型为 tf.Variable),需先获取其值。模型训练后,不能直接对层对象调用 .weights 就拿到可用 numpy 数组——它返回的是包含 kernel 和 bias 的列表,且仍是 tf.Tensor。
实操建议:
- 用
model.layers[i].get_weights():返回[kernel_array, bias_array],其中kernel_array是numpy.ndarray,形状为(height, width, in_channels, out_channels) - 或更精准地:用
model.layers[i].kernel.numpy()(需确保模型已构建、变量已初始化),但注意该方法在tf.function内不可用,且可能触发 eager 模式警告 - 若报错
AttributeError: 'Conv2D' object has no attribute 'kernel',说明层尚未 build,可先调用一次model(tf.zeros((1,)+input_shape))触发初始化
为什么热力图要按输出通道切分绘制
一个 Conv2D 层的卷积核是四维的,比如 (3, 3, 3, 64) 表示 64 个 3×3 的 RGB 输入卷积核。直接展平或取均值会丢失空间感受野结构信息;而把每个 out_channel 对应的 (3, 3, 3) 核强行压缩成二维会混淆通道语义(比如 R/G/B 通道混叠)。
正确做法是:对每个输出通道,分别绘制其对应的所有输入通道卷积核的「空间平均」或「单通道投影」:
- 推荐方式:取每个
(h,w,in_c,out_c)中的in_c维度均值 → 得到(h,w,out_c),再对每个out_c绘制一个热力图 - 或更直观:只看第一个输入通道(如灰度图场景),提取
kernel[:, :, 0, :],得到(h,w,out_c),避免颜色通道干扰 - 别用
np.squeeze()或reshape(-1, out_c)—— 这会让空间结构塌缩,热力图看不出卷积核形状
用 seaborn.heatmap 绘制时的关键参数设置
seaborn.heatmap 默认把矩阵当“行×列”处理,但卷积核的 (h,w) 对应图像的高×宽,直接传入会导致热力图上下颠倒(因为 matplotlib 坐标原点在左下,而图像习惯原点在左上)。
实操要点:
- 必须加
yticklabels=False, xticklabels=False关闭刻度标签,否则数字坐标会干扰视觉判断 - 加
robust=True让颜色映射忽略异常值,防止个别极大权重拉伸整个 colormap - 用
plt.gca().invert_yaxis()或在绘图前对 kernel 矩阵做np.flipud(),否则热力图顶部显示的是 kernel 的最后一行(即底部感受野) - 别用
cmap='viridis'—— 它对中间零值不敏感;改用cmap='RdBu_r'或'coolwarm',能清晰区分正负权重
import seaborn as sns import matplotlib.pyplot as plt <p>k = kernel_mean # shape: (3, 3, 64) for i in range(min(16, k.shape[2])): # 最多画 16 个 plt.subplot(4, 4, i+1) sns.heatmap(np.flipud(k[:, :, i]), cmap='RdBu_r', cbar=False, xticklabels=False, yticklabels=False, robust=True) plt.tight_layout() plt.show()</p>
保存热力图时容易被忽略的尺寸与 DPI 问题
直接 plt.savefig('kernels.png') 会导致小图糊成一团,尤其当卷积核只有 3×3 或 5×5 时,像素级细节完全丢失;而用默认 DPI(100)保存多子图布局,边框和空白区域又会挤压实际热力图区域。
必须调整:
- 用
plt.figure(figsize=(12, 12))显式控制总画布大小,子图数量多时至少设到(len(kernels)**0.5 * 3)级别 - 保存时加
dpi=300,否则打印或放大查看时全是马赛克 - 加
bbox_inches='tight'防止热力图被裁剪,但注意它可能压缩子图间距;更稳的方式是先plt.tight_layout(pad=0.5)再保存 - 别用
.jpg格式存——有损压缩会污染权重数值的视觉表达,一律用.png或.pdf
卷积核可视化真正难的不是代码,而是解释:同一层里不同通道的 kernel 看起来相似,不代表它们学到了重复特征——激活响应的差异往往藏在后续非线性与组合中。别光盯着热力图颜色深浅,先确认你取的是训练后的权重,且没意外用了初始化值。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











