parameters.py 13.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Y
Yu Yang 已提交
15
import numpy as np
Q
qiaolongfei 已提交
16
from paddle.proto.ParameterConfig_pb2 import ParameterConfig
C
caoying03 已提交
17
from collections import OrderedDict
X
xuwei06 已提交
18
import paddle.trainer.config_parser as cp
Y
Yu Yang 已提交
19 20 21
import struct
import tarfile
import cStringIO
Q
qiaolongfei 已提交
22
from topology import Topology
Q
qiaolongfei 已提交
23

Y
Yu Yang 已提交
24
__all__ = ['Parameters', 'create']
Y
Yu Yang 已提交
25 26


Q
qiaolongfei 已提交
27
def create(layers):
Y
Yu Yang 已提交
28
    """
Q
qiaolongfei 已提交
29
    Create parameter pool by topology.
Y
Yu Yang 已提交
30

Q
qiaolongfei 已提交
31
    :param layers:
Y
Yu Yang 已提交
32
    :return:
Y
Yu Yang 已提交
33
    """
Q
qiaolongfei 已提交
34
    topology = Topology(layers)
Q
qiaolongfei 已提交
35
    pool = Parameters()
X
xuwei06 已提交
36
    initializers = cp.g_parameter_initializer_map
Q
qiaolongfei 已提交
37
    for param in topology.proto().parameters:
Q
qiaolongfei 已提交
38
        pool.__append_config__(param)
X
xuwei06 已提交
39 40
        if param.name in initializers:
            pool[param.name] = initializers[param.name](param.name)
Y
Yu Yang 已提交
41
    return pool
Y
Yu Yang 已提交
42 43


Y
Yu Yang 已提交
44
class Parameters(object):
Y
Yu Yang 已提交
45
    """
C
caoying03 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
    `Parameters` manages all the learnable parameters in a neural network.
    It stores parameters' information in an OrderedDict, key of which is
    the name of a parameter, and value related to a key is a parameter's
    configuration, such as initialization mean and std, its size, whether it is
    a static parameter, and so on.

    :param __param_conf__: this member stores the configurations of learnable
        parameters in a network in an OrderedDict. The parameters are added by
        following their creation order in the neural network one by one:
        parameters of the previous layers in a network are careted first.
        When a user iterates over this dict, he can visit parameters in the
        network from button to up.
    :type __param_conf__: OrderedDict
    :param __gradient_machines__: all of the parameters in a neural network are
        appended to a Paddle gradient machine, which is used internally to copy
        the parameter values between the C++ and Python end.
    :type __gradient_machines__: list
    :param __tmp_params__: a dict to store dummy parameters if no
        __gradient_machines__ is appended to `Parameters`.
    :type __tmp_params__: dict
Y
Yu Yang 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78 79

    Basically usage is

    ..  code-block:: python

        data = paddle.layers.data(...)
        ...
        out = paddle.layers.fc(...)

        parameters = paddle.parameters.create(out)

        parameter_names = parameters.names()
        fc_mat = parameters.get('fc')
        print fc_mat
Y
Yu Yang 已提交
80 81
    """

Y
Yu Yang 已提交
82
    def __init__(self):
C
caoying03 已提交
83
        self.__param_conf__ = OrderedDict()
Y
Yu Yang 已提交
84
        self.__gradient_machines__ = []
85
        self.__tmp_params__ = dict()
Y
Yu Yang 已提交
86

Y
Yu Yang 已提交
87 88 89 90 91 92 93 94 95 96
    def __append_config__(self, param_conf):
        """
        Append a parameter configuration. It used to initialize Parameters and
        should be invoked only in paddle.parameters.create

        :param param_conf: The parameter configuration in protobuf
        :type param_conf: ParameterConfig
        :return: Nothing
        """

Y
Yu Yang 已提交
97 98 99 100 101 102 103 104 105
        if not isinstance(param_conf, ParameterConfig):
            raise ValueError("param_conf must be paddle.proto.ParameterConfig")

        if param_conf.name in self.__param_conf__:
            raise ValueError("duplicated parameter %s" % param_conf.name)

        self.__param_conf__[param_conf.name] = param_conf

    def keys(self):
Y
Yu Yang 已提交
106 107
        """
        keys are the names of each parameter.
Y
Yu Yang 已提交
108

Y
Yu Yang 已提交
109 110 111
        :return: list of parameter name
        :rtype: list
        """
