{"notes":[{"id":"classic-cnns/nfnet","category":"classic-cnns","legacyPath":"","legacySlug":"","legacyPaths":[],"title":"NFNet","subtitle":"没有 BN 的日子","author":"Z. Yuan","paperDate":"2021-02-11","publishedOn":"2025-01-21","world":"external/nfnet","paperTitle":"High-Performance Large-Scale Image Recognition Without Normalization","paperUrl":"https://arxiv.org/abs/2102.06171","bibliography":{"title":"High-Performance Large-Scale Image Recognition Without Normalization","authors":["Andrew Brock","Soham De","Samuel L. Smith","Karen Simonyan"],"url":"https://arxiv.org/abs/2102.06171","pdfUrl":"https://arxiv.org/pdf/2102.06171v1","codeUrl":null,"preprint":{"id":"2102.06171","submittedOn":"2021-02-11","url":"https://arxiv.org/abs/2102.06171"},"publication":{"venue":"ICML","year":2021,"kind":"conference","url":"https://proceedings.mlr.press/v139/brock21a.html"},"citation":"@misc{classiccnnsnfnet2021,\n  title = {{High-Performance Large-Scale Image Recognition Without Normalization}},\n  author = {Andrew Brock and Soham De and Samuel L. Smith and Karen Simonyan},\n  year = {2021},\n  eprint = {2102.06171},\n  archivePrefix = {arXiv},\n  url = {https://arxiv.org/abs/2102.06171}\n}\n","citationPath":"/content/papers/citations/classic-cnns/nfnet.91baba59df96.bib"},"thumbnail":{"src":"/content/papers/assets/7552bff30433f4f33bec5edb2421acd94d61d452888dde6df17557778e8fb1c6.png","width":1004,"height":783},"locale":"zh-hans","path":"/content/papers/zh-hans/classic-cnns/nfnet.38a489ed8238.md","headings":[{"id":"定义问题","text":"定义问题","depth":2},{"id":"batchnorm-到底帮了什么","text":"BatchNorm 到底帮了什么","depth":3},{"id":"均值偏移长什么样子","text":"均值偏移长什么样子","depth":3},{"id":"解决问题","text":"解决问题","depth":2},{"id":"先把-nf-resnet-补齐","text":"先把 NF-ResNet 补齐","depth":3},{"id":"自适应梯度裁剪","text":"自适应梯度裁剪","depth":3},{"id":"nfnet-的架构","text":"NFNet 的架构","depth":3},{"id":"讨论","text":"讨论","depth":2},{"id":"每一步改动的贡献","text":"每一步改动的贡献","depth":3},{"id":"拿掉-bn-真的比较快吗","text":"拿掉 BN 真的比较快吗","depth":3},{"id":"和其他模型比","text":"和其他模型比","depth":3},{"id":"预训练之后再微调","text":"预训练之后再微调","depth":3},{"id":"结论","text":"结论","depth":2}],"anchorAliases":{},"guide":null,"relatedReading":[{"id":"normalization/batchnorm","relation":"referenced","topic":""}]},{"id":"normalization/batchnorm","category":"normalization","legacyPath":"normalization/1502-batchnorm","legacySlug":"1502-batchnorm","legacyPaths":[],"title":"BatchNorm","subtitle":"批次标准化","author":"Z. Yuan","paperDate":"2015-02-11","publishedOn":"2024-08-01","paperTitle":"Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift","paperUrl":"https://arxiv.org/abs/1502.03167","bibliography":{"title":"Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift","authors":["Sergey Ioffe","Christian Szegedy"],"url":"https://arxiv.org/abs/1502.03167","pdfUrl":"https://arxiv.org/pdf/1502.03167","codeUrl":null,"preprint":{"id":"1502.03167","submittedOn":"2015-02-11","url":"https://arxiv.org/abs/1502.03167"},"publication":{"venue":"ICML","year":2015,"kind":"conference","url":"https://proceedings.mlr.press/v37/ioffe15.html"},"citation":"@misc{normalizationbatchnorm2015,\n  title = {{Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift}},\n  author = {Sergey Ioffe and Christian Szegedy},\n  year = {2015},\n  eprint = {1502.03167},\n  archivePrefix = {arXiv},\n  url = {https://arxiv.org/abs/1502.03167}\n}\n","citationPath":"/content/papers/citations/normalization/batchnorm.bfa8abb33f32.bib"},"thumbnail":{"src":"/content/papers/assets/48b236d4b897164b93764eccfe22db29e5b9bce290d864336b8f12ec127c1e4b.jpg","width":1016,"height":824},"locale":"zh-hans","path":"/content/papers/zh-hans/normalization/batchnorm.1521f6672c5f.md","headings":[{"id":"定义问题","text":"定义问题","depth":2},{"id":"解决问题","text":"解决问题","depth":2},{"id":"提高学习率","text":"提高学习率","depth":3},{"id":"不需要-dropout","text":"不需要 Dropout","depth":3},{"id":"讨论","text":"讨论","depth":2},{"id":"在-mnist-实验","text":"在 MNIST 实验","depth":3},{"id":"在-imagenet-实验","text":"在 ImageNet 实验","depth":3},{"id":"结论","text":"结论","depth":2}],"anchorAliases":{},"guide":null,"relatedReading":[{"id":"normalization/layernorm","relation":"next","topic":"归一化究竟沿哪个维度算"}]}],"text":"2015 年之后，几乎每个图像分类网络里都有 BatchNorm。它让深层网络好训练、可以用大学习率，还附带一点正则化效果，好用到大家很少再去想它的缺点。\n\n\n> [!TIP]\n> \n> BatchNorm 可以先看我们的笔记：\n> \n> - **[[15.02] BatchNorm：批次标准化](/blog/papers/normalization/batchnorm)**\n\n\n缺点其实不少。它要算批量统计量，占内存也花时间；训练时用批量统计、推理时用移动平均，两边行为不一致；它还让同一个批量里的样本互相影响，这是最麻烦的一点。批量太小时效果变差，分布式训练时容易出错，在对比学习里还得特别处理，免得模型靠批量统计偷看答案。\n\nDeepMind 的 Andrew Brock、Soham De、Samuel L. Smith 与 Karen Simonyan 想把 BatchNorm 整个拿掉。他们在前一篇论文里已经做出不用归一化层的 NF-ResNet，准确率追上了同级的 BN-ResNet；这篇要解决的是剩下的两个问题。\n\n## 定义问题\n\n### BatchNorm 到底帮了什么\n\n要拿掉 BatchNorm，得先知道它做了哪些事，再一件一件找替代品。作者整理了四项：\n\n1. **把残差分支的输出压小**。BN 放在残差分支上时，初始化阶段的分支输出比较小，信号主要走捷径，训练初期的梯度比较稳。\n2. **消除均值偏移**。ReLU、GELU 这些激活函数的输出均值不是 0，一层层叠下去，不同样本的特征会越来越像。BN 每层都把均值拉回 0。\n3. **正则化效果**。批量统计量带有噪声，有点像在训练时加噪声。\n4. **让大批量训练可行**。BN 让损失曲面比较平滑，可以用更大的学习率，大批量训练才有意义。\n\n前一篇的 NF-ResNet 用初始化和权重标准化处理了前两项，再加 Dropout 和 Stochastic Depth 补上第三项。剩下的问题有两个：批量大到 4096 以上时训练不稳，最好的成绩也还追不上 EfficientNet。\n\n### 均值偏移长什么样子\n\n第二项比较抽象，我们先看看没有归一化时会发生什么事。取 64 个互不相关的随机样本，每次经过 ReLU 和一层用 He 初始化的权重，叠 20 层，测量不同样本之间的平均 cosine 相似度；另一组换成 NF-ResNet 的做法，等一下再解释：\n\n```python\nimport math\nimport torch\nimport torch.nn.functional as F\n\ntorch.manual_seed(0)\nx = torch.randn(64, 512)                      # 64 张互不相关的样本，各 512 个特征\n\ndef ws(W):\n    \"\"\"Scaled Weight Standardization：每个输出单元的权重减去均值，再除以 std·√N\"\"\"\n    N = W.shape[1]\n    return (W - W.mean(1, keepdim=True)) / (W.std(1, unbiased=False, keepdim=True) * math.sqrt(N))\n\ndef mean_cos(h):\n    \"\"\"不同样本之间的平均 cosine 相似度\"\"\"\n    h = F.normalize(h, dim=1)\n    sim = h @ h.T\n    return (sim.sum() - sim.diagonal().sum()).item() / (h.shape[0] * (h.shape[0] - 1))\n\ngamma = math.sqrt(2 / (1 - 1 / math.pi))      # ReLU 的增益\nprint(f\"γ = {gamma:.4f}\")\nplain, std = x.clone(), x.clone()\nfor layer in range(1, 21):\n    W = torch.randn(512, 512)\n    plain = F.relu(plain) @ (W * math.sqrt(2 / 512)).T      # He 初始化，没有归一化\n    std = (gamma * F.relu(std)) @ ws(W).T                   # 标准化权重加增益\n    if layer in (1, 5, 10, 20):\n        print(f\"第 {layer:>2} 层｜He 初始化：样本间相似度 {mean_cos(plain):.2f}\"\n              f\"｜Scaled WS：相似度 {mean_cos(std):.2f}，方差 {std.var().item():.2f}\")\n```\n\n```text\nγ = 1.7129\n第  1 层｜He 初始化：样本间相似度 0.31｜Scaled WS：相似度 0.00，方差 0.99\n第  5 层｜He 初始化：样本间相似度 0.72｜Scaled WS：相似度 -0.00，方差 0.98\n第 10 层｜He 初始化：样本间相似度 0.87｜Scaled WS：相似度 0.00，方差 0.95\n第 20 层｜He 初始化：样本间相似度 0.96｜Scaled WS：相似度 0.00，方差 0.88\n```\n\n输入的 64 个样本本来互不相关，经过 20 层之后，He 初始化那组的平均相似度已经到了 0.96，几乎所有样本都长得一样。原因是 ReLU 的输出都大于等于 0，每一层都把同一个正的均值加进所有样本。论文引用的研究指出，这种偏移会让深层网络在初始化时对所有样本预测同一个类别。\n\n## 解决问题\n\n### 先把 NF-ResNet 补齐\n\n右边那组用的是前一篇 NF-ResNet 的两个技巧。\n\n第一个是 **Scaled Weight Standardization**。卷积层的每个输出单元，权重先减去自己的均值，再除以标准差和 $\\sqrt{N}$：\n\n$$\n\\hat{W}_{ij}=\\frac{W_{ij}-\\mu_i}{\\sqrt{N}\\,\\sigma_i},\n$$\n\n$N$ 是 fan-in。减掉均值之后，每个输出单元的权重加起来等于 0，激活函数带进来的那个共同的正值就被抵消了，这就是上面相似度一直停在 0 的原因。\n\n第二个是激活函数后面乘上的增益 $\\gamma$。对 ReLU 来说 $\\gamma=\\sqrt{2/(1-1/\\pi)}$，大约 1.71，刚好补回 ReLU 砍掉负值造成的方差损失。两者合起来，每一层的输出方差大致维持在 1。20 层后的 0.88 是一层层累积下来的小偏差，和另一组样本几乎变成同一个相比，小得多。\n\n有了「每层大致保持方差」的分支，NF-ResNet 的残差块写成\n\n$$\nh_{i+1}=h_i+\\alpha\\,f_i\\!\\left(h_i/\\beta_i\\right).\n$$\n\n$\\beta_i$ 是预先算好的输入标准差，分支吃进去的信号一律缩回方差 1；$\\alpha$ 是一个小常数（通常取 0.2），控制每个块让方差增加多少。所以主干的方差可以直接算出来，每过一个块加 $\\alpha^2$。我们搭 12 个块来验证：\n\n```python\nimport math\nimport torch\nimport torch.nn.functional as F\n\ntorch.manual_seed(0)\ngamma = math.sqrt(2 / (1 - 1 / math.pi))\n\ndef ws(W):\n    N = W.shape[1]\n    return (W - W.mean(1, keepdim=True)) / (W.std(1, unbiased=False, keepdim=True) * math.sqrt(N))\n\ndef branch(h):\n    \"\"\"残差分支：两层「增益 ReLU + 标准化权重」，输出方差约等于输入\"\"\"\n    for _ in range(2):\n        h = (gamma * F.relu(h)) @ ws(torch.randn(512, 512)).T\n    return h\n\nalpha = 0.2\nh = torch.randn(4096, 512)\nexpected = 1.0\nfor i in range(1, 13):\n    beta = math.sqrt(expected)                # 预先算好的输入标准差\n    h = h + alpha * branch(h / beta)          # h_{i+1} = h_i + α·f(h_i / β_i)\n    expected += alpha ** 2                    # Var(h_{i+1}) = Var(h_i) + α²\n    if i in (1, 4, 8, 12):\n        print(f\"第 {i:>2} 个块后：预期方差 {expected:.2f}，实际 {h.var().item():.2f}\")\n```\n\n```text\n第  1 个块后：预期方差 1.04，实际 1.04\n第  4 个块后：预期方差 1.16，实际 1.16\n第  8 个块后：预期方差 1.32，实际 1.32\n第 12 个块后：预期方差 1.48，实际 1.48\n```\n\n实际的方差和预期完全一致。这就是 BatchNorm 第一项功能的替代品。因为 $\\alpha$ 很小，信号主要走捷径，分支一开始只做小修正。遇到要缩小特征图的块，捷径也吃缩放过的输入，方差重新从 $1+\\alpha^2$ 算起。另外，每个残差分支最后还有一个初始化为 0 的可学习标量（SkipInit），训练刚开始时整个分支相当于关掉。\n\n到这里，NF-ResNet 在批量 1024 时已经能追上 BN-ResNet，可是批量一大就不稳。\n\n### 自适应梯度裁剪\n\n大批量训练要用大学习率，梯度一大，参数就可能被一步推得太远。常见的解法是梯度裁剪：\n\n$$\nG\\rightarrow\n\\begin{cases}\n\\lambda\\dfrac{G}{\\lVert G\\rVert} & \\text{if }\\lVert G\\rVert>\\lambda,\\\\\nG & \\text{otherwise.}\n\\end{cases}\n$$\n\n作者发现这样确实能撑到比较大的批量，可是阈值 $\\lambda$ 非常难调。模型深度、批量大小、学习率一变，就得重新找。\n\n问题出在固定的阈值不管参数本身有多大。同样是 0.01 的梯度，加在大小为 1 的权重上只是 1% 的变动，加在大小为 0.01 的权重上就是整个翻掉。作者因此改看比值：\n\n$$\n\\frac{\\lVert G^\\ell\\rVert_F}{\\lVert W^\\ell\\rVert_F},\n$$\n\n这个比值大致代表一步更新会让权重改变多大比例。实际做的时候不是整层算一次，而是以**单元**为单位，也就是权重矩阵的每一行（卷积的话是每个输出通道，连同它的空间范围）：\n\n$$\nG_i^\\ell\\rightarrow\n\\begin{cases}\n\\lambda\\dfrac{\\lVert W_i^\\ell\\rVert_F^\\star}{\\lVert G_i^\\ell\\rVert_F}\\,G_i^\\ell & \\text{if }\\dfrac{\\lVert G_i^\\ell\\rVert_F}{\\lVert W_i^\\ell\\rVert_F^\\star}>\\lambda,\\\\[6pt]\nG_i^\\ell & \\text{otherwise.}\n\\end{cases}\n$$\n\n$\\lVert W_i\\rVert_F^\\star=\\max(\\lVert W_i\\rVert_F,\\epsilon)$，$\\epsilon=10^{-3}$，避免初始化为 0 的参数的梯度永远被裁成 0。这就是 Adaptive Gradient Clipping（AGC）。\n\n拿一个有三个单元的小例子，看它怎么裁：\n\n```python\nimport torch\n\nW = torch.tensor([[1.0, 0.0, 0.0],\n                  [0.0, 0.1, 0.0],\n                  [0.0, 0.0, 2.0]])           # 三个输出单元，权重大小不同\nG = torch.tensor([[0.005, 0.0, 0.0],\n                  [0.0, 0.05, 0.0],\n                  [0.0, 0.0, 0.01]])          # 三行梯度\n\nlam, eps = 0.01, 1e-3\nw_norm = W.norm(dim=1, keepdim=True).clamp(min=eps)\ng_norm = G.norm(dim=1, keepdim=True)\nratio = g_norm / w_norm\nclipped = torch.where(ratio > lam, G * (lam * w_norm / g_norm), G)\n\nfor i in range(3):\n    print(f\"单元 {i}：‖W‖={w_norm[i].item():.2f}，‖G‖={g_norm[i].item():.3f}，比值 {ratio[i].item():.3f}\"\n          f\" → 裁剪后 ‖G‖={clipped[i].norm().item():.4f}\")\nprint(\"整体梯度范数：\", round(G.norm().item(), 4))\n```\n\n```text\n单元 0：‖W‖=1.00，‖G‖=0.005，比值 0.005 → 裁剪后 ‖G‖=0.0050\n单元 1：‖W‖=0.10，‖G‖=0.050，比值 0.500 → 裁剪后 ‖G‖=0.0010\n单元 2：‖W‖=2.00，‖G‖=0.010，比值 0.005 → 裁剪后 ‖G‖=0.0100\n整体梯度范数： 0.0512\n```\n\n只有单元 1 被裁了。它的权重只有 0.1，梯度却有 0.05，一步就要改掉一半，所以被压到 $\\lambda\\times0.1=0.001$。另外两个单元的比值都在阈值以下，原封不动。如果用一般的整体裁剪，三个单元会被同一个系数一起缩小，真正需要压的那一个反而不一定压得够。\n\n上面的写法和 timm 的 `timm.utils.agc.adaptive_clip_grad` 结果一样，三个单元裁剪后分别是 0.005、0.001、0.01。\n\n[![两张折线图。(a) 横轴是批量大小（256 到 4096），纵轴是 Top-1 准确率；蓝线是 BatchNorm，橙线是 NF-ResNet，绿线是 NF-ResNet+AGC，实线为 ResNet50、虚线为 ResNet200。橙线在批量 2048 之后急剧下滑，绿线一路维持在和蓝线相近或更高的位置。(b) ResNet50 在不同裁剪阈值（0.01 到 0.16）下的准确率，每条线对应一种批量大小；批量 4096 的紫线在阈值 0.02 之后崩掉](/content/papers/assets/9c1e7cd2c8c545de3ebab245e654b0dd69b3921c4f0479f0e99131833bdfc5f2.png)](/content/papers/assets/9c1e7cd2c8c545de3ebab245e654b0dd69b3921c4f0479f0e99131833bdfc5f2.png)\n\n*原论文 Figure 2，取自 [**v1 PDF 第 4 页**](https://arxiv.org/pdf/2102.06171v1#page=4)。(a) AGC 让 NF-ResNet 能用更大的批量；(b) 不同裁剪阈值 $\\lambda$ 的表现。*\n\n左图的橙线是没有 AGC 的 NF-ResNet，批量到 2048 以后就崩了；加上 AGC 的绿线一路撑到 4096，准确率和 BatchNorm 的蓝线差不多甚至更好。右图看的是阈值，批量越大，阈值就要越小，批量 4096 时只有 0.01 和 0.02 撑得住。\n\n作者也试了哪些层需要裁。最后的全连接层不裁比较好；第一层卷积常常可以不裁，四个 stage 的权重则在批量 4096 时都得裁。之后的实验都对最后一层以外的所有参数使用 AGC。\n\n### NFNet 的架构\n\n有了 AGC，作者接着设计新的网络。这次的目标不是 FLOPs 或参数量，而是**在 TPU 和 GPU 上实际的训练速度**。作者指出 EfficientNet 的 FLOPs 很低，训练速度却没有跟着变快。EfficientNet-B0 的 FLOPs 只有 ResNet-50 的十分之一，在 GPU 或 TPU 上训练起来却差不多慢。\n\n[![NFNet 的 bottleneck 块示意图：输入先乘上 1/β，经过 1x1 卷积、两个 3x3 卷积、再一个 1x1 卷积，乘上 α 后和捷径相加。右侧列出各 stage 的宽度与深度：ResNet 宽度 [256, 512, 1024, 2048]、NFNet 宽度 [256, 512, 1536, 1536]；ResNet 深度 [3, 4, 6, 3]、[3, 4, 23, 3]…，NFNet 深度 [1, 2, 6, 3] 乘上 N](/content/papers/assets/6dc27a9ccca4de619c057562ea6384aab3c095ddc08b22f475601ab03fcf03bb.png)](/content/papers/assets/6dc27a9ccca4de619c057562ea6384aab3c095ddc08b22f475601ab03fcf03bb.png)\n\n*原论文 Figure 3，取自 [**v1 PDF 第 5 页**](https://arxiv.org/pdf/2102.06171v1#page=5)。NFNet 的 bottleneck 块，以及和 ResNet 在宽度、深度上的差别。*\n\n起点是 SE-ResNeXt-D，激活函数换成 GELU，再改四个地方：\n\n1. **分组宽度固定为 128**。分组越细 FLOPs 越低，但计算密度也跟着下降，在加速器上实际不会变快。\n2. **深度配置 [1, 2, 6, 3]**。较大的模型直接乘上 N，例如 F1 是 [2, 4, 12, 6]。ResNet 传统上只加深中间两个 stage，作者认为这不是最好的做法。\n3. **宽度配置 [256, 512, 1536, 1536]**。把容量集中到第三个 stage，第四个 stage 略微缩小，训练速度大致不变。\n4. **块里多一层 $3\\times3$ 分组卷积**。就是图上连续的两个 3x3。作者说它只多一点点 FLOPs，在他们的加速器上，训练时间几乎不受影响。\n\n放大模型时只加深度和分辨率，不加宽度，作者发现加宽对 ResNet 类的骨干效果不好。每往上一级，训练时间大约加倍，Dropout 也跟着调高，从 F0 的 0.2 到 F6 的 0.5。少了 BatchNorm 的隐式正则化，不靠 Dropout 这些显式的正则化，模型会严重过拟合。\n\n## 讨论\n\n### 每一步改动的贡献\n\n| 设置 | F0 | F1 | F2 | F3 |\n| --- | ---: | ---: | ---: | ---: |\n| 起点 | 80.4% | 81.7% | 82.0% | 82.3% |\n| ＋修改宽度 | 80.9% | 81.8% | 82.0% | 82.3% |\n| ＋第二层 $3\\times3$ | 81.3% | 82.2% | 82.4% | 82.7% |\n| ＋MixUp | 82.2% | 82.9% | 83.1% | 83.5% |\n| ＋RandAugment | 83.2% | 84.6% | 84.8% | 85.0% |\n| ＋CutMix | **83.6%** | **84.7%** | **85.1%** | **85.7%** |\n| 原本宽度＋上述增强 | 83.1% | 84.5% | 85.0% | 85.5% |\n\n*表：摘自原论文 Table 2，ImageNet top-1 准确率，3 个随机种子的平均，训练 360 个 epoch、批量 4096。*\n\n架构上的两个改动合起来多了 0.4 到 0.9 个百分点，真正拉开差距的是数据增强，光 RandAugment 就有 1 到 1.7 个百分点。这些强增强正是原本的 NF-ResNet 撑不住的东西，AGC 让它们能用上。最后一行换回 ResNet 原本的宽度配置，四个尺寸都比较差，证明宽度的调整确实有用。\n\n作者也把同样的增强套到 EfficientNet 上，RandAugment 有帮助，但层数超过 2，或再加 MixUp、CutMix，都没有更好。他们据此认为 NFNet 的进步不单是增强带来的。\n\n### 拿掉 BN 真的比较快吗\n\n把同样的架构改回 BatchNorm 训练，准确率和每一步的训练时间如下：\n\n| 模型 | NF 版本 | BN 版本 |\n| --- | --- | --- |\n| F0 | 83.6%，73.3 ms | 83.4%，111.7 ms |\n| F1 | 84.7%，158.5 ms | 84.4%，258.0 ms |\n| F2 | 85.1%，295.8 ms | 85.1%，396.3 ms |\n\n*表：摘自原论文 Table 6，ImageNet top-1 准确率与 TPUv3 上每一步的训练时间。*\n\n准确率差不多，NF 版本略好或打平，每一步的训练时间则少了两成五到四成。作者还提到，更大的 F4、F5 改用 BatchNorm 时，加不加 AGC 都训练不稳，他们推测是为了塞进内存而用的 bfloat16，和批量统计量的计算合不来。\n\n### 和其他模型比\n\n[![散点折线图，横轴是 TPUv3 上每一步的训练时间（秒），纵轴是 ImageNet Top-1 准确率（80% 到 87%）。红色实线是 NFNet-F0 到 F5，位置在所有其他模型的左上方；蓝色虚线是 EfficientNet B2 到 B7，绿色是 LambdaNet，橙色是 BoTNet，黑点是 DeiT。NFNet-F1 的准确率和最右边的 EffNet-B7 相当，训练时间却短得多](/content/papers/assets/7552bff30433f4f33bec5edb2421acd94d61d452888dde6df17557778e8fb1c6.png)](/content/papers/assets/7552bff30433f4f33bec5edb2421acd94d61d452888dde6df17557778e8fb1c6.png)\n\n*原论文 Figure 1，取自 [**v1 PDF 第 1 页**](https://arxiv.org/pdf/2102.06171v1#page=1)。ImageNet 验证准确率对 TPUv3 训练时间，单模型、单次裁剪。*\n\n红线整条都在其他模型的左上方，也就是同样的训练时间下准确率更高。最常被引用的一组数字是 NFNet-F1 和 EfficientNet-B7，两者都是 84.7%，F1 每一步只要 158.5 ms，B7 要 1397.0 ms，快了将近 9 倍。最大的 NFNet-F5 做到 86.0%，加上 SAM 优化器，F5 到 86.3%，F6 到 86.5%，是当时不用额外数据的最佳成绩。\n\n不过这条红线是按训练时间画的。换成参数量来看，NFNet 一点也不省。F0 有 71.5M 参数、12.38 GFLOPs，EfficientNet-B0 只有 5.3M、0.39 GFLOPs；F1 的参数也是 B7 的两倍。作者在论文里说得很清楚，他们优化的就是训练时间，FLOPs 和参数量不是目标。\n\n### 预训练之后再微调\n\n最后一组实验，作者在 3 亿张有标注的图像上预训练，再到 ImageNet 微调。BatchNorm 的正则化效果在这种数据量下可能反而碍事，作者推测没有 BN 的网络更适合这种场景：\n\n| 模型 | 224px | 320px | 384px |\n| --- | ---: | ---: | ---: |\n| BN-ResNet-50 | 78.1% | 79.6% | 79.9% |\n| NF-ResNet-50 | **79.5%** | **80.9%** | **81.1%** |\n| BN-ResNet-200 | 81.8% | 83.1% | 83.5% |\n| NF-ResNet-200 | **82.9%** | **84.1%** | **84.3%** |\n\n*表：摘自原论文 Table 4 的两组，预训练后在 ImageNet 微调的 top-1 准确率。完整的表格还有 ResNet-101 与 152，结果一致。*\n\n每一组都是 NF 版本赢，差距大约 1 个百分点。作者再用稍微加宽的 NFNet-F4+ 预训练 20 个 epoch，微调后做到 89.2%，是当时用迁移学习得到的最高成绩。\n\n## 结论\n\nBatchNorm 拿掉了，训练反而更快。只是 BatchNorm 原本顺手做掉的事，得一件件自己补回来，均值偏移交给 Scaled Weight Standardization 和增益，残差分支的大小交给 $\\alpha$、$\\beta$ 和 SkipInit，正则化交给 Dropout 和强数据增强，大批量训练则靠 AGC 撑住。\n\n想直接用的话，timm 里有 `dm_nfnet_f0` 到 `dm_nfnet_f6`，还有 timm 自己训练、为 GPU 瘦身过的 `eca_nfnet_l0` 系列；AGC 也能用 `adaptive_clip_grad` 单独套在自己的模型上。\n\n噢，对了，如果你要训练任何 NFNet 变体，要记得开梯度裁剪，别不信邪呀！\n","imageSizes":{"/content/papers/assets/9c1e7cd2c8c545de3ebab245e654b0dd69b3921c4f0479f0e99131833bdfc5f2.png":{"width":1004,"height":417},"/content/papers/assets/6dc27a9ccca4de619c057562ea6384aab3c095ddc08b22f475601ab03fcf03bb.png":{"width":966,"height":288},"/content/papers/assets/7552bff30433f4f33bec5edb2421acd94d61d452888dde6df17557778e8fb1c6.png":{"width":1004,"height":783}}}
