卷积核(kernel)就是一段滑动窗口上的 stored procedure——同一个小函数在整张图上逐个位置滑过去,每到一处就对覆盖的局部像素做一次点积。关键不在"滑动",在参数共享(weight sharing):一份逻辑部署到所有位置,就像一份 CDN 边缘代码处理所有区域的请求,而不是每个像素配一套独立权重。
如果用 Day 17 的全连接层(fully-connected,每个输入连每个神经元)直接喂图像,参数会爆炸:一张 1000×1000 的图就有百万输入,接一个千神经元的隐层 = 十亿参数,且模型不知道"猫平移了 10 像素还是同一只猫"。卷积用两招破局:局部连接(每个神经元只看一小块感受野)+ 参数共享(同一个 kernel 扫全图)。于是参数从十亿降到几百,还天然获得平移不变性(translation invariance)——同一个特征出现在哪都能被同一个 kernel 抓到。
机制上,每个 kernel 学一种"局部模式探测器":第一层学边缘/颜色块,往上堆叠后感受野逐层扩大,第二层组合成纹理、角点,更高层组合成眼睛、轮子乃至整只猫。中间穿插 pooling(池化,取局部最大/平均)做降采样,缩小尺寸、增强对微小位移的鲁棒性。这套"局部 → 组合 → 抽象"的层级正是 1989 年 LeCun 的设计直觉,2012 年 AlexNet 在 ImageNet 上引爆。
import torch import torch.nn as nn # 一个卷积层:输入 3 通道(RGB),输出 16 个特征图 conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1) x = torch.randn(1, 3, 32, 32) # 1 张 32×32 的 RGB 图 y = conv(x) print(y.shape) # [1, 16, 32, 32] padding=1 保持尺寸 # 关键:参数量 = 16×3×3×3 + 16(bias) = 448,与图像大小无关 # 同样的输出若用全连接层 = 3·32·32 × 16·32·32 ≈ 8 亿参数 print(sum(p.numel() for p in conv.parameters())) # 448
⚠️ 误区:以为 kernel 是人手工设计的(像 Photoshop 的锐化/模糊滤镜)。其实 CNN 的 kernel 权重全部由反向传播自动学出来——你只定义 kernel 的尺寸和数量,长成什么探测器是数据决定的。
🎯 BigCat 场景:你做个人项目要给上千张家庭照片自动打标签/去重,第一反应可能是调云端大模型 API。但一个几层的小 CNN(或现成预训练 backbone)在本地就能跑,零 API 成本、数据不出门——理解卷积让你知道"什么时候不需要大模型"。
残差连接(residual / skip connection)就是给每一层加一条 bypass 直通线——代码写出来是 return x + f(x)。类比中间件链:每个 middleware 默认有一条"什么都不改、原样透传"的旁路,要改才改。这条旁路让"加深网络"从"必须学得更好"退化成"至少不能学得更差"。
2015 年前大家发现一个反常现象:网络加到几十层后,训练误差反而上升。注意——这不是过拟合(那是训练好、测试差),而是退化(degradation):连训练集都拟合不好。直觉上多加几层至少能学个"恒等映射(identity,原样输出)"保底,但实际优化器很难让一堆非线性层凑出恒等函数,深层梯度回传也衰减严重。
He et al. 2015 的解法极简:不让层直接学目标 H(x),而是学残差 F(x) = H(x) − x,最终输出 H(x) = F(x) + x。如果最优就是恒等映射,优化器只需把 F(x) 压到 0——比从零拟合一个恒等函数容易得多。更妙的是那个 +x 给梯度开了一条高速公路:反向传播时梯度能沿 skip 直接回到浅层,绕开链式衰减。靠这招,ResNet 把网络从十几层推到 152 层,拿下 2015 年 ImageNet 冠军,从此"残差块"成了几乎所有深网络(包括 Transformer)的标配。
import torch.nn as nn class ResidualBlock(nn.Module): def __init__(self, ch): super().__init__() self.conv1 = nn.Conv2d(ch, ch, 3, padding=1) self.conv2 = nn.Conv2d(ch, ch, 3, padding=1) self.relu = nn.ReLU() def forward(self, x): out = self.relu(self.conv1(x)) out = self.conv2(out) return self.relu(out + x) # 核心:+x 这条 skip 就是全部魔法
⚠️ 误区:以为残差连接是为了"解决梯度消失"。梯度通畅是副作用之一,原论文强调的核心是退化问题——让深层网络"易于学到恒等映射"。两者相关但不是一回事。
🎯 BigCat 场景:残差思想的迁移价值远超视觉。你设计一个多步 AI 流水线(agent / 数据处理链)时,给关键环节保留一条"原始输入直通"的旁路,能让系统在某步失灵时优雅降级而非全盘崩——这就是 output = step(x) + x 的工程哲学。
+x 让千层网络成为可能,今天的 Transformer 每个 block 都在用它。ViT 干的事就一句话:把图像"分词(tokenize)"。它把图切成 16×16 的小块(patch),每块拍平当成一个 token,于是一张图变成"一句由图块组成的话",然后直接套用 Day 1 那套处理文本的 Transformer。视觉问题被翻译成了 NLP 问题。
CNN 自带强归纳偏置(inductive bias,架构里预置的先验假设):局部性、平移不变。这让它在中小数据集上又快又准,但也限制了对全局长程关系的建模——卷积要堆很多层才能让左上角"看到"右下角。Transformer 的 self-attention 天生是全局的:任意两个 token 一步直连。Dosovitskiy et al. 2020 的赌注是——能不能扔掉卷积的先验,纯靠 attention + 海量数据让模型自己学出视觉规律?
流程:图像切 patch → 每个 patch 线性投影成向量(patch embedding)→ 加位置编码(Day 13,否则模型不知道图块的空间顺序)→ 拼一个 [CLS] token 汇总全局信息 → 喂进标准 Transformer encoder → 用 [CLS] 的输出做分类。结论很关键也很诚实:数据量是分水岭。在 ImageNet 这种"中等"规模上,ViT 干不过同级 CNN(缺了归纳偏置这个先验,等于让它从数据里重新学"局部性");但预训练数据涨到上亿张时,ViT 反超——先验少反而成了优势,因为它不被人为假设束缚。这正是 Day 14 scaling laws 的视觉版印证。
from transformers import ViTImageProcessor, ViTForImageClassification from PIL import Image # 加载在 ImageNet 上预训练好的 ViT(base, patch16) proc = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224") model = ViTForImageClassification.from_pretrained("google/vit-base-patch16-224") img = Image.open("cat.jpg") inputs = proc(images=img, return_tensors="pt") # 自动切 patch + 归一化 logits = model(**inputs).logits pred = logits.argmax(-1).item() print(model.config.id2label[pred]) # 预测类别,如 'Egyptian cat'
⚠️ 误区:以为"ViT 全面取代了 CNN"。现实是各有地盘:数据/算力有限或要快时 CNN(及其现代变体)仍是默认;超大规模预训练、要和文本/多模态打通时 ViT 占优。把它当"必选升级"会在小数据上吃亏。
🎯 BigCat 场景:ViT 的真正意义是架构统一——图像、文本、音频都能切成 token 进同一个 Transformer。理解它,你就理解了今天多模态大模型(GPT/Gemini 看图)底层为什么能"用一套引擎吃所有模态"。
CLIP 就是把图像和文本塞进同一个向量空间的双塔检索(two-tower retrieval)——你熟悉的 embedding 语义搜索(Day 4)的多模态版。一个图像编码器、一个文本编码器,训练目标是:配对的图文向量靠拢,不配对的推远。训练完,"一张猫的图"和"a photo of a cat"在空间里几乎重合。
传统视觉模型有个硬伤:只认训练时定死的类别。在 1000 类 ImageNet 上训练的模型,永远只会输出这 1000 个标签——想加一类"咖啡拉花",得重新标数据、重新训练。CLIP 要的是 zero-shot(零样本,没专门训练过也能识别任意新概念)。
Radford et al. 2021 的做法:从互联网爬 4 亿对(图,文),用对比学习(contrastive learning)训练。每个 batch 取 N 张图、N 段文,编码后算出 N×N 相似度矩阵——对角线是真实配对(正样本,要拉高),其余 N²−N 个是错配(负样本,要压低)。这就是对比学习的精髓:不预测绝对标签,只学"谁和谁该靠近"。推理时玩法很巧:把候选类别名套进模板变成文本("a photo of a {label}"),编码后和图像向量比相似度,最高的就是预测——分类问题被转成了图文检索,类别想加就加,无需重训。CLIP 因此成了 Stable Diffusion、多模态 LLM 的视觉地基。
from transformers import CLIPProcessor, CLIPModel from PIL import Image model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32") proc = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32") img = Image.open("photo.jpg") # 候选类别——任意写,无需模型训练过 labels = ["a photo of a cat", "a photo of a dog", "a coffee latte"] inputs = proc(text=labels, images=img, return_tensors="pt", padding=True) out = model(**inputs) probs = out.logits_per_image.softmax(dim=1) # 图文相似度 → 概率 print(labels[probs.argmax().item()]) # zero-shot 预测
⚠️ 误区:以为 CLIP "理解"图像内容。它学的是图文统计关联,不是真正的视觉推理——遇到训练分布外的概念、需要数数或空间推理的任务会翻车。它强在"匹配",弱在"推理"。
🎯 BigCat 场景:CLIP 让你能搭一个零训练的私人图片语义搜索——把家庭相册全部编码进向量库,然后用自然语言"孩子在海边的照片"直接检索。这是 Day 4 向量检索能力在图像上的直接延伸,本地可跑、隐私可控。
x + f(x) 让"多加一层"的最坏情况退化成"什么都没做",于是加深永远不会更糟,这给了堆叠任意深度的信心;(2) 梯度高速公路——反向传播时梯度能沿 +x 直达浅层,绕开逐层连乘的衰减。Transformer 动辄几十上百层,没有残差根本训不动,所以每个 attention 子层、每个 FFN 子层后面都接 x + sublayer(x)。更深一层看,这是个架构设计的元原则:让"什么都不做"成为默认、容易达到的状态,把学习压力集中在"相对当前该改什么"(残差)而非"从零构造全部"(绝对)。这个思想在工程上无处不在——增量更新、差分编码、git diff,都是"存残差不存全量"。