都9102年了还不懂动态图吗?一文带你了解飞桨动态图

导读:飞桨PaddlePaddle致力于让深度学习技术的创新与应用更简单。飞桨核心框架已提供了动态图(DyGraph)相关的API和文档,并且还附有Language model、Sentiment Classification、OCR、ResNet等模型的动态图版本官方实现。飞桨目前兼具了动态图和静态图的优势,同时具备灵活性和高效性。 


飞桨动态图&静态图整体结构如下:

1. 动态图与静态图

目前深度学习框架主要有声明式编程和命令式编程两种编程方式。声明式编程,代码先描述要做的事情但不立即执行,对深度学习任务建模,需要事先定义神经网络的结构,然后再执行整个图结构,这一般称为静态图模式。而命令式编程对应的动态图模式,代码直接返回运算的结果,神经网络结构的定义和执行同步。通常来说,静态图模式能够对整体性做编译优化,更有利于性能的提升,而动态图则非常便于用户对程序进行调试。

2. 飞桨动态图的三大特色

飞桨的DyGraph模式是一种动态的图执行机制。与静态计算图的执行机制不同,DyGraph模式下的操作可以立即获得执行结果,而不必等待计算图全部构建完成。这样可以让开发者更加直观地构建深度学习任务并进行模型的调试,同时还减少了大量用于构建静态计算图的代码,使得编写、调试网络的过程变得非常便捷。

飞桨DyGraph动态图模式,主要有三大特色:

  • 灵活便捷的代码书写方式:能够使用Python的控制流(for,if…else..等)进行编程。

  • 便捷的调试功能:直接使用Python的打印方法即时打印所需要的结果,从而检查正在运行的模型结果便于调试。

  • 和静态执行图通用的模型代码:对于没有使用Python控制流的网络,动态图的代码可以直接在静态图模式下执行,提升执行的效率。

3. 飞桨动态图与静态图的直观对比

让我们通过一个实际例子,直观地感受一下动态图与静态图在使用过程中的差异。

想要实现如下的功能:

(1)  如果inp1各元素之和小于inp2各元素之和,那么执行inp1与 inp2各元素对应相加。

(2)  如果inp1各元素之和大于等于inp2各元素之和,那么执行inp1与 inp2各元素对应相减。

如果使用飞桨动态图来实现的话,代码如下:

import paddle.fluid asfluid
import numpy as np
inp1 = np.random.rand(4, 3, 3)
inp2 = np.random.rand(4, 3, 3)
# dynamic graph
with fluid.dygraph.guard():    
if np.sum(inp1) <np.sum(inp2):       
 x =fluid.layers.elementwise_add(inp1, inp2)    else:        
x =fluid.layers.elementwise_sub(inp1, inp2)    dygraph_result = x.numpy()


在飞桨动态图的模式下,可以灵活复用(if…else…)等Python控制流操作,关键代码只需要短短6行,非常简单。

 

而如果换用静态图方式来实现的话,代码可就复杂多了。具体如下:

import paddle.fluid asfluid
import numpy as np
inp1 = np.random.rand(4, 3, 3)
inp2 = np.random.rand(4, 3, 3)
# static graph
with new_program_scope():    
inp_data1 =fluid.layers.data(name='inp1', shape=[3, 3], dtype=np.float32)    
inp_data2 =fluid.layers.data(name='inp2', shape=[3, 3], dtype=np.float32)     
a=fluid.layers.expand(fluid.layers.reshape(fluid.layers.reduce_sum(inp_data1),[1, 1]), [4, 1])    b=fluid.layers.expand(fluid.layers.reshape(fluid.layers.reduce_sum(inp_data2),[1, 1]), [4, 1])  
  cond =fluid.layers.less_than(x=a, y=b)
    
 ie =fluid.layers.IfElse(cond) 
   with ie.true_block():    
    d1 =ie.input(inp_data1)      
  d2 =ie.input(inp_data2)     
   d3 =fluid.layers.elementwise_add(d1, d2)       
 ie.output(d3)   
  with ie.false_block():      
  d1 =ie.input(inp_data1) 
       d2 =ie.input(inp_data2)    
    d3 =fluid.layers.elementwise_sub(d1, d2)     
   ie.output(d3)    out = ie()    
 exe =fluid.Executor(fluid.CPUPlace() if not core.is_compiled_with_cuda() elsefluid.CUDAPlace(0))   
 static_result =exe.run(fluid.default_main_program(),feed={'inp1': inp1,'inp2':inp2},fetch_list=out)[0]

怎么样?感受到差异了吗?

直观一点,直接看代码行数。

