到目前为止我们所使用的全部是命令式编程,事实上我们用的大部分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))