Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 additions & 1 deletion docs/api/paddle/jit/save_cn.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,96 @@ save
代码示例
:::::::::

COPY-FROM: paddle.jit.save
```python
# example 1: save layer
import numpy as np
import paddle
import paddle.nn as nn
import paddle.optimizer as opt

BATCH_SIZE = 16
BATCH_NUM = 4
EPOCH_NUM = 4

IMAGE_SIZE = 784
CLASS_NUM = 10

# define a random dataset
class RandomDataset(paddle.io.Dataset):
def __init__(self, num_samples):
self.num_samples = num_samples

def __getitem__(self, idx):
image = np.random.random([IMAGE_SIZE]).astype('float32')
label = np.random.randint(0, CLASS_NUM, (1, )).astype('int64')
return image, label

def __len__(self):
return self.num_samples

class LinearNet(nn.Layer):
def __init__(self):
super(LinearNet, self).__init__()
self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)

@paddle.jit.to_static
def forward(self, x):
return self._linear(x)

def train(layer, loader, loss_fn, opt):
for epoch_id in range(EPOCH_NUM):
for batch_id, (images, labels) in enumerate(loader()):
out = layer(images)
loss = loss_fn(out, labels)
loss.backward()
opt.step()
opt.clear_grad()
print("Epoch {} batch {}: loss = {}".format(
epoch_id, batch_id, np.mean(loss.numpy())))

# 1. train & save model.

# create network
layer = LinearNet()
loss_fn = nn.CrossEntropyLoss()
adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())

# create data loader
dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
loader = paddle.io.DataLoader(dataset,
batch_size=BATCH_SIZE,
shuffle=True,
drop_last=True,
num_workers=2)

# train
train(layer, loader, loss_fn, adam)

# save
path = "example_model/linear"
paddle.jit.save(layer, path)
```

```python
# example 2: save function
import paddle
from paddle.static import InputSpec


def save_function():
@paddle.jit.to_static
def fun(inputs):
return paddle.tanh(inputs)

path = 'test_jit_save_load_function/func'
inps = paddle.rand([3, 6])
origin = fun(inps)

paddle.jit.save(fun, path)
load_func = paddle.jit.load(path)

load_result = load_func(inps)
print((load_result - origin).abs().max() < 1e-10)

save_function()
```
9 changes: 7 additions & 2 deletions docs/guides/jit/basic_usage_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,11 @@ class SimpleNet(Layer):
若被装饰函数的参数列表除了 Tensor 类型,还包含其他如 Int、 String 等非 Tensor 类型时,推荐在函数中使用 kwargs 形式定义非 Tensor 参数,如下述样例中的 ``use_act`` 参数。

```python
import paddle
from paddle.jit import to_static
from paddle.nn import Layer
from paddle.static import InputSpec

class SimpleNet(Layer):
def __init__(self, ):
super(SimpleNet, self).__init__()
Expand All @@ -896,11 +901,11 @@ class SimpleNet(Layer):

net = SimpleNet()
# 方式一:save inference model with use_act=False
net = to_static(input_spec=[InputSpec(shape=[None, 10], name='x')])
net = to_static(net, input_spec=[InputSpec(shape=[None, 10], name='x')])
paddle.jit.save(net, path='./simple_net')

# 方式二:save inference model with use_act=True
net = to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), True])
net = to_static(net, input_spec=[InputSpec(shape=[None, 10], name='x'), True])
paddle.jit.save(net, path='./simple_net')
```

Expand Down
40 changes: 22 additions & 18 deletions docs/guides/jit/case_analysis_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@
def forward(self, x):
out = self.linear(x) # [bs, 3]

# 以下将 tensor 转为了 numpy 进行一系列操作
# 以下将 tensor 转为了 numpy array 进行一系列操作
x_data = x.numpy().astype('float32') # [bs, 10]
weight = np.random.randn([10,3])
mask = paddle.to_tensor(x_data * weight) # 此处又转回了 Tensor
weight = np.random.randn(10, 3)
mask = paddle.to_tensor(x_data @ weight) # 此处又转回了 Tensor

out = out * mask
return out
Expand All @@ -113,7 +113,7 @@ def forward(self, x):
out = self.linear(x) # [bs, 3]

weight = paddle.randn([10,3], 'float32')
mask = x * weight
mask = paddle.matmul(x, weight)

out = out * mask
return out
Expand All @@ -129,7 +129,7 @@ def forward(self, x):

## 四、to_tensor() 的使用

``paddle.to_tensor()`` 接口是动态图模型代码中使用比较频繁的一个接口。 ``to_tensor`` 功能强大,将可以将一个 ``scalar`` , ``list`` ,``tuple`` , ``numpy.ndarray`` 转为 ``paddle.Tensor`` 类型。
``paddle.to_tensor()`` 接口是动态图模型代码中使用比较频繁的一个接口。 ``to_tensor`` 功能强大,可以将一个 ``scalar`` , ``list`` ,``tuple`` , ``numpy.ndarray`` 转为 ``paddle.Tensor`` 类型。

此接口是动态图独有的接口,在动转静时,会转换为 ``assign`` 接口:

Expand Down Expand Up @@ -193,14 +193,14 @@ class SimpleNet(paddle.nn.Layer):

动态图模型常常包含很多嵌套的子网络,建议各个自定义的子网络 ``sublayer`` **无论是否包含了参数,都继承 ``nn.Layer`` .**

从 **Parameters 和 Buffers** 章节可知,有些 ``paddle.to_tensor`` 接口转来的 ``Tensor`` 也可能参与预测逻辑分支的计算,即模型导出时,也需要作为参数序列化保存到 ``.pdiparams`` 文件中。
[**Parameters 和 Buffers**](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/jit/principle_cn.html#buffer) 章节可知,有些 ``paddle.to_tensor`` 接口转来的 ``Tensor`` 也可能参与预测逻辑分支的计算,即模型导出时,也需要作为参数序列化保存到 ``.pdiparams`` 文件中。

> **原因**: 若某个 sublayer 包含了 buffer Variables,但却没有继承 ``nn.Layer`` ,则可能导致保存的 ``.pdiparams`` 文件缺失部分重要参数。

**举个例子:**

```python
class SimpleNet(object): # <---- 继承 Object
class SimpleNet(object): # <---- 继承 object
def __init__(self, mask):
super(SimpleNet, self).__init__()
self.linear = paddle.nn.Linear(10, 3) # <---- Linear 参数永远都不会被更新
Expand All @@ -215,7 +215,7 @@ class SimpleNet(object): # <---- 继承 Object

同时,所有继承 ``nn.Layer`` 的 ``sublayer`` 都建议:

+ 重写 ``forward`` 函数,尽量避免重写 ``__call__``` 函数
+ 重写 ``forward`` 函数,尽量避免重写 ``__call__`` 函数
> ``__call__`` 函数通常会包含框架层面的一些通用的处理逻辑,比如 ``pre_hook`` 和 ``post_hook`` 。重写此函数可能会覆盖框架层面的逻辑。

+ 尽量将 ``forward`` 函数作为 sublayers 的调用入口
Expand Down Expand Up @@ -265,19 +265,23 @@ def forward(self, x):
model = SimpleNet()
model.eval() # <---- 一键切换分支,则只会导出 eval 相关的预测分支

jit.save(mode, model_path)
paddle.jit.save(model, model_path)
```


推荐使用 ``self.training`` 或其他非 Tensor 类型的 bool 值进行区分。

此 flag 继承自 ``nn.Layer`` ,因此可通过 ``model.train()`` 和 ``model.eval()`` 来全局切换所有 sublayers 的分支状态。
此 flag 继承自 ``paddle.nn.Layer`` ,因此可通过 ``model.train()`` 和 ``model.eval()`` 来全局切换所有 sublayers 的分支状态。

## 七、非 forward 函数导出

`@to_static` 与 `jit.save` 接口搭配也支持导出非 forward 的其他函数,具体使用方式如下:
`@paddle.jit.to_static` 与 `paddle.jit.save` 接口搭配也支持导出非 forward 的其他函数,具体使用方式如下:

```python
import paddle
from paddle.jit import to_static
from paddle.static import InputSpec

class SimpleNet(paddle.nn.Layer):
def __init__(self):
super(SimpleNet, self).__init__()
Expand All @@ -300,24 +304,24 @@ net = SimpleNet()
net.eval()

# step 2: 定义 InputSpec 信息 (同上)
x_spec = InputSpec(shape=[None, 3], dtype='float32', name='x')
x_spec = InputSpec(shape=[None, 10], dtype='float32', name='x')

# step 3: @to_static 装饰
static_func = to_static(net.another_func, input_spec=[x_spec])

# step 4: 调用 jit.save 接口
net = paddle.jit.save(static_func, path='another_func')
# step 4: 调用 paddle.jit.save 接口
paddle.jit.save(static_func, path='another_func')
```

使用上的区别主要在于:

+ **`@to_static` 装饰**:导出其他函数时需要显式地用 `@to_static` 装饰,以告知动静转换模块将其识别、并转为静态图 Program;
+ **`save`接口参数**:调用`jit.save`接口时,需将上述被`@to_static` 装饰后的函数作为**参数**;
+ **`save`接口参数**:调用`paddle.jit.save`接口时,需将上述被`@to_static` 装饰后的函数作为**参数**;

执行上述代码样例后,在当前目录下会生成三个文件:
```
another_func.pdiparams // 存放模型中所有的权重数据
another_func.pdimodel // 存放模型的网络结构
another_func.pdmodel // 存放模型的网络结构
another_func.pdiparams.info // 存放额外的其他信息
```

Expand All @@ -326,7 +330,7 @@ another_func.pdiparams.info // 存放额外的其他信息

## 八、再谈控制流

前面[【控制流转写】(./basic_usage_cn.html#sikongzhiliuzhuanxie)]提到,不论控制流 ``if/for/while`` 语句是否需要转为静态图中的 ``cond_op/while_op`` ,都会先进行代码规范化,如 ``IfElse`` 语句会规范为如下范式:
前面[【控制流转写】](./principle_cn.html#kongzhiliuzhuanxie)提到,不论控制流 ``if/for/while`` 语句是否需要转为静态图中的 ``cond_op/while_op`` ,都会先进行代码规范化,如 ``IfElse`` 语句会规范为如下范式:

```python
def true_fn_0(out):
Expand All @@ -348,7 +352,7 @@ out = convert_ifelse(paddle.mean(x) > 5.0, true_fn_0, false_fn_0, (x,), (x,), (o

当控制流中,出现了 ``list.append`` 类似语法时,情况会有一点点特殊。

Paddle 框架中的 ``cond_op`` 和 [``while_loop``](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/fluid/layers/while_loop_cn.html#cn-api-fluid-layers-while-loop) 对输入和返回类型有一个要求:
Paddle 框架中的 [``cond_op``](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api/paddle/static/nn/cond_cn.html#cond) 和 [``while_loop``](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api/paddle/static/nn/while_loop_cn.html) 对输入和返回类型有一个要求:
> 输入或者返回类型必须是:LoDTensor 或者 LoDTensorArray <br><br>
> 即:不支持其他非 LoDTensor 类型

Expand Down
12 changes: 7 additions & 5 deletions docs/guides/jit/grammar_list_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ def break_usage(x):
break # <------- jump out of while loop when break ;
return tensor_idx
```
当时输入 x = Tensor([1.0, 2.0 ,3.0]) 时,输出的 tensor_idx 是 Tensor([1])。
当输入 x = Tensor([1.0, 2.0 ,3.0]) 时,输出的 tensor_idx 是 Tensor([1])。

> 注:这里虽然 idx 是-1,但是返回值还是 Tensor。因为`tensor_idx` 在 while loop 中转化为了`Tensor`。
> 注:这里虽然 tensor_idx 初始值是-1,但是返回值还是 Tensor。因为 `idx` 在 while loop 中转化为了只含一个索引值的 `Tensor`。


### 3.5 与、或、非
Expand Down Expand Up @@ -167,9 +167,11 @@ def and(x, y):
<span id='6'></span>
**主要逻辑:**

动态图中可以直接用 Python 的类型转化语法来转化 Tensor 类型。如若 x 是 Tensor 时,float(x)可以将 x 的类型转化为 float。
动态图中可以直接用 Python 的类型转化语法来转化 Tensor 类型。如若 x 是 Tensor 时(只支持含有一个元素的 Tensor ),

动转静在运行时判断 x 是否是 Tensor,若是,则在动转静时使用静态图`cast`接口转化相应的 Tensor 类型。
float(x) 可以将 x 的类型转化为 float。动转静在运行时判断 x 是否是 Tensor,若是,则在动转静时使用静态图 `cast` 接口

转化相应的 Tensor 类型。

**使用样例**:

Expand Down Expand Up @@ -239,7 +241,7 @@ def recur_call(x):
def list_example(x, y):
a = [ x ] # < ------ 支持直接创建
a.append(x) # < ------ 支持调用 append、pop 操作
a[1] = y # < ------ 支持下标修改 append
a[1] = y # < ------ 支持下标修改
return a[0] # < ------ 支持下标获取
```

Expand Down