PyTorch自定义优化器初始化方式为何影响ResNet在CIFAR-10的性能?
自定义SGD扩展优化器的两种初始化逻辑为何性能差异显著?
我尝试实现一个基于SGD扩展的自定义PyTorch优化器,先后写了两种初始化逻辑,但在CIFAR-10上训练ResNet时,二者测试准确率差异很大:minimiser_1准确率约88%,minimiser_2则能达到约95%。对比二者的defaults、param_groups等属性,除了字典键值对顺序不同,内容完全一致,想知道性能差异的根源。
1. minimiser_1 初始化逻辑
class minimiser_1(torch.optim.Optimizer): def __init__(self, params, base_optimizer, rho=0.05, **kwargs): params_copy = list(params) defaults = dict(rho=rho, **kwargs) self.base_optimizer = base_optimizer(params_copy, **kwargs) defaults = {**self.base_optimizer.defaults, **defaults} super().__init__(params_copy, defaults)
2. minimiser_2 初始化逻辑
class minimiser_2(torch.optim.Optimizer): def __init__(self, params, base_optimizer, rho=0.05, **kwargs): defaults = dict(rho=rho, **kwargs) super().__init__(params, defaults) self.base_optimizer = base_optimizer(self.param_groups, **kwargs) self.param_groups = self.base_optimizer.param_groups self.defaults.update(self.base_optimizer.defaults)
核心原因:参数组的所有权与共享机制差异
虽然表面上param_groups内容一致,但二者的参数组指向完全不同:
- 在
minimiser_1中,先创建params_copy并用它初始化基础优化器,再将params_copy传给父类Optimizer构造函数。这会导致父类Optimizer和基础优化器各自维护独立的参数组副本——父类的param_groups和基础优化器的param_groups是两个不同的字典对象,仅内容相同。后续训练时,若自定义优化器的step()操作父类参数组,而基础优化器更新自身副本,会出现参数更新不同步的情况,部分梯度更新未生效,最终模型收敛效果差。 - 在
minimiser_2中,先让父类Optimizer初始化参数组,再将父类的self.param_groups传给基础优化器,随后将self.param_groups替换为基础优化器的参数组并更新defaults。这实现了父类与基础优化器共享同一套参数组对象——后续自定义逻辑和基础优化器的step()操作的是同一个参数组,参数更新完全同步,梯度能正确作用到模型参数上,收敛效果符合预期。
补充验证方法
可在两种优化器的step()方法中打印以下内容的内存地址:
self.param_groups[0]['params'][0].gradself.base_optimizer.param_groups[0]['params'][0].gradminimiser_1中二者地址不同,证明是独立副本;minimiser_2中二者地址相同,证明是共享对象。
这种参数组的隐性引用不一致,是PyTorch自定义优化器的常见坑——看似属性内容一致,但对象引用关系不同,直接导致参数更新逻辑失效。
内容的提问来源于stack exchange,提问作者VJ123
相关产品推荐
相关产品推荐

