導讀:飛槳PaddlePaddle致力於讓深度學習技術的創新與應用更簡單。飛槳核心框架已提供了動態圖(DyGraph)相關的API和文檔,而且還附有Language model、Sentiment Classification、OCR、ResNet等模型的動態圖版本官方實現。飛槳目前兼具了動態圖和靜態圖的優點,同時具有靈活性和高效性。javascript
飛槳動態圖&靜態圖總體結構以下:java
目前深度學習框架主要有聲明式編程和命令式編程兩種編程方式。聲明式編程,代碼先描述要作的事情但不當即執行,對深度學習任務建模,須要事先定義神經網絡的結構,而後再執行整個圖結構,這通常稱爲靜態圖模式。而命令式編程對應的動態圖模式,代碼直接返回運算的結果,神經網絡結構的定義和執行同步。一般來講,靜態圖模式可以對總體性作編譯優化,更有利於性能的提高,而動態圖則很是便於用戶對程序進行調試。python
飛槳的DyGraph模式是一種動態的圖執行機制。與靜態計算圖的執行機制不一樣,DyGraph模式下的操做能夠當即得到執行結果,而沒必要等待計算圖所有構建完成。這樣可讓開發者更加直觀地構建深度學習任務並進行模型的調試,同時還減小了大量用於構建靜態計算圖的代碼,使得編寫、調試網絡的過程變得很是便捷。c++
飛槳DyGraph動態圖模式,主要有三大特點:git
靈活便捷的代碼書寫方式:可以使用Python的控制流(for,if…else..等)進行編程。github
便捷的調試功能:直接使用Python的打印方法即時打印所須要的結果,從而檢查正在運行的模型結果便於調試。算法
和靜態執行圖通用的模型代碼:對於沒有使用Python控制流的網絡,動態圖的代碼能夠直接在靜態圖模式下執行,提高執行的效率。編程
讓咱們經過一個實際例子,直觀地感覺一下動態圖與靜態圖在使用過程當中的差別。網絡
想要實現以下的功能: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的打印方法,就能夠即時打印出所須要的結果,從而檢查正在運行的模型結果,很是方便調試。
飛槳動態圖具備如此多的優點,下面講述最基本的一些用法。
(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
介紹網絡訓練的基本結構,也比較簡單,兩組conv2d和pool2d層,最後一個輸出的全鏈接層。
飛槳動態圖模式下搭建網絡並訓練模型的全過程主要包含如下內容:
首先使用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)
爲了可以支持更復雜的網絡搭建,動態圖引入了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
使用經典的Adam優化算法:
adam =fluid.optimizer.AdamOptimizer(learning_rate=0.001)
構建訓練循環,順序爲: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()))
預測的目標是爲了在訓練的同時,瞭解一下在開發集上模型的表現狀況,因爲動態圖的訓練和預測使用同一個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曲線:
調試是咱們在搭建網絡時候很是重要的功能,動態圖因爲是命令式編程,用戶能夠直接利用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