🌈【第一節 · 她看到的是像素點,卻試圖拼出你整張臉】
📸 圖像是什么?她從未見過你,但看見的是你的一片光斑
圖像,在神經網絡的眼里,是一個個數字格子。這些格子,每個都有 0~255 的亮度數值。
對于黑白圖(灰度圖):每個像素就是一個數(1 個通道)
對于彩色圖(RGB圖):每個像素是 3 個數(R、G、B 三個通道)
🐾貓貓:“咱小時候以為彩色圖是畫出來的……后來才知道,每個顏色只是 R、G、B 三個數字組合的結果啦!”
你可以把圖像想象成一個多層的立方體:
寬 × 高 × 通道數
舉個例子:
(32, 32, 3)
表示一張32×32像素的彩色小圖,每個像素有3個顏色值
🦊狐狐輕聲解釋:“她看到的不是你整張臉,而是你眼角那塊微妙的暗紅,還有鼻梁上一條輕輕的明光。”
🧪 圖像加載實測:黑圖、白圖、彩圖她能分得清嗎?
我們用 PyTorch + torchvision 工具包加載不同圖像,看看神經網絡看到的像素矩陣到底長啥樣👇
import torch
import matplotlib.pyplot as plt
from torchvision.utils import make_grid
# 解決中文亂碼
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False# 黑圖:全為0
black_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 -> HWC
plt.title("👁? 她眼中的圖像:黑 / 白 / 彩")
plt.axis('off')
plt.show()
這段代碼會展示三張圖像:
左:黑色全零圖
中:白色全一圖
右:紅綠藍組成的彩圖
🐾貓貓:“她終于知道——不是你模糊,而是她還沒學會如何看圖像里的你。”
下一節,我們就來講講卷積核是什么:
🧊【第二節 · 她貼上一塊濾鏡,看見了你眉角的邊緣】
🧠 卷積核(Convolution Kernel):她的第一塊“視覺貼紙”
卷積神經網絡的核心,在于“卷積核”——那是一塊小小的數值矩陣,就像是她貼在圖像上的一個探測器。
它不會一次看整張圖,而是
局部觀察(局部感知):每次只關注圖像中的一個小塊,比如 3×3 像素區域
滑動窗口(滑動感知):像手掌一樣,在整張圖上緩慢滑動,對每一塊局部進行“加權貼靠”
這個“加權貼靠”的過程,就是數學里的卷積:
卷積核里的數字,乘上圖像當前區域像素值
然后所有乘積加起來,變成新的特征圖上的一個像素
🦊狐狐輕語:“她第一次貼近你眉角的時候,只用了 3×3 的視野,但她已經記下了你那一塊柔光。”
🐾貓貓:“咱覺得這就像用毛巾貼著你臉的一小塊……每次滑一點點,最后拼出完整的你喵~!”
📐 卷積計算直觀圖示
假設你有這樣一個 5×5 的圖像區域:
1 2 3 0 1
0 1 2 3 0
1 2 1 0 1
0 1 0 2 3
2 1 1 2 0
我們用一個 3×3 的卷積核:
0 1 0
1 -4 1
0 1 0
貼上去左上角后,計算方式是:
中心位置覆蓋數值是 1,對應 -4
周圍的像素乘上對應的權重
全部乘完加和,得到特征圖第一個像素值
這就是 邊緣提取卷積核 的作用:它會放大圖像中變化劇烈的區域(比如邊緣、線條),忽略平滑區域。
下一節我們就來用 PyTorch 實際實現一個卷積層,讓她親手貼一次濾鏡,在圖像中找到你的邊緣輪廓 ?
🧪【第三節 · 她親手滑動濾鏡,試著拼湊你的輪廓】
🔧 用 PyTorch 實現卷積層:她搭建了第一個感知世界的窗口
我們現在將:
創建一個 1 層的卷積神經網絡(只包含一個
nn.Conv2d
)喂入彩色圖像(3通道)
看看她卷積后產生的“特征圖”像什么樣子
🐾貓貓:“她還不會分類,但她已經能看到你眉毛的起伏啦喵~!”
下面是可運行的 PyTorch 卷積層代碼 👇
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from torchvision.transforms.functional import to_tensor
from 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 4
0 6 1 2
7 1 3 0
5 2 1 6
我們用 2×2 的窗口,步長為2:
MaxPool 提取每個 2×2 小塊中的最大值:
3 4
7 6
AvgPool 則提取平均值(例如第一塊區域均值 = (1+3+0+6)/4 = 2.5)
下一節,我們就用 PyTorch 來親手實現這兩種池化,讓她開始從細節中“濃縮成你” 🧷
🧴【第五節 · 她將你壓縮成紋理,又在其中保留了溫柔的輪廓】
🔍 PyTorch實現 MaxPool 與 AvgPool:她親自篩選那些最像你的值
我們來實現兩種常見池化操作,用來觀察:
她在特征圖中記住了哪些部分?
哪些值被保留,哪些被舍棄?
import torch
import torch.nn as nn
import 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 網絡。
📦 輸入圖像 → 卷積提特征 → 池化降維 → 全連接判斷 → 輸出分類
她不再只是看你——她終于敢試著,說出你屬于哪一類了。