title поиск квартиры часть 3

Для тех, кто не хочет читать, а хочет сразу увидеть код - Jupyter notebook

Введение

Данный пост является заключительным в серии постов, описывающих обработку данных с использованием Pandas,Numpy и иных средств визуализации/анализа данных.

Disclaimer(отказ от ответственности)

Данная информация находится в первом посте этой серии, и рекомендуется ознакомиться с ней.

Pre-requierments

Для работы с используемым набором данных(здесь и далее - датасет(dataset)) на данном этапе будет производиться отсев лишних признаков с использованием эвристик из Scikit-learn , а также Matplotlib и Seaborn

1
2
3
4
5
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(color_codes=True)

Подготовка к работе

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

Разделим его на две части: features(независимые переменные) и target(зависимая переменная)

1
2
3
df = pd.read_csv('flat.csv', sep='\t')
y = df[['cost']]
X = df[['district', 'total_area', 'repair', 'year', 'distance']]

После чего разделим наши данные на обучающую и контрольную выборки

1
2
from sklearn.model_selection import train_test_split
x_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

Простейшая модель регрессии

Регрессия - это статистический метод анализа связей/зависимостей двух и более переменных в данных. И, если нам нужно предсказать что-то - регрессия - “то, что доктор прописал”.
Как вы могли,наверное, догадаться, речь пойдет о известной всем линейной регрессии, которую возьмем из пакета Sklearn. Забегая вперед, заметим, что будет использовано R-Square для измерения точности, поскольку у каждой модели изначально заложены свои подходы к измерению точности предсказания([R]MSE как пример), и нам нужна некая “независимая” метрика для унификации и сравнения.

1
2
3
4
5
6
from sklearn.model_selection import cross_val_score
model = linear_model.LinearRegression()
model.fit(x_train, y_train)
predicted = model.predict(X_test)
accuracy = r2_score(y_test, predicted)
print(accuracy)

Точность предсказания составила 0.686532872588, что достаточно мало.

После чего отобразим график “частоты” ошибок, иными словами, на сколько ошибалась наша модель и как часто. Для этого используем Seaborn

1
2
3
4
5
6
7
8
9
10
errors = np.round(np.abs(1 - predicted / y_test['cost'].values) * 100, 0)
mean_error = np.mean(errors)
max_error = errors.max()
min_error = errors.min()
c = Counter([x for x in errors])
data = {'error': list(c.keys()), 'count': list(c.values())}
df = pd.DataFrame.from_dict(data)
sns.lmplot('count', 'error', data=df, fit_reg=False, markers=["."])
ax = plt.gca()
ax.set_title(f'{regressor_name}\nDetermination-{round(accuracy,3)}')

title линейная регрессия

Как видно из текущего рисунка - максимальная ошибка предсказания была равна примерно 63%, также была парочка весьма точных предсказаний. В целом ошибки распределены достаточно равномерно и в основном величина ошибки меньше, либо равна 20%.Средняя ошибка равна 28.

Более сложные модели

Простые модели несомненно хороши. Хороши тем, что они просты и очевидны. Но как мы можем улучшить результаты предсказания? Стоит ли отказаться от простой модели и взять более сложную? Не придется ли платить за это слишком большую цену?

Для подобных случаев можно использовать другие регрессионные модели, например, дерево решений, которое зачастую используется как классификатор. Но оно также может быть использовано как “регрессор”(DecisionTreeRegressor). Добавочно мы применим алгоритм бустинга. Бустинг позволит использовать много простых моделей для предсказания цены, а потом усреднит их предсказания,что должно позволить уменьшить процент ошибок. В качестве “обвязки”, которая даст нам итоговое решение, используем алгоритм AdaBoost

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.ensemble import AdaBoostRegressor
model = AdaBoostRegressor(DecisionTreeRegressor(random_state=0), random_state=0)
model.fit(x_train, y_train)
predicted = model.predict(X_test)
accuracy = r2_score(y_test, predicted)
print(accuracy)
regressor_name = model.__class__.__name__
errors = np.round(np.abs(1 - predicted / y_test['cost'].values) * 100, 0)
mean_error = np.mean(errors)
max_error = errors.max()
min_error = errors.min()
c = Counter([x for x in errors])
data = {'error': list(c.keys()), 'count': list(c.values())}
df = pd.DataFrame.from_dict(data)
sns.lmplot('count', 'error', data=df, fit_reg=False, markers=["."])
ax = plt.gca()
ax.set_title(f'{regressor_name}\nDetermination-{round(accuracy,3)}')

title adaboost decision tree

Точность предсказания в этом случае составила 0.678886412274, что оказалось даже меньше, чем у линейной регрессии. С другой стороны, величина ошибки уменьшилась, и теперь у нас всего 12 ошибок прогнозирования с расхождением более 20%. Максимальная ошибка упала до 60%, а средняя - до 10. Неплохое начало.

Популярная модель

