网络参数

Ascend GPU CPU 模型开发

image0image1image2image3

概述

MindSpore提供了网络参数初始化模块,用户可以通过封装算子来调用字符串、Initializer子类或自定义Tensor等方式完成对网络参数进行初始化。本章主要介绍了Parameter的初始化以及属性和方法的使用,同时介绍了ParameterTuple和参数的依赖控制。

Parameter

Parameter是变量张量,代表在训练网络时,需要被更新的参数。

初始化

mindspore.Parameter(default_input, name, requires_grad=True, layerwise_parallel=False)
  • default_input: 初始化一个Parameter对象,传入的数据支持TensorInitializerintfloat四种类型。 Initializer是初始化器,可调用initializer接口生成Initializer对象。当使用init去初始化Tensor时,Tensor仅保存张量的形状和类型,而不保存实际数据,所以不会占用任何内存,可调用init_data接口将Parameter里保存的Tensor转化为数据。

  • name: 可为每个Parameter指定一个名称,便于后续操作和更新。如果在Cell里初始化一个Parameter作为Cell的属性时,建议使用默认值None,否则可能会出现Parameter的name与预期的不一致的情况。

  • requires_grad: 当参数需要被更新时,需要将requires_grad设置为True

  • layerwise_parallel: 当layerwise_parallel(混合并行)配置为True时,参数广播和参数梯度聚合时会过滤掉该参数。

有关分布式并行的相关配置,可以参考文档:https://www.mindspore.cn/docs/programming_guide/zh-CN/r1.5/auto_parallel.html

下例通过三种不同的数据类型构造了Parameter,三个Parameter都需要更新,都不采用layerwise并行。

代码样例如下:

[ ]:
import numpy as np
from mindspore import Tensor, Parameter
from mindspore import dtype as mstype
from mindspore.common.initializer import initializer

x = Parameter(default_input=Tensor(np.arange(2*3).reshape((2, 3))), name="x")
y = Parameter(default_input=initializer('ones', [1, 2, 3], mstype.float32), name='y')
z = Parameter(default_input=2.0, name='z')

print(x, "\n\n", y, "\n\n", z)
 Parameter (name=x, shape=(2, 3), dtype=Int64, requires_grad=True)

 Parameter (name=y, shape=(1, 2, 3), dtype=Float32, requires_grad=True)

 Parameter (name=z, shape=(), dtype=Float32, requires_grad=True)

属性

  • inited_param:返回保存了实际数据的Parameter

  • name:实例化Parameter时,为其指定的名字。

  • sliced:用在自动并行场景下,表示Parameter里保存的数据是否是分片数据。

    如果是,就不再对其进行切分,如果不是,需要根据网络并行策略确认是否对其进行切分。

  • is_initParameter的初始化状态。在GE后端,Parameter需要一个init graph来从主机同步数据到设备侧,该标志表示数据是否已同步到设备。 此标志仅在GE后端起作用,其他后端将被设置为False。

  • layerwise_parallelParameter是否支持layerwise并行。如果支持,参数就不会进行广播和梯度聚合,反之则需要。

  • requires_grad:是否需要计算参数梯度。如果参数需要被训练,则需要计算参数梯度,否则不需要。

  • dataParameter本身。

下例通过Tensor初始化一个Parameter,获取了Parameter的相关属性。如下:

[1]:
import numpy as np

from mindspore import Tensor, Parameter

x = Parameter(default_input=Tensor(np.arange(2*3).reshape((2, 3))), name="x")

print("name: ", x.name, "\n",
      "sliced: ", x.sliced, "\n",
      "is_init: ", x.is_init, "\n",
      "inited_param: ", x.inited_param, "\n",
      "requires_grad: ", x.requires_grad, "\n",
      "layerwise_parallel: ", x.layerwise_parallel, "\n",
      "data: ", x.data)
 name:  x
 sliced:  False
 is_init:  False
 inited_param:  None
 requires_grad:  True
 layerwise_parallel:  False
 data:  Parameter (name=x, shape=(2, 3), dtype=Int64, requires_grad=True)

