parameters.py 12.3 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
X
xuwei06 已提交
17
import paddle.trainer.config_parser as cp
Y
Yu Yang 已提交
18 19 20
import struct
import tarfile
import cStringIO
Q
qiaolongfei 已提交
21
from topology import Topology
Q
qiaolongfei 已提交
22

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


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

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


Y
Yu Yang 已提交
43
class Parameters(object):
Y
Yu Yang 已提交
44
    """
Y
Yu Yang 已提交
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
    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 已提交
62 63
    """

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

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

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

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

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

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

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

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

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

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

            raise RuntimeError("Unexpected branch")

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

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

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

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

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

223 224 225 226 227 228 229 230 231 232 233 234 235
    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
        return self.__getter_inner(key, api.PARAMETER_GRADIENT)

Y
Yu Yang 已提交
236
    def set(self, parameter_name, value):
Y
Yu Yang 已提交
237 238
        """
        Set parameter by parameter name & matrix.
Y
Yu Yang 已提交
239

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

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

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

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

Y
Yu Yang 已提交
272 273 274 275 276 277 278 279 280 281 282 283
    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)
284
        f.write(param.tostring())
Y
Yu Yang 已提交
285 286 287 288 289 290 291 292 293 294

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

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

Y
Yu Yang 已提交
298
    def to_tar(self, f):
Y
Yu Yang 已提交
299 300 301 302 303 304 305 306 307
        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 已提交
308 309 310 311 312 313 314 315 316 317
            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 已提交
318 319 320 321 322 323 324 325 326 327 328 329
        """
        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 已提交
330 331 332 333 334 335 336 337 338 339 340 341 342 343
        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 已提交
344

345
    def init_from_tar(self, f):
D
dangqingqing 已提交
346 347 348 349 350 351 352 353 354
        """
        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.
        """

355
        tar_param = Parameters.from_tar(f)
356 357 358 359
        for pname in tar_param.names():
            if pname in self.names():
                self.set(pname, tar_param.get(pname))

Y
Yu Yang 已提交
360 361 362

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

Y
Yu Yang 已提交
364 365 366 367 368 369 370 371
    :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 已提交
372

Y
Yu Yang 已提交
373 374 375 376 377 378
    if len(params) == 0:
        raise ValueError("No such parameter")
    elif len(params) > 1:
        raise ValueError("Unexpected branch")
    else:
        return params[0]
Y
Yu Yang 已提交
379 380


Y
Yu Yang 已提交
381
def __copy_parameter_to_gradient_machine__(gradient_machine, name, arr):
Y
Yu Yang 已提交
382
    """
Y
Yu Yang 已提交
383
    Copy a python ndarray into the gradient machine.
Y
Yu Yang 已提交
384

Y
Yu Yang 已提交
385 386 387 388 389
    :param gradient_machine:
    :type gradient_machine: api.GradientMachine
    :param name:
    :param arr:
    :type arr: np.ndarray
Y
Yu Yang 已提交
390
    :return:
Y
Yu Yang 已提交
391
    :rtype: api.Parameter
Y
Yu Yang 已提交
392
    """
Y
Yu Yang 已提交
393
    import py_paddle.swig_paddle as api
Y
Yu Yang 已提交
394 395 396 397
    param = __get_parameter_in_gradient_machine__(gradient_machine, name)
    vec = param.getBuf(api.PARAMETER_VALUE)
    assert isinstance(vec, api.Vector)
    vec.copyFromNumpyArray(arr.flatten())