Y
Yu Yang 已提交
112 113 114
        return self.__param_conf__.keys()

    def names(self):
Y
Yu Yang 已提交
115 116
        """
        names of each parameter.
Y
Yu Yang 已提交
117

Y
Yu Yang 已提交
118 119 120
        :return: list of parameter name
        :rtype: list
        """
Y
Yu Yang 已提交
121 122 123
        return self.keys()

    def has_key(self, key):
Y
Yu Yang 已提交
124 125
        """
        has_key return true if there are such parameter name == key
Y
Yu Yang 已提交
126

Y
Yu Yang 已提交
127 128 129 130
        :param key: Parameter name
        :type key: basestring
        :return: True if contains such key
        """
Y
Yu Yang 已提交
131 132
        return key in self.__param_conf__.keys()

Y
Yu Yang 已提交
133
    def __iter__(self):
Y
Yu Yang 已提交
134 135 136 137 138 139 140 141 142 143 144 145
        """
        Return an iterator of parameter name. It is used by `for loop`
        or `in` operator.

        ..  code-block:: python

            parameters = paddle.parameters.create(...)
            if "fc_param" in parameters:
                print 'OK'
        :return: an iterator of parameter name
        :rtype: iterator
        """
Y
Yu Yang 已提交
146 147
        return iter(self.__param_conf__)

148
    def __getter_inner(self, key, param_type):
Y
Yu Yang 已提交
149
        import py_paddle.swig_paddle as api
Y
Yu Yang 已提交
150 151 152 153
        shape = self.get_shape(key)

        if len(self.__gradient_machines__) == 0:
            # create new parameter in python numpy.
154 155 156 157
            if key in self.__tmp_params__:
                return self.__tmp_params__[key]
            else:
                return np.ndarray(shape=shape, dtype=np.float32)
Y
Yu Yang 已提交
158 159 160 161 162 163
        else:
            for each_gradient_machine in self.__gradient_machines__:
                param = __get_parameter_in_gradient_machine__(
                    each_gradient_machine, key)
                # for simplify implementation now, we always copy from C++
                assert isinstance(param, api.Parameter)
164
                val = param.getBuf(param_type)
Y
Yu Yang 已提交
165
                assert isinstance(val, api.Vector)
Y
Yu Yang 已提交
166 167
                val = val.copyToNumpyArray()
                return val
Y
Yu Yang 已提交
168 169 170 171
                # else continue

            raise RuntimeError("Unexpected branch")

172 173 174 175 176 177 178 179 180 181 182 183 184
    def __getitem__(self, key):
        """
        Get parameter by parameter name. It uses Python dict syntax.

        :note: It will always copy the parameter from C++ side.
        :param key: Parameter name
        :type key: basestring
        :return: parameter value
        :rtype: np.ndarray
        """
        import py_paddle.swig_paddle as api
        return self.__getter_inner(key, api.PARAMETER_VALUE)

Y
Yu Yang 已提交
185
    def get_shape(self, key):
Y
Yu Yang 已提交
186 187
        """
        get shape of the parameter.
Y
Yu Yang 已提交
188

Y
Yu Yang 已提交
189 190 191 192 193
        :param key: parameter name
        :type key: basestring
        :return: parameter's shape
        :rtype: tuple
        """
Y
Yu Yang 已提交
194 195 196 197 198
        if not isinstance(key, basestring):
            raise ValueError("parameter name should be string")
        if not self.has_key(key):
            raise ValueError("No such parameter %s" % key)
        conf = self.__param_conf__[key]
D
dangqingqing 已提交
199 200
        dims = conf.dims if conf.dims else (1, conf.size)
        return tuple(map(int, dims))
Y
Yu Yang 已提交
201 202

    def __setitem__(self, key, value):
Y
Yu Yang 已提交
203 204 205 206 207 208 209 210 211 212 213
        """
        Set parameter by parameter name & value. It use Python dict syntax.

        :note: It will always copy the parameter to C++ side.
        :param key: Parameter name
        :type key: basestring
        :param value: Parameter matrix.
        :type value: np.ndarray
        :return: Nothing
        """

Y
Yu Yang 已提交
214 215 216 217
        if not isinstance(value, np.ndarray):
            raise ValueError("Must return ndarray")
        value = value.astype(dtype=np.float32)
        shape = self.get_shape(key)
