歡迎你們來到此次實驗,在此次實驗中咱們將使用PaddlePaddle來實現一個多層神經網絡,這個多層神經網絡包含2個隱藏層,而且在隱藏層中使用到了Relu激活函數,在最後的輸出層使用了Softmax激活函數。多層神經網絡具備比邏輯迴歸更強的學習能力,而且更適合解決多分類問題,如今讓咱們進入實驗來看看多層神經網絡與邏輯迴歸之間的差別性吧! html
你將學會python
實現一個具備兩個隱藏層的神經網絡,用於解決多分類問題算法
使用batch_norm作數據歸一化數組
在隱藏層中使用Relu激活函數網絡
在輸出層使用Softmax激活函數app
使用classification_cost框架
使用Adam做爲優化器less
如今讓咱們進入實驗吧!ide
首先,載入幾個須要用到的庫,它們分別是:函數
In[2]
import matplotlib import numpy as np import matplotlib.pyplot as plt import os import csv import paddle import paddle.fluid as fluid from __future__ import print_function try: from paddle.fluid.contrib.trainer import * from paddle.fluid.contrib.inferencer import * except ImportError: print( "In the fluid 1.0, the trainer and inferencer are moving to paddle.fluid.contrib", file=sys.stderr) from paddle.fluid.trainer import * from paddle.fluid.inferencer import * %matplotlib inline
問題描述:
紅酒的品種多樣,質量也有高低之分,質量的好壞決定了紅酒的價格定位,假設你被聘爲一家紅酒供應商的紅酒質量鑑定專家,紅酒供應商給你提供了一些紅酒的指標值和評分數據,但願你能從這些數據中學習到紅酒的質量鑑定方法。
你的目標:
構建一個多層神經網絡來對紅酒質量評分
數據集分析:
紅酒數據集是採集於葡萄牙北部「Vinho Verde」葡萄酒的數據,它是研究Classification/Regression模型訓練的經典數據集。這個數據集包含了紅白兩種葡萄酒的數據,在本實驗中採用了紅酒數據做爲實驗數據,紅酒的數據包含 11 個特徵值(指標)和一個 0-10 的評分值(因爲隱私等問題,在特徵值中不包含價格、品牌等因素,只涵蓋了紅酒的物理化學性質因素):
輸入值:
輸出值:
12 - quality (0-10的評分) 質量
文件路徑
紅酒數據被存儲在當前文件夾下的 data 目錄中,data 目錄中共有兩個數據文件,分別是:
咱們暫時先使用數據量較少的紅酒數據來訓練模型,固然你能夠在完成實驗後,使用白(葡萄)酒數據來從新訓練或者驗證你的模型。
In[3]
# 得到當前文件夾 cur_dir = os.path.dirname(os.path.realpath("__file__")) # 得到文件路徑 filename = cur_dir + "/winequality-red.csv"
載入數據
首先,咱們使用csv.reader()來讀取紅酒數據,並存入data數組中,輸出數據的屬性和一組值。
In[4]
with open(filename) as f: reader = csv.reader(f) data = [] for row in reader: data.append([i for i in row[0].split(';')]) print( data[0],"\n" ) print( data[1] )
['fixed acidity', '"volatile acidity"', '"citric acid"', '"residual sugar"', '"chlorides"', '"free sulfur dioxide"', '"total sulfur dioxide"', '"density"', '"pH"', '"sulphates"', '"alcohol"', '"quality"'] ['7.4', '0.7', '0', '1.9', '0.076', '11', '34', '0.9978', '3.51', '0.56', '9.4', '5']
能夠看到,數據中存儲了關於紅酒的11類特徵值和1個標籤值(分數)。
預處理
如今讓咱們對數據進行一些預處理操做。觀察上面輸出的數據樣例,咱們發現數據是以字符串的形式存儲的,而且第一行數據(data[0])存儲的是屬性,而不是具體的數據,因此咱們須要去除第一行數據,而且將剩餘的數據類型轉換爲np.float32的numpy數組。這一操做十分簡單,只須要使用 np.array(array).astype(type) 便可完成。 例如,咱們有一個 list 爲 arr = ['1', '2', '3']
,咱們使用 np.array(arr).astype(np.float32)
既能夠將其轉化爲 numpy 類型的數據。
練習:
取出除第一行外的數據,並將數據類型轉換爲 np.float32 的 numpy 數組。
特別須要注意除了第一行外,其餘的每一行都要轉化,因此,能夠考慮使用切片技術,將除了第一行意外的數據都放入
np.array()
切片技術:data[5:] 表示從下標爲 5 的位置開始向後取得全部的行
In[49]
##練習 data=np.array(data[1:]).astype(np.float32) print( data[0] )
[ 7.4 0.7 0. 1.9 0.076 11. 34. 0.9978 3.51 0.56 9.4 5. ]
** 指望輸出: **
[ 7.4000001 0.69999999 0. 1.89999998 0.076 11. 34. 0.99779999 3.50999999 0.56 9.39999962 5. ] |
想要訓練一個模型,咱們首先須要將原始數據集切分爲訓練數據集(train set)和訓練數據集(test set),定義一個ratioratioratio變量,它是一個介於[0,1][0,1][0,1]區間的標量,表明着訓練數據佔總數據的比重,例如設置ratio=0.8ratio=0.8ratio=0.8,它表示訓練數據佔總數據量的八成,若是data_num表明數據總數,那麼ratio * data_num等於訓練集數量。
在數據量不大的狀況下,一般的切分方式 8:2 或者 7:3
** 練習: **
將數據劃分爲訓練數據集和測試數據集(由於數據量較少,建議將ratio設置爲0.8左右較爲合理)
In[50]
# 練習 ratio = 0.8 data_num = len(data) slice = int(ratio * data_num) train_set = data[:slice] test_set = data[slice:] print( "train set shape:", train_set.shape ) print( "test set shape:", test_set.shape )
train set shape: (1273, 12) test set shape: (319, 12)
若是將ratio設置爲0.8則
** 指望輸出: **
** train set shape ** | (1278, 12) |
** test set shape ** | (320, 12) |
在邏輯迴歸的實驗中咱們介紹了reader()的構造方法以及生成器的概念,在這裏咱們一樣構造一個read_data()函數來讀取訓練數據集train_set或者測試數據集test_set。它的具體實現是在read_data()函數內部構造一個reader(),使用yield關鍵字來讓reader()成爲一個Generator(生成器)。
注意 因爲紅酒的品質鑑定屬於多分類問題(將結果劃分爲0-10的離散整數),因此咱們將標籤值(評分)的數據類型轉化爲integer類型。
In[27]
def read_data(data): def reader(): for d in data: yield d[:-1], int(d[-1]) return reader test_arr = [ [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ] reader = read_data(test_arr) for d in reader(): print( d )
([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], 1) ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0)
指望輸出:
([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], 1) ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0) |
完成了數據的預處理工做並構造了 read_data() 來讀取數據,接下來將進入模型的訓練過程,使用 PaddlePaddle 來構造可訓練的 Logistic 迴歸模型,關鍵步驟以下:
設置訓練場所
配置網絡結構和優化方法
訓練準備
模型訓練
模型檢驗
預測
繪製學習曲線
首先進行設置訓練使用的設備。在複雜量較低的時候使用 CPU 就能夠完成任務,可是對於大規模計算就須要使用 GPU 訓練。目前 GPU 訓練都是基於 CUDA 工具之上的。
In[28]
# 設置訓練場所 use_cuda = False place = fluid.CUDAPlace(1) if use_cuda else fluid.CPUPlace()
這一階段,咱們關注的是網絡拓撲結構的配置和優化方法的配置
** 網絡結構: **
瞭解一下咱們即將要配置的網絡結構,如圖所示,它的基本結構是輸入層、兩個隱藏層和輸出層,兩個隱藏層使用ReLU激活函數,輸出層使用Softmax激活函數(多分類),除此以外,在輸入層以後添加一層Batch Normalization對數據進行歸一化處理。
背景知識
下面咱們簡單介紹一下網絡結構中使用到的 ReLU 以及 Softmax 激活函數:
ReLU(Rectified linear unit)
ReLU激活函數一般比sigmoid和tanh激活函數的表現更好,其中一個緣由是 sigmoid 和 tanh 在兩端的飽和階段梯度接近 0,容易形成梯度消失問題(Vanishing Gradient Problem),尤爲是在深度網絡中更加明顯,而 ReLU 在非負區間的梯度爲常數,所以不存在梯度消失問題,使得模型的收斂速度維持在一個穩定狀態。咱們在兩個隱藏層上使用 ReLU 做爲激活函數。
Softmax
Softmax 是神經網絡中另外一種激活函數,計算輸出層的值。主要用於神經網絡最後一層,做爲輸出層進行多分類,是邏輯迴歸二分類的推廣。
Sigmoid 將結果值映射到 [0,1][0,1][0,1] 區間,用來作二分類。 而 Softmax 函數形式以下,把一個 k 維的向量 (y_1,y_2,y_3,y_4 … y_k.) 映射成 (a_1,a_2,a_3 … a_k.),其中 aia_iai 介於區間[0,1][0,1][0,1],根據 aia_iai 的大小來進行多分類的任務,如取權重最大的一維。
配置網絡結構
如今咱們已經瞭解了網絡結構,開始着手配置吧!
輸入層:
咱們能夠定義 x = fluid.layers.data(name='x', shape=[11], dtype='float32') 來表示生成一個數據輸入層,名稱爲「x」,數據類型爲11維向量;
隱藏層:
咱們定義兩個隱藏層h1和h2,以h1爲例,定義h1 = fluid.layers.fc(input=x, size=32, act='relu'),表示生成一個全鏈接層類型的隱藏層,輸入數據爲norm1,神經元個數爲32,激活函數爲Relu();
輸出層:
咱們能夠定義predict = fluid.layers.fc(input=h2, size=10, act='softmax')表示生成一個全鏈接層,輸入數據爲h2,輸出結果共有10個,分別表示十個不一樣的分類,激活函數爲Softmax();
標籤層
咱們能夠定義label = fluid.layers.data(name='label', shape=[1], dtype='int64')表示生成一個數據層,名稱爲「label」,數據類型爲包含0-9的整型。
定義損失函數
在配置網絡結構以後,咱們須要定義一個損失函數來計算梯度並優化參數。fluid 提供不少的損失函數, 在這裏咱們可使用 fluid 提供的用於多分類的損失函數。
fluid 在 layers 裏面提供了 cross_entropy 函數用來作分類問題的損失函數。這個函數有兩個參數分別是 input 和 lable,分別對應預測值和標籤值。其形式以下: cost = paddle.layer.cross_entropy(input=y_predict, label=y_label)。
當輸入一個batch的數據時,損失算子的輸出有多個值,每一個值對應一條樣本的損失,因此一般會在損失算子後面使用mean等算子,來對損失作歸約。 損失規約形式以下:avg_cost = fluid.layers.mean(cost)。 注意,雖然在神經網絡知識中沒有這個步驟,可是在 fluid 代碼中老是建議加入這個步驟。
練習
特別的
因爲本例使用 trainer 的寫法,因此須要將整個網絡拓撲結構包裝到函數中方便後面使用。
In[61]
# 封裝 train_func def train_func(): # 輸入層 x = fluid.layers.data(name='x', shape=[11], dtype='float32') #隱藏層 ### START CODE HERE ### (≈ 2 lines of code) h1 = fluid.layers.fc(input=x, size=32, act='relu') h2 = fluid.layers.fc(input=h1, size=16, act='relu') ### END CODE HERE ### #預測層 ### START CODE HERE ### (≈ 2 lines of code) y_predict = fluid.layers.fc(input=h2, size=10, act='softmax') ### END CODE HERE ### #標籤 y_label = fluid.layers.data(name='label', shape=[1], dtype='int64') # 損失函數 ### START CODE HERE ### (≈ 2 lines of code) cost = fluid.layers.cross_entropy(input=y_predict, label=y_label) avg_cost = fluid.layers.mean(cost) #acc = fluid.layers.accuracy(input=y_predict, label=y_label) ### END CODE HERE ### return avg_cost
** optimizer **
參數建立完成後,咱們須要定義一個優化器optimizer,在這裏咱們嘗試使用Adam來做爲優化器,它的計算公式以下:
\begin{split}m(w, t) & = \beta_1 m(w, t-1) + (1 - \beta_1) \nabla Q_i(w) \\ v(w, t) & = \beta_2 v(w, t-1) + (1 - \beta_2)(\nabla Q_i(w)) ^2 \\ w & = w - \frac{\eta m(w, t)}{\sqrt{v(w,t) + \epsilon}}\end{split}
Adam是一種經常使用的、效果良好的自適應學習率調整優化算法,一般只須要將參數設置爲beta1=0.9,beta2=0.999,epsilon=1e-08,不須要做修改便可讓模型產生好的收斂效果。在 fluid 中可使用接口 fluid.optimizer.Adam() 來建立 Adam 優化器。這個函數接受 4個參數分別是:learning_rate, beta1, beta2 和 epsilon。
建立優化器僅僅是向 fluid 後臺添加了優化器並無顯示的使用。若是想讓優化器真正的發揮做用,須要使用優化器,使用的方法十分簡單,調用優化器的 minimize 函數便可,這個函數接受的參數就是將要被優化的損失函數。其形式以下:avg_cost
** 練習: **
** **特別的** **
因爲本例使用 trainer 的寫法,因此須要將優化器包裝到函數中方便後面使用。
In[52]
#封裝 優化器 def optimizer_func(): #建立optimizer ### START CODE HERE ### (≈ 1 lines of code) optimizer=fluid.optimizer.Adam( learning_rate=0.2, beta1=0.9, beta2=0.999, epsilon=1e-08 ) #opts = optimizer.minimize(avg_cost) ### END CODE HERE ### return optimizer
這個階段咱們關注的是小的相關內容的配置。
** 定義映射 **
輸入網絡的數據要與網絡自己應該接受的數據相匹配。在 fluid 中使用 feed_order 的概念來保證輸入的數據與網絡接受的數據的順序是一致的。本示例中使用 feed_order = ['x', 'label'] 來告知網絡,輸入的數據是分爲兩部分,第一部分是 x 值,第二部分是 label 值。
In[53]
feed_order = ['x', 'label']
** 定義文件路徑 **
在 fluid 中,默認模型的相關數據是須要保存在硬盤上的。也就是說在訓練階段會將訓練好的模型保存在硬盤上,在將預測階段能夠直接 load 磁盤上的模型數據,進而作出預測。
In[54]
params_dirname = "./DNN_model"
** 定義事件處理函數 **
在 fluid 中,若是是用 trainer 的方式來訓練的話,那麼,在訓練的時候容許開發者本身定義事件回調函數。目前接受的事件有 BeginEpochEvent、EndEpochEvent、BeginStepEvent、EndStepEvent。
In[55]
# Plot data from paddle.v2.plot import Ploter train_title = "Train cost" test_title = "Test cost" plot_cost = Ploter(train_title, test_title) step = 0 # 事件處理 def event_handler_plot(event): global step if isinstance(event, EndStepEvent): if event.step % 2 == 0: # 若干個batch,記錄cost if event.metrics[0] < 10: plot_cost.append(train_title, step, event.metrics[0]) plot_cost.plot() if event.step % 20 == 0: # 若干個batch,記錄cost test_metrics = trainer.test( reader=test_reader, feed_order=feed_order) if test_metrics[0] < 10: plot_cost.append(test_title, step, test_metrics[0]) plot_cost.plot() # if test_metrics[0] < 1.0: # # 若是準確率達到閾值,則中止訓練 # print('loss is less than 10.0, stop') # trainer.stop() # 將參數存儲,用於預測使用 if params_dirname is not None: trainer.save_params(params_dirname) step += 1
** 定義執行器 **
爲了可以運行開發者定義的網絡拓撲結構和優化器,須要定義執行器。由執行器來真正的執行參數的初始化和網絡的訓練過程。
In[56]
# 建立執行器,palce在程序初始化時設定 exe = fluid.Executor(place) # 初始化執行器 exe.run( fluid.default_startup_program() )
[]
** 定義reader **
網絡接受的數據其實是一個又一個的 mini-batch 。 paddle 框架爲開發者準備好了 paddle.batch 函數來提供一個又一個 mini-batch。在實際輸入數據的時候,咱們但願的是數據順序不要影響網絡是訓練,paddle 框架也準備了 paddle.reader.shuffle 函數來打亂輸入的順序。
** 練習: ** 設置 BATCH_SIZE 爲 10
BATCH_SIZE 的大小決定了 每一個 mini-batch 中灌入的數據的數量
In[57]
# 設置 BATCH_SIZE 的大小 ### START CODE HERE ### (≈ 1 lines of code) BATCH_SIZE = 10 ### END CODE HERE ### # 設置訓練reader train_reader = paddle.batch( paddle.reader.shuffle( read_data(train_set), buf_size=500), batch_size=BATCH_SIZE) #設置測試 reader test_reader = paddle.batch( paddle.reader.shuffle( read_data(test_set), buf_size=500), batch_size=BATCH_SIZE)
** 定義trainer **
trainer 負責收集訓練須要的相關信息。定義 trainer 時須要提供 3個重要信息:
In[62]
#建立訓練器 from paddle.fluid.contrib.trainer import * from paddle.fluid.contrib.inferencer import * trainer = Trainer( train_func= train_func, place= place, optimizer_func= optimizer_func)
** 開始訓練 **
在作好了全部的準備工做以後,就開始開始訓練了。因爲本例使用的是 trainer 的方法,因此能夠直接調用 trainer 的 train 方法來執行訓練。train 方法主要須要設置3個參數: reader、num_epochs 和 feeder_order。 其中,reader 表示可以持續提供 mini-batch 的數據源。num_epochs 表示全部的數據將要訓練多少輪次(就是一個數字)。 feeder_order 表示數據的順序。
咱們注意到,reader 和 feeder_order 在前面的準備過程當中已經準備好了。 除了這三個參數外,train 還接受一個 event_handler 參數。這個參數容許開發者本身定義回調函數,用以在訓練過程當中打印訓練相關的信息,甚至在合適的時候中止訓練。 函數的形式以下:
trainer.train( reader= , num_epochs= , event_handler= , feed_order= )
** 練習: **
In[63]
from paddle.fluid.contrib.trainer import * from paddle.fluid.contrib.inferencer import * ### START CODE HERE ### (≈ 1 lines of code) trainer.train( reader=train_reader, num_epochs=10, event_handler=event_handler_plot, feed_order=feed_order) ### END CODE HERE ###
<Figure size 432x288 with 0 Axes>
模型訓練完成後,接下來使用訓練好的模型在測試數據集上看看效果。 本階段主要有兩個部分:預測和預測效果評估
** 定義預測網絡拓撲結構 **
fluid 設計者認爲訓練的網絡和預測的網絡並不必定是徹底相同的。因此在預測階段,開發者須要本身定義測試的網絡,可是這個網絡拓撲結構和訓練網絡的拓撲結構必須是兼容的,不然從硬盤中 load 回來的數據是沒法應用到預測網絡中。
In[64]
# 定義數據的 feeder 爲預測使用 feeder = None #定義預測網絡拓撲結構 def inference_func(): global feeder x = fluid.layers.data(name='x', shape=[11], dtype='float32') h1 = fluid.layers.fc(input=x, size=32, act='relu') h2 = fluid.layers.fc(input=h1, size=16, act='relu') predict = fluid.layers.fc(input=h2, size=10, act='softmax') label = fluid.layers.data(name='label', shape=[1], dtype='int64') feeder = fluid.DataFeeder(place=place, feed_list=['x', 'label']) return predict
** 定義運算設備 **
通常是 CPU 或者 GPU 設備
In[65]
# 設置訓練場所 use_cuda = False place = fluid.CUDAPlace(1) if use_cuda else fluid.CPUPlace()
** 定義預測器 **
預測器的定義須要3個重要信息:
具體代碼形式以下:
inferencer = Inferencer( infer_func= , param_path= , place= )
** 練習: ** 本身定義 預測器
In[79]
# 定義預測器 ### START CODE HERE ### (≈ 3 lines of code) inferencer = Inferencer( infer_func =inference_func, param_path = params_dirname, place=place) ### END CODE HERE ###
** 執行預測 **
有了 預測器以後 僅僅是有了預測的能力尚未真的去預測結果。真正的預測須要使用 inferencer.infer() 函數。這個函數的參數就將要被預測的數據。 這個函數接受的參數有兩種寫法:
In[80]
# 從新定義 test reader BATCH_SIZE = 4 #設置測試 reader test_reader = paddle.batch( paddle.reader.shuffle( read_data(test_set), buf_size=200), batch_size=BATCH_SIZE)
In[81]
for mini_batch in test_reader(): #真的執行預測 mini_batch_data = feeder.feed(mini_batch) mini_batch_result = inferencer.infer(mini_batch_data) # 打印預測結果 mini_batch_result = np.argsort(mini_batch_result) #找出可能性最大的列標,升序排列 mini_batch_result = mini_batch_result[0][:, -1] #把這些列標拿出來 print('預測結果:%s'%mini_batch_result) # 打印真實結果 label = np.array(mini_batch_data['label']) # 轉化爲 label label = label.flatten() #轉化爲一個 array print('真實結果:%s'%label) break
預測結果:[6 5 5 6] 真實結果:[5 5 6 5]
下面定義評估效果的函數
In[82]
def right_ratio(right_counter, total): ratio = float(right_counter)/total return ratio
In[83]
# 評估函數 data_set 是一個reader def evl(data_set): total = 0 #操做的元素的總數 right_counter = 0 #正確的元素 pass_num = 0 for mini_batch in data_set(): pass_num += 1 #預測 mini_batch_data = feeder.feed(mini_batch) mini_batch_result = inferencer.infer(mini_batch_data) #預測的結果 mini_batch_result = np.argsort(mini_batch_result) #找出可能性最大的列標,升序排列 mini_batch_result = mini_batch_result[0][:, -1] #把這些列標拿出來 # print('預測結果:%s'%mini_batch_result) label = np.array(mini_batch_data['label']) # 轉化爲 label label = label.flatten() #轉化爲一個 array # print('真實結果:%s'%label) #計數 label_len = len(label) total += label_len for i in xrange(label_len): if mini_batch_result[i] == label[i]: right_counter += 1 ratio = right_ratio(right_counter, total) return ratio
In[84]
ratio = evl(train_reader) print('訓練數據的正確率 %0.2f%%'%(ratio*100)) ratio = evl(test_reader) print('預測數據的正確率 %0.2f%%'%(ratio*100))
訓練數據的正確率 44.23% 預測數據的正確率 41.69%
經過這個練習咱們應該記住:
ReLU激活函數比tanh和sigmoid更適合深層神經網絡,由於它不存在梯度消失問題
使用Batch Normalization可以加速模型訓練
利用Softmax能夠解決多分類問題
Adam是一種經常使用的、效果良好的自適應學習率調整優化算法,一般使用它可以獲得不錯的學習效果。
至此,咱們完成了比邏輯迴歸模型稍複雜的多層神經網絡模型的配置和訓練,不難發現,在PaddlePaddle中,只須要經過簡單地疊加或刪除數據層、鏈接層等,就能夠輕易地改變模型結構,自由度很高,你們能夠嘗試使用更多層數的神經網絡或者改變每一層的神經元個數來修改模型,在調試中加深對深度學習的理解和PaddlePaddle框架的熟悉度。
>> 訪問 PaddlePaddle 官網,瞭解更多相關內容。