parameters.py 13.6 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
    `Parameters` manages all the learnable parameters in a neural network.
47 48 49 50 51 52 53 54 55 56
    It stores parameters' information in an OrderedDict. The key is
    the name of a parameter, and value is a parameter's configuration(in
    protobuf format), such as initialization mean and std, its size, whether it
    is a static parameter, and so on.

    :param __param_conf__: store the configurations of learnable parameters in
        the network in an OrderedDict. Parameter is added one by one into the
        dict by following their created order in the network: parameters of
        the previous layers in a network are careted first. You can visit the
        parameters from bottom to top by iterating over this dict.
C
caoying03 已提交
57 58
    :type __param_conf__: OrderedDict
    :param __gradient_machines__: all of the parameters in a neural network are
59 60
        appended to a PaddlePaddle gradient machine, which is used internally to
        copy parameter values between C++ and Python end.
C
caoying03 已提交
61 62 63 64
    :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 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78

    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 已提交
79 80
    """

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

Y
Yu Yang 已提交
86 87 88 89 90 91 92 93 94 95
    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 已提交
96 97 98 99 100 101 102 103 104
        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 已提交
105 106
        """
        keys are the names of each parameter.
Y
Yu Yang 已提交
107

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

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

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

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

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

Y
Yu Yang 已提交
132
    def __iter__(self):
Y
Yu Yang 已提交
133 134 135 136 137 138 139 140 141 142 143 144
        """
        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 已提交
145 146
        return iter(self.__param_conf__)

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

        if len(self.__gradient_machines__) == 0:
            # create new parameter in python numpy.
153 154 155 156
            if key in self.__tmp_params__:
                return self.__tmp_params__[key]
            else:
                return np.ndarray(shape=shape, dtype=np.float32)
Y
Yu Yang 已提交
157 158 159 160 161 162
        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)
163
                val = param.getBuf(param_type)
Y
Yu Yang 已提交
164
                assert isinstance(val, api.Vector)
Y
Yu Yang 已提交
165 166
                val = val.copyToNumpyArray()
                return val
Y
Yu Yang 已提交
167 168 169 170
                # else continue

            raise RuntimeError("Unexpected branch")

171 172 173 174 175 176 177 178 179 180 181 182 183
    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 已提交
184
    def get_shape(self, key):
Y
Yu Yang 已提交
185 186
        """
        get shape of the parameter.
Y
Yu Yang 已提交
187

Y
Yu Yang 已提交
188 189 190 191 192
        :param key: parameter name
        :type key: basestring
        :return: parameter's shape
        :rtype: tuple
        """
Y
Yu Yang 已提交
193 194 195 196 197
        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 已提交
198 199
        dims = conf.dims if conf.dims else (1, conf.size)
        return tuple(map(int, dims))
Y
Yu Yang 已提交
200 201

    def __setitem__(self, key, value):
Y
Yu Yang 已提交
202 203 204 205 206 207 208 209 210 211 212
        """
        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 已提交
213 214 215 216
        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 已提交
217
        if value.shape != shape:
Y
Yu Yang 已提交
218 219 220 221
            raise ValueError("Value shape mismatch, expect %s, should %s" %
                             (shape, value.shape))

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

Y
Yu Yang 已提交
228
    def get(self, parameter_name):
Y
Yu Yang 已提交
229 230 231 232 233 234 235 236 237
        """
        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 已提交
238 239
        return self.__getitem__(key=parameter_name)

240 241 242 243 244 245 246 247 248 249 250
    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 已提交
251 252 253
        if self.__param_conf__[key].is_static:
            return np.zeros(self.__param_conf__[key].size, dtype=np.float32)

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

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

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

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

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

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

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

Y
Yu Yang 已提交
292 293 294 295 296 297 298 299 300 301 302 303
    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)
304 305 306 307 308 309 310
        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 已提交
311 312 313 314 315 316 317 318 319 320

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

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

Y
Yu Yang 已提交
324
    def to_tar(self, f):
Y
Yu Yang 已提交
325 326 327 328 329 330 331 332 333
        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 已提交
334 335 336 337 338 339 340 341 342 343
            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 已提交
344 345 346 347 348 349 350 351 352 353 354 355
        """
        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 已提交
356 357 358 359 360 361 362 363 364 365 366 367 368 369
        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 已提交
370

371
    def init_from_tar(self, f):
D
dangqingqing 已提交
372 373 374 375 376 377 378 379 380
        """
        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.
        """

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

Y
Yu Yang 已提交
386 387 388

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

Y
Yu Yang 已提交
390 391 392 393 394 395 396 397
    :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 已提交
398

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


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

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