Y
Yu Yang 已提交
218
        if value.shape != shape:
Y
Yu Yang 已提交
219 220 221 222
            raise ValueError("Value shape mismatch, expect %s, should %s" %
                             (shape, value.shape))

        if len(self.__gradient_machines__) == 0:
223
            self.__tmp_params__[key] = value
Y
Yu Yang 已提交
224 225 226 227 228
        else:
            for each_gradient_machine in self.__gradient_machines__:
                __copy_parameter_to_gradient_machine__(each_gradient_machine,
                                                       key, value)

Y
Yu Yang 已提交
229
    def get(self, parameter_name):
Y
Yu Yang 已提交
230 231 232 233 234 235 236 237 238
        """
        Get parameter by parameter name.

        :note: It will always copy the parameter from C++ side.
        :param parameter_name: parameter name
        :type parameter_name: basestring
        :return: The parameter matrix.
        :rtype: np.ndarray
        """
Y
Yu Yang 已提交
239 240
        return self.__getitem__(key=parameter_name)

241 242 243 244 245 246 247 248 249 250 251
    def get_grad(self, key):
        """
        Get grandient by parameter name.

        :note: It will always copy the parameter from C++ side.
        :param key: parameter name
        :type key: basestring
        :return: The grandient matrix.
        :rtype: np.ndarray
        """
        import py_paddle.swig_paddle as api
C
caoying03 已提交
252 253 254
        if self.__param_conf__[key].is_static:
            return np.zeros(self.__param_conf__[key].size, dtype=np.float32)

255 256
        return self.__getter_inner(key, api.PARAMETER_GRADIENT)

Y
Yu Yang 已提交
257
    def set(self, parameter_name, value):
Y
Yu Yang 已提交
258 259
        """
        Set parameter by parameter name & matrix.
Y
Yu Yang 已提交
260

Y
Yu Yang 已提交
261 262 263 264 265 266
        :param parameter_name: parameter name
        :type parameter_name: basestring
        :param value: parameter matrix
        :type value: np.ndarray
        :return: Nothing.
        """
Y
Yu Yang 已提交
267 268
        self.__setitem__(key=parameter_name, value=value)

Y
Yu Yang 已提交
269
    def append_gradient_machine(self, gradient_machine):
Y
Yu Yang 已提交
270 271 272 273 274 275 276 277
        """
        append gradient machine to parameters. This method is used internally in
        Trainer.train.

        :param gradient_machine: Paddle C++ GradientMachine object.
        :type gradient_machine: api.GradientMachine
        :return:
        """
Y
Yu Yang 已提交
278
        import py_paddle.swig_paddle as api
Y
Yu Yang 已提交
279 280 281 282
        if not isinstance(gradient_machine, api.GradientMachine):
            raise ValueError("gradient_machine should be api.GradientMachine")

        if len(self.__tmp_params__) != 0:
283
            for name, val in self.__tmp_params__.iteritems():
Y
Yu Yang 已提交
284 285 286 287 288 289
                try:
                    __copy_parameter_to_gradient_machine__(gradient_machine,
                                                           name, val)
                except ValueError:
                    # If no such parameter in gradient machine, then don't copy
                    pass
290 291

        self.__gradient_machines__.append(gradient_machine)
Y
Yu Yang 已提交
292

Y
Yu Yang 已提交
293 294 295 296 297 298 299 300 301 302 303 304
    def serialize(self, name, f):
        """

        :param name:
        :param f:
        :type f: file
        :return:
        """
        param = self.get(name)
        size = reduce(lambda a, b: a * b, param.shape)
        f.write(struct.pack("IIQ", 0, 4, size))
        param = param.astype(np.float32)
305 306 307 308 309 310 311
        s = param.tostring()
        wrote_size = 0
        buf = buffer(s, wrote_size, 65535)
        while buf:  # f.write crashes with big data blog.
            f.write(buf)
            wrote_size += 65535
            buf = buffer(s, wrote_size, 65535)
Y
Yu Yang 已提交
312 313 314 315 316 317 318 319 320 321

    def deserialize(self, name, f):
        """

        :param name:
        :param f:
        :type f: file
        :return:
        """
        f.read(16)  # header
Y
Yu Yang 已提交
322
        arr = np.frombuffer(f.read(), dtype=np.float32)
Y
Yu Yang 已提交
323 324
        self.set(name, arr.reshape(self.get_shape(name)))

