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ù)求平均。下面的代碼看起來更直白。

圖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()

圖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()

圖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)系我。