- 深度學習訓練營 21天實戰TensorFlow+Keras+scikit-learn
- 張強
- 2455字
- 2020-04-14 15:04:08
1.3 基于Keras實現房價預測
前面我們使用scikit-learn機器學習庫實現了房價預測。本節將使用神經網絡模型來實現房價預測,使用的是TensorFlow下的Keras的API代碼。我們將使用Keras創建模型、訓練模型、預測和對比圖預覽。
Keras封裝了一套高級的API用于構建深度學習模型,常用于快速實現原型設計和高級搜索。Keras對開發者的友好性、模塊化、可組合性和易于擴展的關鍵特征,使其成為目前使用較為廣泛的深度學習開源框架,它的后端之一就是TensorFlow。Keras在2015年12月份就將其以TensorFlow為后端的部分API融合到了TensorFlow框架中,所以目前我們可以通過tf.keras或者獨立的Keras庫來使用它。本書后面的章節會大量使用TensorFlow和Keras來構建深度學習模型。
1.3.1 數據準備
我們通過TensorFlow提供的Keras接口下的datasets模塊來加載數據集。數據集由卡內基梅隆大學維護,是波士頓近郊房價數據。讀者也可以通過前言中的鏈接去查看后續更新的數據集。在tf.keras.datasets下加載的數據集和我們在鏈接中看到的數據集的格式是一樣的,只是年份可能不同。加載數據集的代碼如下。
import tensorflow as tf # 從TensorFlow導入Keras模塊 from tensorflow import keras import numpy as np # 加載波士頓房價數據集 (train_data, train_labels), (test_data, test_labels) = \ keras.datasets.boston_housing.load_data() # 清洗訓練集數據 # np.random.random()表示返回在0.0到1.0之間指定個數的隨機浮點數 # np.argsort()表示返回對數組進行排序的索引 order = np.argsort(np.random.random(train_labels.shape)) train_data = train_data[order] train_labels = train_labels[order] # 歸一化處理數據 # 對不同的范圍和比例進行歸一化處理,并且每個元素都要減去均值后再除以標準差 # 雖然模型在沒有特征歸一化時也可以得到收斂,但是這會讓訓練更加困難, # 而且會導致結果模型依賴于訓練數據 mean = train_data.mean(axis=0) std = train_data.std(axis=0) train_data = (train_data - mean) / std test_data = (test_data - mean) / std print("train_data.shape: {}, train_labels.shape: {}." .format(train_data.shape, train_labels.shape)) print("test_data.shape: {}, test_labels.shape: {}." .format(test_data.shape, test_labels.shape))
輸出如下。
train_data.shape:(404,13),train_labels.shape:(404,). test_data.shape:(102,13),test_labels.shape:(102,).
1.3.2 創建神經網絡模型
在Keras中,我們創建神經網絡模型,就是使用Sequential類來創建Keras模型。本次創建的模型比較簡單,通過Dense類來創建神經網絡層。對于輸入層,層的深度是64個units,輸入層必須傳入input_shape參數,表示輸入數據的特征維度的大小。
激活函數(activation),我們指定的是修正線性單元(ReLU),它是神經網絡中最常用的激活函數。當輸入的值為正數時,導數不為0,返回它本身,這就允許在訓練模型時進行基于梯度的學習,也會使計算變得更快;當輸入的值為負數時,學習速度可能會變得很慢,甚至會使神經元直接失效,這是因為輸入的值是小于0的值,計算它的梯度也為0,從而使其權重無法得到更新,因此在傳播到下一個神經網絡層的時候,返回的值為0就沒有什么意義了。
然后我們再添加一個隱藏層,這一層不需要input_shape參數,因為在輸入層時已經指定了。我們仍然設置層的深度是64個units,激活函數是ReLu。
輸出層只有一個unit,代碼如下。
# 定義創建模型函數 def build_model(): model = keras.Sequential([ keras.layers.Dense(64, activation=tf.nn.relu, input_shape=(train_data.shape[1],)), keras.layers.Dense(64, activation=tf.nn.relu), keras.layers.Dense(1) ]) # 使用RMSProp(均方根傳播)優化器,它可以加速梯度下降,其中學習速度適用于每個參數 optimizer = tf.train.RMSPropOptimizer(0.001) # mse(均方差)一般用于回歸問題的損失函數 # mae(平均絕對誤差)一般用于回歸問題的測量/評估 model.compile(loss='mse', optimizer=optimizer, metrics=['mae']) return model model = build_model() # 查看模型的架構 model.summary()
輸出的模型架構如圖1.12所示。全部參數有5121個,模型中的每一層參數都可以在表格的Param列看到。每一層的輸出大小在Output Shape列中顯示,其中None表示是可變的batch size,它會在訓練模型或者模型預測時被自動填充上具體的值。

