目錄
- 房價預測--線性回歸
- 資料處理
- 資料形狀變換
- 資料集劃分
- 資料歸一化處理
- 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
函數中聲明。
實作過程分如下兩步:
- 定義init函數:在類的初始化函數中聲明每一層網絡的實作函數。在房價預測模型中,隻需要定義一層全連接配接層。
- 定義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
訓練配置
訓練配置過程包含四步:
- 以
函數指定運作訓練的機器資源,表明在guard
作用域下的程式均執行在本機的CPU資源上。with
表示在dygraph.guard
作用域下的程式會以飛槳動态圖的模式執行(實時執行)。with
- 聲明定義好的回歸模型Regressor執行個體,并将模型的狀态設定為訓練。
- 使用load_data函數加載訓練資料和測試資料。
- 設定優化算法和學習率,優化算法采用随機梯度下降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())
說明:
- 預設本案例運作在讀者的筆記本上,是以模型訓練的機器資源為CPU。
- 模型執行個體有兩種狀态:訓練狀态
和預測狀态.train()
。訓練時要執行正向計算和反向傳播梯度兩個過程,而預測時隻需要執行正向計算。為模型指定運作狀态,有兩點原因:.eval()
(1)部分進階的算子(例如Drop out和Batch Normalization,在計算機視覺的章節會詳細介紹)在兩個狀态執行的邏輯不同。
(2)從性能和存儲空間的考慮,預測狀态時更節省記憶體,性能更好。
- 在上述代碼中可以發現聲明模型、定義優化器等操作都在
建立的fluid.dygraph.guard()上下文環境中進行,可以了解為with
建立了飛槳動态圖的工作環境,在該環境下完成模型聲明、資料轉換及模型訓練等操作。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。
每次内層循環都需要執行如下四個步驟。
- 資料準備:将一個批次的資料轉變成np.array和内置格式。
- 前向計算:将一個批次的樣本資料灌入網絡中,計算輸出結果。
- 計算損失函數:以前向計算結果和真實房價作為輸入,通過損失函數square_error_cost計算出損失函數值(Loss)。飛槳所有的API接口都有完整的說明和使用案例。
- 反向傳播:執行梯度反向傳播
函數,即從後到前逐層計算每一層的梯度,并根據設定的優化算法更新參數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中")
理論而言,直接使用模型執行個體即可完成預測,而本教程中預測的方式為什麼是先儲存模型,再加載模型呢?這是因為在實際應用中,訓練模型和使用模型往往是不同的場景。模型訓練通常使用大量的線下伺服器(不對外向企業的客戶/使用者提供線上服務),而模型預測則通常使用線上提供預測服務的伺服器,或者将已經完成的預測模型嵌入手機或其他終端裝置中使用。是以本教程的講解方式更貼合真實場景的使用方法。
飛槳的願景是使用者隻需要了解模型的邏輯概念,不需要關心實作細節,就能搭建強大的模型。
測試模型
下面我們選擇一條資料樣本,測試下模型的預測效果。測試過程和在應用場景中使用模型的過程一緻,主要可分成如下三個步驟:
- 配置模型預測的機器資源。本案例預設使用本機,是以無需寫代碼指定。
- 将訓練好的模型參數加載到模型執行個體中。由兩個語句完成,第一句是從檔案中讀取模型參數;第二句是将參數内容加載到模型。加載完畢後,需要将模型的狀态調整為
(校驗)。上文中提到,訓練狀态的模型需要同時支援前向計算和反向傳導梯度,模型的實作較為臃腫,而校驗和預測狀态的模型隻需要支援前向計算,模型的實作更加簡單,性能更好。eval()
- 将待預測的樣本特征輸入到模型中,列印輸出的預測結果。
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))