input_spec_cn.rst 9.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
.. _user_guide_dy2sta_input_spec_cn:

InputSpec功能介绍
=================


在PaddlePaddle(下文简称:Paddle)框架中,可以通过 ``paddle.jit.to_static`` 装饰普通函数或 Layer 的最外层 forward 函数,将动态图模型转换为静态图执行。但在动转静时,需要给模型传入 Tensor 数据并执行一次前向,以保证正确地推导出网络中各 Tensor 的 shape 。此转换流程需要显式地执行一次动态图函数,增加了接口使用的成本;同时,传入实际 Tensor 数据则无法定制化模型输入的shape,如指定某些维度为 None 。

因此,Paddle 提供了 InputSpec 接口,可以更加便捷地执行动转静功能,以及定制化输入 Tensor 的 shape 、name 等信息。


一、InputSpec 对象构造方法
-------------------------

1.1 直接构造 InputSpec 对象
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

InputSpec 接口在 ``paddle.static`` 目录下,用于描述一个 Tensor 的签名信息:shape、dtype、name。使用样例如下:

.. code-block:: python

    from paddle.static import InputSpec

    x = InputSpec([None, 784], 'float32', 'x')
    label = InputSpec([None, 1], 'int64', 'label')

    print(x)      # InputSpec(shape=(-1, 784), dtype=VarType.FP32, name=x)
    print(label)  # InputSpec(shape=(-1, 1), dtype=VarType.INT64, name=label)


InputSpec 初始化中的只有 ``shape`` 是必须参数, ``dtype`` 和 ``name`` 可以缺省,默认取值分别为 ``float32`` 和 ``None`` 。



1.2 根据 Tensor 构造 InputSpec 对象
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

可以借助 ``InputSpec.from_tensor`` 方法,从一个 Tensor 直接创建 InputSpec 对象,其拥有与源 Tensor 相同的 ``shape`` 和 ``dtype`` 。使用样例如下:

.. code-block:: python

    import numpy as np
    import paddle
    from paddle.static import InputSpec

    paddle.disable_static()

    x = paddle.to_tensor(np.ones([2, 2], np.float32))
    x_spec = InputSpec.from_tensor(x, name='x')
    print(x_spec)  # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x)


.. note::
    若未在 ``from_tensor`` 中指定新的name,则默认使用与源Tensor相同的name。


1.3 根据 numpy.ndarray 构造 InputSpec 对象
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

也可以借助 ``InputSpec.from_numpy`` 方法,从一个 Numpy.ndarray 直接创建 InputSpec 对象,其拥有与源 ndarray 相同的 ``shape`` 和 ``dtype`` 。使用样例如下:

.. code-block:: python

    import numpy as np
    from paddle.static import InputSpec

    x = np.ones([2, 2], np.float32)
    x_spec = InputSpec.from_numpy(x, name='x')
    print(x_spec)  # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x)


.. note::
    若未在 ``from_numpy`` 中指定新的 name,则默认使用 None 。


二、基本使用方法
------------------

动转静 ``paddle.jit.to_static`` 装饰器支持 ``input_spec`` 参数,用于指定被装饰函数每个 Tensor 类型输入参数的 ``shape`` 、 ``dtype`` 、 ``name`` 等签名信息。不必再显式地传入 Tensor 数据以触发网络层 shape 的推导。 Paddle 会解析 ``to_static`` 中指定的 ``input_spec`` 参数,构建网络的起始输入,进行后续的模型组网。

同时,借助 ``input_spec`` 参数,可以自定义输入 Tensor 的 shape ,比如指定 shape 为 ``[None, 784]`` ,其中 ``None`` 表示变长的维度。

2.1 to_static 装饰器模式
^^^^^^^^^^^^^^^^^^^^^^^^^^

如下是一个简单的使用样例:

.. code-block:: python

    import paddle
    from paddle.jit import to_static
    from paddle.static import InputSpec
    from paddle.fluid.dygraph import Layer

    class SimpleNet(Layer):
        def __init__(self):
            super(SimpleNet, self).__init__()
            self.linear = paddle.nn.Linear(10, 3)

        @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')])
        def forward(self, x, y):
            out = self.linear(x)
            out = out + y
            return out


    paddle.disable_static()

    net = SimpleNet()

    # save static model for inference directly
    paddle.jit.save(net, './simple_net')


在上述的样例中, ``to_static`` 装饰器中的 ``input_spec`` 为一个 InputSpec 对象组成的列表,用于依次指定参数 x 和 y 对应的 Tensor 签名信息。在实例化 SimpleNet 后,可以直接调用 ``paddle.jit.save`` 保存静态图模型,不需要执行任何其他的代码。

.. note::
    1. input_spec 参数中只支持 InputSpec 对象,暂不支持如 int 、 float 等类型。
    2. 若指定 input_spec 参数,则需为被装饰函数的所有必选参数都添加对应的 InputSpec 对象,如上述样例中,不支持仅指定 x 的签名信息。
    3. 若被装饰函数中包括非 Tensor 参数,且指定了 input_spec ,请确保函数的非 Tensor 参数都有默认值,如 ``forward(self, x, use_bn=False)``


