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(), '\n')
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'])

 自定義初始化方法

有時候我們需要的初始化方法並沒有在init模塊中提供。這時,可以實現一個Initializer類的子類,從而能夠像使用其他初始化方法那樣使用它。通常,我們只需要實現_init_weight這個函數,並將其傳入的NDArray修改成初始化的結果。在下面的例子里,我們令權重有一半概率初始化為0,有另一半概率初始化為[-10,-5]和[5,10]兩個區間里均勻分布的隨機數。

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

net.initialize(MyInit(), force_reinit=True)
net[0].weight.data()[0]

此外,我們還可以通過Parameter類的set_data函數來直接改寫模型參數。例如,在下例中我們將隱藏層參數在現有的基礎上加1。

net[0].weight.set_data(net[0].weight.data() + 1)
net[0].weight.data()[0]

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM