news 2026/9/18 8:44:19

手算卷积入门:从6×6图像到CNN每一层的空间逻辑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手算卷积入门:从6×6图像到CNN每一层的空间逻辑

1. 这不是“背概念”,而是搞懂CNN里每一层到底在干啥——从手算卷积开始

你翻过十几份《机器学习入门》讲义,看到“全连接层”和“卷积层”的对比表格,上面写着“参数量少”“局部感受野”“权值共享”——但这些词像贴在玻璃上的标签,你看得见,却摸不着它怎么动、怎么算、怎么影响最终结果。我带过三届本科生做图像分类项目,最常听到的困惑不是“什么是反向传播”,而是:“为什么我改了卷积核大小,准确率突然掉5%?”“为什么padding=1时输出尺寸没变,但模型反而学得更慢?”“明明输入是3×224×224,为什么第一个卷积层输出是32×224×224,而不是32×222×222?”这些问题,没有一个能靠背定义解决。

这节内容,我们不讲PPT里的结构图,不列公式推导,就用一支笔、一张纸、一个3×3的卷积核、一张6×6的灰度图,从零手算一遍:卷积运算到底发生了什么?填充(padding)是在补哪几行哪几列?步幅(stride)怎么决定输出尺寸?三维数据(比如RGB图)上卷积核怎么滑动?多个卷积核输出的特征图,为什么能堆叠成通道维度?批处理(batch)又如何让整个计算过程变成矩阵乘法?全连接层和卷积层的本质区别,不在名字,而在它们对数据空间关系的“态度”——前者把图像当一串毫无关联的像素数字,后者则坚信:相邻像素之间有不可割裂的语义联系。这种信念,直接决定了CNN能识别出猫的耳朵,而全连接网络只能记住“第12789个像素亮、第12790个像素暗”这种毫无泛化能力的模式。如果你正准备西电或山大机器学习期末考试,或者刚跑通CSND上的人脸识别开源项目却卡在模型结构调整上,这篇就是为你写的。它不教你“怎么调参”,而是让你看清参数背后的空间逻辑——这才是真正入门的起点。

2. 全连接层 vs 卷积层:两种世界观的碰撞

2.1 全连接层:把世界拍扁再数点

先看全连接层(Fully Connected Layer,FC)。假设你有一张28×28的手写数字图片(MNIST),共784个像素。全连接层第一件事,就是把这张二维图像“拍扁”成一个784维的向量。然后,它为这个向量中的每一个元素,都分配一个独立的权重,去连接下一层的每一个神经元。如果下一层有128个神经元,那这一层就要维护784×128=99,328个参数。每个参数都是独立训练的,彼此之间没有任何约束。

提示:你可以把全连接层想象成一个极度近视的裁判——他只盯着单个像素点看,完全不知道左边那个像素是鼻子,右边那个是眼睛。他判断“这是数字7”全靠统计:过去1000张7的图片里,第342号像素平均亮度是0.82,第567号是0.15……于是他记下这两条规则。但一旦遇到一张旋转了15度的7,或者墨水晕开导致第342号像素变暗,这套规则立刻失效。因为它的知识是“离散点”的集合,不是“空间结构”的理解。

这就是全连接层的致命缺陷:参数爆炸空间不变性缺失。参数爆炸意味着你需要海量数据去填满这些参数,否则极易过拟合;空间不变性缺失意味着模型无法理解“猫耳朵无论出现在左上角还是右下角,都是耳朵”这种基本常识。这也是为什么纯FC网络在ImageNet上连10%准确率都难破——它根本没学会“看图”,只是在记“像素组合”。

2.2 卷积层:用同一把尺子丈量所有角落

卷积层彻底换了一种思路。它不给每个像素配独立权重,而是设计一把“小尺子”——这就是卷积核(Kernel/Filter),比如3×3大小。这把尺子只含9个可学习参数。它从图像左上角开始,与对应区域的9个像素做逐元素相乘再求和,得到一个标量输出;然后,它向右平移一步(步幅stride=1),再算一次;走到尽头后,向下平移一步,继续……直到扫完整张图。

关键来了:这把尺子在整个图像上是“共享”的。左上角用的3×3参数,右下角用的还是同一组9个参数。这意味着,模型学到的不是“某个位置的纹理”,而是“某种通用的局部模式”,比如边缘、线条、斑点。无论猫的耳朵出现在图中哪个位置,只要它包含相似的局部结构,这把尺子就能检测出来——这就是平移不变性(Translation Invariance)的来源。

注意:卷积核的“共享”不是人为规定,而是由问题本质决定的。图像的底层规律(如边缘检测)在空间上是重复出现的,强行让不同位置用不同参数,等于假设“左上角的边缘和右下角的边缘是两种完全不同的东西”,这违背视觉感知的基本原理。卷积操作,本质上是对图像进行一种受控的、局部的、参数共享的线性变换。

2.3 参数量对比:一个数字说明一切

我们来算一笔硬账。还是那张28×28的图,输入通道C_in=1(灰度图),输出通道C_out=32(即用32把不同的“尺子”去检测32种不同模式)。

  • 全连接层方案:输入784维 → 输出32维,参数量 = 784 × 32 =25,088
  • 卷积层方案:3×3卷积核 × 1输入通道 × 32输出通道 =288

差距接近100倍。而且,这个差距会随着图像变大急剧扩大。一张224×224的RGB图(3通道),全连接层输入维度是224×224×3=150,528,哪怕只连到64个神经元,参数也高达9.6M;而一个3×3卷积层,参数量仅为3×3×3×64=1,728。卷积层的参数效率,不是优化技巧,而是对图像数据物理本质的尊重。

2.4 感受野:卷积层的“视野”如何层层扩大

全连接层的每个神经元,感受野(Receptive Field)就是整张图——它“看见”全部,却“理解”无能。卷积层则不同,它的感受野是逐步构建的。第一层卷积核3×3,感受野就是3×3;第二层再用3×3卷积核作用于第一层的输出特征图,其感受野就变成了5×5;第三层叠加后,感受野扩大到7×7……以此类推。

实操心得:我在调试LeNet-5时发现,如果第一层卷积核太大(比如7×7),虽然单层感受野大,但丢失了精细纹理信息,导致对数字笔画粗细变化不敏感;而用两个3×3卷积层堆叠,总参数更少,感受野同样达到7×7,却能分层提取“边缘→线条→部件”。这就是“小核深堆叠”优于“大核浅层”的核心原因——它模拟了人类视觉皮层的层级处理机制。

3. 卷积神经网络结构:从LeNet-5到现代架构的演进逻辑

3.1 LeNet-5:教科书级的奠基结构(1998)

Yann LeCun提出的LeNet-5,至今仍是理解CNN结构的黄金模板。它处理32×32的手写数字图,结构清晰:

Input (32×32) ↓ Conv1: 6个5×5卷积核, stride=1, no padding → Output: 6×28×28 ↓ ReLU (非线性激活) ↓ Pool1: 2×2最大池化, stride=2 → Output: 6×14×14 ↓ Conv2: 16个5×5卷积核, stride=1, no padding → Output: 16×10×10 ↓ ReLU ↓ Pool2: 2×2最大池化, stride=2 → Output: 16×5×5 ↓ FC1: 全连接层, 120个神经元 → Output: 120 ↓ ReLU ↓ FC2: 全连接层, 84个神经元 → Output: 84 ↓ Output Layer: 10个神经元 (对应0-9数字)