关键代码部分,静态图方式的代码行数有20行,而动态图方式仅需要短短的6行代码。代码量减少到1/3,逻辑复杂程度也大大简化。

这就是飞桨动态图在Python控制流操作复用和代码简洁性方面的优势。

除此之外,飞桨动态图还提供了非常便捷的调试功能,直接使用Python的打印方法,就可以即时打印出所需要的结果,从而检查正在运行的模型结果,非常方便调试。

4. 飞桨动态图的基本用法

飞桨动态图具有如此多的优势,下面讲述最基本的一些用法。

(1)  动态图与静态图的最大区别是采用了命令式的编程方式,任务不用在区分组网阶段和执行阶段。代码运行完成之后,可以立马获取结果。由于采用与我们书写大部分Python和c++的方式是一致的命令式编程方式,程序的编写和调试会非常的容易。

(2)  同时动态图能够使用Python的控制流,例如for,if else, switch等,对于rnn等任务的支持更方便。

(3)  动态图能够与numpy更好的交互。

使用飞桨动态图,首先需要将PaddlePaddle升级到最新的1.5.1版本,使用以下命令即可。

pip install -q --upgrade paddlepaddle==1.5.1import paddle.fluid as fluidwith fluid.dygraph.guard():

这样就可以在fluid.dygraph.guard()上下文环境中使用动态图DyGraph的模式运行网络了。DyGraph将改变以往静态图的执行方式,开始运行之后会立即执行,并且将计算结果返回给Python。

Dygraph非常适合和Numpy一起使用,使用fluid.dygraph.to_variable(x)将会将Numpy的ndarray转换为fluid.Variable,而使用fluid.Variable.numpy()将可以把任意时刻获取到的计算结果转换为Numpy ndarray,举例如下:

import paddle.fluid asfluidimport numpy as npx = np.ones([10, 2, 2], np.float32) with fluid.dygraph.guard():     inputs = []     seq_len = x.shape[0]     for i in range(seq_len):        inputs.append(fluid.dygraph.to_variable(x[i]))     ret =fluid.layers.sums(inputs)     print(ret.numpy())  

得到输出:

   [[10. 10.]   [10. 10.]]           

以上代码根据输入x的第0维的长度、将x拆分为多个ndarray的输入,执行了一个sum操作之后,可以直接将运行的结果打印出来。然后通过调用reduce_sum后使用Variable.backward()方法执行反向,使用Variable.gradient()方法即可获得反向网络执行完成后的梯度值的ndarray形式:

    loss =fluid.layers.reduce_sum(ret)    loss.backward()    print(loss.gradient())

得到输出 :

   [1.]


 5. 飞桨动态图的项目实战


下面以“手写数字识别”为例讲解一个动态图实战案例,手写体识别是一个非常经典的图像识别任务,任务中的图片如下图所示,根据一个28 * 28像素的图像,识别图片中的数字。


MNIST示例代码地址:

https://github.com/PaddlePaddle/models/tree/develop/dygraph/mnist



介绍网络训练的基本结构,也比较简单,两组conv2d和pool2d层,最后一个输出的全连接层。

飞桨动态图模式下搭建网络并训练模型的全过程主要包含以下内容:

5.1   数据准备

首先使用paddle.dataset.mnist作为训练所需要的数据集:飞桨把一些公开的数据集进行了封装,用户可以通过dataset.mnist接口直接调用mnist数据集,train()返回训练数据的reader,test()接口返回测试的数据的reader。

train_reader = paddle.batch(paddle.dataset.mnist.train(),batch_size=BATCH_SIZE, drop_last=True)

5.2  Layer定义

为了能够支持更复杂的网络搭建,动态图引入了Layer模块,每个Layer是一个独立的模块,Layer之间又可以互相嵌套。

用户需要关注的是,a)Layer存储的状态,包含一些隐层维度、需要学习的参数等;b)包含的sub Layer,为了方便大家使用,飞桨提供了一些定制好的Layer结构,如果Conv2D,Pool2D,FC等。c) 前向传播的函数,这个函数中定义了图的运行结构,这个函数与静态图的网络搭建是完全不一样的概念,函数只是描述了运行结构,在函数被调用的时候代码才执行,静态图的网络搭建是代码真正在执行。

Conv2D是飞桨提供的卷积运算的Layer,Pool2D是池化操作的Layer。

1)定义SimpleImgConvPool 子Layer:SimpleImgConvPool把网络中循环使用的部分进行整合,其中包含包含了两个子Layer,Conv2D和Pool2D,forward函数定义了前向运行时的结构。

