Введение

Моя цель в этом проекте — создать комплексное решение для прогнозирования цен на жилье лучше, чем эксперты по недвижимости, которые делают это вручную.

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

Давайте начнем с загрузки данных и некоторых общих необходимых библиотек.

  1. Понимание данных
import sklearn
import numpy as np
import os
import seaborn as sns
import pandas as pd
# To plot pretty figures
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)
# Load the data  import pandas as pd 
housing= pd.read_csv("housing.csv")
# Explore the data
housing.head()

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

housing.info()

В наборе данных 20 640 наблюдений с некоторым недостающим значением для функции total_bedroom. Кроме того, мы видим, что все функции являются числовыми, кроме ocean_proximity, которая является категориальной переменной. Посмотрим, сколько у него различных значений.

housing["ocean_proximity"].value_counts()

Мы видим, что близость океана имеет 5 значений. Давайте еще немного изучим наши данные, используя функцию описания в python.

# Use describe to explore the numerical variables 
housing.describe()

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

housing.hist(bins=50, figsize=(20,15))
plt.show()

Мы можем быстро увидеть, что более чем в 800 районах средняя стоимость дома составляет около 100 000 долларов. График среднего дохода кажется немного странным, так как данные были масштабированы и ограничены 15 для более высокого среднего дохода и 0,5 для более низкого среднего дохода. Точно так же мы можем видеть, что средний возраст ограничен 50 годами, а средняя стоимость дома ограничена 500 000 долларов. Мы можем либо собрать правильные значения для значений с ограничениями, либо удалить эти районы для наборов данных. Мы также видим, что не все функции имеют одинаковый масштаб, и многие функции имеют тяжелый хвост, т. Е. Они простираются намного дальше вправо от медианы, чем влево.

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

from sklearn.model_selection import train_test_split
train_set, test_set= train_test_split(housing, test_size=0.2, random_state=42)

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

housing["median_income"].hist()

# Checking for the right number of bins for the response variable

housing["income_cat"]= pd.cut(housing["median_income"],
                             bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
                             labels= [1,2,3,4,5])
housing["income_cat"].hist()

Здесь мы рассмотрели распределение медианного дохода и создали 5 уровней категории дохода.

# Startified sampling based on income_cat to make the datasets more random and representative

from sklearn.model_selection import StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in split.split(housing, housing["income_cat"]):
    strat_train_set = housing.loc[train_index]
    strat_test_set = housing.loc[test_index]

Проверим, равномерно ли распределена переменная категории дохода.

## Check if the strata worked for entire datasets
housing["income_cat"].value_counts() / len(housing)

Теперь давайте посмотрим, применялась ли та же пропорция в тестовых наборах.

strat_test_set["income_cat"].value_counts() / len(strat_test_set)

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

# Removing income_cat from the dataset so data goes back to original # state
for set_ in (strat_train_set, strat_test_set): set_.drop("income_cat", axis=1, inplace=True)

2. Визуализация и изучение данных

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

## Exploring high density areas
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)

Теперь мы можем быстро увидеть высокую плотность вокруг таких областей, как залив, Лос-Анджелес и Сан-Диего. Теперь давайте посмотрим на цены на жилье в этих районах.

# Lets look at housing prices with circle representing district     # population and color representing price
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.4,
 s=housing["population"]/100, label="population", figsize=(10,7),
 c="median_house_value", cmap=plt.get_cmap("jet"), colorbar=True, sharex=False)
plt.legend()

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

## How other variables relate with our target variable
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)

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

from pandas.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms",
              "housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12, 8))
save_fig("scatter_matrix_plot")

Поскольку средний доход является наиболее важной переменной, давайте рассмотрим ее немного подробнее.

sns.jointplot(x="median_income", y="median_house_value", data=housing)

Мы видим некоторые необычные линии около 450 000 долларов, 350 000 долларов и около 280 000 долларов. И, как отмечалось ранее, мы видим сильную линию около 500 000 долларов, которая является линией ограничения. Обычно хорошей практикой является удаление тех районов, которые образуют сплошные линии.

3. Разработка функций