这里的关键设计选择,全是为了解决实际问题:

  • 为什么Conv1用5×5?当时计算资源有限,5×5能在保留足够局部信息的同时,控制参数量。实测发现3×3虽更省,但对MNIST这种简单任务易欠拟合。
  • 为什么Pool1后尺寸减半?最大池化(Max Pooling)不仅降维,更重要的是提供平移鲁棒性——即使数字轻微移动几个像素,池化后的最大值大概率不变,模型判别更稳定。
  • 为什么Conv2输出16个通道?第一层检测基础边缘,第二层需要组合这些边缘形成更复杂模式(如“L形拐角”、“十字交叉”),16个核提供了足够的模式组合空间。

注意:LeNet-5的“C3层”其实是局部连接(不是全连接),这是早期为减少参数做的折中。现代CNN已普遍采用全连接卷积(即每个输出通道连接所有输入通道),因为GPU算力足以支撑,且效果更好。

3.2 AlexNet:深度革命的引爆点(2012)

AlexNet将CNN带入主流,其结构颠覆了LeNet的温和路线:

  • 更深:8层(5卷积+3全连接),远超LeNet的5层。
  • 更大卷积核:第一层用11×11,第二层用5×5——当时为弥补ReLU激活函数初期饱和问题,用大核强行提取强特征。
  • 重叠池化:池化步幅(stride)小于池化窗口(pool size),如3×3池化用stride=2,避免特征图过度缩小。
  • Dropout:在最后两个全连接层间加入Dropout(p=0.5),显著抑制过拟合。

但AlexNet最革命性的贡献,是证明了深度带来的表征能力跃迁。它在ImageNet上将错误率从26%降到16%,不是靠微调,而是靠堆叠更多非线性变换层。这直接催生了VGG、ResNet等更深架构。

3.3 VGG与ResNet:宽度、深度与残差的平衡术

  • VGG-16:用大量3×3小卷积核堆叠(如13个3×3替代1个7×7),参数量虽增,但感受野等效、梯度更平滑、易于训练。其结构像一条直筒:卷积→ReLU→卷积→ReLU→池化,循环往复。

  • ResNet-50:解决深度网络梯度消失问题。它引入残差连接(Skip Connection)Output = F(x) + x。当F(x)学习困难时,网络可自动退化为恒等映射(F(x)=0),保证深层信息不被破坏。ResNet的“块”(Block)设计,让50层甚至1000层网络成为可能。

实操心得:我在实验室部署一个工业质检模型时,用VGG-16做基线,准确率92.3%;换成ResNet-18后,提升到94.7%,但推理速度慢15%。最终选了ResNet-18的轻量化版(通道数减半),准确率94.1%,速度反超VGG。这说明:结构选择不是“越深越好”,而是要匹配你的硬件(GPU显存)、延迟要求(实时检测需<50ms)和数据规模(小样本用浅网更稳)。

3.4 现代CNN的通用骨架:Conv → BN → ReLU → Pool(可选)

今天绝大多数CNN,无论用于医疗影像分割还是自动驾驶感知,都遵循一个稳健骨架:

  1. 卷积层(Conv):提取局部特征,核大小通常3×3(兼顾感受野与参数效率)。
  2. 批归一化(BatchNorm):对每一批(batch)数据的每个通道做归一化(减均值、除标准差),极大加速训练、提升稳定性。没有BN,ResNet根本训不动。
  3. 激活函数(ReLU):引入非线性。ReLU(f(x)=max(0,x))因计算简单、缓解梯度消失,成为绝对主流。
  4. 池化层(Pooling):传统CNN必备,但现代趋势是用带步幅的卷积(Strided Convolution)替代。例如,用3×3卷积核、stride=2,既能降维又能学习特征,比固定池化更灵活。

这个骨架的每一环,都不是凭空而来。BN解决了深层网络训练难,ReLU解决了Sigmoid梯度消失,Strided Conv解决了池化信息损失——它们共同构成了现代CNN的“工业标准”。

4. 卷积运算手把手拆解:从二维到三维,从单核到多核

4.1 二维卷积:纸上谈兵不如动手算

取一张极简的6×6灰度图(数值代表像素亮度):

