MXNET深度學習框架-26-Hybridize:更快更好地移植

        到目前爲止我們所使用的全部是命令式編程,事實上我們用的大部分Python代碼都是採用的命令式編程,比如下面這個例子:

def add(x,y):
    return x+y
def func_add(A,B,C,D):
    E=add(A,B)
    F=add(C,D)
    G=add(E,F)
    return G
resut=func_add(1,2,3,4)
print(resut)

結果:
在這裏插入圖片描述
        這是我們絕大多數人編程時所採用的慣有方法,然而,這種方法也有它的不足:速度慢。這是因爲它要跟Python的運行環境打交道,如上面的例子所示,一共進行了3次加法,也就是說它要跟Python環境打3次交道,並且,在程序沒有結束前,它會一直保留之前計算的結果,比如例子中的“E”和“F”。

        這裏有一種方式叫做"符號式編程",大部分的深度學習框架如TensorFlow、Theano等都用了這個方式。通常這個方式包含以下三個步驟:
        1)定義計算流程;
        2)編譯成可執行的程序;
        3)給定輸入,調用編譯過的程序。

# 符號式編程
def add_str():
    return ''' # 返回一個str類型的結果
def add(x,y):
    return x+y
    '''
def func_add_str():
    return '''
def func_add(A,B,C,D):
    E=add(A,B)
    F=add(C,D)
    G=add(E,F)
    return G
    '''
def evok_str():
    return add_str()+func_add_str()+'''
resut=func_add(1,2,3,4)
print(resut)
    '''
pro=evok_str()
print(pro)
y=compile(pro,'','exec')
exec(y)

結果:
在這裏插入圖片描述
        從上面的例子可以看到,我們只是返回了三個計算流程,之後我們編譯再執行。在編譯的時候系統能看到整個程序,因此有更多的優化空間。其實,在一些主流的深度學習框架中應用的就是這種方法。

        在mxnet中能夠提升運算速度的方法是Hybridize,下面我們來做一個簡單的測試,定義一個簡單的模型,看看不使用Hybridize和使用Hybridize之後的程序有什麼不同:

def get_net():
    net=gn.nn.HybridSequential()
    with net.name_scope():
        net.add(gn.nn.Dense(128,activation="relu"))
        net.add(gn.nn.Dense(64, activation="relu"))
        net.add(gn.nn.Dense(2))
    return net
net=get_net()
net.initialize()
x=nd.random_normal(shape=(1,512))
# print(net(x)) #不使用Hybridize
print("不使用Hybridize後的結果爲:",net(x))
net.hybridize() #使用Hybridize

print("使用Hybridize後的結果爲:",net(x))

結果:
在這裏插入圖片描述
        我們可以看到,計算結果其實都是一樣的。需要注意的是,只有繼承HybridBlock類的層纔會被優化計算。下面我們來比較一下性能:

def bench(net,x):
    start=time()
    for i in range(1000):
        y=net(x)
    nd.waitall()   # 等待所有計算完成
    return time()-start
net=get_net()
net.initialize()
x=nd.random_normal(shape=(1,512))
print("不使用Hybridize後的運行時間爲:%.4f s"%(bench(net,x)))
net.hybridize()
print("使用Hybridize後的運行時間爲:%.4f s"%(bench(net,x)))

運行結果:
在這裏插入圖片描述
        可以看到,使用了Hybridize之後的程序是沒有使用Hybridize的3倍。

        我們同樣也可以使用一個變量,之後再給變量賦值:

import mxnet.symbol as symbol
k=symbol.var(name="data") # 聲明一個變量
print(net(k))
print(net(k).tojson()) #查看json文件

        當然了,這樣出來的結果也是一個symbol類型的值,因爲我們並沒有給它賦值。而且,使用了Hybridize之後,還可以打印json文件,一般來說,Python只是用來訓練,如果真正要用在產品線上的話,Python很難移植,而使用json文件可能會起到作用。結果:
在這裏插入圖片描述
        雖然Hybridize執行效率高,可移植性也高,但是,它的靈活性比較差,下面演示一個例子來做說明:

class Hybridize_net(gn.nn.HybridBlock):
    def __init__(self, **kwargs):
        super(Hybridize_net, self).__init__(**kwargs)
        self.d1 = gn.nn.Dense(10)
        self.out = gn.nn.Dense(2)

    def hybrid_forward(self, F, x):# 如果使用了Hybridize嗎,F就是一個變量,否則,就是一個ndArray
        print("F:",F)
        print("x:",x)
        x=F.relu(self.d1(x))
        print("hidden:",x)
        return self.out(x)

如果不使用Hybridize的話,F就是一個ndArray:

net=Hybridize_net()
net.initialize()
x=nd.random_normal(shape=(1,3))
print(net(x))

運行結果如下:
在這裏插入圖片描述
如果使用了Hybridize,F就是一個變量(symbol),那麼會有什麼結果?

#使用Hybridize
net.hybridize()
print(net(x))

結果:
在這裏插入圖片描述
        從中可以看到,F是一個symbol,輸入x也變成了symbol(之前是一個ndArray),中間層也變成了symbol。並且再次執行的時候,就只有輸出了(Notebook執行),中間打印的部分全都沒有了,因爲它只執行一次,所以能夠加快速度。當然,它還有一個非常大的弊端:無法Debug!!!,比如,if、for這樣的語句它是無法使用的,所以在編程的時候需要結合實際來選擇是否要使用Hybridize。

本章測試使用代碼:

import matplotlib.pyplot as plt
import mxnet as mx
import mxnet.ndarray as nd
from time import *
from mxnet import image
import mxnet.initializer as init
import mxnet.gluon as gn
import mxnet.symbol as symbol
import mxnet.autograd as ag

# 命令式編程
# def add(x,y):
#     return x+y
# def func_add(A,B,C,D):
#     E=add(A,B)
#     F=add(C,D)
#     G=add(E,F)
#     return G
# resut=func_add(1,2,3,4)
# print(resut)
#
# # 符號式編程
# def add_str():
#     return ''' # 返回一個str類型的結果
# def add(x,y):
#     return x+y
#     '''
# def func_add_str():
#     return '''
# def func_add(A,B,C,D):
#     E=add(A,B)
#     F=add(C,D)
#     G=add(E,F)
#     return G
#     '''
# def evok_str():
#     return add_str()+func_add_str()+'''
# resut=func_add(1,2,3,4)
# print(resut)
#     '''
# pro=evok_str()
# print(pro)
# y=compile(pro,'','exec')
# exec(y)

'''---Hybridize---'''


def get_net():
    net = gn.nn.HybridSequential()
    with net.name_scope():
        net.add(gn.nn.Dense(128, activation="relu"))
        net.add(gn.nn.Dense(64, activation="relu"))
        net.add(gn.nn.Dense(2))
    return net


# net=get_net()
# net.initialize()
# x=nd.random_normal(shape=(1,512))
# # print(net(x)) #不使用Hybridize
# print("不使用Hybridize後的結果爲:",net(x))
# net.hybridize() #使用Hybridize
#
# print("使用Hybridize後的結果爲:",net(x))


# def bench(net,x):
#     start=time()
#     for i in range(1000):
#         y=net(x)
#     nd.waitall()   # 等待所有計算完成
#     return time()-start
# net=get_net()
# net.initialize()
# x=nd.random_normal(shape=(1,512))
#
# print("不使用Hybridize後的運行時間爲:%.4f s"%(bench(net,x)))
# net.hybridize()
# print("使用Hybridize後的運行時間爲:%.4f s"%(bench(net,x)))
#
# # 變量的使用
# k=symbol.var(name="data") # 聲明一個變量
# print(net(k))
# print(net(k).tojson()) #查看json文件

class Hybridize_net(gn.nn.HybridBlock):
    def __init__(self, **kwargs):
        super(Hybridize_net, self).__init__(**kwargs)
        self.d1 = gn.nn.Dense(10)
        self.out = gn.nn.Dense(2)

    def hybrid_forward(self, F, x):# 如果使用了Hybridize,F就是一個變量,否則,就是一個ndArray
        print("F:",F)
        print("x:",x)
        x=F.relu(self.d1(x))
        print("hidden:",x)
        return self.out(x)
# 不使用Hybridize
net=Hybridize_net()
net.initialize()
x=nd.random_normal(shape=(1,3))
print(net(x))
#使用Hybridize
net.hybridize()
print(net(x))

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章