У нас есть функции под названием total_rooms и total_bedrooms, которые в основном представляют собой общее количество комнат и спален в этом районе. Эти функции бесполезны для нас, если мы не преобразуем их в общее количество комнат и спален на домохозяйство. Мы также создадим новую переменную, называемую населением на домохозяйство, используя переменную населения.

housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]
housing["population_per_household"]=housing["population"]/housing["households"]

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

Мы видим, что количество комнат на домохозяйство гораздо более коррелировано, чем общее количество комнат.

4. Подготовка данных для ввода в модели машинного обучения

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

# Here first we will create a copy and separate the target variable as we do not want to do the same transformation
housing= strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()

4 а. Вменение отсутствующих значений

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

from sklearn.impute import SimpleImputer
# Remove ocean_proximity feature which is text
# Now, lets impute missing values
imputer = SimpleImputer(strategy="median") 
housing_num = housing.drop("ocean_proximity", axis=1) 
imputer.fit(housing_num)

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

imputer.statistics_
## Apply same logic to all the numeric datasets in case future data has missing values
housing_num.median().values

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

# Now lets use this trained imputer to transform the training sets X = imputer.transform(housing_num)

Теперь давайте вернем его во фрейм данных pandas.

housing_tr = pd.DataFrame(X, columns=housing_num.columns)
housing_tr.info()

4 б. Преобразование категориальных переменных

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

Здесь происходит создание одного бинарного атрибута для каждой категории. один атрибут равен 1, когда категория «ВНУТРЕННЯЯ СВЯЗЬ» и 0 в противном случае для всех уровней, и только один атрибут будет равен 1 (горячий), а другие будут равны 0 (холодный).

cat_encoder.categories_

4 в. Пользовательское преобразование

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

#### Creating custom transformation to add extra attributes from sklearn.base import BaseEstimator, TransformerMixin # column indexclass CombinedAttributesAdder(BaseEstimator, TransformerMixin):attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6 
 def __init__(self, add_bedrooms_per_room = True): # no *args or **kargs
 self.add_bedrooms_per_room = add_bedrooms_per_room
housing_extra_attribs = attr_adder.transform(housing.values) 
 def fit(self, X, y=None):
 return self # nothing else to do
 def transform(self, X, y=None):
 rooms_per_household = X[:, rooms_ix] / X[:, households_ix]
 population_per_household = X[:, population_ix] / X[:, households_ix]
 if self.add_bedrooms_per_room:
 bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
 return np.c_[X, rooms_per_household, population_per_household,
 bedrooms_per_room]
 else:
 return np.c_[X, rooms_per_household, population_per_household]

Теперь давайте добавим атрибут обратно в набор данных.

### Adding attributes to the datasets

4 д. Конвейеры преобразования

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

from sklearn.pipeline import Pipelinehousing_num_tr = num_pipeline.fit_transform(housing_num)
from sklearn.preprocessing import StandardScaler 
housing_num_tr

Теперь давайте преобразуем категориальную переменную

from sklearn.compose import ColumnTransformer num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"] housing_prepared = full_pipeline.fit_transform(housing) housing_prepared.shape

Теперь наш обучающий набор данных содержит 16 512 строк и 16 переменных.

5. Обучение модели машинного обучения

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

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

Теперь у нас есть работающая линейная регрессия. Давайте попробуем сделать прогноз на нескольких экземплярах.

some_data = housing.iloc[:5] some_labels = housing_labels.iloc[:5] some_data_prepared = full_pipeline.transform(some_data) print("Predictions:", lin_reg.predict(some_data_prepared))

Давайте сравним это с реальными значениями.

### Compare against actual values
print("Labels:", list(some_labels))

Во-первых, наша модель дешевле примерно на 76 000 долларов. Давайте измерим RMSE регрессионной модели.

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

5 б. Деревья решений

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

### Using DecisionTreeRegressor
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(housing_prepared, housing_labels)

Теперь у нас есть режим построения, давайте снова оценим модель дерева решений, используя RMSE.

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

Давайте посмотрим на результирующую модель дерева решений после перекрестной проверки.

### Let's look at the scores
def display_scores(scores):
 print("Scores:", scores)
 print("Mean:", scores.mean())
 print("Standard deviation:", scores.std())
