大数跨境

深度学习里有哪些神奇又不太讲道理的现象?

深度学习里有哪些神奇又不太讲道理的现象? 知乎AI先行者
2026-09-19
4
导读:这篇回答远胜过国内诸多大学 AI 课

@白露未晞me

开源项目《Games》 作者

原问题:深度学习里有哪些神奇有趣的现象?

谢邀。笑死,我来歪个楼,讲几个没那么「正经」的现象。

NO.1 

Understanding Deep Learning Requires Rethinking Generalization

16 年的论文,现在差不多 6-7k 的 citations 了,算是深度学习泛化理论里一个很经典的反直觉结果。比如把二维点完全随机分成红蓝两类:位置和类别没有任何关系。足够大的神经网络仍然可以把训练集硬背下来,很好地拟合随机标签,这也是为什么「训练误差低」本身完全不能解释泛化。

( 2026 年 8 月 26 日补充:貌似有一些评论对此有异议,展开聊一下吧。

现在看当然不算特别反直觉,但这是拿今天的常识倒推 2016 年。而且我怀疑很多人本身就理解错重点了,而不是是不是用现代视角看的问题,这篇论文真正冲击的并不是「神经网络居然能记住训练集」,而是同一个网络明明有能力把完全随机的标签都拟合到零误差,为什么在真实标签上训练时却又能很好地泛化? 在此之前我们很容易把「不会过拟合」理解成「模型没能力乱背」;而这篇论文告诉你这是不对的,模型完全有能力乱背。于是问题就变成了:既然它能乱背,为什么真实世界里却常常没有乱背? 这也说明,仅靠「限制模型容量、通过正则化避免过拟合」这套传统直觉,并不足以解释现代深度网络的泛化。这篇论文的重要性,就是把这个矛盾用一个非常直接的实验摆了出来;后来 implicit bias、margin/norm-based explanations、double descent 等大量工作,其实都在从不同角度继续回答这个问题。)

一个非常简单的复现代码如下:

import torchimport torch.nn as nnimport matplotlib.pyplot as pltimport numpy as np
torch.manual_seed(0)
N = 200
X = torch.rand(N, 2) * 2 - 1
# 完全随机标签y = torch.randint(02, (N,))
net = nn.Sequential(    nn.Linear(2128), nn.ReLU(),    nn.Linear(128128), nn.ReLU(),    nn.Linear(128128), nn.ReLU(),    nn.Linear(1282))
opt = torch.optim.Adam(net.parameters(), lr=2e-3)loss_fn = nn.CrossEntropyLoss()
a = np.linspace(-11150)gx, gy = np.meshgrid(a, a)
grid = torch.tensor(    np.c_[gx.ravel(), gy.ravel()],    dtype=torch.float32)
plt.ion()fig, ax = plt.subplots()
for step inrange(4000):
    out = net(X)    loss = loss_fn(out, y)
    opt.zero_grad()    loss.backward()    opt.step()
    if step % 50 == 0:
        with torch.no_grad():            z = net(grid).softmax(1)[:, 1]            z = z.reshape(150150)
            acc = (net(X).argmax(1) == y).float().mean()
        ax.clear()
        ax.contourf(            gx, gy, z,            levels=np.linspace(0115),            alpha=0.7        )
        ax.scatter(            X[:, 0], X[:, 1],            c=y,            edgecolors="black"        )
        ax.set_title(            f"step={step}, train acc={acc:.3f}"        )
        plt.pause(0.001)
plt.ioff()plt.show()

效果大概长这样,最后 acc 基本都能到 100%:

Understanding Deep Learning Requires Rethinking Generalization



NO. 2 

On the Spectral Bias of Neural Networks

也是一篇几千 citations 的论文 Orz,核心发现是 spectral bias。即不同频率具有不同学习速度。

比如你设目标函数 f(x)= \sin x+0.3\sin(10x). 明明两部分同时存在,但 MLP 通常会先把低频的 \sin x 学出来,然后高频小波纹才慢慢出现。

一个简单的复现代码如下:

import torchimport torch.nn as nnimport matplotlib.pyplot as pltimport mathimport numpy as npimport imageio.v2 as imageio
torch.manual_seed(0)
x = torch.linspace(-math.pi, math.pi, 500)[:, None]
low = torch.sin(x)high = 0.3 * torch.sin(10 * x)y = low + high
net = nn.Sequential(    nn.Linear(1128), nn.Tanh(),    nn.Linear(128128), nn.Tanh(),    nn.Linear(1281))
opt = torch.optim.Adam(net.parameters(), lr=1e-3)
plt.ion()fig, ax = plt.subplots()
ax.plot(x, y, label="target", linewidth=2)line, = ax.plot(x, torch.zeros_like(x), label="network", linewidth=2)
ax.set_xlim(-math.pi, math.pi)ax.set_ylim(-1.61.6)ax.legend()
frames = []
for step inrange(10001):
    pred = net(x)    loss = ((pred - y) ** 2).mean()
    opt.zero_grad()    loss.backward()    opt.step()
    # 前面变化快,多保存;后面变化慢,少保存    save_frame = (        (step < 2000and step % 20 == 0)        or        (2000 <= step < 6000and step % 50 == 0)        or        (step >= 6000and step % 100 == 0)    )
    if save_frame:
        with torch.no_grad():            pred = net(x)
        line.set_ydata(pred.numpy())
        ax.set_title(            f"Spectral Bias | step={step} | loss={loss.item():.5f}"        )
        plt.pause(0.001)
        fig.canvas.draw()
        frame = np.asarray(            fig.canvas.buffer_rgba()        )[:, :, :3].copy()
        frames.append(frame)

plt.ioff()
imageio.mimsave(    "spectral_bias.gif",    frames,    duration=0.05,    loop=0)
plt.show()

效果如下:

On the Spectral Bias of Neural Networks



NO.3 

The Large Learning Rate Phase of Deep Learning: the Catapult Mechanism

这篇论文目前大概几百的 citations,它描述了一个很有意思的现象。

我们的传统直觉是:学习率太大 → loss 爆炸 → 训练废了。

但神经网络有一个相当奇怪的区域:loss 可以突然暴涨几十、几百甚至几千倍,之后重新跌下来继续正常训练。Lewkowycz 等人把这类大步长动力学称为 catapult mechanism;后续也有类似工作观察到 SGD 中明显的 loss spike,并研究了其与 feature learning 的关系:

Catapults in SGD: spikes in the training loss and their impact on generalization through feature learning

这篇论文的复现也十分方便,代码如下:

