《Deep Learning with Python》第三章 3.6 走進(jìn)神經(jīng)網(wǎng)絡(luò)之房價(jià)預(yù)測

3.6 房價(jià)預(yù)測:線性回歸

前面兩個(gè)例子都可以看成是分類問題,它的目標(biāo)是預(yù)測某個(gè)輸入數(shù)據(jù)點(diǎn)的單個(gè)離散label。常見的另外一類機(jī)器學(xué)習(xí)問題是線性回歸,其預(yù)測的是一個(gè)連續(xù)值,而不是離散label。比如,根據(jù)氣象信息預(yù)測明天的氣溫;根據(jù)軟件項(xiàng)目計(jì)劃書預(yù)測實(shí)現(xiàn)時(shí)間。

注意:不要混淆線性回歸和邏輯回歸算法。邏輯回歸不是回歸算法,而是分類算法。

3.6.1 波士頓房價(jià)數(shù)據(jù)集

波士頓房價(jià)數(shù)據(jù)集是1970年代中期波士頓郊區(qū)的數(shù)據(jù)樣本,包含犯罪率、不動(dòng)產(chǎn)稅稅率等。你將用該數(shù)據(jù)集預(yù)測當(dāng)?shù)胤績r(jià)的中間價(jià)。波士頓房價(jià)數(shù)據(jù)集與前面兩個(gè)例子都不太一樣,數(shù)據(jù)樣本點(diǎn)相當(dāng)少:只有506個(gè),其中404個(gè)作為訓(xùn)練樣本和102個(gè)測試樣本。輸入數(shù)據(jù)的每個(gè)特征都有不同的scale。例如,一些比例值,取值范圍在0和1之間;另一些取值在1到12之間;還有些取值在0到100之間,等等。

#Listing 3.24 Loading the Boston housing dataset
from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()

查看數(shù)據(jù)樣本:

>>> train_data.shape
(404, 13)
>>> test_data.shape
(102, 13)

從上面返回的結(jié)果可以看出,有404個(gè)訓(xùn)練樣本和102個(gè)測試樣本,每個(gè)樣本有13個(gè)數(shù)值型特征,比如犯罪率,每個(gè)住處的房屋平均數(shù)量,高速可達(dá)性,等等。

target是自住房屋的中間價(jià),單位為千美元:

>>> train_targets
[ 15.2, 42.3, 50. ... 19.4, 19.4, 29.1]

房價(jià)普遍在$10000和$50000之間。價(jià)格看起來便宜,但是要記住這是1970年代中期。

3.6.2 準(zhǔn)備數(shù)據(jù)

把不同取值范圍的變量值賦給神經(jīng)網(wǎng)絡(luò)會(huì)出現(xiàn)問題。雖然神經(jīng)網(wǎng)絡(luò)會(huì)自適應(yīng)各種各樣的數(shù)據(jù),但是這會(huì)造成模型學(xué)習(xí)的過程變得困難。一個(gè)廣泛使用的最佳實(shí)踐是對(duì)此類數(shù)據(jù)集特征進(jìn)行歸一化:對(duì)輸入數(shù)據(jù)的每個(gè)特征(輸入矩陣的一個(gè)列),減去該特征的均值并除以標(biāo)準(zhǔn)方差,這樣每個(gè)特征的均值為0、均方差為1。

#Listing 3.25 Normalizing the data
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std

注意,測試集的歸一化使用的是訓(xùn)練集的均值和均方差。一般來說,你不用對(duì)測試集數(shù)據(jù)進(jìn)行數(shù)量計(jì)算,即使是簡單的數(shù)據(jù)歸一化。

3.6.3 構(gòu)建神經(jīng)網(wǎng)絡(luò)

由于這個(gè)例子中的樣本量比較少,所以選用兩個(gè)隱藏單元為64的hidden layer的神經(jīng)網(wǎng)絡(luò)。一般來說,訓(xùn)練數(shù)據(jù)集越小,過擬合的情況越糟糕。這里使用小規(guī)模神經(jīng)網(wǎng)絡(luò)以減輕過擬合。

#Listing 3.26 Model definition
from keras import models
from keras import layers

def build_model():
'''
Because you’ll need to instantiate
the same model multiple times, you
use a function to construct it.
'''
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(train_data.shape[1],)))
model.add(layers.Dense(64, activation='rely'))
model.add(layers.Dense(1))
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
return model

上面的神經(jīng)網(wǎng)絡(luò)以單元大小為1且不帶激活函數(shù)的Dense layer為結(jié)束,意味著這是標(biāo)量回歸(線性回歸)。應(yīng)用激活函數(shù)可以限制輸出結(jié)果的取值范圍,比如,如果你在最后一個(gè)layer應(yīng)用sigmoid激活函數(shù),神經(jīng)網(wǎng)絡(luò)只會(huì)學(xué)習(xí)0到1之間的預(yù)測值。本例中最后一個(gè)layer是純線性的,神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)任意取值范圍的預(yù)測值。