2.2 to_static函数调用
^^^^^^^^^^^^^^^^^^^^

若期望在动态图下训练模型,在训练完成后保存预测模型,并指定预测时需要的签名信息,则可以选择在保存模型时,直接调用 ``to_static`` 函数。使用样例如下:

.. code-block:: python

    class SimpleNet(Layer):
        def __init__(self):
            super(SimpleNet, self).__init__()
            self.linear = paddle.nn.Linear(10, 3)

        def forward(self, x, y):
            out = self.linear(x)
            out = out + y
            return out

    paddle.disable_static()
    net = SimpleNet()

    # train process (Pseudo code)
    for epoch_id in range(10):
        train_step(net, train_reader)
        
    net = to_static(net, input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')])

    # save static model for inference directly
    paddle.jit.save(net, './simple_net')


如上述样例代码中,在完成训练后,可以借助 ``to_static(net, input_spec=...)`` 形式对模型实例进行处理。Paddle 会根据 input_spec 信息对 forward 函数进行递归的动转静,得到完整的静态图,且包括当前训练好的参数数据。


2.3 支持 list 和 dict 推导
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

上述两个样例中,被装饰的 forward 函数的参数均为 Tensor 。这种情况下,参数个数必须与 InputSpec 个数相同。但当被装饰的函数参数为list或dict类型时,``input_spec`` 需要与函数参数保持相同的嵌套结构。

当函数的参数为 list 类型时,input_spec 列表中对应元素的位置,也必须是包含相同元素的 InputSpec 列表。使用样例如下:

.. code-block:: python

    class SimpleNet(Layer):
        def __init__(self):
            super(SimpleNet, self).__init__()
            self.linear = paddle.nn.Linear(10, 3)

        @to_static(input_spec=[[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]])
        def forward(self, inputs):
            x, y = inputs[0], inputs[1]
            out = self.linear(x)
            out = out + y
            return out


其中 ``input_spec`` 参数是长度为 1 的 list ,对应 forward 函数的 inputs 参数。 ``input_spec[0]`` 包含了两个 InputSpec 对象,对应于参数 inputs 的两个 Tensor 签名信息。

当函数的参数为dict时, ``input_spec`` 列表中对应元素的位置,也必须是包含相同键(key)的 InputSpec 列表。使用样例如下:

.. code-block:: python

    class SimpleNet(Layer):
        def __init__(self):
            super(SimpleNet, self).__init__()
            self.linear = paddle.nn.Linear(10, 3)

        @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), {'x': InputSpec(shape=[3], name='bias')}])
        def forward(self, x, bias_info):
            x_bias = bias_info['x']
            out = self.linear(x)
            out = out + x_bias
            return out


其中 ``input_spec`` 参数是长度为 2 的 list ,对应 forward 函数的 x 和 bias_info 两个参数。 ``input_spec`` 的最后一个元素是包含键名为 x 的 InputSpec 对象的 dict ,对应参数 bias_info 的 Tensor 签名信息。


200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
2.4 指定非Tensor参数类型
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

目前,``to_static`` 装饰器中的 ``input_spec`` 参数仅接收 ``InputSpec`` 类型对象。若被装饰函数的参数列表除了 Tensor 类型,还包含其他如 Int、 String 等非 Tensor 类型时,推荐在函数中使用 kwargs 形式定义非 Tensor 参数,如下述样例中的 use_act 参数。

.. code-block:: python

    class SimpleNet(Layer):
        def __init__(self, ):
            super(SimpleNet, self).__init__()
            self.linear = paddle.nn.Linear(10, 3)
            self.relu = paddle.nn.ReLU()

        @to_static(input_spec=[InputSpec(shape=[None, 10], name='x')])
        def forward(self, x, use_act=False):
            out = self.linear(x)
            if use_act:
                out = self.relu(out)
            return out

    net = SimpleNet()
    adam = paddle.optimizer.Adam(parameters=net.parameters())

    # train model
    batch_num = 10
    for step in range(batch_num):
        x = paddle.rand([4, 10], 'float32')
        use_act = (step%2 == 0)
        out = net(x, use_act)
        loss = paddle.mean(out)
        loss.backward()
        adam.minimize(loss)
        net.clear_gradients()

    # save inference model with use_act=False
    paddle.jit.save(net, model_path='./simple_net')


在上述样例中,step 为奇数时,use_act 取值为 False ; step 为偶数时, use_act 取值为 True 。动转静支持非 Tensor 参数在训练时取不同的值,且保证了取值不同的训练过程都可以更新模型的网络参数,行为与动态图一致。

kwargs 参数的默认值主要用于保存推理模型。在借助 ``paddle.jit.save`` 保存预测模型时,动转静会根据 input_spec 和 kwargs 的默认值保存推理模型和网络参数。因此建议将 kwargs 参数默认值设置为预测时的取值。

242
更多关于动转静 ``to_static`` 搭配 ``paddle.jit.save/load`` 的使用方式,可以参考 :ref:`user_guide_model_save_load` 。