Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4c5dead
remove input requirment in dygraph Model
LiuChiachi Sep 25, 2020
47e375a
Merge branch 'develop' of https://github.com/PaddlePaddle/Paddle into…
LiuChiachi Sep 25, 2020
5d2fad7
correct unittest
LiuChiachi Sep 25, 2020
f7f4063
upadte save inference model in dygraph without input
LiuChiachi Sep 27, 2020
87a1b53
fix unittets for test_model.py
LiuChiachi Sep 27, 2020
9048b14
solve conflicts
LiuChiachi Sep 27, 2020
6396fce
solve conflicts
LiuChiachi Sep 27, 2020
67bac48
solve conflicts
LiuChiachi Sep 27, 2020
8dcc03a
delete http.log
LiuChiachi Sep 27, 2020
cc71c7b
fix test_model.py bug, correct initialization of MyModel
LiuChiachi Sep 27, 2020
7afd7d4
fix unittests bugs
LiuChiachi Sep 27, 2020
707a421
set paddle manual seed for unittest
LiuChiachi Sep 27, 2020
c941c36
Merge branch 'develop' of https://github.com/PaddlePaddle/Paddle into…
LiuChiachi Sep 27, 2020
7d8d791
fix Model bugs, because inputs can be list or dict when it is provided.
LiuChiachi Sep 27, 2020
fc0943c
Merge branch 'develop' of https://github.com/PaddlePaddle/Paddle into…
LiuChiachi Sep 27, 2020
3e56bfb
add random seed for test_export_deploy_model
LiuChiachi Sep 27, 2020
16139dc
Merge branch 'develop' of https://github.com/PaddlePaddle/Paddle into…
LiuChiachi Sep 27, 2020
5ecba2a
delete redundant codes, because calls
LiuChiachi Sep 28, 2020
7ab5cb6
Merge branch 'develop' of https://github.com/PaddlePaddle/Paddle into…
LiuChiachi Sep 28, 2020
b151fba
Code optimization, error information optimization
LiuChiachi Sep 28, 2020
9e9132d
Merge branch 'develop' of https://github.com/PaddlePaddle/Paddle into…
LiuChiachi Sep 28, 2020
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
80 changes: 66 additions & 14 deletions python/paddle/hapi/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ def __init__(self, model):
'test_batch': 0
}

self._shapes = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议使用一个更达义的变量名,比如self._input_shapes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢指出,已修改

if self._nranks > 1:
stradegy = fluid.dygraph.parallel.ParallelStrategy()
stradegy.nranks = ParallelEnv().nranks
Expand All @@ -622,6 +623,7 @@ def train_batch(self, inputs, labels=None):
self.model.network.train()
self.mode = 'train'
inputs = to_list(inputs)
self._shapes = [list(input.shape) for input in inputs]
labels = labels or []
labels = [to_variable(l) for l in to_list(labels)]

Expand Down Expand Up @@ -656,6 +658,7 @@ def eval_batch(self, inputs, labels=None):
self.model.network.eval()
self.mode = 'eval'
inputs = to_list(inputs)
self._shapes = [list(input.shape) for input in inputs]
labels = labels or []
labels = [to_variable(l) for l in to_list(labels)]

Expand Down Expand Up @@ -704,6 +707,7 @@ def test_batch(self, inputs):
self.model.network.eval()
self.mode = 'test'
inputs = [to_variable(x) for x in to_list(inputs)]
self._shapes = [list(input.shape) for input in inputs]
outputs = self.model.network.forward(*inputs)
if self._nranks > 1 and isinstance(self.model._place, fluid.CUDAPlace):
outputs = [_all_gather(o, self._nranks) for o in to_list(outputs)]
Expand Down Expand Up @@ -778,7 +782,7 @@ def load(self, param_state_pairs, optim_state):