神經(jīng)網(wǎng)絡(luò)模型編譯使用的mse損失函數(shù):均方誤差(mean squared error)。其損失函數(shù)廣泛應(yīng)用在回歸問題當(dāng)中。

你也可以在模型訓(xùn)練中使用平均絕對(duì)誤差MAE(mean absolute error)。它監(jiān)測的是預(yù)測值和目標(biāo)值差值的絕對(duì)值,比如,MAE為0.5意味著你的預(yù)測值偏離均值$500。

3.6.4 使用K-fold驗(yàn)證模型

為了評(píng)估神經(jīng)網(wǎng)絡(luò)模型的同時(shí)調(diào)整網(wǎng)絡(luò)模型超參(比如模型訓(xùn)練的epoch數(shù)量),你可以將數(shù)據(jù)集分割成訓(xùn)練集和驗(yàn)證集。但是由于數(shù)據(jù)樣本點(diǎn)太少,所以驗(yàn)證集數(shù)量也非常少(比如,大約100個(gè)樣本)。這會(huì)導(dǎo)致驗(yàn)證集上的分?jǐn)?shù)更多的依賴于你選擇哪部分?jǐn)?shù)據(jù)作為驗(yàn)證集哪部分?jǐn)?shù)據(jù)作為訓(xùn)練集:可能因?yàn)榉指畹津?yàn)證集的數(shù)據(jù)點(diǎn)不同而產(chǎn)生較高的variance。這會(huì)影響模型評(píng)估的真實(shí)性。

解決上面問題的最佳方法是使用K-fold交叉驗(yàn)證,見圖3.11。它將數(shù)據(jù)樣本點(diǎn)分割為K個(gè)partition(一般K=4或者5),實(shí)例化K個(gè)相同的模型,分別在K-1個(gè)partition數(shù)據(jù)集訓(xùn)練每個(gè)模型并在余下的partition數(shù)據(jù)集上進(jìn)行評(píng)估。然后將得到的K個(gè)模型的驗(yàn)證集上的分?jǐn)?shù)求平均。下面的代碼看起來更直白。

image

圖3.11 3-fold交叉驗(yàn)證

#Listing 3.27 K-fold validation
import numpy as np

k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = []

for i in range(k):
    print('processing fold #', i)
    '''
    Prepares the validation data: data from partition #k
    '''
    val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]

    '''
    Prepares the training data: data from all other partitions
    '''
    partial_train_data = np.concatenate(
        [train_data[:i * num_val_samples],
         train_data[(i + 1) * num_val_samples:]], axis=0)
    partial_train_targets = np.concatenate(
        [train_targets[:i * num_val_samples],
         train_targets[(i + 1) * num_val_samples:]], axis=0)

    '''
    Builds the Keras model (already compiled)
    '''
    model = build_model()
    '''
    Trains the model (in silent mode, verbose = 0)
    '''
    model.fit(partial_train_data, partial_train_targets, epochs=num_epochs,
              batch_size=1, verbose=0)
    '''
    Evaluates the model on the validation data
    '''
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
    all_scores.append(val_mae)

設(shè)置num_epochs = 100,運(yùn)行得到如下的結(jié)果:

>>> all_scores
[2.588258957792037, 3.1289568449719116, 3.1856116051248984, 3.0763342615401386]
>>> np.mean(all_scores)
2.9947904173572462

每次運(yùn)行的確得到不同的驗(yàn)證分?jǐn)?shù),從2.6到3.2。平均值為3.0,這比任意單個(gè)分?jǐn)?shù)都要穩(wěn)定,這也是K-fold交叉驗(yàn)證的價(jià)值所在。在本例中,偏離平均的大小為$3000,這明顯對(duì)于$10000到$50000范圍的價(jià)格還是可以的。

下面將神經(jīng)網(wǎng)絡(luò)模型訓(xùn)練epoch設(shè)為500。修改迭代訓(xùn)練的代碼保存每個(gè)epoch的驗(yàn)證分?jǐn)?shù):

#Listing 3.28 Saving the validation logs at each fold
num_epochs = 500
all_mae_histories = []

for i in range(k):
    print('processing fold #', i)
    '''
    Prepares the validation data: data from partition #k
    '''
    val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]

    '''
    Prepares the training data: data from all other partitions
    '''
    partial_train_data = np.concatenate(
        [train_data[:i * num_val_samples],
         train_data[(i + 1) * num_val_samples:]], axis=0)
    partial_train_targets = np.concatenate(
        [train_targets[:i * num_val_samples],
         train_targets[(i + 1) * num_val_samples:]], axis=0)

    '''
    Builds the Keras model (already compiled)
    '''
    model = build_model()
    '''
    Trains the model (in silent mode, verbose = 0)
    '''
    history = model.fit(partial_train_data, partial_train_targets,
                        epochs=num_epochs,
                        validation_data=(val_data, val_targets),
                        batch_size=1, verbose=0)
    mae_history = history.history['val_mean_absolute_error']
    all_mae_histories.append(mae_history)