import torchimport torch.nn as nnimport matplotlib.pyplot as pltimport numpy as npimport imageio.v2 as imageio
torch.manual_seed(0)torch.set_num_threads(1)
x = torch.linspace(-11256)[:, None]y = torch.sin(5 * x)
net = nn.Sequential(    nn.Linear(1256),    nn.ReLU(),    nn.Linear(2561))
opt = torch.optim.SGD(net.parameters(), lr=0.05)
history = []
plt.ion()fig, ax = plt.subplots(figsize=(85))
line, = ax.plot([], [])ax.set_yscale("log")ax.set_xlim(0300)ax.set_ylim(0.252000)ax.set_xlabel("step")ax.set_ylabel("loss")ax.set_title("step=0, loss=0")ax.grid(alpha=0.25)
fig.tight_layout()plt.show(block=False)
with imageio.get_writer("training.gif", mode="I", fps=20, loop=0as writer:    for step inrange(300):        pred = net(x)        loss = ((pred - y) ** 2).mean()
        opt.zero_grad()        loss.backward()        opt.step()
        history.append(loss.item())
        line.set_data(range(len(history)), history)        ax.set_title(f"step={step}, loss={loss.item():.3g}")
        fig.canvas.draw()        fig.canvas.flush_events()
        frame = np.asarray(fig.canvas.buffer_rgba())[..., :3].copy()        writer.append_data(frame)
        plt.pause(0.02)
plt.ioff()plt.show()

效果如下:

The Large Learning Rate Phase of Deep Learning: the Catapult Mechanism



NO.4

Grokking: Generalization Beyond Overfitting on Small Algorithmic Datasets

这也是一篇小 1k citations 的论文,这篇论文大概说了这么一件事。

让模型学习: 模型训练的 accuracy 很快就能达到 100%,但 test accuracy 可能长期接近随机,然后经过大量额外训练才明显上升。

Power 等人的原始工作把这种「过拟合很久之后才出现泛化」称为 grokking。

复现代码如下:

import torchimport torch.nn as nnimport matplotlib.pyplot as pltfrom matplotlib.animation import PillowWriter
torch.manual_seed(0)torch.set_num_threads(1)
p = 23
X = torch.cartesian_prod(    torch.arange(p),    torch.arange(p))
y = (X[:, 0] + X[:, 1]) % p
perm = torch.randperm(len(X))
n_train = len(X) // 2
train = perm[:n_train]test = perm[n_train:]

classModel(nn.Module):
    def__init__(self):        super().__init__()
        self.emb = nn.Embedding(p, 16)
        self.mlp = nn.Sequential(            nn.Linear(3264),            nn.ReLU(),            nn.Linear(64, p)        )
    defforward(self, x):
        a = self.emb(x[:, 0])        b = self.emb(x[:, 1])
        return self.mlp(            torch.cat([a, b], dim=1)        )

net = Model()
opt = torch.optim.AdamW(    net.parameters(),    lr=3e-3,    weight_decay=1.0)
loss_fn = nn.CrossEntropyLoss()
train_hist = []test_hist = []steps = []
plt.ion()
fig, ax = plt.subplots()
l1, = ax.plot([], [], label="train")l2, = ax.plot([], [], label="test")
ax.set_ylim(01.05)ax.legend()
# 新增:GIF writerwriter = PillowWriter(fps=10)
# 新增:保存 GIFwith writer.saving(fig, "grokking.gif", dpi=100):
    for step inrange(15000):
        out = net(X[train])
        loss = loss_fn(            out,            y[train]        )
        opt.zero_grad()        loss.backward()        opt.step()
        if step % 100 == 0:
            with torch.no_grad():
                train_acc = (                    net(X[train]).argmax(1)                    == y[train]                ).float().mean()
                test_acc = (                    net(X[test]).argmax(1)                    == y[test]                ).float().mean()
            steps.append(step)            train_hist.append(train_acc)            test_hist.append(test_acc)
            l1.set_data(steps, train_hist)            l2.set_data(steps, test_hist)
            ax.relim()            ax.autoscale_view(scalex=True, scaley=False)
            ax.set_title(                f"step={step} "                f"train={train_acc:.2f} "                f"test={test_acc:.2f}"            )
            plt.pause(0.001)
            # 新增:把当前画面写入 GIF            writer.grab_frame()
plt.ioff()plt.show()

效果如下:

Grokking: Generalization Beyond Overfitting on Small Algorithmic Datasets



NO. 5 Toy Models of Superposition

差不多也是一篇小 1k citations 的论文。

Superposition 指的是:当模型的表示维度小于潜在特征数量时,网络并不一定为每个特征分配独立的神经元或维度,而可能让多个特征共享同一个表示空间。尤其当特征是稀疏激活的、很少同时出现时,这种「叠加存储」可以显著提高有限表示容量的利用率。代价是不同特征之间会产生一定干扰。Elhage 等人系统展示了这一现象,并发现模型会自发形成具有几何结构的特征表示。

简单复现一下:

import torchimport matplotlib.pyplot as pltfrom matplotlib.animation import PillowWriter
torch.manual_seed(0)torch.set_num_threads(1)
features = 5hidden = 2
# 每个 feature 在二维隐藏空间里的方向W = torch.nn.Parameter(    torch.randn(hidden, features) * 0.2)
b = torch.nn.Parameter(    torch.zeros(features))
opt = torch.optim.Adam(    [W, b],    lr=3e-3)
plt.ion()
fig, ax = plt.subplots(figsize=(55))
writer = PillowWriter(fps=10)
with writer.saving(fig, "superposition.gif", dpi=100):
    for step inrange(8000):
        # sparse features        x = torch.rand(1024, features)
        mask = (            torch.rand_like(x) > 0.9        ).float()
        x = x * mask
        # compress 5D -> 2D        h = x @ W.T
        # reconstruct        y = torch.relu(            h @ W + b        )
        loss = ((y - x) ** 2).mean()
        opt.zero_grad()        loss.backward()        opt.step()
        if step % 100 == 0:
            ax.clear()
            w = W.detach()
            for i inrange(features):
                ax.arrow(                    00,                    w[0, i],                    w[1, i],                    head_width=0.05,                    length_includes_head=True                )
                ax.text(                    w[0, i] * 1.1,                    w[1, i] * 1.1,                    str(i)                )
            ax.set_xlim(-1.51.5)            ax.set_ylim(-1.51.5)            ax.set_aspect("equal")
            ax.set_title(                f"step={step}, loss={loss.item():.4f}"            )
            plt.pause(0.01)
            writer.grab_frame()
plt.ioff()plt.show()

效果如下:

Toy Models of Superposition



NO. 6 Deep Image Prior

这篇论文感觉做 cv 的应该都知道。

它的核心发现是:即使 CNN 完全没有经过训练,它的网络结构本身也天然偏向生成自然、平滑且具有局部结构的图像。 因此,只需固定一份随机噪声作为输入,用单张受损或含噪图像反向优化网络参数,CNN 往往会先拟合出图像中的真实结构,再逐渐拟合噪声。这个现象说明,网络架构本身就可以充当一种有效的图像先验,而不一定依赖大规模训练数据。

一个简单的测试代码:

import torchimport torch.nn as nnimport torch.nn.functional as Fimport matplotlib.pyplot as pltfrom matplotlib.animation import PillowWriter
torch.manual_seed(0)torch.set_num_threads(1)
H = W = 64
yy, xx = torch.meshgrid(    torch.linspace(-11, H),    torch.linspace(-11, W),    indexing="ij")
clean = torch.zeros(H, W)
# 圆clean[    (xx + 0.35) ** 2    + (yy + 0.15) ** 2    < 0.18 ** 2] = 1
# 方块clean[    (xx > 0.05) &    (xx < 0.65) &    (yy > -0.5) &    (yy < -0.1)] = 0.7
clean = clean[NoneNone]
noisy = (    clean    + 0.25 * torch.randn_like(clean)).clamp(01)

classGenerator(nn.Module):
    def__init__(self):        super().__init__()
        self.c1 = nn.Conv2d(16643, padding=1)        self.c2 = nn.Conv2d(64643, padding=1)        self.c3 = nn.Conv2d(64323, padding=1)        self.c4 = nn.Conv2d(3213, padding=1)
    defforward(self, z):
        x = F.relu(self.c1(z))
        x = F.interpolate(            x,            scale_factor=2,            mode="bilinear"        )
        x = F.relu(self.c2(x))
        x = F.interpolate(            x,            scale_factor=2,            mode="bilinear"        )
        x = F.relu(self.c3(x))
        x = F.interpolate(            x,            scale_factor=2,            mode="bilinear"        )
        return torch.sigmoid(            self.c4(x)        )

net = Generator()
# 固定随机输入z = torch.rand(11688)
opt = torch.optim.Adam(    net.parameters(),    lr=5e-3)
plt.ion()
fig, ax = plt.subplots(13)
ax[0].imshow(    clean[00],    cmap="gray")
ax[0].set_title("clean")
ax[1].imshow(    noisy[00],    cmap="gray")
ax[1].set_title("noisy")
writer = PillowWriter(fps=10)
with writer.saving(fig, "deep_image_prior.gif", dpi=100):
    for step inrange(500):
        out = net(z)
        # 注意:网络只看 noisy!        loss = (            (out - noisy) ** 2        ).mean()
        opt.zero_grad()        loss.backward()        opt.step()
        if step % 10 == 0:
            ax[2].clear()
            ax[2].imshow(                out[00].detach(),                cmap="gray",                vmin=0,                vmax=1            )
            ax[2].set_title(                f"network step={step}"            )
            plt.pause(0.01)
            writer.grab_frame()
plt.ioff()plt.show()

效果如下:

Deep Image Prior

后续看到其他有意思的再补充吧。


知友讨论

@东田鸟:

博主的这篇回答远胜过国内诸多大学ai课…

@我在这里寻宝:

好回答。最后一个我也读过,很有意思哈哈哈,跟现在的各种刷sota的复杂模型对比是一股清流。

@志愿的人:

Deep Image Prior这篇文章的论证我感觉是薄弱的,甚至可以用上面论文的结论来反驳它:On the Spectral Bias of Neural Networks论证了mlp模型是先学习低频信号,再学习高频。图像里的结构信息就是低频,噪声是高频。既然mlp结构就会先学习低频信息,这说明这一现象与CNN结构无关。

@QuinHai:

No1 其实说明了业界主流观点,压缩即智能
示例里面总可训练参数量: 33666
训练样本数量: 200
参数量 / 样本数 = 168.33
而GPT3是175B的参数量,训练Token 300B
下面有压缩即智能的实验研究
Language Modeling Is Compression (2024‑05) arXiv:2405.04762
Compression Represents Intelligence Linearly (COLM 2024) arXiv:2404.09937





阅读更多

我们从知乎 83 篇长文里,整理了一条 AI 学习路径
胡渊鸣丨GPT-6 Astra 会取代 3D 生成吗?
《我不得不把才华埋葬在昨天》真正想说的
GPT-6 Astra 炸出了一堆具身智能从业者

🤝 知乎 AI Researcher Club 招募:

知乎联合 10+ 家媒体、创投孵化合作伙伴发起「AI Researcher Club」共创计划,长期支持支持 AI 前沿研究者,让重要的研究被看见、被讨论,让研究者找到彼此,也让新的合作由此发生。点击了解 >> AI Researcher Club

🚀 知乎 AI 社群:

如果你对 AI 共识、AI 活动感兴趣,欢迎扫码加入知乎社群↓,我们将每周送上知乎 AI 周报,分享社区内的 AI 讨论与共识,并不定时送上 AI 各类活动报名。








知乎AI情报站








让一部分开发者先走起来

🚀 知乎科技账号 @ZhihuFrontier 正式登陆 X

🌏 知乎 AI Works 项目广场:

👉🏻 https://www.zhihu.com/project-square,上传你的 Vibe Coding 项目,在知乎遇见更多可能。

【声明】内容源于网络
0
0
知乎AI先行者
在智能之海寻找信标,航向未来。
内容 197
粉丝 0
知乎AI先行者 在智能之海寻找信标,航向未来。
总阅读2.4k
粉丝0
内容197