Y
Yu Yang 已提交
325
    def to_tar(self, f):
Y
Yu Yang 已提交
326 327 328 329 330 331 332 333 334
        tar = tarfile.TarFile(fileobj=f, mode='w')
        for nm in self.names():
            buf = cStringIO.StringIO()
            self.serialize(nm, buf)
            tarinfo = tarfile.TarInfo(name=nm)
            buf.seek(0)
            tarinfo.size = len(buf.getvalue())
            tar.addfile(tarinfo, buf)

Y
Yu Yang 已提交
335 336 337 338 339 340 341 342 343 344
            conf = self.__param_conf__[nm]
            confStr = conf.SerializeToString()
            tarinfo = tarfile.TarInfo(name="%s.protobuf" % nm)
            tarinfo.size = len(confStr)
            buf = cStringIO.StringIO(confStr)
            buf.seek(0)
            tar.addfile(tarinfo, fileobj=buf)

    @staticmethod
    def from_tar(f):
D
dangqingqing 已提交
345 346 347 348 349 350 351 352 353 354 355 356
        """
        Create a `Parameters` object from the given file. And
        the `Parameters` only contains the parameters in this
        file. It is adapted the parameters are same in the
        defined network and the given file. For example, it
        can be used in the inference.

        :param f: the initialized model file.
        :type f: tar file
        :return: A Parameters object.
        :rtype: Parameters.
        """
Y
Yu Yang 已提交
357 358 359 360 361 362 363 364 365 366 367 368 369 370
        params = Parameters()
        tar = tarfile.TarFile(fileobj=f, mode='r')
        for finfo in tar:
            assert isinstance(finfo, tarfile.TarInfo)
            if finfo.name.endswith('.protobuf'):
                f = tar.extractfile(finfo)
                conf = ParameterConfig()
                conf.ParseFromString(f.read())
                params.__append_config__(conf)

        for param_name in params.names():
            f = tar.extractfile(param_name)
            params.deserialize(param_name, f)
        return params
Y
Yu Yang 已提交
371

372
    def init_from_tar(self, f):
D
dangqingqing 已提交
373 374 375 376 377 378 379 380 381
        """
        Different from `from_tar`, this interface can be used to
        init partial network parameters from another saved model.

        :param f: the initialized model file.
        :type f: tar file
        :return: Nothing.
        """

382
        tar_param = Parameters.from_tar(f)
383 384 385 386
        for pname in tar_param.names():
            if pname in self.names():
                self.set(pname, tar_param.get(pname))

Y
Yu Yang 已提交
387 388 389

def __get_parameter_in_gradient_machine__(gradient_machine, name):
    """
Y
Yu Yang 已提交
390

Y
Yu Yang 已提交
391 392 393 394 395 396 397 398
    :param gradient_machine:
    :type gradient_machine: api.GradientMachine
    :param name:
    :return:
    :rtype: api.Parameter
    """
    params = filter(lambda p: p.getName() == name,
                    gradient_machine.getParameters())
Y
Yu Yang 已提交
399

Y
Yu Yang 已提交
400 401 402 403 404 405
    if len(params) == 0:
        raise ValueError("No such parameter")
    elif len(params) > 1:
        raise ValueError("Unexpected branch")
    else:
        return params[0]
Y
Yu Yang 已提交
406 407


Y
Yu Yang 已提交
408
def __copy_parameter_to_gradient_machine__(gradient_machine, name, arr):
Y
Yu Yang 已提交
409
    """
Y
Yu Yang 已提交
410
    Copy a python ndarray into the gradient machine.
Y
Yu Yang 已提交
411

Y
Yu Yang 已提交
412 413 414 415 416
    :param gradient_machine:
    :type gradient_machine: api.GradientMachine
    :param name:
    :param arr:
    :type arr: np.ndarray
Y
Yu Yang 已提交
417
    :return:
Y
Yu Yang 已提交
418
    :rtype: api.Parameter
Y
Yu Yang 已提交
419
    """
Y
Yu Yang 已提交
420
    import py_paddle.swig_paddle as api
Y
Yu Yang 已提交
421 422 423 424
    param = __get_parameter_in_gradient_machine__(gradient_machine, name)
    vec = param.getBuf(api.PARAMETER_VALUE)
    assert isinstance(vec, api.Vector)
    vec.copyFromNumpyArray(arr.flatten())