[1 2 3 4 5 6] [7 8 9 10 11 12] [13 14 15 16 17 18] [19 20 21 22 23 24] [25 26 27 28 29 30] [31 32 33 34 35 36]

再取一个3×3卷积核(检测垂直边缘):

[-1 0 1] [-1 0 1] [-1 0 1]

Step 1:确定输出尺寸
公式:Output_H = floor((H + 2*P - K) / S) + 1
其中H=6(输入高),P=0(无填充),K=3(核高),S=1(步幅)
Output_H = floor((6+0-3)/1)+1 = 4
同理,Output_W=4。所以输出是4×4矩阵。

Step 2:手算第一个输出值(位置[0,0])
卷积核左上角对齐输入图左上角3×3区域:

输入区域: [1 2 3] 卷积核: [-1 0 1] [7 8 9] [-1 0 1] [13 14 15] [-1 0 1]

计算:(1×-1)+(2×0)+(3×1)+(7×-1)+(8×0)+(9×1)+(13×-1)+(14×0)+(15×1)
=-1 + 0 + 3 -7 + 0 + 9 -13 + 0 + 15 = 7

Step 3:滑动计算
向右滑1步,计算[0,1]位置;再滑得[0,2]、[0,3];然后下移一行……最终得到4×4输出矩阵。你会发现,输出中数值大的位置,正是原图中垂直亮度突变(如从1→7→13的列)的地方——这把“尺子”真的在检测垂直边缘!

提示:手算一遍的价值,远超看十遍公式。你会真切感受到:卷积不是抽象数学,而是像素间的加权投票。每个输出值,都是局部区域对某种模式的“置信度打分”。

4.2 填充(Padding):为什么需要“补边”?

继续用6×6图,但这次加padding=1(四周各补一圈0):

[0 0 0 0 0 0 0 0] [0 1 2 3 4 5 6 0] [0 7 8 9 10 11 12 0] [0 13 14 15 16 17 18 0] [0 19 20 21 22 23 24 0] [0 25 26 27 28 29 30 0] [0 31 32 33 34 35 36 0] [0 0 0 0 0 0 0 0]

新尺寸8×8。再用3×3核、stride=1卷积:Output_H = floor((8+0-3)/1)+1 = 6
输出变回6×6!这就是same padding的核心目的:保持空间尺寸不变,让网络可以堆叠多层而不至于特征图迅速萎缩。

注意:padding不是“作弊”,而是对边界信息的合理处理。不补零,边界像素只参与一次卷积(因为核无法完全覆盖),导致边界特征弱于中心。补零虽引入人工值,但实践中效果远好于不补。更高级的padding如“reflect”(镜像反射)或“replicate”(复制边缘),在特定任务(如图像修复)中效果更好。

4.3 步幅(Stride):控制“滑动速度”的杠杆

还是6×6图,padding=0,但stride=2:

Output_H = floor((6+0-3)/2)+1 = floor(3/2)+1 = 1+1 = 2
输出2×2。这意味着卷积核每次向右/下跳2格,大幅降低计算量,同时实现下采样。

实操心得:在移动端部署模型时,我常用stride=2的卷积替代池化层。因为卷积能学习下采样模式(如保留重要纹理),而池化是固定规则(取最大值),前者更鲁棒。但要注意:stride过大(如>3)会导致信息丢失严重,尤其对小目标检测不利。

4.4 三维卷积:RGB图上的真实战场

真实图像不是6×6,而是3×224×224(C×H×W)。卷积核也不再是3×3,而是3×3×3——3个通道(R,G,B)各有一个3×3平面,共27个参数。

运算时,输入图的3个通道,分别与卷积核的3个通道平面做二维卷积,得到3个中间结果;再将这3个结果对应位置相加,得到1个标量输出。所以,一个3×3×3卷积核,作用于3×224×224输入,输出是1×222×222(假设no padding, stride=1)。

4.5 多维卷积核:从1个“尺子”到32把“尺子”

