
我读这篇 Group Equivariant Convolutional Networks 的时候正是被一组实验数字刺激到了同一个卷积网络输入旋转 45 度之后分类准确率直接掉了将近 20 个百分点而加上论文里那套群卷积结构之后旋转任意角度预测结果几乎纹丝不动。这种结构本身带来的稳定性和靠数据增强硬扛出来的稳定性体验完全不一样。这篇笔记我不打算复述论文公式而是把 G-CNN 为什么会有效、群卷积到底做了什么、以及我用 PyTorch 复现 P4-CNN 时踩过的坑整理出来给那些正在研究几何深度学习、或者被图像旋转问题折磨的读者一个可以直接上手的参考。 我需要先说明一点G-CNN 不是某个炼丹技巧而是一种通过改变网络内部特征表示方式让卷积结构本身对旋转、反射这类对称变换免疫的设计思路。这篇论文发表于 2016 年作者是 Taco Cohen 和 Max Welling到现在依然是理解等变网络的必读材料。如果你关心的是为什么同样的数据别人转一下图效果还是很好或者如何让模型在数据很少的情况下也能学会旋转不变性那这篇笔记应该能帮你把这套思路吃透。1. 为什么 CNN 对旋转这么脆弱从平移等变化说起1.1 平移等变是 CNN 的隐形底座说到卷积神经网络有一个特性经常被默认是正确的那就是平移等变性。什么叫等变简单说如果输入图像往右平移了 5 个像素那么卷积输出的特征图也会跟着往右平移 5 个像素而且每个位置的激活值基本不变。这个性质保证了我们在一张图的左上角检测到一只猫的眼睛换到右下角时同一个卷积核依然能检测到它。这个特性的来源是卷积操作的数学本质卷积核在输入上滑动不管滑动到哪里都是同一套权重。这就是权值共享。权值共享让 CNN 天生具备了应对物体出现在不同位置的能力。很多人没有意识到的是这个能力是结构带来的不需要任何数据去学。我们给网络喂再多猫在左边的样本也无法教会网络猫在右边同样好使——但卷积结构天然就做到了。问题是同样的逻辑一旦换到旋转上就全部失效了。猫转了个方向卷积核还是原来那个方向的边缘检测器激活值当然就变了。你可能会说多加点旋转增强的数据不就行了但这里有个关键区别数据增强是在训练阶段把各种旋转版本喂给网络让网络见过这些情况。它确实有效但本质上是在用数据量弥补结构缺陷而且训练完的模型面对没见过的旋转角度时依然没有保证。1.2 旋转一变效果就崩一个直观的实验现象我在复现这篇论文之前先做了一个简单验证用标准的 4 层 CNN 在 MNIST 上训练然后在测试阶段把输入图像旋转 45 度去看准确率。结果非常直观——训练时准确率 99% 以上旋转之后直接掉到 40% 上下。这是很典型的行为。原因也不难理解。CNN 的第一层会学习到各种方向的边缘、角点特征但是这些特征的方向分布不是均匀的。因为训练数据里数字大多是正的网络学到的水平边缘检测器权重就很强垂直的弱一点斜的更弱。一旦输入整体旋转原来竖直的笔画变成了斜的原来的水平边缘检测器响应大幅降低后续层跟着遭殃。整个特征表示错位了。有人可能觉得那我用旋转增强不就行了。对但增强有天花板。比如你把训练数据里的每个样本都旋转 8 个角度模型确实对离散的 8 个角度鲁棒了但旋转 15 度呢插值误差加上卷积核的方向选择性效果还是会下降。而且数据量会变成原来的 8 倍训练成本也跟着涨。G-CNN 处理的是这个问题的根源它让卷积核本身就具备方向感知能力同时通过群结构共享不同方向之间的参数。1.3 群论把变换变成数学对象要理解 G-CNN最基础的概念是群。群论听起来高大上其实就是一个集合加上一种运算满足封闭性、结合律、单位元和逆元这几个条件。对我们这个场景来说最重要的例子是群的组合结构平移有平移群旋转有旋转群它们可以组合成更大的群。举个例子P4 群就是我们这篇论文里最常用的一个对称群。它包含正方形网格上的 4 个旋转0 度、90 度、180 度、270 度加上平面上的所有平移。你可以先旋转再平移也可以先平移再旋转这两种操作组合起来仍然是群里的一个元素只是顺序不同结果不一定相同。这就是半直积结构。为什么需要这个结构因为单说旋转不变性还不够。如果你只对图像做旋转不考虑平移那输出还是一个全局的描述对检测任务完全不够用。实际上我们需要的是图像里的物体不管出现在哪里、朝向哪个方向网络都能给它一个稳定且位置感知的特征描述。这就需要把旋转和平移放在同一个数学框架里统一处理。群论恰好提供了一个精确的语言来描述这种组合关系这是 G-CNN 的理论根基。2. 论文核心思路如何在群上做卷积2.1 从平面卷积到群卷积核心公式的直觉理解普通 CNN 的卷积公式写出来本质上是在平面网格上做加权求和。每个输出位置的值是输入上一个小窗口和卷积核的内积。平移等变性就藏在这个公式里窗口滑到哪卷积核就在哪计算权重不随位置改变。G-CNN 做的事情是把滑动窗口这个概念从二维平面推广到群上。群卷积的公式长这样(f * ψ)(g) Σ_{h∈G} f(h) ψ(g^{-1}h)第一次看到这个公式的人普遍会懵。我用一个直白的类比来解释假设你有一组变换操作比如旋转 0 度、90 度、180 度、270 度。群卷积在计算某个位置 g 的输出时会把所有可能变换 h 对应的特征 f(h) 都取出来和经过 g^{-1}h 变换后的卷积核做内积然后求和。这个公式的妙处在于它不止对输入特征做了变换还把卷积核本身做了变换。正是这个核也跟着变换的动作让卷积操作对群里的所有元素保持一致的行为。今天的特征图如果转了个角度明天的卷积核也会以同样的方式转过来特征图的变化就能与输入的变化同步这就是等变性。2.2 提升映射把二维图像抬到群上那第一层怎么办输入图像只是一个定义在二维平面上的标量场不是定义在群上的函数。论文提出的解决办法是提升映射。所谓提升就是把一个平面上的特征图 f(x) 扩展成一个群上的函数 f(g) f(g^{-1}x)。这句话翻译成人话就是在输入图片上把所有可能的旋转都做一遍把每个旋转结果单独存成一个通道。比如对于 P4 群输入是一张 HxW 的灰度图经过提升层之后变成 4 个 HxW 的特征图分别对应旋转 0、90、180、270 度后的结果。这就是论文里说的 lift 层。这一步的本质是给网络一个显式的方向坐标。在普通 CNN 里方向信息是隐式的模型只能通过不同位置的权重去隐式编码方向。而在 G-CNN 里每个方向都对应一个显式的通道卷积操作可以在这个方向维度上跨越这就为后面的方向信息共享奠定了基础。提升层的参数量其实和普通卷积层一样因为旋转后的特征图只是输入的不同变换版本并不引入新权重大小。但是输出通道数变多了计算量也相应增加。这是 G-CNN 的第一个成本稍微多用了一点显存但结构上的收益随后会体现出来。2.3 核心性质为什么等变不是不变我在读论文时花了好一会才理解等变和不变的区别这是理解 G-CNN 最要紧的一步。所谓旋转不变指的是输出完全不受旋转影响。分类任务最终要的是这种性质一张猫的图不管怎么转类别都是猫。但如果网络第一层就要求不变问题就大了——猫的位置信息、朝向信息全被抹掉了后续层根本无法定位物体。而事实上中间层的特征表示需要保留这些信息不然检测、分割这类空间敏感任务就废了。等变的意思是输入旋转多少度特征图也跟着旋转多少度。特征图的内容没变只是坐标系变了。这就保证了信息的完整保留只是换了个参考系。真正的旋转不变性可以在网络的最终层通过池化操作来实现把群上所有元素的响应取最大值或者均值就得到了一个与旋转无关的描述。论文的洞察就在这里把等变和不变拆开。中间层只需要且必须是等变的最后用一个对称化的算子把等变变成不变。这也是为什么 G-CNN 比把输入做成各种旋转然后扔进网络的思路更优雅——它从底层维护了变换之间的关联性而不是在输入端粗暴地把所有情况枚举一遍。2.4 滤波器共享参数量省在哪很多人第一次看 G-CNN 的参数量分析会有点困惑输出通道明明多了 4 倍为什么参数量反而更少这里要理解群卷积和普通卷积的实现差异。在普通 CNN 的某一层中假设有 C_in 个输入通道、C_out 个输出通道卷积核大小是 k×k那么权重大小是 C_in × C_out × k × k。每一个输出通道对应一个独立的卷积核不同输出通道之间没有任何共享。在 G-CNN 的群卷积层中输入变成了 C_in × |G| 个通道输出变成了 C_out × |G| 个通道表面上看权重应该是 C_in × |G| × C_out × |G| × k × k这就爆炸了。但群卷积的关键设计是不同群元素对应的卷积核不是独立可学习的而是同一个基础卷积核经过群变换得到的。也就是说从 C_in × C_out × k × k 这个基础核出发通过旋转它来生成所有方向上的变体真正可学习的参数只有这一个基础核。这就是参数共享的力量。普通 CNN 如果要处理旋转只能增加数据或者增加通道数来隐式学习方向特征每个方向都要独立学习一套权重。而 G-CNN 通过数学约束让一个核衍生出所有方向学习复杂度大大降低。论文实验里反复强调的参数效率就是这个意思。3. 复现实操手写一个 P4-CNN3.1 环境准备与数据集选择复现这篇论文不挑环境PyTorch、TensorFlow 都行关键是你得理解 P4 群对应的旋转操作怎么在张量上落地。我这边用的环境是 Python 3.9 PyTorch 1.13 CUDA 11.7数据集用的 rot-MNIST就是每个样本随机旋转后的 MNIST。这个数据集是检验旋转等变的经典基准训练集和测试集都包含随机角度模型必须在结构上具备旋转鲁棒性才能拿高分。我用普通 CNN 做了一个 baseline结构是 32C3-BN-ReLU-MaxPool 重复两轮再加全连接层测试集准确率大概在 85% 左右。这里要说明一下这个数字看起来不高是因为 rot-MNIST 的测试集也是随机旋转的普通 CNN 靠训练集的增强很难覆盖到所有角度。如果只在原始 MNIST 上做旋转增强再测固定角度数据分布会简单很多。用 rot-MNIST 才能真正看出结构性的差异。在开始写 G-CNN 之前我建议先在你的环境里跑通一个最小的等变性测试脚本构造一张随机图旋转 90 度之后过同一个网络对比输出是否也跟着旋转。如果这个测试都过不了后面就别谈什么群卷积了。等变性测试是整个复现过程中最核心的验证手段我会在后面的坑位环节重点讲。3.2 预计算旋转卷积核核心步骤详解P4-CNN 的实现思路可以拆成两步第一步是预处理把基础卷积核旋转出 4 个方向版本第二步是执行群卷积用这些旋转后的核分别对输入做常规卷积然后按群结构聚合。先说预处理。假设我有一个 3×3 的基础卷积核是一个二维张量。旋转 90 度在 PyTorch 里可以直接用 torch.rot90 实现注意旋转方向的正负号要统一。我建议把 4 个旋转版本保存在一个维度为 4×C_in×C_out×k×k 的张量里这样后续可以一次性喂给卷积函数。这里有个容易被忽略的点如果输入特征图本身已经是群上的特征也就是它有 C_in×4 个通道那么旋转卷积核时不能只转空间维度还要同时处理通道维度的对应关系。具体来说群卷积需要对每个输入方向版本使用对应的旋转核。这个对应关系如果写错了网络虽然能训但等变性验证会失败效果也会打折扣。我一开始在这里写反了输入输出方向的映射导致输出特征在旋转不同角度时不是完美对齐测试集上准确率比正确实现低了 2 个百分点左右。3.3 提升卷积层与群卷积层的完整代码下面给出我这边的核心实现。为了简洁我只写了 P4-群卷积最核心的部分实际项目中你还需要把这些操作封装成 torch.nn.Module 并处理好 batch 维度。import torch import torch.nn.functional as F def make_rotated_kernels(base_kernel, rotations4): base_kernel: shape (C_in, C_out, k, k) return: shape (rotations, C_in, C_out, k, k) kernels [] for r in range(rotations): # 逆时针旋转 r*90 度注意 rot90 的参数 k 表示逆时针旋转次数 rotated torch.rot90(base_kernel, kr, dims[-2, -1]) kernels.append(rotated) return torch.stack(kernels, dim0) def p4_lift_conv(x, base_kernel): 提升卷积输入 x shape (B, C_in, H, W) 输出 shape (B, C_in, 4, H, W) B, C_in, H, W x.shape C_in, C_out, k, _ base_kernel.shape kernels make_rotated_kernels(base_kernel, rotations4) # (4, C_in, C_out, k, k) outputs [] for r in range(4): # 用第 r 个旋转核做普通卷积 out F.conv2d(x, kernels[r], paddingk // 2) outputs.append(out) # (B, C_out, H, W) return torch.stack(outputs, dim2) # (B, C_out, 4, H, W) def p4_group_conv(x, base_kernel): 群卷积输入 x shape (B, C_in, 4, H, W) (已经在群上) 输出 shape (B, C_in, 4, H, W) B, C_in, R, H, W x.shape _, C_out, k, _ base_kernel.shape kernels make_rotated_kernels(base_kernel, rotations4) # (4, C_in, C_out, k, k) # 将旋转维度合并到 batch 维度方便做普通卷积 x x.permute(0, 2, 1, 3, 4).reshape(B * R, C_in, H, W) outputs torch.zeros(B, R, C_out, H, W, devicex.device) for r_out in range(R): # 对每个输出旋转 r_out需要把所有输入旋转 r_in 的结果聚合 partial torch.zeros(B, C_out, H, W, devicex.device) for r_in in range(R): # 对应关系r_out r_in r_kernel (模4)其中 r_kernel 是核的旋转 r_kernel (r_out - r_in) % R # 从合并后的 batch 中取出第 r_in 个输入旋转版本 x_in x[r_in::R] # (B, C_in, H, W) out F.conv2d(x_in, kernels[r_kernel], paddingk // 2) partial out outputs[:, r_out, :, :, :] partial # (B, R, C_out, H, W) - (B, C_out, R, H, W) return outputs.permute(0, 2, 1, 3, 4)这段代码有个细节我要特别强调F.conv2d的 padding 是空间维度的对旋转维度没有影响所以群卷积里的空间边界处理和普通 CNN 一样。但旋转维度上的聚合是全连接式的每个输出旋转都需要所有输入旋转做一次卷积后求和。这也是为什么群卷积的计算量是普通卷积的 R 的平方倍。在 P4 群里 R4计算量是 16 倍这个成本不小但换来的是对旋转的完全等变。实际部署时我建议把上面这个双层循环优化成用分组卷积实现速度可以快很多。具体做法是把 4 个输入旋转版本作为 4 个分组每个分组分别做卷积然后通过 reshape 和 permute 来聚合。论文早期的代码就是这样做的。不过第一次复现时先用循环把逻辑跑通能帮助你更好地理解群卷积到底在干什么。3.4 完整网络结构设计建议论文里的 G-CNN 网络结构并不复杂。它用了三层群卷积每层之后接 BatchNorm 和 ReLU中间穿插池化最后接一个全连接层做分类。我自己复现时用的结构如下第一层提升卷积把单通道输入变成 10 通道的群特征输出尺寸为 (10, 4, H, W)第二层群卷积把 10 通道变成 20 通道输出尺寸 (20, 4, H/2, W/2)中间用最大池化降采样第三层群卷积把 20 通道变成 50 通道输出尺寸 (50, 4, H/4, W/4)最后对整个群维度做最大池化得到一个与旋转无关的向量再送入全连接层这里有个容易踩坑的设计问题池化放在群卷积之前还是之后答案是要放在群卷积之后因为池化操作本身也需要等变。如果你在群特征上直接做标准空间池化它确实对平移等变但对旋转维度没有处理。实际上标准池化对旋转是等变的因为池化是在空间上逐通道独立做的旋转只改变空间布局不改变每个通道的激活值分布。所以先群卷积再池化没问题。但要注意的是如果你的池化核不是正方形的比如 2×1 的矩形池化旋转之后就不等变了。所以我建议统一用正方形池化核这是最简单稳妥的选择。3.5 参数量与计算量的真实对比我把普通 CNN 和 G-CNN 在相近容量配置下的参数量做了对比结果很有意思G-CNN 的参数量反而更少。原因就是之前的权重共享逻辑。我们以第二层群卷积为例普通 CNN 如果输入是 10 通道输出 20 通道核大小 3×3参数量是 10×20×3×3 1800。G-CNN 的输入是 10×4 个通道输出是 20×4 个通道但因为旋转版本共享同一套核参数量依然是 10×20×3×3 1800。输出通道多了 4 倍参数量却一样这就是共享的魔力。计算量则明显增加。普通 CNN 这一层的 FLOPs 大约是输入尺寸乘以参数量G-CNN 因为是完整的群卷积需要对 4 个输入旋转和 4 个输出旋转分别计算计算量是普通 CNN 的 16 倍。在实际显存和算力有限的情况下这是一个需要考虑的 trade-off。从论文报告和我的复现结果看G-CNN 在 rot-MNIST 上准确率可以到 98% 以上而参数量只有普通 CNN 的四分之一左右。在 CIFAR-10 这种自然图像上G-CNN 也能达到接近甚至超过普通 CNN 的水平且对旋转的鲁棒性更强。如果算力允许这种结构上的收益是值得投入的。4. 复现路上的坑与排查心得4.1 边界效应对旋转等变性的破坏这是我复现时遇到的第一大坑。群卷积的实现里使用了 padding但 PyTorch 默认的 padding 是补零。图像旋转之后补零区域的位置也随之旋转这本来没问题。但边界上的零值进入卷积窗口后会导致边界处的响应与中间区域不一致破坏严格的等变性。具体的表现是一张图旋转 90 度后过网络输出特征图在中心区域完美对齐但靠近边缘的地方有明显的数值差异。这个差异在深层网络中被逐层放大最终影响分类结果。论文里对此没有详细讨论但在实际实验里是真实存在的。解决办法有几个一是对输入做中心裁剪并配合反射 padding减少补零的影响二是在代码里对所有旋转操作保持相同的边界处理方式三是在评估等变性时只比较中心区域的响应把边界效应的影响隔离开。我后来采取了反射 padding 之后等变性验证的误差下降了将近一个数量级。4.2 旋转方向的约定一个隐蔽的错误源旋转卷积核时逆时针还是顺时针、用 torch.rot90 的 k 参数还是自己写坐标变换这些细节很容易写反。写反的结果是网络性能下降但不会完全崩溃所以很难发现。我排查这个问题的方式是写了一个单元测试随机生成一个基础核旋转一次之后再做一次反向旋转看是否能恢复成原来的核。如果这里通过了再验证整个群卷积的等变性。顺便说一句torch.rot90 的默认方向是逆时针你在代码注释里最好写清楚不然过一个月自己都会忘。还有一个小细节群卷积里的旋转对应关系和提升层需要保持一致。提升层用了 4 个旋转核群卷积里的聚合顺序必须和提升层的旋转顺序匹配。我是通过打印每一层的输出形状和对应激活值来排查的最后发现是索引顺序写拧了。4.3 全连接层如何做到旋转不变很多人会在群卷积之后直接拍平特征图再接全连接层。这里有个大坑如果你把特征图拍平成向量旋转操作就不再是置换操作了全连接层学到的权重也不会自动对旋转等变。换句话说你在群里精心构造的等变性到全连接层就断了。正确做法是在全连接层之前先对群维度做一次对称化池化。具体来说把特征图在旋转维度上取最大值或者平均值这样特征就变成了一个与旋转无关的向量此时再进全连接层旋转就影响不到分类结果了。我用平均值池化效果比最大值池化稳定因为最大值池化在某些情况下会对噪声敏感。这个做法和卷积层内的等变性是配套的前面一直保持等变最后一步用对称化把等变变成不变。如果这一步做错整个 G-CNN 的效果会大打折扣。4.4 训练策略BatchNorm 的维度问题群卷积的输出特征图是 (B, C_out, R, H, W) 的形状。很多人在用 BatchNorm 时直接把特征图 reshape 成 (B, C_out×R, H, W)然后调用标准的 BatchNorm2d。这样做的结果是 BatchNorm 会在不同旋转版本之间共享统计量导致旋转维度的均值和方差被混合。这样做有两个问题一是如果不同旋转版本的激活值分布差异很大BatchNorm 的归一化效果会变差二是严格来说BatchNorm 本身对旋转并不是完全等变的因为它是逐通道归一化而旋转操作会改变通道与空间位置的对应关系。论文里用的是普通的 BatchNorm但我的实际经验是把旋转维度当作通道的一部分并在每个旋转版本上分别做归一化收敛会更稳定。具体实现就是使用 BatchNorm2d 并设置通道数为 C_out×R然后把特征图 reshape 成 (B, C_out×R, H, W) 再过 BN。这样每个旋转版本都有自己的归一化统计量。当然这也会增加一些显存占用但对训练稳定性帮助很大。4.5 如何验证你的实现真的等变这是复现过程中最重要的一步甚至可以当作验收标准。做法很简单取一张输入图像 x过一个 G-CNN 的某一层得到输出 y。然后把输入旋转 90 度得到 x_rot再过同一层得到 y_rot。如果这个层是等变的y_rot 应该等于 y 在对应维度上旋转 90 度后的结果。我写了一个脚本用随机生成的输入和随机初始化的网络做这个检查并计算最大绝对误差。刚开始误差大概在 1e-3 量级看起来还行但我把 padding 从补零换成反射之后误差降到了 1e-6。这个数字的差别说明边界处理方式才是影响等变性的最大因素而浮点运算本身的误差可以忽略不计。强烈建议你把这个测试脚本挂在网络训练的每一步一旦出现误差突然增大的情况立刻可以定位到是哪一层出了问题。这个习惯帮我省了不少排查时间。5. G-CNN 之后相关方向与使用建议5.1 从离散群到连续群Steerable CNN 的发展G-CNN 的核心思想是用离散群来对称化网络结构但它有个天然局限只能处理你预先定义好的那几个离散角度。P4 群只能处理 90 度的整数倍如果图像旋转了 45 度效果就退化了。实际场景里物体朝向往往是连续分布的这就需要更精细的工具。后续的 Steerable CNN 系列工作就是为了解决这个问题。它的核心思路是不直接对卷积核做旋转而是把核表示为一系列基函数的线性组合这些基函数在旋转下有明确的行为这样网络就可以处理连续旋转。这就有点像傅里叶分解任何核都可以表示成可旋转基的叠加变换的时候只需要变换基系数保持不变。Harmonic Networks 也做了类似的事只不过它把旋转表示放在频率域里来操作。这些方法在理论上比 G-CNN 更优雅但实现复杂度也更高。如果你只是想在常规任务里提升旋转鲁棒性G-CNN 的离散群方案已经够用了如果你在钻研点云、分子结构这类旋转对称性极其重要的领域那 steerable 方向的思路更值得深入。5.2 什么场景适合用 G-CNN根据我自己的实验适合使用 G-CNN 的场景有几个共同特征一是任务本身对旋转敏感比如医学图像里的细胞方向、遥感图像里的地物朝向、工业检测里的零件摆放二是数据量不大参数效率比数据增强更重要三是你关心的变换是严格的离散旋转比如工业产品总是以 0/90/180/270 度摆放。反过来如果你的任务是自然图像分类物体方向整体是竖着的旋转扰动很小那 G-CNN 带来的收益就不明显反而因为计算量翻了好几倍而变慢。这种情况下普通 CNN 加适量数据增强反而是更划算的选择。我之前在 CIFAR-10 上测试过G-CNN 相比同容量普通 CNN 提升只有 1 个百分点左右但训练时间增加了 3 倍性价比很低。5.3 我对群卷积设计的几点个人体会复现完这篇论文给我最大的冲击不是它提升了多少准确率而是它让我重新理解了卷积的本质。传统 CNN 的权值共享是一种隐式的对称性假设而群卷积把这个假设显式化并且让你可以自由地选择要对称化的变换种类。这个思路可以平移应用到很多地方图神经网络里可以用置换等变替代平移等变点云网络里可以用旋转等变替代平移等变。另外一点是做这类结构创新时验证手段必须非常严谨。我花了大量时间写等变性测试反复核对旋转方向和边界处理这些工作看似和效果无关但事实上它们是整个方法成立的前提。如果你实现里的等变性误差在 1e-2 量级最终实验结论可能完全不可靠。把这个测试框架沉淀下来对以后研究其他等变结构也很有用。最后说一个小技巧如果你打算在现有项目里试用 G-CNN可以先用小模型在小数据集上跑通全流程再逐步扩大。因为群卷积的计算量增长快从一开始就在大数据集上调试会很痛苦。先把等变性验证、训练稳定性、超参数调优这些基础环节在小规模实验里搞定再上大规模预算会顺很多。