if not hasattr(self.model._optimizer, 'set_state_dict'):
warnings.warn(
"paddle.fluid.optimizer is deprecated in API 2.0, please use paddle.optimizer instead"
"paddle.fluid.optimizer is deprecated in API 2.0, please use paddle.optimizer instead."
)
self.model._optimizer.set_dict(converted_state)
else:
Expand All @@ -792,14 +796,15 @@ class Model(object):
switched by `paddle.disable_static()`. The usage is as follows.
But note, the switching between dynamic and static should be before
instantiating a Model. The input description, i.e, paddle.static.InputSpec,
must be required.
must be required for static graph.

Args:
network (paddle.nn.Layer): The network is an instance of
paddle.nn.Layer.
inputs (InputSpec|list|dict|None): `inputs`, entry points of network,
could be a InputSpec instance, or lits of InputSpec instances,
or dict ({name: InputSpec}), and it couldn't be None.
or dict ({name: InputSpec}), and it couldn't be None in static
graph.
labels (InputSpec|list|None): `labels`, entry points of network,
could be a InputSpec instnace or lits of InputSpec instances,
or None. For static graph, if labels is required in loss,
Expand Down Expand Up @@ -844,14 +849,21 @@ def __init__(self, network, inputs=None, labels=None):
self._loss = None
self._loss_weights = None
self._optimizer = None
self._optimizer = None
self._shapes = None
self._is_shape_inferred = False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议一个更达义的变量名替换self._shapes。这里如果成员变量inputs初始化后是不变的话,是否不需要一个额外的shape变量。只需要一个成员函数解析一下self._inputs即可?

如果保留这个shape变量的话,建议修改下变量名。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动、静态图下在Model初始化时都需要对self._inputs进行初始化,因为目前train_batch, eval_batch等也需要用到self._inputs。因此需要在动态图下用户没提供inputs时用self._input_shapes记录下在运行模型推导出的输入shape,以便能通过此次更新后的self._verify_spec根据shape获取一个可传递给paddle.to_static的较为合理的self._inputs

self._test_dataloader = None

if not isinstance(inputs, (list, dict, Input)):
raise TypeError(
"'inputs' must be list or dict in static graph mode")

self._inputs = self._verify_spec(inputs, True)
if not in_dygraph_mode():
if not isinstance(inputs, (list, dict, Input)):
raise TypeError(
"'inputs' must be list or dict, and couldn't be None.")
elif inputs:
if isinstance(inputs, list):
self._shapes = [list(input.shape) for input in inputs]
elif isinstance(inputs, dict):
self._shapes = [list(inputs[name].shape) for name in inputs]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如上个comment,elif的这部分逻辑其实可以抽离到一个成员函数里,用于解析inputs的shape

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修改,感谢~

self._inputs = self._verify_spec(inputs, is_input=True)
self._labels = self._verify_spec(labels)