class SimpleImgConvPool(fluid.dygraph.Layer)   
 def __init__(self,name_scope, num_filters, filter_size, pool_size, pool_stride, pool_padding=0, pool_type='max',global_pooling=False, conv_stride=1, conv_padding=0, conv_dilation=1, conv_groups=1,act=None, use_cudnn=False, param_attr=None, bias_attr=None):       
  super(SimpleImgConvPool,self).__init__(name_scope)      
   self._conv2d =fluid.dygraph.Conv2D(self.full_name(), num_filters=num_filters, filter_size=filter_size,stride=conv_stride,padding=conv_padding, dilation=conv_dilation, groups=conv_groups,aram_attr=None, bias_attr=None, act=act, use_cudnn=use_cudnn)       
  self._pool2d =fluid.dygraph.Pool2D(self.full_name(), pool_size=pool_size, pool_type=pool_type,pool_stride=pool_stride, pool_padding=pool_padding, global_pooling=global_pooling,use_cudnn=use_cudnn)  
 def forward(self,inputs):      
   x =self._conv2d(inputs)    
     x = self._pool2d(x)      
   return x 


2)构建MNIST Layer,MNIST Layes包含了两个SimpleImgConvPool子Layer,以及一个FC(全连接层),forward函数定义了如图2所示得网络结构

class MNIST(fluid.dygraph.Layer):    def __init__(self,name_scope):        super(MNIST,self).__init__(name_scope)        self._simple_img_conv_pool_1 = SimpleImgConvPool(self.full_name(), 20,5, 2, 2, act="relu")        self._simple_img_conv_pool_2 = SimpleImgConvPool(self.full_name(), 50,5, 2, 2, act="relu")        pool_2_shape = 50 *4 * 4        SIZE = 10        scale = (2.0 / (pool_2_shape**2 *SIZE))**0.5        self._fc =fluid.dygraph.FC(self.full_name(),10, param_attr=fluid.param_attr.ParamAttr(initializer=fluid.initializer.NormalInitializer(loc=0.0, scale=scale)),act="softmax")    def forward(self, inputs,label=None):        x =self._simple_img_conv_pool_1(inputs)        x =self._simple_img_conv_pool_2(x)        x = self._fc(x)        if label is notNone:            acc =fluid.layers.accuracy(input=x, label=label)            return x, acc        else:            return x


5.3  优化器定义

使用经典的Adam优化算法:

adam =fluid.optimizer.AdamOptimizer(learning_rate=0.001)

 5.4   训练


构建训练循环,顺序为:1).从reader读取数据 2).调用MNIST Layer 前向网络3).利用cross_entropy计算loss 4)调用backward计算梯度 5)调用adam.minimize更新梯度,6) clear_gradients()将梯度设置为0(这种方案是为了支持backward of backward功能,如果系统自动将梯度置为0,则无法使用backward of backward功能)

with fluid.dygraph.guard(): 
   epoch_num = 5   
 BATCH_SIZE = 64    
 mnist =MNIST("mnist")  
  adam =fluid.optimizer.AdamOptimizer(learning_rate=0.001)   
 train_reader =paddle.batch(paddle.dataset.mnist.train(), batch_size= BATCH_SIZE,drop_last=True)     np.set_printoptions(precision=3,suppress=True)
    for epoch inrange(epoch_num):      
  for batch_id, data inenumerate(train_reader()):        
    dy_x_data = np.array(         
      [x[0].reshape(1, 28, 28)             
    for x indata]).astype('float32')    
        y_data =np.array(            
    [x[1] for xin data]).astype('int64').reshape(BATCH_SIZE, 1)      
       img =fluid.dygraph.to_variable(dy_x_data)            label =fluid.dygraph.to_variable(y_data)            label.stop_gradient = True         
  cost 
=mnist(img)    
       loss =fluid.layers.cross_entropy(cost, label)        
   avg_loss =fluid.layers.mean(loss)        
   dy_out =avg_loss.numpy()        
   avg_loss.backward()          
 adam.minimize(avg_loss)    
       mnist.clear_gradients()          
 dy_param_value ={}         
  for param inmnist.parameters():           
    dy_param_value[param.name] = param.numpy()     
      if batch_id % 20== 0:         
      print("Loss at step {}: {}".format(batch_id,avg_loss.numpy()))

 

5.5  预测

预测的目标是为了在训练的同时,了解一下在开发集上模型的表现情况,由于动态图的训练和预测使用同一个Layer,有一些op(比如dropout)在训练和预测时表现不一样,用户需要切换到预测的模式,通过 .eval()接口进行切换(注:训练的时候需要切回到训练的模式)

预测代码如下图所示:

