到目前爲止我們所使用的全部是命令式編程,事實上我們用的大部分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))