Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
84 changes: 71 additions & 13 deletions python/paddle/hapi/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,15 @@ def _init_context():
return strategy


def _update_input_shapes(inputs):
shapes = None
if isinstance(inputs, list):
shapes = [list(input.shape) for input in inputs]
elif isinstance(inputs, dict):
shapes = [list(inputs[name].shape) for name in inputs]
return shapes


class StaticGraphAdapter(object):
"""
Model traning/inference with a static graph.
Expand Down Expand Up @@ -598,6 +607,7 @@ def __init__(self, model):
'test_batch': 0
}

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

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

Expand Down Expand Up @@ -704,6 +716,7 @@ def test_batch(self, inputs):
self.model.network.eval()
self.mode = 'test'
inputs = [to_variable(x) for x in to_list(inputs)]
self._input_shapes = _update_input_shapes(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 +791,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 +805,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 +858,18 @@ def __init__(self, network, inputs=None, labels=None):
self._loss = None
self._loss_weights = None
self._optimizer = None
self._optimizer = None
self._input_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")
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:
self._input_shapes = _update_input_shapes(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, True)
self._inputs = self._verify_spec(inputs, is_input=True)
self._labels = self._verify_spec(labels)

# init backend
Expand Down Expand Up @@ -902,7 +920,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._input_shapes is None:
self._input_shapes = self._adapter._input_shapes
self._is_shape_inferred = True
self._inputs = self._verify_spec(None, self._input_shapes, True)
return loss

def eval_batch(self, inputs, labels=None):
"""
Expand Down Expand Up @@ -947,7 +970,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._input_shapes is None:
self._input_shapes = self._adapter._input_shapes
self._is_shape_inferred = True
self._inputs = self._verify_spec(None, self._input_shapes, True)
return loss

def test_batch(self, inputs):
"""
Expand Down Expand Up @@ -987,7 +1015,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._input_shapes is None:
self._input_shapes = self._adapter._input_shapes
self._is_shape_inferred = True
self._inputs = self._verify_spec(None, self._input_shapes, True)
return loss

def save(self, path, training=True):
"""
Expand Down Expand Up @@ -1677,6 +1710,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._input_shapes is None: # No provided or inferred
raise RuntimeError(
"Saving inference model needs 'inputs' or running before saving. Please specify 'inputs' in Model initialization or input training zqqdata and perform a training for shape derivation."
)
if self._is_shape_inferred:
warnings.warn(
"'inputs' was not specified when Model initialization, so the input shape to be saved will be the shape derived from the user's actual inputs. The input shape to be saved is %s. For saving correct input shapes, please provide 'inputs' for Model initialization."
% self._input_shapes)
layer.forward = paddle.jit.to_static(
layer.forward, input_spec=self._inputs)

Expand Down Expand Up @@ -1775,6 +1816,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 +1906,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