Базовая регрессия: прогнозирование топливной экономичности

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

В задаче регрессии, цель состоит в том, чтобы предсказать результат непрерывной величины, как цена или вероятность. Сравните с проблемой классификации, где целью является выбрать класс из списка классов (например, когда картина содержит яблоко или апельсин признание которых плод на рисунке).

Этот ноутбук использует классический Auto MPG Dataset и строит модель для прогнозирования эффективности использования топлива конце 1970-х и начала 1980 - х автомобилей. Для этого снабдите модель описанием многих автомобилей того времени. Это описание включает такие атрибуты, как: цилиндры, рабочий объем, мощность и вес.

В этом примере используется tf.keras API см этого руководства для деталей.

# 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
from tensorflow.keras.layers.experimental import preprocessing

print(tf.__version__)
2.5.0

Набор данных 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()
MPG             0
Cylinders       0
Displacement    0
Horsepower      6
Weight          0
Acceleration    0
Model Year      0
Origin          0
dtype: int64

Отбросьте эти строки, чтобы не усложнять начальное руководство.

dataset = dataset.dropna()

"Origin" колонна действительно категоричны, не числовой. Так обращенным , что один-горячий с pd.get_dummies :

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')
<seaborn.axisgrid.PairGrid at 0x7f46335d2f90>

PNG

Также посмотрите на общую статистику, обратите внимание, что каждая функция охватывает очень разный диапазон:

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']]

Хорошей практикой является нормализация функций, использующих разные масштабы и диапазоны.

Одна из причин, по которой это важно, заключается в том, что характеристики умножаются на веса модели. Таким образом, масштаб выходных данных и масштаб градиентов зависит от масштаба входных данных.

Хотя модель может сходиться без признака нормализации, нормализации делает обучение гораздо более стабильной.

Слой нормализации

preprocessing.Normalization слой представляет собой чистый и простой способ построить , что предварительная обработка в модели.

Первый шаг - создать слой:

normalizer = preprocessing.Normalization(axis=-1)

Тогда .adapt() его данные:

normalizer.adapt(np.array(train_features))

Это вычисляет среднее значение и дисперсию и сохраняет их в слое.

print(normalizer.mean.numpy())
[   5.478  195.318  104.869 2990.252   15.559   75.898    0.178    0.197
    0.624]

Когда слой вызывается, он возвращает входные данные, при этом каждая функция нормализуется независимо:

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

with np.printoptions(precision=2, suppress=True):
  print('First example:', first)
  print()
  print('Normalized:', normalizer(first).numpy())
First example: [[   4.    90.    75.  2125.    14.5   74.     0.     0.     1. ]]

Normalized: [[-0.87 -1.01 -0.79 -1.03 -0.38 -0.52 -0.47 -0.5   0.78]]

Линейная регрессия

Перед построением модели DNN начните с линейной регрессии.

Одна переменная

Начните с одной переменной линейной регрессии, чтобы предсказать , MPG от Horsepower .

Подготовка модели с tf.keras обычно начинается с определения архитектуры модели.

В этом случае используют keras.Sequential модель. Эта модель представляет собой последовательность шагов. В этом случае есть два шага:

  • Нормализация входной horsepower .
  • Применить линейное преобразование ($ у = х + Ь $) для получения 1 выход с помощью layers.Dense .

Количество входов может быть либо установлено с помощью input_shape аргумента, или автоматически , когда модель запускается в первый раз.

Сначала создайте сильное Normalization слой:

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

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

Постройте последовательную модель:

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

horsepower_model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
normalization_1 (Normalizati (None, 1)                 3         
_________________________________________________________________
dense (Dense)                (None, 1)                 2         
=================================================================
Total params: 5
Trainable params: 2
Non-trainable params: 3
_________________________________________________________________

Эта модель будет предсказывать MPG от Horsepower .

Запустите неподготовленную модель на первых 10 значениях мощности в лошадиных силах. Выход не будет хорошо, но вы увидите , что она имеет ожидаемую форму, (10,1) :

horsepower_model.predict(horsepower[:10])
array([[-0.489],
       [-0.276],
       [ 0.902],
       [-0.685],
       [-0.62 ],
       [-0.243],
       [-0.734],
       [-0.62 ],
       [-0.162],
       [-0.276]], dtype=float32)

После того , как модель построена, настроить процедуру обучения с использованием Model.compile() метода. Наиболее важные аргументы компиляции являются loss и optimizer , так как они определяют , что будут оптимизированы ( mean_absolute_error ) и как ( с помощью optimizers.Adam ).

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

После того , как обучение настроено, используйте Model.fit() , чтобы выполнить обучение:

%%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)
CPU times: user 3.51 s, sys: 682 ms, total: 4.2 s
Wall time: 2.66 s

Визуализируйте прогресс обучения в модели , используя статистику , хранящиеся в 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)

PNG

Соберите результаты на тестовом наборе, чтобы потом:

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)

PNG

Несколько входов

Вы можете использовать почти идентичную настройку, чтобы делать прогнозы на основе нескольких входных данных. Эта модель по-прежнему делает то же самое $ y = mx + b $, за исключением того, что $ m $ - это матрица, а $ b $ - вектор.

На этот раз использовать Normalization слой , который был адаптирован для всего набора данных.

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

При вызове этой модели на партию из входов, она производит units=1 выходы для каждого примера.