display_scores(tree_rmse_scores)

Теперь мы видим, что линейная регрессия была даже лучше, чем дерево решений, которое имеет среднюю ошибку 71 407 долларов США со стандартным отклонением +- 2 439 долларов США по сравнению со среднеквадратичной ошибкой 68 628 долларов США линейной регрессии. Давайте выясним, каким будет RMSE, если мы также применим 10-кратную перекрестную проверку в регрессии.

## Using cross validation on linear regression
lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels, scoring="neg_mean_squared_error", cv=10)
lin_rmse_scores = np.sqrt(-lin_scores)
display_scores(lin_rmse_scores)

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

5 в. Случайный лес

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

Давайте посмотрим на RMSE случайного леса на обучающих наборах.

housing_predictions = forest_reg.predict(housing_prepared) forest_mse = mean_squared_error(housing_labels, housing_predictions) forest_rmse = np.sqrt(forest_mse)
forest_rmse

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

### Using cross validation in random forest
from sklearn.model_selection import cross_val_score
forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels,scoring="neg_mean_squared_error", cv=10)
forest_rmse_scores = np.sqrt(-forest_scores)
display_scores(forest_rmse_scores)

Неплохо, на данный момент это одна из лучших моделей с частотой ошибок 50 182 долл. США, хотя мы видим, что частота ошибок в наборах данных проверки довольно высока по сравнению с обучающими наборами, предполагающими, что может быть проблема с перенастройкой. Давайте попробуем последнюю модель, прежде чем начать штрафовать настроить нашу окончательную модель.

5 д. Машина опорных векторов

### Lets see how SVM performs
from sklearn.svm import SVR
svm_reg = SVR(kernel="linear")
svm_reg.fit(housing_prepared, housing_labels)
housing_predictions = svm_reg.predict(housing_prepared)
svm_mse = mean_squared_error(housing_labels, housing_predictions)
svm_rmse = np.sqrt(svm_mse)
svm_rmse

RMSE в размере 111 094 долларов США определяет Support Vector Machine с момента окончательного рассмотрения.

6. Тонкая настройка модели

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

### Using grid search to fine tune the model. Random forest from sklearn.model_selection import GridSearchCV param_grid = [forest_reg = RandomForestRegressor(random_state=42)
 # try 12 (3×4) combinations of hyperparameters
 {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
# train across 5 folds, that's a total of (12+6)*5=90 rounds of training 
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
 scoring='neg_mean_squared_error',
 return_train_score=True)
grid_search.fit(housing_prepared, housing_labels) 
 # then try 6 (2×3) combinations with bootstrap set as False
 {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
 ] grid_search.best_params_

Давайте посмотрим на результат комбинации гиперпараметров, протестированной во время поиска по сетке.

cvres = grid_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
 print(np.sqrt(-mean_score), params)

Мы видим, что комбинация 8 функций и 30 оценщиков дает наименьшее среднеквадратичное отклонение в размере 49 682 долларов. Когда проблема и данные огромны, обычно рекомендуется использовать рандомизированный поиск, а не поиск по сетке, как показано ниже.

# Randomized hyper parameter search from sklearn.model_selection import RandomizedSearchCVcvres = rnd_search.cv_results_
from scipy.stats import randint 
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
 print(np.sqrt(-mean_score), params)

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

# Feature Importance feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances

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

7. Оцените модель на тестовом наборе

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

final_model = grid_search.best_estimator_ X_test = strat_test_set.drop("median_house_value", axis=1)X_test_prepared = full_pipeline.transform(X_test)final_mse = mean_squared_error(y_test, final_predictions)
y_test = strat_test_set["median_house_value"].copy() 
final_predictions = final_model.predict(X_test_prepared) 
final_rmse = np.sqrt(final_mse)
final_rmse

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

# Computing 95% confidence interval
from scipy import stats

Это говорит нам о том, что ошибка предсказания может колебаться от 45 685 до 49 691 долларов. Разрыв в доверительном интервале около 4000 долларов — это то, с чем мы можем жить. Итак, это наша последняя модель.

8. Выводы и следующие шаги

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