PyTorch中s[:]与s在AdaDelta优化器中的差异疑问
s[:]与直接赋值s的差异疑问 我在学习《Dive Into Deep Learning》中的AdaDelta优化器时,发现示例代码里用了s[:]和delta[:]做赋值操作。一开始我觉得直接用s和delta就行,就把代码第1、2行的左侧赋值改成了s和delta,但多次设置manual_seed测试后,结果差异特别大——直接用s和delta时的结果曲线波动剧烈得多。我注意到两种赋值方式后s的形状是一致的,而且用s.data和delta.data替换s[:]、delta[:]并做对应修改后,效果和原代码一致,想问问这两种写法的本质差异是什么。
原代码:
def adadelta(params, states, hyperparams): rho, eps = hyperparams['rho'], 1e-5 for p, (s, delta) in zip(params, states): s[:] = rho * s + (1 - rho) * (p.grad.data**2)# 1 g = p.grad.data * torch.sqrt((delta + eps) / (s + eps)) p.data -= g delta[:] = rho * delta + (1 - rho) * g * g# 2
本质差异解析
1. 对象引用 vs 原地修改
直接写s = rho * s + ...时,你是创建了一个全新的Tensor对象,并让循环里的变量s指向这个新对象。但states列表中保存的还是原来那个旧Tensor的引用,下一次迭代时,取到的依然是旧对象的初始值(或未正确更新的值)——这等于完全破坏了AdaDelta核心的滑动平均累积逻辑,因为每次迭代都没有真正更新状态变量,导致优化器无法正确跟踪梯度的二阶矩,最终训练曲线波动剧烈。
而s[:] = rho * s + ...是原地修改原Tensor的内容:变量s指向的还是states里的那个原始Tensor对象,只是把它内部的数据更新了。这样每次迭代后,states中的状态值都会被正确累积,AdaDelta的滑动平均计算才能正常工作。
2. 关于.data的等价性
用s.data = ...或s.data.copy_(...)替换s[:]效果一致,是因为.data访问的是Tensor的底层数据存储,修改它同样是在原对象上操作,没有改变states中的引用关系。不过要注意,PyTorch新版本中.data已被标记为过时,更推荐用torch.no_grad()上下文管理器包裹赋值操作,或者直接使用原地切片赋值(s[:] = ...)的方式,避免意外的计算图追踪问题。
内容的提问来源于stack exchange,提问作者XUHAO77