接下來計(jì)算每個(gè)epoch的K個(gè)fold所對(duì)應(yīng)的MAE的平均值:

#Listing 3.29 Building the history of successive mean K-fold validation scores
average_mae_history = [
    np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]

下面繪制驗(yàn)證分?jǐn)?shù)圖表,見3.12。

#Listing 3.30 Plotting validation scores
import matplotlib.pyplot as pet

plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
image

圖3.12 每個(gè)epoch驗(yàn)證集MAE的趨勢圖

看上面的圖表可能有點(diǎn)困難,因?yàn)閿?shù)量級(jí)的問題和相當(dāng)高的variance。下面對(duì)其進(jìn)行處理:

  • 忽略前十個(gè)數(shù)據(jù)點(diǎn),因?yàn)樗鼈兣c曲線上剩余的值不在同一個(gè)量級(jí)上;
  • 用每個(gè)數(shù)據(jù)點(diǎn)的指數(shù)滑動(dòng)平均值代替原數(shù)據(jù)點(diǎn),來平滑曲線

對(duì)應(yīng)的結(jié)果見圖3.13。

#Listing 3.31 Plotting validation scores, excluding the first 10 data points

def smooth_curve(points, factor=0.9):
    smoothed_points = []
    for point in points:
        if smoothed_points:
            previous = smoothed_points[-1]
            smoothed_points.append(previous * factor + point * (1 - factor))
            else:
                smoothed_points.append(point)
            return smoothed_points

smooth_mae_history = smooth_curve(average_mae_history[10:])

plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
image

圖3.13 每個(gè)epoch驗(yàn)證集MAE的趨勢圖(排除前十個(gè)數(shù)據(jù)點(diǎn))

根據(jù)上面的圖表可以看出,驗(yàn)證集的MAE在80個(gè)epoch之后沒有什么顯著的提升。意味著該點(diǎn)之后模型開始過擬合。

一旦你完成模型其它參數(shù)的調(diào)優(yōu)(除了epoch數(shù)量,你可以調(diào)整hidden layer的隱藏單元數(shù)量),你可以用最佳的參數(shù)在所有訓(xùn)練集上訓(xùn)練最終的生產(chǎn)上使用的模型,然后看下該模型在測試集上的效果。

#Listing 3.32 Training the final model

#Gets a fresh, compiled model
model = build_model()

#Trains it on the entirety of the data
model.fit(train_data, train_targets,
          epochs=80, batch_size=16, verbose=0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)

最終結(jié)果如下:

>>> test_mae_score
2.5532484335057877

最后得到的結(jié)果偏離平均為$2550。

3.6.5 小結(jié)

從本例你應(yīng)該學(xué)到以下知識(shí)點(diǎn):

  • 回歸模型的損失函數(shù)與分類問題的不同,常用均方差損失函數(shù)(MSE)
  • 相應(yīng)的,常用的回歸模型指標(biāo)是平均絕對(duì)誤差(MAE),確切地說,回歸模型沒有準(zhǔn)確度的概念
  • 輸入數(shù)據(jù)的取值范圍不同時(shí),應(yīng)該在數(shù)據(jù)預(yù)處理階段將每個(gè)特征進(jìn)行歸一化
  • 當(dāng)數(shù)據(jù)樣本太少時(shí),可以使用K-fold交叉驗(yàn)證穩(wěn)定的評(píng)估一個(gè)模型
  • 當(dāng)訓(xùn)練集數(shù)據(jù)比較少時(shí),傾向于使用小規(guī)模神經(jīng)網(wǎng)絡(luò)(一般是一到兩個(gè)隱藏層),避免過擬合

未完待續(xù)。。。

Enjoy!

翻譯本書系列的初衷是,覺得其中把深度學(xué)習(xí)講解的通俗易懂。不光有實(shí)例,也包含作者多年實(shí)踐對(duì)深度學(xué)習(xí)概念、原理的深度理解。最后說不重要的一點(diǎn),F(xiàn)ran?ois Chollet是Keras作者。
聲明本資料僅供個(gè)人學(xué)習(xí)交流、研究,禁止用于其他目的。如果喜歡,請(qǐng)購買英文原版。


俠天,專注于大數(shù)據(jù)、機(jī)器學(xué)習(xí)和數(shù)學(xué)相關(guān)的內(nèi)容,并有個(gè)人公眾號(hào)分享相關(guān)技術(shù)文章。

若發(fā)現(xiàn)以上文章有任何不妥,請(qǐng)聯(lián)系我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容