天天看點

深度學習經典入門項目—波士頓房價預測

目錄

  • ​​房價預測--線性回歸​​
  • ​​資料處理​​
  • ​​資料形狀變換​​
  • ​​資料集劃分​​
  • ​​資料歸一化處理​​
  • ​​housing.data資料格式​​
  • ​​模型設計​​
  • ​​線性回歸模型設計​​
  • ​​訓練配置​​
  • ​​訓練過程​​
  • ​​儲存并測試模型​​
  • ​​儲存模型​​
  • ​​測試模型​​

房價預測–線性回歸

波士頓房價預測資料集是經典的機器學習、深度學習入門的資料集。下面我們用這個資料集完成房價預測任務。

深度學習經典入門項目—波士頓房價預測

學習目标:

1.了解深度學習架構編寫代碼的基本套路;

2.了解線性回歸任務的基本模式;

#加載飛槳、Numpy和相關類庫
import paddle
import paddle.fluid as fluid
import paddle.fluid.dygraph as dygraph
from paddle.fluid.dygraph import Linear
import numpy as np
import os
import      

代碼中參數含義如下:

  • paddle/fluid:飛槳的主庫,目前大部分的實用函數均在paddle.fluid包内。
  • dygraph:動态圖的類庫。
  • Linear:神經網絡的全連接配接層函數,即包含所有輸入權重相加和激活函數的基本神經元結構。在房價預測任務中,使用隻有一層的神經網絡(全連接配接層)來實作線性回歸模型。

說明:

飛槳支援兩種深度學習模組化編寫方式,更友善調試的動态圖模式和性能更好并便于部署的靜态圖模式。

  • 靜态圖模式(聲明式程式設計範式,類比C++):先編譯後執行的方式。使用者需預先定義完整的網絡結構,再對網絡結構進行編譯優化後,才能執行獲得計算結果。
  • 動态圖模式(指令式程式設計範式,類比Python):解析式的執行方式。使用者無需預先定義完整的網絡結構,每寫一行網絡代碼,即可同時獲得計算結果。

為了學習模型和調試的友善,本教程均使用動态圖模式編寫模型。在後續的資深教程中,會詳細介紹靜态圖以及将動态圖模型轉成靜态圖的方法。僅在部分場景下需要模型轉換,并且是相對容易的。

資料處理

資料處理包含五個部分:資料導入、資料形狀變換、資料集劃分、資料歸一化處理和封裝load data函數。資料預處理後,才能被模型調用。

資料處理的代碼不依賴架構實作,直接使用 python 來完成任務。

資料形狀變換

由于讀入的原始資料是1維的,所有資料都連在一起。是以需要我們将資料的形狀進行變換,形成一個2維的矩陣,每行為一個資料樣本(14個值),每個資料樣本包含13個X(影響房價的特征)和一個Y(該類型房屋的均價)。

資料集劃分

将資料集劃分成訓練集和測試集,其中訓練集用于确定模型的參數,測試集用于評判模型的效果。為什麼要對資料集進行拆分,而不能直接應用于模型訓練呢?這與學生時代的授課和考試關系比較類似。

深度學習經典入門項目—波士頓房價預測

上學時總有一些自作聰明的同學,平時不認真學習,考試前臨陣抱佛腳,将習題死記硬背下來,但是成績往往并不好。因為學校期望學生掌握的是知識,而不僅僅是習題本身。另出新的考題,才能鼓勵學生努力去掌握習題背後的原理。同樣我們期望模型學習的是任務的本質規律,而不是訓練資料本身,模型訓練未使用的資料,才能更真實的評估模型的效果。

在本案例中,我們将80%的資料用作訓練集,20%用作測試集,實作代碼如下。通過列印訓練集的形狀,可以發現共有404個樣本,每個樣本含有13個特征和1個預測值。

資料歸一化處理

對每個特征進行歸一化處理,使得每個特征的取值縮放到0~1之間。這樣做有兩個好處:一是模型訓練更高效;二是特征前的權重大小可以代表該變量對預測結果的貢獻度(因為每個特征值本身的範圍相同)。

