parameters.py 12.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
    """
Y
Yu Yang 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    Parameters is a dictionary contains Paddle's parameter. The key of
    Parameters is the name of parameter. The value of Parameters is a plain
    :code:`numpy.ndarry` .

    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 已提交
63 64
    """

Y
Yu Yang 已提交
65
    def __init__(self):
C
caoying03 已提交
66
        self.__param_conf__ = OrderedDict()
Y
Yu Yang 已提交
67
        self.__gradient_machines__ = []
68
        self.__tmp_params__ = dict()
Y
Yu Yang 已提交
69

Y
Yu Yang 已提交
70 71 72 73 74 75 76 77 78 79
    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 已提交
80 81 82 83 84 85 86 87 88
        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 已提交
89 90
        """
        keys are the names of each parameter.
Y
Yu Yang 已提交
91

Y
Yu Yang 已提交
92 93 94
        :return: list of parameter name
        :rtype: list
        """
Y
Yu Yang 已提交
95 96 97
        return self.__param_conf__.keys()

    def names(self):
Y
Yu Yang 已提交
98 99
        """
        names of each parameter.
Y
Yu Yang 已提交
100

Y
Yu Yang 已提交
101 102 103
        :return: list of parameter name
        :rtype: list
        """
Y
Yu Yang 已提交
104 105 106
        return self.keys()

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

Y
Yu Yang 已提交
110 111 112 113
        :param key: Parameter name
        :type key: basestring
        :return: True if contains such key
        """
Y
Yu Yang 已提交
114 115
        return key in self.__param_conf__.keys()

Y
Yu Yang 已提交
116
    def __iter__(self):
Y
Yu Yang 已提交
117 118 119 120 121 122 123 124 125 126 127 128
        """
        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 已提交
129 130
        return iter(self.__param_conf__)

131
    def __getter_inner(self, key, param_type):
Y
Yu Yang 已提交
132
        import py_paddle.swig_paddle as api
Y
Yu Yang 已提交
133 134 135 136
        shape = self.get_shape(key)

        if len(self.__gradient_machines__) == 0:
            # create new parameter in python numpy.
137 138 139 140
            if key in self.__tmp_params__:
                return self.__tmp_params__[key]
            else:
                return np.ndarray(shape=shape, dtype=np.float32)
Y
Yu Yang 已提交
141 142 143 144 145 146
        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)
147
                val = param.getBuf(param_type)
Y
Yu Yang 已提交
148
                assert isinstance(val, api.Vector)
Y
Yu Yang 已提交
149 150
                val = val.copyToNumpyArray()
                return val
Y
Yu Yang 已提交
151 152 153 154
                # else continue

            raise RuntimeError("Unexpected branch")

155 156 157 158 159 160 161 162 163 164 165 166 167
    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 已提交
168
    def get_shape(self, key):
Y
Yu Yang 已提交
169 170
        """
        get shape of the parameter.
Y
Yu Yang 已提交
171

Y
Yu Yang 已提交
172 173 174 175 176
        :param key: parameter name
        :type key: basestring
        :return: parameter's shape
        :rtype: tuple
        """
Y
Yu Yang 已提交
177 178 179 180 181
        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 已提交
182 183
        dims = conf.dims if conf.dims else (1, conf.size)
        return tuple(map(int, dims))
Y
Yu Yang 已提交
184 185

    def __setitem__(self, key, value):
Y
Yu Yang 已提交
186 187 188 189 190 191 192 193 194 195 196
        """
        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 已提交
197 198 199 200
        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 已提交
201
        if value.shape != shape:
Y
Yu Yang 已提交
202 203 204 205
            raise ValueError("Value shape mismatch, expect %s, should %s" %
                             (shape, value.shape))

        if len(self.__gradient_machines__) == 0:
206
            self.__tmp_params__[key] = value
Y
Yu Yang 已提交
207 208 209 210 211
        else:
            for each_gradient_machine in self.__gradient_machines__:
                __copy_parameter_to_gradient_machine__(each_gradient_machine,
                                                       key, value)

Y
Yu Yang 已提交
212
    def get(self, parameter_name):
Y
Yu Yang 已提交
213 214 215 216 217 218 219 220 221
        """
        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 已提交
