parameters.py 14.0 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):
325 326 327 328 329 330 331 332 333 334 335
        """
        Save parameters to a tar file.

        WARNING: Do not use this function to save parameters directly unless you
            know exactly what you are doing. `paddle.v2.trainer.SGD.save_parameter_to_tar(f)`
            should be used instead.

        :param f:
        :type f: file
        :return:
        """
Y
Yu Yang 已提交
336 337 338 339 340 341 342 343 344
        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 已提交
345 346 347 348 349 350 351 352 353 354
            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 已提交
355 356 357 358 359 360 361 362 363 364 365 366
        """
        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 已提交
367 368 369 370 371 372 373 374 375 376 377 378 379 380
        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 已提交
381

382
    def init_from_tar(self, f):
D
dangqingqing 已提交
383 384 385 386 387 388 389 390 391
        """
        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.
        """

392
        tar_param = Parameters.from_tar(f)
393 394 395 396
        for pname in tar_param.names():
            if pname in self.names():
                self.set(pname, tar_param.get(pname))

Y
Yu Yang 已提交
397 398 399

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

Y
Yu Yang 已提交
401 402 403 404 405 406 407 408
    :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 已提交
409

Y
Yu Yang 已提交
410 411 412 413 414 415
    if len(params) == 0:
        raise ValueError("No such parameter")
    elif len(params) > 1:
        raise ValueError("Unexpected branch")
    else:
        return params[0]
Y
Yu Yang 已提交
416 417


Y
Yu Yang 已提交
418
def __copy_parameter_to_gradient_machine__(gradient_machine, name, arr):
Y
Yu Yang 已提交
419
    """
Y
Yu Yang 已提交
420
    Copy a python ndarray into the gradient machine.
Y
Yu Yang 已提交
421

Y
Yu Yang 已提交
422 423 424 425 426
    :param gradient_machine:
    :type gradient_machine: api.GradientMachine
    :param name:
    :param arr:
    :type arr: np.ndarray
Y
Yu Yang 已提交
427
    :return:
Y
Yu Yang 已提交
428
    :rtype: api.Parameter
Y
Yu Yang 已提交
429
    """
Y
Yu Yang 已提交
430
    import py_paddle.swig_paddle as api
Y
Yu Yang 已提交
431 432 433 434
    param = __get_parameter_in_gradient_machine__(gradient_machine, name)
    vec = param.getBuf(api.PARAMETER_VALUE)
    assert isinstance(vec, api.Vector)
    vec.copyFromNumpyArray(arr.flatten())