PaddlePaddle 是百度自主研發,集深度學習核心框架、工具組件和服務平臺為一體的開源深度學習平臺。該平臺技術領先、功能完備。Paddle Fluid 是 PaddlePaddle 的核心框架,滿足模型開發、訓練、部署的全流程需求。本文將展示如何用 Paddle Fluid API 編程並搭建一個簡單的神經網絡。
本文將介紹:
Paddle Fluid 有哪些核心概念如何在 Paddle Fluid 中定義運算過程如何使用 executor 運行 Paddle Fluid 操作如何從邏輯層對實際問題建模如何調用 API(層,數據集,損失函數,優化方法等等)
使用 Tensor 表示數據
Paddle Fluid 和其它主流框架一樣,使用 Tensor 數據結構來承載數據。Tensor 可以簡單理解成一個多維數組,一般而言可以有任意多的維度。不同的 Tensor 可以具有自己的數據類型和形狀,同一 Tensor 中每個元素的數據類型是一樣的,Tensor 的形狀就是 Tensor 的維度。
下圖直觀地表示 1~6 維的 Tensor:
在 Paddle Fluid 中存在三種特殊的 Tensor:
1. 模型中的可學習參數
模型中的可學習參數(包括網絡權重、偏置等)生存期和整個訓練任務一樣長,會接受優化算法的更新,在 Paddle Fluid 中以 Variable 的子類 Parameter 表示。
在 Paddle Fluid 中可以通過 fluid.layers.create_parameter 來創建可學習參數:
w = fluid.layers.create_parameter(name="w",shape=[1],dtype='float32')
一般情況下,您不需要自己來創建網絡中的可學習參數,Paddle Fluid 為大部分常見的神經網絡基本計算模塊都提供了封裝。以最簡單的全連接模型為例,下面的代碼片段會直接為全連接層創建連接權值(W)和偏置(bias)兩個可學習參數,無需顯式地調用 Parameter 相關接口來創建。
import paddle.fluid as fluidy = fluid.layers.fc(input=x, size=128, bias_attr=True)
2. 輸入輸出 Tensor
整個神經網絡的輸入數據也是一個特殊的 Tensor,在這個 Tensor 中,一些維度的大小在定義模型時無法確定(通常包括:batch size,如果 mini-batch 之間數據可變,也會包括圖片的寬度和高度等),在定義模型時需要佔位。
Paddle Fluid 中使用 fluid.layers.data 來接收輸入數據,fluid.layers.data 需要提供輸入 Tensor 的形狀信息,當遇到無法確定的維度時,相應維度指定為 None 或 -1,如下面的代碼片段所示:
import paddle.fluid as fluid#定義x的維度為[3,None],其中我們只能確定x的第一的維度為3,第二個維度未知,要在程序執行過程中才能確定x = fluid.layers.data(name="x", shape=[3,None], dtype="int64")#batch size無需顯示指定,框架會自動補充第0維為batch size,並在運行時填充正確數值a = fluid.layers.data(name="a",shape=[3,4],dtype='int64')#若圖片的寬度和高度在運行時可變,將寬度和高度定義為None。#shape的三個維度含義分別是:channel、圖片的寬度、圖片的高度b = fluid.layers.data(name="image",shape=[3,None,None],dtype="float32")
其中,dtype="int64" 表示有符號 64 位整數數據類型,更多 Paddle Fluid 目前支持的數據類型請在官網查閱:http://paddlepaddle.org/documentation/docs/zh/1.4/user_guides/howto/prepare_data/feeding_data.html#fluid。
3. 常量 Tensor
Paddle Fluid 通過 fluid.layers.fill_constant 來實現常量 Tensor,用戶可以指定 Tensor 的形狀,數據類型和常量值。代碼實現如下所示:
import paddle.fluid as fluiddata = fluid.layers.fill_constant(shape=[1], value=0, dtype='int64')
需要注意的是,上述定義的 tensor 並不具有值,它們僅表示將要執行的操作,如您直接列印 data 將會得到描述該 data 的一段信息:
print data
輸出結果:
name: "fill_constant_0.tmp_0"type {type: LOD_TENSOR lod_tensor { tensor { data_type: INT64 dims: 1 } }}persistable: false
具體輸出數值將在 Executor 運行時得到,詳細過程會在後文展開描述。
數據傳入
Paddle Fluid 有特定的數據傳入方式:
您需要使用 fluid.layers.data 配置數據輸入層,並在 fluid.Executor 或 fluid.ParallelExecutor 中,使用 executor.run(feed=...) 傳入訓練數據。
具體的數據準備過程,您可以閱讀官網使用指南「準備數據」章節。
使用 Operator 表示對數據的操作
在 Paddle Fluid 中,所有對數據的操作都由 Operator 表示,您可以使用內置指令來描述它們的神經網絡。為了便於用戶使用,在 Python 端,Paddle Fluid 中的 Operator 被一步封裝入 paddle.fluid.layers,paddle.fluid.nets 等模塊。這是因為一些常見的對 Tensor 的操作可能是由更多基礎操作構成,為了提高使用的便利性,框架內部對基礎 Operator 進行了一些封裝,包括創建 Operator 依賴可學習參數,可學習參數的初始化細節等,減少用戶重複開發的成本。例如用戶可以利用 paddle.fluid.layers.elementwise_add() 實現兩個輸入 Tensor 的加法運算:
#定義網絡import paddle.fluid as fluida = fluid.layers.data(name="a",shape=[1],dtype='float32')b = fluid.layers.data(name="b",shape=[1],dtype='float32')result = fluid.layers.elementwise_add(a,b)#定義Exectorcpu = fluid.core.CPUPlace() #定義運算場所,這裡選擇在CPU下訓練exe = fluid.Executor(cpu) #創建執行器exe.run(fluid.default_startup_program()) #網絡參數初始化#準備數據import numpydata_1 = int(input("Please enter an integer: a="))data_2 = int(input("Please enter an integer: b="))x = numpy.array([[data_1]])y = numpy.array([[data_2]])#執行計算outs = exe.run(feed={'a':x,'b':y}, fetch_list=[result.name])#驗證結果print "%d+%d=%d" % (data_1,data_2,outs[0][0])
輸出結果:
a=7b=37+3=10
本次運行時,輸入 a=7,b=3,得到 outs=10。
您可以複製這段代碼在本地執行,根據指示輸入其它數值觀察計算結果。
如果想獲取網絡執行過程中的 a,b 的具體值,可以將希望查看的變量添加在 fetch_list 中。
...#執行計算outs = exe.run(feed={'a':x,'b':y}, fetch_list=[a,b,result.name])#查看輸出結果print outs
輸出結果:
[array([[7]]), array([[3]]), array([[10]])]
使用 Program 描述神經網絡模型
Paddle Fluid 不同於其它大部分深度學習框架,去掉了靜態計算圖的概念,代之以 Program 的形式動態描述計算過程。這種動態的計算描述方式兼具網絡結構修改的靈活性和模型搭建的便捷性,在保證性能的同時極大地提高了框架對模型的表達能力。
開發者的所有 Operator 都將寫入 Program,在 Paddle Fluid 內部將自動轉化為一種叫作 ProgramDesc 的描述語言,Program 的定義過程就像在寫一段通用程序,有開發經驗的用戶在使用 Paddle Fluid 時,會很自然的將自己的知識遷移過來。
其中,Paddle Fluid 通過提供順序、分支和循環三種執行結構的支持,讓用戶可以通過組合描述任意複雜的模型。
順序執行:
用戶可以使用順序執行的方式搭建網絡:
x = fluid.layers.data(name='x',shape=[13], dtype='float32')y_predict = fluid.layers.fc(input=x, size=1, act=None)y = fluid.layers.data(name='y', shape=[1], dtype='float32')cost = fluid.layers.square_error_cost(input=y_predict, label=y)
條件分支——switch、if else:
Paddle Fluid 中有 switch 和 if-else 類來實現條件選擇,用戶可以使用這一執行結構在學習率調節器中調整學習率或其它希望的操作:
lr = fluid.layers.tensor.create_global_var(shape=[1], value=0.0, dtype='float32', persistable=True, name="learning_rate")one_var = fluid.layers.fill_constant(shape=[1], dtype='float32', value=1.0)two_var = fluid.layers.fill_constant(shape=[1], dtype='float32', value=2.0)with fluid.layers.control_flow.Switch() as switch: with switch.case(global_step == zero_var): fluid.layers.tensor.assign(input=one_var, output=lr) with switch.default(): fluid.layers.tensor.assign(input=two_var, output=lr)
關於 Paddle Fluid 中 Program 的詳細設計思想,可以參考閱讀官網進階使用「設計思想」中更多 Fluid 中的控制流,可以參考閱讀 API 文檔。
使用 Executor 執行 Program
Paddle Fluid 的設計思想類似於高級程式語言 C++和 JAVA 等。程序的執行過程被分為編譯和執行兩個階段。用戶完成對 Program 的定義後,Executor 接受這段 Program 並轉化為 C++後端真正可執行的 FluidProgram,這一自動完成的過程叫做編譯。編譯過後需要 Executor 來執行這段編譯好的 FluidProgram。例如上文實現的加法運算,當構建好 Program 後,需要創建 Executor,進行初始化 Program 和訓練 Program:
#定義Exectorcpu = fluid.core.CPUPlace() #定義運算場所,這裡選擇在CPU下訓練exe = fluid.Executor(cpu) #創建執行器exe.run(fluid.default_startup_program()) #用來進行初始化的program#訓練Program,開始計算#feed以字典的形式定義了數據傳入網絡的順序#fetch_list定義了網絡的輸出outs = exe.run(feed={'a':x,'b':y}, fetch_list=[result.name])
代碼實例
您已經對 Paddle Fluid 核心概念有了初步認識了,不妨嘗試配置一個簡單的網絡吧。如果感興趣的話可以跟隨本部分,完成一個非常簡單的數據預測。
從邏輯層面明確了輸入數據格式、模型結構、損失函數以及優化算法後,需要使用 Paddle Fluid 提供的 API 及算子來實現模型邏輯。一個典型的模型主要包含 4 個部分,分別是:輸入數據格式定義,模型前向計算邏輯,損失函數以及優化算法。
1、問題描述
給定一組數據 <X,Y>,求解出函數 f,使得 y=f(x),其中 X,Y 均為一維張量。最終網絡可以依據輸入 x,準確預測出 y_predict。
2、定義數據
假設輸入數據 X=[1 2 3 4],Y=[2,4,6,8],在網絡中定義:
#定義X數值train_data=numpy.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')#定義期望預測的真實值y_truey_true = numpy.array([[2.0],[4.0],[6.0],[8.0]]).astype('float32')
3、搭建網絡(定義前向計算邏輯)
接下來需要定義預測值與輸入的關係,本次使用一個簡單的線性回歸函數進行預測:
#定義輸入數據類型x = fluid.layers.data(name="x",shape=[1],dtype='float32')#搭建全連接網絡y_predict = fluid.layers.fc(input=x,size=1,act=None)
這樣的網絡就可以進行預測了,雖然輸出結果只是一組隨機數,離預期結果仍相差甚遠:
#加載庫import paddle.fluid as fluidimport numpy#定義數據train_data=numpy.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')y_true = numpy.array([[2.0],[4.0],[6.0],[8.0]]).astype('float32')#定義預測函數x = fluid.layers.data(name="x",shape=[1],dtype='float32')y_predict = fluid.layers.fc(input=x,size=1,act=None)#參數初始化cpu = fluid.core.CPUPlace()exe = fluid.Executor(cpu)exe.run(fluid.default_startup_program())#開始訓練outs = exe.run(feed={'x':train_data}, fetch_list=[y_predict.name])#觀察結果print outs
輸出結果:
[array([[0.74079144],[1.4815829 ], [2.2223744 ], [2.9631658 ]], dtype=float32)]
4、添加損失函數
完成模型搭建後,如何評估預測結果的好壞呢?我們通常在設計的網絡中添加損失函數,以計算真實值與預測值的差。
在本例中,損失函數採用均方差函數:
cost = fluid.layers.square_error_cost(input=y_predict, label=y)avg_cost = fluid.layers.mean(cost)
輸出一輪計算後的預測值和損失函數:
#加載庫import paddle.fluid as fluidimport numpy#定義數據train_data=numpy.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')y_true = numpy.array([[2.0],[4.0],[6.0],[8.0]]).astype('float32')#定義網絡x = fluid.layers.data(name="x",shape=[1],dtype='float32')y = fluid.layers.data(name="y",shape=[1],dtype='float32')y_predict = fluid.layers.fc(input=x,size=1,act=None)#定義損失函數cost = fluid.layers.square_error_cost(input=y_predict,label=y)avg_cost = fluid.layers.mean(cost)#參數初始化cpu = fluid.core.CPUPlace()exe = fluid.Executor(cpu)exe.run(fluid.default_startup_program())#開始訓練outs = exe.run(feed={'x':train_data,'y':y_true}, fetch_list=[y_predict.name,avg_cost.name])#觀察結果print outs
輸出結果:
[array([[0.9010564],[1.8021128], [2.7031693], [3.6042256]], dtype=float32), array([9.057577], dtype=float32)]
可以看到第一輪計算後的損失函數為 9.0,仍有很大的下降空間。
5、網絡優化
確定損失函數後,可以通過前向計算得到損失值,然後通過鏈式求導法則得到參數的梯度值。
獲取梯度值後需要更新參數,最簡單的算法是隨機梯度下降法:w=wηg,由 fluid.optimizer.SGD 實現:
sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.01)
讓我們的網絡訓練 100 次,查看結果:
#加載庫import paddle.fluid as fluidimport numpy#定義數據train_data=numpy.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')y_true = numpy.array([[2.0],[4.0],[6.0],[8.0]]).astype('float32')#定義網絡x = fluid.layers.data(name="x",shape=[1],dtype='float32')y = fluid.layers.data(name="y",shape=[1],dtype='float32')y_predict = fluid.layers.fc(input=x,size=1,act=None)#定義損失函數cost = fluid.layers.square_error_cost(input=y_predict,label=y)avg_cost = fluid.layers.mean(cost)#定義優化方法sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.01)sgd_optimizer.minimize(avg_cost)#參數初始化cpu = fluid.core.CPUPlace()exe = fluid.Executor(cpu)exe.run(fluid.default_startup_program())##開始訓練,迭代100次for i in range(100):outs = exe.run( feed={'x':train_data,'y':y_true}, fetch_list=[y_predict.name,avg_cost.name])#觀察結果print outs
輸出結果:
[array([[2.2075021],[4.1005487], [5.9935956], [7.8866425]], dtype=float32), array([0.01651453], dtype=float32)]
可以看到 100 次迭代後,預測值已經非常接近真實值了,損失值也從初始值 9.05 下降到了 0.01。
至此,恭喜您!已經成功使用 PaddlePaddle 核心框架 Paddle Fluid 搭建了一個簡單網絡。如果您還想嘗試更多,可以從官網繼續閱讀相關的文檔及更多豐富的模型實例。
PaddlePaddle 項目地址:https://github.com/PaddlePaddlePaddlePaddle 官網使用指南地址:http://paddlepaddle.org/documentation/docs/zh/1.4/user_guides/index_cn.html?from=paddlenav