
一句话总结:何恺明用「简洁到令人发指」的结构设计,三次改写计算机视觉的历史走向——Dark Channel Prior 让单张图像去雾成为可能,ResNet 让深度网络真正变深,MAE 让视觉自监督学习走向可扩展。本文从原理、公式、源码到效果对比,系统拆解他的六大核心贡献。
一、何恺明是谁?——从高考满分到 CV 界天花板
先简单交代背景,方便理解他每一步研究的内在逻辑。
学术轨迹:
- 2003年:广东省高考满分状元(标准分900),进入清华大学基础科学班
- 2007-2011年:香港中文大学博士,师从汤晓鸥教授
- 2011-2016年:微软亚洲研究院(MSRA)研究员 → ResNet 诞生于此
- 2016-2024年:Meta FAIR(Facebook AI研究院)核心科学家 → MoCo、MAE 诞生于此
- 2024年至今:MIT EECS 副教授(一年拿到终身教职)
- 2025年起:兼任 Google DeepMind 杰出科学家
核心荣誉:两次 CVPR Best Paper(2009 去雾、2016 ResNet)、ICCV Best Paper(2017 Mask R-CNN)、PAMI 青年研究者奖、2023 未来科学大奖、论文总引用超 71 万次,ResNet 单篇引用超 28 万次,是 21 世纪引用最高的论文。
研究风格:追求极简结构解决核心难题。论文逻辑清晰、公式简洁,仅凭示意图就能看懂核心思想。每次都精准踩在最关键的问题上。
下面按照时间顺序,逐一拆解他的六大核心贡献。
二、Dark Channel Prior(暗通道先验)—— 2009 CVPR Best Paper
2.1 介绍
这是何恺明的第一篇论文,发表时他只有 25 岁,还是 MSRA 的实习生。论文标题是《Single Image Haze Removal Using Dark Channel Prior》。
问题背景:雾霾天气下拍摄的图像,对比度低、颜色偏灰白、细节丢失。传统去雾方法要么需要多张图像、要么需要深度信息,单张图像去雾一直是个难题。
何恺明的洞察简单到令人拍大腿:在无雾的自然图像中,每个局部区域里,RGB 三个通道中至少有一个通道的某些像素值非常低(接近0)。他把这个统计规律叫做"暗通道先验"。
2.2 原理
暗通道先验的理论基础是大气散射模型(Atmospheric Scattering Model)。有雾图像可以表示为:
I
(
x
)
=
J
(
x
)
⋅
t
(
x
)
+
A
⋅
(
1
−
t
(
x
)
)
I(x) = J(x) \\cdot t(x) + A \\cdot (1 – t(x))
I(x)=J(x)⋅t(x)+A⋅(1−t(x))
其中:
-
I
(
x
)
I(x)
I(x):观察到的有雾图像 -
J
(
x
)
J(x)
J(x):无雾的真实场景辐射(待恢复) -
t
(
x
)
t(x)
t(x):透射率,反映光线穿透雾的能力 -
A
A
A:全局大气光值 -
J
(
x
)
⋅
t
(
x
)
J(x) \\cdot t(x)
J(x)⋅t(x) 称为直接衰减 -
A
⋅
(
1
−
t
(
x
)
)
A \\cdot (1-t(x))
A⋅(1−t(x)) 称为大气光分量
去雾的核心就是:从
I
(
x
)
I(x)
I(x) 估计
t
(
x
)
t(x)
t(x) 和
A
A
A,然后反推
J
(
x
)
J(x)
J(x)。
暗通道的定义:对无雾图像
J
J
J,其暗通道定义为:
J
d
a
r
k
(
x
)
=
min
c
∈
{
r
,
g
,
b
}
(
min
y
∈
Ω
(
x
)
J
c
(
y
)
)
J^{dark}(x) = \\min_{c \\in \\{r,g,b\\}} \\left( \\min_{y \\in \\Omega(x)} J^c(y) \\right)
Jdark(x)=c∈{r,g,b}min(y∈Ω(x)minJc(y))
其中
Ω
(
x
)
\\Omega(x)
Ω(x) 是以像素
x
x
x 为中心的局部窗口。对于无雾的自然图像(非天空区域),
J
d
a
r
k
(
x
)
→
0
J^{dark}(x) \\to 0
Jdark(x)→0。
为什么?因为自然图像的局部区域中,通常存在阴影、彩色物体或暗色物体,使得至少一个通道的值很低。但雾的存在会"照亮"这些暗值,让暗通道不再趋近于0——这就是去雾的突破口。
2.3 公式推导
Step 1:估计透射率
对大气散射模型两边除以
A
c
A^c
Ac:
I
c
(
x
)
A
c
=
J
c
(
x
)
A
c
t
(
x
)
+
(
1
−
t
(
x
)
)
\\frac{I^c(x)}{A^c} = \\frac{J^c(x)}{A^c} t(x) + (1 – t(x))
AcIc(x)=AcJc(x)t(x)+(1−t(x))
对每个通道取局部最小值:
min
y
∈
Ω
(
x
)
I
c
(
y
)
A
c
=
t
~
(
x
)
min
y
∈
Ω
(
x
)
J
c
(
y
)
A
c
+
(
1
−
t
~
(
x
)
)
\\min_{y \\in \\Omega(x)} \\frac{I^c(y)}{A^c} = \\tilde{t}(x) \\min_{y \\in \\Omega(x)} \\frac{J^c(y)}{A^c} + (1 – \\tilde{t}(x))
y∈Ω(x)minAcIc(y)=t~(x)y∈Ω(x)minAcJc(y)+(1−t~(x))
再对三个通道取最小值,根据暗通道先验
min
c
min
y
∈
Ω
(
x
)
J
c
(
y
)
A
c
→
0
\\min_c \\min_{y \\in \\Omega(x)} \\frac{J^c(y)}{A^c} \\to 0
mincminy∈Ω(x)AcJc(y)→0:
t
~
(
x
)
=
1
−
min
c
min
y
∈
Ω
(
x
)
I
c
(
y
)
A
c
\\tilde{t}(x) = 1 – \\min_{c} \\min_{y \\in \\Omega(x)} \\frac{I^c(y)}{A^c}
t~(x)=1−cminy∈Ω(x)minAcIc(y)
引入去雾保留系数
ω
\\omega
ω(通常取 0.95,保留少量雾使图像更自然):
t
~
(
x
)
=
1
−
ω
⋅
min
c
min
y
∈
Ω
(
x
)
I
c
(
y
)
A
c
\\tilde{t}(x) = 1 – \\omega \\cdot \\min_{c} \\min_{y \\in \\Omega(x)} \\frac{I^c(y)}{A^c}
t~(x)=1−ω⋅cminy∈Ω(x)minAcIc(y)
Step 2:估计大气光
A
A
A
在暗通道图中,选取亮度最高的前 0.1% 像素(对应雾最浓的区域),在原图中这些位置取各通道最大值作为
A
A
A。
Step 3:恢复无雾图像
J
(
x
)
=
I
(
x
)
−
A
t
(
x
)
+
A
J(x) = \\frac{I(x) – A}{t(x)} + A
J(x)=t(x)I(x)−A+A
为避免
t
(
x
)
t(x)
t(x) 过小导致噪声放大,设置下限
t
0
=
0.1
t_0 = 0.1
t0=0.1:
J
(
x
)
=
I
(
x
)
−
A
max
(
t
(
x
)
,
t
0
)
+
A
J(x) = \\frac{I(x) – A}{\\max(t(x), t_0)} + A
J(x)=max(t(x),t0)I(x)−A+A
2.4 算法流程图示
输入有雾图像 I(x)
│
▼
计算暗通道 I^dark(x) ──── 取 RGB 最小值 + 局部最小值滤波
│
├──► 从暗通道最亮的前 0.1% 像素估计大气光 A
│
└──► 估计粗透射率 t̃(x) = 1 – ω·min_c(min_{Ω(x)} I^c/A^c)
│
▼
导向滤波细化透射率 ──► 消除块效应/halo
│
▼
J(x) = (I(x) – A) / max(t(x), t0) + A
│
▼
输出无雾图像 J(x)
2.5 踩坑点
天空区域失效:暗通道先验假设对天空区域不成立——天空的暗通道值远大于0,导致透射率被低估,去雾后天空区域出现严重色偏和噪声。后续改进方案通常对天空区域单独处理(如用 CLAHE 增强代替 DCP)。
软抠图太慢:原论文用软抠图(Soft Matting)细化透射率,计算复杂度极高
O
(
N
1.2
)
O(N^{1.2})
O(N1.2),处理一张图要好几秒。后来何恺明自己又提出了导向滤波(Guided Filter),把复杂度降到
O
(
N
)
O(N)
O(N)。
窗口大小敏感:局部窗口
Ω
(
x
)
\\Omega(x)
Ω(x) 的选取直接影响效果。窗口太小,去雾不彻底;窗口太大,边缘处出现 halo 效应。一般取 15×15 是经验最优值。
不适用于夜间图像:暗通道先验基于白天自然光的统计规律,对夜间低照度图像效果很差。
2.6 效果对比
| 对比度 | 低 | 显著提升 |
| 颜色饱和度 | 偏灰白 | 恢复正常 |
| 暗通道值 | 偏高 | 趋近0 |
| 天空区域 | 正常 | 色偏/噪声 |
| 处理速度 | – | 导向滤波后 ~0.1s/张 |
这篇论文的意义远超去雾本身——它展现了何恺明终其一生的研究哲学:用最简单的统计观察,解决最本质的问题。
三、He 初始化(Kaiming 初始化)与 PReLU —— 2015 ICCV
3.1 介绍
论文标题:《Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification》。
背景:深度神经网络的权重初始化至关重要——初始化不好,网络要么梯度消失要么梯度爆炸。2010年 Xavier Glorot 和 Yoshua Bengio 提出了 Xavier 初始化,但它针对的是 tanh/sigmoid 激活函数。随着 ReLU 的流行,Xavier 初始化在深层 ReLU 网络中表现不佳——30层 CNN 直接停止学习。
何恺明从方差分析的角度,推出了专门针对 ReLU 的初始化方案,并在 ImageNet-2012 上取得 4.94% top-5 错误率,首次超越人类水平(5.1%)。
3.2 原理
核心思想:让每一层输出的方差与输入方差保持一致。
对于 ReLU 激活函数
f
(
x
)
=
max
(
0
,
x
)
f(x) = \\max(0, x)
f(x)=max(0,x),它会把一半的输入置零。如果输入方差为
Var
(
x
)
\\text{Var}(x)
Var(x),经过 ReLU 后输出方差约为
1
2
Var
(
x
)
\\frac{1}{2} \\text{Var}(x)
21Var(x)。
假设权重
W
W
W 服从均值为0的分布,且与输入独立,那么第
l
l
l 层输出的方差为:
Var
(
y
l
)
=
n
l
⋅
Var
(
W
l
)
⋅
1
2
Var
(
x
l
)
\\text{Var}(y_l) = n_l \\cdot \\text{Var}(W_l) \\cdot \\frac{1}{2} \\text{Var}(x_l)
Var(yl)=nl⋅Var(Wl)⋅21Var(xl)
其中
n
l
n_l
nl 是第
l
l
l 层的 fan-in(输入神经元数),
1
2
\\frac{1}{2}
21 来自 ReLU 的截断。
要使
Var
(
y
l
)
=
Var
(
x
l
)
\\text{Var}(y_l) = \\text{Var}(x_l)
Var(yl)=Var(xl) 对所有层成立,需要:
n
l
⋅
Var
(
W
l
)
⋅
1
2
=
1
n_l \\cdot \\text{Var}(W_l) \\cdot \\frac{1}{2} = 1
nl⋅Var(Wl)⋅21=1
Var
(
W
l
)
=
2
n
l
\\text{Var}(W_l) = \\frac{2}{n_l}
Var(Wl)=nl2
3.3 公式
He 初始化:权重从均值为0、方差为
2
n
\\frac{2}{n}
n2 的正态分布中采样:
W
∼
N
(
0
,
2
n
)
W \\sim \\mathcal{N}\\left(0, \\frac{2}{n}\\right)
W∼N(0,n2)
或者等价的均匀分布版本:
W
∼
U
(
−
6
n
,
6
n
)
W \\sim \\mathcal{U}\\left(-\\sqrt{\\frac{6}{n}}, \\sqrt{\\frac{6}{n}}\\right)
W∼U(−n6
,n6
)
其中
n
n
n 是 fan-in(当前层的输入通道数 × 卷积核高度 × 卷积核宽度)。
对比 Xavier 初始化:
Var
(
W
)
=
1
n
\\text{Var}(W) = \\frac{1}{n}
Var(W)=n1,He 初始化的方差恰好是 Xavier 的两倍——多出来的那个因子 2 正是补偿 ReLU 截断造成的方差减半。
PReLU(Parametric ReLU):论文同时提出了参数化 ReLU,让负半轴的斜率
α
\\alpha
α 可学习:
f
(
x
)
=
{
x
,
x
>
0
α
x
,
x
≤
0
f(x) = \\begin{cases} x, & x > 0 \\\\ \\alpha x, & x \\leq 0 \\end{cases}
f(x)={x,αx,x>0x≤0
PReLU 的初始化需要考虑
α
\\alpha
α 的影响:
Var
(
W
)
=
2
(
1
+
α
2
)
⋅
n
\\text{Var}(W) = \\frac{2}{(1 + \\alpha^2) \\cdot n}
Var(W)=(1+α2)⋅n2
3.4 踩坑点
别用错激活函数:He 初始化是给 ReLU 系列用的。如果你的网络用 tanh 或 sigmoid,用 Xavier 初始化反而更好。PyTorch 中 nn.init.kaiming_normal_ 默认是针对 ReLU 的(mode='fan_in', nonlinearity='leaky_relu'),但很多人传参时搞混了 nonlinearity 参数。
fan_in vs fan_out:He 初始化默认用 fan_in(输入维度),但在某些场景下(如转置卷积、某些 GAN 结构)应该用 fan_out。何恺明论文中指出对于前向传播用 fan_in,对于反向传播用 fan_out 更合适。
PReLU 的过拟合:PReLU 虽然引入的额外参数很少,但在小数据集上仍然可能过拟合。实际应用中 ReLU 已经够用,PReLU 更多是用在大数据集 + 大模型上。
3.5 源码拆解(PyTorch 风格)
import torch
import torch.nn as nn
# He 正态初始化(手动实现)
def kaiming_normal_(tensor, fan_in=None, a=0):
if fan_in is None:
fan_in = tensor.shape[1] * tensor.shape[2] * tensor.shape[3]
# a=0 对应标准 ReLU;若 PReLU,a 就是负半轴斜率
gain = torch.sqrt(torch.tensor(2.0 / (1 + a ** 2)))
std = gain / torch.sqrt(torch.tensor(fan_in * 1.0))
with torch.no_grad():
tensor.normal_(0, std)
return tensor
# PyTorch 内置版本
conv = nn.Conv2d(64, 128, 3, padding=1)
nn.init.kaiming_normal_(conv.weight, mode='fan_in', nonlinearity='relu')
nn.init.zeros_(conv.bias) # 偏置初始化为0
3.6 效果对比
| 随机初始化(标准正态) | 不收敛 | 爆炸 | – |
| Xavier 初始化 | 30层停止学习 | 梯度消失 | ~10% |
| He 初始化 | 正常收敛 | 稳定 | 4.94% |
四、ResNet(深度残差网络)—— 2016 CVPR Best Paper
4.1 介绍
论文标题:《Deep Residual Learning for Image Recognition》。
这篇论文不需要太多介绍——它是 21 世纪引用最高的论文,没有之一。但它解决的问题其实非常具体:网络更深反而更差。
在 ResNet 之前,VGG 能训 19 层,GoogleNet 能训 22 层。人们天真地以为加更多层会更好,结果发现:34层网络比18层网络训练误差更高。这不是过拟合(因为训练集也变差了),而是退化问题(degradation problem)——深层网络更难优化。
何恺明的解决方案异常简洁:与其让网络学
H
(
x
)
H(x)
H(x),不如让网络学
F
(
x
)
=
H
(
x
)
−
x
F(x) = H(x) – x
F(x)=H(x)−x,即残差。
4.2 原理
核心假设:如果恒等映射(identity mapping)是最优的,那么让非线性层拟合恒等映射很难,但让残差
F
(
x
)
→
0
F(x) \\to 0
F(x)→0 很容易——只要权重趋近于0即可。
换句话说:残差学习保证深层网络至少不会比浅层网络差。最坏情况下
F
(
x
)
=
0
F(x) = 0
F(x)=0,输出就是输入
x
x
x,相当于跳过了这几层。
4.3 公式
残差块:
y
=
F
(
x
,
{
W
i
}
)
+
x
y = F(x, \\{W_i\\}) + x
y=F(x,{Wi})+x
其中
F
F
F 是残差映射,
x
x
x 是恒等跳连。对于两层卷积:
F
=
W
2
σ
(
W
1
x
)
F = W_2 \\sigma(W_1 x)
F=W2σ(W1x)
其中
σ
\\sigma
σ 是 ReLU。
当维度不匹配时(如通道数变化),用
1
×
1
1 \\times 1
1×1 卷积做投影:
y
=
F
(
x
,
{
W
i
}
)
+
W
s
x
y = F(x, \\{W_i\\}) + W_s x
y=F(x,{Wi})+Wsx
Bottleneck 结构(ResNet-50/101/152 使用):
F
(
x
)
=
W
3
σ
(
W
2
σ
(
W
1
x
)
)
F(x) = W_3 \\sigma(W_2 \\sigma(W_1 x))
F(x)=W3σ(W2σ(W1x))
其中
W
1
W_1
W1 是
1
×
1
1 \\times 1
1×1 降维、
W
2
W_2
W2 是
3
×
3
3 \\times 3
3×3 卷积、
W
3
W_3
W3 是
1
×
1
1 \\times 1
1×1 升维,用瓶颈结构减少计算量。
4.4 图示
Plain Network Residual Network
Input Input ─────────────┐
│ │ │ (identity shortcut)
▼ ▼ │
Conv 3×3, 64 Conv 3×3, 64 │
│ │ │
▼ ▼ │
Conv 3×3, 64 Conv 3×3, 64 │
│ │ │
▼ ▼ │
Conv 3×3, 64 Conv 3×3, 64 │
│ │ + <──────────┘
▼ ▼
… ReLU
│ │
▼ ▼
Output Output
34层:训练误差 > 18层 34层:训练误差 < 18层 ✓
4.5 踩坑点
BN 层的位置很关键:ResNet 把 BN 放在卷积后、ReLU 前。后来何恺明自己在《Identity Mappings in Deep Residual Networks》(ResNet v2)中发现,pre-activation 结构(BN → ReLU → Conv)效果更好。但 PyTorch 自带的 torchvision.models.resnet50 用的是原始结构,很多人不知道这俩的区别。
下采样时的投影短连:当 stride=2 且通道数翻倍时,短连必须用
1
×
1
1 \\times 1
1×1 卷积投影。但原始论文发现,用零填充短连(Option A)也能work,只是不如投影短连(Option B)。零填充不引入额外参数,但精度略低。
深度不是越深越好:ResNet-1202 在 CIFAR-10 上虽然能训练,但精度不如 ResNet-110。过深的网络会过拟合。真正实用的深度上限大约在 152 层(ImageNet)和 110 层(CIFAR)。
残差的数学直觉:很多人误以为残差连接解决了梯度消失。严格来说,梯度消失已经被 He 初始化和 BN 基本解决了。ResNet 解决的是优化困难——让深层网络的损失函数地形更平缓,更容易找到好的极小值。
4.6 源码拆解
import torch
import torch.nn as nn
class BasicBlock(nn.Module):
"""ResNet-18/34 的基本残差块"""
expansion = 1
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels,
kernel_size=3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels,
kernel_size=3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample # 维度不匹配时的投影短连
def forward(self, x):
identity = x # 恒等映射
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x) # 1×1 卷积投影
out += identity # ← 这就是残差连接的核心:F(x) + x
out = self.relu(out)
return out
class Bottleneck(nn.Module):
"""ResNet-50/101/152 的瓶颈残差块"""
expansion = 4
def __init__(self, in_channels, mid_channels, stride=1, downsample=None):
super().__init__()
# 1×1 降维
self.conv1 = nn.Conv2d(in_channels, mid_channels, 1, bias=False)
self.bn1 = nn.BatchNorm2d(mid_channels)
# 3×3 卷积(瓶颈)
self.conv2 = nn.Conv2d(mid_channels, mid_channels, 3,
stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(mid_channels)
# 1×1 升维
self.conv3 = nn.Conv2d(mid_channels, mid_channels * self.expansion, 1, bias=False)
self.bn3 = nn.BatchNorm2d(mid_channels * self.expansion)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
def forward(self, x):
identity = x
out = self.relu(self.bn1(self.conv1(x))) # 降维
out = self.relu(self.bn2(self.conv2(out))) # 3×3
out = self.bn3(self.conv3(out)) # 升维
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
4.7 效果对比
| VGG-16 | 16 | 28.1% | 9.9% |
| GoogLeNet | 22 | – | 9.2% |
| Plain-34 | 34 | 比Plain-18更高 | 退化 |
| ResNet-34 | 34 | 21.4% | 5.7% |
| ResNet-50 | 50 | 20.4% | 4.5% |
| ResNet-101 | 101 | 19.6% | 4.1% |
| ResNet-152 | 152 | 19.4% | 3.57% |
ResNet-152 在 ILSVRC 2015 上横扫五项冠军:分类、检测、定位、COCO 检测和分割。
影响力:AlphaGo、AlphaFold、ViT、GPT、自动驾驶感知——几乎所有现代 AI 系统的视觉模块都建立在 ResNet 之上。2023年,何恺明、孙剑、任少卿、张祥雨凭此论文共同斩获未来科学大奖。
五、Mask R-CNN —— 2017 ICCV Best Paper
5.1 介绍
论文标题:《Mask R-CNN》。
在 ResNet 之后,何恺明把目光转向了目标检测。2015年的 Faster R-CNN 已经能做目标检测(画框 + 分类),但实例分割(pixel-level mask)还很难。Mask R-CNN 在 Faster R-CNN 基础上增加了一个掩码预测分支,同时用 RoIAlign 替换了 RoIPooling,让检测精度大幅提升。
5.2 原理
Mask R-CNN 的核心改进有两点:
改进1:增加 Mask 分支
Faster R-CNN 对每个 RoI 输出两样东西:类别标签和边界框回归参数。Mask R-CNN 增加了第三个输出:一个 FCN(全卷积网络),为每个 RoI 生成
m
×
m
m \\times m
m×m 的二值分割掩码。
关键设计:类别与掩码解耦。不对所有类别做 softmax,而是对每个类别独立预测一个二值 mask。分类分支决定"是什么",掩码分支决定"长什么样",两者并行且互不干扰。
改进2:RoIAlign 替换 RoIPool
RoIPool 的问题:它把浮点坐标量化为整数(截断),然后又在 bin 级别再次量化。两次量化导致特征与 RoI 区域不对齐——对分类影响不大,但对像素级分割是致命的。
RoIAlign 的解决方案:完全不做量化,用双线性插值在 RoI 的每个 bin 内采样 4 个等距点,然后取最大值或平均值。这保留了精确的空间对齐。
5.3 公式
Mask R-CNN 的多任务损失:
L
=
L
c
l
s
+
L
b
o
x
+
L
m
a
s
k
L = L_{cls} + L_{box} + L_{mask}
L=Lcls+Lbox+Lmask
其中:
-
L
c
l
s
L_{cls}
Lcls:分类损失(交叉熵) -
L
b
o
x
L_{box}
Lbox:边界框回归损失(Smooth L1) -
L
m
a
s
k
L_{mask}
Lmask:每个类别的二值掩码损失(对每个像素做 sigmoid 二分类交叉熵)
L
m
a
s
k
=
−
1
m
2
∑
i
,
j
[
y
i
j
log
y
^
i
j
+
(
1
−
y
i
j
)
log
(
1
−
y
^
i
j
)
]
L_{mask} = -\\frac{1}{m^2} \\sum_{i,j} [y_{ij} \\log \\hat{y}_{ij} + (1-y_{ij}) \\log(1-\\hat{y}_{ij})]
Lmask=−m21i,j∑[yijlogy^ij+(1−yij)log(1−y^ij)]
注意:
L
m
a
s
k
L_{mask}
Lmask 只对正确类别对应的 mask 计算,不对所有类别都算。
5.4 图示
输入图像
│
▼
┌──────────────────┐
│ Backbone │ ResNet-50/101 + FPN
│ (特征提取) │
└──────────────────┘
│
▼
┌──────────────────┐
│ RPN │ 生成候选区域 (proposals)
│ (区域提议网络) │
└──────────────────┘
│
▼
┌──────────────────┐
│ RoIAlign │ ← 替换 RoIPool,用双线性插值,不做量化
└──────────────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌──────────┐
│ 分类 │ │ 边框 │ │ Mask 分支│
│ branch │ │ 回归 │ │ (FCN) │
│(softmax)│ │branch │ │ │
└────────┘ └────────┘ └──────────┘
│
▼
每类一个 m×m 二值掩码
取正确类别的mask输出
5.5 踩坑点
RoIAlign 的采样点数:原始论文在每个 bin 内采样 4 个点,但实际发现采样点数对精度影响不大,2 个点就够了。PyTorch 的 RoIAlign 默认 sampling_ratio=2。
别用 softmax mask:很多人用 softmax 给 mask 分类,结果性能下降。原因:softmax 会让不同类别之间竞争,而 sigmoid 允许一个像素同时属于多个类别(虽然有实例分割的语义约束,但 sigmoid 更灵活)。
mask 分辨率:论文用
14
×
14
14 \\times 14
14×14 或
28
×
28
28 \\times 28
28×28 的 mask,然后上采样到原图大小。更大的 mask 分辨率(如
56
×
56
56 \\times 56
56×56)效果略好但计算量翻倍,性价比不高。
FPN 很重要:Mask R-CNN + ResNet-C4 的效果不如 Mask R-CNN + ResNet-FPN,因为 FPN 提供了多尺度特征。别省这个。
5.6 源码拆解(核心结构)
import torch
import torch.nn as nn
import torch.nn.functional as F
class MaskRCNNHead(nn.Module):
"""Mask R-CNN 的掩码预测分支"""
def __init__(self, in_channels, num_classes, mask_resolution=28):
super().__init__()
# 4 层 3×3 卷积
layers = []
for _ in range(4):
layers.append(nn.Conv2d(in_channels, in_channels, 3, padding=1))
layers.append(nn.ReLU(inplace=True))
self.convs = nn.Sequential(*layers)
# 反卷积上采样
self.upsample = nn.ConvTranspose2d(in_channels, in_channels, 2, stride=2)
# 1×1 卷积输出每类一个 mask
self.predictor = nn.Conv2d(in_channels, num_classes, 1)
self.resolution = mask_resolution
def forward(self, x):
x = self.convs(x)
x = F.relu(self.upsample(x))
x = self.predictor(x) # [N, num_classes, 2*res, 2*res]
# 注意:这里不接 sigmoid,在计算 loss 时再做
return x
class RoIAlign(nn.Module):
"""简化版 RoIAlign:不做量化,用双线性插值"""
def __init__(self, output_size=(7, 7), sampling_ratio=2):
super().__init__()
self.output_size = output_size
self.sampling_ratio = sampling_ratio
def forward(self, features, rois):
"""
features: [B, C, H, W] 特征图
rois: [N, 5] (batch_idx, x1, y1, x2, y2) 浮点坐标
"""
# 对每个 RoI,在 output_size × output_size 的网格上
# 每个网格内取 sampling_ratio² 个等距点
# 用双线性插值计算这些点的值
# 然后取平均(或最大值)作为该网格的输出
# 实际实现用 torchvision.ops.roi_align
from torchvision.ops import roi_align
return roi_align(features, rois, self.output_size,
sampling_ratio=self.sampling_ratio,
aligned=True) # aligned=True 是关键!
5.7 效果对比
| Faster R-CNN (ResNet-101-FPN) | 36.2 | – | 5 fps |
| FCIS (2016冠军方案) | 37.0 | 34.7 | 复杂 |
| Mask R-CNN (ResNet-101-FPN) | 38.2 | 35.7 | 5 fps |
| Mask R-CNN (ResNeXt-101-FPN) | 39.8 | 36.7 | 4 fps |
Mask R-CNN 在只增加约 20% 计算量的情况下,超越了之前所有实例分割方法。
六、MoCo(动量对比学习)—— 2020 CVPR
6.1 介绍
论文标题:《Momentum Contrast for Unsupervised Visual Representation Learning》。
在监督学习已经快被"标签用完"的时代,自监督学习成为热门方向。对比学习(Contrastive Learning)的核心思想是:让正样本对的特征更接近,负样本对的特征更远。
但对比学习有个瓶颈:负样本越多效果越好,但 GPU 显存有限,batch size 上不去。SimCLR 需要 4096 的 batch size 才有好效果,一般人玩不起。
何恺明把对比学习重新定义为字典查询问题,用队列 + 动量编码器巧妙地解耦了 batch size 和字典大小。
6.2 原理
MoCo 把对比学习看成字典查询:
- Query:对输入图像做数据增强得到
x
q
x_q
xq,通过编码器f
q
f_q
fq 得到特征q
=
f
q
(
x
q
)
q = f_q(x_q)
q=fq(xq) - Key:对同一图像做不同增强得到
x
k
x_k
xk,通过编码器f
k
f_k
fk 得到特征k
=
f
k
(
x
k
)
k = f_k(x_k)
k=fk(xk) - 正样本:
x
q
x_q
xq 和x
k
x_k
xk 来自同一图像 - 负样本:字典中其他 key
创新1:队列(Queue)
字典不限于当前 batch,而是维护一个 FIFO 队列。每次把当前 batch 的 key 入队,最老的 key 出队。队列大小可以远大于 batch size(通常 65536)。
创新2:动量编码器(Momentum Encoder)
队列中的 key 是历史 batch 生成的,如果编码器
f
k
f_k
fk 更新太快,队列中不同时刻的 key 不一致,训练不稳定。
解决方案:
f
k
f_k
fk 不通过梯度更新,而是用动量方式跟随
f
q
f_q
fq:
θ
k
←
m
⋅
θ
k
+
(
1
−
m
)
⋅
θ
q
\\theta_k \\leftarrow m \\cdot \\theta_k + (1 – m) \\cdot \\theta_q
θk←m⋅θk+(1−m)⋅θq
其中
m
∈
[
0
,
1
)
m \\in [0, 1)
m∈[0,1),通常取 0.999。这让
f
k
f_k
fk 更新非常缓慢,保证字典中 key 的一致性。
6.3 公式
InfoNCE 对比损失:
L
MoCo
=
−
log
exp
(
q
⋅
k
+
/
τ
)
∑
i
=
0
N
exp
(
q
⋅
k
i
/
τ
)
\\mathcal{L}_{\\text{MoCo}} = -\\log \\frac{\\exp(q \\cdot k^+ / \\tau)}{\\sum_{i=0}^{N} \\exp(q \\cdot k_i / \\tau)}
LMoCo=−log∑i=0Nexp(q⋅ki/τ)exp(q⋅k+/τ)
其中:
-
q
q
q:查询特征 -
k
+
k^+
k+:正样本 key 特征 -
k
i
k_i
ki:字典中第i
i
i 个 key -
N
N
N:字典大小 -
τ
\\tau
τ:温度超参数(通常 0.07)
动量更新:
θ
k
←
m
⋅
θ
k
+
(
1
−
m
)
⋅
θ
q
,
m
=
0.999
\\theta_k \\leftarrow m \\cdot \\theta_k + (1 – m) \\cdot \\theta_q, \\quad m = 0.999
θk←m⋅θk+(1−m)⋅θq,m=0.999
注意:
θ
q
\\theta_q
θq 通过反向传播更新(有梯度),
θ
k
\\theta_k
θk 只通过动量更新(无梯度)。
6.4 图示
x ──── 数据增强1 ──► xq ──► [fq] ──► q (query)
│ │
│ 计算 q·k 的相似度
│ │
└── 数据增强2 ──► xk ──► [fk] ──► k+ (positive key)
│ │
动量更新 θk 入队
│ │
▼ ▼
┌─────────────────────────┐
│ 字典队列 (Queue) │
│ [k1, k2, …, kN] │ ← 负样本
│ (N=65536) │
└─────────────────────────┘
│
▼
InfoNCE Loss: L = -log(exp(q·k+/τ) / Σexp(q·ki/τ))
│
▼
反向传播更新 θq(只更新query编码器)
│
动量更新 θk = 0.999·θk + 0.001·θq
6.5 踩坑点
动量系数
m
m
m 太小会崩:
m
=
0.9
m=0.9
m=0.9 时训练不稳定,因为
f
k
f_k
fk 更新太快,字典中 key 不一致。
m
=
0.999
m=0.999
m=0.999 是经验最优值,
m
=
0.9999
m=0.9999
m=0.9999 也行但收敛慢。
队列大小不是越大越好:增大队列在初期提升明显,但超过 65536 后收益递减。更大的队列还会让 stale key(过期 key)的比例增大,反而有害。
MoCo v2 的改进别忽略:MoCo v2 加入了 MLP projection head 和更强的数据增强(高斯模糊、颜色抖动),效果大幅提升。用 MoCo 就直接上 v2。
温度参数
τ
\\tau
τ 敏感:
τ
\\tau
τ 太大,损失对负样本的区分度下降;
τ
\\tau
τ 太小,梯度容易消失。0.07 是经验值。
6.6 源码拆解
import torch
import torch.nn as nn
import torch.nn.functional as F
class MoCo(nn.Module):
def __init__(self, base_encoder, dim=128, K=65536, m=0.999, T=0.07):
super().__init__()
self.K = K # 队列大小
self.m = m # 动量系数
self.T = T # 温度
# query 编码器(通过梯度更新)
self.encoder_q = base_encoder()
# key 编码器(通过动量更新,不通过梯度)
self.encoder_k = base_encoder()
# 初始化:两个编码器参数相同
for param_q, param_k in zip(
self.encoder_q.parameters(), self.encoder_k.parameters()):
param_k.data.copy_(param_q.data)
param_k.requires_grad = False # key 编码器不需要梯度
# 维护的负样本队列
self.register_buffer("queue", torch.randn(dim, K))
self.queue = F.normalize(self.queue, dim=0)
self.register_buffer("queue_ptr", torch.zeros(1, dtype=torch.long))
@torch.no_grad()
def _momentum_update_key_encoder(self):
"""动量更新 key 编码器"""
for param_q, param_k in zip(
self.encoder_q.parameters(), self.encoder_k.parameters()):
param_k.data = param_k.data * self.m + param_q.data * (1.0 – self.m)
@torch.no_grad()
def _dequeue_and_enqueue(self, keys):
"""出队 + 入队"""
batch_size = keys.shape[0]
ptr = int(self.queue_ptr)
# 替换队列中对应位置
self.queue[:, ptr:ptr + batch_size] = keys.T
ptr = (ptr + batch_size) % self.K
self.queue_ptr[0] = ptr
def forward(self, im_q, im_k):
# 计算 query 特征
q = self.encoder_q(im_q) # [B, C]
q = F.normalize(q, dim=1)
# 计算 key 特征(no_grad,不反向传播)
with torch.no_grad():
self._momentum_update_key_encoder() # 动量更新
k = self.encoder_k(im_k) # [B, C]
k = F.normalize(k, dim=1)
# InfoNCE 损失
# 正样本:q · k^+
l_pos = torch.einsum('nc,nc->n', [q, k]).unsqueeze(–1) # [B, 1]
# 负样本:q · queue 中的所有 key
l_neg = torch.einsum('nc,ck->nk', [q, self.queue.clone()]) # [B, K]
# 拼接后计算 InfoNCE
logits = torch.cat([l_pos, l_neg], dim=1) # [B, 1+K]
logits /= self.T # 温度缩放
labels = torch.zeros(logits.shape[0], dtype=torch.long).cuda()
# 第 0 个位置是正样本
loss = F.cross_entropy(logits, labels)
# 入队
self._dequeue_and_enqueue(k)
return loss
6.7 效果对比
| Supervised ResNet-50 | – | 76.1% |
| SimCLR (ResNet-50) | 4096 | 69.3% |
| MoCo (ResNet-50) | 256 | 60.6% |
| MoCo v2 (ResNet-50) | 256 | 71.1% |
| MoCo v3 (ViT-B) | 4096 | 76.7% |
MoCo 的最大优势:只需要普通 batch size(256)就能达到接近 SimCLR 大 batch 的效果,让对比学习真正变得"买得起"。
七、MAE(掩码自编码器)—— 2022 CVPR
7.1 介绍
论文标题:《Masked Autoencoders Are Scalable Vision Learners》。
NLP 早已用 BERT 的 “mask and predict” 范式做自监督预训练,但视觉领域一直跟不上。原因有二:
何恺明的解决方案简洁到令人叹服:既然图像冗余,那就大刀阔斧地 mask 掉 75%。只保留 25% 的 patch 输入编码器,逼模型学真正的高层语义,而不是局部插值。
7.2 原理
MAE 的核心是非对称编码器-解码器架构:
- 编码器(重):只处理可见的 25% patch,不处理 mask token。这大幅减少了计算量(约 4× 加速)。
- 解码器(轻):接收编码器的可见 patch 表示 + mask token,重建被 mask 掉的像素值。
训练目标极其简单:用 MSE 损失重建被 mask 掉的 patch 的原始像素值。
为什么 mask 75%? 论文发现,mask 比例在 75% 时效果最佳。低于这个比例,模型可以靠局部插值"作弊",学不到高层语义。高于 75%,任务太难,信息不足。
7.3 公式
MSE 重建损失(只在被 mask 的位置计算):
L
=
1
∣
M
∣
∑
i
∈
M
∥
x
i
−
x
^
i
∥
2
2
\\mathcal{L} = \\frac{1}{|\\mathcal{M}|} \\sum_{i \\in \\mathcal{M}} \\| x_i – \\hat{x}_i \\|_2^2
L=∣M∣1i∈M∑∥xi−x^i∥22
其中
M
\\mathcal{M}
M 是被 mask 的 patch 索引集合,
x
i
x_i
xi 是原始像素值(经过 per-patch 归一化),
x
^
i
\\hat{x}_i
x^i 是重建值。
注意:只对被 mask 的 patch 计算 loss,不对可见 patch 计算——否则模型直接复制就行了。
7.4 图示
原始图像 (224×224)
┌───────────────────────┐
│ ▓ ▓ ░ ░ ▓ ░ ░ ░ ░ ▓ │ ▓ = 可见 patch (25%)
│ ░ ▓ ░ ░ ░ ▓ ░ ░ ░ ░ │ ░ = 被 mask 的 patch (75%)
│ ░ ░ ▓ ░ ░ ░ ░ ▓ ░ ░ │
│ ░ ░ ░ ░ ▓ ░ ░ ░ ▓ ░ │
└───────────────────────┘
│
▼ 只取可见 patch
┌───────────────┐
│ Encoder │ ViT-Huge(只处理 49 个 patch,而非 196 个)
│ (Heavy) │ → 3×~4× 加速
└───────────────┘
│
▼ 可见 patch 的 latent representation
┌───────────────────────────┐
│ Decoder (Lightweight) │
│ │
│ 输入:可见 patch 编码 │
│ + mask token 填充 │
│ + 位置编码 │
│ │
│ 输出:所有 patch 的重建 │
└───────────────────────────┘
│
▼
MSE Loss(只计算被 mask 位置的重建误差)
│
▼
训练完成后:丢弃 Decoder,只保留 Encoder 做下游任务
7.5 踩坑点
解码器深度很关键:太浅的解码器会让编码器被迫学习低层像素特征(因为得自己重建)。论文发现解码器至少 4-8 层 Transformer block 才好。编码器越深,解码器可以越浅——但别太极端。
归一化方式影响效果:对每个 patch 做 per-patch 归一化(减均值除标准差)比直接重建原始像素略好。这是一个容易忽略的细节。
MAE 特征的线性可分性不如对比学习:MoCo 的特征直接加个线性分类器就有不错效果,但 MAE 需要端到端 fine-tune 才发挥实力。如果你的下游任务只能做 linear probe,别用 MAE;如果能 fine-tune,MAE 优于对比学习。
别用离散 tokenizer:BEiT 用 dVAE 把图像 patch 变成离散 token 再预测,比 MAE 复杂得多但效果不一定好。何恺明证明了:直接重建像素就够了。
mask 策略很重要:随机均匀 mask 是最好的。有人试过 block-wise mask(整块遮挡),效果反而差——因为破坏了全局信息流。
7.6 源码拆解
import torch
import torch.nn as nn
import torch.nn.functional as F
from timm.models.vision_transformer import PatchEmbed, Block
class MaskedAutoencoderViT(nn.Module):
"""简化版 MAE"""
def __init__(self, img_size=224, patch_size=16, in_chans=3,
embed_dim=1024, depth=24, num_heads=16,
decoder_embed_dim=512, decoder_depth=8,
mlp_ratio=4., norm_pix_loss=True):
super().__init__()
self.patch_embed = PatchEmbed(img_size, patch_size, in_chans, embed_dim)
num_patches = self.patch_embed.num_patches # 14×14=196
# 分类 token 和位置编码
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))
# 编码器(重)
self.blocks = nn.ModuleList([
Block(embed_dim, num_heads, mlp_ratio)
for _ in range(depth)])
# 解码器的嵌入层(把编码器输出映射到解码器维度)
self.decoder_embed = nn.Linear(embed_dim, decoder_embed_dim)
# mask token(可学习的占位符)
self.mask_token = nn.Parameter(torch.zeros(1, 1, decoder_embed_dim))
# 解码器位置编码
self.decoder_pos_embed = nn.Parameter(
torch.zeros(1, num_patches + 1, decoder_embed_dim))
# 解码器(轻)
self.decoder_blocks = nn.ModuleList([
Block(decoder_embed_dim, num_heads, mlp_ratio)
for _ in range(decoder_depth)])
# 重建预测头:输出每个 patch 的像素值
self.decoder_pred = nn.Linear(decoder_embed_dim,
patch_size ** 2 * in_chans)
self.norm_pix_loss = norm_pix_loss
def patchify(self, imgs):
"""把图像切分成 patch"""
p = self.patch_embed.patch_size[0]
h = w = imgs.shape[2] // p
x = imgs.unfold(2, p, p).unfold(3, p, p) # [B, C, h, w, p, p]
x = x.contiguous().view(x.shape[0], x.shape[1], –1, p*p)
x = x.permute(0, 2, 1, 3) # [B, num_patches, C, p*p]
x = x.flatten(1) if False else x.flatten(2) # [B, num_patches, C*p*p]
return x
def forward(self, imgs, mask_ratio=0.75):
# 1. Patchify
x = self.patch_embed(imgs) # [B, N, D]
# 2. 随机 mask(保留 25%)
x, mask, ids_restore = self.random_masking(x, mask_ratio)
# 3. 编码器只处理可见 patch
x = torch.cat([self.cls_token.expand(x.shape[0], –1, –1), x], dim=1)
x = x + self.pos_embed
for blk in self.blocks:
x = blk(x)
x = x[:, 1:, :] # 去掉 cls token
# 4. 解码器:插入 mask token
x = self.decoder_embed(x)
# 在被 mask 的位置插入 mask token
mask_tokens = self.mask_token.expand(
x.shape[0], ids_restore.shape[1] – x.shape[1], –1)
x = torch.cat([x, mask_tokens], dim=1)
x = torch.gather(x, dim=1,
index=ids_restore.unsqueeze(–1).repeat(1, 1, x.shape[2]))
x = torch.cat([self.cls_token.expand(x.shape[0], –1, –1), x], dim=1)
x = x + self.decoder_pos_embed
for blk in self.decoder_blocks:
x = blk(x)
x = x[:, 1:, :] # 去掉 cls token
# 5. 重建预测
pred = self.decoder_pred(x) # [B, N, p*p*3]
# 6. 计算 MSE loss(只在 mask 位置)
target = self.patchify(imgs)
if self.norm_pix_loss:
mean = target.mean(dim=–1, keepdim=True)
var = target.var(dim=–1, keepdim=True)
target = (target – mean) / (var + 1.e-6).sqrt()
loss = (pred – target) ** 2
loss = loss.mean(dim=–1) # 每个 patch 的 loss
loss = (loss * mask).sum() / mask.sum() # 只算 mask 位置
return loss
def random_masking(self, x, mask_ratio):
"""随机 mask:保留 (1-mask_ratio) 的 patch"""
N, L, D = x.shape
len_keep = int(L * (1 – mask_ratio))
noise = torch.rand(N, L, device=x.device)
ids_shuffle = torch.argsort(noise, dim=1)
ids_restore = torch.argsort(ids_shuffle, dim=1)
x_keep = torch.gather(x, dim=1,
index=ids_shuffle[:, :len_keep].unsqueeze(–1)
.repeat(1, 1, D))
mask = torch.ones([N, L], device=x.device)
mask[:, :len_keep] = 0
mask = torch.gather(mask, dim=1, index=ids_restore)
return x_keep, mask, ids_restore
7.7 效果对比
| Supervised | ViT-Huge | – | ImageNet-1K (标签) |
| DINO | ViT-B/16 | 77.4 | ImageNet-1K |
| BEiT | ViT-B/16 | 77.9 | ImageNet-1K |
| MAE | ViT-B/16 | 83.6 | ImageNet-1K (无标签) |
| MAE | ViT-L/16 | 85.9 | ImageNet-1K (无标签) |
| MAE | ViT-H/16 | 86.9 | ImageNet-1K (无标签) |
| MAE (长训练) | ViT-H/16 | 87.8 | ImageNet-1K (无标签) |
ViT-Huge + MAE 是当时在仅用 ImageNet-1K 数据下取得的最佳精度。
八、最新研究方向(2024-2025)—— 生成式模型
何恺明在 2024 年加入 MIT 后,研究重心明显转向了生成式模型,尤其是扩散模型(Diffusion Models)的高效推理。
8.1 核心论文
《Is Noise Conditioning Necessary for Denoising Generative Models?》(ICML 2025):质疑扩散模型中噪声条件是否必需。结论:去掉噪声条件后模型仍能生成高质量样本,简化了训练流程。
《Mean Flows for One-Step Generative Modeling》(NeurIPS 2025):提出 Mean Flow 框架,用均值流替代常微分方程(ODE)求解,将多步扩散模型压缩到一步推理,大幅缩小了与多步模型的差距。
《Deconstructing Denoising Diffusion Models for Self-Supervised Learning》(ICLR 2025):拆解扩散模型的内部表示,将其用于自监督学习,建立了生成模型与表示学习的桥梁。
《Transformers without Normalization》(CVPR 2025):与 Yann LeCun 合作,证明 Transformer 可以不需要归一化层(如 LayerNorm),用简单的仿射变换替代,挑战了"归一化不可或缺"的共识。
《Fractal Generative Models》(TMLR 2025):提出分形生成模型——将生成模型抽象为原子模块,递归调用形成自相似的分形结构,为生成模型的可扩展性提供了新范式。
《Autoregressive Image Generation without Vector Quantization》(NeurIPS 2024):在连续值空间做自回归图像生成,用扩散过程建模每个 token 的概率分布,避免了向量量化的信息损失。
8.2 研究风格的一以贯之
纵观这些最新工作,何恺明的研究风格依然鲜明:
- 质疑共识:噪声条件真的必要吗?归一化层真的不可或缺吗?
- 追求简洁:去掉一个模块、减少一个步骤,看效果会不会变差
- 从表示学习到生成:自监督学习的特征能否反过来指导生成?生成模型能否用于表示学习?
这种"用最简单的方法挑战最核心假设"的风格,从 2009 年的暗通道先验到 2025 年的无归一化 Transformer,一脉相承。
九、总结:何恺明的方法论
回顾何恺明从 2009 到 2025 的研究轨迹,可以提炼出几个核心方法论:
9.1 简洁至上
他的每一篇代表作都有一个共同特征:核心思想简单到可以用一句话讲清楚。
- Dark Channel Prior:无雾图像的暗通道趋近于0
- He 初始化:ReLU 的方差补偿因子就是
2
\\sqrt{2}
2 - ResNet:学残差
F
(
x
)
F(x)
F(x) 而不是H
(
x
)
H(x)
H(x) - MoCo:用队列和动量解耦 batch size 和字典大小
- MAE:mask 掉 75%,只处理 25%
这种简洁不是偷懒,而是对问题本质的深刻洞察——当你真正理解了问题的核心,复杂的方案往往是多余的。
9.2 从观察出发
何恺明不追求数学上的花哨。他的每一步都是从经验观察出发:
- Dark Channel Prior 来自对自然图像的统计观察
- ResNet 来自对"深层网络退化"这一现象的观察
- MAE 来自对"图像空间冗余"的认知
先观察现象,理解本质,再设计方案。而不是先有方案,再找问题来套。
9.3 可扩展性思维
从 ResNet 到 MoCo 到 MAE,他始终在思考一个问题:这个方法能不能 scale?
- ResNet:从 18 层到 152 层到 1202 层
- MoCo:字典大小从 batch 级到 65536
- MAE:模型从 ViT-B 到 ViT-H,训练加速 3-4×
2025 年的分形生成模型更是把"可扩展"推向了新的维度:递归调用原子模块,形成分形结构。
9.4 学术与产业的双轨并行
何恺明是罕见的在学术界和工业界都达到顶尖水平的研究者。他的研究既有理论深度(ResNet 的残差学习理论),又有直接的应用价值(Mask R-CNN 的工业落地、MoCo/MAE 的大模型预训练)。
2024年加入 MIT、2025年兼任 Google DeepMind,这种"学界 + 业界双轨并行"的模式,让他始终保持对最前沿问题的敏锐嗅觉。
十、何恺明核心论文时间线
| 2009 | Single Image Haze Removal Using Dark Channel Prior | CVPR Best Paper | 暗通道先验去雾 |
| 2015 | Delving Deep into Rectifiers | ICCV | He初始化 + PReLU,首次超越人类 |
| 2016 | Deep Residual Learning for Image Recognition | CVPR Best Paper | 残差连接,训练152层网络 |
| 2017 | Mask R-CNN | ICCV Best Paper | 实例分割 + RoIAlign |
| 2017 | Focal Loss for Dense Object Detection | ICCV Best Student Paper | Focal Loss 解决类别不平衡 |
| 2018 | Non-local Neural Networks | CVPR | 视觉自注意力机制 |
| 2020 | Momentum Contrast (MoCo) | CVPR | 动量对比学习 |
| 2021 | Exploring Simple Siamese (SimSiam) | CVPR | 无负样本的对比学习 |
| 2022 | Masked Autoencoders (MAE) | CVPR | 掩码自编码器自监督学习 |
| 2024 | Autoregressive Image Generation w/o VQ | NeurIPS | 连续值自回归生成 |
| 2025 | Transformers without Normalization | CVPR | 无归一化Transformer |
| 2025 | Mean Flows for One-Step Generative Modeling | NeurIPS | 一步生成模型 |
| 2025 | Fractal Generative Models | TMLR | 分形生成模型 |
结语
何恺明的学术生涯,是一部"如何用最简洁的方案解决最核心问题"的教科书。从 25 岁的暗通道先验到 40 岁的分形生成模型,他的研究始终围绕同一个底层信念:简单,但深刻。
对每一个做研究或做工程的人来说,何恺明的方法论有着朴素的启示——
与其追求复杂,不如理解本质。与其增加模块,不如减少假设。与其追赶热点,不如回到最基本的问题:什么才是真正重要的?
这篇博客拆解了何恺明六大核心贡献的原理、公式、源码和效果对比,希望能为你自己的研究和工程实践提供参考。如果你对其中某个方向有更深入的兴趣,建议直接精读原始论文——何恺明的论文本身就是最好的教程,简洁、清晰、扎实。
本文基于何恺明 2009-2025 年的核心论文和公开资料整理撰写。论文链接可在 Google Scholar 或 何恺明个人主页 查阅。

![【题解】[COCI 2025/2026 #6] 滑雪 / Skijanje(李超树 0 基础友好喵)-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260826083930-6a8ea642697bc-220x25.png)
