都9102年了還不懂動態圖嗎?一文帶你瞭解飛槳動態圖

導讀:飛槳PaddlePaddle致力於讓深度學習技術的創新與應用更簡單。飛槳核心框架已提供了動態圖(DyGraph)相關的API和文檔,而且還附有Language model、Sentiment Classification、OCR、ResNet等模型的動態圖版本官方實現。飛槳目前兼具了動態圖和靜態圖的優點,同時具有靈活性和高效性。javascript

 

飛槳動態圖&靜態圖總體結構以下:java

 

640?wx_fmt=png

 

1. 動態圖與靜態圖

 

目前深度學習框架主要有聲明式編程和命令式編程兩種編程方式。聲明式編程,代碼先描述要作的事情但不當即執行,對深度學習任務建模,須要事先定義神經網絡的結構,而後再執行整個圖結構,這通常稱爲靜態圖模式。而命令式編程對應的動態圖模式,代碼直接返回運算的結果,神經網絡結構的定義和執行同步。一般來講,靜態圖模式可以對總體性作編譯優化,更有利於性能的提高,而動態圖則很是便於用戶對程序進行調試。python

 

2. 飛槳動態圖的三大特點

 

飛槳的DyGraph模式是一種動態的圖執行機制。與靜態計算圖的執行機制不一樣,DyGraph模式下的操做能夠當即得到執行結果,而沒必要等待計算圖所有構建完成。這樣可讓開發者更加直觀地構建深度學習任務並進行模型的調試,同時還減小了大量用於構建靜態計算圖的代碼,使得編寫、調試網絡的過程變得很是便捷。c++

 

飛槳DyGraph動態圖模式,主要有三大特點:git

 

  • 靈活便捷的代碼書寫方式:可以使用Python的控制流(for,if…else..等)進行編程。github

  • 便捷的調試功能:直接使用Python的打印方法即時打印所須要的結果,從而檢查正在運行的模型結果便於調試。算法

  • 和靜態執行圖通用的模型代碼:對於沒有使用Python控制流的網絡,動態圖的代碼能夠直接在靜態圖模式下執行,提高執行的效率。編程

 

3. 飛槳動態圖與靜態圖的直觀對比

 

讓咱們經過一個實際例子,直觀地感覺一下動態圖與靜態圖在使用過程當中的差別。網絡

 

想要實現以下的功能:app

(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.1	
import paddle.fluid as fluid	
with 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 asfluid	
import numpy as np	
x = 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

 

640?wx_fmt=png

 

介紹網絡訓練的基本結構,也比較簡單,兩組conv2d和pool2d層,最後一個輸出的全鏈接層。

640?wx_fmt=png

 

飛槳動態圖模式下搭建網絡並訓練模型的全過程主要包含如下內容:

 

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曲線:

 

640?wx_fmt=png

 

5.6   調試

 

調試是咱們在搭建網絡時候很是重要的功能,動態圖因爲是命令式編程,用戶能夠直接利用python的print打印變量,經過print( tensor.numpy() ) 直接打印tensor的值。在執行了backward以後,用戶能夠經過print(tensor.gradient) 打印反向的梯度值。

 

這樣,一個簡單的動態圖的實例就完成了,親愛的開發者們,大家學會了麼?

 

 

想與更多的深度學習開發者交流,請加入飛槳官方QQ羣:432676488。

 

想了解更多內容:

 

  • 官網地址:https://www.paddlepaddle.org.cn/

  • 動態圖代碼地址:

    https://github.com/PaddlePaddle/models/tree/v1.5.1/dygraph

640?wx_fmt=jpeg

相關文章
相關標籤/搜索