Часть 1 - сформулируйте проблему, показатели эффективности, получение данных, быструю проверку, создание набора тестов и визуальное представление;

Часть 2 - конвейер предварительной обработки, моделирование, оценка;

Оригинальное репозиторий github находится здесь.

8. Предварительная обработка (подготовить данные для моделирования)

Шаги должны охватывать:

  1. разделить объекты, метки из набора обучающих данных;
  2. очистка данных (нет данных)
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()

Отсутствует значение

Варианты:

  1. удалить строки
  2. удалить cols
  3. вменение некоторой стоимости

Категориальная обработка признаков

Почему: большинство ML-моделей предпочитают работать с числами
Labelencoder sklearn может сначала подогнать и преобразовать (или просто использовать метод fit_transform) категориальный столбец в числовой эквивалент

from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
housing_cat = housing["ocean_proximity"]
housing_cat_encoded = encoder.fit_transform(housing_cat)
print(encoder.classes_)
print(encoder.transform(encoder.classes_))
print(encoder.inverse_transform(encoder.transform(encoder.classes_)))

Проблемы с этой кодировкой: эта кодировка подразумевает, что «1 час океана» ближе к «внутреннему», чем «остров» или «около океана». OneHotEncoder более уместен.

from sklearn.preprocessing import OneHotEncoder, LabelBinarizer
encoder = OneHotEncoder(sparse=False)
encoder.fit_transform(housing_cat.values.reshape(-1,1))

Мы также можем сохранить вывод в scipy разреженной матрице (установив sparse = True, этот формат экономит использование памяти, поскольку он сохраняет только позицию «1» разреженной матрицы

Заказные трансформеры

Это важный раздел, так как во многих случаях нам нужно иметь возможность сгенерировать собственный класс трансформатора, чтобы мы могли соответствующим образом обрабатывать различные данные и делать нашу работу автоматической и воспроизводимой. Ниже можно использовать как шаблон для дальнейшего использования.
Чтобы убедиться, что наш настраиваемый преобразователь безупречно работает с функциями sklearn (конвейер и т. д.), нам нужно убедиться, что необходимые методы (fit, transform, fit_transform) доступны в нашем настраиваемом классе.
При создании нашего настраиваемого класса класса (т. е. преобразователя, оценщика), мы можем добавить BaseEstimator и TransformerMixin в качестве базовых классов. Первый дает нам методы get_params () и set_params (), а второй дает нам метод fit_transform () бесплатно.

from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, add_bedrooms_per_room = True): # make sure no *args or **kargs
        self.add_bedrooms_per_room = add_bedrooms_per_room
    def fit(self, X, y=None):
        return self
    def transform(self, X, y=None):
        rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
        population_per_household = X[:, population_ix] / X[:, household_ix]
        if self.add_bedrooms_per_room:
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, household_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]
# transform np.array of original df
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)

Масштабирование функций

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

Трансформационный конвейер

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy="median")),
    ('attribs_adder', CombinedAttributesAdder()),
    ('std_scaler', StandardScaler()),
])
housing_num_tr = num_pipeline.fit_transform(housing_num)

Примечание. Imputer теперь называется SimpleImputer от sklearn.

Оценщики sklearn работают только с массивами numpy. Вместо того, чтобы вручную преобразовывать pd.df в np.array (используя df [cols] .values. Мы можем создать класс, который будет делать это автоматически. Затем его можно будет включить в sklearn.pipeline

class DataFrameSelector(BaseEstimator, TransformerMixin):
    def __init__(self, attribute_names):
        self.attribute_names = attribute_names
    def fit(self, X, y=None):
        return self
    def transform(self, X, y=None):
        return X[self.attribute_names].values
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
num_pipeline = Pipeline([
    ('selector', DataFrameSelector(num_attribs)),
    ('imputer', SimpleImputer(strategy="median")),
    ('attribs_adder', CombinedAttributesAdder()),
    ('std_scaler', StandardScaler()),
])
cat_pipeline = Pipeline([
    ('selector', DataFrameSelector(cat_attribs)),
    ('one_hot_encoder', OneHotEncoder(sparse=False)),
])

Из оригинальной книги handson ml, LabelBinarizer использовался в качестве второго шага в приведенной выше cat_pipeline, однако, поскольку его fit_transform принимает только два позиционных аргумента, см. Здесь, это вызовет проблему, когда мы запустим метод fit_transform на full_pipeline, как и любой другой трансформеры имеют 3 позиционных аргумента. В результате вместо него используется OneHotEncoder, подробнее см. Здесь. Когда я поискал в Интернете, я обнаружил, что этот вопрос уже находится в stackoverflow, поэтому я также внес свои выводы в этот вопрос :).

# Make a complete pipeline by joining the two pipelines using FeatureUnion
from sklearn.pipeline import FeatureUnion
full_pipeline = FeatureUnion(transformer_list=[
    ("num_pipeline", num_pipeline),
    ("cat_pipeline", cat_pipeline),
])
housing_prepared = full_pipeline.fit_transform(housing)

9. Моделирование… Наконец…

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

Общий процесс:

  1. поместите несколько моделей в набор обучающих данных (прежде чем тратить слишком много времени на настройку их гиперпараметров)
  2. использовать перекрестную проверку для получения оценок прогнозов как для обучающего, так и для проверочного набора
  3. Шорт-лист еще несколько перспективных моделей (от двух до пяти)
  4. точно настроить гиперпараметры этих моделей (например, поиск по сетке, рандомизированный поиск)
  5. ансамбль моделей
  6. улучшить выбор функций
  7. оценить результат на тестовом наборе

Подходит для нескольких моделей (например)

# linear regressor
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
# decision tree regressor
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)
# random forest regressor
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor()
forest_reg.fit(housing_prepared, housing_labels)

Перекрестная проверка

# !pip install PrettyTable
from prettytable import PrettyTable
# I wrote a small funciton to display training and cross validation scores for a series of models
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import cross_val_score
models = [lin_reg, tree_reg, forest_reg]
def display_scores(models, model_names, X, y, cv=10, score='rmse'):
    """
    this function will display both training and validation score for a series of models
    models: list of models, 
    X: np array of training_set_features, 
    y: np array of training_set_labels
    score: string that specifies performance score
    at the moment, it's only displaying scores for 'rmse', will extend this function more types later
    """
    
    t = PrettyTable(['Models', 'Training_'+score.upper(), 'Validation_'+score.upper()])
    
    if score == 'rmse':
        for i in range(len(models)):
            # get the training score
            predictions = models[i].predict(X)
            training_mse = mean_squared_error(y, predictions)
            training_rmse = np.sqrt(training_mse)
            # get the validation score
            val_mse = -cross_val_score(models[i], X, y,
                            scoring="neg_mean_squared_error", cv=cv).mean()
            val_rmse = np.sqrt(val_mse)
            # insert data into final table
            t.add_row([model_names[i], training_rmse, val_rmse])
            
    print(t)
display_scores(models, model_names=['lin_reg', 'tree_reg', 'forest_reg'], X=housing_prepared, y=housing_labels)

Интерпретация нашего результата

Значения median_housing_values ​​в большинстве округов находятся в диапазоне от 120 000 до 265 000, поэтому типичная ошибка прогноза от 50 000 до 70 000 не очень удовлетворительна. Когда ошибка обучения высока, наша модель не подходит. Когда ошибка валидации высока (выше, чем ошибка обучения), наша модель переоснащается. Чтобы решить проблему недостаточного оснащения, мы можем увеличить сложность модели. Для лечения переобучения мы можем использовать: 1) регуляризацию; 2) упростить модель; 3) получить больше данных

Точная настройка нашей модели - настройка гиперпараметров

from sklearn.model_selection import GridSearchCV
param_grid = [
{'n_estimators': [3, 10, 30, 50], 'max_features': [2, 4, 6, 8]},
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
scoring='neg_mean_squared_error')
grid_search.fit(housing_prepared, housing_labels)
grid_search.best_estimator_

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

Важность функции

Регрессор случайного леса дает нам указание на важность функции.

feature_importances = grid_search.best_estimator_.feature_importances_
extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
cat_one_hot_attribs = encoder.categories_[0].tolist()
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importances, attributes), reverse=True)

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

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

Не так уж плохо! Конечно, можно сделать многое: попробуйте больше моделей (например, нейронных сетей, моделей ансамбля), больше способов поиска лучших гиперпараметров и т. Д.

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