Автор: Trent Hauck
Переводчик: Wizardforcel
Лицензия: CC BY-NC-SA 4.0
В этом рецепте мы создадим кросс-валидацию, которая может считаться одной из наиболее важных моделей послеобучения. Мы обсудим кросс-валидацию методом k-Fold. Существует несколько видов кросс-валидации с различными случайными режимами. K-Fold является одним из самых известных случайных режимов.
Сначала создадим набор данных, а затем будем обучать классификаторы на различных сложениях. Важно отметить, что если вы можете сохранить часть данных, это будет лучшим вариантом. Например, имеется набор данных N = 1000
, и если мы сохраним 200 точек данных, а затем используем остальные 800 точек данных для кросс-валидации, чтобы найти наилучшие параметры.
Сначала создадим некоторое поддельное данные, протестируем параметры, а затем посмотрим на размер конечного набора данных.
>>> N = 1000
>>> holdout = 200
>>> from sklearn.datasets import make_regression
>>> X, y = make_regression(n_samples=N, shuffle=True)
Теперь, когда у нас есть данные, давайте сохраним 200 точек, а затем обработаем сложение.
>>> X_h, y_h = X[:holdout], y[:holdout]
>>> X_t, y_t = X[holdout:], y[holdout:]
>>> from sklearn.model_selection import KFold
```Метод K-Fold предоставляет нам несколько опций для выбора количества сложений, значений индексов или булевых значений, перемешивания данных и состояния случайности (в основном для воспроизводимости). Значения индексов фактически могут вызвать ошибку в последних версиях. Предположим, что значение индексов равно `True`.Теперь создадим объект кросс-валидации:
```py
>>> kfold = KFold(n_splits=4, random_state=None, shuffle=False)
Теперь можем итерировать по объекту K-Fold:
>>> output_string = "Fold: {}, N_train: {}, N_test: {}"
>>> for i, (train, test) in enumerate(kfold):
... print(output_string.format(i, len(train), len(test)))
...
Fold: 0, N_train: 600, N_test: 200
Fold: 1, N_train: 600, N_test: 200
Fold: 2, N_train: 600, N_test: 200
Fold: 3, N_train: 600, N_test: 200
Каждый проход должен возвращать одинаковое количество тестовых данных.
Как уже было отмечено, принцип работы K-Fold заключается в том, чтобы итерировать по сложениям и сохранять 1/n_splits * N
данных, где N
— это длина нашего y_t
. С точки зрения Python, объект кросс-валидации имеет итератор, который можно использовать с оператором in
. Обычно это полезно при создании обёртки для объекта кросс-валидации, которая будет итерировать по подмножествам данных. Например, мы можем иметь набор данных, содержащий повторные измерения данных, или набор данных пациентов, где каждый пациент имеет свои измерения.
Мы хотим объединить эти данные и использовать Pandas:
>>> import numpy as np
>>> import pandas as pd
>>> patients = np.repeat(np.arange(0, 100, dtype=np.int8), 8)
>>> measurements = pd.DataFrame({'patient_id': patients,
'ys': np.random.normal(0, 1, 800)})
Имея данные, мы хотим оставить только определённых пациентов, а не всех точек данных.
>>> custids = np.unique(measurements.patient_id)
>>> customer_kfold = KFold(n_splits=custids.size, n_folds=4)
```>>> output_string = "Fold: {}, N_train: {}, N_test: {}"
>>> for i, (train, test) in enumerate(customer_kfold):
train_cust_ids = custids[train]
training = measurements[measurements.patient_id.isin(train_cust_ids)]
testing = measurements[~measurements.patient_id.isin(train_cust_ids)]
print(output_string.format(i, len(training), len(testing)))
Fold: 0, N_train: 600, N_test: 200
Fold: 1, N_train: 600, N_test: 200
Fold: 2, N_train: 600, N_test: 200
Fold: 3, N_train: 600, N_test: 200
## 5.2 Автоматизация кросс-валидации
Мы рассмотрим, как использовать встроенные механизмы кросс-валидации из библиотеки Scikit-Learn, но также можем использовать вспомогательную функцию для автоматизации выполнения кросс-валидации. Это аналогично тому, как другие объекты Scikit-Learn оборачиваются в вспомогательные функции и пайплайны.
### Подготовка
Сначала нам нужно создать пример классификатора, что может быть любым — дерево решений, случайный лес и так далее. Для нас это случайный лес. Мы затем создаём набор данных и используем функцию кросс-валидации.
### Как это работает
Для начала импортируем модуль `ensemble`:
```py
>>> from sklearn import ensemble
>>> rf = ensemble.RandomForestRegressor(max_features='auto')
Отлично, теперь давайте создадим некоторые данные для регрессии:
>>> from sklearn import datasets
>>> X, y = datasets.make_regression(10000, 10)
Имея данные, мы можем импортировать модуль cross_validation
, чтобы получить функции, которые будем использовать.
>>> from sklearn.model_selection import cross_val_score
>>> scores = cross_val_score(rf, X, y)
>>> print(scores)
[ 0.86823874 0.86763225 0.86986129]
```## Как это работает
Основная часть работы выполняется объектом кросс-валидации. Одним из преимуществ является возможность параллельной обработки кросс-валидации.
Можно включить подробный режим:
```py
>>> scores = cross_validation.cross_val_score(rf, X, y, verbose=3,
cv=4)
[CV] нет параметров для установки
[CV] нет параметров для установки, score=0.872866 - 0.7s
[CV] нет параметров для установки
[CV] нет параметров для установки, score=0.873679 - 0.6s
[CV] нет параметров для установки
[CV] нет параметров для установки, score=0.878018 - 0.7s
[CV] нет параметров для установки
[CV] нет параметров для установки, score=0.871598 - 0.6s
[Parallel(n_jobs=1)]: выполнено 1 задач(и) | затрачено времени: 0.7s
[Parallel(n_jobs=1)]: выполнено 4 задач(и) из 4 | затрачено времени: 2.6s завершено
Мы видим, что во время каждой итерации вызывается функция для получения оценки. Также мы можем наблюдать, как работает модель.
Не менее важно отметить, что можно получить прогнозные оценки для модели, которую мы пытаемся обучить. Мы также обсудим, как создать свои собственные функции оценки.
ShuffleSplit
— один из самых простых методов кросс-валидации. Этот подход просто использует выборки данных для указанного количества итераций.
ShuffleSplit
— ещё один простой метод кросс-валидации. Мы указываем общее количество элементов в наборе данных, и он использует оставшиеся части. Мы рассмотрим пример, чтобы оценить среднее значение одномерного набора данных. Это немного похоже на переобразование, но демонстрирует причину использования кросс-валидации при её демонстрации.### Шаги выполненияСначала нам нужно создать набор данных. Мы используем NumPy для создания набора данных, где мы знаем истинное среднее значение. Мы будем брать случайные образцы из половины набора данных для оценки среднего значения и сравнивать его с истинным средним значением.
>>> import numpy as np
>>> true_loc = 1000
>>> true_scale = 10
>>> N = 1000
>>> dataset = np.random.normal(true_loc, true_scale, N)
>>> import matplotlib.pyplot as plt
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.hist(dataset, color='k', alpha=.65, histtype='stepfilled');
>>> ax.set_title("Гистограмма набора данных");
f.savefig("978-1-78398-948-5_06_06.png")
NumPy выводит следующее:

Теперь давайте вырежем первое полное наблюдение данных и сделаем прогноз среднего значения:
```py
>>> from sklearn import cross_validation
>>> holdout_set = dataset[:500]
>>> fitting_set = dataset[500:]
>>> estimate = fitting_set[:N//2].mean()
>>> from matplotlib.pyplot import plt
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.set_title("Истинное среднее против обычной оценки")
>>> ax.vlines(true_loc, 0, 1, color='r', linestyles='-', lw=5,
alpha=.65, label='истинное среднее')
>>> ax.vlines(estimate, 0, 1, color='g', linestyles='-', lw=5,
alpha=.65, label='обычная оценка')
>>> ax.set_xlim(999, 1001)
>>> ax.legend()
>>> f.savefig("978-1-78398-948-5_06_07.png")
Выход будет таким:
Теперь мы можем использовать ShuffleSplit
, чтобы обучить оценки на множестве схожих наборов данных.
>>> from sklearn.cross_validation import ShuffleSplit
>>> shuffle_split = ShuffleSplit(len(fitting_set))
>>> mean_p = []
``````markdown
>>> для train, _ в shuffle_split:
mean_p.append(fitting_set[train].mean())
shuf_estimate = np.mean(mean_p)
>>> от matplotlib.pyplot импортировать plt
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.vlines(true_loc, 0, 1, color='r', linestyles='-', lw=5,
alpha=.65, label='истинное среднее')
>>> ax.vlines(estimate, 0, 1, color='g', linestyles='-', lw=5,
alpha=.65, label='обычная оценка')
>>> ax.vlines(shuf_estimate, 0, 1, color='b', linestyles='-', lw=5,
alpha=.65, label='shuffle split оценка')
>>> ax.set_title("Все Оценки")
>>> ax.set_xlim(999, 1001)
>>> ax.legend(loc=3)
Выход будет таким:
Мы видим, что получили оценку, которая похожа на ожидаемую, но возможно нам потребуется несколько выборок, чтобы получить это значение.
В этом рецепте мы быстро рассмотрим кросс-валидацию с учетом распределения классов. Мы будем рассматривать различные рецепты, где представление классификации каким-то образом неравномерно. Кросс-валидация с учетом распределения классов очень хороша, так как её модель специально создана для поддержания пропорций классов.
Мы хотим создать небольшой набор данных. В этом наборе данных мы затем будем использовать кросс-валидацию с учётом распределения классов. Мы хотим сделать его как можно меньше, чтобы увидеть изменения. Для больших образцов это может быть не особенно эффективно. После этого мы будем строить графики соотношения классов на каждом шаге, чтобы показать, как поддерживать соотношение классов.
>>> from sklearn import datasets
>>> X, y = datasets.make_classification(n_samples=int(1e3),
weights=[1./11])
```
Давайте проверим общее распределение весов классов:
```py
>>> y.mean()
0.90300000000000002
```
90,5% образцов имеют значение 1, остальные — 0.
### Шаги выполнения
Создадим объект с 层化K折交叉验证并遍历每个折叠。我们将测量标记为1的比例。然后,我们将通过拆分子索引来绘制类别的比例,以查看它们是如何变化的。这段代码展示了为什么它是如此有效。我们也将在基础的`ShuffleSplit`上绘制这段代码。
```py
>>> from sklearn import cross_validation
>>> n_folds = 50
>>> strat_kfold = cross_validation.StratifiedKFold(y,
n_folds=n_folds)
>>> shuff_split = cross_validation.ShuffleSplit(n=len(y),
n_iter=n_folds)
>>> kfold_y_props = []
>>> shuff_y_props = []
>>> for (k_train, k_test), (s_train, s_test) in zip(strat_kfold,
>>> shuff_split):
kfold_y_props.append(y[k_train].mean())
shuff_y_props.append(y[s_train].mean())
```
Теперь давайте построим графики для каждого шага:
```py
>>> import matplotlib.pyplot as plt
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.plot(range(n_folds), shuff_y_props, label="ShuffleSplit",
color='k')
>>> ax.plot(range(n_folds), kfold_y_props, label="Stratified",
color='k', ls='--')
>>> ax.set_title("Сравнение соотношений классов.")
>>> ax.legend(loc='best')
```
Результат будет следующим:

Можно заметить, что соотношение классов в каждом слое KFold остаётся стабильным между слоями.
### Как это работает
Принцип работы Stratified KFold заключается в выборе значений `y`. Сначала получаем соотношение всех классов, а затем разделяем тренировочные и тестовые наборы пропорционально. Это можно распространить на несколько меток:
```py
>>> strat_kfold = cross_validation.StratifiedKFold(y, n_folds=n_folds)
>>> shuff_split = cross_validation.ShuffleSplit(n=len(y), n_iter=n_folds)
``````py
>>> import numpy as np
>>> three_classes = np.random.choice([1, 2, 3], p=[0.1, 0.4, 0.5],
size=1000)
>>> import itertools as it
>>> for train, test in cross_validation.StratifiedKFold(three_classes, 5):
print(np.bincount(three_classes[train]))
[ 0 0 90 314 395]
[ 0 0 90 314 395]
[ 0 0 90 314 395]
[ 0 0 91 315 395]
[ 0 0 91 315 396]
```
Можно заметить, что мы получили размер выборок для каждого класса, который точно соответствует пропорциям тренировочных и тестовых наборов данных.
## 5.5 Грид-поиск для новичков
В этом рецепте мы будем использовать Python для представления базового грид-поиска, а также Scikit-Learn для моделирования моделей и Matplotlib для визуализации.
### Подготовка
В этом рецепте мы выполним следующие действия:
+ Создание базового грид-поиска в пространстве параметров.
+ Итерация через этот грид и проверка значения функции потерь или метрики для каждой точки в пространстве параметров.
+ Выбор точки в пространстве параметров, которая максимизирует или минимизирует значение функции оценки.
Аналогично, модель, которую мы обучаем, представляет собой базовый классификатор дерева решений. Пространство параметров является двумерным, что помогает нам визуализировать его.
```
criteria = {'gini', 'entropy'}
max_features = {'auto', 'log2', None}
```
Пространство параметров представляет собой декартово произведение `criteria` и `max_features`.
Мы узнаем, как использовать `itertools`, чтобы итерировать через это пространство.
```Давайте создадим набор данных, чтобы начать:
```py
>>> from sklearn import datasets
>>> X, y = datasets.make_classification(n_samples=2000, n_features=10)
```
### Шаги операции
Как было сказано ранее, мы используем грид-поиск для настройки двух параметров — `criteria` и `max_features`. Нам потребуется представить эти параметры в виде Python множества, после чего использовать `itertools.product` для итерации через них.
Теперь, когда у нас есть пространство параметров, давайте итерировать через него и проверять точность каждого моделируемого параметра. После этого мы сохраним эту точность для сравнения различных пространств параметров. Мы также будем использовать наборы обучения и тестирования, разделённые на части соотношением 50/50.
```py
import numpy as np
train_set = np.random.choice([True, False], size=len(y))
from sklearn.tree import DecisionTreeClassifier
accuracies = {}
for criterion, max_feature in parameter_space:
dt = DecisionTreeClassifier(criterion=criterion,
max_features=max_feature)
dt.fit(X[train_set], y[train_set])
accuracies[(criterion, max_feature)] = (dt.predict(X[~train_set]) == y[~train_set]).mean()
>>> accuracies
{('entropy', None): 0.974609375, ('entropy', 'auto'): 0.9736328125, ('entropy', 'log2'): 0.962890625, ('gini', None): 0.9677734375, ('gini', 'auto'): 0.9638671875, ('gini', 'log2'): 0.96875}
```
Так что теперь у нас есть точность и её производительность. Давайте визуализируем её производительность.```py
>>> from matplotlib import pyplot as plt
>>> from matplotlib import cm
>>> cmap = cm.RdBu_r
>>> f, ax = plt.subplots(figsize=(7, 4))
>>> ax.set_xticklabels([''] + list(кriterion))
>>> ax.set_yticklabels([''] + list(max_functions))
>>> plot_array = []
>>> for max_function in max_functions:
m = []
>>> for criterion in criteria:
m.append(accuracies[(criterion, max_function)])
plot_array.append(m)
>>> colors = ax.matshow(plot_array, vmin=np.min(list(accuracies.values())) - 0.001, vmax=np.max(list(accuracies.values())) + 0.001, cmap=cmap)
>>> f.colorbar(colors)
```Выход будет следующим:

Очень легко заметить, какой вариант лучше всего работает. Вы можете использовать метод бустинга, чтобы увидеть, как он может быть использован для дальнейшей работы с данными.
### Как это работает
Принцип очень простой — нам нужно выполнить следующие шаги:
1. Выбрать набор параметров
2. Пройтись по ним и вычислить точность каждого варианта
3. Визуализировать данные для поиска наилучшего варианта
## 5.6 Бустинг сеточного поиска
В этом рецепте мы будем использовать `Sklearn` для более подробного сеточного поиска. Это почти то же самое, что и в прошлом рецепте, но мы используем встроенные методы.
Мы также рассмотрим пример использования случайной оптимизации. Это альтернативный подход к бустингу поиска. Основная идея заключается в том, что мы тратим некоторое количество вычислительных циклов, чтобы гарантировать, что всё пространство было исследовано. Мы были довольно спокойны в прошлом рецепте, но вы можете представить себе модели с несколькими этапами, где первым шагом является оценка пропущенных данных, а затем использование PCA для снижения размерности перед классификацией. Ваше пространство параметров может быть очень большим и сложным, поэтому поиск части этого пространства может быть полезным.### Подготовка
Для начала нам потребуются следующие шаги:
1. Создание набора данных
2. Создание объекта `LogisticRegression`, обучение нашей модели
3. Создание объекта поиска, `GridSearch` и `RandomizedSearchCV`
### Как это работает
Выполните следующий код для создания набора категориальных данных:
```py
>>> from sklearn.datasets import make_classification
>>> X, y = make_classification(n_samples=1000, n_features=5)
```
Теперь создаем объект логистической регрессии:
```py
>>> from sklearn.linear_model import LogisticRegression
>>> lr = LogisticRegression(C=1.0, class_weight='balanced', dual=False, fit_intercept=True, penalty='l2', random_state=None, tol=0.0001)
```
Нам нужно указать параметры, которые хотим исследовать. Для `GridSearch` мы можем указать диапазон значений, но для `RandomizedSearchCV` нам действительно нужно указать распределение того же самого пространства:
```py
>>> lr.fit(X, y)
>>> grid_search_params = {'penalty': ['l1', 'l2'], 'C': [1, 2, 3, 4]}
```
Теперь нам нужно сделать единственное изменение — представить параметр `C` как распределение вероятностей. В данный момент мы делаем это просто, хотя используем библиотеку `scipy` для его описания.
```py
>>> import scipy.stats as st
>>> random_search_params = {'penalty': ['l1', 'l2'], 'C': st.randint(low=1, high=4)}
```
## Как это работает
Теперь мы будем обучать классификатор. Основная идея заключается в передаче объекту поиска параметра `lr`.```py
>>> from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
>>> gs = GridSearchCV(lr, grid_search_params)
```
`GridSearchCV` реализует ту же самую API, что и другие методы:
```py
>>> gs.fit(X, y)
GridSearchCV(cv=None, estimator=Логистическая регрессия (C=1.0,
class_weight='auto', dual=False, fit_intercept=True,
intercept_scaling=1, penalty='l2', random_state=None,
tol=0.0001), fit_params={}, iid=True, loss_function=None,
n_jobs=1, param_grid={'penalty': ['l1', 'l2'], 'C': [1, 2, 3, 4]},
pre_dispatch='2*n_jobs', refit=True, score_func=None, scoring=None,
verbose=0)
```
Можно заметить, что параметры `penalty` и `C` являются массивами в `param_grid`.
Для оценки качества можно использовать атрибут `cv_results_` объекта `GridSearchCV`. Мы также хотим найти оптимальное сочетание параметров. Также можно просмотреть средние значения метрик.
```py
>>> gs.cv_results_
[{'mean_test_score': 0.90300, 'std_test_score': 0.01192, 'params': {'penalty': 'l1', 'C': 1}},
{'mean_test_score': 0.90100, 'std_test_score': 0.01258, 'params': {'penalty': 'l2', 'C': 1}},
{'mean_test_score': 0.90200, 'std_test_score': 0.01117, 'params': {'penalty': 'l1', 'C': 2}},
{'mean_test_score': 0.90100, 'std_test_score': 0.01258, 'params': {'penalty': 'l2', 'C': 2}},
{'mean_test_score': 0.90200, 'std_test_score': 0.01117, 'params': {'penalty': 'l1', 'C': 3}},
{'mean_test_score': 0.90100, 'std_test_score': 0.01258, 'params': {'penalty': 'l2', 'C': 3}},
{'mean_test_score': 0.90100, 'std_test_score': 0.01258, 'params': {'penalty': 'l1', 'C': 4}},
{'mean_test_score': 0.90100, 'std_test_score': 0.01258, 'params': {'penalty': 'l2', 'C': 4}}]
```
Мы можем захотеть получить максимальное значение метрики.```py
>>> gs.cv_results_[1]['mean_test_score']
0.90100000000000002
>>> max(gs.cv_results_, key=lambda x: x['mean_test_score'])
{'mean_test_score': 0.90300, 'std_test_score': 0.01192, 'params': {'penalty': 'l1', 'C': 1}}
```
Полученные параметры являются нашими лучшими параметрами для логистической регрессии.
## 5.7 Использование поддельных оценщиков для сравнения результатов
Этот рецепт посвящён созданию псевдооценщиков. Это может не выглядеть эстетично или интересно, но мы заслуживаем иметь эталон для последней построенной модели.### Подготовка
В этом рецепте мы выполним следующие задачи:
1. Создание случайных данных
2. Обучение нескольких поддельных оценщиков
Мы будем выполнять эти шаги как для регрессионных данных, так и для классификационных данных.
### Операция
Сначала создадим случайные данные:
```py
>>> X, y = make_regression()
>>> from sklearn import dummy
>>> dumdum = dummy.DummyRegressor()
>>> dumdum.fit(X, y)
DummyRegressor(constant=None, strategy='mean')
```
Обычно, оценщик использует среднее значение данных для прогнозирования.
```py
>>> dumdum.predict(X)[:5]
array([2.23297907, 2.23297907, 2.23297907, 2.23297907, 2.23297907])
```
Можно попробовать две других стратегии. Можно предоставить константное значение для прогнозирования (то есть `constant=None` в команде выше), а также использовать медианное значение для прогнозирования.
Если стратегия — это `constant`, то используется предоставленное константное значение.
Давайте проверим:
```py
>>> predictors = [("mean", None),
("median", None),
("constant", 10)]
>>> for strategy, constant in predictors:
dumdum = dummy.DummyRegressor(strategy=strategy,
constant=constant)
>>> dumdum.fit(X, y)
>>> print("strategy: {}".format(strategy)), ",".join(map(str,
dumdum.predict(X)[:5]))
strategy: mean 2.23297906733,2.23297906733,2.23297906733,2.23297906733,2.23297906733
strategy: median 20.38535248,20.38535248,20.38535248,20.38535248,20.38535248
strategy: constant 10.0,10.0,10.0,10.0,10.0
```
На самом деле, у нас есть четыре варианта классификатора. Эти стратегии аналогичны непрерывному случаю, но применимы к задачам классификации:```py
>>> predictors = [("constant", 0),
("stratified", None),
("uniform", None),
("most_frequent", None)]
```
Также нам потребуется создать некоторые классификационные данные:
```
>>> X, y = make_classification()
>>> for strategy, constant in predictors:
dumdum = dummy.DummyClassifier(strategy=strategy,
constant=constant)
dumdum.fit(X, y)
print("стратегия: {}".format(strategy)),",".join(map(str,dumdum.predict(X)[:5]))
стратегия: constant 0,0,0,0,0
стратегия: stratified 1,0,0,1,0
стратегия: uniform 0,0,0,1,1
стратегия: most_frequent 1,1,1,1,1
```
### Принцип работы
Лучше всего тестировать вашу модель на самом простом примере, что и делает поддельный оценщик. Например, в модели 5% данных являются поддельными. Поэтому мы можем обучить красивую модель, не пытаясь угадывать подделку.
Мы можем использовать стратифицированный (`stratified`) подход для обучения модели с помощью следующей команды. Также можно получить хороший пример того, почему неравномерность классификации может вызвать проблемы:
```py
>>> X, y = make_classification(20000, weights=[.95, .05])
>>> dumdum = dummy.DummyClassifier(strategy='most_frequent')
>>> dumdum.fit(X, y)
DummyClassifier(constant=None, random_state=None, strategy='most_frequent')
>>> from sklearn.metrics import accuracy_score
>>> print(accuracy_score(y, dumdum.predict(X)))
0.94575
```
На самом деле, мы часто правы, но это не главное. Главное то, что это наш базовый уровень. Если мы не можем создать модель для распознавания подделки и сделать её более точной, чем этот базовый уровень, это не стоит затраченного времени.
```## 5.8 Оценка моделей регрессии
Мы уже узнали, как количественно оценивать ошибки в классификации, теперь рассмотрим ошибки в задачах с непрерывными значениями. Например, когда мы пытаемся предсказать возраст вместо пола.
### Подготовка
Как и в случае классификации, мы будем генерировать некоторые поддельные данные и затем отображать изменения. Мы начнем с простого и постепенно усложняем. Данные представляют собой линейную модель.
```py
m = 2
b = 1
y = lambda x: m * x + b
```
Также импортируем наши модули:
```py
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from sklearn import metrics
```
### Шаги выполнения
Мы выполним следующие шаги:
1. Используем `y`, чтобы сгенерировать `y_actual`.
2. Используем `y_actual` вместе с некоторым `err`, чтобы сгенерировать `y_prediction`.
3. Отобразим различия.
4. Пройдемся по различным метрикам и отобразим их.
Давайте сосредоточимся одновременно на шагах 1 и 2 и создадим функцию, которая поможет нам. Это то же самое, что мы только что видели, но мы добавили возможность указывать ошибку (если она постоянна, то это будет смещение).
```py
>>> def data(x, m=2, b=1, e=None, s=10):
"""
Аргументы:
x: Значение x
m: Угловой коэффициент
b: Свободный член
e: Ошибка, опционально, если True, будет случайная ошибка
"""
if e is None:
e_i = 0
elif e is True:
e_i = np.random.normal(0, s, len(xs))
else:
e_i = e
return x * m + b + e_i
```Теперь что у нас есть функция, давайте определим `y_hat` и `y_actual`. Мы реализуем это удобным способом:
```py
>>> from functools import partial
>>> N = 100
>>> xs = np.sort(np.random.rand(N)*100)
>>> y_pred_gen = partial(данные, x=xs, e=True)
>>> y_true_gen = partial(данные, x=xs)
>>> y_pred = y_pred_gen()
>>> y_true = y_true_gen()
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.set_title("Построение графика соответствия модели процессу.")
>>> ax.scatter(xs, y_pred, label=r'$\hat{y}$')
>>> ax.plot(xs, y_true, label=r'$y$')
>>> ax.legend(loc='best')
```
Выход будет следующим:

Для проверки выхода мы вычислим классический остаток.
```py
>>> e_hat = y_pred - y_true
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.set_title("Остатки")
>>> ax.hist(e_hat, color='r', alpha=.5, histtype='stepfilled')
```
Выход будет следующим:

Все выглядит хорошо.
### Как это работает
Теперь давайте рассмотрим метрики.
Сначала одна из метрик — среднеквадратичная ошибка.
```
MSE(y_true, y_pred) = E((y_true - y_pred)^2)
```
```py
mse = ((y_true - y_pred) ** 2).mean()
```
Вы можете использовать следующий код для вычисления значения среднеквадратичной ошибки:
```py
>>> metrics.mean_squared_error(y_true, y_pred)
93.342352628475368
```
Обратите внимание, этот код накладывает штраф за большие ошибки. Обратите внимание, то что мы делаем здесь, это применяем возможную функцию потери модели к тестовым данным.
Другой метрикой является средняя абсолютная ошибка. Нам нужно вычислить абсолютное значение разницы. Если бы мы этого не сделали, наши значения могли бы приближаться к нулю, то есть к среднему значению распределения:```
MAD(y_истинное, y_предсказанное) = E(|y_истинное - y_предсказанное|)
```
```py
mad = np.abs(y_истинное - y_предсказанное).mean()
```
Конечным вариантом является коэффициент детерминации \( R^2 \), который равен единице минус отношение среднеквадратичной ошибки модели к среднеквадратичной ошибке всего набора данных:
```py
rsq = 1 - ((y_истинное - y_предсказанное) ** 2).sum() / ((y_истинное - y_истинное.mean()) ** 2).sum()
```
```py
>>> метрики.r2_score(y_истинное, y_предсказанное)
0.9729312117010761
```
\( R^2 \) является описательным, он не предоставляет четкого представления о точности модели.
## 5.9 Выбор признаков
Эта рецепция и последующие относятся к автоматическому выбору признаков. Я предпочитаю рассматривать его как замену параметров в задаче настройки гиперпараметров. Так же как мы используем кросс-валидацию для поиска подходящих общих параметров, мы можем искать подходящее общее подмножество признаков. Это затрагивает несколько различных методов. Наивным подходом является простое отборочное действие. Другие методы включают в себя работу с комбинациями признаков.
Дополнительной выгодой отбора признаков является то, что он может уменьшить нагрузку на сбор данных. Представьте, что вы уже построили модель на небольшом подмножестве данных. Если всё хорошо, вы можете расширить её для прогнозирования всего этого множества данных. В этом случае можно уменьшить объём работы по сбору данных.### Подготовка
В одновекторном отборе снова появляется оценочная функция. На этот раз она определяет метрику сравнения, которую мы можем использовать для удаления некоторых признаков.
В этом рецепте мы будем обучать регрессионную модель с 10000 признаками, но только с 1000 наблюдениями. Мы рассмотрим несколько вариантов одновекторного отбора признаков.
```py
>>> from sklearn import datasets
>>> X, y = datasets.make_regression(1000, 10000)
```
Имея данные, мы сравним признаки различными способами. Это очень распространено при анализе текстовых данных или биоинформатических данных.
### Операция
Сначала нам нужно импортировать модуль `feature_selection`.
```py
>>> from sklearn import feature_selection
>>> f, p = feature_selection.f_regression(X, y)
```
Здесь `f` — это F-оценка, связанная с каждым признаком линейной модели. Мы можем сравнивать эти признаки и выбирать лучшие на основе этой оценки. `p` — это значение p для каждого значения F.
В статистике значение p представляет собой вероятность того, что значение будет еще более экстремальным, чем текущее значение тестовой статистики. Здесь тестовая статистика — это значение F.
```py
>>> f[:5]
array([ 1.06271357e-03, 2.91136869e+00, 1.01886922e+00,
2.22483130e+00, 4.67624756e-01])
>>> p[:5]
array([ 0.97400066, 0.08826831, 0.31303204, 0.1361235 , 0.49424067])
```
Можно заметить, что многие значения p слишком велики. Нам хотелось бы видеть меньшие значения p. Поэтому мы можем взять NumPy из нашего инструментария и выбрать значения p меньше 0.05. Эти будут нашими признаками для анализа.```py
>>> import numpy as np
>>> idx = np.arange(0, X.shape[1])
>>> features_to_keep = idx[p < .05]
>>> len(features_to_keep)
501
```
Вы можете видеть, что мы действительно сохранили значительное количество признаков. В зависимости от контекста модели, мы можем уменьшить значение p до другого уровня. Это уменьшит количество сохранённых признаков. Другой выбор — использовать объект `VarianceThreshold`. Мы уже немного с ним работали. Однако важно понять, что наша способность обучать модели основана на изменениях, создаваемых признаками. Без этих изменений наши признаки не смогут описать изменения зависимой переменной. В соответствии с документацией, хорошие признаки могут использоваться в непараметрических случаях, так как они не являются результатом случайной вариации. Для фильтрации признаков нам требуется установить начальное значение порога. Для этого мы выбираем и предоставляем медианное значение дисперсии признаков.
```py
>>> var_threshold = feature_selection.VarianceThreshold(np.median(np.var(X, axis=1)))
>>> var_threshold.fit_transform(X).shape
(1000, 4835)
```
Можно заметить, что мы отфильтровали почти половину признаков, что в целом соответствует нашим ожиданиям.
### Как это работает
Обычно принцип всех этих методов заключается в использовании одного признака для обучения базовой модели. В зависимости от того, является ли задача классификацией или регрессией, можно использовать подходящую функцию оценки.
```Рассмотрим более маленькую проблему и визуализируем, как выборка признаков отсеивает конкретные признаки. Мы используем ту же функцию оценки, что и в первом примере, но теперь имеется всего 20 признаков.
```py
>>> X, y = datasets.make_regression(10000, 20)
>>> f, p = feature_selection.f_regression(X, y)
```
Теперь давайте построим график значений `p` для каждого признака:
```py
>>> from matplotlib import pyplot as plt
>>> f, ax = plt.subplots(figsize=(7, 5))
>>> ax.bar(np.arange(20), p, color='k')
>>> ax.set_title("Значения p для признаков")
```
На выходе получаем следующий график:

Можно видеть, что многие признаки были отсеяны, но некоторые признаки всё ещё остаются.
## 5.10 Выборка признаков с помощью нормы L1
Мы хотим реализовать аналогичные идеи, которые мы уже встретили в рецепте про лассо-регрессию. В том рецепте мы рассматривали количество признаков с нулевыми коэффициентами.
Теперь мы хотим пойти дальше и использовать норму L1 для предварительной обработки признаков.
### Подготовка
Мы будем использовать набор данных диабета для обучения модели регрессии. Сначала обучим базовую модель `LinearRegression`, а затем воспользуемся выборкой признаков для удаления бесполезных признаков. Наша цель — избежать переобучения, то есть чтобы модель была слишком специфична для тренировочных данных и не могла хорошо работать на новых данных.Мы выполним следующие шаги:
1. Загрузка данных
2. Обучение базовой модели линейной регрессии
3. Удаление бесполезных признаков с помощью выборки признаков
4. Обучение модели линейной регрессии заново и сравнение качества её прогнозов с полной моделью.
### Описание действий
Сначала загружаем данные:
```py
>>> import sklearn.datasets as ds
>>> diabetes = ds.load_diabetes()
```
Давайте импортируем функцию `mean_squared_error` модуля метрик и функцию `ShuffleSplit` модуля кросс-валидации.
```py
>>> from sklearn import metrics
>>> from sklearn import cross_validation
>>> shuff = cross_validation.ShuffleSplit(diabetes.target.size)
```
Теперь обучим модель и будем отслеживать среднеквадратическую ошибку в каждом цикле `ShuffleSplit`.
```py
>>> mses = []
>>> for train, test in shuff:
train_X = diabetes.data[train]
train_y = diabetes.target[train]
test_X = diabetes.data[~train]
test_y = diabetes.target[~train]
lr.fit(train_X, train_y)
mses.append(metrics.mean_squared_error(test_y,
lr.predict(test_X)))
>>> np.mean(mses)
2856.366626198198
```
Так как мы выполнили обычное обучение модели, давайте проверим её снова после удаления признаков с коэффициентами равными нулю. Обучим модель лассо-регрессии:
```py
>>> from sklearn import feature_selection
>>> from sklearn import linear_model
>>> cv = linear_model.LassoCV()
>>> cv.fit(diabetes.data, diabetes.target)
>>> cv.coef_
array([ -0., -226.2375274, 526.85738059, 314.44026013,
-196.92164002, 1.48742026, -151.78054083, 106.52846989,
530.58541123, 64.50588257])
```
Мы удалим первый признак. Я использую массив NumPy для представления колонок, содержащихся в модуле.```py
>>> import numpy as np
>>> columns = np.arange(diabetes.data.shape[1])[cv.coef_ != 0]
>>> columns
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
```
Хорошо, теперь мы будем использовать конкретные признаки для обучения модели (смотрите ниже):
```py
>>> l1mses = []
>>> for train, test in shuff:
train_X = diabetes.data[train][:, columns]
train_y = diabetes.target[train]
test_X = diabetes.data[~train][:, columns]
test_y = diabetes.target[~train]
lr.fit(train_X, train_y)
l1mses.append(metrics.mean_squared_error(test_y,
lr.predict(test_X)))
>>> np.mean(l1mses)
2861.0763924492171
>>> np.mean(l1mses) - np.mean(mses)
4.7097662510191185
```
Можно заметить, что даже после удаления бесполезных признаков, качество модели не сильно улучшилось. Это не всегда происходит. В следующей части мы сравним модели, где многие признаки несут мало информации.### Принцип работы
Сначала мы создаем набор данных для регрессии с большим количеством бесполезных признаков:
```py
>>> X, y = ds.make_regression(noise=5)
```
Давайте обучим обычную модель регрессии:
```py
>>> mses = []
>>> shuff = cross_validation.ShuffleSplit(y.size)
>>> for train, test in shuff:
... train_X = X[train]
... train_y = y[train]
...
... test_X = X[~train]
... test_y = y[~train]
...
... lr.fit(train_X, train_y)
... mses.append(metrics.mean_squared_error(test_y,
... lr.predict(test_X)))
>>>
>>> np.mean(mses)
879.75447864034209
```
Теперь мы можем использовать тот же процесс для применения регрессии Lasso:
```py
>>> cv.fit(X, y)
LassoCV(alphas=None, copy_X=True, cv=None, eps=0.001,
fit_intercept=True, max_iter=1000, n_alphas=100,
n_jobs=1, normalize=False, positive=False, precompute='auto',
tol=0.0001, verbose=False)
```Мы снова создаем столбцы. Это хорошая практика, позволяющая нам выбирать, какие столбцы включать.
```py
>>> import numpy as np
>>> columns = np.arange(X.shape[1])[cv.coef_ != 0]
>>> columns[:5]
array([11, 15, 17, 20, 21])
>>> mses = []
>>> shuff = cross_validation.ShuffleSplit(y.size)
>>> for train, test in shuff:
... train_X = X[train][:, columns]
... train_y = y[train]
...
... test_X = X[~train][:, columns]
... test_y = y[~train]
...
... lr.fit(train_X, train_y)
... mses.append(metrics.mean_squared_error(test_y,
... lr.predict(test_X)))
>>>
>>> np.mean(mses)
15.755403220117708
```
Мы видим, что достигли значительного улучшения модели. Это объясняет необходимость учета того факта, что не все признаки должны быть использованы в модели.
## 5.11 Сохранение модели с помощью joblib
В этом рецепте мы покажем, как сохранять модель для последующего использования. Например, вы можете использовать модель для прогнозирования и автоматического принятия решений.
### Подготовка
В этом рецепте мы выполним следующие задачи:
1. Обучим модель, которую будем сохранять.
2. Импортируем `joblib` и сохраним модель.
### Операция
Чтобы сохранить нашу модель с помощью `joblib`, используйте следующий код:
```py
>>> from sklearn import datasets, tree
>>> X, y = datasets.make_classification()
>>> dt = tree.DecisionTreeClassifier()
>>> dt.fit(X, y)
DecisionTreeClassifier(compute_importances=None, criterion='gini',
max_depth=None, max_features=None,
max_leaf_nodes=None, min_density=None,
min_samples_leaf=1, min_samples_split=2,
random_state=None, splitter='best')
```>>> from sklearn.externals import joblib
>>> joblib.dump(dt, "dtree.clf")
['dtree.clf',
'dtree.clf_метод']
Далее следует описание принципа работы:
Принцип работы заключается в сохранении состояния объекта таким образом, чтобы его можно было восстановить обратно в объект Sklearn. Важно отметить, что состояние модели может иметь различную степень сложности в зависимости от типа модели.
Для простоты представим, что мы сохраняем модель, которая используется для прогнозирования результатов. Для задач регрессии это довольно просто — достаточно линейной алгебры. Однако для моделей, таких как случайный лес, могут потребоваться сотни деревьев с различной степенью сложности, что делает процесс более трудоемким.
### Дополнительно
Можно также рассмотреть пример сохранения случайного леса:
```py
>>> from sklearn import ensemble
>>> rf = ensemble.RandomForestClassifier()
>>> rf.fit(X, y)
RandomForestClassifier(bootstrap=True, compute_importances=None,
criterion='gini', max_depth=None,
max_features='auto', max_leaf_nodes=None,
min_density=None, min_samples_leaf=1,
min_samples_split=2, n_estimators=10,
n_jobs=1, oob_score=False,
random_state=None, verbose=0)
```
Выход был бы слишком длинным, поэтому я приведу лишь часть вывода. На моей машине получилось 52 файла.
```py
>>> joblib.dump(rf, "rf.clf")
['rf.clf',
'rf.clf_01.npy',
'rf.clf_02.npy',
'rf.clf_03.npy',
'rf.clf_04.npy',
'rf.clf_05.npy',
'rf.clf_06.npy',
...]
```
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )