От сбора и подготовки данных до обучения и оценки модели - включая исходный код.

В наши дни глубокое обучение - это большая проблема. Черт возьми, это даже требование для большинства вакансий в области науки о данных, даже начального уровня. Нет лучшей вводной лекции, чем регресс. Вы уже знакомы с концепциями базовой статистики и машинного обучения, а теперь пришло время добавить сюда нейронные сети.

Эта статья покажет вам, как это сделать. К концу у вас будет полнофункциональная модель для прогнозирования цен на жилье, которую вы можете добавить в свой портфель - желательно после некоторых изменений.

Не хочется читать? Вместо этого посмотрите мое видео:

Сегодняшняя статья посвящена следующим темам:

· Dataset used
· Dataset exploration and preparationDeleting unnecessary columnsFeature engineeringTarget variable visualizationData preparation for ML
· Training a regression model with TensorFlowLoss trackingBuilding a modelMaking predictionsModel evaluation

Вы можете скачать исходный код на GitHub.

Используемый набор данных

Давайте сегодня будем простыми и будем придерживаться хорошо известного набора данных о ценах на жилье:

В нем есть множество функций, которые изначально невозможно использовать с моделью нейронной сети, поэтому вам придется потратить некоторое время на их изучение. Загрузите набор данных, извлеките ZIP-файл и поместите набор данных CSV в безопасное место.

Затем активируйте виртуальную среду, в которой установлен TensorFlow 2+, и запустите JupyterLab. Вы можете использовать любую другую среду IDE, но все скриншоты ниже будут взяты из Jupyter.

Исследование и подготовка набора данных

Первый шаг - импортировать Numpy и Pandas, а затем импортировать набор данных. Следующий фрагмент делает это, а также печатает случайную пару строк:

import numpy as np
import pandas as pd

df = pd.read_csv('data/data.csv')
df.sample(5)

Вот как выглядит набор данных:

Нейросеть в таком формате точно не передать.

Удаление ненужных столбцов

Поскольку мы не хотим тратить слишком много времени на подготовку данных, лучше отказаться от большинства нечисловых функций. Оставьте только столбец city, так как его достаточно просто закодировать:

to_drop = ['date', 'street', 'statezip', 'country']
df = df.drop(to_drop, axis=1)

df.head()

Вот как это должно выглядеть сейчас:

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

Функциональная инженерия

Теперь вы потратите время на настройку набора данных. Столбец yr_renovated иногда имеет значение 0. Я предполагаю, что это потому, что дом не ремонтировался. Вы создадите пару характеристик: возраст дома, ремонтировался ли дом или нет, ремонтировался ли он за последние 10 лет и ремонтировался ли он за последние 30 лет.

Вы можете использовать список для каждой упомянутой функции. Вот как:

# How old is the house?
df['house_age'] = [2021 - yr_built for yr_built in df['yr_built']]

# Was the house renovated and was the renovation recent?
df['was_renovated'] = [1 if yr_renovated != 0 else 0 
    for yr_renovated in df['yr_renovated']]
df['was_renovated
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['figure.figsize'] = (16, 6)
rcParams['axes.spines.top'] = False
rcParams['axes.spines.right'] = False

plt.hist(df['price'], bins=100);
yrs'] = [1 if (2021 - yr_renovated) <= 10 else 0 for yr_renovated in df['yr_renovated']] df['was_renovatedravel()yrs'] = [1 if 10 < (2021 - yr_renovated) <= 30 else 0 for yr_renovated in df['yr_renovated']] # Drop original columns df = df.drop(['yr_built', 'yr_renovated'], axis=1) df.head()

Вот как теперь выглядит набор данных:

Теперь займемся столбцом city. Во многих городах перечислено всего несколько домов, поэтому вы можете объявить функцию, которая избавится от всех значений города, которые встречаются нечасто. Это то, что делает функция remap_location() - если в этом городе меньше 50 домов, он заменяется чем-то другим. Это просто способ сократить количество вариантов:

def remap_location(data: pd.DataFrame, 
                   location: str, 
                   threshold: int = 50) -> str:
    if len(data[data['city'] == location]) < threshold:
        return 'Rare'
    return location

Давайте протестируем функцию - в городе Сиэтл перечислено много домов, а в Fall City всего 11:

Давайте применим эту функцию ко всем городам и распечатаем образец из 10 строк:

df['city'] = df['city'].apply(
    lambda x: remap_location(data=df, location=x)
)
df.sample(10)

Все выглядит как надо, продолжим.

Визуализация целевой переменной

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

import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['figure.figsize'] = (16, 6)
rcParams['axes.spines.top'] = False
rcParams['axes.spines.right'] = False

plt.hist(df['price'], bins=100);

Вот как это выглядит:

Выбросы определенно присутствуют, так что давайте разберемся с ними дальше. Довольно часто приходится рассчитывать Z-баллы. Они позволяют узнать, на сколько стандартных отклонений значение находится от среднего. В случае нормального распределения все, что ниже или выше 3 стандартных отклонений, классифицируется как выброс. Распределение цен ненормальное, но давайте все же проведем Z-тест, чтобы удалить дома справа.

Вы можете рассчитать Z-оценку с помощью Scipy. Вы назначите их как новый столбец набора данных - price_z, а затем оставите только те строки, в которых абсолютное значение Z меньше или равно трем.

Также есть около 50 домов по цене 0 долларов, так что вы тоже удалите их:

from scipy import stats

# Calculate Z-values
df['price_z'] = np.abs(stats.zscore(df['price']))

# Filter out outliers
df = df[df['price_z'] <= 3]

# Remove houses listed for $0
df = df[df['price'] != 0]

# Drop the column
df = df.drop('price_z', axis=1)

# Draw a histogram
plt.hist(df['price'], bins=100);

Вот как сейчас выглядит раздача:

Небольшой перекос все еще присутствует, но давайте объявим его достаточно хорошим.

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

Подготовка данных для ML

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

Теперь вы можете преобразовать каждую функцию по отдельности, но есть способ лучше. Вы можете использовать функцию make_column_transformer() из Scikit-Learn, чтобы применить масштабирование и кодирование за один раз.

Вы можете игнорировать такие функции, как waterfront, was_renovated, was_renovated_10_yrs и was_renovatedravel()yrs, поскольку они уже находятся в нужном вам формате:

from sklearn.compose import make_column_transformer
from sklearn.preprocessing import MinMaxScaler, OneHotEncoder

transformer = make_column_transformer(
    (MinMaxScaler(), 
        ['sqft_living', 'sqft_lot','sqft_above', 
         'sqft_basement', 'house_age']),
    (OneHotEncoder(handle_unknown='ignore'), 
        ['bedrooms', 'bathrooms', 'floors', 
         'view', 'condition'])
)

Затем давайте отделим функции от целевой переменной и разделим набор данных на обучающую и тестовую части. Набор поездов будет составлять 80% данных, а все остальное мы будем использовать для тестирования:

from sklearn.model_selection import train_test_split

X = df.drop('price', axis=1)
y = df['price']

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

И, наконец, вы можете применить преобразования, объявленные минуту назад. Вы подгоните и трансформируете обучающие функции и примените трансформации только к набору тестирования:

# Fit
transformer.fit(X_train)

# Apply the transformation
X_train = transformer.transform(X_train)
X_test = transformer.transform(X_test)

Вы не сможете напрямую проверять X_train и X_test, поскольку теперь они хранятся в виде разреженной матрицы:

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

X_train.toarray()

Преобразуйте оба набора функций в массив Numpy, и все готово:

X_train = X_train.toarray()
X_test = X_test.toarray()

Наконец-то обучим модель.

Обучение регрессионной модели с помощью TensorFlow

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

import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import backend as K

Отслеживание убытков

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

Вы можете вычислить квадратный корень из MSE, чтобы вернуться к исходным единицам. Этот показатель не поддерживается по умолчанию, но мы можем указать его вручную. Имейте в виду, что вам придется использовать функции из бэкэнда Keras, чтобы он заработал:

def rmse(y_true, y_pred):
    return K.sqrt(K.mean(K.square(y_pred - y_true)))

Построение модели

И теперь можно наконец заявить о модели. Это будет простой, с тремя скрытыми слоями по 256, 256 и 128 единиц. Не стесняйтесь экспериментировать с ними, поскольку нет правильного или неправильного способа настроить нейронную сеть. Затем за этими слоями следует выходной слой из одного узла, поскольку вы прогнозируете числовое значение.

Затем вы скомпилируете модель, используя RMSE в качестве способа отслеживания потерь и в качестве оценочной метрики, и оптимизируете модель с помощью оптимизатора Adam.

Наконец, вы обучите модель на обучающих данных для 100 эпох:

tf.random.set_seed(42)

model = Sequential([
    Dense(256, activation='relu'),
    Dense(256, activation='relu'),
    Dense(128, activation='relu'),
    Dense(1)
])

model.compile(
    loss=rmse,
    optimizer=Adam(),
    metrics=[rmse]
)

model.fit(X_train, y_train, epochs=100)

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

Окончательное значение RMSE на обучающем наборе чуть выше 192000, что означает, что для среднего дома модель неверна в оценке цены на 192000 долларов.

Делать прогнозы

Вы можете делать прогнозы по набору тестов:

predictions = model.predict(X_test)
predictions[:5]

Вот как выглядят первые пять прогнозов:

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

predictions = np.ravel(predictions)
predictions[:5]

Вот результаты:

Оценка модели

А теперь давайте оценим прогнозы на тестовом наборе с помощью RMSE:

rmse(y_test, predictions).numpy()

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

Напутственные слова

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

Например, вы могли бы потратить гораздо больше времени на подготовку данных. Мы удалили функцию даты и времени, информацию об улице, почтовый индекс и т. Д., Которые могли быть полезны для производительности модели. Дело в том, что на подготовку этих статей уйдет слишком много времени, и я хочу, чтобы эти статьи были краткими.

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

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

Спасибо за прочтение.

Понравилась статья? Станьте средним участником, чтобы продолжить обучение без ограничений. Если вы воспользуетесь следующей ссылкой, я получу часть вашего членского взноса без каких-либо дополнительных затрат.



Оставайся на связи

  • Подпишитесь на мою рассылку"
  • Подпишитесь на YouTube
  • Подключиться к LinkedIn