方法

  • init_data:在网络采用半自动或者全自动并行策略的场景下, 当初始化Parameter传入的数据是Initializer时,可调用该接口将Parameter保存的数据转换为Tensor

  • set_data:设置Parameter保存的数据,支持传入TensorInitializerintfloat进行设置, 将方法的入参slice_shape设置为True时,可改变Parameter的shape,反之,设置的数据shape必须与Parameter原来的shape保持一致。

  • set_param_ps:控制训练参数是否通过Parameter Server进行训练。

  • clone:克隆Parameter,克隆完成后可以给新Parameter指定新的名字。

下例通过Initializer来初始化Tensor,调用了Parameter的相关方法。如下:

[2]:
import numpy as np
from mindspore import Tensor, Parameter
from mindspore import dtype as mstype
from mindspore.common.initializer import initializer

x = Parameter(default_input=initializer('ones', [1, 2, 3], mstype.float32))

print(x)
x_clone = x.clone()
x_clone.name = "x_clone"
print(x_clone)

print(x.init_data())
print(x.set_data(data=Tensor(np.arange(2*3).reshape((1, 2, 3)))))
Parameter (name=Parameter, shape=(1, 2, 3), dtype=Float32, requires_grad=True)
Parameter (name=x_clone, shape=(1, 2, 3), dtype=Float32, requires_grad=True)
Parameter (name=Parameter, shape=(1, 2, 3), dtype=Float32, requires_grad=True)
Parameter (name=Parameter, shape=(1, 2, 3), dtype=Float32, requires_grad=True)

ParameterTuple

继承于tuple,用于保存多个Parameter,通过__new__(cls, iterable)传入一个存放Parameter的迭代器进行构造,提供clone接口进行克隆。

下例构造了一个ParameterTuple对象,并进行了克隆。如下:

[3]:
import numpy as np
from mindspore import Tensor, Parameter, ParameterTuple
from mindspore import dtype as mstype
from mindspore.common.initializer import initializer

x = Parameter(default_input=Tensor(np.arange(2*3).reshape((2, 3))), name="x")
y = Parameter(default_input=initializer('ones', [1, 2, 3], mstype.float32), name='y')
z = Parameter(default_input=2.0, name='z')
params = ParameterTuple((x, y, z))
params_copy = params.clone("params_copy")
print(params, "\n")
print(params_copy)
(Parameter (name=x, shape=(2, 3), dtype=Int64, requires_grad=True), Parameter (name=y, shape=(1, 2, 3), dtype=Float32, requires_grad=True), Parameter (name=z, shape=(), dtype=Float32, requires_grad=True))

(Parameter (name=params_copy.x, shape=(2, 3), dtype=Int64, requires_grad=True), Parameter (name=params_copy.y, shape=(1, 2, 3), dtype=Float32, requires_grad=True), Parameter (name=params_copy.z, shape=(), dtype=Float32, requires_grad=True))

使用封装算子对参数初始化

MindSpore提供了多种参数初始化的方式,并在部分算子中封装了参数初始化的功能。本节将介绍带有参数初始化功能的算子对参数进行初始化的方法,以Conv2d算子为例,分别介绍以字符串,Initializer子类和自定义Tensor等方式对网络中的参数进行初始化,以下代码示例中均以Initializer的子类Normal为例,代码示例中Normal均可替换成Initializer子类中任何一个。

字符串

使用字符串对网络参数进行初始化,字符串的内容需要与Initializer子类的名称保持一致,使用字符串方式进行初始化将使用Initializer子类中的默认参数,例如使用字符串Normal等同于使用Initializer的子类Normal(),代码样例如下:

[ ]:
import numpy as np
import mindspore.nn as nn
from mindspore import Tensor
from mindspore import set_seed

set_seed(1)

