Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Oneflow-Inc
oneflow-documentation
比较版本
abd38c77d75fd186107d99bd75fe3cb595b2c195...2618ad66e0fcf121319b54e709c5c32e2fa868d6
O
oneflow-documentation
项目概览
Oneflow-Inc
/
oneflow-documentation
上一次同步 接近 3 年
通知
0
Star
47
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
O
oneflow-documentation
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
源分支
2618ad66e0fcf121319b54e709c5c32e2fa868d6
选择Git版本
...
目标分支
abd38c77d75fd186107d99bd75fe3cb595b2c195
选择Git版本
比较
Commits (3)
https://gitcode.net/Oneflow-Inc/oneflow-documentation/-/commit/c68443d27e792f0b7c27344ee4f2fe1770e1a9a2
autograd 100%(excpet imgs), optimizer 0%
2021-08-15T12:40:52+08:00
YaoChi
later@usopp.net
https://gitcode.net/Oneflow-Inc/oneflow-documentation/-/commit/0af00f67703c07abe67f4153065a35c03df83b8e
refine optimization 100%
2021-08-15T17:23:32+08:00
YaoChi
later@usopp.net
https://gitcode.net/Oneflow-Inc/oneflow-documentation/-/commit/2618ad66e0fcf121319b54e709c5c32e2fa868d6
rm useless py script
2021-08-15T17:28:46+08:00
YaoChi
later@usopp.net
隐藏空白更改
内联
并排
Showing
6 changed file
with
276 addition
and
203 deletion
+276
-203
cn/docs/basics/05_autograd.md
cn/docs/basics/05_autograd.md
+200
-51
cn/docs/basics/06_optimization.md
cn/docs/basics/06_optimization.md
+76
-57
cn/docs/code/basics_topics/autograd_demo.py
cn/docs/code/basics_topics/autograd_demo.py
+0
-19
cn/docs/code/basics_topics/manual_autograd.py
cn/docs/code/basics_topics/manual_autograd.py
+0
-31
cn/docs/code/basics_topics/optim_autograd.py
cn/docs/code/basics_topics/optim_autograd.py
+0
-33
cn/docs/code/basics_topics/retain_demo.py
cn/docs/code/basics_topics/retain_demo.py
+0
-12
未找到文件。
cn/docs/basics/05_autograd.md
浏览文件 @
2618ad66
# 自动求
导
# 自动求
梯度
神经网络的训练过程离不开
**反向传播算法**
,在反向传播过程中,需要获取 loss 函数对模型参数的梯度,用于更新参数。
神经网络的训练过程离不开
**反向传播算法**
,在反向传播过程中,需要获取 loss 函数对模型参数的梯度,用于更新参数。
...
@@ -34,7 +34,9 @@ l = loss(z,y)
...
@@ -34,7 +34,9 @@ l = loss(z,y)
反向传播过程中,需要求得
`l`
对
`w`
、
`b`
的梯度,以更新这两个模型参数。因此,我们在创建它们时,设置
`requires_grad`
为
`True`
。
反向传播过程中,需要求得
`l`
对
`w`
、
`b`
的梯度,以更新这两个模型参数。因此,我们在创建它们时,设置
`requires_grad`
为
`True`
。
## 自动求导
## 自动求梯度
### backward 与梯度
在反向传播的过程中,需要得到
`l`
分别对
`w`
、
`b`
的梯度 $
\f
rac{
\p
artial l}{
\p
artial w}$ 和 $
\f
rac{
\p
artial l}{
\p
artial b}$。我们只需要对
`l`
调用
`backward()`
方法,然后 OneFlow 就会自动计算梯度,并且存放到
`w`
与
`b`
的
`grad`
成员中。
在反向传播的过程中,需要得到
`l`
分别对
`w`
、
`b`
的梯度 $
\f
rac{
\p
artial l}{
\p
artial w}$ 和 $
\f
rac{
\p
artial l}{
\p
artial b}$。我们只需要对
`l`
调用
`backward()`
方法,然后 OneFlow 就会自动计算梯度,并且存放到
`w`
与
`b`
的
`grad`
成员中。
...
@@ -53,88 +55,235 @@ tensor([[0.9397, 2.5428, 2.5377],
...
@@ -53,88 +55,235 @@ tensor([[0.9397, 2.5428, 2.5377],
tensor([[0.9397, 2.5428, 2.5377]], dtype=oneflow.float32)
tensor([[0.9397, 2.5428, 2.5377]], dtype=oneflow.float32)
```
```
### 停止对某个 Tensor 求梯度
### 对非叶子节点求梯度
默认情况下,只有
`requires_grad=True`
的叶子节点的梯度会被保留。非叶子节点的
`grad`
属性默认在
`backward`
执行过程中,会自动释放,不能查看。
### 对一个计算图多次 `backward()`
如果想保留并查看非叶子节点的梯度,可以调用
`Tensor.retain_grad`
方法:
### 对非叶子节点求梯度
```
python
from
math
import
pi
n1
=
flow
.
tensor
(
pi
/
2
,
requires_grad
=
True
)
n2
=
flow
.
sin
(
n1
)
n2
.
retain_grad
()
n3
=
flow
.
pow
(
n2
,
2
)
n3
.
backward
()
print
(
n1
.
grad
)
print
(
n2
.
grad
)
```
以上代码,既求 $
\f
rac{
\p
artial n_3}{
\p
artial n_1}$,也求 $
\f
rac{
\p
artial n_3}{
\p
artial n_2}$
输出:
## 输出为 Tensor 时如何求导
```
-
scalar implicity only
tensor(-8.7423e-08, dtype=oneflow.float32)
tensor(2., dtype=oneflow.float32)
```
### 对一个计算图多次 `backward()`
默认情况下,对于给定的计算图,只能调用
`backward()`
一次。比如,以下代码会报错:
```
python
n1
=
flow
.
tensor
(
10.
,
requires_grad
=
True
)
n2
=
flow
.
pow
(
n1
,
2
)
n2
.
backward
()
n2
.
backward
()
```
报错信息:
### 为什么默认保留叶子节点的导数
> Maybe you try to backward through the node a second time. Specify retain_graph=True when calling .backward() or autograd.grad() the first time.
在
**2.1 快速上手**
中,我们的训练循环结尾会有一步
`.zero_grad()`
的过程。这是因为,所有叶子节点的导数会被保留。每次在我们进行
`.backward()`
操作后,梯度需要被保留以用于更新参数 (也就是后文中的
`optimizer.step()`
)
。
如果想要在同一个计算图上调用多次
`backward()`
,需要在调用时设置
`retain_graph=True`
。
## `grad` 成员及相关操作
```
python
n1
=
flow
.
tensor
(
10.
,
requires_grad
=
True
)
n2
=
flow
.
pow
(
n1
,
2
)
### detach
n2
.
backward
(
retain_graph
=
True
)
print
(
n1
.
grad
)
n2
.
backward
()
print
(
n1
.
grad
)
```
detach 可以让 oneflow 停止对
`requires_grad=True`
的元素进行求导跟踪。例如:
输出:
```
text
tensor(20., dtype=oneflow.float32)
tensor(40., dtype=oneflow.float32)
```
以上输出可知,OneFlow 会
**累加**
多次
`backward()`
计算得到的梯度。
如果想清空梯度,可以调用
`zeros_`
方法:
```
python
```
python
# 建立一个简单的张量,并做简单的变换
n1
=
flow
.
tensor
(
10.
,
requires_grad
=
True
)
x
=
flow
.
tensor
([
1.0
,
2.0
,
3.0
],
requires_grad
=
True
)
n2
=
flow
.
pow
(
n1
,
2
)
print
(
x
)
y
=
x
*
x
n2
.
backward
(
retain_graph
=
True
)
z
=
x
**
3
print
(
n1
.
grad
)
a
=
y
+
z
n1
.
grad
.
zeros_
()
# 用 MSE 来计算 x 与 a 的差距
n2
.
backward
()
loss
=
flow
.
nn
.
MSELoss
()
print
(
n1
.
grad
)
out
=
loss
(
x
,
a
)
print
(
out
)
# 反向传播,计算导数
out
.
backward
()
print
(
x
.
grad
)
```
```
其输出为
输出:
```
shell
```
text
tensor
([
1., 2., 3.],
dtype
=
oneflow.float32,
requires_grad
=
True
)
tensor(20., dtype=oneflow.float32)
tensor
(
396.6667,
dtype
=
oneflow.float32,
grad_fn
=
<scalar_mul_backward>
)
tensor(20., dtype=oneflow.float32)
tensor
([
2.6667, 100. , 704.
]
,
dtype
=
oneflow.float32
)
```
```
但若我们将
`z = x**3`
替换成
`z = x.detach()**3`
, 输出会变为:
### 停止对某个 Tensor 求梯度
默认情况下,OneFlow 会 tracing
`requires_grad`
为
`True`
的 Tensor,自动求梯度。
不过有些情况可能并不需要 OneFlow 这样做,比如只是想试一试前向推理。那么可以使用
[
oneflow.no_grad
](
https://oneflow.readthedocs.io/en/master/oneflow.html#oneflow.no_grad
)
或
[
oneflow.Tensor.detach
](
https://oneflow.readthedocs.io/en/master/tensor.html#oneflow.Tensor.detach
)
方法设置。
```
shell
```
python
tensor
([
1., 2., 3.],
dtype
=
oneflow.float32,
requires_grad
=
True
)
z
=
flow
.
matmul
(
x
,
w
)
+
b
tensor
(
396.6667,
dtype
=
oneflow.float32,
grad_fn
=
<scalar_mul_backward>
)
print
(
z
.
requires_grad
)
tensor
([
0.6667, 20. , 110.
]
,
dtype
=
oneflow.float32
)
with
flow
.
no_grad
():
z
=
flow
.
matmul
(
x
,
w
)
+
b
print
(
z
.
requires_grad
)
```
输出:
```
text
True
False
```
```
原因很简单,加入 detach 操作后,oneflow 就不会在反向过程中对 z 所参与的节点求导,从而导致
`x.grad`
值的变化。
```
python
z_det
=
z
.
detach
()
print
(
z_det
.
requires_grad
)
```
在实际运用中,假设有两个模型,第一个模型的输出为第二个模型的输入。若你只想训练第二个模型,那么只需在第一个模型后面加入 detach 操作就可以达到目的。
输出:
### retain_graph
```
text
False
```
上面讲到,非叶子节点的梯度会在更新完被释放。但如果我们想查看被释放的梯度呢?只需要在 autograd 函数中加上
`requires_grad=True`
即可
## 输出不是标量时如何求导
通常,调用
`backward()`
方法的 Tensor 是神经网络的 loss,是一个标量。
比如:
但是,如果调用是张量,直接
`backward()`
时会报错。
```
python
```
python
import
oneflow
as
flow
x
=
flow
.
randn
(
1
,
2
,
requires_grad
=
True
)
y
=
3
*
x
+
1
y
.
backward
()
```
x
=
flow
.
tensor
(
2.
,
requires_grad
=
True
)
报错信息:
y
=
flow
.
tensor
(
3.
,
requires_grad
=
True
)
z
=
x
*
y
x_grad
=
flow
.
autograd
.
grad
(
z
,
x
,
retain_graph
=
True
)
> Check failed: IsScalarTensor(*outputs.at(i)) Grad can be implicitly created only for scalar outputs
y_grad
=
flow
.
autograd
.
grad
(
z
,
y
)
print
(
x_grad
[
0
],
y_grad
[
0
])
而对
`y`
求
`sum`
后可以求梯度:
```
python
x
=
flow
.
randn
(
1
,
2
,
requires_grad
=
True
)
y
=
3
*
x
+
1
y
=
y
.
sum
()
y
.
backward
()
print
(
x
.
grad
)
```
```
输出:
输出:
```
shell
```
text
tensor
(
3.,
dtype
=
oneflow.float32
)
tensor
(
2.
,
dtype
=
oneflow.float32
)
tensor(
[[3., 3.]]
, dtype=oneflow.float32)
```
```
若没有加入
`requires_grad=True`
, oneflow 会默认报错,因为梯度已被释放。
错误原因及解决方法的分析如下:
`x`
张量中有两个元素,记作 $x_1$ 与 $x_2$,
`y`
张量中的两个元素记作 $y_1$ 与 $y_2$,那么两者的关系是:
$$
\m
athbf{x} = [x_1, x_2]
$$
$$
\m
athbf{y} = [y_1, y_2] = [3x_1+1, 3x_2+1]
$$
此时,想直接求 $
\f
rac{
\p
artial
\m
athbf{y}}{
\p
artial
\m
athbf{x}}$
$$
\f
rac{
\p
artial
\m
athbf{y}}{
\p
artial
\m
athbf{x}} =
\f
rac{[3x_1+1, 3x_2+1]}{[x_1, x_2]}
$$
在数学上是没有意义的,因此当然就报错了。
实际上,当用户调用
`y.backward()`
时,其实想要的结果通常是:
$$
[
\f
rac{
\p
artial y_1}{
\p
artial x_1},
\f
rac{
\p
artial y_2}{
\p
artial x_2}]
$$
当对
`y`
进行
`sum`
运算后:
$$
y = y_1 + y_2 = 3x_1 + 3x_2 + 2
$$
此时,调用
`backward()`
时,对 $x_1$ 和 $x_2$ 可求梯度:
$$
\f
rac{
\p
artial y}{
\p
artial x_1} =
\f
rac{
\p
artial 3x_1 + 3x_2 + 2}{
\p
artial x_1} = 3
$$
$$
\f
rac{
\p
artial y}{
\p
artial x_2} =
\f
rac{
\p
artial 3x_1 + 3x_2 + 2}{
\p
artial x_2} = 3
$$
### 扩展阅读:VJP
还可以使用更通用方法,即
**Vector Jacobian Product(VJP)**
完成非标量的根的梯度计算。依然用上文的例子,在反向传播过程中,OneFlow 会根据计算图生成雅可比矩阵:
$$
J =
\b
egin{pmatrix}
\f
rac{
\p
artial y_1}{
\p
artial x_1} &
\f
rac{
\p
artial y_1}{
\p
artial x_2}
\\
\f
rac{
\p
artial y_2}{
\p
artial x_1} &
\f
rac{
\p
artial y_2}{
\p
artial x_2}
\e
nd{pmatrix}
\\
=
\b
egin{pmatrix}
\f
rac{
\p
artial y_1}{
\p
artial x_1} & 0
\\
0 &
\f
rac{
\p
artial y_2}{
\p
artial x_2}
\e
nd{pmatrix}
$$
只需提供一个与 $
\m
athbf{y}$ 大小一致的向量 $
\m
athbf{v}$,即可计算 VJP:
$$
\b
egin{bmatrix}
v_1
\\
v_2
\e
nd{bmatrix}
\t
imes
\b
egin{pmatrix}
\f
rac{
\p
artial y_1}{
\p
artial x_1} & 0
\\
0 &
\f
rac{
\p
artial y_2}{
\p
artial x_2}
\e
nd{pmatrix}=
\b
egin{bmatrix}
v_1
\f
rac{
\p
artial y_1}{
\p
artial x_1}
\\
v_2
\f
rac{
\p
artial y_2}{
\p
artial x_2}
\e
nd{bmatrix}
$$
若向量 $
\m
athbf{v}$ 是反向传播中上一层的梯度,VJP 的结果刚好是当前层要求的梯度。
`backward`
方法是可以接受一个张量做参数的,该参数就是 VJP 中的 $
\m
athbf{v}$,理解以上道理后,还可以使用以下的方式对张量求梯度:
```
python
x
=
flow
.
randn
(
1
,
2
,
requires_grad
=
True
)
y
=
3
*
x
+
1
y
.
backward
(
flow
.
ones_like
(
y
))
print
(
x
.
grad
)
```
输出:
```
text
tensor([[3., 3.]], dtype=oneflow.float32)
```
\ No newline at end of file
cn/docs/basics/06_optimization.md
浏览文件 @
2618ad66
# 反向传播
# 反向传播
与 optimizer
再上一章节中,我们讲到了求导对于模型训练的重要性。然而,在求导后,我们还需要让机器根据求出的导数来更新梯度。而这一过程,就被称之为反向传播
。
到目前为止,我们已经掌握如何使用 OneFlow
[
搭建模型
](
todo_build_model.md
)
、
[
加载数据
](
todo_dataset_dataloader.md
)
、
[
自动计算模型参数的梯度
](
./05_autograd.md
)
,将它们组合在一起,我们就可以利用反向传播算法训练模型
。
## 利用自动求导手工实现反向传播
在
[
oneflow.optim
](
https://oneflow.readthedocs.io/en/master/optim.html
)
中,有各类
`optimizer`
,它们可以简化实现反向传播的代码。
为了更方便理解自动求导的作用,我们在这里提供了一份用 numpy 纯手写的简单模型:
本文将先介绍反向传播的基本概念,再介绍如何使用
`oneflow.optimz`
类。
## numpy 手工实现反向传播
为了读者更方便理解反向传播与自动求导的关系,在这里提供了一份仅用 numpy 实现的简单模型的训练过程:
```
python
```
python
import
numpy
as
np
import
numpy
as
np
...
@@ -16,16 +20,16 @@ LR = 0.01
...
@@ -16,16 +20,16 @@ LR = 0.01
def
forward
(
x
,
w
):
def
forward
(
x
,
w
):
return
np
.
matmul
(
x
,
w
)
return
np
.
matmul
(
x
,
w
)
# 损失函数
(return MSE 的导数)
# 损失函数
def
loss
(
y_pred
,
y
):
def
loss
(
y_pred
,
y
):
return
(
0.5
*
(
y_pred
-
y
)
**
2
).
sum
()
return
(
0.5
*
(
y_pred
-
y
)
**
2
).
sum
()
# 计算
导数
# 计算
梯度
def
gradient
(
x
,
y
,
y_pred
):
def
gradient
(
x
,
y
,
y_pred
):
return
np
.
matmul
(
x
.
T
,
(
y_pred
-
y
))
return
np
.
matmul
(
x
.
T
,
(
y_pred
-
y
))
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
# 训练目
的
: Y = 2*X1 + 3*X2
# 训练目
标
: Y = 2*X1 + 3*X2
x
=
np
.
array
([[
1
,
2
],
[
2
,
3
],
[
4
,
6
],
[
3
,
1
]],
dtype
=
np
.
float32
)
x
=
np
.
array
([[
1
,
2
],
[
2
,
3
],
[
4
,
6
],
[
3
,
1
]],
dtype
=
np
.
float32
)
y
=
np
.
array
([[
8
],
[
13
],
[
26
],
[
9
]],
dtype
=
np
.
float32
)
y
=
np
.
array
([[
8
],
[
13
],
[
26
],
[
9
]],
dtype
=
np
.
float32
)
...
@@ -44,7 +48,7 @@ if __name__ == "__main__":
...
@@ -44,7 +48,7 @@ if __name__ == "__main__":
输出:
输出:
```
shell
```
text
50/500 loss:0.0012162785263114685
50/500 loss:0.0012162785263114685
100/500 loss:3.11160142374838e-05
100/500 loss:3.11160142374838e-05
150/500 loss:7.960399867959713e-07
150/500 loss:7.960399867959713e-07
...
@@ -55,96 +59,111 @@ if __name__ == "__main__":
...
@@ -55,96 +59,111 @@ if __name__ == "__main__":
400/500 loss:8.723474589862032e-15
400/500 loss:8.723474589862032e-15
450/500 loss:2.231723694177745e-16
450/500 loss:2.231723694177745e-16
500/500 loss:5.7094113647001346e-18
500/500 loss:5.7094113647001346e-18
w:[[2.]
w:[[2.
00000001
]
[
3.
]]
[
2.99999999
]]
```
```
可以看到,以上代码的主要目的为训练模型去寻找公式中2,3两个参数 (
`Y = 2*X1 + 3*X2`
)。具体训练过程及步骤在
**2.1 快速上手**
中已有详细介绍。
注意我们选择的 loss 函数表达式为 $
\s
um
\f
rac{1}{2}(y_{p} - y)^2$,因此
`loss`
对参数
`w`
求梯度的代码为:
在
**2.1 快速上手**
的反向传播时,我们与用了一个简单的
`.backward`
就解决了更新导数的问题。但为了更好的诠释
`.backward`
的过程,我们这里手写了反向传播。
`loss() `
的返回结果
`(0.5*(y_pred-y)**2).sum()`
是我们为了方便自己定义的损失函数,且其导数显而易见: 就是
`y_pred-y`
。也就是为什么我们在计算导数 (gradient) 时:
```
python
```
python
def
gradient
(
x
,
y
,
y_pred
):
def
gradient
(
x
,
y
,
y_pred
):
return
np
.
matmul
(
x
.
T
,
(
y_pred
-
y
))
return
np
.
matmul
(
x
.
T
,
(
y_pred
-
y
))
```
```
是将 x 的 transpose 与
`(y_pred-y)`
做矩阵乘法。
更新参数采用的是
[
SGD
](
https://en.wikipedia.org/wiki/Stochastic_gradient_descent
)
:
而更新权重就比较简单了:
```
python
```
python
grad
=
gradient
(
x
,
y
,
y_pred
)
grad
=
gradient
(
x
,
y
,
y_pred
)
w
-=
LR
*
grad
w
-=
LR
*
grad
```
```
可以看到,所谓更新权重,就是简单的导数乘以学习率。
总结而言,训练中的一次完整迭代包括以下步骤:
1.
模型根据输入、参数,计算得出预测值 (
`y_pred`
)
2.
计算 loss,即预测值与标签之间的误差
3.
求 loss 对参数的梯度
4.
更新参数
简单来说,一次完整的反向传播就是: 计算损失函数与叶子结点 (这里 x 为叶子结点) 的导数 -> 通过导数乘以学习率来更新参数
其中 1~2 为正向传播过程;3~4为反向传播过程。
当然,更复杂的模型并不只有这简单的两步,但万变不离其宗。
## 超参 Hyperparameters
## 利用 `flow.optim` 中已有的类进行反向传播
超参数是有关模型训练设置的参数,可以影响到模型训练的效率和结果。如以上代码中的
`ITER_COUNT`
、
`LR`
就是超参数。
上面手写的模型似乎很麻烦。我们不但要对其导数公式,还需要手写更新过程。在训练稍稍复杂一点的模型的话,工作量会大大提高 (激活函数等等都需要手写)。下面是我们用 oneflow 写出的训练
`Y = 2*X1 + 3*X2`
的模型。
## 使用 `oneflow.optim` 中的优化器类
使用
`oneflow.optim`
中的优化器类进行反向传播会更简洁方便,接下来,我们展示如何使用。
首先,先准备好数据和模型,使用 Module 的一个方便之处就是,可以把超参放置在 Module 中便于管理。
```
python
```
python
import
oneflow
as
flow
import
oneflow
as
flow
x
=
flow
.
tensor
([[
1
,
2
],
[
2
,
3
],
[
4
,
6
],
[
3
,
1
]],
dtype
=
flow
.
float32
)
y
=
flow
.
tensor
([[
8
],
[
13
],
[
26
],
[
9
]],
dtype
=
flow
.
float32
)
class
MyLrModule
(
flow
.
nn
.
Module
):
class
MyLrModule
(
flow
.
nn
.
Module
):
def
__init__
(
self
,
lr
,
iter_count
):
def
__init__
(
self
,
lr
,
iter_count
):
super
().
__init__
()
super
().
__init__
()
self
.
w
=
flow
.
nn
.
Parameter
(
flow
.
tensor
([[
1
],
[
1
]],
dtype
=
flow
.
float32
))
self
.
w
=
flow
.
nn
.
Parameter
(
flow
.
randn
(
2
,
1
,
dtype
=
flow
.
float32
))
self
.
lr
=
lr
self
.
lr
=
lr
self
.
iter_count
=
iter_count
self
.
iter_count
=
iter_count
def
forward
(
self
,
x
):
def
forward
(
self
,
x
):
return
flow
.
matmul
(
x
,
self
.
w
)
return
flow
.
matmul
(
x
,
self
.
w
)
if
__name__
==
"__main__"
:
# train data: Y = 2*X1 + 3*X2
x
=
flow
.
tensor
([[
1
,
2
],
[
2
,
3
],
[
4
,
6
],
[
3
,
1
]],
dtype
=
flow
.
float32
)
y
=
flow
.
tensor
([[
8
],
[
13
],
[
26
],
[
9
]],
dtype
=
flow
.
float32
)
model
=
MyLrModule
(
0.01
,
500
)
model
=
MyLrModule
(
0.01
,
500
)
loss
=
flow
.
nn
.
MSELoss
(
reduction
=
'sum'
)
```
optimizer
=
flow
.
optim
.
SGD
(
model
.
parameters
(),
model
.
lr
)
for
i
in
range
(
0
,
model
.
iter_count
):
### loss 函数
y_pred
=
model
(
x
)
l
=
loss
(
y_pred
,
y
)
if
(
i
+
1
)
%
50
==
0
:
print
(
f
"
{
i
+
1
}
/
{
model
.
iter_count
}
loss:
{
l
}
"
)
l
.
backward
()
然后,选择好 loss 函数,OneFlow 自带了多种 loss 函数,我们在这里选择
[
MSELoss
](
https://oneflow.readthedocs.io/en/master/nn.html?highlight=mseloss#oneflow.nn.MSELoss
)
:
optimizer
.
step
()
optimizer
.
zero_grad
()
print
(
f
"w:
{
model
.
w
}
"
)
```
python
loss
=
flow
.
nn
.
MSELoss
(
reduction
=
'sum'
)
```
```
### 构造 optimizer
上文总结的训练中一次迭代里,反向传播的逻辑,都被封装在 optimizer 中。我们在此选择
[
SGD
](
https://oneflow.readthedocs.io/en/master/optim.html?highlight=sgd#oneflow.optim.SGD
)
优化器,你可以根据需要选择其它的优化器,如
[
Adam
](
https://oneflow.readthedocs.io/en/master/optim.html?highlight=adam#oneflow.optim.Adam
)
、
[
AdamW
](
https://oneflow.readthedocs.io/en/master/optim.html?highlight=adamw#oneflow.optim.AdamW
)
等。
```
shell
```
python
50/500 loss:tensor
(
0.0004,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
optimizer
=
flow
.
optim
.
SGD
(
model
.
parameters
(),
model
.
lr
)
100/500 loss:tensor
(
2.2268e-07,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
150/500 loss:tensor
(
1.3461e-10,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
200/500 loss:tensor
(
3.8654e-12,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
250/500 loss:tensor
(
3.8654e-12,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
300/500 loss:tensor
(
3.8654e-12,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
350/500 loss:tensor
(
3.8654e-12,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
400/500 loss:tensor
(
3.8654e-12,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
450/500 loss:tensor
(
3.8654e-12,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
500/500 loss:tensor
(
3.8654e-12,
dtype
=
oneflow.float32,
grad_fn
=
<reduce_sum_backward>
)
w: tensor
([[
2.],
[
3.]],
dtype
=
oneflow.float32,
grad_fn
=
<accumulate_grad>
)
```
```
可以看到,我们不需要再手写损失函数以及其导数。oneflow.nn 中含有大量的损失函数供用户使用。其次,在实现反向传播时,用户只需在训练循环最后加入:
构造时
`optimizer`
,将模型参数及 learning rate 传递给
`SGD`
,在之后若调用
`optimizer.step()`
,在其内部就会自动完成对模型参数求梯度、并按照 SGD 算法更新模型参数。
### 训练
以上准备完成后,可以开始训练:
```
python
```
python
l
.
backward
()
for
i
in
range
(
0
,
model
.
iter_count
):
optimizer
.
step
()
# 更新权重
y_pred
=
model
(
x
)
optimizer
.
zero_grad
()
# 清除导数
l
=
loss
(
y_pred
,
y
)
```
if
(
i
+
1
)
%
50
==
0
:
print
(
f
"
{
i
+
1
}
/
{
model
.
iter_count
}
loss:
{
l
}
"
)
即可。
optimizer
.
zero_grad
()
l
.
backward
()
optimizer
.
step
()
最后清除导数的原因也很简单(详见
**自动求导**
-
**为什么保留叶子结点导数**
)。
`backward()`
过后会保留导数以进行权重更新。但这个导数在每次迭代中会重新计算 (因为权重的更新会导致损失函数值的变化,每变化一次就要对新的损失函数值进行求导,与上一次迭代中的导数没有叠加关系),顾需要清除导数以方便下一次迭代的运算。
print
(
f
"
\n
w:
{
model
.
w
}
"
)
```
输出:
```
text
50/500 loss:0.0015626397216692567
100/500 loss:8.896231520338915e-07
150/500 loss:5.038600647822022e-10
200/500 loss:9.094947017729282e-13
250/500 loss:9.094947017729282e-13
300/500 loss:9.094947017729282e-13
350/500 loss:9.094947017729282e-13
400/500 loss:9.094947017729282e-13
450/500 loss:9.094947017729282e-13
500/500 loss:9.094947017729282e-13
w: tensor([[2.0000],
[3.0000]], dtype=oneflow.float32, grad_fn=<accumulate_grad>)
```
cn/docs/code/basics_topics/autograd_demo.py
已删除
100644 → 0
浏览文件 @
abd38c77
import
oneflow
as
flow
import
numpy
as
np
# 建立一个简单的张量,并做简单的变换
x
=
flow
.
tensor
([
1.0
,
2.0
,
3.0
],
requires_grad
=
True
)
print
(
x
)
y
=
x
*
x
z
=
x
**
3
a
=
y
+
z
# 用 MSE 来计算 x 与 y 的差距
loss
=
flow
.
nn
.
MSELoss
()
out
=
loss
(
x
,
a
)
print
(
out
)
# 反向传播,计算导数
out
.
backward
()
print
(
x
.
grad
)
cn/docs/code/basics_topics/manual_autograd.py
已删除
100644 → 0
浏览文件 @
abd38c77
import
numpy
as
np
ITER_COUNT
=
500
LR
=
0.01
def
forward
(
x
,
w
):
return
np
.
matmul
(
x
,
w
)
def
loss
(
y_pred
,
y
):
return
(
0.5
*
(
y_pred
-
y
)
**
2
).
sum
()
def
gradient
(
x
,
y
,
y_pred
):
return
np
.
matmul
(
x
.
T
,
(
y_pred
-
y
))
if
__name__
==
"__main__"
:
# train data: Y = 2*X1 + 3*X2
x
=
np
.
array
([[
1
,
2
],
[
2
,
3
],
[
4
,
6
],
[
3
,
1
]],
dtype
=
np
.
float32
)
y
=
np
.
array
([[
8
],
[
13
],
[
26
],
[
9
]],
dtype
=
np
.
float32
)
w
=
np
.
random
.
rand
(
2
,
1
)
for
i
in
range
(
0
,
ITER_COUNT
):
y_pred
=
forward
(
x
,
w
)
l
=
loss
(
y_pred
,
y
)
if
(
i
+
1
)
%
50
==
0
:
print
(
f
"
{
i
+
1
}
/
{
500
}
loss:
{
l
}
"
)
grad
=
gradient
(
x
,
y
,
y_pred
)
w
-=
LR
*
grad
print
(
f
"w:
{
w
}
"
)
cn/docs/code/basics_topics/optim_autograd.py
已删除
100644 → 0
浏览文件 @
abd38c77
import
oneflow
as
flow
class
MyLrModule
(
flow
.
nn
.
Module
):
def
__init__
(
self
,
lr
,
iter_count
):
super
().
__init__
()
self
.
w
=
flow
.
nn
.
Parameter
(
flow
.
tensor
([[
1
],
[
1
]],
dtype
=
flow
.
float32
))
self
.
lr
=
lr
self
.
iter_count
=
iter_count
def
forward
(
self
,
x
):
return
flow
.
matmul
(
x
,
self
.
w
)
if
__name__
==
"__main__"
:
# train data: Y = 2*X1 + 3*X2
x
=
flow
.
tensor
([[
1
,
2
],
[
2
,
3
],
[
4
,
6
],
[
3
,
1
]],
dtype
=
flow
.
float32
)
y
=
flow
.
tensor
([[
8
],
[
13
],
[
26
],
[
9
]],
dtype
=
flow
.
float32
)
model
=
MyLrModule
(
0.01
,
500
)
loss
=
flow
.
nn
.
MSELoss
(
reduction
=
'sum'
)
optimizer
=
flow
.
optim
.
SGD
(
model
.
parameters
(),
model
.
lr
)
for
i
in
range
(
0
,
model
.
iter_count
):
y_pred
=
model
(
x
)
l
=
loss
(
y_pred
,
y
)
if
(
i
+
1
)
%
50
==
0
:
print
(
f
"
{
i
+
1
}
/
{
model
.
iter_count
}
loss:
{
l
}
"
)
l
.
backward
()
optimizer
.
step
()
optimizer
.
zero_grad
()
print
(
f
"w:
{
model
.
w
}
"
)
cn/docs/code/basics_topics/retain_demo.py
已删除
100644 → 0
浏览文件 @
abd38c77
import
oneflow
as
flow
x
=
flow
.
tensor
(
2.
,
requires_grad
=
True
)
y
=
flow
.
tensor
(
3.
,
requires_grad
=
True
)
z
=
x
*
y
x_grad
=
flow
.
autograd
.
grad
(
z
,
x
,
retain_graph
=
True
)
y_grad
=
flow
.
autograd
.
grad
(
z
,
y
)
print
(
x_grad
[
0
],
y_grad
[
0
])