ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Weight Standardization 权重标准化:数学原理、PyTorch 实现与 CIFAR-10 实验详解

Weight Standardization 权重标准化:数学原理、PyTorch 实现与 CIFAR-10 实验详解 Weight Standardization 权重标准化:数学原理、PyTorch 实现与 CIFAR-10 实验详解【免费下载链接】annotated_deep_learning_paper_implementations‍ 60 Implementations/tutorials of deep learning papers with side-by-side notes ; including transformers (original, xl, switch, feedback, vit, ...), optimizers (adam, adabelief, sophia, ...), gans(cyclegan, stylegan2, ...), reinforcement learning (ppo, dqn), capsnet, distillation, ... 项目地址: https://gitcode.com/gh_mirrors/an/annotated_deep_learning_paper_implementations本篇聚焦于 annotated_deep_learning_paper_implementations 仓库中 Weight Standardization 模块 所讲解的核心技术:源自论文Micro-Batch Training with Batch-Channel Normalization and Weight Standardization(arXiv:1903.10520) 的权重标准化方法。读完后,你将理解 Batch Normalization 在小批量(micro-batch)场景下的失效原因、权重标准化的数学定义与平滑损失景观(降低 Lipschitz 常数)的机理,并掌握如何在 PyTorch 中实现标准化的卷积层,以及仓库配套的可直接运行的 CIFAR-10 VGG 训练实验代码。一、为什么需要 Weight StandardizationBatch Normalization(BN)的两大经典收益,在 模块文档 中有明确陈述:平滑损失景观(smooth loss landscape):BN 使网络的损失函数更平滑,便于优化;避免消除奇异性(avoiding elimination singularities):所谓消除奇异性,是指网络中的某些节点变得无用——例如某个 ReLU 神经元输出恒为 0,该节点从此对网络不再贡献任何信息。BN 之所以能避免消除奇异性,是因为它让每个节点的输出统计量与输入保持一致:只要输入近似服从正态分布,输出也保持在激活函数的有效激活区间内,不会出现ReLU 输入恒为负这类节点死亡的情况。但 BN 有一个硬伤:当 batch size 过小时,BN 失效。训练大型网络时,受设备显存限制,batch size 往往被迫调小,此时 BN 统计量(每个 mini-batch 内计算的均值和方差)噪声极大,归一化效果急剧退化。针对这一问题,论文提出了Weight Standardization(权重标准化) Batch-Channel Normalization的组合方案,作为小批量训练下更优的替代。文档归纳了 Weight Standardization 的三点核心作用:归一化梯度(normalizes the gradients);平滑损失景观,即降低函数的 Lipschitz 常数;避免消除奇异性——因为它使输出的统计量与输入保持相似:只要输入近似正态分布,输出就保持在近似正态的状态,节点输出就不会持续落在激活函数的无效区间之外。其中 Lipschitz 常数的定义为(引自init.py):设 $f: A \rightarrow \mathbb{R}^m$, $L$ 是满足如下条件的最小值,则称 $L$ 为 Lipschitz 常数:$$\forall a,b \in A: \lVert f(a) - f(b) \rVert \le L \lVert a - b \rVert$$直观地说,Lipschitz 常数是函数在任意两点间斜率的上界。Lipschitz 常数越小,损失表面越平缓,梯度变化越温和,优化器越不容易冲出好区域。仓库文档明确标注:上述性质的严格证明请参见原文论文,此处只做概念性引用。二、Weight Standardization 的数学定义与实现2.1 数学公式weight_standardization 函数 的文档给出了完整定义。对权重矩阵 $W \in \mathbb{R}^{O \times I}$:$$\hat{W}{i,j} \frac{W{i,j} - \mu_{W_{i,\cdot}}} {\sigma_{W_{i,\cdot}}}$$其中$$\mu_{W_{i,\cdot}} \frac{1}{I} \sum_{j1}^{I} W_{i,j}, \qquad \sigma_{W_{i,\cdot}} \sqrt{\frac{1}{I} \sum_{j1}^{I} W^2_{i,j} - \mu^2_{W_{i,\cdot}} \epsilon}$$即沿每一行(每个输出通道)计算均值与标准差,再逐行做标准化。对一个 2D 卷积层:$O C_{out}$:输出通道数;$I C_{in} \times k_H \times k_W$:输入通道数乘以核的高、宽。关键点在于:标准化是按输出通道对整个(输入通道, 核高, 核宽)展平向量进行的,而不是按输入通道,也不是对整个张量。这保证了每个输出卷积核内部统计量稳定,而不同输出核之间可以保留各自不同的尺度信息。2.2 PyTorch 实现逐行解析核心实现非常精炼,位于 labml_nn/normalization/weight_standardization/init.py:def weight_standardization(weight: torch.Tensor, eps: float): # Get $C_{out}$, $C_{in}$ and kernel shape c_out, c_in, *kernel_shape weight.shape # Reshape $W$ to $O \times I$ weight weight.view(c_out, -1) # Calculate # # \begin{align} # \mu_{W_{i,\cdot}} \frac{1}{I} \sum_{j1}^I W_{i,j} \\ # \sigma^2_{W_{i,\cdot}} \frac{1}{I} \sum_{j1}^I W^2_{i,j} - \mu^2_{W_{i,\cdot}} # \end{align} var, mean torch.var_mean(weight, dim1, keepdimTrue) # Normalize # $$\hat{W}_{i,j} \frac{W_{i,j} - \mu_{W_{i,\cdot}}} {\sigma_{W_{i,\cdot}}}$$ weight (weight - mean) / (torch.sqrt(var eps)) # Change back to original shape and return return weight.view(c_out, c_in, *kernel_shape)实现细节说明:形状拆解:c_out, c_in, *kernel_shape weight.shape利用 Python 解包,把[C_out, C_in, k_H, k_W]拆开为输出通道数、输入通道数与核形状三元组,使同一函数天然适配任意维度的核;视图变换:weight.view(c_out, -1)将张量重塑为 $O \times I$ 的二维视图(不复制内存),其中每行对应一个输出通道展平后的权重向量;统计量计算:torch.var_mean(weight, dim1, keepdimTrue)沿维度 1(即 $I$ 维)一次性算出方差与均值,keepdimTrue便于后续广播;源码注释中给出的方差等价式 $\sigma^2 \mathbb{E}[W^2] - \mathbb{E}[W]^2$ 在数值上可能比两遍扫描略有精度损失,这是torch.var_mean的直接实现;数值稳定性:sqrt(var eps)中的eps防止标准差为 0 时除零;形状还原:最后view(c_out, c_in, *kernel_shape)还原为原始卷积权重形状返回,保证下游卷积算子无感知。一个值得注意的设计:标准化在每次前向传播时对原始参数self.weight现场计算,参数本身按常规方式被优化器更新。也就是说,训练的是原始权重,而真正参与卷积的是标准化后的权重;权重在参数空间中的尺度被吸收掉了,只保留方向信息(具体缩放由卷积后的归一化层/偏置恢复表达能力)。三、带权重标准化的 2D 卷积层仅有一个标准化函数还不够,仓库提供了把它注入卷积层的模块 conv2d.py:import torch import torch.nn as nn from torch.nn import functional as F from labml_nn.normalization.weight_standardization import weight_standardization class Conv2d(nn.Conv2d): ## 2D Convolution Layer This extends the standard 2D Convolution layer and standardize the weights before the convolution step. def __init__(self, in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups: int 1, bias: bool True, padding_mode: str zeros, eps: float 1e-5): super(Conv2d, self).__init__(in_channels, out_channels, kernel_size, stridestride, paddingpadding, dilationdilation, groupsgroups, biasbias, padding_modepadding_mode) self.eps eps def forward(self, x: torch.Tensor): return F.conv2d(x, weight_standardization(self.weight, self.eps), self.bias, self.stride, self.padding, self.dilation, self.groups)设计要点(参见 forward 实现):该类直接继承nn.Conv2d,签名与标准卷积层完全一致,额外只增加一个eps参数(默认1e-5);forward不调用super().forward,而是手动调用F.conv2d,并传入weight_standardization(self.weight, self.eps)的返回值作为权重——即先标准化、再卷积。由于self.weight仍是nn.Parameter,反向传播会正常流经标准化变换更新原始权重;文件末尾附带了一个 自测函数_test():构造Conv2d(10, 20, 5),对[10, 10, 100, 100]的零张量做前向,用labml.logger.inspect打印权重与输出的张量形状,用于验证重塑逻辑没有破坏张量尺寸:def _test(): A simple test to verify the tensor sizes conv2d Conv2d(10, 20, 5) from labml.logger import inspect inspect(conv2d.weight) import torch inspect(conv2d(torch.zeros(10, 10, 100, 100)))四、搭档:Batch-Channel Normalization论文的方案是权重标准化 Batch-Channel Normalization成对使用。仓库在 labml_nn/normalization/batch_channel_norm/init.py 提供了对应实现,其结构是:先做 Batch Normalization,再做 Channel Normalization(近似 Group Normalization)。小批量问题的处理:标准 BN 依赖当前 mini-batch 的统计量,batch 小时不可靠。BatchChannelNorm默认使用EstimatedBatchNorm(由构造参数estimate: bool True控制),它用**跨多个 batch 维护的指数移动平均(EMA)**均值 $\hat{\mu}_C$ 与方差 $\hat{\sigma}^2_C$ 来归一化:$$\hat{\mu}C \longleftarrow (1 - r)\hat{\mu}C r \frac{1}{B H W} \sum{b,h,w} X{b,c,h,w}$$其中 $r$ 是 momentum(默认0.1),更新仅在training模式下发生(见 EstimatedBatchNorm.forward 中的if self.training:分支与torch.no_grad()包裹)。通道归一化:ChannelNorm(channels, groups, eps)类似 Group Normalization,但有一个微妙差别——其仿射变换(scale/shift)参数是按组(per group)定义的,而非按通道定义(见 ChannelNorm 实现 的注释:self.scale nn.Parameter(torch.ones(groups)))。构造签名:BatchChannelNorm(channels, groups, eps1e-5, momentum0.1, estimateTrue);归一化公式为 $\dot{X}{\cdot,C,\cdot,\cdot} \gamma_C \frac{X{\cdot,C,\cdot,\cdot} - \hat{\mu}_C}{\hat{\sigma}_C} \beta_C$。这套EMA 均值方差 分组通道归一化的设计,正是为了在 batch size 很小时仍有稳定的归一化行为,与权重标准化一起构成论文的 micro-batch 训练方案。五、CIFAR-10 完整实验:VGG 权重标准化5.1 模型结构实验代码位于 labml_nn/normalization/weight_standardization/experiment.py,它复用了仓库通用的 CIFAR-10 VGG 架构基类,只重写了卷积块以注入权重标准化与 Batch-Channel Norm:class Model(CIFAR10VGGModel): ### VGG model for CIFAR-10 classification This derives from the generic VGG style architecture. def conv_block(self, in_channels, out_channels) - nn.Module: return nn.Sequential( Conv2d(in_channels, out_channels, kernel_size3, padding1), BatchChannelNorm(out_channels, 32), nn.ReLU(inplaceTrue), ) def __init__(self): super().__init__([[64, 64], [128, 128], [256, 256, 256], [512, 512, 512], [512, 512, 512]])每个卷积块为Conv2d(3x3, padding1) → BatchChannelNorm(out_channels, groups32) → ReLU,五组通道数分别为 64/128/256/512/512(与 Model 定义 一致)。基类CIFAR10VGGModel保证:每个块末尾接一次MaxPool2d(2, 2),共 5 次池化,把 32×32 的 CIFAR-10 图像逐步降到 1×1,最后接nn.Linear(in_channels, 10)输出 10 类 logits(见 CIFAR10VGGModel)。5.2 训练配置与运行方式实验入口main()(见 experiment.py)的关键配置:配置项取值说明optimizer.optimizerAdam通过experiment.configs覆盖优化器optimizer.learning_rate2.5e-4小批量场景下的小学习率train_batch_size64训练 batch 大小epochs10(继承默认值)定义于 MNISTConfigsdevice自动选择 GPU/CPU继承自DeviceConfigs完整运行流程基于labml实验框架:def main(): # Create experiment experiment.create(namecifar10, commentweight standardization) # Create configurations conf CIFAR10Configs() # Load configurations experiment.configs(conf, { optimizer.optimizer: Adam, optimizer.learning_rate: 2.5e-4, train_batch_size: 64, }) # Start the experiment and run the training loop with experiment.start(): conf.run()等价的交互式版本是 experiment.ipynb,其步骤为:pip install labml-nn安装包 → 导入experiment与Configs→experiment.create(namecifar10, commentWS BCN)→conf Configs()→ 用experiment.configs(conf, {...})覆盖上表三个配置 →with experiment.start(): conf.run()。训练循环由 MNISTConfigs.step 提供:每个 step 内把数据移到设备、前向得到output、用nn.CrossEntropyLoss计算损失并记录到 tracker、计算并记录准确率、loss.backward()与optimizer.step();且只在每个 epoch 的最后一个 batch 记录一次完整的模型参数与梯度(if batch_idx.is_last: tracker.add(model, self.model))。数据增强方面,cifar10_train_augmented 对训练集使用RandomCrop(32, padding4)RandomHorizontalFlip(),并统一Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5));验证集不做增强,仅做同样的归一化。5.3 运行前提从 requirements.txt 看,运行该实验需要torch1.10、torchvision0.11、labml0.4.147等依赖(labml-nn包本身还依赖fairscale与einops,见 setup.py);数据集会在首次运行时自动下载到lab.get_data_path()指定目录。适用前提:仓库代码按 PyTorch 1.10 与 Python 3 编写,实验依赖labml实验框架(负责创建实验目录、记录配置与指标),若只想复用算法本身,可直接拷贝weight_standardization函数与Conv2d类,二者仅依赖torch。六、小结与延伸阅读本文梳理了仓库中 Weight Standardization 模块的完整技术脉络:动机:BN 能平滑损失景观、避免消除奇异性,但小 batch 下统计量不可靠;权重标准化从权重一侧稳定网络行为,三点收益(归一化梯度、降低 Lipschitz 常数、避免消除奇异性);实现:weight_standardization沿输出通道把权重重塑为 $O \times I$ 并逐行标准化,Conv2d子类在每次前向时用标准化权重调用F.conv2d;组合:与EstimatedBatchNorm(EMA 统计量) 按组仿射的ChannelNorm组成 Batch-Channel Normalization,共同支撑 CIFAR-10 上的 VGG micro-batch 实验。延伸阅读(仓库内同系列实现,路径均相对于仓库根目录):Batch-Channel Normalization:实验中的归一化搭档,EMA 统计量与分组通道归一化的完整实现;CIFAR-10 通用实验框架 与 MNIST 训练器配置:本实验继承的模型骨架与训练循环;Group Normalization 与 Batch Normalization:理解 ChannelNorm 与 BN 变体差异的对照参考;Normalization 模块总览:仓库内全部归一化层实现的导航入口。【免费下载链接】annotated_deep_learning_paper_implementations‍ 60 Implementations/tutorials of deep learning papers with side-by-side notes ; including transformers (original, xl, switch, feedback, vit, ...), optimizers (adam, adabelief, sophia, ...), gans(cyclegan, stylegan2, ...), reinforcement learning (ppo, dqn), capsnet, distillation, ... 项目地址: https://gitcode.com/gh_mirrors/an/annotated_deep_learning_paper_implementations创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表