圖1.12 一個簡單的Keras模型架構圖
1.3.3 訓練網絡模型
訓練這個模型500次,并且將訓練精確度和驗證精確度記錄在history對象中,以便繪圖預覽。我們自定義一個回調對象類,重寫on_epoch_end()函數,在每次epoch結束時會調用該函數。
# 自定義一個回調對象類,在每次epoch(代)結束時都會調用該函數 class PrintDot(keras.callbacks.Callback): def on_epoch_end(self, epoch, logs): if epoch % 100 == 0: print('') print('.', end='') EPOCHS = 500 # 訓練模型 # 參數1:房屋特征數據 # 參數2:房屋價格數據 # 參數3:迭代次數 # 參數4:驗證集分割比例,0.2表示20%的數據用于驗證,80%的數據用于訓練 # 參數5:輸出打印日志信息,0表示不輸出打印日志信息 # 參數6:回調對象,這里我們使用自定義的回調類PrintDot history = model.fit(train_data, train_labels, epochs=EPOCHS, validation_split=0.2, verbose=0, callbacks=[PrintDot()])
1.3.4 可視化模型的結果
通過history對象,我們可以讀取該模型訓練時的誤差數值,以便于觀察何時是最佳模型,何時應該停止訓練。
import matplotlib.pyplot as plt # 繪制圖來顯示訓練時的accuracy和loss def plot_history(history): plt.figure() plt.xlabel('Epoch') plt.ylabel('Mean Abs Error [1000$]') plt.plot(history.epoch, np.array(history.history['mean_absolute_error']), label='Train Loss') plt.plot(history.epoch, np.array(history.history['val_mean_absolute_error']), label='Val loss') plt.legend() plt.ylim([0, 5]) plt.show() plot_history(history)
輸出如圖1.13所示。

圖1.13 模型訓練時的平均絕對誤差表現圖
可以發現,大約在150到200次迭代時,訓練損失值就沒怎么降低了。所以,這里我們要用到一個降低過擬合技術:早期停止(Early Stopping)。它是指在指定的迭代次數內,如果依舊沒有損失降低、模型性能提升的話,就自動終止訓練。
我們重新構建和訓練該模型,在重新構建模型前,請先清除Keras的內存狀態。最簡單的辦法就是重新運行Jupyter Notebook;如果讀者使用的是終端,就重新啟動該腳本程序。
# 重新構建模型 model = build_model() # 設置早期停止,如果20次的迭代依舊沒有降低驗證損失,則自動停止訓練 early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=20) # 重新訓練模型,此時的callbacks有兩個回調函數,所以我們使用數組的形式傳入它們 history = model.fit(train_data, train_labels, epochs=EPOCHS, validation_split=0.2, verbose=0, callbacks=[early_stop, PrintDot()]) # 打印輸出歷史記錄的曲線圖 plot_history(history)
輸出如圖1.14所示。

圖1.14 訓練和驗證模型時的平均絕對誤差圖(使用早期停止技術)
1.3.5 評估和預測模型
接下來,我們來測試模型在測試集下的表現,這就需要對模型進行評估了。通過evaluate()函數,傳入測試房屋特征數據集,測試房屋價格數據,計算測試集的平均絕對誤差。
[loss, mae] = model.evaluate(test_data, test_labels, verbose=0) print("Testing set Mean Abs Error: ${:7.2f}".format(mae * 1000))
輸出日志如下。
Testing set Mean Abs Error:$2930.86
然后預測模型,通過predict()函數,傳入測試房屋特征數據集,返回預測房價;最后將返回的數據通過flatten()函數進行扁平化處理,以便于繪制散點圖。
# 使用測試數據集預測模型 test_predictions = model.predict(test_data).flatten() plt.scatter(test_labels, test_predictions) plt.xlabel('True Values [1000$]') plt.ylabel('Predictions [1000$]') plt.axis('equal') plt.xlim(plt.xlim()) plt.ylim(plt.ylim()) plt.plot([-100, 100], [-100, 100]) plt.show()
輸出結果如圖1.15所示。

圖1.15 預測房價的回歸模型圖
1.3.6 預測可視化顯示
接下來,我們來看下將真實房價和預測房價進行對比,從而形成的價格差直方圖。我們先計算預測房價和真實房價的差價,代碼如下。
error = test_predictions - test_labels plt.hist(error, bins=50) plt.xlabel("Prediction Error [1000$]") plt.ylabel("Count") plt.show()
輸出如圖1.16所示。

圖1.16 預測房價和真實房價的價格差直方圖
最后,我們通過真實房價和預測房價生成一張更直觀的圖。函數plotVersusFigure()在上面的代碼中已經定義過,這里就不再介紹。
plotVersusFigure(test_labels,test_predictions)
輸出如圖1.17所示。

圖1.17 真實房價與預測房價的對比圖
通過以上分析可知,不管是用scikit-learn的機器學習庫來預測房價,還是使用Keras的神經網絡模型來預測房價,真實房價和預測房價總是有些誤差。所以我們能控制的就是在訓練神經網絡模型時,調整訓練的超參數、迭代次數、網絡層數和優化器等參數,以得到更好的、適用于該房屋數據的預測模型。這里的數據量比較小,如果數據量更大一些,模型效果會更好。