上例中,我们只用1个卷积核,输出1个特征图。但实际中,我们会同时用32个不同的3×3×3卷积核(每个核27个参数,共32×27=864参数),每个核独立扫描输入,生成自己的特征图。最终,32个特征图堆叠起来,形成32×222×222的输出张量——这就是“输出通道数(C_out)”的含义。

关键理解:通道(Channel)不是颜色,而是特征维度。第1个通道可能响应“红色垂直边缘”,第2个响应“蓝色水平线条”,第32个响应“绿色圆形斑点”。网络通过训练,自动学会分配这些“探测器”的职责。

4.6 批处理(Batch):让GPU吃饱的工程智慧

单张图计算太慢。我们一次喂给网络32张图(batch_size=32),输入张量变为32×3×224×224。卷积运算时,32张图是并行处理的:同一个卷积核,在32张图上同时滑动计算。输出张量变为32×32×222×222

这不仅是提速,更是训练稳定的关键。BatchNorm依赖批次统计量(均值、方差),没有batch,BN就失效。Adam优化器的自适应学习率,也基于batch梯度估计。batch_size=1(逐样本训练)在CNN中几乎不可行——它会让BN崩溃,梯度噪声巨大,收敛极慢。

注意:batch_size不是越大越好。显存有限制:32×32×222×222×4字节(float32)≈ 200MB,这只是中间特征图,还没算参数和梯度。我常用经验法则:显存(GB)÷ 2 ≈ 最大batch_size(以ResNet-50为基准)。

5. 实操全流程:用PyTorch从零构建一个可运行的CNN模块

5.1 环境与数据准备:5分钟搭好脚手架

# 创建虚拟环境(推荐) conda create -n cnn-tutorial python=3.9 conda activate cnn-tutorial pip install torch torchvision matplotlib numpy

数据用torchvision内置的MNIST(手写数字),它已预处理为28×28灰度图:

import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader # 数据预处理:转Tensor + 归一化(0-1缩放到-1到1) transform = transforms.Compose([ transforms.ToTensor(), # HWC→CHW, uint8→float32, [0,255]→[0,1] transforms.Normalize((0.1307,), (0.3081,)) # MNIST均值/标准差 ]) train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform) test_dataset = datasets.MNIST('./data', train=False, transform=transform) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)

提示:Normalize参数不是随便写的。0.1307是MNIST所有训练图的像素均值,0.3081是标准差。归一化让输入分布更集中,加速收敛。不归一化也能训,但学习率要调小10倍,且容易震荡。

5.2 构建CNN模型:逐层解析代码含义

class SimpleCNN(nn.Module): def __init__(self, num_classes=10): super().__init__() # 第一层卷积:32个3x3核,输入1通道(灰度),输出32通道 self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, stride=1, padding=1) # 批归一化:对32个通道分别归一化 self.bn1 = nn.BatchNorm2d(32) # 第二层卷积:64个3x3核,输入32通道,输出64通道 self.conv2 = nn.Conv2d(32, 64, 3, 1, 1) self.bn2 = nn.BatchNorm2d(64) # 最大池化:2x2窗口,步幅2,降维 self.pool = nn.MaxPool2d(2, 2) # 全连接层:输入是池化后特征图展平的维度 # 经过conv1→bn1→conv2→bn2→pool,尺寸:28→28→28→28→14 # 所以输入维度 = 64 * 14 * 14 = 12544 self.fc1 = nn.Linear(64 * 14 * 14, 128) self.fc2 = nn.Linear(128, num_classes) def forward(self, x): # x shape: [64, 1, 28, 28] (batch, channel, height, width) x = self.pool(torch.relu(self.bn1(self.conv1(x)))) # [64, 32, 14, 14] x = self.pool(torch.relu(self.bn2(self.conv2(x)))) # [64, 64, 7, 7] x = x.view(x.size(0), -1) # 展平: [64, 64*7*7] = [64, 3136] x = torch.relu(self.fc1(x)) # [64, 128] x = self.fc2(x) # [64, 10] return x model = SimpleCNN() print(model)

