https://www.cnblogs.com/hellcat/p/9084894.html
目录
- 一、符号式编程 
   
- 1、命令式编程和符号式编程
 - 2.MXNet的符号式编程
 
 - 二、惰性计算 
   
- 用同步函数实际计算出结果
 
 - 三、自动并行
 
回到顶部
一、符号式编程
1、命令式编程和符号式编程
命令式:
1 2 3 4 5 6 7 8 9 10def  
         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 
         ) 
         
         
      
     
    
   
  
 
符号式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24def  
         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类
1 2 3 4 5 6 7 8 9 10 11 12 13def  
         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类
1 2 3 4 5 6 7 8 9 10 11 12class  
         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操作)。
二、惰性计算
可以使用不同的前端语言编写MXNet程序,像Python、R、Scala和C++。无论使用何种前端编程语言,MXNet程序的执行主要都发生在C++实现的后端。换句话说,用户写好的前端MXNet程序会传给后端执行计算。后端有自己的线程来不断收集任务,构造、优化并执行计算图。后端优化的方式有很多种,其中包括本章将介绍的惰性计算。
假设我们在前端调用以下四条语句。MXNet后端的线程会分析它们的依赖关系并构建出如下图所示的计算图。
In [3]:
a = nd.ones((1, 2))
b = nd.ones((1, 2))
c = a * b + 2
c
Out[3]:
[[ 3. 3.]]
<NDArray 1x2 @cpu(0)>
在惰性计算中,前端执行前三条语句的时候,仅仅是把任务放进后端的队列里就返回了。当最后一条语句需要打印计算结果时,前端会等待后端线程把c的结果计算完。此设计的一个好处是,这里的Python前端线程不需要做实际计算。因此,无论Python的性能如何,它对整个程序性能的影响会很小。只要C++后端足够高效,那么不管前端语言性能如何,MXNet都可以提供一致的高性能。
用同步函数实际计算出结果
print()
nd数组.wait_to_read()
nd.waitall()
由于asnumpy、asscalar(Python内置、numpy等数据结构并不支持惰性计算)和print函数会触发让前端等待后端计算结果的行为,我们通常把这类函数称作同步函数。
惰性计算会极大提高运行效率,但是会增加内存开销。
回到顶部三、自动并行
在“惰性计算”里我们提到MXNet后端会自动构建计算图。通过计算图,系统可以知道所有计算的依赖关系,并可以选择将没有依赖关系的多个任务并行执行来获得性能的提升。以“惰性计算”一节中的计算图为例。其中a=nd.ones((1,2))和b=nd.ones((1,2))这两步计算之间并没有依赖关系。因此,系统可以选择并行执行它们。
通常一个运算符会用掉一个CPU/GPU上所有计算资源。例如,dot操作符会用到所有CPU(即使是有多个CPU)或单个GPU上所有线程。因此在单CPU/GPU上并行运行多个运算符可能效果并不明显。
MXNet通过自动并行计算提升计算性能,主要经由CPU和GPU的并行以及计算和通讯的并行实现,
- 没有依赖关系的数学计算之间会自动并行
 - 没有依赖关系的数学计算和设备之间的数据拷贝会自动并行
 