input_data = Tensor(np.ones([1, 3, 16, 50], dtype=np.float32))
net = nn.Conv2d(3, 64, 3, weight_init='Normal')
output = net(input_data)
print(output)
[[[[ 3.10382620e-02  4.38603461e-02  4.38603461e-02 ...  4.38603461e-02
     4.38603461e-02  1.38719045e-02]
   [ 3.26051228e-02  3.54298912e-02  3.54298912e-02 ...  3.54298912e-02
     3.54298912e-02 -5.54019120e-03]
   [ 3.26051228e-02  3.54298912e-02  3.54298912e-02 ...  3.54298912e-02
     3.54298912e-02 -5.54019120e-03]
   ...
   [ 3.26051228e-02  3.54298912e-02  3.54298912e-02 ...  3.54298912e-02
     3.54298912e-02 -5.54019120e-03]
   [ 3.26051228e-02  3.54298912e-02  3.54298912e-02 ...  3.54298912e-02
     3.54298912e-02 -5.54019120e-03]
   [ 9.66199022e-03  1.24104535e-02  1.24104535e-02 ...  1.24104535e-02
     1.24104535e-02 -1.38977719e-02]]

  ...

  [[ 3.98553275e-02 -1.35465711e-03 -1.35465711e-03 ... -1.35465711e-03
    -1.35465711e-03 -1.00310734e-02]
   [ 4.38403059e-03 -3.60766202e-02 -3.60766202e-02 ... -3.60766202e-02
    -3.60766202e-02 -2.95619294e-02]
   [ 4.38403059e-03 -3.60766202e-02 -3.60766202e-02 ... -3.60766202e-02
    -3.60766202e-02 -2.95619294e-02]
   ...
   [ 4.38403059e-03 -3.60766202e-02 -3.60766202e-02 ... -3.60766202e-02
    -3.60766202e-02 -2.95619294e-02]
   [ 4.38403059e-03 -3.60766202e-02 -3.60766202e-02 ... -3.60766202e-02
    -3.60766202e-02 -2.95619294e-02]
   [ 1.33139016e-02  6.74417242e-05  6.74417242e-05 ...  6.74417242e-05
     6.74417242e-05 -2.27325838e-02]]]]

Initializer子类

使用Initializer子类对网络参数进行初始化,与使用字符串对参数进行初始化的效果类似,不同的是使用字符串进行参数初始化是使用Initializer子类的默认参数,如要使用Initializer子类中的参数,就必须使用Initializer子类的方式对参数进行初始化,以Normal(0.2)为例,代码样例如下:

[ ]:
import numpy as np
import mindspore.nn as nn
from mindspore import Tensor
from mindspore import set_seed
from mindspore.common.initializer import Normal

set_seed(1)

input_data = Tensor(np.ones([1, 3, 16, 50], dtype=np.float32))
net = nn.Conv2d(3, 64, 3, weight_init=Normal(0.2))
output = net(input_data)
print(output)
[[[[ 6.2076533e-01  8.7720710e-01  8.7720710e-01 ...  8.7720710e-01
     8.7720710e-01  2.7743810e-01]
   [ 6.5210247e-01  7.0859784e-01  7.0859784e-01 ...  7.0859784e-01
     7.0859784e-01 -1.1080378e-01]
   [ 6.5210247e-01  7.0859784e-01  7.0859784e-01 ...  7.0859784e-01
     7.0859784e-01 -1.1080378e-01]
   ...
   [ 6.5210247e-01  7.0859784e-01  7.0859784e-01 ...  7.0859784e-01
     7.0859784e-01 -1.1080378e-01]
   [ 6.5210247e-01  7.0859784e-01  7.0859784e-01 ...  7.0859784e-01
     7.0859784e-01 -1.1080378e-01]
   [ 1.9323981e-01  2.4820906e-01  2.4820906e-01 ...  2.4820906e-01
     2.4820906e-01 -2.7795550e-01]]

  ...

  [[ 7.9710668e-01 -2.7093157e-02 -2.7093157e-02 ... -2.7093157e-02
    -2.7093157e-02 -2.0062150e-01]
   [ 8.7680638e-02 -7.2153252e-01 -7.2153252e-01 ... -7.2153252e-01
    -7.2153252e-01 -5.9123868e-01]
   [ 8.7680638e-02 -7.2153252e-01 -7.2153252e-01 ... -7.2153252e-01
    -7.2153252e-01 -5.9123868e-01]
   ...
   [ 8.7680638e-02 -7.2153252e-01 -7.2153252e-01 ... -7.2153252e-01
    -7.2153252e-01 -5.9123868e-01]
   [ 8.7680638e-02 -7.2153252e-01 -7.2153252e-01 ... -7.2153252e-01
    -7.2153252e-01 -5.9123868e-01]
   [ 2.6627803e-01  1.3488382e-03  1.3488382e-03 ...  1.3488382e-03
     1.3488382e-03 -4.5465171e-01]]]]