Алгоритм бустинга далеко не нов, он давно уже занял свое место среди инструментов людей, что увлекаются machine learning. Например, на том же Kaggle зачастую используют XGBoost как “рабочую лошадку”, чтоб выжать дополнительные проценты точности в задачах классификации или регрессии. Попробуем его и в нашей задаче.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import xgboost as xgb
model = xgb.XGBRegressor(random_state=0)
model.fit(x_train, y_train)
predicted = model.predict(X_test)
accuracy = r2_score(y_test, predicted)
print(accuracy)
regressor_name = model.__class__.__name__
errors = np.round(np.abs(1 - predicted / y_test['cost'].values) * 100, 0)
mean_error = np.mean(errors)
max_error = errors.max()
min_error = errors.min()
c = Counter([x for x in errors])
data = {'error': list(c.keys()), 'count': list(c.values())}
df = pd.DataFrame.from_dict(data)
sns.lmplot('count', 'error', data=df, fit_reg=False, markers=["."])
ax = plt.gca()
ax.set_title(f'{regressor_name}\nDetermination-{round(accuracy,3)}')

title xgboost

Точность предсказания в этом случае составила 0.695841310462, что лучше чему у двух предыдущих. Теперь в прогнозировании 13 ошибок с расхождением более 20%, максимальная ошибка подскочила до 68%, а средняя увеличилась до 11%. Не самый лучший результат, но лучше, чем линейная регрессия. К тому же можно под-tune’ить настройки алгоритма, добавить решающих деревьев, подкорректировать размер выборки для бустинга и получить результаты гораздо выше. Но более “честно” будет остановиться на том, что есть, и рассматривать, как работает алгоритм “из коробки”

From Russia with Love

Как говорил когда то Linus Torvalds - “Опять эти сумасшедшие русские придумали что-то”, упоминая какой то патч в ядро Linux. Одна компания широко известная в России, а также за рубежом, презентовала свой алгоритм градиентного бустинга CatBoost на деревьях решений(на котором и основан XGBoost и его аналоги). Часть Boost в его названии явно указывает на основную идею - бустинг. А Cat (нет не про котиков, хотя отсылка хороша)- это Categorical data, что намекает на работу с категориальными переменными. В текущих данных нет переменных подобного рода, но почему бы не опробовать данный алгоритм?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from catboost import CatBoostRegressor
model = CatBoostRegressor(verbose=False, random_state=0)
model.fit(x_train, y_train)
predicted = model.predict(X_test)
accuracy = r2_score(y_test, predicted)
print(accuracy)
regressor_name = model.__class__.__name__
errors = np.round(np.abs(1 - predicted / y_test['cost'].values) * 100, 0)
mean_error = np.mean(errors)
max_error = errors.max()
min_error = errors.min()
c = Counter([x for x in errors])
data = {'error': list(c.keys()), 'count': list(c.values())}
df = pd.DataFrame.from_dict(data)
sns.lmplot('count', 'error', data=df, fit_reg=False, markers=["."])
ax = plt.gca()
ax.set_title(f'{regressor_name}\nDetermination-{round(accuracy,3)}')

title catboost

Точность предсказания в этом случае составила 0.69908708758, что лучше всех алгоритмов рассмотренных ранее. Теперь в прогнозировании 12 ошибок с расхождением более 20%, максимальная ошибка подскочила до 72%,а средняя стала даже меньше, чем у Adaboost. Достаточно неплохо для “малоизвестного” алгоритма с параметрами “из коробки”, по сравнению с популярным XGBoost.

Кросс-валидация

Многим из тех, кто занимается работой с данными, известны случаи, когда модель, созданная на одной обучающей выборке и достаточно хорошо предсказывающая результаты на контрольной выборке, резко теряет в качестве предсказаний, будучи обучена на других данных из генеральной выборки. Чтобы бороться с данной проблемой, был разработан подход называемый кросс-валидация, который позволяет оценить уровень ошибки на различных множествах, что будут взяты из генеральной выборки. Проверим наши модели в рамках данного подхода, используя те же алгоритмы LinearRegression, AdaBoost,XGBoost и CatBoost

1
2
3
4
5
6
7
8
9
10
11
from sklearn.model_selection import KFold
cat = CatBoostRegressor(verbose=False, random_state=0)
xgbtree = xgb.XGBRegressor(random_state=0)
regr = linear_model.LinearRegression()
adaboost = AdaBoostRegressor(DecisionTreeRegressor(random_state=0), random_state=0)
for model in [regr, cat, xgbtree, adaboost]:
regressor_name = model.__class__.__name__
fold = KFold(n_splits=10, shuffle=True, random_state=0)
scores_on_this_split = cross_val_score(estimator=model, X=x_train, y=y_train, cv=fold, scoring='r2')
accuracy = scores_on_this_split.mean()
print(f'{regressor_name} - {accuracy}')

В итоге средняя ошибка по каждой модели будет выражена как:

LinearRegression - 0.6939594465172012
CatBoostRegressor - 0.7439838443789393
XGBRegressor - 0.7212568562820845
AdaBoostRegressor - 0.6924071663923113

Что подтверждает ранее продемонстрированную картину:
AdaBoost - дает результат чуть хуже, чем обычная линейная регрессия
XGBRegressor - уже лучше, чем просто подход в лоб с LinearRegression
CatBoostRegressor от отечественной компании - добрал и улучшил точность предсказания.

Заключение

Таким образом, CatBoostRegressor внезапно оказался самым хорошим решением для предсказания цены на текущем датасете. Можно попробовать использовать модель, полученную в результате исследования на реальных данных. Просто дать ей на вход параметры, характеризующие район, ремонт, общую жилплощадь, возраст дома и расстояние до центра. А затем сравнить существующее значение с тем, что предсказала модель.

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