def load_data():
    # 從檔案導入資料
    datafile = './work/housing .data'
    data = np.fromfile(datafile, sep=' ')

    # 每條資料包括14項,其中前面13項是影響因素,第14項是相應的房屋價格中位數
    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', \
                      'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
    feature_num = len(feature_names)

    # 将原始資料進行Reshape,變成[N, 14]這樣的形狀
    data = data.reshape([data.shape[0] // feature_num, feature_num])

    # 将原資料集拆分成訓練集和測試集
    # 這裡使用80%的資料做訓練,20%的資料做測試
    # 測試集和訓練集必須是沒有交集的
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    training_data = data[:offset]

    # 計算train資料集的最大值,最小值,平均值
    maximums, minimums, avgs = training_data.max(axis=0), training_data.min(axis=0), \
                                 training_data.sum(axis=0) / training_data.shape[0]
    
    # 記錄資料的歸一化參數,在預測時對資料做歸一化
    global max_values
    global min_values
    global avg_values
    max_values = maximums
    min_values = minimums
    avg_values = avgs

    # 對資料進行歸一化處理
    for i in range(feature_num):
        #print(maximums[i], minimums[i], avgs[i])
        data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])

    # 訓練集和測試集的劃分比例
    #ratio = 0.8
    #offset = int(data.shape[0] * ratio)
    training_data = data[:offset]
    test_data = data[offset:]
    return training_data,      

housing.data資料格式

深度學習經典入門項目—波士頓房價預測

百度網盤下載下傳連結如下:

連結: ​​https://pan.baidu.com/s/1ShQ7FxyatK_akkfGWOKApQ​​ 密碼: 3lbj

模型設計

模型設計是深度學習模型關鍵要素之一,也稱為網絡結構設計,相當于模型的假設空間,即實作模型“前向計算”(從輸入到輸出)的過程。模型定義的實質是定義線性回歸的網絡結構,飛槳建議通過建立Python類的方式完成模型網絡的定義,即定義​

​init​

​​函數和​

​forward​

​​函數。​

​forward​

​​函數是架構指定實作前向計算邏輯的函數,程式在調用模型執行個體時會自動執行forward方法。在​

​forward​

​​函數中使用的網絡層需要在​

​init​

​函數中聲明。

實作過程分如下兩步:

  1. 定義init函數:在類的初始化函數中聲明每一層網絡的實作函數。在房價預測模型中,隻需要定義一層全連接配接層。
  2. 定義forward函數:建構神經網絡結構,實作前向計算過程,并傳回預測結果,在本任務中傳回的是房價預測結果。

線性回歸模型設計

如果将輸入特征和輸出預測值均以向量表示,輸入特征有13個分量,有1個分量,那麼參數權重的形狀(shape)是。假設我們以如下任意數字指派參數做初始化:

假設房價和各影響因素之間能夠用線性關系來描述:

模型的求解即是通過資料拟合出每個和。其中,和分别表示該線性模型的權重和偏置。一維情況下, 和

線性回歸模型使用均方誤差作為損失函數(Loss),用以衡量預測房價和真實房價的差異,公式如下:

class Regressor(fluid.dygraph.Layer):
    def __init__(self):
        super(Regressor, self).__init__()
        
        # 定義一層全連接配接層,輸出次元是1,激活函數為None,即不使用激活函數
         self.fc = Linear(input_dim=13, output_dim=1, act=None)
    
    # 網絡的前向計算函數
    def forward(self, inputs):
        x = self.fc(inputs)
        return      

訓練配置

訓練配置過程包含四步:

深度學習經典入門項目—波士頓房價預測
  1. 以​

    ​guard​

    ​​函數指定運作訓練的機器資源,表明在​

    ​with​

    ​​作用域下的程式均執行在本機的CPU資源上。​

    ​dygraph.guard​

    ​​表示在​

    ​with​

    ​作用域下的程式會以飛槳動态圖的模式執行(實時執行)。
  2. 聲明定義好的回歸模型Regressor執行個體,并将模型的狀态設定為訓練。
  3. 使用load_data函數加載訓練資料和測試資料。
  4. 設定優化算法和學習率,優化算法采用随機梯度下降​​SGD​​,學習率設定為0.01。

