認識波士頓住房價資料集

  • 透過 tensorflow 引入資料集
    • 波士頓住房價資料集是 1970 年代中期波士頓的郊區資料,包含犯罪率、當地財產稅等。
    from tensorflow.keras.datasets import boston_housing
    (train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
    
    • 與先前兩個範例最大的差別是,資料點明顯較少,共 404 + 102 = 506 筆。
    print(train_data.shape)
    print(train_targets.shape)
    print(test_data.shape)
    print(test_targets.shape)
    
    > (404, 13)
      (404,)
      (102, 13)
      (102,)
    
    • 需注意,每個特徵都有不同的單位刻度
      查看特徵分布
      • 特徵值範圍分析:

        特徵最小值最大值平均值標準差特徵意義
        CRIM0.00688.9763.6148.602城鎮人均犯罪率
        ZN0.000100.00011.36423.322佔地面積超過25000平方呎的住宅用地比例
        INDUS0.46027.74011.1376.860每個城鎮非零售商業用地的比例
        CHAS0.0001.0000.0690.254Charles River 虛擬變數 (1 if tract bounds river; 0 otherwise)
        NOX0.3850.8710.5550.116一氧化氮濃度
        RM3.5618.7806.2850.703每棟住宅的平均房間數
        AGE2.900100.00068.57528.1491940年之前建成的自用房屋比例
        DIS1.13012.1263.7952.106到波士頓5個就業中心的加權距離
        RAD1.00024.0009.5498.707到高速公路的可達性指數
        TAX187.000711.000408.237168.537每10000美元的房產稅率
        PTRATIO12.60022.00018.4562.165城鎮師生比例
        B0.320396.900356.67491.2951000(Bk - 0.63)^2,其中Bk為城鎮中黑人的比例
        LSTAT1.73037.97012.6537.141人口中地位較低人群的百分比
      • 程式碼:

          import numpy as np
          import pandas as pd
      
          # 合併訓練和測試資料
          combined_data = np.vstack((train_data, test_data))
          print("Combined data shape:", combined_data.shape)
      
          # 創建特徵名稱列表 (Boston Housing Dataset 的特徵)
          feature_names = [
              'CRIM',     # 城鎮人均犯罪率
              'ZN',       # 佔地面積超過25000平方呎的住宅用地比例
              'INDUS',    # 每個城鎮非零售商業用地的比例
              'CHAS',     # Charles River 虛擬變數 (1 if tract bounds river; 0 otherwise)
              'NOX',      # 一氧化氮濃度
              'RM',       # 每棟住宅的平均房間數
              'AGE',      # 1940年之前建成的自用房屋比例
              'DIS',      # 到波士頓5個就業中心的加權距離
              'RAD',      # 到高速公路的可達性指數
              'TAX',      # 每10000美元的房產稅率
              'PTRATIO',  # 城鎮師生比例
              'B',        # 1000(Bk - 0.63)^2,其中Bk為城鎮中黑人的比例
              'LSTAT'     # 人口中地位較低人群的百分比
          ]
      
          # 創建 DataFrame 以方便分析
          df = pd.DataFrame(combined_data, columns=feature_names)
      
          # 分析每個特徵的範圍
          feature_analysis = pd.DataFrame({
              'Min': df.min(),
              'Max': df.max(),
              'Mean': df.mean(),
              'Std': df.std()
          }).round(3)
      
          print("\n特徵值範圍分析:")
          print(feature_analysis)
          ```
      

準備資料

  • 首先我們先做正規化處理(normalization),常用的方法是減去特徵的平均值並除以標準差,使平均值為 0 且標準差為 1。 x=xμσ x’ = \frac{x-\mu}{\sigma}
mean = train_data.mean(axis=0)
std = train_data.std(axis=0)
train_data -= mean
train_data /= std
test_data -= mean
test_data /= std

建立模型

  • 一般來說,訓練資料愈少,overfitting 的情況會愈嚴重,所有我們採用較小型的神經網路。
from keras import models
from keras import layers

def build_model():
    model = keras.Sequential([
        layers.Dense(64, activation='relu'),
        layers.Dense(64, activation='relu'),
        layers.Dense(1)
    ])
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
    return model
  • 注意到最後我們以 Dense(1) 結束,沒有套用激活函數,原因是我們希望輸出一個浮點數型別的數值,即迴歸值。若是套用 sigmoid 函數,則神經網路只能輸出 0 到 1 之間的預測值。在此例中,因為最後一層是純線性的,所以神經網路可以自由地預測任何範圍內的值。
  • 我們這邊使用的評量指標是 mae,代表 ypredytrue|\text{y}_ \text{pred}-\text{y}_ \text{true}|,更能直觀的表達與目標值的差異,如 MAE = 0.5,代表與預測值的差異是 $500 美元。

K-fold 驗證

  • 由於資料點很少,驗證資料也會很少,所以驗證分數會因驗證集的選用而有很大的變化,造成驗證分數會有很大的變異性(variance),導致評斷模型優劣的可靠性降低。

  • 因此,我們可以採用K-fold 交叉驗證(K-fold cross validation)。一般會將資料拆分為 K 個區塊,通常 K = 4 or 5。

  • 示意圖:

    • 將 data 分成 K 個部分: data[0:n,:], data[n:2*n,:], data[2*n:3*n,:], data[3*n:4*n,:], data[4*n:5*n,:]
    • 每次取其中一個區塊當驗證集,其餘區塊當訓練集,最後再取 K 次測試分數的平均值作為驗證分數。 part 1part 2part 3part 4part 51st fold驗證訓練訓練訓練訓練測試分數 #12nd fold訓練驗證訓練訓練訓練測試分數 #23rd fold訓練訓練驗證訓練訓練測試分數 #34th fold訓練訓練訓練驗證訓練測試分數 #45th fold訓練訓練訓練訓練驗證測試分數 #5 \begin{array}{cccccccc} &\text{part 1}&\text{part 2}&\text{part 3}&\text{part 4}&\text{part 5}\\ \hline \text{1st fold} & \boxed{\red{\text{驗證}}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \rightarrow & \text{測試分數 \#1}\\ \text{2nd fold} & \boxed{\text{訓練}} & \boxed{\red{\text{驗證}}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \rightarrow & \text{測試分數 \#2}\\ \text{3rd fold} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\red{\text{驗證}}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \rightarrow & \text{測試分數 \#3}\\ \text{4th fold} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\red{\text{驗證}}} & \boxed{\text{訓練}} & \rightarrow & \text{測試分數 \#4}\\ \text{5th fold} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\text{訓練}} & \boxed{\red{\text{驗證}}} & \rightarrow & \text{測試分數 \#5} \end{array}
from os import kill
k = 5
n = len(train_data) // k
epochs = 100
scores = []
for i in range(k):
    print('Processing fold #', i)
    val_data = train_data[i * n: (i+1) * n]
    val_targets = train_targets[i * n: (i+1) * n]
    partial_train_data = np.concatenate(
        [train_data[:i * n],
         train_data[(i+1) * n:]],
        axis=0
    )
    partial_train_targets = np.concatenate(
        [train_targets[:i * n],
         train_targets[(i+1) * n:]],
        axis=0
    )

    model = build_model()
    model.fit(partial_train_data, partial_train_targets, 
              epochs=epochs, batch_size=16, verbose=0)
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
    scores.append(val_mae)
  • 以此為例我們會得到
print(scores)
print(np.mean(scores))

> [1.720955491065979, 2.8763468265533447, 2.1907858848571777, 2.566359043121338, 2.54329252243042]
> 2.379547953605652
  • 最後我們可以用求出來的最佳 epochs 代入全部資料,重新訓練最終的模型。
# 使用全部訓練數據重新訓練模型
final_model = build_model()
final_history = final_model.fit(
    train_data, train_targets,
    epochs=best_epoch,  # 使用找到的最佳 epoch 數
    batch_size=16,
    verbose=1
)
  • 最後我們就可以用 model 來進行預測
predictions = model.predict(test_data)