В задачах регресии нам требуется прогнозировать непрерывное значение (значение, входящее в какой-либо диапазон, не имеющее набора дискретных значений), как цена или вероятность. Это контрастно классификационным задачам, где нам требуется прогнозировать дискретную метку (например, содержит ли картинка яблоко или апельсин).
В этом посте мы построим модель для прогнозирования средней цены домов в окрестностях Бостона в середине 70х. Чтобы сделать это мы предоставим модели параметры данных об окрестностях, такие как уровень криминальности и уровень местного налога на недвижимость.
Это руководство использует tf.keras API.
from future import absolute_import, division, print_function
import tensorflow as tf
from tensorflow import keras
import numpy as np
print(tf.version)
1.9.0
Набор данных цен домов Бостона
Этот набор доступен напрямую из TensorFlow. Загрузим и перемешаем данные тренировочного набора:
boston_housing = keras.datasets.boston_housing
(train_data, train_labels), (test_data, test_labels) = boston_housing.load_data()
# Перемешаем тренировочные данные
order = np.argsort(np.random.random(train_labels.shape))
train_data = train_data[order]
train_labels = train_labels[order]
Downloading data from https://s3.amazonaws.com/keras-datasets/boston_housing.npz
57344/57026 [==============================] - 0s 4us/step
Примеры и свойства
Этот набор данных намного меньше, чем другие: он имеет в целом 506 примеров, разделенных между 404 тренировочными примерами и 102 тестовыми примерами:
print("Training set: {}".format(train_data.shape)) # 404 примера, 13 свойств
print("Testing set: {}".format(test_data.shape)) # 102 примера, 13 свойств
Training set: (404, 13)
Testing set: (102, 13)
Набор содержит 13 различных свойств:
- Уровень преступности на душу населения.
- Доля жилой земли, выделенной для участков более 25 000 квадратных футов.
- Доля не торговых бизнес акров на город.
- Переменная реки Чарлз (равна 1 если рядом река, 0 если нет).
- Концентрация оксидов азота (частей на 10 миллионов)
- Среднее количество комнат на жилье.
- Пропорция домов частных владельцев, построенных до 1940 года.
- Взвешенные расстояния до пяти рабочих центров Бостона.
- Индекс доступности близлежащих автомагистралей.
- Полная стоимость налога на имущество на 10 000 долларов США.
- Среднее количество учителей для детей на город.
- 1000 * (Bk - 0.63) ** 2, где Bk - это пропорция афроамериканцев на город.
- Процент населения с низким социальным статусом.
Каждое из этих вводных свойств сохранено в разном масштабе. Некоторые свойства представлены дробным значением между 0 и 1, другие в диапазоне между 1 и 12, некоторые в диапазоне от 0 до 100 и т.д. Такое часто встречается в реальных данных. Понимание и умение исследовать и очищать такие данные - важный навык в машинном обучении.
print(train_data[0]) # Отобразим свойства примеров, отметим разные масштабы
[7.8750e-02 4.5000e+01 3.4400e+00 0.0000e+00 4.3700e-01 6.7820e+00
4.1100e+01 3.7886e+00 5.0000e+00 3.9800e+02 1.5200e+01 3.9387e+02
6.6800e+00]
Используем библиотеку pandas, чтобы отобразить первые несколько строк набора данных в хорошо отформатированной таблице:
import pandas as pd
column_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD',
'TAX', 'PTRATIO', 'B', 'LSTAT']
df = pd.DataFrame(train_data, columns=column_names)
df.head()
Метки
Метки - это цены домов в тысячах долларов. (Цены 1970х годов.)
print(train_labels[0:10]) # Отобразим первые 10
[32. 27.5 32. 23.1 50. 20.6 22.6 36.2 21.8 19.5]
Нормализация свойств
Рекомендуется нормализовать свойства, которые используют разные масштабы и диапазоны. Для каждого свойства вычтем среднее значение свойства и разделим на стандартное отклонение:
# Тестовые данные не используются при вычислении mean и std
mean = train_data.mean(axis=0)
std = train_data.std(axis=0)
train_data = (train_data - mean) / std
test_data = (test_data - mean) / std
print(train_data[0]) # Первый тренировочный пример, нормализованный
[-0.39725269 1.41205707 -1.12664623 -0.25683275 -1.027385 0.72635358
-1.00016413 0.02383449 -0.51114231 -0.04753316 -1.49067405 0.41584124
-0.83648691]
Хотя модель может сойтись без нормализации свойств, это делает тренировку труднее и это делает результирующую модель более зависимой от выбора элементов, используемых на входе.
Создание модели
Построим нашу модель. Здесь мы будем использовать Sequential модель с двумя тесно связанными скрытыми слоями, а выводной слой возвращает единственное недискретное значение. Шаги построения модели обернуты в функцию, build_model.
def build_model():
model = keras.Sequential([
keras.layers.Dense(64, activation=tf.nn.relu,
input_shape=(train_data.shape[1],)),
keras.layers.Dense(64, activation=tf.nn.relu),
keras.layers.Dense(1)
])
optimizer = tf.train.RMSPropOptimizer(0.001)
model.compile(loss='mse',
optimizer=optimizer,
metrics=['mae'])
return model
model = build_model()
model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 64) 896
_________________________________________________________________
dense_1 (Dense) (None, 64) 4160
_________________________________________________________________
dense_2 (Dense) (None, 1) 65
=================================================================
Total params: 5,121
Trainable params: 5,121
Non-trainable params: 0
_________________________________________________________________
Тренировка модели
Тренируем модель 500 эпох и записываем тренировочную и валидационную аккуратность в объект истории.
# Отобразим прогресс тренировки
# печатая по одной точке на каждую завершенную эпоху
class PrintDot(keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs):
if epoch % 100 == 0: print('')
print('.', end='')
EPOCHS = 500
# Сохраним тренировочную статистику
history = model.fit(train_data, train_labels, epochs=EPOCHS,
validation_split=0.2, verbose=0,
callbacks=[PrintDot()])
.......................................................
.......................................................
.......................................................
.......................................................
.......................................................
Визуализируем прогресс тренировки модели, используя статистику, сохраненную в объекте истории. Мы будем использовать эти данные, чтобы определить сколько тренировать модель до того как сделать остановку.
import matplotlib.pyplot as plt
def plot_history(history):
plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Abs Error [1000$]')
plt.plot(history.epoch,
np.array(history.history['mean_absolute_error']),
label='Train Loss')
plt.plot(history.epoch,
np.array(history.history['val_mean_absolute_error']),
label = 'Val loss')
plt.legend()
plt.ylim([0, 5])
plot_history(history)
Этот граф показывает небольшое улучшение в модели после 200 эпох. Обновим model.fit метод, чтобы автоматически остановить тренировку, когда валидационный счет перестает улучшаться. Мы будем использовать callback, который тестирует условие для каждой эпохи. Если количество эпох истекает без показа улучшения, тогда тренировка останавливается.
model = build_model()
# Параметр терпения - количество эпох, чтобы проверить улучшение
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss',
patience=20)
history = model.fit(train_data, train_labels, epochs=EPOCHS,
validation_split=0.2, verbose=0,
callbacks=[early_stop, PrintDot()])
plot_history(history)
..........................................................
..........................................................
Граф показывает среднюю ошибку около 2500 долларов. Это нормально? Ну, 2500 не является незначительным количеством, когда некоторые метки всего 15000.
Посмотрим как модель будет выполняться на тестовом наборе:
[loss, mae] = model.evaluate(test_data, test_labels, verbose=0)
print("Testing set Mean Abs Error: ${:7.2f}".format(mae * 1000))
Testing set Mean Abs Error: $2599.79
Прогнозирование
В итоге сделаем прогноз нескольких цен, используя данные тестового набора:
test_predictions = model.predict(test_data).flatten()
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [1000$]')
plt.ylabel('Predictions [1000$]')
plt.axis('equal')
plt.xlim(plt.xlim())
plt.ylim(plt.ylim())
_ = plt.plot([-100, 100], [-100, 100])
error = test_predictions - test_labels
plt.hist(error, bins = 50)
plt.xlabel("Prediction Error [1000$]")
_ = plt.ylabel("Count")
Заключение
В этом руководстве упомянуты несколько техник для решения задач регрессии:
- Средняя квадратичная ошибка (Mean Squared Error (MSE)) - функция потери, используемая для задач регрессии (отличных от задач классификации).
- Аналогично, оценка метрик, используемая для регрессии отличается от классификации. Общеиспользуемая метрика регрессии - Средняя абсолютная ошибка (Mean Absolute Error (MAE)).
- Когда свойства вводных данных имеют значения с разными диапазонами, каждое свойство следует масштабировать независимо.
- Если присуствует не так много тренировочных данных, предпочтительно использовать маленькую сеть с небольшим количеством скрытых слоев, чтобы исбежать переобучения.
- Ранняя остановка - это полезная техника для предупреждения переобучения.