『MXNet』第五弹_Gluon性能提升

一、符号式编程

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)

 符号式:

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)

以上定义的三个函数都只是返回计算流程。最后,我们编译完整的计算流程并运行。

由于在编译时系统能够完整地看到整个程序,因此有更多空间优化计算。例如,编译的时候可以将程序改写成print((1 + 2) + (3 + 4)),甚至直接改写成print(10)。这样不仅减少了函数调用,还节省了内存。

2.MXNet的符号式编程

Sequential类 -> HybridSequential类

Block类 -> HybridBlock

使用上面两个基于Hybrid的类构建的网络实例会具有.hybridize()方法,进行.hybridize()声明之后网络的第一次运行会生成编译好的C++代码,之后再运行网络实例不会运行python代码,而回转向C++代码,也就是"静态图",同样的,MXNet的静态结构决定了其对python的动态控制流程不支持(同TensorFlow),但是效率大大提升。

HybridSequential类

def get_net():
    net = nn.HybridSequential()
    net.add(
        nn.Dense(256, activation="relu"),
        nn.Dense(128, activation="relu"),
        nn.Dense(2)
    )
    net.initialize()
    return net

x = nd.random.normal(shape=(1, 512))
net = get_net()
net(x)

我们可以通过调用hybridize函数来编译和优化HybridSequential实例中串联的层的计算。模型的计算结果不变。

In [5]:
net.hybridize()
net(x) Out[5]:
[[ 0.08827581  0.00505182]]
<NDArray 1x2 @cpu(0)>

需要注意的是,只有继承HybridBlock的层才会被优化。例如,HybridSequential类和Gluon提供的Dense类都是HybridBlock的子类,它们都会被优化计算。如果一个层只是继承自Block而不是HybridBlock类,那么它将不会被优化。我们接下会讨论如何使用HybridBlock类。

HybridBlock类

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)

在继承HybridBlock类时,我们需要在hybrid_forward函数中添加额外的输入F。我们知道,MXNet既有基于命令式编程的NDArray类,又有基于符号式编程的Symbol类。由于这两个类的函数基本一致,MXNet会根据输入来决定F使用NDArray或Symbol。

In [12]:
net.hybridize() net(x)
F:  <module 'mxnet.symbol' from '/var/lib/jenkins/miniconda3/envs/gluon_zh_docs/lib/python3.6/site-packages/mxnet/symbol/__init__.py'>
x:  <Symbol data>
hidden:  <Symbol hybridnet0_relu0>
Out[12]:
[[ 0.00370749  0.00134991]]
<NDArray 1x2 @cpu(0)>

可以看到,F变成了Symbol。而且,虽然输入数据还是NDArray,但hybrid_forward函数里,相同输入和中间输出全部变成了Symbol。

再运行一次看看。

In [13]:
net(x)
Out[13]:
[[ 0.00370749  0.00134991]]
<NDArray 1x2 @cpu(0)>

可以看到hybrid_forward函数里定义的三行打印语句都没有打印任何东西。这是因为上一次在调用hybridize函数后运行net(x)的时候,符号式程序已经得到。之后再运行net(x)的时候MXNet将不再访问Python代码,而是直接在C++后端执行符号式程序。这也是调用hybridize后模型计算性能会提升的一个原因。但它可能的问题是我们损失了写程序的灵活性。在上面这个例子中,如果我们希望使用那三行打印语句调试代码,执行符号式程序时会跳过它们无法打印。

此外,对于少数Symbol不支持的函数,例如asnumpy,我们是无法在hybrid_forward函数中使用并在调用hybridize函数后进行模型计算的(mxnet.sym类即为Symbol类,支持大部分Ndarray操作)。

二、惰性计算

 Python内置、numpy等数据结构并不支持

猜你喜欢

转载自www.cnblogs.com/hellcat/p/9084894.html
今日推荐