基本迴歸:預測燃油效率

在 TensorFlow.org 上檢視 在 Google Colab 中執行 在 GitHub 上檢視原始碼 下載筆記本

迴歸問題中,目標是預測連續值的輸出,例如價格或機率。這與分類問題形成對比,分類問題的目標是從類別清單中選取類別 (例如,圖片包含蘋果或橘子,辨識圖片中的水果)。

本教學課程使用經典的 Auto MPG 資料集,並示範如何建構模型來預測 1970 年代末期和 1980 年代初期汽車的燃油效率。若要執行這項操作,您將提供模型當時許多汽車的描述。此描述包括汽缸數、排氣量、馬力和重量等屬性。

本範例使用 Keras API。(如需瞭解詳情,請造訪 Keras 教學課程指南。)

# Use seaborn for pairplot.
pip install -q seaborn
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

# Make NumPy printouts easier to read.
np.set_printoptions(precision=3, suppress=True)
import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers

print(tf.__version__)

Auto MPG 資料集

資料集可從 UCI Machine Learning Repository 取得。

取得資料

首先使用 pandas 下載並匯入資料集

url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data'
column_names = ['MPG', 'Cylinders', 'Displacement', 'Horsepower', 'Weight',
                'Acceleration', 'Model Year', 'Origin']

raw_dataset = pd.read_csv(url, names=column_names,
                          na_values='?', comment='\t',
                          sep=' ', skipinitialspace=True)
dataset = raw_dataset.copy()
dataset.tail()

清理資料

資料集包含一些不明值

dataset.isna().sum()

捨棄這些列以保持本入門教學課程的簡潔性

dataset = dataset.dropna()

"Origin" 欄是類別資料,而非數值資料。因此,下一步是使用 pd.get_dummies 對欄中的值進行 one-hot 編碼。

dataset['Origin'] = dataset['Origin'].map({1: 'USA', 2: 'Europe', 3: 'Japan'})
dataset = pd.get_dummies(dataset, columns=['Origin'], prefix='', prefix_sep='')
dataset.tail()

將資料分割為訓練集和測試集

現在,將資料集分割為訓練集和測試集。您將在模型的最終評估中使用測試集。

train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)

檢查資料

檢閱訓練集中幾對欄的聯合分佈。

頂端列表示燃油效率 (MPG) 是所有其他參數的函數。其他列表示它們是彼此的函數。

sns.pairplot(train_dataset[['MPG', 'Cylinders', 'Displacement', 'Weight']], diag_kind='kde')

我們也來檢查整體統計資料。請注意每個特徵涵蓋的範圍差異很大

train_dataset.describe().transpose()

從標籤分割特徵

從特徵中分離目標值 (「標籤」)。此標籤是您將訓練模型預測的值。

train_features = train_dataset.copy()
test_features = test_dataset.copy()

train_labels = train_features.pop('MPG')
test_labels = test_features.pop('MPG')

正規化

在統計資料表中,很容易看出每個特徵的範圍差異有多大

train_dataset.describe().transpose()[['mean', 'std']]

最好將使用不同比例和範圍的特徵正規化。

這項作業之所以重要,原因之一是特徵會乘以模型權重。因此,輸出的比例和梯度的比例會受到輸入比例的影響。

雖然模型可能在沒有特徵正規化的情況下收斂,但正規化可讓訓練更穩定。

正規化層

tf.keras.layers.Normalization 是將特徵正規化新增至模型的簡潔方法。

第一步是建立層

normalizer = tf.keras.layers.Normalization(axis=-1)

接著,呼叫 Normalization.adapt,將預先處理層的狀態擬合到資料

normalizer.adapt(np.array(train_features))

計算平均值和變異數,並將其儲存在層中

print(normalizer.mean.numpy())

呼叫層時,它會傳回輸入資料,且每個特徵都會獨立正規化

first = np.array(train_features[:1])

with np.printoptions(precision=2, suppress=True):
  print('First example:', first)
  print()
  print('Normalized:', normalizer(first).numpy())

線性迴歸

在建構深度神經網路模型之前,先從使用單一變數和多個變數的線性迴歸開始。

單一變數線性迴歸

從單一變數線性迴歸開始,以根據 'Horsepower' 預測 'MPG'

使用 tf.keras 訓練模型通常從定義模型架構開始。使用 tf.keras.Sequential 模型,此模型代表一系列步驟