自定义的Tensor

除上述两种初始化方法外,当网络要使用MindSpore中没有的数据类型对参数进行初始化,用户可以通过自定义Tensor的方式来对参数进行初始化,代码样例如下:

[ ]:
import numpy as np
import mindspore.nn as nn
from mindspore import Tensor
from mindspore import dtype as mstype

weight = Tensor(np.ones([64, 3, 3, 3]), dtype=mstype.float32)
input_data = Tensor(np.ones([1, 3, 16, 50], dtype=np.float32))
net = nn.Conv2d(3, 64, 3, weight_init=weight)
output = net(input_data)
print(output)
[[[[12. 18. 18. ... 18. 18. 12.]
   [18. 27. 27. ... 27. 27. 18.]
   [18. 27. 27. ... 27. 27. 18.]
   ...
   [18. 27. 27. ... 27. 27. 18.]
   [18. 27. 27. ... 27. 27. 18.]
   [12. 18. 18. ... 18. 18. 12.]]

  ...

  [[12. 18. 18. ... 18. 18. 12.]
   [18. 27. 27. ... 27. 27. 18.]
   [18. 27. 27. ... 27. 27. 18.]
   ...
   [18. 27. 27. ... 27. 27. 18.]
   [18. 27. 27. ... 27. 27. 18.]
   [12. 18. 18. ... 18. 18. 12.]]]]

依赖控制

如果函数的运行结果依赖或影响外部状态,我们认为该函数具有副作用,比如函数会改变外部全局变量、函数的结果依赖全局变量的值。如果操作符会改变输入参数的值或者操作符的输出依赖全局参数的值,我们认为这是带副作用的操作符。

根据内存属性和IO状态,将副作用划分为内存副作用和IO副作用。当前内存副作用主要有Assign、优化器算子等等,IO副作用主要有Print算子。详细可以查看算子定义,内存副作用算子在定义中有side_effect_mem属性,IO副作用算子在定义中有side_effect_io属性。

Depend用于处理依赖项操作。 在大多数情况下,如果操作符有IO副作用或内存副作用,则将根据用户的语义执行它们,不需要另外使用Depend算子来保证执行顺序。在某些情况下,如果两个运算符A和B没有顺序依赖关系,并且A必须在B之前执行,我们建议使用Depend指定它们的执行顺序。使用方法如下:

a = A(x)                --->        a = A(x)
b = B(y)                --->        y = Depend(y, a)
                        --->        b = B(y)

值得说明的是,用于浮点数溢出状态检测的一组特殊算子它们存在隐含副作用,但又不属于IO副作用或内存副作用。此外,使用时还有严格的顺序要求,即:在使用NPUClearFloatStatus算子前需要保证NPUAllocFloatStatus已经执行,使用NPUGetFloatStatus算子前需要保证NPUClearFloatStatus已经执行。因为这些算子使用较少,目前的方案是保持它们的定义为无副作用形式,以Depend确保执行顺序。如下:

[ ]:
import numpy as np
from mindspore.common.tensor import Tensor
from mindspore import ops

npu_alloc_status = ops.NPUAllocFloatStatus()
npu_get_status = ops.NPUGetFloatStatus()
npu_clear_status = ops.NPUClearFloatStatus()
x = Tensor(np.ones([3, 3]).astype(np.float32))
y = Tensor(np.ones([3, 3]).astype(np.float32))
init = npu_alloc_status()
sum_ = ops.Add()(x, y)
product = ops.MatMul()(x, y)
init = ops.depend(init, sum_)
init = ops.depend(init, product)
get_status = npu_get_status(init)
sum_ = ops.depend(sum_, get_status)
product = ops.depend(product, get_status)
out = ops.Add()(sum_, product)
init = ops.depend(init, out)
clear = npu_clear_status(init)
out = ops.depend(out, clear)
print(out)
[[5. 5. 5.]
 [5. 5. 5.]
 [5. 5. 5.]]

具体使用可参考溢出检测逻辑中start_overflow_check函数的实现。