zoukankan      html  css  js  c++  java
  • Mxnet基础知识(二)

    1 混合式编程

      深度学习框架中,pytorch采用命令式编程,tensorflow采用符号式编程。mxnet的gluon则尝试将命令式编程和符号式编程结合。

    1.1 符号式编程和命令式编程

      符号式编程更加灵活,便于理解和调试;命令式编程能对代码进行优化,执行起来效率更高,如下所示:

      命令式编程:代码会根据执行顺序,逐行执行

    #命令式编程
    
    def add(a, b):
        return a + b
    
    def fancy_func(a, b, c, d):
        e = add(a, b)
        f = add(c, d)
        g = add(e, f)
        return g
    
    fancy_func(1, 2, 3, 4)

      符号式编程:下面代码会通过字符串的形式传给compile,compile能看到所有的代码,能对代码结构和内存进行优化,加快代码执行效率

    #符号式编程
    
    def add_str():
        return '''
    def add(a, b):
        return a + b
    '''
    
    def fancy_func_str():
        return '''
    def fancy_func(a, b, c, d):
        e = add(a, b)
        f = add(c, d)
        g = add(e, f)
        return g
    '''
    
    def evoke_str():
        return add_str() + fancy_func_str() + '''
    print(fancy_func(1, 2, 3, 4))
    '''
    
    prog = evoke_str()
    print(prog)
    y = compile(prog, '', 'exec')
    exec(y)

      mxnet构建网络时除了nn.Block和nn.Sequential外,还有nn.HybridBlock和nn.HybridSequential, 实现在构建时通过命令式编程方式,代码执行时转变成符号式编程。HybridBlock和HybridSequential构建的网络net,通过net.hybride()可以将网络转变成符号网络图(symbolic graph),对代码结构进行优化,而且mxnet会缓存符号图,随后的前向传递中重复使用符号图。

    #coding:utf-8
    from mxnet.gluon import nn
    from mxnet import nd
    
    class HybridNet(nn.HybridBlock):
        def __init__(self, **kwargs):
            super(HybridNet, self).__init__(**kwargs)
            self.hidden = nn.Dense(10)
            self.output = nn.Dense(2)
    
        def hybrid_forward(self, F, x):
            print('F: ', F)
            print('x: ', x)
            x = F.relu(self.hidden(x))
            print('hidden: ', x)
            return self.output(x)
    
    #按原始命令式编程方程,逐行执行
    net = HybridNet()
    net.initialize()
    x = nd.random.normal(shape=(1, 4))
    net(x)
    
    #net.hybridize()会对代码结构进行优化,转变成符号式编程
    net.hybridize()
    net(x)
    
    #再次执行时,不会打印代码中的print部分,这是因为hybride后,构建成符号式代码网络,mxnet会缓存符号图,直接执行符号图,不会再去调用python原始代码
    net(x)

      另外,继承自HybridBlock的网络需要实现的是hybrid_forward()相比于forward()多了一个参数F,F会根据输入的x类型选择执行,即x若为mxnet.ndarry,则F调用ndarry的方法;若x若为mxnet.symbol,则调用symbol的方法。 

    2. 延迟初始化

      在构建网络时,mxnet支持不指明参数的输入尺寸,只需指明参数的输出尺寸。这是通过延迟初始化实现

    from mxnet import init, nd
    from mxnet.gluon import nn
    
    
    def getnet():
        net = nn.Sequential()
        net.add(nn.Dense(256, activation='relu'))
        net.add(nn.Dense(10))
        return net
    
    #网络参数未初始化,无具体值
    net = getnet()
    print(1, net.collect_params())   #print(1, net[0].weight.data())
    
    #网络参数未初始化,无具体值
    net.initialize()
    print(2, net.collect_params())  #print(2, net[0].weight.data())
    
    #根据输入x的尺寸,网络推断出各层参数的尺寸,然后进行初始化
    x = nd.random.uniform(shape=(2, 30))
    net(x)
    print(3, net.collect_params())
    print(3, net[0].weight.data())

    #第二次执行时,不会再进行初始化
    net(x)

      init提供了许多初始化方法,如下:

    init.Zero()               #初始化为常数0
    init.One()                 #初始化为常数1
    init.Constant(value=0.05)  #初始化为常数0.05
    init.Orthogonal()          #初始化为正交矩阵
    init.Uniform(scale=0.07)  #(-0.07, 0.07)之间的随机分布
    init.Normal(sigma=0.01)  #均值为0, 标准差为0.01的正态分布
    init.Xavier(magnitude=3)  # magnitude初始化, 适合tanh
    init.MSRAPrelu(slope=0.25)  #凯明初始化,适合relu

      自定义初始化

    #第一层和第二层采用不同的方法进行初始化,
    # force_reinit:无论网络是否初始化,都重新初始化
    net[0].weight.initialize(init=init.Xavier(), force_reinit=True)
    net[1].initialize(init=init.Constant(42), force_reinit=True)
    #自定义初始化,需要继承init.Initializer, 并实现 _init_weight
    class MyInit(init.Initializer):
        def _init_weight(self, name, data):
            print('Init', name, data.shape)
            data[:] = nd.random.uniform(low=-10, high=10, shape=data.shape)
            data *= data.abs() >= 5   # 绝对值小于5的赋值为0, 大于等于5的保持不变
    
    net.initialize(MyInit(), force_reinit=True)
    net[0].weight.data()[0]

    3. 参数和模块命名

      mxnet网络中的parameter和block都有命名(prefix), parameter的名字由用户指定,block的名字由用户或mxnet自动创建

    mydense = nn.Dense(100, prefix="mydense_")
    print(mydense.prefix)  #mydense_
    print(mydense.collect_params())    #mydense_weight, mydense_bias
     
    dense0 = nn.Dense(100)
    print(dense0.prefix)      #dense0_
    print(dense0.collect_params())  #dense0_weight, dense0_bias
    
    dense1 = nn.Dense(100)     
    print(dense1.prefix)   #dense1_
    print(dense1.collect_params())  #dense1_weight, dense1_bias

      每一个block都有一个name_scope(), 在其上下文中创建的子block,会采用其名字作为前缀, 注意下面model0和model1的名字差别

    from mxnet import gluon
    import mxnet as mx
    
    class Model(gluon.Block):
        def __init__(self, **kwargs):
            super(Model, self).__init__(**kwargs)
            with self.name_scope():
                self.dense0 = gluon.nn.Dense(20)
                self.dense1 = gluon.nn.Dense(20)
                self.mydense = gluon.nn.Dense(20, prefix='mydense_')
    
        def forward(self, x):
            x = mx.nd.relu(self.dense0(x))
            x = mx.nd.relu(self.dense1(x))
            return mx.nd.relu(self.mydense(x))
    
    model0 = Model()
    model0.initialize()
    model0(mx.nd.zeros((1, 20)))
    print(model0.prefix)         #model0_
    print(model0.dense0.prefix)  #model0_dense0_
    print(model0.dense1.prefix)  #model0_dense1_
    print(model0.mydense.prefix) #model0_mydense_
    
    
    model1 = Model()
    model1.initialize()
    model1(mx.nd.zeros((1, 20)))
    print(model1.prefix)          #model1_
    print(model1.dense0.prefix)   #model1_dense0_
    print(model1.dense1.prefix)   #model1_dense1_
    print(model1.mydense.prefix)  #model1_mydense_

      不同的命名,其保存的参数名字也会有差别,在保存和加载模型参数时会引起错误,如下所示:

    #如下方式保存和加载:model0保存的参数,model1加载会报错
    model0.collect_params().save('model.params')
    try:
        model1.collect_params().load('model.params', mx.cpu())
    except Exception as e:
        print(e)
    print(model0.collect_params(), '
    ')
    print(model1.collect_params())
    
    
    #如下方式保存和加载:model0保存的参数,model1加载不会报错
    model0.save_parameters('model.params')
    model1.load_parameters('model.params')
    print(mx.nd.load('model.params').keys())

          在加载预训练的模型,进行finetune时,注意命名空间, 如下所示:

    #加载预训练模型,最后一层为1000类别的分类器
    alexnet = gluon.model_zoo.vision.alexnet(pretrained=True)
    print(alexnet.output)
    print(alexnet.output.prefix)
    
    #修改最后一层结构为 100类别的分类器,进行finetune
    with alexnet.name_scope():
        alexnet.output = gluon.nn.Dense(100)
    alexnet.output.initialize()
    print(alexnet.output)

       Sequential创建的net获取参数

    from mxnet import init, nd
    from mxnet.gluon import nn
    
    
    net = nn.Sequential()
    net.add(nn.Dense(256, activation='relu'))
    net.add(nn.Dense(10))
    net.initialize()  # Use the default initialization method
    
    x = nd.random.uniform(shape=(2, 20))
    net(x)            # Forward computation
    
    print(net[0].params)
    print(net[1].params)
    
    #通过属性获取
    print(net[1].bias)
    print(net[1].bias.data())
    print(net[0].weight.grad())
    #通过字典方式获取
    print(net[0].params['dense0_weight'])
    print(net[0].params['dense0_weight'].data())
    #获取所有参数
    print(net.collect_params())
    print(net[0].collect_params())
    net.collect_params()['dense1_bias'].data()
    #正则匹配
    print(net.collect_params('.*weight'))  
    print(net.collect_params('dense0.*'))

      Block创建网络获取参数

    from mxnet import gluon
    import mxnet as mx
    
    class Model(gluon.Block):
        def __init__(self, **kwargs):
            super(Model, self).__init__(**kwargs)
            with self.name_scope():
                self.dense0 = gluon.nn.Dense(20)
                self.dense1 = gluon.nn.Dense(20)
                self.mydense = gluon.nn.Dense(20, prefix='mydense_')
    
        def forward(self, x):
            x = mx.nd.relu(self.dense0(x))
            x = mx.nd.relu(self.dense1(x))
            return mx.nd.relu(self.mydense(x))
    
    model0 = Model()
    model0.initialize()
    model0(mx.nd.zeros((1, 20)))
    
    #通过有序字典_children
    print(model0._children)
    print(model0._children['dense0'].weight._data)
    print(model0._children['dense0'].bias._data)
    
    #通过收集所有参数
    print(model0.collect_params()['model0_dense0_weight']._data)
    print(model0.collect_params()['model0_dense0_bias']._data)

         Parameter和ParameterDict

      gluon.Parameter类能够创建网络中的参数,gluon.ParameterDict类是字典,建立了parameter name和parameter实例之间的映射,通过ParameterDict也可以创建parameter.

    Parameter的使用

    class MyDense(nn.Block):
    
        def __init__(self, units, in_units, **kwargs):
            # units: the number of outputs in this layer
            # in_units: the number of inputs in this layer
    
            super(MyDense, self).__init__(**kwargs)
            self.weight = gluon.Parameter('weight', shape=(in_units, units))  #创建名为weight的参数
            self.bias = gluon.Parameter('bias', shape=(units,))    #创建名为bias的参数
    
        def forward(self, x):
            linear = nd.dot(x, self.weight.data()) + self.bias.data()
            return nd.relu(linear)
    
    net
    = nn.Sequential() net.add(MyDense(units=8, in_units=64), MyDense(units=1, in_units=8)) #初始化参数 for block in net: if hasattr(block, "weight"): block.weight.initialize() if hasattr(block, "bias"): block.bias.initialize() print(net(nd.random.uniform(shape=(2, 64)))) print(net)

    ParameterDict使用

    #创建一个parameterdict,包含一个名为param2的parameter
    params
    = gluon.ParameterDict() params.get('param2', shape=(2, 3)) print(params) print(params.keys()) print(params['param2'])
  • 相关阅读:
    第四周进度条
    单元测试
    第四周开发日志(psp)
    软件工程个人作业03
    第四周课堂作业——单元测试
    进度条 第三周
    开发日志
    软件工程个人作业2
    《构建之法》阅读笔记01
    第一二周进度条
  • 原文地址:https://www.cnblogs.com/silence-cho/p/12953471.html
Copyright © 2011-2022 走看看