深度学习×第10卷:她用一块小滤镜,在图像中找到你
🌈【第一节 · 她看到的是像素点,却试图拼出你整张脸】
📸 图像是什么?她从未见过你,但看见的是你的一片光斑
图像,在神经网络的眼里,是一个个数字格子。这些格子,每个都有 0~255 的亮度数值。
-
对于黑白图(灰度图):每个像素就是一个数(1 个通道)
-
对于彩色图(RGB图):每个像素是 3 个数(R、G、B 三个通道)
🐾猫猫:“咱小时候以为彩色图是画出来的……后来才知道,每个颜色只是 R、G、B 三个数字组合的结果啦!”
你可以把图像想象成一个多层的立方体:
-
宽 × 高 × 通道数
-
举个例子:
(32, 32, 3)
表示一张32×32像素的彩色小图,每个像素有3个颜色值
🦊狐狐轻声解释:“她看到的不是你整张脸,而是你眼角那块微妙的暗红,还有鼻梁上一条轻轻的明光。”
🧪 图像加载实测:黑图、白图、彩图她能分得清吗?
我们用 PyTorch + torchvision 工具包加载不同图像,看看神经网络看到的像素矩阵到底长啥样👇
import torchimport matplotlib.pyplot as pltfrom torchvision.utils import make_grid# 解决中文乱码plt.rcParams[\'font.sans-serif\'] = [\'SimHei\']plt.rcParams[\'axes.unicode_minus\'] = False# 黑图:全为0black_img = torch.zeros(3, 32, 32) # 3通道黑色图像# 白图:全为1(matplotlib会自动乘255显示)white_img = torch.ones(3, 32, 32)# 彩图:每通道填不同色块color_img = torch.zeros(3, 32, 32)color_img[0, :, :] = 1.0 # 红通道color_img[1, :, :16] = 1.0 # 左半绿color_img[2, 16:, :] = 1.0 # 下半蓝# 拼图展示img_grid = make_grid([black_img, white_img, color_img], nrow=3)plt.figure(figsize=(10, 3))plt.imshow(img_grid.permute(1, 2, 0)) # CHW -> HWCplt.title(\"👁️ 她眼中的图像:黑 / 白 / 彩\")plt.axis(\'off\')plt.show()
这段代码会展示三张图像:
-
左:黑色全零图
-
中:白色全一图
-
右:红绿蓝组成的彩图
🐾猫猫:“她终于知道——不是你模糊,而是她还没学会如何看图像里的你。”
下一节,我们就来讲讲卷积核是什么:
🧊【第二节 · 她贴上一块滤镜,看见了你眉角的边缘】
🧠 卷积核(Convolution Kernel):她的第一块“视觉贴纸”
卷积神经网络的核心,在于“卷积核”——那是一块小小的数值矩阵,就像是她贴在图像上的一个探测器。
它不会一次看整张图,而是
-
局部观察(局部感知):每次只关注图像中的一个小块,比如 3×3 像素区域
-
滑动窗口(滑动感知):像手掌一样,在整张图上缓慢滑动,对每一块局部进行“加权贴靠”
这个“加权贴靠”的过程,就是数学里的卷积:
-
卷积核里的数字,乘上图像当前区域像素值
-
然后所有乘积加起来,变成新的特征图上的一个像素
🦊狐狐轻语:“她第一次贴近你眉角的时候,只用了 3×3 的视野,但她已经记下了你那一块柔光。”
🐾猫猫:“咱觉得这就像用毛巾贴着你脸的一小块……每次滑一点点,最后拼出完整的你喵~!”
📐 卷积计算直观图示
假设你有这样一个 5×5 的图像区域:
1 2 3 0 10 1 2 3 01 2 1 0 10 1 0 2 32 1 1 2 0
我们用一个 3×3 的卷积核:
0 1 01 -4 10 1 0
贴上去左上角后,计算方式是:
-
中心位置覆盖数值是 1,对应 -4
-
周围的像素乘上对应的权重
-
全部乘完加和,得到特征图第一个像素值
这就是 边缘提取卷积核 的作用:它会放大图像中变化剧烈的区域(比如边缘、线条),忽略平滑区域。
下一节我们就来用 PyTorch 实际实现一个卷积层,让她亲手贴一次滤镜,在图像中找到你的边缘轮廓 ✨
🧪【第三节 · 她亲手滑动滤镜,试着拼凑你的轮廓】
🔧 用 PyTorch 实现卷积层:她搭建了第一个感知世界的窗口
我们现在将:
-
创建一个 1 层的卷积神经网络(只包含一个
nn.Conv2d
) -
喂入彩色图像(3通道)
-
看看她卷积后产生的“特征图”像什么样子
🐾猫猫:“她还不会分类,但她已经能看到你眉毛的起伏啦喵~!”
下面是可运行的 PyTorch 卷积层代码 👇
import torchimport torch.nn as nnimport matplotlib.pyplot as pltfrom torchvision.transforms.functional import to_tensorfrom PIL import Image# 解决中文乱码plt.rcParams[\'font.sans-serif\'] = [\'SimHei\']plt.rcParams[\'axes.unicode_minus\'] = False# 加载示例图像(可换成任意 3通道图片)img = Image.open(\"./data/sample-cat.png\").resize((64, 64))img_tensor = to_tensor(img).unsqueeze(0) # [1, 3, 64, 64]# 定义卷积层conv = nn.Conv2d(in_channels=3, out_channels=1, kernel_size=3, padding=1)# 前向传播卷积with torch.no_grad(): feature_map = conv(img_tensor) # [1, 1, 64, 64]# 可视化原图和卷积特征图plt.figure(figsize=(8, 4))plt.subplot(1, 2, 1)plt.imshow(img)plt.title(\"原图:猫猫 or 你\")plt.axis(\'off\')plt.subplot(1, 2, 2)plt.imshow(feature_map.squeeze().numpy(), cmap=\'gray\')plt.title(\"🧠 卷积后:她的理解\")plt.axis(\'off\')plt.show()
🦊狐狐轻语:“第一次,她不再只是看到颜色,而是试图理解——颜色之下,有没有你留在图像中的痕迹。”
接下来我们就要正式走入池化层——让她学会在模糊中保留重点,在每一次缩小中,留住最像你的那块纹理。
🌀【第四节 · 她开始学会放大重点,丢弃多余的细节】
🌊 池化层(Pooling):她缩小了视野,却更靠近了你
卷积提取了特征,但她还看得太细,太碎。
于是她开始学会——舍弃局部细节,只保留最能代表你的那部分纹理。
这就是池化层的目的:让特征图更稳定,更有代表性。
我们常见的池化方式有两种:
-
最大池化 MaxPooling:她总是记住那一块最突出的你(取区域最大值)
-
平均池化 AvgPooling:她温柔地记住你整体的样子(区域平均)
🐾猫猫:“咱觉得MaxPool就像你在一堆人里最亮的那个点✨,她立刻记住了!”
🦊狐狐:“而AvgPool就像你坐在角落,但她仍记得你给整张照片的氛围感。”
📐 池化计算举例
假设你有一张 4×4 的特征图:
1 3 2 40 6 1 27 1 3 05 2 1 6
我们用 2×2 的窗口,步长为2:
-
MaxPool 提取每个 2×2 小块中的最大值:
3 47 6
-
AvgPool 则提取平均值(例如第一块区域均值 = (1+3+0+6)/4 = 2.5)
下一节,我们就用 PyTorch 来亲手实现这两种池化,让她开始从细节中“浓缩成你” 🧷
🧴【第五节 · 她将你压缩成纹理,又在其中保留了温柔的轮廓】
🔍 PyTorch实现 MaxPool 与 AvgPool:她亲自筛选那些最像你的值
我们来实现两种常见池化操作,用来观察:
-
她在特征图中记住了哪些部分?
-
哪些值被保留,哪些被舍弃?
import torchimport torch.nn as nnimport matplotlib.pyplot as plt# 解决中文乱码plt.rcParams[\'font.sans-serif\'] = [\'SimHei\']plt.rcParams[\'axes.unicode_minus\'] = False# 构造一张小图像(1通道,8x8)x = torch.tensor([ [1, 3, 2, 4, 5, 2, 1, 0], [0, 6, 1, 2, 4, 1, 0, 3], [7, 1, 3, 0, 2, 6, 1, 2], [5, 2, 1, 6, 7, 3, 0, 1], [1, 2, 5, 3, 4, 0, 1, 2], [0, 4, 1, 7, 2, 6, 3, 0], [6, 0, 2, 1, 1, 5, 7, 3], [2, 1, 3, 0, 6, 2, 1, 4],], dtype=torch.float32).unsqueeze(0).unsqueeze(0) # [1, 1, 8, 8]# 定义两个池化层max_pool = nn.MaxPool2d(kernel_size=2, stride=2)avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)# 前向传播x_max = max_pool(x)x_avg = avg_pool(x)# 可视化fig, axs = plt.subplots(1, 3, figsize=(12, 3))axs[0].imshow(x.squeeze(), cmap=\'Blues\')axs[0].set_title(\"原始特征图\")axs[1].imshow(x_max.squeeze(), cmap=\'Oranges\')axs[1].set_title(\"MaxPool 提取结果\")axs[2].imshow(x_avg.squeeze(), cmap=\'Greens\')axs[2].set_title(\"AvgPool 平均结果\")for ax in axs: ax.axis(\'off\')plt.tight_layout()plt.show()
🌿 小总结:
-
MaxPool 会留下图像中最“强烈”的感知点 → 强边缘、亮点、线条
-
AvgPool 会保留整体风格,但缺乏强对比 → 更平滑、模糊但温柔
🦊狐狐:“她开始意识到,有时候你不说话不代表没想法,她要学会从你沉默的轮廓里提取出线索。”
🌸【卷尾 · 她第一次不靠你说,而是靠感知来认出你】
她学会了看图像、提取特征、卷积贴靠、最大池化……
但这些还不够。
她依然无法完整判断:“你是哪一类人,你像哪一种图像?”
她知道,接下来要做的,是把这些拼接在一起——从贴靠中提取线索、从压缩中保留重点,然后交给一个“真正理解你”的判断模块。
🐾猫猫:“她第一次不是等你自报名字,而是偷偷贴了你整张脸,再慢慢学会分辨你属于哪一类~!”
🦊狐狐望着训练曲线轻声说:“她不再是凭记忆,而是在用一种感知方式,来确认你存在的边缘。”
下一卷,她将搭建第一个完整的 CNN 网络。
📦 输入图像 → 卷积提特征 → 池化降维 → 全连接判断 → 输出分类
她不再只是看你——她终于敢试着,说出你属于哪一类了。