您的單一變數線性迴歸模型中有兩個步驟

  • 使用 tf.keras.layers.Normalization 預先處理層正規化 'Horsepower' 輸入特徵。
  • 套用線性轉換 (\(y = mx+b\)),使用線性層 (tf.keras.layers.Dense) 產生 1 個輸出。

輸入數量可以透過 input_shape 引數設定,也可以在模型首次執行時自動設定。

首先,建立由 'Horsepower' 特徵組成的 NumPy 陣列。然後,例項化 tf.keras.layers.Normalization,並將其狀態擬合到 horsepower 資料

horsepower = np.array(train_features['Horsepower'])

horsepower_normalizer = layers.Normalization(input_shape=[1,], axis=None)
horsepower_normalizer.adapt(horsepower)

建構 Keras Sequential 模型

horsepower_model = tf.keras.Sequential([
    horsepower_normalizer,
    layers.Dense(units=1)
])

horsepower_model.summary()

此模型將根據 'Horsepower' 預測 'MPG'

在最初 10 個 'Horsepower' 值上執行未訓練的模型。輸出結果不會太好,但請注意,其具有預期的 (10, 1) 形狀

horsepower_model.predict(horsepower[:10])

模型建構完成後,請使用 Keras Model.compile 方法設定訓練程序。編譯最重要的引數是 lossoptimizer,因為這些引數定義了將最佳化的內容 (mean_absolute_error) 和方式 (使用 tf.keras.optimizers.Adam)。

horsepower_model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
    loss='mean_absolute_error')

使用 Keras Model.fit 執行 100 個週期的訓練

%%time
history = horsepower_model.fit(
    train_features['Horsepower'],
    train_labels,
    epochs=100,
    # Suppress logging.
    verbose=0,
    # Calculate validation results on 20% of the training data.
    validation_split = 0.2)

使用儲存在 history 物件中的統計資料,視覺化模型的訓練進度

hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
def plot_loss(history):
  plt.plot(history.history['loss'], label='loss')
  plt.plot(history.history['val_loss'], label='val_loss')
  plt.ylim([0, 10])
  plt.xlabel('Epoch')
  plt.ylabel('Error [MPG]')
  plt.legend()
  plt.grid(True)
plot_loss(history)

收集測試集上的結果以供稍後使用

test_results = {}

test_results['horsepower_model'] = horsepower_model.evaluate(
    test_features['Horsepower'],
    test_labels, verbose=0)

由於這是單一變數迴歸,因此很容易將模型的預測視為輸入的函數

x = tf.linspace(0.0, 250, 251)
y = horsepower_model.predict(x)
def plot_horsepower(x, y):
  plt.scatter(train_features['Horsepower'], train_labels, label='Data')
  plt.plot(x, y, color='k', label='Predictions')
  plt.xlabel('Horsepower')
  plt.ylabel('MPG')
  plt.legend()
plot_horsepower(x, y)

多個輸入的線性迴歸

您可以使用幾乎相同的設定,根據多個輸入進行預測。此模型仍然執行相同的 \(y = mx+b\) 運算,只是 \(m\) 是矩陣,而 \(x\) 是向量。

再次建立雙步驟 Keras Sequential 模型,第一個步驟是您先前定義並調整為整個資料集的 normalizer (tf.keras.layers.Normalization(axis=-1))

linear_model = tf.keras.Sequential([
    normalizer,
    layers.Dense(units=1)
])

當您在輸入批次上呼叫 Model.predict 時,它會為每個範例產生 units=1 個輸出

linear_model.predict(train_features[:10])

當您呼叫模型時,其權重矩陣將會建構,請檢查 kernel 權重 (即 \(y=mx+b\) 中的 \(m\)) 是否具有 (9, 1) 的形狀

linear_model.layers[1].kernel

使用 Keras Model.compile 設定模型,並使用 Model.fit 訓練 100 個週期

linear_model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
    loss='mean_absolute_error')
%%time
history = linear_model.fit(
    train_features,
    train_labels,
    epochs=100,
    # Suppress logging.
    verbose=0,
    # Calculate validation results on 20% of the training data.
    validation_split = 0.2)

在此迴歸模型中使用所有輸入,比具有單一輸入的 horsepower_model 獲得更低的訓練和驗證錯誤

plot_loss(history)

收集測試集上的結果以供稍後使用

test_results['linear_model'] = linear_model.evaluate(
    test_features, test_labels, verbose=0)

深度神經網路 (DNN) 迴歸

在上一節中,您實作了適用於單一輸入和多個輸入的兩個線性模型。

