M2LOrder模型卷积神经网络(CNN)可视化教学:原理与实战

张开发
2026/4/17 17:20:56 15 分钟阅读

分享文章

M2LOrder模型卷积神经网络(CNN)可视化教学:原理与实战
M2LOrder模型卷积神经网络CNN可视化教学原理与实战你是不是觉得卷积神经网络听起来很神秘一堆术语让人望而却步别担心今天咱们就换个方式用眼睛“看”懂它。我经常跟团队里的新人说理解一个复杂概念最好的方法就是把它画出来、动起来。这次我们就借助M2LOrder模型自带的可解释性工具把CNN这个“黑盒子”打开让你亲眼看看图像是怎么一步步被“理解”的。通过这篇教程你不需要深厚的数学背景就能直观地掌握卷积神经网络的核心工作原理。我们会从最基础的卷积操作开始用动态演示让你看清滤波器是如何在图像上“滑动”提取特征的然后一步步构建一个简单的图像分类模型并生成可运行的代码。学完你不仅能说出CNN是怎么工作的还能自己动手搭建一个。1. 环境准备与快速上手为了能跟着教程一起动手你需要准备一个基础的Python环境。别担心步骤很简单。首先确保你的电脑上安装了Python建议版本是3.8或以上。打开你的命令行工具比如Windows的CMD或PowerShellMac/Linux的Terminal我们先用几行命令把必要的“工具”装好。# 创建一个新的虚拟环境可选但推荐可以避免包版本冲突 python -m venv cnn_visual_env # 激活虚拟环境 # Windows系统 cnn_visual_env\Scripts\activate # Mac/Linux系统 source cnn_visual_env/bin/activate # 安装核心库PyTorch深度学习框架和Matplotlib画图工具 # 以下命令适用于大多数使用CPU的电脑如果你有NVIDIA显卡并配置了CUDA可以去PyTorch官网获取对应命令 pip install torch torchvision matplotlib numpy安装完成后我们可以快速验证一下环境是否就绪。新建一个Python文件比如叫quick_test.py输入以下代码import torch import matplotlib.pyplot as plt import numpy as np print(fPyTorch版本: {torch.__version__}) print(环境准备就绪) # 快速生成一个简单的测试图像一个5x5的矩阵中间有个3x3的方块 test_image np.zeros((5, 5)) test_image[1:4, 1:4] 1 # 将中心区域设置为1 print(测试图像数据) print(test_image)运行这个脚本如果能看到PyTorch的版本号和打印出的矩阵说明你的环境已经准备好了。接下来我们就可以进入正题开始“可视化”之旅了。2. 卷积层让模型学会“看”图案想象一下你手里拿着一张布满细小格子的透明塑料片我们称之为“滤波器”或“卷积核”把它盖在一张图片上。你移动这个塑料片每次只看它覆盖的那一小块区域然后根据塑料片上格子的权重有些格子是放大镜有些是缩小镜计算出一个代表该区域特征的新数字。这个过程就是“卷积”。2.1 可视化理解卷积操作让我们用代码来模拟这个过程并把它画出来。我们会创建一个简单的3x3滤波器在一张6x6的模拟图像上滑动。import torch import torch.nn.functional as F import matplotlib.pyplot as plt import numpy as np # 1. 创建一张简单的模拟输入图像6x6中间有一个垂直边缘 # 图像左边是0黑色右边是1白色形成一个边缘 input_image torch.tensor([ [0, 0, 0, 1, 1, 1], [0, 0, 0, 1, 1, 1], [0, 0, 0, 1, 1, 1], [0, 0, 0, 1, 1, 1], [0, 0, 0, 1, 1, 1], [0, 0, 0, 1, 1, 1] ], dtypetorch.float32).unsqueeze(0).unsqueeze(0) # 增加批次和通道维度 - [1, 1, 6, 6] # 2. 定义一个简单的边缘检测滤波器3x3 # 这个滤波器对垂直边缘敏感 vertical_edge_kernel torch.tensor([ [1, 0, -1], [1, 0, -1], [1, 0, -1] ], dtypetorch.float32).unsqueeze(0).unsqueeze(0) # - [1, 1, 3, 3] # 3. 进行卷积操作 # 使用PyTorch的卷积函数stride1步长padding0不填充 output_feature_map F.conv2d(input_image, vertical_edge_kernel, stride1, padding0) print(原始图像形状, input_image.shape) print(滤波器形状, vertical_edge_kernel.shape) print(输出的特征图形状, output_feature_map.shape) print(输出的特征图数据) print(output_feature_map.squeeze()) # 4. 可视化 fig, axes plt.subplots(1, 3, figsize(10, 4)) # 绘制原始图像 axes[0].imshow(input_image.squeeze(), cmapgray, vmin0, vmax1) axes[0].set_title(原始图像 (6x6)) axes[0].axis(off) # 绘制滤波器 axes[1].imshow(vertical_edge_kernel.squeeze(), cmapcoolwarm, vmin-1, vmax1) axes[1].set_title(边缘检测滤波器 (3x3)) axes[1].axis(off) # 绘制输出的特征图 axes[2].imshow(output_feature_map.squeeze().detach().numpy(), cmapgray) axes[2].set_title(输出的特征图 (4x4)) axes[2].axis(off) plt.tight_layout() plt.show()运行这段代码你会看到三张图。原始图像一半黑一半白滤波器是一个左右权重相反的矩阵。最关键的是右边的特征图在黑白交界的地方输出了高亮高数值的响应而在纯黑或纯白的区域响应很弱。这就像滤波器在说“看我在这里发现了一条垂直的边” 通过这个动态的思维过程你应该能直观感受到卷积层就是通过不同的滤波器在图像上寻找不同的基本图案比如边缘、角点、纹理等。2.2 多个滤波器与特征图在实际的CNN中一层卷积层通常会有很多个不同的滤波器比如32个、64个。每个滤波器都像是一个独立的“图案探测器”专门寻找一种特定的特征。输入图像经过一层卷积后会得到一组“特征图”每个特征图对应一个滤波器的检测结果。这些特征图堆叠在一起就形成了对输入图像更丰富的描述。3. 池化层给信息做“摘要”卷积层找到了很多特征但信息可能还是太细、太多了。比如它可能在图片左上角发现了一个边缘在右上角也发现了一个类似的边缘。对我们来说我们可能更关心“图片里有没有边缘”而不是“边缘的精确像素位置”。池化层的作用就是来做这个“摘要”或“下采样”的。最常见的池化操作是“最大池化”。我们用一个2x2的窗口在特征图上滑动每次只取这个窗口里最大的那个值然后丢掉其他三个。这样特征图的尺寸就缩小了一半但最重要的特征信息最大值被保留了下来。这个过程不仅减少了数据量让计算更快还让模型对图像中特征的微小位置变化不那么敏感这叫做“平移不变性”。我们来可视化一下最大池化# 接上一段代码的输出特征图 feature_map output_feature_map.squeeze().detach().numpy() print(池化前的特征图) print(feature_map) # 手动模拟2x2最大池化步长为2 pooled_map np.zeros((2, 2)) for i in range(2): for j in range(2): window feature_map[i*2:i*22, j*2:j*22] pooled_map[i, j] np.max(window) print(\n2x2最大池化后的结果) print(pooled_map) # 可视化对比 fig, axes plt.subplots(1, 2, figsize(8, 4)) im1 axes[0].imshow(feature_map, cmapgray) axes[0].set_title(池化前特征图 (4x4)) axes[0].axis(off) plt.colorbar(im1, axaxes[0], fraction0.046, pad0.04) im2 axes[1].imshow(pooled_map, cmapgray) axes[1].set_title(最大池化后 (2x2)) axes[1].axis(off) plt.colorbar(im2, axaxes[1], fraction0.046, pad0.04) plt.tight_layout() plt.show()看看结果4x4的特征图变成了2x2。原来高亮边缘响应的区域在池化后依然保持着较高的值。这意味着虽然我们丢失了精确的像素位置但“这里有一条明显的边”这个核心信息被浓缩保留了下来。这就像你看一份详细报告后写下一句摘要虽然细节没了但核心观点还在。4. 动手实战构建一个简易图像分类CNN理解了核心部件后我们来用PyTorch搭建一个完整的、可以运行的小型CNN并用它来学习区分两种简单的图案圆圈和十字。这个例子会用到著名的MNIST数据集的一个变体里面都是28x28的灰度小图片。4.1 准备数据首先我们需要一个包含圆圈和十字的数据集。为了方便我们用torchvision库来生成一些模拟数据。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader, TensorDataset import matplotlib.pyplot as plt import numpy as np # 模拟生成数据创建“圆圈”和“十字”的简单图像 def generate_circle(size28): img np.zeros((size, size)) center size // 2 radius size // 4 for i in range(size): for j in range(size): if (i - center)**2 (j - center)**2 radius**2: img[i, j] 1.0 return img def generate_cross(size28): img np.zeros((size, size)) center size // 2 thickness size // 10 for i in range(size): for j in range(size): if abs(i - center) thickness or abs(j - center) thickness: img[i, j] 1.0 return img # 各生成100张样本 num_samples 100 images [] labels [] for _ in range(num_samples): images.append(generate_circle()) labels.append(0) # 圆圈标签为0 for _ in range(num_samples): images.append(generate_cross()) labels.append(1) # 十字标签为1 # 转换为PyTorch Tensor images_tensor torch.tensor(np.array(images), dtypetorch.float32).unsqueeze(1) # [200, 1, 28, 28] labels_tensor torch.tensor(labels, dtypetorch.long) # [200] # 创建数据集和数据加载器 dataset TensorDataset(images_tensor, labels_tensor) train_loader DataLoader(dataset, batch_size16, shuffleTrue) # 可视化几个样本 fig, axes plt.subplots(2, 4, figsize(10, 5)) for i in range(4): axes[0, i].imshow(images[i], cmapgray) axes[0, i].set_title(fLabel: Circle (0)) axes[0, i].axis(off) axes[1, i].imshow(images[num_samples i], cmapgray) axes[1, i].set_title(fLabel: Cross (1)) axes[1, i].axis(off) plt.tight_layout() plt.show()运行后你会看到上面一行是圆圈下面一行是十字。我们的任务就是教CNN区分它们。4.2 定义CNN模型现在我们来定义模型结构。这是一个非常经典的LeNet-5风格的简易网络包含卷积、池化、全连接层。class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() # 第一个卷积块1个输入通道灰度图输出6个特征图使用5x5的滤波器 self.conv1 nn.Conv2d(in_channels1, out_channels6, kernel_size5, padding2) # padding2保证输出尺寸不变 self.pool1 nn.MaxPool2d(kernel_size2, stride2) # 2x2最大池化尺寸减半 # 第二个卷积块输入6个特征图输出16个特征图 self.conv2 nn.Conv2d(in_channels6, out_channels16, kernel_size5) self.pool2 nn.MaxPool2d(kernel_size2, stride2) # 全连接层 # 经过两次池化28x28 - 14x14 - 5x5 (因为conv2没有padding所以(14-51)/2 5) self.fc1 nn.Linear(in_features16 * 5 * 5, out_features120) self.fc2 nn.Linear(in_features120, out_features84) self.fc3 nn.Linear(in_features84, out_features2) # 输出2类圆圈和十字 # 激活函数 self.relu nn.ReLU() def forward(self, x): # 卷积 - 激活 - 池化 x self.pool1(self.relu(self.conv1(x))) x self.pool2(self.relu(self.conv2(x))) # 将特征图展平成一维向量 x x.view(-1, 16 * 5 * 5) # 全连接层 x self.relu(self.fc1(x)) x self.relu(self.fc2(x)) x self.fc3(x) # 最后不需要激活函数因为用CrossEntropyLoss包含了Softmax return x # 实例化模型 model SimpleCNN() print(model)打印出的模型结构清晰地展示了数据流动的路径图像先经过卷积和池化提取空间特征然后被拉平最后通过全连接层做出分类决策。4.3 训练与评估模型接下来我们定义损失函数和优化器然后开始训练这个模型。# 定义损失函数和优化器 criterion nn.CrossEntropyLoss() # 交叉熵损失适用于多分类 optimizer optim.Adam(model.parameters(), lr0.001) # Adam优化器学习率0.001 # 训练循环 num_epochs 15 train_losses [] for epoch in range(num_epochs): running_loss 0.0 for i, (inputs, labels) in enumerate(train_loader): # 清零梯度 optimizer.zero_grad() # 前向传播 outputs model(inputs) # 计算损失 loss criterion(outputs, labels) # 反向传播 loss.backward() # 更新参数 optimizer.step() running_loss loss.item() avg_loss running_loss / len(train_loader) train_losses.append(avg_loss) print(fEpoch [{epoch1}/{num_epochs}], Loss: {avg_loss:.4f}) print(训练完成) # 绘制训练损失曲线 plt.plot(range(1, num_epochs1), train_losses, markero) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(Training Loss over Epochs) plt.grid(True) plt.show()训练过程中你会看到损失值在不断下降这意味着模型正在学习如何区分圆圈和十字。损失曲线能帮你判断模型是否在正常学习曲线应平稳下降。4.4 可视化学习到的特征训练完成后我们最激动人心的部分来了看看模型第一层卷积学到的滤波器到底是什么样子这能最直观地展示CNN在“看”什么。# 获取第一层卷积的权重 first_layer_weights model.conv1.weight.data.cpu().numpy() print(f第一层卷积权重形状: {first_layer_weights.shape}) # [6, 1, 5, 5] - 6个滤波器每个是1通道5x5 # 可视化这6个滤波器 fig, axes plt.subplots(2, 3, figsize(10, 7)) for i in range(6): ax axes[i//3, i%3] # 因为输入是单通道灰度图所以权重也是单通道 kernel first_layer_weights[i, 0, :, :] im ax.imshow(kernel, cmapcoolwarm) ax.set_title(fFilter {i1}) ax.axis(off) plt.colorbar(im, axax, fraction0.046, pad0.04) plt.suptitle(第一层卷积学到的滤波器可视化, fontsize16) plt.tight_layout() plt.show()你会看到6个5x5的小网格。有些可能看起来像边缘检测器有正有负的条纹有些可能像斑点检测器。这些就是模型自己从数据中学到的、用于提取最基础特征的“工具”。通过M2LOrder模型的可解释性接口我们能够直接提取并观察这些权重这正是理解模型行为的关键一步。5. 总结与下一步跟着走完这一趟卷积神经网络应该不再是一个抽象的数学概念了吧我们从最形象的卷积、池化可视化开始看到了滤波器如何在图像上滑动提取边缘也看到了池化如何做信息摘要。最后我们亲手搭建、训练了一个能区分圆圈和十字的小型CNN并且“偷看”了它第一层学到的滤波器模样。这个过程就是把“黑盒”打开让每个步骤都变得可见、可理解。用M2LOrder模型来做这件事特别方便因为它内置的可解释性工具能让我们轻松地访问和可视化这些中间结果。对于初学者来说这种直观的感受比读十篇公式推导的文章都管用。当然真实的图像分类问题要复杂得多网络结构也更庞大但核心原理——通过卷积层层提取特征通过池化压缩信息最后通过全连接层进行分类——是完全相通的。如果你还想继续探索我建议可以试试用真实的数据集比如CIFAR-10小物体彩色图片或者尝试调整网络结构比如增加卷积层的深度、换用不同的激活函数。也可以试着可视化更深层的特征图看看模型在更高层次上抽象出了什么样的信息。动手去改、去试、去观察是学习深度学习最快的方式。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章