def test_mnist(reader, model, batch_size):    acc_set = []    avg_loss_set = []    for batch_id, data in enumerate(reader()):       dy_x_data = np.array([x[0].reshape(1, 28, 28) for x indata]).astype('float32')       y_data = np.array([x[1] for x indata]).astype('int64').reshape(batch_size, 1)       img = to_variable(dy_x_data)       label = to_variable(y_data)       label.stop_gradient = True       prediction, acc = model(img, label)       loss = fluid.layers.cross_entropy(input=prediction, label=label)       avg_loss = fluid.layers.mean(loss)       acc_set.append(float(acc.numpy()))       avg_loss_set.append(float(avg_loss.numpy()))       # get test acc and loss   acc_val_mean = np.array(acc_set).mean()   avg_loss_val_mean = np.array(avg_loss_set).mean()   return avg_loss_val_mean, acc_val_mean

最终可以通过打印数据自行绘制Loss曲线:

5.6   调试

调试是我们在搭建网络时候非常重要的功能,动态图由于是命令式编程,用户可以直接利用python的print打印变量,通过print( tensor.numpy() ) 直接打印tensor的值。在执行了backward之后,用户可以通过print(tensor.gradient) 打印反向的梯度值。

这样,一个简单的动态图的实例就完成了,亲爱的开发者们,你们学会了么?

想与更多的深度学习开发者交流,请加入飞桨官方QQ群:432676488。

PaddlePaddle
PaddlePaddle

PaddlePaddle是百度独立研发的深度学习平台,易用,高效,灵活可伸缩,可支持海量图像识别分类、机器翻译和自动驾驶等多个领域业务需求,现已全面开源。

产业PaddlePaddle优化器逻辑回归人工智能深度学习
1
相关数据
深度学习技术

深度学习(deep learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。 深度学习是机器学习中一种基于对数据进行表征学习的算法,至今已有数种深度学习框架,如卷积神经网络和深度置信网络和递归神经网络等已被应用在计算机视觉、语音识别、自然语言处理、音频识别与生物信息学等领域并获取了极好的效果。

池化技术

池化(Pooling)是卷积神经网络中的一个重要的概念,它实际上是一种形式的降采样。有多种不同形式的非线性池化函数,而其中“最大池化(Max pooling)”是最为常见的。它是将输入的图像划分为若干个矩形区域,对每个子区域输出最大值。直觉上,这种机制能够有效的原因在于,在发现一个特征之后,它的精确位置远不及它和其他特征的相对位置的关系重要。池化层会不断地减小数据的空间大小,因此参数的数量和计算量也会下降,这在一定程度上也控制了过拟合。通常来说,CNN的卷积层之间都会周期性地插入池化层。

参数技术

在数学和统计学裡,参数(英语:parameter)是使用通用变量来建立函数和变量之间关系(当这种关系很难用方程来阐述时)的一个数量。

神经网络技术

(人工)神经网络是一种起源于 20 世纪 50 年代的监督式机器学习模型,那时候研究者构想了「感知器(perceptron)」的想法。这一领域的研究者通常被称为「联结主义者(Connectionist)」,因为这种模型模拟了人脑的功能。神经网络模型通常是通过反向传播算法应用梯度下降训练的。目前神经网络有两大主要类型,它们都是前馈神经网络:卷积神经网络(CNN)和循环神经网络(RNN),其中 RNN 又包含长短期记忆(LSTM)、门控循环单元(GRU)等等。深度学习是一种主要应用于神经网络帮助其取得更好结果的技术。尽管神经网络主要用于监督学习,但也有一些为无监督学习设计的变体,比如自动编码器和生成对抗网络(GAN)。

逻辑技术

人工智能领域用逻辑来理解智能推理问题;它可以提供用于分析编程语言的技术,也可用作分析、表征知识或编程的工具。目前人们常用的逻辑分支有命题逻辑(Propositional Logic )以及一阶逻辑(FOL)等谓词逻辑。

优化器技术

优化器基类提供了计算梯度loss的方法,并可以将梯度应用于变量。优化器里包含了实现了经典的优化算法,如梯度下降和Adagrad。 优化器是提供了一个可以使用各种优化算法的接口,可以让用户直接调用一些经典的优化算法,如梯度下降法等等。优化器(optimizers)类的基类。这个类定义了在训练模型的时候添加一个操作的API。用户基本上不会直接使用这个类,但是你会用到他的子类比如GradientDescentOptimizer, AdagradOptimizer, MomentumOptimizer(tensorflow下的优化器包)等等这些算法。

推荐文章
暂无评论
暂无评论~