问题:
1.resnet
2.纹理,结构,语义;后面两个特征怎么理解
3.Stable Diffusion 是什么?
4.unet最底层bottlen bolck是干什么的,里面细节
5.diffusion里面的预测噪声网络,timestep怎么进去的?
1. ResNet 到底是什么?为什么需要它?
先不要看 ResNet,先想一个很朴素的问题。
假设我们想让神经网络识别猫。
最开始:
text
图片
↓
卷积
↓
卷积
↓
分类
后来大家想:
网络是不是越深越厉害?
于是:
text
10层
↓
20层
↓
50层
↓
100层
理论上,网络越深,表达能力应该越强。
但实际出现了一个奇怪现象:
网络加深之后,连训练集上的表现都变差了。
这不是过拟合。
因为过拟合通常是:
text
训练集:很好
测试集:不好
而这里是:
text
训练集:都变差了
这说明:
网络太深以后,优化变困难了。
这个现象叫:
Degradation Problem,退化问题。
一个特别通俗的例子
假设现在有一个 20 层网络,已经很好用了。
现在我非得再加 10 层。
理论上,这 10 层至少可以什么都不做,对吧?
比如:
text
输入:猫的特征
↓
新加的10层
↓
还是原来的猫的特征
数学上就是:H(x)=x
这叫:
Identity Mapping,恒等映射。
也就是:
输入什么,输出什么。
听起来很简单。
但问题是:
让很多层卷积网络准确学成"什么都不改变",其实并没有想象中容易。
ResNet 的聪明之处来了
ResNet 说:
既然:H(x)=x
不好学,
那我干脆别让网络直接学:H(x)
而是让它学习:F(x)=H(x)-x
于是:H(x)=F(x)+x
这就是 ResNet 最核心的公式。
为什么这样反而容易?
如果最好的情况就是:H(x)=x
那么:F(x)=H(x)-x
所以:F(x)=0
也就是说:
普通网络需要学习:
"怎样把输入原封不动地复制出来?"
而 ResNet 只需要学习:
"我什么都别改,输出 0。"
通常:
学一个接近 0 的残差,比让很多层精确模拟 identity mapping 更容易。
什么叫"残差"?
这个词其实没有那么玄。
假设:
text
原始输入 x = 100
希望得到 H(x) = 103
那么真正需要学习的变化只有:103-100=3
这个:3
就是 residual:
相对于原输入,需要修改多少。
所以 ResNet 的思路其实是:
不要重新生成所有东西,只学习"需要修改的那一小部分"。
ResNet Block 长什么样?
最简单的情况:
text
┌──────────────────────┐
│ │
│ ▼
x ──→ Conv ─→ ReLU ─→ Conv ─────── (+) ─→ ReLU
│ ▲
└──────────────────────┘
更准确地说:
text
┌──────────── x ─────────────┐
│ │
│ ↓
x → Conv → BN → ReLU → Conv → BN → + → ReLU
上面那条直接从:
x
跑到最后的线就是:
Shortcut / Skip Connection
最后:
y=F(x)+x
其中:
text
F(x)
就是两层卷积学习出来的变化。
ResNet 为什么还能帮助梯度传播?
text
梯度
↓
门
↓
门
↓
门
↓
门
↓
前面
每经过一层都可能越来越小。
ResNet:
text
────────────────
梯度 ←──────── 快速通道
↑
普通网络
所以 shortcut 不仅帮助 forward,也帮助 backward。
但有个问题
假如:x尺寸是:
text
56 × 56 × 64
而 (F(x)) 是:
text
28 × 28 × 128
你不能直接:x+F(x)
就像:
text
3维向量 + 5维向量
没法加。
怎么办?
使用:1*1卷积。
例如:
text
x
56×56×64
↓
1×1 Conv, stride=2
↓
28×28×128
然后:
text
28×28×128
+
28×28×128
就能加了。
所以以后看到:
ResNet shortcut 上有一个 1×1 Conv
通常就是因为:
尺寸或者通道数对不上,需要 projection。
ResNet18 和 ResNet50 又是什么?
主要区别之一在 block。
ResNet18 常用:
text
3×3 Conv
↓
3×3 Conv
叫:
BasicBlock。
ResNet50 常用:
text
1×1
↓
3×3
↓
1×1
叫:
Bottleneck Block。
例如:
text
256 channels
↓
1×1 Conv
↓
64 channels
↓
3×3 Conv
↓
64 channels
↓
1×1 Conv
↓
256 channels
为什么?
因为 3×3 卷积很贵。
先:
256\\rightarrow64
降低维度。
再进行 3×3。
最后恢复:
64\\rightarrow256
可以节约大量计算。
面试怎么回答 ResNet?
如果面试官问:
ResNet 为什么有效?
你不要只说:
防止梯度消失。
应该说:
深层普通网络会出现 degradation problem,即随着网络加深,训练误差反而升高。ResNet 通过 shortcut 将目标映射 (H(x)) 改成残差学习 (F(x)=H(x)-x),最终输出 (H(x)=F(x)+x)。当额外层不需要学习新信息时,只需要让 (F(x)) 接近 0,就可以实现 identity mapping,因此更加容易优化。同时 shortcut 也提供了直接的梯度传播路径,使深层网络更容易训练。
这就是比较完整的回答。
2. 纹理、结构、语义到底是什么?
这个非常重要。
我们还是拿:
猫
举例。
第一层:纹理 Texture
纹理是很局部的。
比如:
text
毛发
条纹
颗粒
草地细纹
砖墙纹路
你截一小块:
text
///////
///////
///////
可能就能知道:
这里像毛发。
但你不知道:
这是猫还是狗。
所以:
纹理强调局部的重复模式和细节。
第二层:结构 Structure
结构比纹理更大尺度。
例如:
text
猫耳朵的轮廓
眼睛的位置
脸的形状
车轮和车身的位置关系
建筑物边缘
人的手臂轮廓
例如只看到:
text
/\
/ \
你可能觉得:
这像一个耳朵。
这已经不只是"毛发纹理"了。
而是在理解:
某些边缘是怎样组合在一起的。
所以:
结构是不同局部特征之间的空间组织关系。
第三层:语义 Semantic
语义就更高级。
语义是:
"这个东西是什么?"
例如:
text
这是一只猫
这是一个人
这是汽车
这是天空
这是道路
注意。
猫可以:
text
白猫
黑猫
花猫
短毛
长毛
坐着
躺着
纹理可能完全不同。
结构也会发生变化。
但:
语义依然是"猫"。
所以语义是:
对对象或者场景含义的抽象理解。
三个层次放一起
假设一张:
老虎图片。
低层 CNN:
text
橙色
黑色条纹
边缘
这是:
纹理。
中层:
text
耳朵
眼睛
四肢轮廓
脸部结构
这是:
结构。
高层:
text
这是老虎
这是:
语义。
为什么 UNet 同时需要这些?
例如图像恢复。
输入是一张模糊猫图。
你既需要知道:
这里应该有毛发。
这是纹理。
也需要知道:
眼睛应该在哪,耳朵形状应该是什么。
这是结构。
还需要知道:
整体上这是猫,而不是桌子。
这是语义。
所以:
Encoder 越往下:
越偏结构和高级语义。
浅层:
更偏边缘、纹理和局部细节。
这就是为什么 UNet 要有 skip connection。
3. Stable Diffusion 是什么?
先一句话:
Stable Diffusion 是一种在"潜空间"里运行的文本条件扩散模型,可以根据文字生成图片。
例如你输入:
一只宇航员猫坐在月球上。
它生成图片。
普通 Diffusion
普通 diffusion 可以直接对图片进行:
text
512×512×3
加噪、去噪。
但是图片非常大。
计算非常贵。
Stable Diffusion 的关键思想
不要直接在像素空间做 diffusion。
先把:
512\\times512\\times3
图片压缩成一个更小的 latent:
text
512×512×3
↓ VAE Encoder
64×64×4
然后:
在 64×64×4 的 latent 上做 diffusion。
这就快很多。
所以 Stable Diffusion 属于:
Latent Diffusion Model,LDM。
Stable Diffusion 主要有三大部分
你先记:
text
Text Encoder
+
VAE
+
UNet
① Text Encoder
例如:
"a cat wearing sunglasses"
先经过文本编码器。
得到:
text
文本
↓
Text Encoder
↓
Text Embedding
把文字变成数字特征。
早期 Stable Diffusion 常用 CLIP Text Encoder。
② VAE
VAE 的作用:
图片空间 ↔ latent 空间。
Encoder:
text
图片
↓
VAE Encoder
↓
latent
Decoder:
text
latent
↓
VAE Decoder
↓
图片
Diffusion 不需要一直处理原始大图片。
③ UNet
真正负责:
预测噪声。
整个过程:
text
随机噪声 latent
↓
UNet
↓
去一点噪声
↓
UNet
↓
再去一点
↓
...
↓
clean latent
↓
VAE Decoder
↓
图片
Text 怎么控制图片?
靠:
Cross Attention。
大致:
text
UNet feature
↓
Q
文本 embedding
↓
K,V
然后做:
Attention(Q,K,V)
于是 UNet 在生成不同区域时会参考:
文本说了什么。
所以:
text
"a red car"
和:
text
"a blue dog"
会影响生成。
Stable Diffusion 总流程
你可以记这张:
text
Prompt
│
▼
Text Encoder
│
Text Embedding
│
└──────────────┐
▼
Random latent → UNet → 去噪 → UNet → 去噪 → ...
▲
│
timestep
│
▼
Clean latent
│
▼
VAE Decoder
│
▼
Image
这就是最基本的 Stable Diffusion。
4. UNet 最底层 Bottleneck 是干什么的?
很好,这个问题你开始问到 UNet 真正的内部了。
经典 UNet:
text
Encoder
↓
↓
↓
Bottleneck
↑
↑
↑
Decoder
Bottleneck 就是:
U 最底部那一层。
为什么叫 Bottleneck?
因为这时候:
空间尺寸最小:
text
H/8 × W/8
或者:
text
H/16 × W/16
但是 channel 往往最多。
比如:
text
输入:
256×256×3
↓
128×128×64
↓
64×64×128
↓
32×32×256
↓
16×16×512 ← Bottleneck
它到底干什么?
最重要的作用:
整合高层、全局的信息。
为什么?
因为经过连续下采样之后,一个位置对应原图很大一片区域。
所以它的 receptive field 很大。
在这里,网络看到的不再只是:
一个毛发。
而可能是:
猫的整张脸。
或者:
整体结构。
举例
浅层:
text
这个位置有一条边。
再深一点:
text
这些边组成一个耳朵。
Bottleneck:
text
这里整体可能是一只猫。
然后 Decoder 利用这个高级信息:
把图逐渐恢复回来。
经典 UNet Bottleneck 里面是什么?
传统 UNet 通常类似:
text
Conv 3×3
↓
BN / GroupNorm
↓
ReLU
↓
Conv 3×3
↓
BN / GroupNorm
↓
ReLU
如果是现代 diffusion UNet,则更复杂。
常见:
text
ResBlock
↓
Attention
↓
ResBlock
为什么 Bottleneck 经常放 Attention?
因为空间已经很小。
例如原图:
512512
直接 self-attention:
262144个 token,非常贵。
但降到:32 32
只有:1024个位置。
这时候做 Attention 便宜很多。
而且 bottleneck 本来就是:
负责全局结构。
Attention 又特别适合:
建模长距离关系。
所以非常合适。
Diffusion UNet 的 Bottleneck 举个简化版本
text
Encoder
↓
ResBlock
↓
Attention
↓
ResBlock
↓
────── Bottleneck ──────
↓
ResBlock
↓
Self-Attention / Cross-Attention
↓
ResBlock
↓
────── Decoder ─────────
在 Stable Diffusion 里面:
Cross Attention 还能把文本条件放进来。
5. Diffusion 预测噪声网络里,timestep 怎么进去?
这是一个非常重要的问题。
先解决:
为什么必须告诉 UNet 时间 (t)?
想象两个输入
一个:
x_{10}
只有一点噪声:
text
🐱 还能明显看出猫
另一个:
x_{900}
已经几乎:
text
████░▒▓░▒▓
全是噪声。
显然:
处理方法不能完全一样。
所以 UNet 必须知道:
"现在噪声进行到第几步了?"
也就是:t
最简单的想法
直接给 UNet 一个数字:
text
t = 500
不太好。
为什么?
网络更擅长处理:
向量 embedding。
所以要把:
t
变成:
e_t
一个高维向量。
这叫:
Time Embedding / Timestep Embedding
第一步:把 t 编码
通常使用和 Transformer 类似的:
sinusoidal positional embedding。
比如:t=500
经过:
text
Sinusoidal Embedding
变成:
text
[0.21, -0.95, 0.53, 0.12, ...]
可能是:128维或者256维。
为什么不用单个数字?
因为 embedding 能够表达不同尺度的时间关系。
有点像给每一个:
text
t=1
t=2
t=3
...
分配一个独特但连续变化的"身份证"。
第二步:经过 MLP
通常:
text
t
↓
Sinusoidal Embedding
↓
Linear
↓
SiLU
↓
Linear
↓
Time embedding
比如最终:
e_t\\in\\mathbb R\^{512}
第三步:送到 UNet 的各个 ResBlock
注意:
timestep 不是只在输入的时候拼一次。
通常会送进很多个 ResBlock。
例如:
text
timestep
↓
Time Embedding
↓
├──────────→ ResBlock1
├──────────→ ResBlock2
├──────────→ ResBlock3
├──────────→ Bottleneck
├──────────→ Decoder Block
└──────────→ ...
所以整个 UNet 都知道:
我现在处于 t=500。
举一个具体数字
Feature:
text
B × 128 × 64 × 64
Time embedding:
text
B × 512
经过:
text
Linear
512 → 128
得到:
text
B × 128
reshape:
text
B × 128 × 1 × 1
再:
text
Feature
B×128×64×64
+
Time
B×128×1×1
broadcast:
text
↓
B×128×64×64
所以相当于:
根据不同 timestep,给不同 channel 一个不同的偏置/调制。
一个简单的 Diffusion ResBlock
你以后在代码里面可能看到这种逻辑:
python
h = conv1(x)
temb = linear(time_embedding)
h = h + temb[:, :, None, None]
h = conv2(h)
out = h + x
这是最容易理解的版本。
最后把五个问题串起来
你现在可以形成这一张图:
text
Diffusion
│
│ x_t + timestep
▼
┌─────────┐
│ UNet │
└─────────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
Encoder Bottleneck Decoder
│ │ │
纹理/局部信息 全局结构/语义 恢复空间细节
│ │ │
└────── Skip Connection ────┘
UNet 内部很多 block
│
▼
ResBlock
│
├──── ResNet 思想:F(x)+x
│
└──── timestep embedding 注入
而 Stable Diffusion 又是在这个基础上再加:
text
Prompt
↓
Text Encoder
↓
Cross Attention
↓
Diffusion UNet
以及把 diffusion 从:
text
pixel space
搬到了:
text
latent space
所以你以后会发现:
ResNet → UNet → Attention → Diffusion → Stable Diffusion
根本不是五个零散知识点。
它们其实是一条完整的技术链。