222 223
        return self.__getitem__(key=parameter_name)

224 225 226 227 228 229 230 231 232 233 234
    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 已提交
235 236 237
        if self.__param_conf__[key].is_static:
            return np.zeros(self.__param_conf__[key].size, dtype=np.float32)

238 239
        return self.__getter_inner(key, api.PARAMETER_GRADIENT)

Y
Yu Yang 已提交
240
    def set(self, parameter_name, value):
Y
Yu Yang 已提交
241 242
        """
        Set parameter by parameter name & matrix.
Y
Yu Yang 已提交
243

Y
Yu Yang 已提交
244 245 246 247 248 249
        :param parameter_name: parameter name
        :type parameter_name: basestring
        :param value: parameter matrix
        :type value: np.ndarray
        :return: Nothing.
        """
Y
Yu Yang 已提交
250 251
        self.__setitem__(key=parameter_name, value=value)

Y
Yu Yang 已提交
252
    def append_gradient_machine(self, gradient_machine):
Y
Yu Yang 已提交
253 254 255 256 257 258 259 260
        """
        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 已提交
261
        import py_paddle.swig_paddle as api
Y
Yu Yang 已提交
262 263 264 265
        if not isinstance(gradient_machine, api.GradientMachine):
            raise ValueError("gradient_machine should be api.GradientMachine")

        if len(self.__tmp_params__) != 0:
266
            for name, val in self.__tmp_params__.iteritems():
Y
Yu Yang 已提交
267 268 269 270 271 272
                try:
                    __copy_parameter_to_gradient_machine__(gradient_machine,
                                                           name, val)
                except ValueError:
                    # If no such parameter in gradient machine, then don't copy
                    pass
273 274

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

Y
Yu Yang 已提交
276 277 278 279 280 281 282 283 284 285 286 287
    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)
288 289 290 291 292 293 294
        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 已提交
295 296 297 298 299 300 301 302 303 304

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

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

Y
Yu Yang 已提交
308
    def to_tar(self, f):
Y
Yu Yang 已提交
309 310 311 312 313 314 315 316 317
        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 已提交
318 319 320 321 322 323 324 325 326 327
            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 已提交
328 329 330 331 332 333 334 335 336 337 338 339
        """
        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 已提交
340 341 342 343 344 345 346 347 348 349 350 351 352 353
        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 已提交
354

355
    def init_from_tar(self, f):
D
dangqingqing 已提交
356 357 358 359 360 361 362 363 364
        """
        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.
        """

365
        tar_param = Parameters.from_tar(f)
366 367 368 369
        for pname in tar_param.names():
            if pname in self.names():
                self.set(pname, tar_param.get(pname))

Y
Yu Yang 已提交
370 371 372

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

Y
Yu Yang 已提交
374 375 376 377 378 379 380 381
    :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 已提交
382

Y
Yu Yang 已提交
383 384 385 386 387 388
    if len(params) == 0:
        raise ValueError("No such parameter")
    elif len(params) > 1:
        raise ValueError("Unexpected branch")
    else:
        return params[0]
Y
Yu Yang 已提交
389 390


Y
Yu Yang 已提交
391
def __copy_parameter_to_gradient_machine__(gradient_machine, name, arr):
Y
Yu Yang 已提交
392
    """
Y
Yu Yang 已提交
393
    Copy a python ndarray into the gradient machine.
Y
Yu Yang 已提交
394

Y
Yu Yang 已提交
395 396 397 398 399
    :param gradient_machine:
    :type gradient_machine: api.GradientMachine
    :param name:
    :param arr:
    :type arr: np.ndarray
Y
Yu Yang 已提交
400
    :return:
Y
Yu Yang 已提交
401
    :rtype: api.Parameter
Y
Yu Yang 已提交
402
    """
Y
Yu Yang 已提交
403
    import py_paddle.swig_paddle as api
Y
Yu Yang 已提交
404 405 406 407
    param = __get_parameter_in_gradient_machine__(gradient_machine, name)
    vec = param.getBuf(api.PARAMETER_VALUE)
    assert isinstance(vec, api.Vector)
    vec.copyFromNumpyArray(arr.flatten())