訓練配置代碼如下所示:

# 定義飛槳動态圖的工作環境
with fluid.dygraph.guard():
    # 聲明定義好的線性回歸模型
    model = Regressor()
    # 開啟模型訓練模式
    model.train()
    # 加載資料
    training_data, test_data = load_data() 
    # 定義優化算法,這裡使用随機梯度下降-SGD
    # 學習率設定為0.01
    opt = fluid.optimizer.SGD(learning_rate=0.01, parameter_list=model.parameters())      

說明:

  1. 預設本案例運作在讀者的筆記本上,是以模型訓練的機器資源為CPU。
  2. 模型執行個體有兩種狀态:訓練狀态​

    ​.train()​

    ​​和預測狀态​

    ​.eval()​

    ​。訓練時要執行正向計算和反向傳播梯度兩個過程,而預測時隻需要執行正向計算。為模型指定運作狀态,有兩點原因:

(1)部分進階的算子(例如Drop out和Batch Normalization,在計算機視覺的章節會詳細介紹)在兩個狀态執行的邏輯不同。

(2)從性能和存儲空間的考慮,預測狀态時更節省記憶體,性能更好。

  1. 在上述代碼中可以發現聲明模型、定義優化器等操作都在​

    ​with​

    ​​建立的​​fluid.dygraph.guard()​​​上下文環境中進行,可以了解為​

    ​with fluid.dygraph.guard()​

    ​建立了飛槳動态圖的工作環境,在該環境下完成模型聲明、資料轉換及模型訓練等操作。

訓練過程

訓練過程采用二層循環嵌套方式:

  • 内層循環: 負責整個資料集的一次周遊,采用分批次方式(batch)。假設資料集樣本數量為1000,一個批次有10個樣本,則周遊一次資料集的批次數量是1000/10=100,即内層循環需要執行100次。
for iter_id, mini_batch in enumerate(mini_batches):      
  • 外層循環: 定義周遊資料集的次數,通過參數EPOCH_NUM設定。
for epoch_id in range(EPOCH_NUM):      

說明:

batch的取值會影響模型訓練效果。batch過大,會增大記憶體消耗和計算時間,且效果并不會明顯提升;batch過小,每個batch的樣本資料将沒有統計意義。由于房價預測模型的訓練資料集較小,我們将batch為設定10。

每次内層循環都需要執行如下四個步驟。

深度學習經典入門項目—波士頓房價預測
  1. 資料準備:将一個批次的資料轉變成np.array和内置格式。
  2. 前向計算:将一個批次的樣本資料灌入網絡中,計算輸出結果。
  3. 計算損失函數:以前向計算結果和真實房價作為輸入,通過損失函數​​square_error_cost​​計算出損失函數值(Loss)。飛槳所有的API接口都有完整的說明和使用案例。
  4. 反向傳播:執行梯度反向傳播​

    ​backward​

    ​​函數,即從後到前逐層計算每一層的梯度,并根據設定的優化算法更新參數​

    ​opt.minimize​

    ​。

因為計算損失時需要把每個樣本的損失都考慮到,是以我們需要對單個樣本的損失函數進行求和,并除以樣本總數。