linear_model.predict(train_features[:10])
array([[-0.354],
       [ 0.183],
       [-4.128],
       [ 0.718],
       [ 3.96 ],
       [-0.498],
       [ 4.393],
       [ 4.577],
       [-1.01 ],
       [ 3.099]], dtype=float32)

При вызове модели будут построены весовые матрицы. Теперь вы можете видеть , что kernel (за $ м $ в $ у = х + Ь $) имеет форму (9,1) .

linear_model.layers[1].kernel
<tf.Variable 'dense_1/kernel:0' shape=(9, 1) dtype=float32, numpy=
array([[ 0.055],
       [-0.419],
       [-0.369],
       [-0.55 ],
       [ 0.758],
       [ 0.441],
       [ 0.721],
       [ 0.551],
       [-0.591]], dtype=float32)>

Используйте ту же compile и fit вызовы , как для одной входных horsepower модели:

linear_model.compile(
    optimizer=tf.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)
CPU times: user 3.53 s, sys: 610 ms, total: 4.14 s
Wall time: 2.62 s

Использование всех входов обеспечивает гораздо меньшую подготовку и ошибка проверки по сравнению с horsepower модели:

plot_loss(history)

PNG

Соберите результаты на тестовом наборе, чтобы потом:

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

Регрессия DNN

В предыдущем разделе реализованы линейные модели для одного и нескольких входов.

В этом разделе реализованы модели DNN с одним и несколькими входами. Код в основном тот же, за исключением того, что модель расширена за счет включения некоторых «скрытых» нелинейных слоев. Название «скрытый» здесь просто означает, что он не подключен напрямую к входам или выходам.

Эти модели будут содержать на несколько слоев больше, чем линейная модель:

  • Слой нормализации.
  • Два скрытых, нелинейные, Dense слои , использующие relu нелинейностью.
  • Линейный одинарный выходной слой.

Оба будут использовать ту же самую процедуру обучения таким образом 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_model = build_and_compile_model(horsepower_normalizer)

Эта модель имеет намного больше обучаемых параметров, чем линейные модели.

dnn_horsepower_model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
normalization_1 (Normalizati (None, 1)                 3         
_________________________________________________________________
dense_2 (Dense)              (None, 64)                128       
_________________________________________________________________
dense_3 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 65        
=================================================================
Total params: 4,356
Trainable params: 4,353
Non-trainable params: 3
_________________________________________________________________

Обучите модель:

%%time
history = dnn_horsepower_model.fit(
    train_features['Horsepower'], train_labels,
    validation_split=0.2,
    verbose=0, epochs=100)
CPU times: user 3.77 s, sys: 643 ms, total: 4.42 s
Wall time: 2.86 s

Эта модель немного лучше, чем модель с линейной мощностью.

plot_loss(history)

PNG

Если построить прогнозы в зависимости от Horsepower , вы увидите , как эта модель использует нелинейность , представленную скрытые слои:

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

PNG

Соберите результаты на тестовом наборе, чтобы потом:

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

Полная модель

Если вы повторите этот процесс, используя все входные данные, это немного повысит производительность набора данных проверки.

dnn_model = build_and_compile_model(normalizer)
dnn_model.summary()
Model: "sequential_3"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
normalization (Normalization (None, 9)                 19        
_________________________________________________________________
dense_5 (Dense)              (None, 64)                640       
_________________________________________________________________
dense_6 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_7 (Dense)              (None, 1)                 65        
=================================================================
Total params: 4,884
Trainable params: 4,865
Non-trainable params: 19
_________________________________________________________________
%%time
history = dnn_model.fit(
    train_features, train_labels,
    validation_split=0.2,
    verbose=0, epochs=100)
CPU times: user 3.67 s, sys: 727 ms, total: 4.39 s
Wall time: 2.85 s
plot_loss(history)

PNG

Соберите результаты на тестовом наборе:

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

Представление

Теперь, когда все модели обучены, проверьте производительность набора тестов и посмотрите, как они работают:

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

Эти результаты соответствуют ошибке проверки, обнаруженной во время обучения.

Делать предсказания

Наконец, предсказать, посмотрите на ошибки, сделанные моделью при прогнозировании на тестовом наборе:

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)

PNG

Похоже, модель достаточно хорошо предсказывает.

Теперь посмотрим на распределение ошибок:

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

PNG

Если модель вас устраивает, сохраните ее для дальнейшего использования:

dnn_model.save('dnn_model')
INFO:tensorflow:Assets written to: dnn_model/assets

Если вы перезагрузите модель, она выдаст идентичный результат:

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

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

Заключение

В этой записной книжке представлены несколько методов решения проблемы регрессии. Вот еще несколько советов, которые могут помочь:

  • Mean Squared Error (MSE) и средняя абсолютная ошибка (МАЕ) являются общие функции потерь , используемых для задач регрессии. Средняя абсолютная ошибка менее чувствительна к выбросам. Для задач классификации используются различные функции потерь.
  • Точно так же метрики оценки, используемые для регрессии, отличаются от классификации.
  • Когда у числовых входных данных есть значения с разными диапазонами, каждая функция должна масштабироваться независимо до одного и того же диапазона.
  • Переобучение - обычная проблема для моделей DNN, это не проблема для этого руководства. См overfit и underfit учебник для получения дополнительной помощи с этим.
# 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.