代码逐行解读:

  • nn.Conv2d(1, 32, 3, 1, 1)in_channels=1(灰度图),out_channels=32(32个卷积核),kernel_size=3(3×3核),stride=1(步幅1),padding=1(same padding,保尺寸)。
  • nn.BatchNorm2d(32):对32个通道分别做BN。注意:BN层参数(gamma, beta)是按通道学习的,不是全局。
  • x.view(x.size(0), -1)x.size(0)是batch_size(64),-1表示自动计算剩余维度(64×7×7=3136)。这是展平操作,为接全连接层做准备。

5.3 训练循环:不只是loss下降,更要监控特征质量

criterion = nn.CrossEntropyLoss() # 分类任务标准损失 optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam比SGD更稳 def train_one_epoch(model, train_loader, criterion, optimizer, device): model.train() running_loss = 0.0 correct = 0 total = 0 for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() # 清空上一轮梯度 output = model(data) # 前向传播 loss = criterion(output, target) # 计算损失 loss.backward() # 反向传播,计算梯度 optimizer.step() # 更新权重 running_loss += loss.item() _, predicted = output.max(1) # 取概率最大类别 total += target.size(0) correct += predicted.eq(target).sum().item() # 每100个batch打印一次 if batch_idx % 100 == 0: print(f'Batch {batch_idx}, Loss: {loss.item():.4f}, Acc: {100.*correct/total:.2f}%') return running_loss / len(train_loader), 100.*correct/total # 训练主循环 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) for epoch in range(5): print(f'\nEpoch {epoch+1}/5') train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device) print(f'Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%')

实操心得:训练时务必监控训练准确率而非只看loss。我见过太多案例:loss在降,但准确率卡在10%(随机猜),说明模型根本没学,可能是学习率太大导致梯度爆炸,或数据加载出错(标签全为0)。另外,optimizer.zero_grad()必须在每个batch开头调用,否则梯度会累积,导致爆炸。

5.4 可视化卷积核:看看你的网络学到了什么

训练完,我们可以提取第一层卷积核,可视化它们在学什么:

import matplotlib.pyplot as plt import numpy as np # 获取第一层卷积核权重 (32, 1, 3, 3) kernels = model.conv1.weight.data.cpu().numpy() # 绘制前16个核 fig, axes = plt.subplots(4, 4, figsize=(8, 8)) for i in range(16): ax = axes[i//4, i%4] # kernels[i] shape: (1, 3, 3) -> squeeze to (3, 3) kernel_img = kernels[i, 0] ax.imshow(kernel_img, cmap='gray', vmin=kernel_img.min(), vmax=kernel_img.max()) ax.set_title(f'Kernel {i+1}') ax.axis('off') plt.tight_layout() plt.show()

你会看到,大部分核呈现明显的边缘检测模式(类似我们手算的[-1,0,1]变体),有些是水平,有些是垂直,有些是45度斜线。这证实了CNN确实在学习图像的底层结构。

注意:可视化要在训练充分后(如5个epoch后)进行。刚开始的核是随机初始化的,看不出模式。另外,不要期望看到“猫耳朵”这种高级模式——那是深层卷积核的任务,第一层只负责基础纹理。

6. 常见问题排查与避坑指南:那些文档里不会写的细节

6.1 问题速查表:症状、原因、解决方案

