編程指南 | 如何用Paddle Fluid API搭建一個簡單的神經網絡?

PaddlePaddle是百度自主研發,集深度學習核心框架、工具組件和服務平臺爲一體的技術領先、功能完備的開源深度學習平臺。如果要進行AI應用或研究,您所需要的,就是這份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** fluid
y **=** 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** fluid
data **=** 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** fluid
a **=** 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)

*#**定義**Exector*
cpu **=** fluid**.**core**.**CPUPlace() *#**定義運算場所,這裏選擇在**CPU**下訓練*
exe **=** fluid**.**Executor(cpu) *#**創建執行器*
exe**.**run(fluid**.**default_startup_program()) *#**網絡參數初始化*

*#**準備數據*
**import** numpy
data_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=7
b=3
7+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:

*#**定義**Exector*
cpu **=** 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_true*
y_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** fluid
**import** 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** fluid
**import** 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** fluid
**import** 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/PaddlePaddle
PaddlePaddle官網使用指南地址:
http://paddlepaddle.org/documentation/docs/zh/1.4/user_guides/index_cn.html?from=paddlenav

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章