# init backend
Expand Down Expand Up @@ -902,7 +914,12 @@ def train_batch(self, inputs, labels=None):
loss = model.train_batch([data], [label])
print(loss)
"""
return self._adapter.train_batch(inputs, labels)
loss = self._adapter.train_batch(inputs, labels)
if fluid.in_dygraph_mode() and self._shapes is None:
self._shapes = self._adapter._shapes
self._is_shape_inferred = True
self._inputs = self._verify_spec(None, self._shapes, True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议把这部分逻辑单独抽离为一个公用函数。下面有多处调用,方便复用

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我理解,静态图下是必须有inputs的,也就是self._inputs不能为空。动态图下这里应该只需要执行一次,以更新self._inputs,后续判断self.inputs非空即可了。

所以这里的条件判断只需要判断self._inputs是否已经被正确初始化就可以了

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动、静态图下在Model初始化时都需要对self._inputs进行初始化,因为目前train_batch, eval_batch等也需要用到self._inputs。因此需要在动态图下用户没提供inputs时用self._input_shapes记录下在运行模型推导出的输入shape,以便能通过此次更新后的self._verify_spec根据shape获取一个可传递给paddle.to_static的较为合理的self._inputs

return loss

def eval_batch(self, inputs, labels=None):
"""
Expand Down Expand Up @@ -947,7 +964,12 @@ def eval_batch(self, inputs, labels=None):
loss = model.eval_batch([data], [label])
print(loss)
"""
return self._adapter.eval_batch(inputs, labels)
loss = self._adapter.eval_batch(inputs, labels)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢~已修正

if fluid.in_dygraph_mode() and self._shapes is None:
self._shapes = self._adapter._shapes
self._is_shape_inferred = True
self._inputs = self._verify_spec(None, self._shapes, True)
return loss

def test_batch(self, inputs):
"""
Expand Down Expand Up @@ -987,7 +1009,12 @@ def test_batch(self, inputs):
out = model.test_batch([data])
print(out)
"""
return self._adapter.test_batch(inputs)
loss = self._adapter.test_batch(inputs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修改,感谢

if fluid.in_dygraph_mode() and self._shapes is None:
self._shapes = self._adapter._shapes
self._is_shape_inferred = True
self._inputs = self._verify_spec(None, self._shapes, True)
return loss

def save(self, path, training=True):
"""
Expand Down Expand Up @@ -1677,6 +1704,14 @@ def get_inout_spec(all_vars, return_name=False):
if fluid.in_dygraph_mode():
with fluid.framework._dygraph_guard(None):
layer = self.network
if self._shapes is None: # No provided or inferred
raise RuntimeError(
"Saving inference model needs `inputs` or running before saving."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

报错文案可以优化下。这里只给出了原因,也可以给出一些解决方式。比如:

  1. 提示用户指定inputs
  2. 可以输入数据,执行一次训练用于shape的推导。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢!已修改,在报错信息里进行了更详细的说明

)
if self._is_shape_inferred:
warnings.warn(
'Saving actual input shapes only if `inputs` is provided, otherwise variable input dimension is immutable.'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个报错文案是否可以优化下。这里warning信息里有两点要提示用户:

  1. 提醒用户Model里没有指定inputs,这里保存时将使用从实际数据推导出来的shape信息保存模型
  2. 打印出推导出来的shape信息,让用户知道自己保存的输入shape

)
layer.forward = paddle.jit.to_static(
layer.forward, input_spec=self._inputs)

Expand Down Expand Up @@ -1775,6 +1810,7 @@ def _run_one_epoch(self, data_loader, callbacks, mode, logs={}):
data = flatten(data)
# LoDTensor.shape is callable, where LoDTensor comes from
# DataLoader in static graph

batch_size = data[0].shape()[0] if callable(data[
0].shape) else data[0].shape[0]

Expand Down Expand Up @@ -1864,10 +1900,26 @@ def summary(self, input_size=None, dtype=None):
_input_size = self._inputs
return summary(self.network, _input_size, dtype)

def _verify_spec(self, specs, is_input=False):
def _verify_spec(self, specs, shapes=None, is_input=False):
out_specs = []

if isinstance(specs, dict):
if specs is None:
# Note(Aurelius84): If not specific specs of `Input`, using argument names of `forward` function
# to generate `Input`. But how can we know the actual shape of each input tensor?

if is_input:
arg_names = extract_args(self.network.forward)[1:]
if shapes is not None and fluid.in_dygraph_mode():
out_specs = [
Input(
name=n, shape=shapes[i])
for i, n in enumerate(arg_names)
]
else:
out_specs = [Input(name=n, shape=[None]) for n in arg_names]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的else分支应该可以去掉吧。直接返回空就可以了,后续依靠推导来得到。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

train_batch, eval_batch目前会用到self._inputs,因此Model初始化时所初始化的self._inputs不能是None

else:
out_specs = to_list(specs)
elif isinstance(specs, dict):
assert is_input == False
out_specs = [specs[n] \
for n in extract_args(self.network.forward) if n != 'self']
Expand Down
86 changes: 48 additions & 38 deletions python/paddle/tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,34 +66,6 @@ def forward(self, inputs):
return x


class LeNetDeclarative(fluid.dygraph.Layer):
def __init__(self, num_classes=10):
super(LeNetDeclarative, self).__init__()
self.num_classes = num_classes
self.features = Sequential(
Conv2d(
1, 6, 3, stride=1, padding=1),
ReLU(),
Pool2D(2, 'max', 2),
Conv2d(
6, 16, 5, stride=1, padding=0),
ReLU(),
Pool2D(2, 'max', 2))

if num_classes > 0:
self.fc = Sequential(
Linear(400, 120), Linear(120, 84), Linear(84, 10))

@declarative
def forward(self, inputs):
x = self.features(inputs)

if self.num_classes > 0:
x = fluid.layers.flatten(x, 1)
x = self.fc(x)
return x


class MnistDataset(MNIST):
def __init__(self, mode, return_label=True, sample_num=None):
super(MnistDataset, self).__init__(mode=mode)
Expand Down Expand Up @@ -440,9 +412,7 @@ def test_dynamic_save_static_load(self):
# dynamic saving
device = paddle.set_device('cpu')
fluid.enable_dygraph(device)
inputs = [InputSpec([None, 20], 'float32', 'x')]
labels = [InputSpec([None, 1], 'int64', 'label')]
model = Model(MyModel(), inputs, labels)
model = Model(MyModel())
optim = fluid.optimizer.SGD(learning_rate=0.001,
parameter_list=model.parameters())
model.prepare(optimizer=optim, loss=CrossEntropyLoss(reduction="sum"))
Expand Down Expand Up @@ -545,6 +515,8 @@ def test_summary_error(self):
paddle.summary(nlp_net, (1, 1, 2))

def test_export_deploy_model(self):
self.set_seed()
np.random.seed(2020)
for dynamic in [True, False]:
paddle.disable_static() if dynamic else None
prog_translator = ProgramTranslator()
Expand Down Expand Up @@ -579,6 +551,35 @@ def test_export_deploy_model(self):
shutil.rmtree(save_dir)
paddle.enable_static()

def test_dygraph_export_deploy_model_without_inputs(self):
mnist_data = MnistDataset(mode='train')
paddle.disable_static()
for initial in ["fit", "train_batch", "eval_batch", "test_batch"]:
save_dir = tempfile.mkdtemp()
if not os.path.exists(save_dir):
os.makedirs(save_dir)
net = LeNet()
model = Model(net)
optim = fluid.optimizer.Adam(
learning_rate=0.001, parameter_list=model.parameters())
model.prepare(
optimizer=optim, loss=CrossEntropyLoss(reduction="sum"))
if initial == "fit":
model.fit(mnist_data, batch_size=64, verbose=0)
else:
img = np.array(
np.random.random((1, 1, 28, 28)), dtype=np.float32)
label = np.array(np.random.rand(1, 1), dtype=np.int64)
if initial == "train_batch":
model.train_batch([img], [label])
elif initial == "eval_batch":
model.eval_batch([img], [label])
else:
model.test_batch([img])

model.save(save_dir, training=False)
shutil.rmtree(save_dir)


class TestRaiseError(unittest.TestCase):
def test_input_without_name(self):
Expand All @@ -589,13 +590,22 @@ def test_input_without_name(self):
with self.assertRaises(ValueError):
model = Model(net, inputs, labels)

def test_input_without_input_spec(self):
for dynamic in [True, False]:
paddle.disable_static() if dynamic else None
net = MyModel()
with self.assertRaises(TypeError):
model = Model(net)
paddle.enable_static()
def test_static_without_inputs(self):
paddle.enable_static()
net = MyModel()
with self.assertRaises(TypeError):
model = Model(net)

def test_save_infer_model_without_inputs_and_run_in_dygraph(self):
paddle.disable_static()
net = MyModel()
save_dir = tempfile.mkdtemp()
if not os.path.exists(save_dir):
os.makedirs(save_dir)
with self.assertRaises(RuntimeError):
model = Model(net)
model.save(save_dir, training=False)
paddle.enable_static()


if __name__ == '__main__':
Expand Down