在此,您將實作單一輸入和多個輸入 DNN 模型。

程式碼基本上相同,只是擴充了模型以包含一些「隱藏」的非線性層。「隱藏」在此僅表示未直接連接到輸入或輸出。

這些模型將包含比線性模型更多的層

  • 與之前相同的正規化層 (單一輸入模型使用 horsepower_normalizer,多個輸入模型使用 normalizer)。
  • 兩個隱藏的非線性 Dense 層,具有 ReLU (relu) 啟動函數非線性。
  • 線性 Dense 單一輸出層。

這兩個模型都將使用相同的訓練程序,因此 compile 方法包含在下方的 build_and_compile_model 函數中。

def build_and_compile_model(norm):
  model = keras.Sequential([
      norm,
      layers.Dense(64, activation='relu'),
      layers.Dense(64, activation='relu'),
      layers.Dense(1)
  ])

  model.compile(loss='mean_absolute_error',
                optimizer=tf.keras.optimizers.Adam(0.001))
  return model

使用 DNN 和單一輸入進行迴歸

建立 DNN 模型,僅使用 'Horsepower' 作為輸入,並使用 horsepower_normalizer (先前定義) 作為正規化層

dnn_horsepower_model = build_and_compile_model(horsepower_normalizer)

此模型具有比線性模型更多的可訓練參數

dnn_horsepower_model.summary()

使用 Keras Model.fit 訓練模型

%%time
history = dnn_horsepower_model.fit(
    train_features['Horsepower'],
    train_labels,
    validation_split=0.2,
    verbose=0, epochs=100)

此模型的效能比線性單一輸入 horsepower_model 略好

plot_loss(history)

如果您將預測繪製為 'Horsepower' 的函數,您應該會注意到此模型如何利用隱藏層提供的非線性

x = tf.linspace(0.0, 250, 251)
y = dnn_horsepower_model.predict(x)
plot_horsepower(x, y)

收集測試集上的結果以供稍後使用

test_results['dnn_horsepower_model'] = dnn_horsepower_model.evaluate(
    test_features['Horsepower'], test_labels,
    verbose=0)

使用 DNN 和多個輸入進行迴歸

使用所有輸入重複先前的程序。模型的效能在驗證資料集上略有提升。

dnn_model = build_and_compile_model(normalizer)
dnn_model.summary()
%%time
history = dnn_model.fit(
    train_features,
    train_labels,
    validation_split=0.2,
    verbose=0, epochs=100)
plot_loss(history)

收集測試集上的結果

test_results['dnn_model'] = dnn_model.evaluate(test_features, test_labels, verbose=0)

效能

由於所有模型都已訓練完成,您可以檢閱其測試集效能

pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T

這些結果與訓練期間觀察到的驗證錯誤相符。

進行預測

您現在可以使用 Keras Model.predict 在測試集上使用 dnn_model 進行預測,並檢閱損失

test_predictions = dnn_model.predict(test_features).flatten()

a = plt.axes(aspect='equal')
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
lims = [0, 50]
plt.xlim(lims)
plt.ylim(lims)
_ = plt.plot(lims, lims)

模型似乎預測得相當好。

現在,檢查錯誤分佈

error = test_predictions - test_labels
plt.hist(error, bins=25)
plt.xlabel('Prediction Error [MPG]')
_ = plt.ylabel('Count')

如果您對模型感到滿意,請使用 Model.save 儲存模型以供日後使用

dnn_model.save('dnn_model.keras')

如果您重新載入模型,它會提供相同的輸出

reloaded = tf.keras.models.load_model('dnn_model.keras')

test_results['reloaded'] = reloaded.evaluate(
    test_features, test_labels, verbose=0)
pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T

結論

本筆記本介紹了一些處理迴歸問題的技巧。以下是一些可能有幫助的訣竅

  • 均方誤差 (MSE) (tf.keras.losses.MeanSquaredError) 和平均絕對誤差 (MAE) (tf.keras.losses.MeanAbsoluteError) 是迴歸問題常用的損失函數。MAE 對離群值較不敏感。分類問題使用不同的損失函數。
  • 同樣地,迴歸使用的評估指標與分類不同。
  • 當數值輸入資料特徵的值具有不同範圍時,每個特徵都應獨立縮放到相同範圍。
  • 過度擬合是 DNN 模型的常見問題,雖然這在本教學課程中不是問題。請造訪過度擬合與擬合不足教學課程以取得更多相關協助。
# MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.