with dygraph.guard(fluid.CPUPlace()):
    EPOCH_NUM = 10   # 設定外層循環次數
    BATCH_SIZE = 10  # 設定batch大小
    
    # 定義外層循環
    for epoch_id in range(EPOCH_NUM):
        # 在每輪疊代開始之前,将訓練資料的順序随機的打亂
        np.random.shuffle(training_data)
        # 将訓練資料進行拆分,每個batch包含10條資料
        mini_batches = [training_data[k:k+BATCH_SIZE] for k in range(0, len(training_data), BATCH_SIZE)]
        # 定義内層循環
        for iter_id, mini_batch in enumerate(mini_batches):
            x = np.array(mini_batch[:, :-1]).astype('float32') # 獲得目前批次訓練資料
            y = np.array(mini_batch[:, -1:]).astype('float32') # 獲得目前批次訓練标簽(真實房價)
            # 将numpy資料轉為飛槳動态圖variable形式
            house_features = dygraph.to_variable(x)
            prices = dygraph.to_variable(y)
            
            # 前向計算
            predicts = model(house_features)
            
            # 計算損失
            loss = fluid.layers.square_error_cost(predicts, label=prices)
            avg_loss = fluid.layers.mean(loss)
            if iter_id%20==0:
                print("epoch: {}, iter: {}, loss is: {}".format(epoch_id, iter_id, avg_loss.numpy()))
            
            # 反向傳播
            avg_loss.backward()
            # 最小化loss,更新參數
            opt.minimize(avg_loss)
            # 清除梯度
            model.clear_gradients()
    # 儲存模型
    fluid.save_dygraph(model.state_dict(), 'LR_model')      

儲存并測試模型

儲存模型

将模型目前的參數資料​

​model.state_dict()​

​儲存到檔案中(通過參數指定儲存的檔案名 LR_model),以備預測或校驗的程式調用,代碼如下所示。

# 定義飛槳動态圖工作環境
with fluid.dygraph.guard():
    # 儲存模型參數,檔案名為LR_model
    fluid.save_dygraph(model.state_dict(), 'LR_model')
    print("模型儲存成功,模型參數儲存在LR_model中")      

理論而言,直接使用模型執行個體即可完成預測,而本教程中預測的方式為什麼是先儲存模型,再加載模型呢?這是因為在實際應用中,訓練模型和使用模型往往是不同的場景。模型訓練通常使用大量的線下伺服器(不對外向企業的客戶/使用者提供線上服務),而模型預測則通常使用線上提供預測服務的伺服器,或者将已經完成的預測模型嵌入手機或其他終端裝置中使用。是以本教程的講解方式更貼合真實場景的使用方法。

飛槳的願景是使用者隻需要了解模型的邏輯概念,不需要關心實作細節,就能搭建強大的模型。

測試模型

下面我們選擇一條資料樣本,測試下模型的預測效果。測試過程和在應用場景中使用模型的過程一緻,主要可分成如下三個步驟:

  1. 配置模型預測的機器資源。本案例預設使用本機,是以無需寫代碼指定。
  2. 将訓練好的模型參數加載到模型執行個體中。由兩個語句完成,第一句是從檔案中讀取模型參數;第二句是将參數内容加載到模型。加載完畢後,需要将模型的狀态調整為​

    ​eval()​

    ​(校驗)。上文中提到,訓練狀态的模型需要同時支援前向計算和反向傳導梯度,模型的實作較為臃腫,而校驗和預測狀态的模型隻需要支援前向計算,模型的實作更加簡單,性能更好。
  3. 将待預測的樣本特征輸入到模型中,列印輸出的預測結果。
def load_one_example(data_dir):
    f = open(data_dir, 'r')
    datas = f.readlines()
    # 選擇倒數第10條資料用于測試
    tmp = datas[-10]
    tmp = tmp.strip().split()
    one_data = [float(v) for v in tmp]

    # 對資料進行歸一化處理
    for i in range(len(one_data)-1):
        one_data[i] = (one_data[i] - avg_values[i]) / (max_values[i] - min_values[i])

    data = np.reshape(np.array(one_data[:-1]), [1, -1]).astype(np.float32)
    label = one_data[-1]
    return data,      
with dygraph.guard():
    # 參數為儲存模型參數的檔案位址
    model_dict, _ = fluid.load_dygraph('LR_model')
    model.load_dict(model_dict)
    model.eval()

    # 參數為資料集的檔案位址
    test_data, label = load_one_example('./work/housing .data')
    # 将資料轉為動态圖的variable格式
    test_data = dygraph.to_variable(test_data)
    results = model(test_data)

    # 對結果做反歸一化處理
    results = results * (max_values[-1] - min_values[-1]) + avg_values[-1]
    print("Inference result is {}, the corresponding label is {}".format(results.numpy(), label))