症状可能原因解决方案
训练loss不下降,准确率≈10%(随机)学习率过大导致梯度爆炸;数据标签加载错误(全为同一类);模型未正确移到GPU降低学习率10倍;用print(target[:5])检查标签;确认model.to(device)data.to(device)
训练loss下降但验证准确率不上升过拟合;验证集未归一化(与训练集不一致);batch_size太小导致BN统计不准加Dropout或L2正则;确保验证集用相同transform;增大batch_size(≥32)
GPU显存不足(CUDA out of memory)batch_size过大;模型层数过多;中间特征图太大(如大图+大核)减小batch_size;用更小的输入尺寸(如224→112);启用梯度检查点(gradient checkpointing)
模型收敛极慢,loss震荡大缺少BatchNorm;学习率太小;数据未归一化在每个卷积层后加BN;增大学习率;检查transform是否包含Normalize
输出尺寸计算错误(如期望28×28却得26×26)padding设置错误;stride理解偏差;忽略了卷积核本身的尺寸用公式floor((H+2P-K)/S)+1手动验算;记住:padding=0时,输出尺寸必然减小

6.2 那些“踩过坑”才懂的经验

坑1:认为“padding=1”就是四周补一圈,却忽略不同框架默认行为
PyTorch的Conv2d(padding=1)对称填充(上下左右各1),但某些旧框架或自定义实现可能只补一侧。最稳妥的方法:显式计算输出尺寸,用torch.nn.functional.pad()手动补零验证。

坑2:在测试时忘记model.eval()
BN和Dropout在训练和测试时行为不同。训练时BN用batch统计,测试时用running_mean/var;Dropout训练时随机置零,测试时全连接。忘记model.eval(),会导致测试结果极不稳定。标准流程:

model.eval() # 切换到评估模式 with torch.no_grad(): # 关闭梯度计算,省显存 for data, target in test_loader: output = model(data) # ... 计算指标

坑3:用view()展平时维度算错
常见错误:x.view(-1, 64*7*7)。这是错的!-1会把整个batch压成一维,得到[64*7*7, 64*7*7]的荒谬形状。正确是x.view(x.size(0), -1)x.size(0)明确指定batch维度。

坑4:可视化卷积核时颜色失真
直接plt.imshow(kernel)会因自动缩放导致细节丢失。必须用vmin/vmax固定范围,或用plt.imshow(kernel, cmap='RdBu', center=0)突出正负权重。

6.3 西电/山大期末高频考点实战解析

结合你提到的“西电机器学习期末”“山东大学机器学习期末”,我整理了3个必考计算题:

考点1:输出尺寸计算(必考)
题:输入3×224×224,卷积层:64个3×3核,stride=2,padding=0。求输出尺寸。
解:H_out = floor((224+0-3)/2)+1 = floor(221/2)+1 = 110+1 = 111,同理W_out=111C_out=6464×111×111

考点2:参数量计算(必考)
题:同上卷积层,求该层参数量(不含bias)。
解:3×3×3×64 = 1728(3输入通道×3×3核×64输出通道)

考点3:感受野计算(中频)
题:LeNet-5中,Conv1(5×5)→ Pool1(2×2, s=2)→ Conv2(5×5),求Conv

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 8:43:35

uniapp多端项目H5接口404原因与本地代理配置完全指南

如果你在uniapp项目里同时开发小程序、App和H5&#xff0c;大概率会碰到这个经典场景&#xff1a;代码在小程序端跑得好好的&#xff0c;接口数据正常返回&#xff0c;一切岁月静好&#xff1b;一切到H5&#xff0c;在浏览器里一打开&#xff0c;接口直接给你一个红色的404。第…

作者头像 李华
网站建设 2026/9/18 8:42:53

给 OpenAI 评估脚本的 API 入口交给 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 8:41:49

时序差分学习:从TD(0)到DQN的核心原理与实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 8:39:49

编译原理第八章:语义分析、属性文法与中间代码全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 8:37:41

HarmonyOS适配Steam Guard的TOTP算法实践

1. 项目背景与核心价值Steam平台作为全球最大的数字游戏分发平台之一&#xff0c;其账号安全机制一直备受关注。Steam Guard作为平台的两步验证系统&#xff0c;通过TOTP&#xff08;基于时间的一次性密码&#xff09;算法为账号提供额外的安全层。传统的Steam Guard验证通常需…

作者头像 李华