From 8a3680238344bb42c2c7d88490fae56369976e63 Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Mon, 28 Mar 2022 21:02:25 +0800
Subject: [PATCH 01/10] add ASGD rfc
Signed-off-by: tiancaishaonvjituizi <452565578@qq.com>
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 145 ++++++++++++++++++++++
1 file changed, 145 insertions(+)
create mode 100644 rfcs/APIs/20220327_api_design_for_ASGD.md
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
new file mode 100644
index 000000000..e988b2824
--- /dev/null
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -0,0 +1,145 @@
+# paddle.optimizer.ASGD 设计文档
+
+
+| API名称 | paddle.optimizer.ASGD |
+| ------------------------------------------------------------ | ----------------------------------- |
+| 提交作者 | 我的名字连起来就是王豆豆 |
+| 提交时间 | 2022-03-27 |
+| 版本号 | V1.0 |
+| 依赖飞桨版本 | Develop |
+| 文件名 | 20220327_api_design_for_ASGD.md
|
+
+
+# 一、概述
+## 1、相关背景
+对应 Issue:https://github.com/PaddlePaddle/Paddle/issues/40314
+
+Averaged SGD 是一种对 SGD 优化算法的改进。可以证明[1]这个优化算法在理论上和使用 Hessian 矩阵的二阶 SGD 有相同的收敛速度,但在实现上要简单的多。它所做的事情和 SGD 完全一样,只是从某一次 iteration t0 之后,它会开始维护模型参数从 t0 时刻到现在的所有版本的平均值,并以这个平均值作为它优化得到的模型参数。
+
+## 2、功能目标
+
+在飞桨中增加 `paddle.optimizer.ASGD` 优化器
+
+## 3、意义
+飞桨用户将和 PyTorch 用户一样可以使用 `paddle.optimizer.ASGD` 优化器。
+
+# 二、飞桨现状
+飞桨目前不支持直接使用 ASGD 优化器,但用户仍可以自己用 SGD 优化器实现相同的功能,只需要在每次迭代时维护参数的平均值即可。但如果想用 ASGD 优化器取得理想的效果,一个合理的学习率策略非常重要[2],这对用户提出了很高的要求。
+
+
+# 三、业内方案调研
+PyTorch 有 ASGD 优化器的实现,文档在 https://pytorch.org/docs/stable/generated/torch.optim.ASGD.html ,代码在 https://github.com/pytorch/pytorch/blob/master/torch/optim/asgd.py 。
+
+PyTorch 的实现其实是**有问题**的,它最初版的实现参考了 [bottou-sgd](https://github.com/npinto/bottou-sgd) (在 PyTorch ASGD 的[最初一个版本](https://github.com/pytorch/pytorch/commit/554a1d83365cf80d8676686e8fcc190c0c95d1a9)中有说明),实现时可能没有特别重视这个优化器,因此没有消化它的原理而是囫囵吞枣的照搬公式和术语,导致它的文档和代码中出现了重复的概念,如:文档中 “eta update” 中的 “eta” 其实就是学习率 lr;参数`lambd` 其实就是 l2 regularization 的系数,和 `weight_decay` 参数的功能是高度重叠的。
+
+这里对照着 PyTorch ASGD 源码的逻辑把 [bottou-sgd](https://github.com/npinto/bottou-sgd) README 里的内容转述如下:
+
+我们要优化一个带 l2 正则项的函数 Obj(w),
+
+![img](https://latex.codecogs.com/gif.latex?%5Clarge%20Obj%28w%29%20%3D%20%5Cfrac%7B1%7D%7B2%7D%5Clambda%20w%5E2%20+%20loss%28w%29)
+
+按照梯度下降法的规则,更新量是 Obj(w) 的梯度乘以学习率(学习率称为 eta_t)(latex 在 codecogs 上渲染的,没有很好的排版功能,见谅!)
+
+![img](https://latex.codecogs.com/gif.latex?%5Clarge%20Obj%27%28w%29%20%3D%20%28lambda%20*%20w%20+%20loss%27%28w%29%29%20*%20%5Ceta_t%20%3D%20lambda%20*%20w%20*%20%5Ceta_t%20+%20w.grad%20*%20%5Ceta_t)
+
+等号最右边的两项里,第一项 lambda * w * eta_t 对应于 PyTorch 实现的 188-189 行
+
+```python
+ # decay term
+ param.mul_(1 - lambd * eta.item())
+```
+
+第二项 w.grad * eta_t 对应于 PyTorch 实现的 191-192 行
+
+```python
+ # update parameter
+ param.add_(grad, alpha=-eta.item())
+```
+
+PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 的计算平均值的算法,ax 在 t0 时刻之前是当前权重,在 t0 时刻之后是从 t0 时刻开始到当前为止的权重平均值。
+
+```python
+ # averaging
+ if mu.item() != 1:
+ ax.add_(param.sub(ax).mul(mu))
+ else:
+ ax.copy_(param)
+
+ new_mu = torch.tensor(1 / max(1, step - t0))
+ mu.copy_(new_mu)
+```
+
+
+
+200-201 行,是更新学习率(在 PyTorch 的实现里,eta 就是学习率,而 lr 是一个常量,专指用户设置的初始学习率)的策略,这个更新策略是照搬自 bottou-sgd,bottou-sgd 参考自 [2]。和其它的优化器不一样,ASGD 优化器的学习率并不能由 lr scheduler 控制,这可能也是它是被不经消化地加入 PyTorch 的一个表现。
+
+```python
+ new_eta = torch.tensor(lr / math.pow((1 + lambd * lr * step), alpha))
+ eta.copy_(new_eta)
+```
+
+再看看 PyTorch 的实现,除了上述的几段代码之外,还有一个名叫 weight_decay 的参数,在上面的推导里我们已经了解到,其实 lambda 就是 l2 正则项的系数,也就是 weight decay,再看看相关的代码来实锤这一点:
+
+```python
+ if weight_decay != 0:
+ grad = grad.add(param, alpha=weight_decay)
+
+ # decay term
+ param.mul_(1 - lambd * eta.item())
+
+ # update parameter
+ param.add_(grad, alpha=-eta.item())
+```
+
+经过一些简单的数学变换,不难发现 `weight_decay` 和 `lambd` 虽然看起来差异很大,但在这段代码里的作用是完全一模一样的。
+
+`lambd` 和 `weight_decay` 唯一不同的地方,是在学习率更新的策略里用到了 `lambd` 而没有用到 `weight_decay`。但 ASGD 作为一个优化方法并不应该和某种具体的学习率更新策略耦合。如果改由外部某个 lr scheduler 来控制 ASGD 的学习率,那么 ASGD 内的 `lambd` 和 `weight_decay` 就完全可以只留一个了。
+
+PyTorch 的 ASGD 还同时存在着 single_tensor 和 multi_tensor 两种实现,其它 PyTorch 优化器也是一样,和 ASGD 本身无关。
+
+到现在,PyTorch 的代码已经分析完成,我们也明白了 ASGD 的实现:它和普通的 SGD 可以说完全一样,只是在 `ax` 里保存了一份权重的平均值而已。因为相关作者的囫囵吞枣,它和其它优化器的实现风格格格不入,这阻碍了对它的理解。
+
+注意:PyTorch 和 TensorFlow 也实现了 Stochastic Weight Averaging,它和 Averaged SGD 并不是相同的概念。具体可以参考 https://pytorch.org/blog/stochastic-weight-averaging-in-pytorch。
+
+# 四、对比分析
+经过上面的分析可以发现 PyTorch 的实现是很有问题的。在飞桨里的实现可以以更加优雅和一致的方式实现。
+
+# 五、设计思路与实现方案
+
+## 命名与参数设计
+```python
+class paddle.fluid.optimizer.ASGDOptimizer(learning_rate, parameter_list=None, regularization=None, name=None)
+```
+
+和飞桨中其它优化器的风格保持一致。weight_decay 通过 `regularization` 参数设置,支持 L1/L2 正则。而学习率用 LR Scheduler 来控制,不内置在优化器内。并新增一个 LRScheduler 实现 [2] 中提出的学习率更新策略(具体名字可以后续决定)。
+
+
+
+## 底层OP设计
+
+基本可以仿照飞桨 SGD 优化器的实现,实现 paddle/fluid/operators/optimizers/asgd_op.cc 和相应的 asgd_kernel.h/.cc/.cu。
+
+## API实现方案
+
+基本可以仿照 python/paddle/optimizer/sgd.py,只是把调用的 op 从 sgd 变成 asgd,并在 outputs 中增加一个输出 “AveragedParamOut”,并提供一个 `GetAveragedParameters` 方法。
+
+# 六、测试和验收的考量
+增加完善的测试和文档,并和 PyTorch 的结果对比一致。
+
+# 七、可行性分析和排期规划
+前两周:实现相关代码、测试用例和文档。
+
+第三周:在 Code Review 中迭代。
+
+# 八、影响面
+ASGD 对其它模块没有影响。
+
+# 名词解释
+
+# 附件及参考资料
+
+[1] http://dl.acm.org/citation.cfm?id=131098
+
+[2] https://arxiv.org/abs/1107.2490
+
+[3] https://pytorch.org/blog/stochastic-weight-averaging-in-pytorch/
From ffd494ab8dae3e8407081854363a412ade621543 Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Tue, 29 Mar 2022 09:43:07 +0800
Subject: [PATCH 02/10] Refine ASGD RFC
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index e988b2824..5177490ca 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -14,23 +14,23 @@
## 1、相关背景
对应 Issue:https://github.com/PaddlePaddle/Paddle/issues/40314
-Averaged SGD 是一种对 SGD 优化算法的改进。可以证明[1]这个优化算法在理论上和使用 Hessian 矩阵的二阶 SGD 有相同的收敛速度,但在实现上要简单的多。它所做的事情和 SGD 完全一样,只是从某一次 iteration t0 之后,它会开始维护模型参数从 t0 时刻到现在的所有版本的平均值,并以这个平均值作为它优化得到的模型参数。
+Averaged SGD 是一种对 SGD 优化算法的改进。可以证明[1]这个优化算法在理论上和使用 Hessian 矩阵的二阶随机梯度下降法有相同的收敛速度,但在实现上要比二阶随机梯度下降法简单的多。Averaged SGD 所做的事情和 SGD 完全一样,只是从某一次 iteration t0 之后,它会开始维护模型参数从 t0 时刻到现在的所有版本的平均值,并以这个平均值作为它优化得到的模型参数。
## 2、功能目标
在飞桨中增加 `paddle.optimizer.ASGD` 优化器
## 3、意义
-飞桨用户将和 PyTorch 用户一样可以使用 `paddle.optimizer.ASGD` 优化器。
+飞桨用户将可以使用 `paddle.optimizer.ASGD` 优化器。
# 二、飞桨现状
-飞桨目前不支持直接使用 ASGD 优化器,但用户仍可以自己用 SGD 优化器实现相同的功能,只需要在每次迭代时维护参数的平均值即可。但如果想用 ASGD 优化器取得理想的效果,一个合理的学习率策略非常重要[2],这对用户提出了很高的要求。
+飞桨目前不支持直接使用 ASGD 优化器,但用户仍可以自己用 SGD 优化器实现相同的功能,只需要在每次迭代时维护参数的平均值即可。但如果想用 ASGD 优化器取得理想的效果,一个合理的学习率策略非常重要[2],所以如果能以开箱即用的方式提供 ASGD 优化器,对用户来说会是很好的体验。
# 三、业内方案调研
PyTorch 有 ASGD 优化器的实现,文档在 https://pytorch.org/docs/stable/generated/torch.optim.ASGD.html ,代码在 https://github.com/pytorch/pytorch/blob/master/torch/optim/asgd.py 。
-PyTorch 的实现其实是**有问题**的,它最初版的实现参考了 [bottou-sgd](https://github.com/npinto/bottou-sgd) (在 PyTorch ASGD 的[最初一个版本](https://github.com/pytorch/pytorch/commit/554a1d83365cf80d8676686e8fcc190c0c95d1a9)中有说明),实现时可能没有特别重视这个优化器,因此没有消化它的原理而是囫囵吞枣的照搬公式和术语,导致它的文档和代码中出现了重复的概念,如:文档中 “eta update” 中的 “eta” 其实就是学习率 lr;参数`lambd` 其实就是 l2 regularization 的系数,和 `weight_decay` 参数的功能是高度重叠的。
+PyTorch 的实现其实是**有问题**的,它最初版的实现参考了 [bottou-sgd](https://github.com/npinto/bottou-sgd) (在 PyTorch ASGD 的[最初一个版本](https://github.com/pytorch/pytorch/commit/554a1d83365cf80d8676686e8fcc190c0c95d1a9)中有说明),当时的 PyTorch 开发者可能没有特别重视这个优化器,因此没有消化它的原理而是囫囵吞枣的照搬公式和术语,导致它的文档和代码出现了问题,如:文档中 “eta update” 中的 “eta” 其实就是学习率 lr;参数`lambd` 其实就是 l2 regularization 的系数,和 `weight_decay` 参数的功能是高度重叠的。
这里对照着 PyTorch ASGD 源码的逻辑把 [bottou-sgd](https://github.com/npinto/bottou-sgd) README 里的内容转述如下:
@@ -56,7 +56,7 @@ PyTorch 的实现其实是**有问题**的,它最初版的实现参考了 [bot
param.add_(grad, alpha=-eta.item())
```
-PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 的计算平均值的算法,ax 在 t0 时刻之前是当前权重,在 t0 时刻之后是从 t0 时刻开始到当前为止的权重平均值。
+PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 的计算平均值的经典算法,ax 在 t0 时刻之前是当前权重,在 t0 时刻之后是从 t0 时刻开始到当前为止的权重平均值。
```python
# averaging
@@ -93,11 +93,11 @@ PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 的计
经过一些简单的数学变换,不难发现 `weight_decay` 和 `lambd` 虽然看起来差异很大,但在这段代码里的作用是完全一模一样的。
-`lambd` 和 `weight_decay` 唯一不同的地方,是在学习率更新的策略里用到了 `lambd` 而没有用到 `weight_decay`。但 ASGD 作为一个优化方法并不应该和某种具体的学习率更新策略耦合。如果改由外部某个 lr scheduler 来控制 ASGD 的学习率,那么 ASGD 内的 `lambd` 和 `weight_decay` 就完全可以只留一个了。
+`lambd` 和 `weight_decay` 唯一不同的地方,是在 200-201 行学习率更新的策略里用到了 `lambd` 而没有用到 `weight_decay`。但 ASGD 作为一个优化方法并不应该和某种具体的学习率更新策略耦合。如果改由外部某个 lr scheduler 来控制 ASGD 的学习率,那么 ASGD 内的 `lambd` 和 `weight_decay` 就完全可以只留一个了。
-PyTorch 的 ASGD 还同时存在着 single_tensor 和 multi_tensor 两种实现,其它 PyTorch 优化器也是一样,和 ASGD 本身无关。
+PyTorch 的 ASGD 还同时存在着 single_tensor 和 multi_tensor 两种实现,其它 PyTorch 优化器也是一样。和 ASGD 本身无关。multi_tensor 使用了 PyTorch 的 foreach API,效率更高,但没有默认启用。
-到现在,PyTorch 的代码已经分析完成,我们也明白了 ASGD 的实现:它和普通的 SGD 可以说完全一样,只是在 `ax` 里保存了一份权重的平均值而已。因为相关作者的囫囵吞枣,它和其它优化器的实现风格格格不入,这阻碍了对它的理解。
+到现在,PyTorch 的代码已经分析完成,我们也明白了 ASGD 的实现:它和普通的 SGD 可以说完全一样,只是在 `ax` 里保存了一份权重的平均值而已。由于相关作者的囫囵吞枣,它和其它优化器的实现风格格格不入,这阻碍了对它的理解。
注意:PyTorch 和 TensorFlow 也实现了 Stochastic Weight Averaging,它和 Averaged SGD 并不是相同的概念。具体可以参考 https://pytorch.org/blog/stochastic-weight-averaging-in-pytorch。
@@ -111,7 +111,7 @@ PyTorch 的 ASGD 还同时存在着 single_tensor 和 multi_tensor 两种实现
class paddle.fluid.optimizer.ASGDOptimizer(learning_rate, parameter_list=None, regularization=None, name=None)
```
-和飞桨中其它优化器的风格保持一致。weight_decay 通过 `regularization` 参数设置,支持 L1/L2 正则。而学习率用 LR Scheduler 来控制,不内置在优化器内。并新增一个 LRScheduler 实现 [2] 中提出的学习率更新策略(具体名字可以后续决定)。
+和飞桨中其它优化器的风格保持一致。weight_decay 通过 `regularization` 参数设置,支持 L1/L2 正则。而学习率用 LR Scheduler 来控制,不内置在优化器内。并新增一个 LRScheduler 实现 [2] 中提出的学习率更新策略(具体名字可以后续决定),用户也可以通过使用其它 LRScheduler 或者 LambdaLR,自由选择其它的学习率更新策略。
From 4e987e9a31fef49be46530797d4c47353bd2bbb9 Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Tue, 29 Mar 2022 10:05:13 +0800
Subject: [PATCH 03/10] Add PyTorch issue link
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index 5177490ca..819ed3297 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -132,7 +132,7 @@ class paddle.fluid.optimizer.ASGDOptimizer(learning_rate, parameter_list=None, r
第三周:在 Code Review 中迭代。
# 八、影响面
-ASGD 对其它模块没有影响。
+ASGD 对其它模块没有影响。PyTorch ASGD 的问题已经向 PyTorch 提交 issue:https://github.com/pytorch/pytorch/issues/74884
# 名词解释
From f8a5914ce05915648f29e26a2d04ecd7ca84ff4b Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Tue, 29 Mar 2022 14:02:31 +0800
Subject: [PATCH 04/10] Refine RFC
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index 819ed3297..56206847a 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -56,7 +56,7 @@ PyTorch 的实现其实是**有问题**的,它最初版的实现参考了 [bot
param.add_(grad, alpha=-eta.item())
```
-PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 的计算平均值的经典算法,ax 在 t0 时刻之前是当前权重,在 t0 时刻之后是从 t0 时刻开始到当前为止的权重平均值。
+PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 地计算平均值的经典算法,ax 在 t0 时刻之前是当前权重,在 t0 时刻之后是从 t0 时刻开始到当前为止的权重平均值。
```python
# averaging
@@ -91,7 +91,7 @@ PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 的计
param.add_(grad, alpha=-eta.item())
```
-经过一些简单的数学变换,不难发现 `weight_decay` 和 `lambd` 虽然看起来差异很大,但在这段代码里的作用是完全一模一样的。
+经过一些简单的数学变换,不难发现在这段代码里 `weight_decay` 和 `lambd` 的用法虽然形式上差异很大,但作用是完全一模一样的。
`lambd` 和 `weight_decay` 唯一不同的地方,是在 200-201 行学习率更新的策略里用到了 `lambd` 而没有用到 `weight_decay`。但 ASGD 作为一个优化方法并不应该和某种具体的学习率更新策略耦合。如果改由外部某个 lr scheduler 来控制 ASGD 的学习率,那么 ASGD 内的 `lambd` 和 `weight_decay` 就完全可以只留一个了。
From c1588d70c2edaa32524a8781c2df86f19122c3eb Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Tue, 29 Mar 2022 14:08:37 +0800
Subject: [PATCH 05/10] Fix typo
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index 56206847a..9b266739c 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -71,14 +71,14 @@ PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 地计
-200-201 行,是更新学习率(在 PyTorch 的实现里,eta 就是学习率,而 lr 是一个常量,专指用户设置的初始学习率)的策略,这个更新策略是照搬自 bottou-sgd,bottou-sgd 参考自 [2]。和其它的优化器不一样,ASGD 优化器的学习率并不能由 lr scheduler 控制,这可能也是它是被不经消化地加入 PyTorch 的一个表现。
+200-201 行,是更新学习率(在 PyTorch 的实现里,eta 就是学习率,而 lr 是一个常量,专指用户设置的初始学习率)的策略,这个更新策略是照搬自 bottou-sgd,bottou-sgd 参考自 [2]。和其它的优化器不一样,ASGD 优化器的学习率并不能由 lr scheduler 控制,这可能也是它被不经消化地加入 PyTorch 的一个表现。
```python
new_eta = torch.tensor(lr / math.pow((1 + lambd * lr * step), alpha))
eta.copy_(new_eta)
```
-再看看 PyTorch 的实现,除了上述的几段代码之外,还有一个名叫 weight_decay 的参数,在上面的推导里我们已经了解到,其实 lambda 就是 l2 正则项的系数,也就是 weight decay,再看看相关的代码来实锤这一点:
+再看看 PyTorch 的实现,除了上述的几段代码之外,还有一个名叫 weight_decay 的参数,在上面的推导里我们已经了解到,其实 lambda 就是 l2 正则项的系数,也就是 “weight decay”,因此不应该再有另一个 weight_decay 参数了。再看看相关的代码:
```python
if weight_decay != 0:
@@ -97,12 +97,12 @@ PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 地计
PyTorch 的 ASGD 还同时存在着 single_tensor 和 multi_tensor 两种实现,其它 PyTorch 优化器也是一样。和 ASGD 本身无关。multi_tensor 使用了 PyTorch 的 foreach API,效率更高,但没有默认启用。
-到现在,PyTorch 的代码已经分析完成,我们也明白了 ASGD 的实现:它和普通的 SGD 可以说完全一样,只是在 `ax` 里保存了一份权重的平均值而已。由于相关作者的囫囵吞枣,它和其它优化器的实现风格格格不入,这阻碍了对它的理解。
+到现在,PyTorch 的代码已经分析完成,我们也明白了 ASGD 的实现:它和普通的 SGD 可以说完全一样,只是在 `ax` 里保存了一份权重的平均值而已。由于相关作者的囫囵吞枣,它和其它优化器的实现风格格格不入,这才阻碍了对它的理解。
注意:PyTorch 和 TensorFlow 也实现了 Stochastic Weight Averaging,它和 Averaged SGD 并不是相同的概念。具体可以参考 https://pytorch.org/blog/stochastic-weight-averaging-in-pytorch。
# 四、对比分析
-经过上面的分析可以发现 PyTorch 的实现是很有问题的。在飞桨里的实现可以以更加优雅和一致的方式实现。
+经过上面的分析可以发现 PyTorch 的实现是很有问题的。在飞桨里它可以以更加优雅和一致的方式实现。
# 五、设计思路与实现方案
@@ -129,7 +129,7 @@ class paddle.fluid.optimizer.ASGDOptimizer(learning_rate, parameter_list=None, r
# 七、可行性分析和排期规划
前两周:实现相关代码、测试用例和文档。
-第三周:在 Code Review 中迭代。
+第三周:Code Review 和迭代 PR。
# 八、影响面
ASGD 对其它模块没有影响。PyTorch ASGD 的问题已经向 PyTorch 提交 issue:https://github.com/pytorch/pytorch/issues/74884
From 05b40530e34d43a1698bda637be92b0fe24bc158 Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Tue, 29 Mar 2022 14:50:27 +0800
Subject: [PATCH 06/10] Refine the test
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index 9b266739c..320c9d485 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -124,7 +124,7 @@ class paddle.fluid.optimizer.ASGDOptimizer(learning_rate, parameter_list=None, r
基本可以仿照 python/paddle/optimizer/sgd.py,只是把调用的 op 从 sgd 变成 asgd,并在 outputs 中增加一个输出 “AveragedParamOut”,并提供一个 `GetAveragedParameters` 方法。
# 六、测试和验收的考量
-增加完善的测试和文档,并和 PyTorch 的结果对比一致。
+增加完善的测试和文档,本地测试和 PyTorch 的结果一致。构造基于 Paddle SGD、在 Python 中计算参数平均值的参考实现,作为 CI 对比中的 baseline。
# 七、可行性分析和排期规划
前两周:实现相关代码、测试用例和文档。
From 145d2c1b6b25cfbcd17b117a9ed5464bdae01c0e Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Wed, 30 Mar 2022 17:13:08 +0800
Subject: [PATCH 07/10] Add more content about op implementation
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index 320c9d485..f0e7e63ae 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -117,7 +117,13 @@ class paddle.fluid.optimizer.ASGDOptimizer(learning_rate, parameter_list=None, r
## 底层OP设计
-基本可以仿照飞桨 SGD 优化器的实现,实现 paddle/fluid/operators/optimizers/asgd_op.cc 和相应的 asgd_kernel.h/.cc/.cu。
+基本可以仿照飞桨 SGD 优化器的实现,实现 paddle/fluid/operators/optimizers/asgd_op.cc 和相应的 asgd_kernel.h/.cc/.cu,注册 ASGDOP 和 CPU 与 CUDA 版的 ASGDOpKernel。
+
+飞桨中 SGD 和 Adam 等常见的优化器的 CPU 版的实现是通过代码生成机制在运行时生成并加载的,而 ASGD 是较冷门的优化器,可以类似于飞桨中的 RMSProp 等优化器,通过 Eigen 库实现 CPU Kernel,通过 for_range + Functor 实现 CUDA Kernel 即可。
+
+ASGD 优化器计划暂不支持 SelectedRows 等稀疏张量和 AMP,毕竟这个优化器实在是冷门,即使是用户量多如 PyTorch,它的 ASGD 优化器可能也没有用户真的使用过。
+
+新增的 LR Scheduler 将是纯 Python 代码(和其它 LR Scheduler 相同),不涉及新增底层 OP。
## API实现方案
From 423d6a262d4b508bdeb5453175598996b9213e08 Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Thu, 31 Mar 2022 12:48:15 +0800
Subject: [PATCH 08/10] Add pseudo code
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 39 +++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index f0e7e63ae..e1abf43a9 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -121,6 +121,45 @@ class paddle.fluid.optimizer.ASGDOptimizer(learning_rate, parameter_list=None, r
飞桨中 SGD 和 Adam 等常见的优化器的 CPU 版的实现是通过代码生成机制在运行时生成并加载的,而 ASGD 是较冷门的优化器,可以类似于飞桨中的 RMSProp 等优化器,通过 Eigen 库实现 CPU Kernel,通过 for_range + Functor 实现 CUDA Kernel 即可。
+具体来讲,如上文所述,ASGD 和普通的 SGD 可以说完全一样,只是额外保存了一份权重沿时间的平均值而已。因此 CPU 版 Kernel 的伪代码如下:
+
+```c++
+ const auto *learning_rate = ctx.Input("LearningRate");
+ const auto *param = ctx.Input("Param");
+ // 相比 SGD 优化器增加一个 AveragedParam 输入,表示该权重到目前为止的平均值
+ const auto *averaged_param = ctx.Input("AveragedParam");
+ std::string regularization_method =
+ ctx.Attr("regularization_method");
+ float regularization_coeff = ctx.Attr("regularization_coeff");
+ int64_t t0 = ctx.Attr("t0");
+ auto *param_out = ctx.Output("ParamOut");
+ // 相比 SGD 优化器增加一个 AveragedParamOut 输出,表示经过本次更新之后的该权重的新平均值
+ auto *averaged_param_out = ctx.Output("AveragedParamOut");
+ const auto *grad = ctx.Input("Grad");
+
+ // 省略构造 EigenVector 的代码
+ // ...
+
+ auto &place =
+ *execution_context.template device_context().eigen_device();
+ if (regularization_method == "l2_decay") {
+ param_out.device(place) = param * (1 - learning_rate * weight_decay); // 处理 weight decay
+ } else if (regularization_method == "l1_decay") {
+ ...
+ }
+ param_out -= learning_rate * grad; // 梯度下降
+
+ // 与普通 SGD 的关键区别,维护参数平均值:
+ if (current_step() < t0) {
+ averaged_param_out.device(place) = param_out;
+ } else {
+ // 与 PyTorch 中的方法相同,更新平均值
+ averaged_param_out.device(place) = update_average(averaged_param, param_out, current_step(), t0);
+ }
+```
+
+CUDA Kernel 的实现也将是类似的。
+
ASGD 优化器计划暂不支持 SelectedRows 等稀疏张量和 AMP,毕竟这个优化器实在是冷门,即使是用户量多如 PyTorch,它的 ASGD 优化器可能也没有用户真的使用过。
新增的 LR Scheduler 将是纯 Python 代码(和其它 LR Scheduler 相同),不涉及新增底层 OP。
From 2daa584cbf3a7478c12e1f849d2640a4519f0ed7 Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Thu, 31 Mar 2022 21:58:46 +0800
Subject: [PATCH 09/10] Address reviews
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index e1abf43a9..5dfa00d64 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -97,7 +97,7 @@ PyTorch 源码中接下来的 194-198 行和 202-203 行,是 on-the-fly 地计
PyTorch 的 ASGD 还同时存在着 single_tensor 和 multi_tensor 两种实现,其它 PyTorch 优化器也是一样。和 ASGD 本身无关。multi_tensor 使用了 PyTorch 的 foreach API,效率更高,但没有默认启用。
-到现在,PyTorch 的代码已经分析完成,我们也明白了 ASGD 的实现:它和普通的 SGD 可以说完全一样,只是在 `ax` 里保存了一份权重的平均值而已。由于相关作者的囫囵吞枣,它和其它优化器的实现风格格格不入,这才阻碍了对它的理解。
+到现在,PyTorch 的代码已经分析完成,我们也明白了 ASGD 的实现:它和普通的 SGD 可以说完全一样,只是在 `ax` 里保存了一份权重的平均值而已。由于相关作者的囫囵吞枣,它和其它优化器的实现风格格格不入,这才阻碍了对它的理解。而 ASGD 并没有一定要使用某一种特定的学习率更新策略,举例来说,PyTorch ASGD 和 bottou-sgd 所用的学习率更新策略 [2] 是比 ASGD 本身 [1] 更晚提出的。这一点也可以从 [维基百科](https://en.wikipedia.org/wiki/Stochastic_gradient_descent) 和 [这个课件](https://courses.cs.washington.edu/courses/cse547/18sp/slides/sgd_averaging.pdf) 对 ASGD 的描述里证实 —— ASGD 只是记录参数的平均值而已。
注意:PyTorch 和 TensorFlow 也实现了 Stochastic Weight Averaging,它和 Averaged SGD 并不是相同的概念。具体可以参考 https://pytorch.org/blog/stochastic-weight-averaging-in-pytorch。
From a52851e4a2f5418edf0a51a388492020795d2392 Mon Sep 17 00:00:00 2001
From: tiancaishaonvjituizi <452565578@qq.com>
Date: Thu, 31 Mar 2022 22:14:02 +0800
Subject: [PATCH 10/10] Add more references
---
rfcs/APIs/20220327_api_design_for_ASGD.md | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/rfcs/APIs/20220327_api_design_for_ASGD.md b/rfcs/APIs/20220327_api_design_for_ASGD.md
index 5dfa00d64..dc22533c7 100644
--- a/rfcs/APIs/20220327_api_design_for_ASGD.md
+++ b/rfcs/APIs/20220327_api_design_for_ASGD.md
@@ -183,8 +183,14 @@ ASGD 对其它模块没有影响。PyTorch ASGD 的问题已经向 PyTorch 提
# 附件及参考资料
-[1] http://dl.acm.org/citation.cfm?id=131098
+[1] http://dl.acm.org/citation.cfm?id=131098 提出 ASGD 算法的 Paper
-[2] https://arxiv.org/abs/1107.2490
+[2] https://arxiv.org/abs/1107.2490 提出 PyTorch 和 bottou-sgd 所用的学习率更新策略的 Paper
[3] https://pytorch.org/blog/stochastic-weight-averaging-in-pytorch/
+
+[4] https://en.wikipedia.org/wiki/Stochastic_gradient_descent SGD 的维基百科,里面有介绍 Averaged SGD
+
+[5] https://courses.cs.washington.edu/courses/cse547/18sp/slides/sgd_averaging.pdf 讲述 ASGD 原理的课件
+
+[6] https://github.com/npinto/bottou-sgd/blob/master/README.txt bottou-sgd README